OpenHarmony子系统开发 - Rust编译构建指导

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。操作步骤如下:

  1. 创建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)
    }
  2. 创建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);
    }
  3. 配置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" ]
    }
  4. 执行编译得到的可执行文件,运行结果如下:

配置三方库示例

rust三方库的BUILD.gn文件可通过cargo2gn工具自动生成。参见:Cargo2gn工具操作指导

该示例用于测试包含预编译文件build.rs的三方静态库rlib文件的编译,使用了模板ohos_rust_executable和ohos_rust_cargo_crate。操作步骤如下:

  1. 创建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);
        }
    }
  2. 创建build/rust/tests/test_rlib_cargo_crate/crate/src/main.rs,如下所示:

    复制代码
    pub fn main() {
        test_rlib_crate::say_hello_from_crate();
    }
  3. 创建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()
    }
  4. 配置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" ]
    }
  5. 执行编译得到的可执行文件,运行结果如下:

其他源码实例

在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 社区代码编译

  1. 下载或更新环境中 OpenHarmony 社区代码,下载方式可参考获取源码

  2. 执行源码中脚本下载安装工具链。

    复制代码
    ./build/prebuilts_download.sh
  3. 准备待编译代码。

    创建 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
      }
    }
  4. 执行编译命令。

    复制代码
    ./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 工具链
  1. 下载 build 仓代码。

    复制代码
    git clone [email protected]:openharmony/build.git
  2. 执行脚本下载安装工具链。

    复制代码
    ./build/prebuilts_download.sh
  3. 查看安装情况。

    复制代码
    ./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 可不安装。

  1. 在 OpenHarmony 的最新版本说明中获取 SDK 包下载路径

  2. 选择 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
编译代码
  1. 代码用例main.rs

    复制代码
    fn main() {
      println!("hello world");
    }
  2. 编译 target 为本地环境时命令示例。

    复制代码
    ./prebuilts/rustc/linux-x86_64/current/bin/rustc main.rs

    执行构建结果。

    复制代码
    ./main
    hello world
  3. 编译 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
  4. 编译 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
  5. 编译 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可以单个库进行转换,也可以多个库进行批量转换。

单个库转换操作步骤

  1. 进入到需要转化的rust三方库的目录下,比如需要转化bindgen。

    复制代码
    cd openharmony/third_party/rust/bindgen
  2. 创建配置文件cargo2gn.json,可以参考如下配置。

    复制代码
    {
        "copy-out": true,
        "run": true,
        "add-workspace": true,
        "cargo-bin": "/mnt/xxx/openharmony/prebuilts/rustc/linux-x86_64/current/bin"
    }
  3. 执行以下命令进行转换。

    复制代码
    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 <[email protected]>,  Emilio Cobos Álvarez <[email protected]>,  Nick Fitzgerald <[email protected]>,  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"]
    }

多个库批量转换操作步骤

  1. 进入到rust目录下。

    复制代码
    cd openharmony/third_party/rust
  2. 把所有需要转换的rust三方库添加到rust目录下的Cargo.toml的[workspace]里,如下所示。

    复制代码
    [workspace]
    members = [
        "aho-corasick",
        "memchr",
    ]
  3. 执行单个库转换操作步骤的2和3。

相关推荐
一个处女座的程序猿O(∩_∩)O2 分钟前
鸿蒙Next与API 12深度解析:架构、开发实践与代码示例
华为·架构·harmonyos
nqqcat~3 分钟前
函数的引用/函数的默认参数/函数的占位参数/函数重载
开发语言·c++·算法
Sheakan4 分钟前
【文献阅读】DeepRAG:大语言模型的检索增强推理新范式
人工智能·语言模型·自然语言处理
meisongqing5 分钟前
目前人工智能的发展,判断10年、20年后的人工智能发展的主要方向,或者带动的主要产业
人工智能
李加号pluuuus6 分钟前
【数据集】CelebA Dataset
人工智能·计算机视觉
getapi7 分钟前
cursor全栈网页开发最合适的技术架构和开发语言
开发语言·架构
pen-ai9 分钟前
【NLP】 9. 处理创造性词汇 & 词组特征(Creative Words & Features Model), 词袋模型处理未知词,模型得分
人工智能·深度学习·自然语言处理
uhakadotcom15 分钟前
提升PyODPS性能的实用技巧
后端·面试·github
字节源流16 分钟前
【SpringMVC】入门版
java·后端
daily_233319 分钟前
c++领域展开第十六幕——STL(vector容器的了解以及各种函数的使用)超详细!!!!
开发语言·c++·vector·visual studio code