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   ![test_rlib_crate](./figures/test_rlib_crate.png)
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   ![test_rlib_cargo_crate](./figures/test_rlib_cargo_crate.png)
302
303### 其他源码实例
304build/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)