OpenHarmony子系统开发 - Rust编译构建指导
一、Rust模块配置规则和指导
概述
Rust是一门静态强类型语言,具有更安全的内存管理、更好的运行性能、原生支持多线程开发等优势。Rust官方也使用Cargo工具来专门为Rust代码创建工程和构建编译。 OpenHarmony为了集成C/C++代码和提升编译速度,使用了GN + Ninja的编译构建系统。GN的构建语言简洁易读,Ninja的汇编级编译规则直接高效。 为了在OpenHarmony中集成Rust代码,并最大程度发挥Rust和OpenHarmony中原有C/C++代码的交互性,采用GN作为统一构建工具,即通过GN构建Rust源码文件(xxx.rs),并增加与C/C++互操作、编译时lint、测试、IDL转换、三方库集成、IDE等功能。同时扩展gn框架,支持接口自动化转换,最大程度简化开发。
基本概念
术语 | 描述 |
---|---|
Cargo | Cargo是Rust官方使用的构建工具,允许Rust项目声明其各种依赖项,并确保您始终获得可重复的构建。 |
crate | crate是一个独立的可编译单元。 |
Lint | Lint是指出常见编程错误、错误、样式错误和可疑结构的工具。可以对程序进行更加广泛的错误分析。 |
配置规则
OpenHarmony提供了用于Rust代码编译构建的各类型GN模板,可以用于编译Rust可执行文件,动态库和静态库等。各类型模板说明如下:
GN模板 | 功能 | 输出 |
---|---|---|
ohos_rust_executable | rust可执行文件 | rust可执行文件,不带后缀 |
ohos_rust_shared_library | rust动态库 | rust dylib动态库,默认后缀.dylib.so |
ohos_rust_static_library | rust静态库 | rust rlib静态库,默认后缀.rlib |
ohos_rust_proc_macro | rust proc_macro | rust proc_macro库, 默认后缀.so |
ohos_rust_shared_ffi | rust FFI动态库 | rust cdylib动态库,给C/C++模块调用,默认后缀.so |
ohos_rust_static_ffi | rust FFI静态库 | rust staticlib库,给C/C++模块调用,默认后缀.a |
ohos_rust_cargo_crate | 三方包Cargo crate | rust三方crate,支持rlib、dylib、bin |
ohos_rust_systemtest | rust系统测试用例 | rust可执行系统测试用例,不带后缀 |
ohos_rust_unittest | rust单元测试用例 | rust可执行单元测试用例,不带后缀 |
ohos_rust_fuzztest | rust Fuzz测试用例 | rust可执行Fuzz测试用例,不带后缀 |
配置指导
配置Rust模块与C/C++模块类似,参考模块配置规则。下面是使用不同模板的示例。
配置Rust静态库示例
该示例用于测试Rust可执行bin文件和静态库rlib文件的编译,以及可执行文件对静态库的依赖,使用模板ohos_rust_executable和ohos_rust_static_library。操作步骤如下:
创建build/rust/tests/test_rlib_crate/src/simple_printer.rs,如下所示:
//! simple_printer /// struct RustLogMessage pub struct RustLogMessage { /// i32: id pub id: i32, /// String: msg pub msg: String, } /// function rust_log_rlib pub fn rust_log_rlib(msg: RustLogMessage) { println!("id:{} message:{:?}", msg.id, msg.msg) }
创建build/rust/tests/test_rlib_crate/src/main.rs,如下所示:
//! rlib_crate example for Rust. extern crate simple_printer_rlib; use simple_printer_rlib::rust_log_rlib; use simple_printer_rlib::RustLogMessage; fn main() { let msg: RustLogMessage = RustLogMessage { id: 0, msg: "string in rlib crate".to_string(), }; rust_log_rlib(msg); }
配置gn脚本build/rust/tests/test_rlib_crate/BUILD.gn,如下所示:
import("//build/ohos.gni") ohos_rust_executable("test_rlib_crate") { sources = [ "src/main.rs" ] deps = [ ":simple_printer_rlib" ] } ohos_rust_static_library("simple_printer_rlib") { sources = [ "src/simple_printer.rs" ] crate_name = "simple_printer_rlib" crate_type = "rlib" features = [ "std" ] }
执行编译得到的可执行文件,运行结果如下:
配置三方库示例
rust三方库的BUILD.gn文件可通过cargo2gn工具自动生成。参见:Cargo2gn工具操作指导
该示例用于测试包含预编译文件build.rs的三方静态库rlib文件的编译,使用了模板ohos_rust_executable和ohos_rust_cargo_crate。操作步骤如下:
创建build/rust/tests/test_rlib_cargo_crate/crate/src/lib.rs,如下所示:
include!(concat!(env!("OUT_DIR"), "/generated/generated.rs")); pub fn say_hello_from_crate() { assert_eq!(run_some_generated_code(), 45); #[cfg(is_new_rustc)] println!("Is new rustc"); #[cfg(is_old_rustc)] println!("Is old rustc"); #[cfg(is_ohos)] println!("Is ohos"); #[cfg(is_mac)] println!("Is darwin"); #[cfg(has_feature_a)] println!("Has feature_a"); #[cfg(not(has_feature_a))] panic!("Wasn't passed feature_a"); #[cfg(not(has_feature_b))] #[cfg(test_a_and_b)] panic!("feature_b wasn't passed"); #[cfg(has_feature_b)] #[cfg(not(test_a_and_b))] panic!("feature_b was passed"); } #[cfg(test)] mod tests { /// Test features are passed through from BUILD.gn correctly. This test is the target configuration. #[test] #[cfg(test_a_and_b)] fn test_features_passed_target1() { #[cfg(not(has_feature_a))] panic!("feature a was not passed"); #[cfg(not(has_feature_b))] panic!("feature b was not passed"); } #[test] fn test_generated_code_works() { assert_eq!(crate::run_some_generated_code(), 45); } }
创建build/rust/tests/test_rlib_cargo_crate/crate/src/main.rs,如下所示:
pub fn main() { test_rlib_crate::say_hello_from_crate(); }
创建build/rust/tests/test_rlib_cargo_crate/crate/build.rs,如下所示:
use std::env; use std::path::Path; use std::io::Write; use std::process::Command; use std::str::{self, FromStr}; fn main() { println!("cargo:rustc-cfg=build_script_ran"); let my_minor = match rustc_minor_version() { Some(my_minor) => my_minor, None => return, }; if my_minor >= 34 { println!("cargo:rustc-cfg=is_new_rustc"); } else { println!("cargo:rustc-cfg=is_old_rustc"); } let target = env::var("TARGET").unwrap(); if target.contains("ohos") { println!("cargo:rustc-cfg=is_ohos"); } if target.contains("darwin") { println!("cargo:rustc-cfg=is_mac"); } let feature_a = env::var_os("CARGO_FEATURE_MY_FEATURE_A").is_some(); if feature_a { println!("cargo:rustc-cfg=has_feature_a"); } let feature_b = env::var_os("CARGO_FEATURE_MY_FEATURE_B").is_some(); if feature_b { println!("cargo:rustc-cfg=has_feature_b"); } // Some tests as to whether we're properly emulating various cargo features. assert!(Path::new("build.rs").exists()); assert!(Path::new(&env::var_os("CARGO_MANIFEST_DIR").unwrap()).join("build.rs").exists()); assert!(Path::new(&env::var_os("OUT_DIR").unwrap()).exists()); // Confirm the following env var is set env::var_os("CARGO_CFG_TARGET_ARCH").unwrap(); generate_some_code().unwrap(); } fn generate_some_code() -> std::io::Result<()> { let test_output_dir = Path::new(&env::var_os("OUT_DIR").unwrap()).join("generated"); let _ = std::fs::create_dir_all(&test_output_dir); // Test that environment variables from .gn files are passed to build scripts let preferred_number = env::var("ENV_VAR_FOR_BUILD_SCRIPT").unwrap(); let mut file = std::fs::File::create(test_output_dir.join("generated.rs"))?; write!(file, "fn run_some_generated_code() -> u32 {{ {} }}", preferred_number)?; Ok(()) } fn rustc_minor_version() -> Option<u32> { let rustc_bin = match env::var_os("RUSTC") { Some(rustc_bin) => rustc_bin, None => return None, }; let output = match Command::new(rustc_bin).arg("--version").output() { Ok(output) => output, Err(_) => return None, }; let rustc_version = match str::from_utf8(&output.stdout) { Ok(rustc_version) => rustc_version, Err(_) => return None, }; let mut pieces = rustc_version.split('.'); if pieces.next() != Some("rustc 1") { return None; } let next_var = match pieces.next() { Some(next_var) => next_var, None => return None, }; u32::from_str(next_var).ok() }
配置gn脚本build/rust/tests/test_rlib_cargo_crate/BUILD.gn,如下所示:
import("//build/templates/rust/ohos_cargo_crate.gni") ohos_cargo_crate("target") { crate_name = "test_rlib_crate" crate_root = "crate/src/lib.rs" sources = [ "crate/src/lib.rs" ] #To generate the build_script binary build_root = "crate/build.rs" build_sources = [ "crate/build.rs" ] build_script_outputs = [ "generated/generated.rs" ] features = [ "my-feature_a", "my-feature_b", "std", ] rustflags = [ "--cfg", "test_a_and_b", ] rustenv = [ "ENV_VAR_FOR_BUILD_SCRIPT=45" ] } # Exists to test the case that a single crate has both a library and a binary ohos_cargo_crate("test_rlib_crate_associated_bin") { crate_root = "crate/src/main.rs" crate_type = "bin" sources = [ "crate/src/main.rs" ] #To generate the build_script binary build_root = "crate/build.rs" build_sources = [ "crate/build.rs" ] features = [ "my-feature_a", "my-feature_b", "std", ] rustenv = [ "ENV_VAR_FOR_BUILD_SCRIPT=45" ] deps = [ ":target" ] }
执行编译得到的可执行文件,运行结果如下:
其他源码实例
在build/rust/tests目录下有Rust各类型模块的配置实例可供参考:
用例目录 | 测试功能 |
---|---|
build/rust/tests/test_bin_crate | 用ohos_rust_executable模板在host平台编译可执行文件,在target平台上运行可执行文件。 |
build/rust/tests/test_static_link | 测试可执行文件对标准库的静态链接。 |
build/rust/tests/test_dylib_crate | 测试对动态库的编译和动态链接功能 |
build/rust/tests/test_rlib_crate | 测试对静态库的编译和静态链接功能 |
build/rust/tests/test_proc_macro_crate | 测试对Rust过程宏的编译和链接功能。提供对不同类型的宏的测试用例。 |
build/rust/tests/test_cdylib_crate | 测试将Rust代码编译成C/C++动态库。 |
build/rust/tests/test_staticlib_crate | 测试将Rust代码编译成C/C++静态库。 |
build/rust/tests/rust_test_ut | 测试Rust代码单元测试模板功能(ability)。 |
build/rust/tests/rust_test_st | 测试Rust代码系统测试模板功能(ability)。 |
build/rust/tests/test_bin_cargo_crate | 测试Rust三方可执行文件的编译和运行。三方源码中包含build.rs。 |
build/rust/tests/test_rlib_cargo_crate | 测试Rust三方静态库的编译和静态链接。三方源码中包含build.rs。 |
build/rust/tests/test_proc_macro_cargo_crate | 测试Rust三方过程宏的编译和链接。三方源码中包含build.rs。 |
build/rust/tests/rust_test_fuzzb | 测试Rust代码Fuzz测试模板功能。 |
参考
特性点实例
Rust源码依赖调用C/C++库
OpenHarmony上C/C++模块动态库默认用.z.so后缀,但是Rust的编译命令通过-l链接时,默认只会链接.so后缀的动态库。因此如果要依赖一个C/C++动态库编译模块,需要在该动态库的GN构建文件中添加output_extension = "so"的声明,这样编译得到的动态库将会以".so"作为后缀,而不是".z.so"。 在Rust源码中如果直接链接动态库,后缀也需要使用".so",这时使用动态库的中间名,不需要添加lib前缀。例如Rust源码中链接libhilog.so:
#[link(name = "hilog")]
externs使用
某个模块如果依赖二进制的rlib库,可以使用externs属性:
executable("foo") {
sources = [ "main.rs" ]
externs = [{ # 编译时会转成`--extern bar=path/to/bar.rlib`
crate_name = "bar"
path = "path/to/bar.rlib"
}]
}
Lint规则
OpenHarmony框架支持rustc lints和clippy lints两种Lint,每种Lint划为三个等级的标准:"openharmony"、"vendor"和"none",严格程度按照"openharmony" -> "vendor" -> "none"逐级递减。 配置Rust模块时可以通过rustc_lints和clippy_lints来指定使用Lint的等级。 模块中没有配置rustc_lints或者clippy_lints时会根据模块所在路径来匹配lints等级。不同路径下的Rust代码的语法规范会有不同程度地约束,因此用户在OpenHarmony配置Rust代码编译模块时还应关注模块所在路径。
rustc lints和clippy lints的各等级标志
lints类型 | 模块属性 | lints等级 | lints等级标志 | lints内容 |
---|---|---|---|---|
rustc_lints | rustc_lints | openharmony | RustOhosLints | "-A deprecated", "-D missing-docs", "-D warnigngs" |
rustc_lints | rustc_lints | vendor | RustcVendorLints | "-A deprecated", "-D warnigs" |
rustc_lints | rustc_lints | none | allowAllLints | "-cap-lints allow" |
clippy lints | clippy lints | openharmony | ClippyOhosLints | "-A clippy::type-complexity", "-A clippy::unnecessary-wraps", "-A clippy::unusual-byte-groupings", "-A clippy::upper-case-acronyms" |
clippy lints | clippy lints | vendor | ClippyVendorLints | "-A clippy::complexity", "-A Clippy::perf", "-A clippy::style" |
clippy lints | clippy lints | none | allowAllLints | "--cap-lints allow" |
代码路径与lints等级的对应关系
路径 | Lints等级 |
---|---|
thirdparty | none |
prebuilts | none |
vendor | vendor |
device | vendor |
others | openharmony |
交互工具使用指导
Cargo2gn工具操作指导
二、Rust 工具链使用说明
简介
本文用于指导 Rust 语言开发者编译构建 OpenHarmony OS Rust 应用程序。
Rust 是一门静态强类型语言,具有更安全的内存管理、更好的运行性能、原生支持多线程开发等优势。
本工具链基于开源 rust 与 llvm 增量开发,适配了 OpenHarmony OS target 二进制构建。可将 rust 源码编译成能在 OpenHarmony OS 设备上使用的目标二进制。
使用场景
- 在 Linux x86环境本地编译 Linux x86 目标二进制或交叉编译 OpenHarmony OS 目标二进制。
- 在 Mac x86 环境本地编译 Mac x86 目标二进制。
- 在 Mac arm64 环境本地编译 Mac arm64 目标二进制。
操作指导
OpenHarmony 社区代码编译
下载或更新环境中 OpenHarmony 社区代码,下载方式可参考获取源码。
执行源码中脚本下载安装工具链。
./build/prebuilts_download.sh
准备待编译代码。
创建 build/rust/tests/test_bin_crate 目录,目录下新建如下所示文件与文件夹。
├── BUILD.gn └── src └── main.rs
main.rs 代码示例。
//! Hello world example for Rust. fn main() { println!("Hello, world!"); println!(env!("RUSTENV_TEST")); }
BUILD.gn 代码示例。
import("//build/ohos.gni") ohos_rust_executable("test_bin_crate") { sources = [ "src/main.rs" ] rustenv = [ "RUSTENV_TEST=123" ] features = [ "std" ] if (is_mingw) { rust_static_link = true } }
执行编译命令。
./build.sh --product-name {product_name} --build-target
以RK3568为例,若要编译,请执行如下命令。
./build.sh --product-name rk3568 --build-target build/rust/tests/test_bin_crate:test_bin_crate –no-prebuilt-sdk
编译生成的文件。
./out/rk3568/build/build_framework/test_bin_crate
非OpenHarmony 社区代码编译
安装 rust 工具链
下载 build 仓代码。
git clone git@gitee.com:openharmony/build.git
执行脚本下载安装工具链。
./build/prebuilts_download.sh
查看安装情况。
./prebuilts/rustc/linux-x86_64/current/bin/rustc --version
有类似如下显示表示安装成功。
rustc 1.72.0-nightly (xxxx)
安装 OpenHarmony OS Clang 工具
说明
用于在 Linux x86 环境下进行 OpenHarmony OS 的 target 交叉编译,不编译 OpenHarmony OS target 可不安装。
在 OpenHarmony 的最新版本说明中获取 SDK 包下载路径
选择 Linux 环境 SDK 包下载,依次解压下载的压缩包。
mv ohos-sdk-windows_linux-public.tar.gz /opt/ cd /opt/ tar -zxvf ohos-sdk-windows_linux-public.tar.gz cd ohos-sdk/linux unzip native-linux-x64-4.1.7.5-Release.zip
编译代码
代码用例main.rs。
fn main() { println!("hello world"); }
编译 target 为本地环境时命令示例。
./prebuilts/rustc/linux-x86_64/current/bin/rustc main.rs
执行构建结果。
./main hello world
编译 target 为 armv7-unknown-linux-ohos时命令示例。
./prebuilts/rustc/linux-x86_64/current/bin/rustc main.rs --target=armv7-unknown-linux-ohos -C linker=/opt/ohos-sdk/linux/native/llvm/bin/armv7-unknown-linux-ohos-clang
编译 target 为 aarch64-unknown-linux-ohos时命令示例。
./prebuilts/rustc/linux-x86_64/current/bin/rustc main.rs --target=aarch64-unknown-linux-ohos -C linker=/opt/ohos-sdk/linux/native/llvm/bin/aarch64-unknown-linux-ohos-clang
编译 target 为 x86_64-unknown-linux-ohos时命令示例。
./prebuilts/rustc/linux-x86_64/current/bin/rustc main.rs --target=x8
三、Cargo2gn工具操作指导
概述
rust三方库使用cargo编译,配置为Cargo.toml。集成到OpenHarmony上需要转换成BUILD.gn规则。为了满足这个需求,需要提供一个cargo2gn转换工具。当需要引入rust三方crate时使用cargo2gn转换工具来把三方库的Cargo.toml转换成BUILD.gn规则。cargo2gn可以单个库进行转换,也可以多个库进行批量转换。
单个库转换操作步骤
进入到需要转化的rust三方库的目录下,比如需要转化bindgen。
cd openharmony/third_party/rust/bindgen
创建配置文件cargo2gn.json,可以参考如下配置。
{ "copy-out": true, "run": true, "add-workspace": true, "cargo-bin": "/mnt/xxx/openharmony/prebuilts/rustc/linux-x86_64/current/bin" }
执行以下命令进行转换。
python3 /mnt/xxx/openharmony/build/scripts/cargo2gn.py --config cargo2gn.json
转换结果
import("//build/templates/rust/ohos_cargo_crate.gni") ohos_cargo_crate("lib") { crate_name = "bindgen" crate_type = "rlib" crate_root = "./lib.rs" sources = ["./lib.rs"] edition = "2018" cargo_pkg_version = "0.64.0" cargo_pkg_authors = "Jyun-Yan You <jyyou.tw@gmail.com>, Emilio Cobos Álvarez <emilio@crisal.io>, Nick Fitzgerald <fitzgen@gmail.com>, The Servo project developers" cargo_pkg_name = "bindgen" cargo_pkg_description = "Automatically generates Rust FFI bindings to C and C++ libraries." deps = [ "//third_party/rust/bitflags:lib", "//third_party/rust/cexpr:lib", "//third_party/rust/clang-sys:lib", "//third_party/rust/lazy_static:lib", "//third_party/rust/lazycell:lib", "//third_party/rust/log:lib", "//third_party/rust/peeking_take_while:lib", "//third_party/rust/proc-macro2:lib", "//third_party/rust/quote:lib", "//third_party/rust/regex:lib", "//third_party/rust/rustc-hash:lib", "//third_party/rust/shlex:lib", "//third_party/rust/syn:lib", "//third_party/rust/which:lib", ] features = [ "default", "log", "logging", "static", "which", "which-rustfmt", ] build_root = "build.rs" build_sources = ["build.rs"] build_script_outputs = ["host-target.txt"] }
多个库批量转换操作步骤
进入到rust目录下。
cd openharmony/third_party/rust
把所有需要转换的rust三方库添加到rust目录下的Cargo.toml的[workspace]里,如下所示。
[workspace] members = [ "aho-corasick", "memchr", ]
执行单个库转换操作步骤的2和3。