1# Rust模块配置规则和指导 2 3## 概述 4 5Rust是一门静态强类型语言,具有更安全的内存管理、更好的运行性能、原生支持多线程开发等优势。Rust官方也使用Cargo工具来专门为Rust代码创建工程和构建编译。 6OpenHarmony为了集成C/C++代码和提升编译速度,使用了GN + Ninja的编译构建系统。GN的构建语言简洁易读,Ninja的汇编级编译规则直接高效。 7为了在OpenHarmony中集成Rust代码,并最大程度发挥Rust和OpenHarmony中原有C/C++代码的交互性,采用GN作为统一构建工具,即通过GN构建Rust源码文件(xxx.rs),并增加与C/C++互操作、编译时lint、测试、IDL转换、三方库集成、IDE等功能。同时扩展gn框架,支持接口自动化转换,最大程度简化开发。 8 9### 基本概念 10 11| 术语 | 描述 | 12| ----- | ------------------------------------------------------------ | 13| Cargo | Cargo是Rust官方使用的构建工具,允许Rust项目声明其各种依赖项,并确保您始终获得可重复的构建。 | 14| crate | crate是一个独立的可编译单元。 | 15| Lint | Lint是指出常见编程错误、错误、样式错误和可疑结构的工具。可以对程序进行更加广泛的错误分析。 | 16 17 18 19## 配置规则 20OpenHarmony提供了用于Rust代码编译构建的各类型GN模板,可以用于编译Rust可执行文件,动态库和静态库等。各类型模板说明如下: 21 22| GN模板 | 功能 | 输出 | 23|--------------------------|-----------------|-----------------------------------| 24| ohos_rust_executable | rust可执行文件 | rust可执行文件,不带后缀 | 25| ohos_rust_shared_library | rust动态库 | rust dylib动态库,默认后缀.dylib.so | 26| ohos_rust_static_library | rust静态库 | rust rlib静态库,默认后缀.rlib | 27| ohos_rust_proc_macro | rust proc_macro | rust proc_macro库, 默认后缀.so | 28| ohos_rust_shared_ffi | rust FFI动态库 | rust cdylib动态库,给C/C++模块调用,默认后缀.so | 29| ohos_rust_static_ffi | rust FFI静态库 | rust staticlib库,给C/C++模块调用,默认后缀.a | 30| ohos_rust_cargo_crate | 三方包Cargo crate | rust三方crate,支持rlib、dylib、bin | 31| ohos_rust_systemtest | rust系统测试用例 | rust可执行系统测试用例,不带后缀 | 32| ohos_rust_unittest | rust单元测试用例 | rust可执行单元测试用例,不带后缀 | 33| ohos_rust_fuzztest | rust Fuzz测试用例 | rust可执行Fuzz测试用例,不带后缀 | 34 35 36## 配置指导 37配置Rust模块与C/C++模块类似,参考[模块配置规则](subsys-build-module.md)。下面是使用不同模板的示例。 38### 配置Rust静态库示例 39该示例用于测试Rust可执行bin文件和静态库rlib文件的编译,以及可执行文件对静态库的依赖,使用模板ohos_rust_executable和ohos_rust_static_library。操作步骤如下: 40 411. 创建build/rust/tests/test_rlib_crate/src/simple_printer.rs,如下所示: 42 43 ```rust 44 //! simple_printer 45 46 /// struct RustLogMessage 47 48 pub struct RustLogMessage { 49 /// i32: id 50 pub id: i32, 51 /// String: msg 52 pub msg: String, 53 } 54 55 /// function rust_log_rlib 56 pub fn rust_log_rlib(msg: RustLogMessage) { 57 println!("id:{} message:{:?}", msg.id, msg.msg) 58 } 59 ``` 60 612. 创建build/rust/tests/test_rlib_crate/src/main.rs,如下所示: 62 63 ```rust 64 //! rlib_crate example for Rust. 65 66 extern crate simple_printer_rlib; 67 68 use simple_printer_rlib::rust_log_rlib; 69 use simple_printer_rlib::RustLogMessage; 70 71 fn main() { 72 let msg: RustLogMessage = RustLogMessage { 73 id: 0, 74 msg: "string in rlib crate".to_string(), 75 }; 76 rust_log_rlib(msg); 77 } 78 ``` 79 803. 配置gn脚本build/rust/tests/test_rlib_crate/BUILD.gn,如下所示: 81 82 ``` 83 import("//build/ohos.gni") 84 85 ohos_rust_executable("test_rlib_crate") { 86 sources = [ "src/main.rs" ] 87 deps = [ ":simple_printer_rlib" ] 88 } 89 90 ohos_rust_static_library("simple_printer_rlib") { 91 sources = [ "src/simple_printer.rs" ] 92 crate_name = "simple_printer_rlib" 93 crate_type = "rlib" 94 features = [ "std" ] 95 } 96 ``` 97 984. 执行编译得到的可执行文件,运行结果如下: 99 100  101 102### 配置三方库示例 103 104rust三方库的BUILD.gn文件可通过cargo2gn工具自动生成。参见:[Cargo2gn工具操作指导](subsys-build-cargo2gn-guide.md) 105 106该示例用于测试包含预编译文件build.rs的三方静态库rlib文件的编译,使用了模板ohos_rust_executable和ohos_rust_cargo_crate。操作步骤如下: 107 1081. 创建build/rust/tests/test_rlib_cargo_crate/crate/src/lib.rs,如下所示: 109 110 ```rust 111 include!(concat!(env!("OUT_DIR"), "/generated/generated.rs")); 112 113 pub fn say_hello_from_crate() { 114 assert_eq!(run_some_generated_code(), 45); 115 #[cfg(is_new_rustc)] 116 println!("Is new rustc"); 117 #[cfg(is_old_rustc)] 118 println!("Is old rustc"); 119 #[cfg(is_ohos)] 120 println!("Is ohos"); 121 #[cfg(is_mac)] 122 println!("Is darwin"); 123 #[cfg(has_feature_a)] 124 println!("Has feature_a"); 125 #[cfg(not(has_feature_a))] 126 panic!("Wasn't passed feature_a"); 127 #[cfg(not(has_feature_b))] 128 #[cfg(test_a_and_b)] 129 panic!("feature_b wasn't passed"); 130 #[cfg(has_feature_b)] 131 #[cfg(not(test_a_and_b))] 132 panic!("feature_b was passed"); 133 } 134 135 #[cfg(test)] 136 mod tests { 137 /// Test features are passed through from BUILD.gn correctly. This test is the target configuration. 138 #[test] 139 #[cfg(test_a_and_b)] 140 fn test_features_passed_target1() { 141 #[cfg(not(has_feature_a))] 142 panic!("feature a was not passed"); 143 #[cfg(not(has_feature_b))] 144 panic!("feature b was not passed"); 145 } 146 147 #[test] 148 fn test_generated_code_works() { 149 assert_eq!(crate::run_some_generated_code(), 45); 150 } 151 } 152 ``` 153 1542. 创建build/rust/tests/test_rlib_cargo_crate/crate/src/main.rs,如下所示: 155 156 ```rust 157 pub fn main() { 158 test_rlib_crate::say_hello_from_crate(); 159 } 160 ``` 161 1623. 创建build/rust/tests/test_rlib_cargo_crate/crate/build.rs,如下所示: 163 164 ```rust 165 use std::env; 166 use std::path::Path; 167 use std::io::Write; 168 use std::process::Command; 169 use std::str::{self, FromStr}; 170 171 fn main() { 172 println!("cargo:rustc-cfg=build_script_ran"); 173 let my_minor = match rustc_minor_version() { 174 Some(my_minor) => my_minor, 175 None => return, 176 }; 177 178 if my_minor >= 34 { 179 println!("cargo:rustc-cfg=is_new_rustc"); 180 } else { 181 println!("cargo:rustc-cfg=is_old_rustc"); 182 } 183 184 let target = env::var("TARGET").unwrap(); 185 186 if target.contains("ohos") { 187 println!("cargo:rustc-cfg=is_ohos"); 188 } 189 if target.contains("darwin") { 190 println!("cargo:rustc-cfg=is_mac"); 191 } 192 193 let feature_a = env::var_os("CARGO_FEATURE_MY_FEATURE_A").is_some(); 194 if feature_a { 195 println!("cargo:rustc-cfg=has_feature_a"); 196 } 197 let feature_b = env::var_os("CARGO_FEATURE_MY_FEATURE_B").is_some(); 198 if feature_b { 199 println!("cargo:rustc-cfg=has_feature_b"); 200 } 201 202 // Some tests as to whether we're properly emulating various cargo features. 203 assert!(Path::new("build.rs").exists()); 204 assert!(Path::new(&env::var_os("CARGO_MANIFEST_DIR").unwrap()).join("build.rs").exists()); 205 assert!(Path::new(&env::var_os("OUT_DIR").unwrap()).exists()); 206 207 // Confirm the following env var is set 208 env::var_os("CARGO_CFG_TARGET_ARCH").unwrap(); 209 210 generate_some_code().unwrap(); 211 } 212 213 fn generate_some_code() -> std::io::Result<()> { 214 let test_output_dir = Path::new(&env::var_os("OUT_DIR").unwrap()).join("generated"); 215 let _ = std::fs::create_dir_all(&test_output_dir); 216 // Test that environment variables from .gn files are passed to build scripts 217 let preferred_number = env::var("ENV_VAR_FOR_BUILD_SCRIPT").unwrap(); 218 let mut file = std::fs::File::create(test_output_dir.join("generated.rs"))?; 219 write!(file, "fn run_some_generated_code() -> u32 {{ {} }}", preferred_number)?; 220 Ok(()) 221 } 222 223 fn rustc_minor_version() -> Option<u32> { 224 let rustc_bin = match env::var_os("RUSTC") { 225 Some(rustc_bin) => rustc_bin, 226 None => return None, 227 }; 228 229 let output = match Command::new(rustc_bin).arg("--version").output() { 230 Ok(output) => output, 231 Err(_) => return None, 232 }; 233 234 let rustc_version = match str::from_utf8(&output.stdout) { 235 Ok(rustc_version) => rustc_version, 236 Err(_) => return None, 237 }; 238 239 let mut pieces = rustc_version.split('.'); 240 if pieces.next() != Some("rustc 1") { 241 return None; 242 } 243 244 let next_var = match pieces.next() { 245 Some(next_var) => next_var, 246 None => return None, 247 }; 248 249 u32::from_str(next_var).ok() 250 } 251 ``` 252 2534. 配置gn脚本build/rust/tests/test_rlib_cargo_crate/BUILD.gn,如下所示: 254 255 ``` 256 import("//build/templates/rust/ohos_cargo_crate.gni") 257 258 ohos_cargo_crate("target") { 259 crate_name = "test_rlib_crate" 260 crate_root = "crate/src/lib.rs" 261 sources = [ "crate/src/lib.rs" ] 262 263 #To generate the build_script binary 264 build_root = "crate/build.rs" 265 build_sources = [ "crate/build.rs" ] 266 build_script_outputs = [ "generated/generated.rs" ] 267 268 features = [ 269 "my-feature_a", 270 "my-feature_b", 271 "std", 272 ] 273 rustflags = [ 274 "--cfg", 275 "test_a_and_b", 276 ] 277 rustenv = [ "ENV_VAR_FOR_BUILD_SCRIPT=45" ] 278 } 279 280 # Exists to test the case that a single crate has both a library and a binary 281 ohos_cargo_crate("test_rlib_crate_associated_bin") { 282 crate_root = "crate/src/main.rs" 283 crate_type = "bin" 284 sources = [ "crate/src/main.rs" ] 285 286 #To generate the build_script binary 287 build_root = "crate/build.rs" 288 build_sources = [ "crate/build.rs" ] 289 features = [ 290 "my-feature_a", 291 "my-feature_b", 292 "std", 293 ] 294 rustenv = [ "ENV_VAR_FOR_BUILD_SCRIPT=45" ] 295 deps = [ ":target" ] 296 } 297 ``` 298 2995. 执行编译得到的可执行文件,运行结果如下: 300 301  302 303### 其他源码实例 304在build/rust/tests目录下有Rust各类型模块的配置实例可供参考: 305 306| 用例目录 | 测试功能 | 307|----------------------------------------------|----------------------------------------------------------| 308| build/rust/tests/test_bin_crate | 用ohos_rust_executable模板在host平台编译可执行文件,在target平台上运行可执行文件。 | 309| build/rust/tests/test_static_link | 测试可执行文件对标准库的静态链接。 | 310| build/rust/tests/test_dylib_crate | 测试对动态库的编译和动态链接功能 | 311| build/rust/tests/test_rlib_crate | 测试对静态库的编译和静态链接功能 | 312| build/rust/tests/test_proc_macro_crate | 测试对Rust过程宏的编译和链接功能。提供对不同类型的宏的测试用例。 | 313| build/rust/tests/test_cdylib_crate | 测试将Rust代码编译成C/C++动态库。 | 314| build/rust/tests/test_staticlib_crate | 测试将Rust代码编译成C/C++静态库。 | 315| build/rust/tests/rust_test_ut | 测试Rust代码单元测试模板功能(ability)。 | 316| build/rust/tests/rust_test_st | 测试Rust代码系统测试模板功能(ability)。 | 317| build/rust/tests/test_bin_cargo_crate | 测试Rust三方可执行文件的编译和运行。三方源码中包含build.rs。 | 318| build/rust/tests/test_rlib_cargo_crate | 测试Rust三方静态库的编译和静态链接。三方源码中包含build.rs。 | 319| build/rust/tests/test_proc_macro_cargo_crate | 测试Rust三方过程宏的编译和链接。三方源码中包含build.rs。 | 320| build/rust/tests/rust_test_fuzzb | 测试Rust代码Fuzz测试模板功能。 | 321## 参考 322 323### 特性点实例 324 325#### Rust源码依赖调用C/C++库 326OpenHarmony上C/C++模块动态库默认用.z.so后缀,但是Rust的编译命令通过-l链接时,默认只会链接.so后缀的动态库。因此如果要依赖一个C/C++动态库编译模块,需要在该动态库的GN构建文件中添加output_extension = "so"的声明,这样编译得到的动态库将会以".so"作为后缀,而不是".z.so"。 327在Rust源码中如果直接链接动态库,后缀也需要使用".so",这时使用动态库的中间名,不需要添加lib前缀。例如Rust源码中链接libhilog.so: 328 329```rust 330#[link(name = "hilog")] 331``` 332#### externs使用 333某个模块如果依赖二进制的rlib库,可以使用externs属性: 334``` 335executable("foo") { 336 sources = [ "main.rs" ] 337 externs = [{ # 编译时会转成`--extern bar=path/to/bar.rlib` 338 crate_name = "bar" 339 path = "path/to/bar.rlib" 340 }] 341} 342``` 343### Lint规则 344OpenHarmony框架支持rustc lints和clippy lints两种Lint,每种Lint划为三个等级的标准:"openharmony"、"vendor"和"none",严格程度按照"openharmony" -> "vendor" -> "none"逐级递减。 345配置Rust模块时可以通过rustc_lints和clippy_lints来指定使用Lint的等级。 346模块中没有配置rustc_lints或者clippy_lints时会根据模块所在路径来匹配lints等级。不同路径下的Rust代码的语法规范会有不同程度地约束,因此用户在OpenHarmony配置Rust代码编译模块时还应关注模块所在路径。 347 348#### rustc lints和clippy lints的各等级标志 349| **lints类型** | **模块属性** | **lints等级** | **lints等级标志** | **lints内容** | 350| ------------- | ------------ | ------------- | ----------------- | ------------------------------------------------------------ | 351| rustc_lints | rustc_lints | openharmony | RustOhosLints | "-A deprecated", "-D missing-docs", "-D warnigngs" | 352| rustc_lints | rustc_lints | vendor | RustcVendorLints | "-A deprecated", "-D warnigs" | 353| rustc_lints | rustc_lints | none | allowAllLints | "-cap-lints allow" | 354| clippy lints | clippy lints | openharmony | ClippyOhosLints | "-A clippy::type-complexity", "-A clippy::unnecessary-wraps", "-A clippy::unusual-byte-groupings", "-A clippy::upper-case-acronyms" | 355| clippy lints | clippy lints | vendor | ClippyVendorLints | "-A clippy::complexity", "-A Clippy::perf", "-A clippy::style" | 356| clippy lints | clippy lints | none | allowAllLints | "--cap-lints allow" | 357 358#### 代码路径与lints等级的对应关系 359| 路径 | Lints等级 | 360| ---------- | ----------- | 361| thirdparty | none | 362| prebuilts | none | 363| vendor | vendor | 364| device | vendor | 365| others | openharmony | 366 367### [交互工具使用指导](subsys-build-bindgen-cxx-guide.md) 368### [Cargo2gn工具操作指导](subsys-build-cargo2gn-guide.md)