Post

rust编译proto文件

rust编译proto文件

前言

由于使用rust改写IM逻辑,但是之前大量的数据结构是使用到了protobuf,于是需要将原始的xxx.proto文件改写成xxx.rs

一、所有的proto生成一个文件

我的demo的整体结构

1
2
3
4
5
6
7
8
9
.
├── greeting
│   ├── Cargo.lock
│   ├── Cargo.toml
│   ├── build.rs
│   ├── src
└── protos
    ├── common.proto
    └── user.proto

原始的proto文件 /xxx/xx/testRust/protos/common.proto

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
syntax = "proto3";
option java_package="com.im.domain.pb";
option java_outer_classname="CommonProto";
// protoc.exe  -I=. --java_out=../src common.proto

// 客户端详情
message ClientInfo {
    string     sessionId      = 1; // session
    int32      appVer         = 2; // 应用版本号
    int32      channelId    = 3; // 包ID
    Platform   plat           = 4; // 登陆平台
    int32      language       = 5; // 系统语言
    string     deviceId         = 6; // 设备号
    string     deviceName       = 7; // 设备名称
}

// 平台
enum Platform {
    app_android     = 0; // 安卓手机端
    app_iphone      = 1; // 苹果手机端
    app_hormary     = 2; // 鸿蒙手机端
    pc_win          = 3; // windows的pc端
    pc_mac          = 4; // Mac的pc端
    pc_linux        = 5; // Mac的pc端
    h5_phone        = 6; // Mac的pc端
    h5_pc           = 7; // Mac的pc端
    
}

/xxx/xx/testRust/protos/user.proto

1
2
3
4
5
6
7
8
9
10
syntax = "proto3";
option java_package="com.im.domain.pb";
option java_outer_classname="UserProto";

// 添加这行导入
import "common.proto";

message CreateArchiveReq {
    ClientInfo clientInfo = 1;  // 使用全限定名
}

在rust项目的目录下配置工具可以编译proto文件 /xxx/xx/testRust/greeting/Cargo.toml

1
2
3
4
5
6
7
8
9
10
11
12
13
[package]
name = "xxxx"
version = "0.1.0"
edition = "2024"

[dependencies]

# Protobuf 引入prost生成proto文件
prost = "0.12"
prost-types = "0.12"

[build-dependencies]
prost-build = "0.12"

在rust项目的目录下配置编译文件 /xxx/xx/testRust/greeting/build.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
use std::path::Path;

fn main() -> Result<(), Box<dyn std::error::Error>> {

 
    let mut config = prost_build::Config::new();
    // 配置proto生成后的文件夹
    config.out_dir("src/generated");
    // 没有package xxx; 则会全部生成到 _.rs里去
    config.compile_protos(&[
        "../protos/user.proto",
    ], &["../protos"])?;

    Ok(())
}

执行代码

1
2
3
cd /xxx/xx/testRust/greeting
cargo clean
cargo build

生成的文件# /xxx/xx/testRust/greeting/src/generated/_.rs 最终生成的文件如下

1
2
3
4
5
6
7
8
9
10
11
12
13
.
├── greeting
│   ├── Cargo.lock
│   ├── Cargo.toml
│   ├── build.rs
│   ├── src
│   │   ├── generated
│   │   │   └── _.rs
│   │   └── main.rs
│   └── temp_protos
└── protos
    ├── common.proto
    └── user.proto

/xxx/xx/testRust/greeting/src/generated/_.rs的内容如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
// This file is @generated by prost-build.
/// 客户端详情
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct ClientInfo {
    /// session
    #[prost(string, tag = "1")]
    pub session_id: ::prost::alloc::string::String,
    /// 应用版本号
    #[prost(int32, tag = "2")]
    pub app_ver: i32,
    /// 包ID
    #[prost(int32, tag = "3")]
    pub channel_id: i32,
    /// 登陆平台
    #[prost(enumeration = "Platform", tag = "4")]
    pub plat: i32,
    /// 系统语言
    #[prost(int32, tag = "5")]
    pub language: i32,
    /// 设备号
    #[prost(string, tag = "6")]
    pub device_id: ::prost::alloc::string::String,
    /// 设备名称
    #[prost(string, tag = "7")]
    pub device_name: ::prost::alloc::string::String,
}
/// 平台
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)]
#[repr(i32)]
pub enum Platform {
    /// 安卓手机端
    AppAndroid = 0,
    /// 苹果手机端
    AppIphone = 1,
    /// 鸿蒙手机端
    AppHormary = 2,
    /// windows的pc端
    PcWin = 3,
    /// Mac的pc端
    PcMac = 4,
    /// Mac的pc端
    PcLinux = 5,
    /// Mac的pc端
    H5Phone = 6,
    /// Mac的pc端
    H5Pc = 7,
}
impl Platform {
    /// String value of the enum field names used in the ProtoBuf definition.
    ///
    /// The values are not transformed in any way and thus are considered stable
    /// (if the ProtoBuf definition does not change) and safe for programmatic use.
    pub fn as_str_name(&self) -> &'static str {
        match self {
            Platform::AppAndroid => "app_android",
            Platform::AppIphone => "app_iphone",
            Platform::AppHormary => "app_hormary",
            Platform::PcWin => "pc_win",
            Platform::PcMac => "pc_mac",
            Platform::PcLinux => "pc_linux",
            Platform::H5Phone => "h5_phone",
            Platform::H5Pc => "h5_pc",
        }
    }
    /// Creates an enum from field names used in the ProtoBuf definition.
    pub fn from_str_name(value: &str) -> ::core::option::Option<Self> {
        match value {
            "app_android" => Some(Self::AppAndroid),
            "app_iphone" => Some(Self::AppIphone),
            "app_hormary" => Some(Self::AppHormary),
            "pc_win" => Some(Self::PcWin),
            "pc_mac" => Some(Self::PcMac),
            "pc_linux" => Some(Self::PcLinux),
            "h5_phone" => Some(Self::H5Phone),
            "h5_pc" => Some(Self::H5Pc),
            _ => None,
        }
    }
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct CreateArchiveReq {
    /// 使用全限定名
    #[prost(message, optional, tag = "1")]
    pub client_info: ::core::option::Option<ClientInfo>,
}


二、不同的proto生成不同的文件

1、这个只需要修改原始的proto文件,要保证每个proto文件都要有package xxx; 2、引入的其他文件必须要使用其他文件的package来一如使用

修改如下 我的demo的整体结构

1
2
3
4
5
6
7
8
9
.
├── greeting
│   ├── Cargo.lock
│   ├── Cargo.toml
│   ├── build.rs
│   ├── src
└── protos
    ├── common2.proto
    └── user2.proto

原始的proto文件 /xxx/xx/testRust/protos/common2.proto

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
syntax = "proto3";
package common2; // 要想生成独立的文件必须这样有package
option java_package="com.im.domain.pb";
option java_outer_classname="CommonProto";
// protoc.exe  -I=. --java_out=../src common.proto

// 客户端详情
message ClientInfo {
    string     sessionId      = 1; // session
    int32      appVer         = 2; // 应用版本号
    int32      channelId    = 3; // 包ID
    Platform   plat           = 4; // 登陆平台
    int32      language       = 5; // 系统语言
    string     deviceId         = 6; // 设备号
    string     deviceName       = 7; // 设备名称
}

// 平台
enum Platform {
    app_android     = 0; // 安卓手机端
    app_iphone      = 1; // 苹果手机端
    app_hormary     = 2; // 鸿蒙手机端
    pc_win          = 3; // windows的pc端
    pc_mac          = 4; // Mac的pc端
    pc_linux        = 5; // Mac的pc端
    h5_phone        = 6; // Mac的pc端
    h5_pc           = 7; // Mac的pc端
    
}

/xxx/xx/testRust/protos/user2.proto

1
2
3
4
5
6
7
8
9
10
11
syntax = "proto3";
package user2; // 要想生成独立的文件必须这样有package
option java_package="com.im.domain.pb";
option java_outer_classname="UserProto";

// 添加这行导入
import "common2.proto";

message CreateArchiveReq {
    common2.ClientInfo clientInfo = 1;  // 使用全限定名 由于ClientInfo是common2里一如的,这里必须要带有包名
}

最终生成的文件如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
.
├── greeting
│   ├── Cargo.lock
│   ├── Cargo.toml
│   ├── build.rs
│   ├── src
│   │   ├── generated
│   │   │   ├── common2.rs
│   │   │   └── user2.rs
│   │   └── main.rs
│   └── temp_protos
└── protos
    ├── common2.proto
    └── user2.proto

三、便利文件夹下的所有proto全自动生成

build.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
use std::path::{Path, PathBuf};
use std::fs;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let proto_dir = PathBuf::from("../protos"); // 简化为相对路径
    let out_dir = PathBuf::from("./src/generated");
    
    // 收集所有非web.proto文件
    let mut proto_files = Vec::new();
    for entry in fs::read_dir(&proto_dir)? {
        let entry = entry?;
        let path = entry.path();
        let file_name = path.to_string_lossy(); // 转为字符串
        
        // 简单粗暴的判断:是.proto文件且不包含"aa.proto"
        if file_name.ends_with(".proto") && !file_name.contains("aa.proto") {
            proto_files.push(path);
        }
    }

    // 编译
    prost_build::Config::new()
        .out_dir(out_dir)
        .compile_protos(&proto_files, &[proto_dir])?;

    Ok(())
}

4、即使在不同文件里定义的枚举的内部名称,只要相同就会编译失败

5、使用自动化脚步自动将一般的proto格式改成rust支持的格式

proto_package.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
#!/usr/bin/env python3
import os
import re
import logging
import shutil
from typing import Dict, Set, Tuple, List
from pathlib import Path

# 配置日志
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')

class ProtoProcessor:
    def __init__(self):
        # 获取脚本所在目录的绝对路径
        self.script_dir = Path(os.path.dirname(os.path.abspath(__file__)))
        # 临时目录名称
        self.temp_dir_name = "temp_protos"
        # 临时目录的完整路径
        self.temp_dir = self.script_dir / self.temp_dir_name
        
    def setup_temp_dir(self):
        """设置临时目录,如果存在则先删除"""
        if self.temp_dir.exists():
            logging.info(f"删除已存在的临时目录: {self.temp_dir}")
            shutil.rmtree(self.temp_dir)
        
        logging.info(f"创建临时目录: {self.temp_dir}")
        self.temp_dir.mkdir(parents=True)
        
        # 复制所有.proto文件到临时目录
        for proto_file in self.script_dir.glob("*.proto"):
            if proto_file.is_file():
                shutil.copy2(proto_file, self.temp_dir)
        logging.info("所有.proto文件已复制到临时目录")

    def get_imported_protos(self, content: str) -> List[Tuple[str, str]]:
        """获取所有导入的proto文件名及其路径"""
        imports = re.findall(r'import\s+"([^"]+)"', content)
        return [(os.path.splitext(os.path.basename(imp))[0], imp) for imp in imports]

    def extract_nested_types(self, content: str, parent_type: str = "") -> Set[str]:
        """提取嵌套的message和enum类型定义"""
        nested_types = set()
        # 匹配message或enum定义块
        blocks = re.finditer(r'(message|enum)\s+(\w+)\s*{([^}]*)}', content)
        for block in blocks:
            type_kind, type_name, block_content = block.groups()
            full_type_name = f"{parent_type}.{type_name}" if parent_type else type_name
            nested_types.add(full_type_name)
            # 递归处理嵌套类型
            nested_types.update(self.extract_nested_types(block_content, full_type_name))
        return nested_types

    def build_type_mapping(self) -> Dict[str, Tuple[str, str]]:
        """构建类型到文件的映射字典,包括嵌套类型"""
        type_mapping = {}  # 类型名 -> (包名, 文件名)
        
        # 第一遍:收集所有类型定义
        for proto_file in self.temp_dir.glob("*.proto"):
            if not proto_file.is_file():
                continue
                
            package_name = proto_file.stem
            content = proto_file.read_text(encoding='utf-8')
                
            # 获取所有顶层和嵌套的类型定义
            all_types = self.extract_nested_types(content)
                
            # 添加到映射字典
            for type_name in all_types:
                if type_name in type_mapping:
                    logging.warning(f"类型 {type_name} 在多个文件中定义: {type_mapping[type_name][1]} 和 {proto_file.name}")
                else:
                    type_mapping[type_name] = (package_name, proto_file.name)
        
        logging.info(f"共发现 {len(type_mapping)} 个类型定义")
        return type_mapping

    def ensure_package_declaration(self, content: str, package_name: str) -> str:
        """确保文件有package声明,如果没有则添加"""
        if not re.search(r'^package\s+[\w.]+\s*;', content, re.MULTILINE):
            # 在syntax声明后添加package声明,处理各种可能的注释格式
            content = re.sub(
                r'(syntax\s*=\s*"proto3"\s*;)(\s*(?://[^\n]*\n|\s)*)', 
                r'\1\n\npackage ' + package_name + r';\n\2', 
                content
            )
            logging.info(f"已添加package声明: {package_name}")
        return content

    def process_type_reference(self, match: re.Match, type_mapping: Dict[str, Tuple[str, str]], 
                             package_name: str, local_types: Set[str], imported_packages: Dict[str, str]) -> str:
        """处理类型引用,添加包名前缀"""
        full_match = match.group(0)
        type_name = match.group(1)
        field_part = match.group(2)
        
        # 如果已经有包名前缀,不处理
        if '.' in type_name:
            return full_match
            
        # 如果是基本类型,不处理
        basic_types = {'string', 'int32', 'int64', 'bool', 'bytes', 'double', 'float', 'map', 'repeated'}
        if type_name.lower() in basic_types:
            return full_match
            
        # 如果是本文件定义的类型,不处理
        if type_name in local_types:
            return full_match
            
        # 如果是引入的类型,添加包名前缀
        if type_name in type_mapping:
            ref_package, ref_file = type_mapping[type_name]
            if ref_package != package_name and ref_package in imported_packages:
                # 确保字段名部分不包含包名前缀
                field_match = re.match(r'(\s+)([\w.]+)(\s*=\s*\d+.*)', field_part)
                if field_match:
                    spaces, field_name, field_def = field_match.groups()
                    # 如果字段名包含点号,只取最后一部分
                    clean_field_name = field_name.split('.')[-1]
                    return f"{ref_package}.{type_name}{spaces}{clean_field_name}{field_def}"
                return f"{ref_package}.{type_name}{field_part}"
        
        return full_match

    def process_proto_file(self, file_path: Path, type_mapping: Dict[str, Tuple[str, str]]) -> None:
        """处理单个proto文件"""
        package_name = file_path.stem
        logging.info(f"正在处理文件: {file_path.name}")
        
        content = file_path.read_text(encoding='utf-8')
        
        # 确保有package声明
        content = self.ensure_package_declaration(content, package_name)
        
        # 获取导入的包
        imported_packages = dict(self.get_imported_protos(content))
        if not imported_packages:
            # 如果没有导入,只需要确保有package声明
            file_path.write_text(content, encoding='utf-8')
            return
        
        # 获取当前文件定义的所有类型
        local_types = {name.split('.')[-1] for name in self.extract_nested_types(content)}
        
        # 匹配所有可能的字段定义模式
        patterns = [
            # repeated字段(确保字段名不被当作类型)
            r'(repeated\s+)([\w.]+)(\s+[\w.]+\s*=\s*\d+[^;]*;)',
            # optional字段
            r'(optional\s+)([\w.]+)(\s+[\w.]+\s*=\s*\d+[^;]*;)',
            # map字段的值类型
            r'map<[\w.]+\s*,\s*([\w.]+)>(\s+[\w]+\s*=\s*\d+[^;]*;)',
            # 返回类型
            r'returns\s+\(?([\w.]+)\)?(\s*[{;])',
            # 普通字段(放在最后,避免与其他模式冲突)
            r'(?<![a-zA-Z0-9_.])([\w.]+)(\s+[\w.]+\s*=\s*\d+[^;]*;)',
        ]
        
        modified_content = content
        for pattern in patterns:
            modified_content = re.sub(
                pattern,
                lambda m: self.process_type_reference(m, type_mapping, package_name, local_types, imported_packages),
                modified_content
            )
        
        # 只有在内容有改变时才写回文件
        if modified_content != content:
            file_path.write_text(modified_content, encoding='utf-8')
            logging.info(f"文件 {file_path.name} 已更新")
        else:
            logging.info(f"文件 {file_path.name} 无需更新")

    def process_all_files(self):
        """处理所有proto文件"""
        # 设置临时目录
        self.setup_temp_dir()
        
        # 首先构建类型映射
        logging.info("开始构建类型映射...")
        type_mapping = self.build_type_mapping()
        logging.info(f"类型映射构建完成,共找到 {len(type_mapping)} 个类型定义")
        
        # 处理所有proto文件
        for proto_file in self.temp_dir.glob("*.proto"):
            if proto_file.is_file():
                self.process_proto_file(proto_file, type_mapping)
        
        logging.info(f"所有文件处理完成,结果保存在 {self.temp_dir} 目录中")

def main():
    processor = ProtoProcessor()
    processor.process_all_files()

if __name__ == '__main__':
    main()

# python3 package.py

在rust项目里调用 build.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
use std::{
    fs,
    path::{PathBuf},
    process::Command,
    env,
};

fn list_proto_files(dir: &PathBuf) -> Result<Vec<PathBuf>, Box<dyn std::error::Error>> {
    let mut files = Vec::new();
    if dir.exists() {
        for entry in fs::read_dir(dir)? {
            let path = entry?.path();
            #[allow(clippy::unnecessary_map_or)]
            if path.extension().map_or(false,|ext| ext == "proto") {
                files.push(path);
            }
        }
    }
    Ok(files)
}

/// 生成 mod.rs 文件,导出目录中的所有 .rs 文件
fn generate_mod_rs(out_dir: &PathBuf, excluded: &[&str]) -> Result<(), Box<dyn std::error::Error>> {
    let mut mod_contents = String::new();
    
    // 读取目录中的所有 .rs 文件
    for entry in fs::read_dir(out_dir)? {
        let path = entry?.path();
        #[allow(clippy::unnecessary_map_or)]
        if path.extension().map_or(false, |ext| ext == "rs") {
            // 获取文件名(不包含扩展名)
            if let Some(file_stem) = path.file_stem() {
                let file_name = file_stem.to_string_lossy();
                // 检查是否在排除列表中
                let proto_name = format!("{}.proto", file_name);
                if !excluded.contains(&proto_name.as_str()) && file_name != "mod" {
                    mod_contents.push_str(&format!("pub mod {};\n", file_name));
                }
            }
        }
    }
    
    // 写入 mod.rs 文件
    let mod_path = out_dir.join("mod.rs");
    fs::write(mod_path, mod_contents)?;
    println!("cargo:warning=已生成 mod.rs 文件");
    
    Ok(())
}

/// 生成 protobuf 代码
/// 
/// # 参数
/// * `proto_root` - proto 文件的根目录路径
/// * `out_dir` - 生成的 Rust 代码输出目录
/// * `excluded` - 要排除的 proto 文件列表
fn generate_proto(
    proto_root: &str,
    out_dir: &str,
    excluded: &[&str],
) -> Result<(), Box<dyn std::error::Error>> {
    println!("cargo:warning=开始生成 protobuf 代码");
    
    // 获取当前 cargo manifest 目录(项目根目录)
    let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR")?);
    
    // 转换为绝对路径
    let proto_root = manifest_dir.join(proto_root);
    let out_dir = manifest_dir.join(out_dir);
    let temp_proto_dir = proto_root.join("temp_protos");
    
    println!("cargo:warning=使用绝对路径:");
    println!("cargo:warning=proto_root: {}", proto_root.display());
    println!("cargo:warning=out_dir: {}", out_dir.display());
    println!("cargo:warning=temp_dir: {}", temp_proto_dir.display());
    
    // 1. 清理并重新创建输出目录
    if out_dir.exists() {
        println!("cargo:warning=清理输出目录: {}", out_dir.display());
        fs::remove_dir_all(&out_dir)?;
    }
    fs::create_dir_all(&out_dir)?;

    // 2. 确保临时目录是清理的状态
    if temp_proto_dir.exists() {
        println!("cargo:warning=清理临时目录: {}", temp_proto_dir.display());
        fs::remove_dir_all(&temp_proto_dir)?;
    }

    // 3. 调用 Python 脚本处理 proto 文件
    let script_path = proto_root.join("proto_package.py");
    println!("cargo:warning=执行 Python 脚本: {}", script_path.display());
    let output = Command::new("python3")
        .arg(&script_path)
        .output()?;

    if !output.status.success() {
        let error = String::from_utf8_lossy(&output.stderr);
        println!("cargo:warning=Python 脚本运行错误: {}", error);
        panic!("Python 脚本运行失败");
    }

    // 4. 收集处理后的 proto 文件
    println!("cargo:warning=收集处理后的 proto 文件");
    let proto_files = list_proto_files(&temp_proto_dir)?;
    println!("cargo:warning=找到 {} 个处理后的 proto 文件", proto_files.len());
    
    // 5. 使用 prost 编译所有 proto 文件
    println!("cargo:warning=开始编译 proto 文件");
    let mut config = prost_build::Config::new();
    config.out_dir(&out_dir);
    config.compile_protos(&proto_files, &[temp_proto_dir.clone()])?;

    // 6. 删除不需要的 .rs 文件
    for excluded_file in excluded {
        let rs_file = out_dir.join(excluded_file.replace(".proto", ".rs"));
        if rs_file.exists() {
            println!("cargo:warning=删除排除的文件: {}", rs_file.display());
            fs::remove_file(rs_file)?;
        }
    }

    // 7. 生成 mod.rs 文件
    generate_mod_rs(&out_dir, excluded)?;

    // 8. 清理临时目录
    if temp_proto_dir.exists() {
        println!("cargo:warning=最终清理临时目录: {}", temp_proto_dir.display());
        // fs::remove_dir_all(&temp_proto_dir)?;
    }

    println!("cargo:warning=Proto 生成完成");

    // 明确告诉 Cargo 只在 build.rs 改变时重新运行
    println!("cargo:rerun-if-changed=build.rs");

    Ok(())
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    println!("cargo:warning=构建脚本开始执行");
    
    // 要排除的 proto 文件名列表(不带路径,全部小写)
    let excluded = vec![
        "aa.proto",
        "bb.proto",
    ];

    // 使用相对于 Cargo.toml 的路径
    generate_proto("../protos", "./src/generated", &excluded)?;

    println!("cargo:warning=构建脚本执行完成");
    Ok(())
}


Cargo.toml里的配置

1
2
3
4
5
6
7
8
9
10
11
[package]
name = "greeting"
version = "0.1.0"
edition = "2024"

[dependencies]
prost = "0.12"
prost-types = "0.12"

[build-dependencies]
prost-build = "0.12"

要重新编译运行 cargo clean && cargo build

This post is licensed under CC BY 4.0 by the author.