You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何构建并发布含生成代码的Rust crate(不附带proto文件)

问题描述

概述

我有一个包含protobuf定义的git仓库,希望基于这些protobuf定义创建Python包(此部分已实现)和Rust crate(此部分存在问题),两者均包含生成的代码。

当前问题

Cargo不接受proto目录位于Cargo.toml目录树之外。由于这些proto文件需要与python目录共享,我不想将其移入Cargo.toml目录树,否则会造成混乱。

于是我在构建脚本中检查两个相对父目录以查找proto文件:一个是运行cargo check时的目录(代码当前所在位置),另一个是运行cargo package时的目录(代码会被放在target/内的某个位置)。

这种双目录检查的临时方案在本地可行,但发布crate时会失败,因为生成的代码未被打包。当尝试使用该crate时,构建脚本会再次运行,但此时已找不到proto定义。而我只想发布手写代码和生成的源代码,不想附带proto文件。

疑问

在不附带proto文件、且不将生成代码提交到git的前提下,打包并发布含生成代码的Rust crate的最佳方式是什么?

详情

目录结构

src
├── proto
│   └── helloworld.proto
├── python
│   ├── my-msgs
│   │   ├── generated
│   │   │   ├── <generated code>
│   ├── requirements.txt
│   └── setup.py
└── rust
    └── my-msgs
        ├── build.rs
        ├── Cargo.lock
        ├── Cargo.toml
        └── src
            ├── lib.rs
            └── test
                ├── helloworld.rs
                └── mod.rs

当前构建脚本(build.rs)

use std::fs;
use std::path::{Path, PathBuf};

fn find_proto_files(input_dir: &Path) -> impl Iterator<Item = PathBuf> {
    // given a directory, return the proto files in it
    let dir_read_result = fs::read_dir(input_dir);
    match dir_read_result {
        Ok(it) => it
            .map(|it| it.expect("Unable to read directory entry").path())
            .filter(|it| match it.extension() {
                Some(e) => e == "proto",
                None => false,
            })
            .map(|it| {
                Path::new(&it).to_owned()
            }),
        Err(err) => panic!(
            "Unable to read directory contents of {}: {:?}",
            input_dir.display(),
            err
        ),
    }
}

fn determine_input_dir() -> Option<PathBuf> {
    let candidates = ["../../proto", "../../../../../proto"];
    match candidates.iter().map(Path::new).find(|it| it.is_dir()) {
        Some(it) => Some(it.to_owned()),
        None => {
            eprintln!(
                "Unable to find directory containing proto files. Checked for {:?}",
                candidates
            );
            None
        }
    }
}

fn main() {
    if let Some(input_dir) = determine_input_dir() {
        println!("Reading proto files from {}", input_dir.display());
        protobuf_codegen::Codegen::new()
            .protoc()
            .includes(&[
                input_dir.to_owned(),
                Path::new("/usr/local/include/google/protobuf/").to_owned(),
            ])
            .inputs(find_proto_files(&input_dir))
            .cargo_out_dir("generated")
            .run_from_script();
    }
}

解决方案

核心思路

在打包前预先生成代码,让Cargo将生成的代码包含进发布包,同时让构建脚本在使用已发布crate时跳过代码生成(因为此时无proto文件可用)。

具体步骤

1. 调整构建脚本逻辑

修改build.rs,仅在本地能找到proto文件时才生成代码,否则跳过:

fn main() {
    // 仅在本地开发环境(能找到proto文件)时生成代码
    if let Some(input_dir) = determine_input_dir() {
        println!("Reading proto files from {}", input_dir.display());
        protobuf_codegen::Codegen::new()
            .protoc()
            .includes(&[
                input_dir.to_owned(),
                Path::new("/usr/local/include/google/protobuf/").to_owned(),
            ])
            .inputs(find_proto_files(&input_dir))
            .cargo_out_dir("generated")
            .run_from_script();
    } else {
        // 已发布crate使用阶段,直接使用打包好的生成代码
        println!("No proto directory found, skipping code generation");
    }
}

2. 预先生成代码并配置Cargo包含它

  • 本地执行cargo build,触发代码生成,此时生成的代码会在target/debug/build/<你的crate名>/out/generated目录下。
  • 将生成的代码复制到rust crate的src/generated目录下(创建该目录如果不存在)。
  • 在Cargo.toml中添加include字段,明确指定要打包的文件,确保生成的代码被包含:
include = [
    "src/**/*",
    "build.rs",
    "Cargo.toml"
]

这样cargo package会把src下的所有文件(包括生成的代码)打包进crate。

3. 打包时跳过重新构建

执行cargo package --no-build,Cargo会直接使用已存在的src目录下的代码打包,不会重新运行构建脚本(避免找不到proto文件的问题)。

更灵活的进阶方案

如果不想手动复制代码,可以修改构建脚本,将生成的代码直接输出到src目录下的指定位置,同时在.gitignore中排除该目录:

  • 修改build.rs中的输出路径:
// 替换原来的cargo_out_dir("generated")
.out_dir("src/generated")
  • 在.gitignore中添加:
src/generated/

这样本地开发时构建脚本会直接在src/generated生成代码,打包时Cargo.toml的include会包含这些文件,同时不会被提交到git。

关键注意事项

  • 不要将proto目录添加到Cargo.toml的include中,确保发布的crate不包含proto文件。
  • 测试已打包的crate:可以用cargo install --path .本地安装测试,验证是否能正常使用。

内容的提问来源于stack exchange,提问作者Patrick

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.28 14:12:04