概述
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_liary | rust動態庫 | rust dylib動態庫,默認后綴.dylib.so |
ohos_rust_static_liary | 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++模塊類似,參考[模塊配置規則]。下面是使用不同模板的示例。
開發前請熟悉鴻蒙開發指導文檔:[gitee.com/li-shizhen-skin/harmony-os/blob/master/README.md
]
配置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("http://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("http://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 |
審核編輯 黃宇
-
開發板
+關注
關注
25文章
5032瀏覽量
97372 -
Rust
+關注
關注
1文章
228瀏覽量
6601 -
鴻蒙
+關注
關注
57文章
2339瀏覽量
42805 -
OpenHarmony
+關注
關注
25文章
3713瀏覽量
16254
發布評論請先 登錄
相關推薦
評論