diff --git a/zh-cn/device-dev/subsystems/Readme-CN.md b/zh-cn/device-dev/subsystems/Readme-CN.md index cef4fc63ae7bb134daabe3a6d01eadb7210a37d9..26f02d5000a01fa6623e9a308e8e103b84b63059 100644 --- a/zh-cn/device-dev/subsystems/Readme-CN.md +++ b/zh-cn/device-dev/subsystems/Readme-CN.md @@ -18,6 +18,10 @@ - [加快本地编译的一些参数](subsys-build-reference.md) - [查看NinjaTrace](subsys-build-reference.md) - [HAP编译构建指导](subsys-build-gn-hap-compilation-guide.md) + - Rust编译构建指导 + - [Rust模块配置规则和指导](subsys-build-rust-compilation.md) + - [交互工具使用指导](subsys-build-bindgen-cxx-guide.md) + - [Cargo2gn工具操作指导](subsys-build-cargo2gn-guide.md) - [ 常见问题](subsys-build-FAQ.md) - [ArkCompiler](subsys-arkcompiler-guide.md) - [分布式远程启动](subsys-remote-start.md) diff --git a/zh-cn/device-dev/subsystems/figures/bindgen_and_cxx_tools.png b/zh-cn/device-dev/subsystems/figures/bindgen_and_cxx_tools.png new file mode 100644 index 0000000000000000000000000000000000000000..637f6cfaaacb832f544a16c8be11c1b29642ac7d Binary files /dev/null and b/zh-cn/device-dev/subsystems/figures/bindgen_and_cxx_tools.png differ diff --git a/zh-cn/device-dev/subsystems/figures/bindgen_test.png b/zh-cn/device-dev/subsystems/figures/bindgen_test.png new file mode 100644 index 0000000000000000000000000000000000000000..4c4d01b567e35fd07ce7a8a90256281cd9fcc165 Binary files /dev/null and b/zh-cn/device-dev/subsystems/figures/bindgen_test.png differ diff --git a/zh-cn/device-dev/subsystems/figures/cpp_call_rust.png b/zh-cn/device-dev/subsystems/figures/cpp_call_rust.png new file mode 100644 index 0000000000000000000000000000000000000000..49503982f893fb6c24d1e41c24ae54aa9681e2c6 Binary files /dev/null and b/zh-cn/device-dev/subsystems/figures/cpp_call_rust.png differ diff --git a/zh-cn/device-dev/subsystems/figures/rust_call_cpp.png b/zh-cn/device-dev/subsystems/figures/rust_call_cpp.png new file mode 100644 index 0000000000000000000000000000000000000000..eba899d0b111c71420b43c36c21e519228a06d54 Binary files /dev/null and b/zh-cn/device-dev/subsystems/figures/rust_call_cpp.png differ diff --git a/zh-cn/device-dev/subsystems/figures/test_rlib_cargo_crate.png b/zh-cn/device-dev/subsystems/figures/test_rlib_cargo_crate.png new file mode 100644 index 0000000000000000000000000000000000000000..86ade8272f625e6aa4336c713d52ee537918531c Binary files /dev/null and b/zh-cn/device-dev/subsystems/figures/test_rlib_cargo_crate.png differ diff --git a/zh-cn/device-dev/subsystems/figures/test_rlib_crate.png b/zh-cn/device-dev/subsystems/figures/test_rlib_crate.png new file mode 100644 index 0000000000000000000000000000000000000000..3df5877b7a3f583513527de1adcdabb80755961a Binary files /dev/null and b/zh-cn/device-dev/subsystems/figures/test_rlib_crate.png differ diff --git a/zh-cn/device-dev/subsystems/subsys-build-bindgen-cxx-guide.md b/zh-cn/device-dev/subsystems/subsys-build-bindgen-cxx-guide.md new file mode 100644 index 0000000000000000000000000000000000000000..21d8a83a6cf10af2162b5dcb637fcdf4eeb9486c --- /dev/null +++ b/zh-cn/device-dev/subsystems/subsys-build-bindgen-cxx-guide.md @@ -0,0 +1,421 @@ +# 交互工具使用指导 + +## 概述 + +Bindgen和CXX工具的主要功能是实现Rust和C/C++之间的交互。其中,Bindgen通过将C接口转换为Rust接口来实现Rust对C的调用,CXX可以通过建立C接口和Rust接口的映射关系来实现C++和Rust的相互调用。 + +![bindgen_and_cxx_tools](./figures/bindgen_and_cxx_tools.png) + +## Bindgen工具使用指导 + +### 操作步骤 +下面是一个使用bindgen实现Rust调用C的示例。 + +1. 在C代码侧,使用头文件lib.h定义两个接口,接口FuncAAddB用来实现两数求和,接口SayHello用来打印字符串。 + + ```c + #ifndef BUILD_RUST_TESTS_BINDGEN_TEST_LIB_H_ + #define BUILD_RUST_TESTS_BINDGEN_TEST_LIB_H_ + #include + #include "build/rust/tests/test_bindgen_test/test_for_hello_world/lib2.h" + + uint32_t FuncAAddB(uint32_t a, uint32_t b); + void SayHello(const char *message); + + #endif // BUILD_RUST_TESTS_BINDGEN_TEST_LIB_H_ + ``` + + +2. 在lib.c中添加对两个接口的对应实现。 + + ```c + #include "build/rust/tests/test_bindgen_test/test_for_hello_world/lib.h" + #include + #include + + void SayHello(const char *message) + { + printf("This is a test for bindgen hello world:\n"); + printf("%s\n", message); + } + + uint32_t FuncAAddB(uint32_t a, uint32_t b) + { + printf("This is a test for bindgen of a + b:\n"); + return a + b; + } + ``` + +3. 添加文件main.rs,就可以在Rust侧通过c_ffi实现对C侧的接口调用。注意Rust侧调用的不安全接口需要使用unsafe封装。 + + ```rust + //! bindgen test for hello world + #![allow(clippy::approx_constant)] + mod c_ffi { + #![allow(dead_code)] + #![allow(non_upper_case_globals)] + #![allow(non_camel_case_types)] + include!(env!("BINDGEN_RS_FILE")); + } + /// pub fn add_two_numbers_in_c + pub fn add_two_numbers_in_c(a: u32, b: u32) -> u32 { + unsafe { c_ffi::FuncAAddB(a, b) } + } + + use std::ffi::c_char; + use std::ffi::CString; + + /// fn main() + fn main() { + println!("{} + {} = {}", 3, 7, add_two_numbers_in_c(3, 7)); + let c_str = CString::new("This is a message from C").unwrap(); + let c_world: *const c_char = c_str.as_ptr() as *const c_char; + unsafe { + c_ffi::SayHello(c_world); + } + } + + ``` + +4. 添加构建文件BUILD.gn,建立Rust模块对C模块的依赖。 + + ```GN + import("//build/ohos.gni") + + ohos_shared_library("c_lib") { + sources = [ "lib.c" ] + defines = [ "COMPONENT_IMPLEMENTATION" ] + } + + rust_bindgen("c_lib_bindgen") { + header = "lib.h" + } + + ohos_rust_executable("bindgen_test") { + deps = [ ":c_lib" ] + deps += [ ":c_lib_bindgen" ] + sources = [ "main.rs" ] + bindgen_output = get_target_outputs(":c_lib_bindgen") + inputs = bindgen_output + rustenv = [ "BINDGEN_RS_FILE=" + rebase_path(bindgen_output[0]) ] + crate_root = "main.rs" + } + ``` + +**调测验证** + +![bindgen_test](./figures/bindgen_test.png) + + +## CXX工具使用指导 + +### C++调用Rust接口 + +1. 在Rust侧文件lib.rs里mod ffi写清楚需要调用的C++接口,并将接口包含在extern "Rust"里面,暴露给C++侧使用。 + + ```rust + //! #[cxx::bridge] + #[cxx::bridge] + mod ffi{ + #![allow(dead_code)] + #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] + struct Shared { + z: usize, + } + extern "Rust"{ + fn print_message_in_rust(); + fn r_return_primitive() -> usize; + fn r_return_shared() -> Shared; + fn r_return_rust_string() -> String; + fn r_return_sum(_: usize, _: usize) -> usize; + } + } + + fn print_message_in_rust(){ + println!("Here is a test for cpp call Rust."); + } + fn r_return_shared() -> ffi::Shared { + println!("Here is a message from Rust,test for ffi::Shared:"); + ffi::Shared { z: 1996 } + } + fn r_return_primitive() -> usize { + println!("Here is a message from Rust,test for usize:"); + 1997 + } + fn r_return_rust_string() -> String { + println!("Here is a message from Rust,test for String"); + "Hello World!".to_owned() + } + fn r_return_sum(n1: usize, n2: usize) -> usize { + println!("Here is a message from Rust,test for {} + {} is:",n1 ,n2); + n1 + n2 + } + + ``` + +2. C++侧将cxx工具转换出来的lib.rs.h包含进来,就可以使用C++侧的接口。 + + ```c++ + #include + #include "build/rust/tests/test_cxx/src/lib.rs.h" + + int main(int argc, const char* argv[]) + { + int a = 2021; + int b = 4; + print_message_in_rust(); + std::cout << r_return_primitive() << std::endl; + std::cout << r_return_shared().z << std::endl; + std::cout << std::string(r_return_rust_string()) << std::endl; + std::cout << r_return_sum(a, b) << std::endl; + return 0; + } + ``` + +3. 添加构建文件BUILD.gn。rust_cxx底层调用CXX工具将lib.rs文件转换成lib.rs.h和lib.rs.cc文件,ohos_rust_static_ffi实现Rust侧源码的编译,ohos_executable实现C++侧代码的编译。 + + ``` + import("//build/ohos.gni") + import("//build/templates/rust/rust_cxx.gni") + + rust_cxx("test_cxx_exe_gen") { + sources = [ "src/lib.rs" ] + } + + ohos_rust_static_ffi("test_cxx_examp_rust") { + sources = [ "src/lib.rs" ] + deps = [ "//build/rust:cxx_rustdeps" ] + } + + ohos_executable("test_cxx_exe") { + sources = [ "main.cpp" ] + sources += get_target_outputs(":test_cxx_exe_gen") + + include_dirs = [ "${target_gen_dir}" ] + deps = [ + ":test_cxx_examp_rust", + ":test_cxx_exe_gen", + "//build/rust:cxx_cppdeps", + ] + } + ``` + +**调测验证** +![cpp_call_rust](./figures/cpp_call_rust.png) + + +### Rust调用C++ + +1. 添加头文件client_blobstore.h。 + + ```c++ + #ifndef BUILD_RUST_TESTS_CLIENT_BLOBSTORE_H + #define BUILD_RUST_TESTS_CLIENT_BLOBSTORE_H + #include + #include "third_party/rust/cxx/include/cxx.h" + + namespace nsp_org { + namespace nsp_blobstore { + struct MultiBufs; + struct Metadata_Blob; + + class client_blobstore { + public: + client_blobstore(); + uint64_t put_buf(MultiBufs &buf) const; + void add_tag(uint64_t blobid, rust::Str add_tag) const; + Metadata_Blob get_metadata(uint64_t blobid) const; + + private: + class impl; + std::shared_ptr impl; + }; + + std::unique_ptr blobstore_client_new(); + } // namespace nsp_blobstore + } // namespace nsp_org + #endif + ``` + +2. 添加cpp文件client_blobstore.cpp。 + + ```c++ + #include + #include + #include + #include + #include + #include "src/main.rs.h" + #include "build/rust/tests/test_cxx_rust/include/client_blobstore.h" + + namespace nsp_org { + namespace nsp_blobstore { + // Toy implementation of an in-memory nsp_blobstore. + // + // In reality the implementation of client_blobstore could be a large complex C++ + // library. + class client_blobstore::impl { + friend client_blobstore; + using Blob = struct { + std::string data; + std::set tags; + }; + std::unordered_map blobs; + }; + + client_blobstore::client_blobstore() : impl(new class client_blobstore::impl) {} + + // Upload a new blob and return a blobid that serves as a handle to the blob. + uint64_t client_blobstore::put_buf(MultiBufs &buf) const + { + std::string contents; + + // Traverse the caller's res_chunk iterator. + // + // In reality there might be sophisticated batching of chunks and/or parallel + // upload implemented by the nsp_blobstore's C++ client. + while (true) { + auto res_chunk = next_chunk(buf); + if (res_chunk.size() == 0) { + break; + } + contents.append(reinterpret_cast(res_chunk.data()), res_chunk.size()); + } + + // Insert into map and provide caller the handle. + auto res = std::hash {} (contents); + impl->blobs[res] = {std::move(contents), {}}; + return res; + } + + // Add add_tag to an existing blob. + void client_blobstore::add_tag(uint64_t blobid, rust::Str add_tag) const + { + impl->blobs[blobid].tags.emplace(add_tag); + } + + // Retrieve get_metadata about a blob. + Metadata_Blob client_blobstore::get_metadata(uint64_t blobid) const + { + Metadata_Blob get_metadata {}; + auto blob = impl->blobs.find(blobid); + if (blob != impl->blobs.end()) { + get_metadata.size = blob->second.data.size(); + std::for_each(blob->second.tags.cbegin(), blob->second.tags.cend(), + [&](auto &t) { get_metadata.tags.emplace_back(t); }); + } + return get_metadata; + } + + std::unique_ptr blobstore_client_new() + { + return std::make_unique(); + } + } // namespace nsp_blobstore + } // namespace nsp_org + + ``` + +3. main.rs文件,在main.rs文件的ffi里面,通过宏include!将头文件client_blobstore.h引入进来,从而在Rust的main函数里面就可以通过ffi的方式调用C++的接口。 + + ```rust + //! test_cxx_rust + #[cxx::bridge(namespace = "nsp_org::nsp_blobstore")] + mod ffi { + // Shared structs with fields visible to both languages. + struct Metadata_Blob { + size: usize, + tags: Vec, + } + + // Rust types and signatures exposed to C++. + extern "Rust" { + type MultiBufs; + + fn next_chunk(buf: &mut MultiBufs) -> &[u8]; + } + + // C++ types and signatures exposed to Rust. + unsafe extern "C++" { + include!("build/rust/tests/test_cxx_rust/include/client_blobstore.h"); + + type client_blobstore; + + fn blobstore_client_new() -> UniquePtr; + fn put_buf(&self, parts: &mut MultiBufs) -> u64; + fn add_tag(&self, blobid: u64, add_tag: &str); + fn get_metadata(&self, blobid: u64) -> Metadata_Blob; + } + } + + // An iterator over contiguous chunks of a discontiguous file object. + // + // Toy implementation uses a Vec> but in reality this might be iterating + // over some more complex Rust data structure like a rope, or maybe loading + // chunks lazily from somewhere. + /// pub struct MultiBufs + pub struct MultiBufs { + chunks: Vec>, + pos: usize, + } + /// pub fn next_chunk + pub fn next_chunk(buf: &mut MultiBufs) -> &[u8] { + let next = buf.chunks.get(buf.pos); + buf.pos += 1; + next.map_or(&[], Vec::as_slice) + } + + /// fn main() + fn main() { + let client = ffi::blobstore_client_new(); + + // Upload a blob. + let chunks = vec![b"fearless".to_vec(), b"concurrency".to_vec()]; + let mut buf = MultiBufs { chunks, pos: 0 }; + let blobid = client.put_buf(&mut buf); + println!("This is a test for Rust call cpp:"); + println!("blobid = {}", blobid); + + // Add a add_tag. + client.add_tag(blobid, "rust"); + + // Read back the tags. + let get_metadata = client.get_metadata(blobid); + println!("tags = {:?}", get_metadata.tags); + } + ``` + +4. 添加构建文件BUILD.gn。使用CXX将main.rs转换成lib.rs.h和lib.rs.cc,同时将产物作为test_cxx_rust_staticlib的源码,编译Rust源码main.rs并将test_cxx_rust_staticlib依赖进来。 + + ``` + import("//build/ohos.gni") + + rust_cxx("test_cxx_rust_gen") { + sources = [ "src/main.rs" ] + } + + ohos_static_library("test_cxx_rust_staticlib") { + sources = [ "src/client_blobstore.cpp" ] + sources += get_target_outputs(":test_cxx_rust_gen") + include_dirs = [ + "${target_gen_dir}", + "//third_party/rust/cxx/v1/crate/include", + "include", + ] + deps = [ + ":test_cxx_rust_gen", + "//build/rust:cxx_cppdeps", + ] + } + + ohos_rust_executable("test_cxx_rust") { + sources = [ "src/main.rs" ] + deps = [ + ":test_cxx_rust_staticlib", + "//build/rust:cxx_rustdeps", + ] + } + ``` + +**调测验证** +![rust_call_cpp](./figures/rust_call_cpp.png) \ No newline at end of file diff --git a/zh-cn/device-dev/subsystems/subsys-build-cargo2gn-guide.md b/zh-cn/device-dev/subsystems/subsys-build-cargo2gn-guide.md new file mode 100644 index 0000000000000000000000000000000000000000..1bd28164b41007f94187a2ab7459bfd79aeec40b --- /dev/null +++ b/zh-cn/device-dev/subsystems/subsys-build-cargo2gn-guide.md @@ -0,0 +1,94 @@ +# 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. 执行以下命令进行转换。 + + ``` + python /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 , Emilio Cobos Álvarez , Nick Fitzgerald , 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。 + diff --git a/zh-cn/device-dev/subsystems/subsys-build-module.md b/zh-cn/device-dev/subsystems/subsys-build-module.md index 80a692a54f836cff2b0bbaaec9969a7e9bad06b9..b5e2555d32cea8a34f7dbf4e691aab6d79d85a2a 100644 --- a/zh-cn/device-dev/subsystems/subsys-build-module.md +++ b/zh-cn/device-dev/subsystems/subsys-build-module.md @@ -23,6 +23,17 @@ ohos_app_scope ohos_js_assets ohos_resources +#rust模板 +ohos_rust_executable +ohos_rust_shared_library +ohos_rust_static_library +ohos_rust_proc_macro +ohos_rust_shared_ffi +ohos_rust_static_ffi +ohos_rust_cargo_crate +ohos_rust_systemtest +ohos_rust_unittest + #其他常用模板 #配置文件 ohos_prebuilt_etc @@ -315,9 +326,11 @@ ohos_prebuilt_static_library("helloworld") { ### Hap模板 -hap模板详见:[ HAP编译构建指导](https://gitee.com/openharmony/docs/blob/master/zh-cn/device-dev/subsystems/subsys-build-gn-hap-compilation-guide.md) +hap模板详见:[ HAP编译构建指导](subsys-build-gn-hap-compilation-guide.md) +### Rust模板 +rust模板详见:[ Rust模块配置规则和指导](subsys-build-rust-compilation.md) ### 其他常用模板 diff --git a/zh-cn/device-dev/subsystems/subsys-build-rust-compilation.md b/zh-cn/device-dev/subsystems/subsys-build-rust-compilation.md new file mode 100644 index 0000000000000000000000000000000000000000..1fc2e56d6d6f306e3ce279b4e1fc460afd3ee577 --- /dev/null +++ b/zh-cn/device-dev/subsystems/subsys-build-rust-compilation.md @@ -0,0 +1,360 @@ +# 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是一个独立的可编译单元。 | +| lints | lints 是指出常见编程错误、错误、样式错误和可疑结构的工具。可以对程序进行更加广泛的错误分析。 | + + + +## 配置规则 +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可执行单元测试用例,不带后缀 | + + + +## 配置指导 +配置Rust模块与C/C++模块类似,参考[模块配置规则](subsys-build-module.md)。下面是使用不同模板的示例。 +### 配置Rust静态库示例 +该示例用于测试Rust可执行bin文件和静态库rlib文件的编译,以及可执行文件对静态库的依赖,使用模板ohos_rust_executable和ohos_rust_static_library。操作步骤如下: + +1. 创建build/rust/tests/test_rlib_crate/src/simple_printer.rs,如下所示: + + ```rust + //! 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,如下所示: + + ```rust + //! 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. 执行编译得到的可执行文件,运行结果如下: + + ![test_rlib_crate](./figures/test_rlib_crate.png) + +### 配置三方库示例 +该示例用于测试包含预编译文件build.rs的三方静态库rlib文件的编译,使用了模板ohos_rust_executable和ohos_rust_cargo_crate。操作步骤如下: + +1. 创建build/rust/tests/test_rlib_cargo_crate/crate/src/lib.rs,如下所示: + + ```rust + 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,如下所示: + + ```rust + pub fn main() { + test_rlib_crate::say_hello_from_crate(); + } + ``` + +3. 创建build/rust/tests/test_rlib_cargo_crate/crate/build.rs,如下所示: + + ```rust + 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 { + 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. 执行编译得到的可执行文件,运行结果如下: + + ![test_rlib_cargo_crate](./figures/test_rlib_cargo_crate.png) + +### 其他源码实例 +在build/rust/tests目录下有Rust各类型模块的配置实例可供参考: +| 用例目录 | 测试功能 | +| -------------------------------------------- | ------------------------------------------------------------ | +| build/rust/tests/test_bin_crate | 测试ohos_rust_executable的host和target编译链接及运行 | +| build/rust/tests/test_static_link | 测试ohos_rust_executable对libstd.rlib进行静态链接 | +| build/rust/tests/test_dylib_crate | 测试ohos_rust_executable对ohos_rust_shared_library的编译依赖和运行 | +| build/rust/tests/test_rlib_crate | 测试ohos_rust_executable对ohos_rust_static_library的编译依赖和运行 | +| build/rust/tests/test_proc_macro_crate | 测试ohos_rust_executable对ohos_rust_proc_macro的编译依赖和运行,对不同类型都有用例覆盖 | +| build/rust/tests/test_cdylib_crate | 测试ohos_rust_executable对ohos_rust_shared_ffi的编译依赖和运行 | +| build/rust/tests/test_staticlib_crate | 测试ohos_rust_executable对ohos_rust_static_ffi的编译依赖和运行 | +| build/rust/tests/test_rust_ut | 测试ohos_rust_unittest,用例代码与特性代码在同一个文件中 | +| build/rust/tests/test_rust_st | 测试ohos_rust_systemtest,用例代码在独立的test目录中 | +| build/rust/tests/test_bin_cargo_crate | 测试ohos_cargo_crate对拥有build.rs预编译的可执行文件编译链接和运行,适用于rust三方crate编译依赖 | +| build/rust/tests/test_rlib_cargo_crate | 测试ohos_cargo_crate对拥有build.rs预编译的静态库文件编译链接和运行,适用于rust三方crate编译依赖 | +| build/rust/tests/test_proc_macro_cargo_crate | 测试ohos_cargo_crate对拥有build.rs预编译的过程宏编译链接和运行,适用于rust三方crate编译依赖 | + +## 参考 + +### 特性点实例 + +#### Rust源码依赖调用C/C++库 +OpenHarmony上C/C++模块动态库默认用.z.so后缀,但是Rust的编译命令通过-l链接时,默认只会链接.so后缀的动态库。因此如果要依赖一个C/C++动态库编译模块,需要在该动态库的GN构建文件中添加output_externsion = "so"的声明,这样编译得到的动态库将会以".so"作为后缀,而不是".z.so"。 +在Rust源码中如果直接链接动态库,后缀也需要使用".so",这时使用动态库的中间名,不需要添加lib前缀。例如Rust源码中链接libhilog.so: +```rust +#[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" + }] +} +``` +### lints规则 +OpenHarmony框架支持rustc lints和clippy lints两种lints,每种lints划为三个等级的标准:"openharmony"、"vendor"和"none",严格程度按照"openharmony" -> "vendor" -> "none"逐级递减。 +配置Rust模块时可以通过rustc_lints和clippy_lints来指定使用lints的等级。 +模块中没有配置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 | +