提交 3f3bba19 编写于 作者: L liuguangfeng 提交者: lubinglun

Add cargo2gn docs

Issue:I6SJNH
Signed-off-by: Nliuguangfeng <liuguangfeng2@huawei.com>
Change-Id: I11ad7ff3802cd810696d0f7cec6c3ed8d538fdf1
上级 415983dd
......@@ -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)
- [Bindgen、CXX工具使用指导](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)
......
# OpenHarmony rust module configuration rules and guidance
## Configuration rules for various types of rust
Currently, OpenHarmony provides various types of gn templates for compiling and building rust code,include ohos_rust_executable、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.
## Guide for configuring various types of rust
### GN template
To use the rust rule, you need to first import "//build/ohos. gni", which is a shared external interface with C/C++
```
import("//build/ohos.gni")
```
To use the rust rule, you need to first import "//build/test. gni", which is a shared external interface with C/C++
```
import("//build/test.gni")
```
### Example
Currently, 'host x86_64', 'target arm64', and 'target arm' architectures have been supported for rust compilation and construction
#### Example reference
There are configuration examples of various types of rust modules available for reference in the build/rust/tests directory:
| test directory | test function |
| ---- | ---- |
| test_bin_crate | Test the host and target compilation links and running of 'ohos_rust_executable' |
| test_static_link | Testing static link from "ohos_rust_executable" to libstd.rlib |
| test_dylib_crate | Test the compilation dependency and running of 'ohos_rust_executable' on 'ohos_rust_shared_library' |
| test_rlib_crate | Test the compilation dependency and running of 'ohos_rust_executable' on 'ohos_rust_static_library' |
| test_proc_macro_crate | Test the compilation dependency and running of 'ohos_rust_executable' on 'ohos_rust_proc_macro' |
| test_cdylib_crate | Test the compilation dependency and running of 'ohos_rust_executable' on 'ohos_rust_shared_ffi' |
| test_staticlib_crate | Test the compilation dependency and running of 'ohos_rust_executable' on 'ohos_rust_static_ffi' |
| test_rust_ut | Test 'ohos_rust_unittest', with the use case code and feature code in the same file |
| test_rust_st | Test 'ohos_rust_systemtest', with the use case code and feature code in the same file |
| test_bin_cargo_crate | Test executable file which using 'ohos_cargo_crate' |
| test_rlib_cargo_crate | Test static library file which using 'ohos_cargo_crate' |
| test_proc_macro_cargo_crate | Test proc_macro which using 'ohos_cargo_crate' |
#### Example of characteristic points
##### Rust source code relies on calling C/C++ libraries
By default, C/C++ modules in a dynamic library on OH use the .z.so suffix, but when they are depended on by Rust, they are converted to -l links, which will only link dynamic libraries with the .so suffix. Therefore, C/C++ dynamic libraries that are dependent on need to add output_externsion = "so". Similarly, if linking to a dynamic library in Rust source code, the suffix also needs to use ".so", and the middle name of the dynamic library does not need to add the "lib" prefix. For example, linking to libhilog.so:
```
#[link(name = "hilog")]
```
##### externs
If a module relies on a binary rlib library, the externs attribute can be used:
```
executable("foo") {
sources = [ "main.rs" ]
externs = [{ # `--extern bar=path/to/bar.rlib`
crate_name = "bar"
path = "path/to/bar.rlib"
}]
}
```
##### rust-project.json for IDE
```
./build.sh --product-name rk3568 --build-target=build/rust:default --export-rust-project
```
#### Verification
Currently target ohos arm and ohos arm64 architectures are supported. Arm64 simulator compilation startup method:
```
./build.sh --product-name qemu-arm64-linux-min
./vendor/ohemu/qemu_arm64_linux_min/qemu_run.sh -e out/qemu-arm-linux/packages/phone/images/
```
### Lints rules
The OH framework supports two levels of lints: rustc lints and clippy lints, identified by the module attributes rustc_lints and clippy_lints respectively. It also supports three standard levels: "openharmony", "vendor", and "none", with "openharmony" being the strictest and system-configured. When a module has no configuration, the level is determined based on path matching.
#### The various level markers for rustc lints and clippy lints.
| **lints type** | **module attribute** | **lints level** | **lints level flags** | **lints content** |
| ---- | ---- | ---- | ---- | ---- |
| 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" |
#### The correspondence between code paths and lint levels.
| path | Lints level | Note |
| ---- | ---- | ---- |
| thirdparty | none | |
| prebuilts | none | |
| vendor | vendor | |
| device | vendor | |
| others | openharmony |
## Bindgen and CXX tool usage guide
![Bindgen and CXX tools](./figures/bindgen_and_cxx_tools.png)
The main function of the Bindgen and CXX tools is to implement interactions between Rust and C/C++. Bindgen can convert C interfaces into Rust interfaces so that Rust can invoke C interfaces, and CXX can extern C++ and rust by extern methods in ffi. Bindgen's function is unidirectional, while CXX's function is bidirectional.
### Bindgen Tool usage guide
#### Use Bindgen to implement the Rust call C interface example
The header file lib.h defines the C interface. Two C interfaces are defined below, FuncAAddB for summing two numbers and SayHello for printing strings
lib.h
```c
#ifndef BUILD_RUST_TESTS_BINDGEN_TEST_LIB_H_
#define BUILD_RUST_TESTS_BINDGEN_TEST_LIB_H_
#include <stdint.h>
#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_
```
C file lib.c specifically write clearly the interface implementation in the lib.h header file
lib.c
```c
#include "build/rust/tests/test_bindgen_test/test_for_hello_world/lib.h"
#include <stdint.h>
#include <stdio.h>
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;
}
```
The Rust side calls the C interface and adds include to c_ffi. (env! ("BINDGEN_RS_FILE")), in which case you import the.rs file converted from bindgen into Rust, allowing you to use the C interface as c_ffi, noting that unsafe interfaces called by Rust require unsafe wrapping.
```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);
}
}
```
Write BUILD.gn to implement dependency building
ohos_shared_library lib to the c compiler so, rust_bindgen lib. H into the side of the rust. Rs file, ohos_rust_executable implementation code of rust side main. Compilation of rs, At the same time, we need to rely on c_lib and c_lib_bindgen in deps. The purpose of relying on c_lib is to identify the implementation of specific C interface, the purpose of relying on c_lib_bindgen is to convert the.rs as input, and at the same time write the path of the file to rustenv. The rust side is able to recognize the converted.rs file.
```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"
}
```
Compile verification
![bindgen_test](./figures/bindgen_test.png)
#### Validation of use cases on bindgen website
Header file of the official website
[rust-bindgen/bindgen-tests/tests/headers at main · rust-lang/rust-bindgen · GitHub](https://github.com/rust-lang/rust-bindgen/tree/main/bindgen-tests/tests/headers)
The conversion file of the official website, the conversion file and the header file are one-to-one correspondence
[rust-bindgen/bindgen-tests/tests/expectations/tests at main · rust-lang/rust-bindgen · GitHub](https://github.com/rust-lang/rust-bindgen/tree/main/bindgen-tests/tests/expectations/tests)
Bindgen is mainly used to implement C interface conversion. When the C++ interface needs to be converted, the.h file can be written as.hpp file. Of course, the clang parameter -x C++ can also be passed to clang, so that bindgen can detect the need to convert C++ related files. For details about which C++ features bindgen supports, see:
[Generating Bindings to C++ - The `bindgen` User Guide (rust-lang.github.io)](https://rust-lang.github.io/rust-bindgen/cpp.html)
- Bindgen website C conversion example
lib.h
```c
#ifndef BUILD_RUST_TESTS_BINDGEN_TEST_LIB_H_
#define BUILD_RUST_TESTS_BINDGEN_TEST_LIB_H_
// A few tests for enum-related issues that should be tested with all the enum
// representations.
enum Foo1 {
BAR = 0,
QUX
};
struct Foo2 {
enum {
FOOFIRST,
FOOSECOND,
} member;
};
/** <div rustbindgen nodebug></div> */
enum NoDebug {
NODEBUG1,
NODEBUG2,
};
/** <div rustbindgen derive="Debug"></div> */
enum Debug {
DEBUG1,
DEBUG2,
};
enum Neg {
MINUSONE = -1,
ONE = 1,
};
#endif // BUILD_RUST_TESTS_BINDGEN_TEST_LIB_H_
```
main.rs
```rust
//! bindgen test for .h file
#![allow(clippy::approx_constant)]
#![allow(clippy::eq_op)]
mod c_ffi {
#![allow(dead_code)]
#![allow(non_upper_case_globals)]
#![allow(non_camel_case_types)]
include!(env!("BINDGEN_RS_FILE"));
}
fn bindgen_test_layout_foo() {
const UNINIT: ::std::mem::MaybeUninit<c_ffi::Foo2> = ::std::mem::MaybeUninit::uninit();
let ptr = UNINIT.as_ptr();
println!(
"The mem size of c_ffi::Foo2 is {} usize",
::std::mem::size_of::<c_ffi::Foo2>()
);
println!(
"The align_of size of c_ffi::Foo2 is {} usize",
::std::mem::align_of::<c_ffi::Foo2>()
);
println!(
"The ptr addr of!((*ptr).member) as usize - ptr as usize is {} usize",
unsafe { ::std::ptr::addr_of!((*ptr).member) as usize - ptr as usize }
);
}
impl Default for c_ffi::Foo2 {
fn default() -> Self {
let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
unsafe {
::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
s.assume_init()
}
}
}
/// fn main()
fn main() {
bindgen_test_layout_foo();
}
```
BUILD.gn
```
import("//build/ohos.gni")
import("//build/templates/rust/rust_bindgen.gni")
import("//build/templates/rust/rust_template.gni")
rust_bindgen("c_lib_bindgen_h") {
header = "lib.h"
}
ohos_rust_executable("bindgen_test_for_h") {
deps = [ ":c_lib_bindgen_h" ]
sources = [ "main.rs" ]
bindgen_output = get_target_outputs(":c_lib_bindgen_h")
inputs = bindgen_output
rustenv = [ "BINDGEN_RS_FILE=" + rebase_path(bindgen_output[0]) ]
crate_root = "main.rs"
}
```
- Bindgen official website conversion C++ example
lib.h
```c++
#ifndef BUILD_RUST_TESTS_BINDGEN_TEST_LIB_H_
#define BUILD_RUST_TESTS_BINDGEN_TEST_LIB_H_
// bindgen-flags: --with-derive-hash --with-derive-partialeq --with-derive-eq
typedef int SecondInt;
class C {
public:
typedef int FirstInt;
typedef const char* Lookup;
FirstInt c;
FirstInt* ptr;
FirstInt arr[10];
SecondInt d;
SecondInt* other_ptr;
void method(FirstInt c);
void methodRef(FirstInt& c);
void complexMethodRef(Lookup& c);
void anotherMethod(SecondInt c);
};
class D : public C {
public:
FirstInt* ptr;
};
#endif // BUILD_RUST_TESTS_BINDGEN_TEST_LIB_H_
```
main.rs
```rust
//! bindgen test for hpp
#![allow(clippy::approx_constant)]
#![allow(non_snake_case)]
mod c_ffi {
#![allow(dead_code)]
#![allow(non_upper_case_globals)]
#![allow(non_camel_case_types)]
include!(env!("BINDGEN_RS_FILE"));
}
fn bindgen_test_layout_C() {
const UNINIT: ::std::mem::MaybeUninit<c_ffi::C> =
::std::mem::MaybeUninit::uninit();
let ptr = UNINIT.as_ptr();
println!(
"The mem size of c_ffi::C is {} usize",
::std::mem::size_of::<c_ffi::C>()
);
println!(
"The align_of size of c_ffi::C is {} usize",
::std::mem::align_of::<c_ffi::C>()
);
println!(
"The addr_of!((*ptr).c) as usize - ptr as usize is {} usize",
unsafe { ::std::ptr::addr_of!((*ptr).c) as usize - ptr as usize }
);
println!(
"The addr_of!((*ptr).ptr) as usize - ptr as usize is {} usize",
unsafe { ::std::ptr::addr_of!((*ptr).ptr) as usize - ptr as usize }
);
println!(
"The addr_of!((*ptr).arr) as usize - ptr as usize is {} usize",
unsafe { ::std::ptr::addr_of!((*ptr).arr) as usize - ptr as usize }
);
println!(
"The addr_of!((*ptr).d) as usize - ptr as usize is {} usize",
unsafe { ::std::ptr::addr_of!((*ptr).d) as usize - ptr as usize }
);
println!(
"The addr_of!((*ptr).other_ptr) as usize - ptr as usize is {} usize",
unsafe {
::std::ptr::addr_of!((*ptr).other_ptr) as usize - ptr as usize
}
);
}
fn bindgen_test_layout_D() {
const UNINIT: ::std::mem::MaybeUninit<c_ffi::D> =
::std::mem::MaybeUninit::uninit();
let ptr = UNINIT.as_ptr();
println!(
"The mem size of c_ffi::D is {} usize",
::std::mem::size_of::<c_ffi::D>()
);
println!(
"The align_of size of c_ffi::D is {} usize",
::std::mem::align_of::<c_ffi::D>()
);
println!(
"The addr_of!((*ptr).ptr) as usize - ptr as usize is {} usize",
unsafe { ::std::ptr::addr_of!((*ptr).ptr) as usize - ptr as usize }
);
}
impl Default for c_ffi::D {
fn default() -> Self {
let mut r = ::std::mem::MaybeUninit::<Self>::uninit();
unsafe {
::std::ptr::write_bytes(r.as_mut_ptr(), 0, 1);
r.assume_init()
}
}
}
/// fn main()
fn main() {
bindgen_test_layout_C();
bindgen_test_layout_D()
}
```
BUILD.gn
```
import("//build/ohos.gni")
rust_bindgen("c_lib_bindgen_hpp") {
header = "lib.h"
enable_c_plus_plus = true
}
ohos_rust_executable("bindgen_test_hpp") {
deps = [ ":c_lib_bindgen_hpp" ]
sources = [ "main.rs" ]
bindgen_output = get_target_outputs(":c_lib_bindgen_hpp")
inputs = bindgen_output
rustenv = [ "BINDGEN_RS_FILE=" + rebase_path(bindgen_output[0]) ]
crate_root = "main.rs"
}
```
#### Implement the Rust call C interface using a extern C approach
test_extern_c.rs
```rust
//! test for extern "C"
#![allow(clippy::approx_constant)]
use std::os::raw::c_double;
use std::os::raw::c_int;
extern "C" {
fn abs(num: c_int) -> c_int;
fn sqrt(num: c_double) -> c_double;
fn pow(num: c_double, power: c_double) -> c_double;
}
/// fn main()
fn main() {
let x: i32 = -123;
println!("This is an example of calling a C library function from Rust:");
println!("The absolute value of {x} is: {}.", unsafe { abs(x) });
let n: f64 = 9.0;
let p: f64 = 3.0;
println!("The {n}th power of {p} is: {}.", unsafe { pow(n, p) });
let mut y: f64 = 64.0;
println!("The square root of {y} is: {}.", unsafe { sqrt(y) });
y = -3.14;
println!("The square root of {y} is: {}.", unsafe { sqrt(y) });
}
```
BUILD.gn
```
import("//build/ohos.gni")
ohos_rust_executable("test_extern_c") {
sources = [ "test_extern_c.rs" ]
}
```
### CXX tool usage guide
#### C++ calls the Rust interface
In the Rust side file lib.rs, mod ffi specifies the C++ interfaces that need to be invoked and extern them with extern "Rust" to expose them to 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
}
```
C++ side will be converted out of the cxx tool lib.rs.h included, you can use the C++ side interface
```c++
#include <iostream>
#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;
}
```
BUILD.gn Builds dependencies
rust_cxx low-level calls CXX tool to lib. Rs file into the lib. Rs. H and lib. Rs. Cc file, ohos_rust_static_ffi compilation of source code, realize the Rust side ohos_executable implementation side of c + + code to compile
```
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",
]
}
```
ohos_rust_static_ffi depend on CXX rlib
```
group("cxx_rustdeps") {
public_deps = [ "//third_party/rust/cxx:lib" ]
}
```
ohos_executable depend on CXX side CXX. H and CXX. Cc file
```
static_library("cxx_cppdeps") {
defines = [ "RUST_CXX_NO_EXCEPTIONS" ]
sources = [
"//third_party/rust/cxx/include/cxx.h",
"//third_party/rust/cxx/src/cxx.cc",
]
deps = [ ":cxx_rustdeps" ]
if (is_win) {
defines += [ "CXX_RS_EXPORT=__declspec(dllexport)" ]
} else {
defines += [ "CXX_RS_EXPORT=__attribute__((visibility(\"default\")))" ]
}
}
```
It also depends on the test_cxx_exe_gen and test_cxx_examp_rust files
Compile verification
![cpp_call_rust](./figures/cpp_call_rust.png)
#### Implementation of Rust calls C++
The header file client_blobstore.h
```c++
#ifndef BUILD_RUST_TESTS_CLIENT_BLOBSTORE_H
#define BUILD_RUST_TESTS_CLIENT_BLOBSTORE_H
#include <memory>
#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> impl;
};
std::unique_ptr<client_blobstore> blobstore_client_new();
} // namespace nsp_blobstore
} // namespace nsp_org
#endif
```
The cpp file client_blobstore.cpp
```c++
#include <algorithm>
#include <functional>
#include <set>
#include <string>
#include <unordered_map>
#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<std::string> tags;
};
std::unordered_map<uint64_t, Blob> 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<const char *>(res_chunk.data()), res_chunk.size());
}
// Insert into map and provide caller the handle.
auto res = std::hash<std::string> {} (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<client_blobstore> blobstore_client_new()
{
return std::make_unique<client_blobstore>();
}
} // namespace nsp_blobstore
} // namespace nsp_org
```
rs file, in the main.rs file inside the ffi, through the macro include! The header client_blobstore.h is introduced so that C++ interfaces can be invoked in the rust main function via ffi
```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<String>,
}
// 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<client_blobstore>;
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<Vec<u8>> 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<Vec<u8>>,
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);
}
```
BUILD.gn specifies the dependencies
Use CXX to convert main.rs to lib.rs.h and lib.rs.cc, and use the product as the source for test_cxx_rust_staticlib. Compile the rust source main.rs and rely on 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",
]
}
```
Compile verification
![rust_call_cpp](./figures/rust_call_cpp.png)
# 交互工具使用指导
## 概述
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工具使用指导
### Rust调用C示例
下面是一个使用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 <stdint.h>
#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 <stdint.h>
#include <stdio.h>
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. 在Rust侧文件main.rs的c_ffi中添加"include!(env!("BINDGEN_RS_FILE"))"",将bindgen转换出来的.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)
### 参考文档
更多用例参考:
[rust-bindgen/bindgen-tests/tests/headers at main · rust-lang/rust-bindgen · GitHub](https://github.com/rust-lang/rust-bindgen/tree/main/bindgen-tests/tests/headers)
[rust-bindgen/bindgen-tests/tests/expectations/tests at main · rust-lang/rust-bindgen · GitHub](https://github.com/rust-lang/rust-bindgen/tree/main/bindgen-tests/tests/expectations/tests)
[Generating Bindings to C++ - The `bindgen` User Guide (rust-lang.github.io)](https://rust-lang.github.io/rust-bindgen/cpp.html)
## 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 <iostream>
#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 <memory>
#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> impl;
};
std::unique_ptr<client_blobstore> blobstore_client_new();
} // namespace nsp_blobstore
} // namespace nsp_org
#endif
```
2. 添加cpp文件client_blobstore.cpp。
```c++
#include <algorithm>
#include <functional>
#include <set>
#include <string>
#include <unordered_map>
#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<std::string> tags;
};
std::unordered_map<uint64_t, Blob> 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<const char *>(res_chunk.data()), res_chunk.size());
}
// Insert into map and provide caller the handle.
auto res = std::hash<std::string> {} (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<client_blobstore> blobstore_client_new()
{
return std::make_unique<client_blobstore>();
}
} // 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<String>,
}
// 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<client_blobstore>;
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<Vec<u8>> 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<Vec<u8>>,
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
# 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 <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"]
}
```
## 多个库批量转换操作步骤
1. 进入到rust目录下。
```
cd openharmony/third_party/rust
```
2. 把所有需要转换的rust三方库添加到rust目录下的Cargo.toml的[workspace]里,如下所示。
```
[workspace]
members = [
"aho-corasick",
"memchr",
]
```
3. 执行单个库转换操作步骤的2和3。
......@@ -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
......@@ -317,7 +328,9 @@ ohos_prebuilt_static_library("helloworld") {
hap模板详见:[ HAP编译构建指导](https://gitee.com/openharmony/docs/blob/master/zh-cn/device-dev/subsystems/subsys-build-gn-hap-compilation-guide.md)
### Rust模板
rust模板详见:[ Rust模块配置规则和指导](https://gitee.com/openharmony/docs/blob/master/zh-cn/device-dev/subsystems/subsys-build-rust-compilation.md)
### 其他常用模板
......
# OpenHarmony rust模块配置规则和指导
## rust各类型模块配置规则
当前OpenHarmony提供了用于rust代码编译构建的各类型gn模板。包含ohos_rust_executable、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.
## rust各类型模块配置指导
### 模板gni文件
使用rust规则需要先import导入"//build/ohos.gni",与C/C++共用的对外接口
```
import("//build/ohos.gni")
```
使用rust测试用例规则需要先import导入"//build/test.gni",与C/C++共用的对外接口
```
import("//build/test.gni")
```
### 实例参考
当前已经支持host x86_64和target arm64和target arm架构的rust相关编译构建
#### 源码实例
在build/rust/tests目录下有rust各类型模块的配置实例可供参考:
| 用例目录 | 测试功能 |
| ---- | ---- |
| test_bin_crate | 测试ohos_rust_executable的host和target编译链接及运行 |
| test_static_link | 测试ohos_rust_executable对libstd.rlib进行静态链接 |
| test_dylib_crate | 测试ohos_rust_executable对ohos_rust_shared_library的编译依赖和运行 |
| test_rlib_crate | 测试ohos_rust_executable对ohos_rust_static_library的编译依赖和运行 |
| test_proc_macro_crate | 测试ohos_rust_executable对ohos_rust_proc_macro的编译依赖和运行,对不同类型都有用例覆盖 |
| test_cdylib_crate | 测试ohos_rust_executable对ohos_rust_shared_ffi的编译依赖和运行 |
| test_staticlib_crate | 测试ohos_rust_executable对ohos_rust_static_ffi的编译依赖和运行 |
| test_rust_ut | 测试ohos_rust_unittest,用例代码与特性代码在同一个文件中 |
| test_rust_st | 测试ohos_rust_systemtest,用例代码在独立的test目录中 |
| test_bin_cargo_crate | 测试ohos_cargo_crate对拥有build.rs预编译的可执行文件编译链接和运行,适用于rust三方crate编译依赖 |
| test_rlib_cargo_crate | 测试ohos_cargo_crate对拥有build.rs预编译的静态库文件编译链接和运行,适用于rust三方crate编译依赖 |
| test_proc_macro_cargo_crate | 测试ohos_cargo_crate对拥有build.rs预编译的过程宏编译链接和运行,适用于rust三方crate编译依赖 |
#### 特性点实例
##### rust源码依赖调用C/C++库
OH上C/C++模块动态库默认用.z.so后缀,但是被rust依赖部分会转成-l链接,默认只会链接.so后缀的动态库。因此被依赖的C/C++动态库要加上output_externsion = "so"。同理,在rust源码中如果直接链接动态库,后缀也需要使用".so",使用动态库的中间名,不需要添加lib前缀,例如链接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"
}]
}
```
##### IDE的rust-project.json编译
```
./build.sh --product-name rk3568 --build-target=build/rust:default --export-rust-project
```
#### 运行验证
当前支持target ohos arm和ohos arm64架构。arm64模拟器编译启动方法:
```
./build.sh --product-name qemu-arm64-linux-min
./vendor/ohemu/qemu_arm64_linux_min/qemu_run.sh -e out/qemu-arm-linux/packages/phone/images/
```
### lints规则
OH框架支持rustc lints和clippy lints两级lints,分别由模块属性rustc_lints和clippy_lints标识;同时支持三个等级的标准:"openharmony"、"vendor"和"none","openharmony"是系统配置,最为严格。模块无配置时按照路径匹配等级。
#### 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 |
## Bindgen、CXX工具使用指导
![Bindgen和CXX工具](./figures/bindgen_and_cxx_tools.png "屏幕截图")
Bindgen和CXX工具的主要功能是为了实现Rust和C/C++之间的交互,其中,Bindgen能够实现将C接口转换成Rust接口从而实现Rust调用C接口,CXX可以通过在ffi里extern的方式实现实现C++和rust的互相调用。Bindgen的功能属于单向的,CXX的功能是双向的。
### Bindgen工具使用指导
#### 使用Bindgen实现Rust调用C接口例子
头文件lib.h定义C接口,下面定义了两个C接口,FuncAAddB用来实现两数求和,SayHello用来打印字符串
lib.h
```c
#ifndef BUILD_RUST_TESTS_BINDGEN_TEST_LIB_H_
#define BUILD_RUST_TESTS_BINDGEN_TEST_LIB_H_
#include <stdint.h>
#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_
```
C文件lib.c具体写清楚了lib.h头文件中的接口实现
lib.c
```c
#include "build/rust/tests/test_bindgen_test/test_for_hello_world/lib.h"
#include <stdint.h>
#include <stdio.h>
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;
}
```
Rust侧调用C接口,在c_ffi里面加上include!(env!("BINDGEN_RS_FILE")),这样就能将bindgen转换出来的.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);
}
}
```
编写BUILD.gn实现依赖关系构建
ohos_shared_library将lib.c编译成so,rust_bindgen将lib.h转换成rust侧的.rs文件,ohos_rust_executable实现rust侧代码main.rs的编译,同时在deps里面需要将c_lib和c_lib_bindgen两个依赖进来,依赖c_lib的目的是为了识别到具体的C接口的实现,依赖c_lib_bindgen的目的是为了将转换出来的.rs作为输入,同时将文件的路径写到rustenv,从而rust侧能够识别到转换出来的.rs文件。
```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 "屏幕截图")
#### bindgen官网用例的验证
官网的头文件
[rust-bindgen/bindgen-tests/tests/headers at main · rust-lang/rust-bindgen · GitHub](https://github.com/rust-lang/rust-bindgen/tree/main/bindgen-tests/tests/headers)
官网的转换文件,转换文件和头文件是一一对应的关系
[rust-bindgen/bindgen-tests/tests/expectations/tests at main · rust-lang/rust-bindgen · GitHub](https://github.com/rust-lang/rust-bindgen/tree/main/bindgen-tests/tests/expectations/tests)
bindgen主要用来实现C接口的转换,当需要转换C++接口的时候,可以将.h文件写成.hpp文件,当然,也可以将clang的参数-x c++传递给clang,从而bindgen能够检测出需要转换C++相关文件。具体bindgen能够支持哪些C++特性的转换可参考:
[Generating Bindings to C++ - The `bindgen` User Guide (rust-lang.github.io)](https://rust-lang.github.io/rust-bindgen/cpp.html)
- bindgen官网C转换例子
lib.h
```c
#ifndef BUILD_RUST_TESTS_BINDGEN_TEST_LIB_H_
#define BUILD_RUST_TESTS_BINDGEN_TEST_LIB_H_
// A few tests for enum-related issues that should be tested with all the enum
// representations.
enum Foo1 {
BAR = 0,
QUX
};
struct Foo2 {
enum {
FOOFIRST,
FOOSECOND,
} member;
};
/** <div rustbindgen nodebug></div> */
enum NoDebug {
NODEBUG1,
NODEBUG2,
};
/** <div rustbindgen derive="Debug"></div> */
enum Debug {
DEBUG1,
DEBUG2,
};
enum Neg {
MINUSONE = -1,
ONE = 1,
};
#endif // BUILD_RUST_TESTS_BINDGEN_TEST_LIB_H_
```
main.rs
```rust
//! bindgen test for .h file
#![allow(clippy::approx_constant)]
#![allow(clippy::eq_op)]
mod c_ffi {
#![allow(dead_code)]
#![allow(non_upper_case_globals)]
#![allow(non_camel_case_types)]
include!(env!("BINDGEN_RS_FILE"));
}
fn bindgen_test_layout_foo() {
const UNINIT: ::std::mem::MaybeUninit<c_ffi::Foo2> = ::std::mem::MaybeUninit::uninit();
let ptr = UNINIT.as_ptr();
println!(
"The mem size of c_ffi::Foo2 is {} usize",
::std::mem::size_of::<c_ffi::Foo2>()
);
println!(
"The align_of size of c_ffi::Foo2 is {} usize",
::std::mem::align_of::<c_ffi::Foo2>()
);
println!(
"The ptr addr of!((*ptr).member) as usize - ptr as usize is {} usize",
unsafe { ::std::ptr::addr_of!((*ptr).member) as usize - ptr as usize }
);
}
impl Default for c_ffi::Foo2 {
fn default() -> Self {
let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
unsafe {
::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
s.assume_init()
}
}
}
/// fn main()
fn main() {
bindgen_test_layout_foo();
}
```
BUILD.gn
```
import("//build/ohos.gni")
import("//build/templates/rust/rust_bindgen.gni")
import("//build/templates/rust/rust_template.gni")
rust_bindgen("c_lib_bindgen_h") {
header = "lib.h"
}
ohos_rust_executable("bindgen_test_for_h") {
deps = [ ":c_lib_bindgen_h" ]
sources = [ "main.rs" ]
bindgen_output = get_target_outputs(":c_lib_bindgen_h")
inputs = bindgen_output
rustenv = [ "BINDGEN_RS_FILE=" + rebase_path(bindgen_output[0]) ]
crate_root = "main.rs"
}
```
- bindgen官网转换C++的例子
lib.h
```c++
#ifndef BUILD_RUST_TESTS_BINDGEN_TEST_LIB_H_
#define BUILD_RUST_TESTS_BINDGEN_TEST_LIB_H_
// bindgen-flags: --with-derive-hash --with-derive-partialeq --with-derive-eq
typedef int SecondInt;
class C {
public:
typedef int FirstInt;
typedef const char* Lookup;
FirstInt c;
FirstInt* ptr;
FirstInt arr[10];
SecondInt d;
SecondInt* other_ptr;
void method(FirstInt c);
void methodRef(FirstInt& c);
void complexMethodRef(Lookup& c);
void anotherMethod(SecondInt c);
};
class D : public C {
public:
FirstInt* ptr;
};
#endif // BUILD_RUST_TESTS_BINDGEN_TEST_LIB_H_
```
main.rs
```rust
//! bindgen test for hpp
#![allow(clippy::approx_constant)]
#![allow(non_snake_case)]
mod c_ffi {
#![allow(dead_code)]
#![allow(non_upper_case_globals)]
#![allow(non_camel_case_types)]
include!(env!("BINDGEN_RS_FILE"));
}
fn bindgen_test_layout_C() {
const UNINIT: ::std::mem::MaybeUninit<c_ffi::C> =
::std::mem::MaybeUninit::uninit();
let ptr = UNINIT.as_ptr();
println!(
"The mem size of c_ffi::C is {} usize",
::std::mem::size_of::<c_ffi::C>()
);
println!(
"The align_of size of c_ffi::C is {} usize",
::std::mem::align_of::<c_ffi::C>()
);
println!(
"The addr_of!((*ptr).c) as usize - ptr as usize is {} usize",
unsafe { ::std::ptr::addr_of!((*ptr).c) as usize - ptr as usize }
);
println!(
"The addr_of!((*ptr).ptr) as usize - ptr as usize is {} usize",
unsafe { ::std::ptr::addr_of!((*ptr).ptr) as usize - ptr as usize }
);
println!(
"The addr_of!((*ptr).arr) as usize - ptr as usize is {} usize",
unsafe { ::std::ptr::addr_of!((*ptr).arr) as usize - ptr as usize }
);
println!(
"The addr_of!((*ptr).d) as usize - ptr as usize is {} usize",
unsafe { ::std::ptr::addr_of!((*ptr).d) as usize - ptr as usize }
);
println!(
"The addr_of!((*ptr).other_ptr) as usize - ptr as usize is {} usize",
unsafe {
::std::ptr::addr_of!((*ptr).other_ptr) as usize - ptr as usize
}
);
}
fn bindgen_test_layout_D() {
const UNINIT: ::std::mem::MaybeUninit<c_ffi::D> =
::std::mem::MaybeUninit::uninit();
let ptr = UNINIT.as_ptr();
println!(
"The mem size of c_ffi::D is {} usize",
::std::mem::size_of::<c_ffi::D>()
);
println!(
"The align_of size of c_ffi::D is {} usize",
::std::mem::align_of::<c_ffi::D>()
);
println!(
"The addr_of!((*ptr).ptr) as usize - ptr as usize is {} usize",
unsafe { ::std::ptr::addr_of!((*ptr).ptr) as usize - ptr as usize }
);
}
impl Default for c_ffi::D {
fn default() -> Self {
let mut r = ::std::mem::MaybeUninit::<Self>::uninit();
unsafe {
::std::ptr::write_bytes(r.as_mut_ptr(), 0, 1);
r.assume_init()
}
}
}
/// fn main()
fn main() {
bindgen_test_layout_C();
bindgen_test_layout_D()
}
```
BUILD.gn
```
import("//build/ohos.gni")
rust_bindgen("c_lib_bindgen_hpp") {
header = "lib.h"
enable_c_plus_plus = true
}
ohos_rust_executable("bindgen_test_hpp") {
deps = [ ":c_lib_bindgen_hpp" ]
sources = [ "main.rs" ]
bindgen_output = get_target_outputs(":c_lib_bindgen_hpp")
inputs = bindgen_output
rustenv = [ "BINDGEN_RS_FILE=" + rebase_path(bindgen_output[0]) ]
crate_root = "main.rs"
}
```
#### 使用extern C的方式实现Rust调用C接口
test_extern_c.rs
```rust
//! test for extern "C"
#![allow(clippy::approx_constant)]
use std::os::raw::c_double;
use std::os::raw::c_int;
extern "C" {
fn abs(num: c_int) -> c_int;
fn sqrt(num: c_double) -> c_double;
fn pow(num: c_double, power: c_double) -> c_double;
}
/// fn main()
fn main() {
let x: i32 = -123;
println!("This is an example of calling a C library function from Rust:");
println!("The absolute value of {x} is: {}.", unsafe { abs(x) });
let n: f64 = 9.0;
let p: f64 = 3.0;
println!("The {n}th power of {p} is: {}.", unsafe { pow(n, p) });
let mut y: f64 = 64.0;
println!("The square root of {y} is: {}.", unsafe { sqrt(y) });
y = -3.14;
println!("The square root of {y} is: {}.", unsafe { sqrt(y) });
}
```
BUILD.gn
```
import("//build/ohos.gni")
ohos_rust_executable("test_extern_c") {
sources = [ "test_extern_c.rs" ]
}
```
### CXX工具使用指导
#### C++调用Rust接口
在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
}
```
C++侧将cxx工具转换出来的lib.rs.h包含进来,就可以使用C++侧的接口
```c++
#include <iostream>
#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;
}
```
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",
]
}
```
ohos_rust_static_ffi需要依赖cxx的rlib
```
group("cxx_rustdeps") {
public_deps = [ "//third_party/rust/cxx:lib" ]
}
```
ohos_executable需要依赖cxx侧的cxx.h和cxx.cc文件
```
static_library("cxx_cppdeps") {
defines = [ "RUST_CXX_NO_EXCEPTIONS" ]
sources = [
"//third_party/rust/cxx/include/cxx.h",
"//third_party/rust/cxx/src/cxx.cc",
]
deps = [ ":cxx_rustdeps" ]
if (is_win) {
defines += [ "CXX_RS_EXPORT=__declspec(dllexport)" ]
} else {
defines += [ "CXX_RS_EXPORT=__attribute__((visibility(\"default\")))" ]
}
}
```
同时依赖上test_cxx_exe_gen和test_cxx_examp_rust文件
编译验证
![cpp_call_rust](./figures/cpp_call_rust.png "屏幕截图")
#### 实现Rust调用C++
头文件client_blobstore.h
```c++
#ifndef BUILD_RUST_TESTS_CLIENT_BLOBSTORE_H
#define BUILD_RUST_TESTS_CLIENT_BLOBSTORE_H
#include <memory>
#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> impl;
};
std::unique_ptr<client_blobstore> blobstore_client_new();
} // namespace nsp_blobstore
} // namespace nsp_org
#endif
```
cpp文件client_blobstore.cpp
```c++
#include <algorithm>
#include <functional>
#include <set>
#include <string>
#include <unordered_map>
#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<std::string> tags;
};
std::unordered_map<uint64_t, Blob> 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<const char *>(res_chunk.data()), res_chunk.size());
}
// Insert into map and provide caller the handle.
auto res = std::hash<std::string> {} (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<client_blobstore> blobstore_client_new()
{
return std::make_unique<client_blobstore>();
}
} // namespace nsp_blobstore
} // namespace nsp_org
```
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<String>,
}
// 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<client_blobstore>;
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<Vec<u8>> 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<Vec<u8>>,
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);
}
```
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 "屏幕截图")
# Rust模块配置规则和指导
## 概述
Rust是一门静态和强类型语言,具有更安全的内存管理、更好的运行性能、原生支持多线程开发等优势。
Rust官方使用了cargo工具创建工程和构建编译,在OpenHarmony中希望通过gn构建Rust源码文件(xxx.rs),增加与C/C++互操作、编译时lint、测试、IDL转换、三方库集成、IDE等功能,扩展gn框架,并增加接口自动化转换,最大程度简化开发。
OpenHarmony拥有集成Rust的先决条件:为了集成OpenHarmony中的C/C++代码,提升编译速度使用了gn+ninja的编译构建系统。gn的构建语言简洁易读,ninja的汇编级编译规则直接高效。
### 基本概念
| 术语 | 描述 |
| ----- | ------------------------------------------------------------ |
| Cargo | Cargo是一个工具,允许Rust 项目声明其各种依赖项,并确保您始终获得可重复的构建。 |
| crate | crate是一个独立的可编译单元。 |
| lints | lints 是指出常见编程错误、错误、样式错误和可疑结构的工具。可以对程序进行更加广泛的错误分析。 |
## 配置规则
当前OpenHarmony提供了用于Rust代码编译构建的各类型gn模板。包含如下模板:
| 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可执行单元测试用例,不带后缀 |
## 配置指导
### test_rlib_crate示例
test_rlib_crate是测试对Rust可执行bin文件和静态库rlib文件的编译,以及可执行文件对静态库的依赖。
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)
### test_rlib_cargo_crate示例
test_rlib_cargo_crate是测试包含预编译文件build.rs的静态库rlib文件的编译。
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<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. 运行结果如下:
![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编译依赖 |
### 运行验证
当前支持target ohos arm和ohos arm64架构。arm64模拟器编译启动方法:
```shell
./build.sh --product-name qemu-arm64-linux-min
./vendor/ohemu/qemu_arm64_linux_min/qemu_run.sh -e out/qemu-arm-linux/packages/phone/images/
```
## 注意事项
### 特性点实例
#### Rust源码依赖调用C/C++库
OpenHarmony上C/C++模块动态库默认用.z.so后缀,但是被Rust依赖部分会转成-l链接,默认只会链接.so后缀的动态库。因此被依赖的C/C++动态库要加上output_externsion = "so"。同理,在Rust源码中如果直接链接动态库,后缀也需要使用".so",使用动态库的中间名,不需要添加lib前缀,例如链接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"
}]
}
```
#### IDE的Rust-project.json编译
```shell
./build.sh --product-name rk3568 --build-target=build/rust:default --export-rust-project
```
### lints规则
OpenHarmony框架支持rustc lints和clippy lints两级lints,分别由模块属性rustc_lints和clippy_lints标识;同时支持三个等级的标准:"openharmony"、"vendor"和"none","openharmony"是系统配置,最为严格。模块无配置时按照路径匹配等级。
#### 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 |
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册