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 RustLogMessagepub struct RustLogMessage {/// i32: idpub id: i32,/// String: msgpub 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 setenv::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 scriptslet 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 binarybuild_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 binarybuild_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。