1. 程式人生 > >Rust跨平臺與條件編譯總結

Rust跨平臺與條件編譯總結

文件列表見:Rust 移動端跨平臺複雜圖形渲染專案開發系列總結(目錄)

專案編譯前的操作 build.rs

build.rs可實現正常專案編譯前的額外操作,比如程式碼生成、編譯所依賴的C/C++庫等行為。為了讓程式設計過程更可控,通常需要輸出狀態表示通過了某一階段,或遇到什麼錯誤,Cargo支援build.rs編譯時輸出不同型別的語句,比如info、warning、error等,以下為示例,參考自rustdroid-native

println!("cargo:warning=Error executing process command <{:?}>: {}", cmd, e);
複製程式碼

呼叫C/C++編譯器編譯所依賴的第三方C/C++庫

目前我看到比較完整的參考是官方的libstd/build.rs,編譯我們業務所需的第三方庫的命令幾乎都可以從那找到“靈感”,下面貼出完整程式碼鎮宅,關鍵操作是build_libbacktrace(),通過cc::Build例項把需要編譯的C/C++程式碼宣告起來,理論上支援正則匹配檔名與路徑

#![deny(warnings)]

extern crate build_helper;
extern crate cc;

use build_helper::native_lib_boilerplate;
use std::env;
use std::fs::File;

fn
main
() { let target = env::var("TARGET").expect("TARGET was not set"); if cfg!(feature = "backtrace") && !target.contains("cloudabi") && !target.contains("emscripten") && !target.contains("msvc") && !target.contains("wasm32") { let
_ = build_libbacktrace(&target); } if target.contains("linux") { if target.contains("android") { println!("cargo:rustc-link-lib=dl"); println!("cargo:rustc-link-lib=log"); println!("cargo:rustc-link-lib=gcc"); } else if !target.contains("musl") { println!("cargo:rustc-link-lib=dl"); println!("cargo:rustc-link-lib=rt"); println!("cargo:rustc-link-lib=pthread"); } } else if target.contains("freebsd") { println!("cargo:rustc-link-lib=execinfo"); println!("cargo:rustc-link-lib=pthread"); } else if target.contains("dragonfly") || target.contains("bitrig") || target.contains("netbsd") || target.contains("openbsd") { println!("cargo:rustc-link-lib=pthread"); } else if target.contains("solaris") { println!("cargo:rustc-link-lib=socket"); println!("cargo:rustc-link-lib=posix4"); println!("cargo:rustc-link-lib=pthread"); println!("cargo:rustc-link-lib=resolv"); } else if target.contains("apple-darwin") { println!("cargo:rustc-link-lib=System"); // res_init and friends require -lresolv on macOS/iOS. // See #41582 and http://blog.achernya.com/2013/03/os-x-has-silly-libsystem.html println!("cargo:rustc-link-lib=resolv"); } else if target.contains("apple-ios") { println!("cargo:rustc-link-lib=System"); println!("cargo:rustc-link-lib=objc"); println!("cargo:rustc-link-lib=framework=Security"); println!("cargo:rustc-link-lib=framework=Foundation"); println!("cargo:rustc-link-lib=resolv"); } else if target.contains("windows") { println!("cargo:rustc-link-lib=advapi32"); println!("cargo:rustc-link-lib=ws2_32"); println!("cargo:rustc-link-lib=userenv"); println!("cargo:rustc-link-lib=shell32"); } else if target.contains("fuchsia") { println!("cargo:rustc-link-lib=zircon"); println!("cargo:rustc-link-lib=fdio"); } else if target.contains("cloudabi") { if cfg!(feature = "backtrace") { println!("cargo:rustc-link-lib=unwind"); } println!("cargo:rustc-link-lib=c"); println!("cargo:rustc-link-lib=compiler_rt"); } } fn build_libbacktrace(target: &str) -> Result<(), ()> { let native = native_lib_boilerplate("libbacktrace", "libbacktrace", "backtrace", "")?; let mut build = cc::Build::new(); build .flag("-fvisibility=hidden") .include("../libbacktrace") .include(&native.out_dir) .out_dir(&native.out_dir) .warnings(false) .file("../libbacktrace/alloc.c") .file("../libbacktrace/backtrace.c") .file("../libbacktrace/dwarf.c") .file("../libbacktrace/fileline.c") .file("../libbacktrace/posix.c") .file("../libbacktrace/read.c") .file("../libbacktrace/sort.c") .file("../libbacktrace/state.c"); let any_debug = env::var("RUSTC_DEBUGINFO").unwrap_or_default() == "true" || env::var("RUSTC_DEBUGINFO_LINES").unwrap_or_default() == "true"; build.debug(any_debug); if target.contains("darwin") { build.file("../libbacktrace/macho.c"); } else if target.contains("windows") { build.file("../libbacktrace/pecoff.c"); } else { build.file("../libbacktrace/elf.c"); let pointer_width = env::var("CARGO_CFG_TARGET_POINTER_WIDTH").unwrap(); if pointer_width == "64" { build.define("BACKTRACE_ELF_SIZE", "64"); } else { build.define("BACKTRACE_ELF_SIZE", "32"); } } File::create(native.out_dir.join("backtrace-supported.h")).unwrap(); build.define("BACKTRACE_SUPPORTED", "1"); build.define("BACKTRACE_USES_MALLOC", "1"); build.define("BACKTRACE_SUPPORTS_THREADS", "0"); build.define("BACKTRACE_SUPPORTS_DATA", "0"); File::create(native.out_dir.join("config.h")).unwrap(); if !target.contains("apple-ios") && !target.contains("solaris") && !target.contains("redox") && !target.contains("android") && !target.contains("haiku") { build.define("HAVE_DL_ITERATE_PHDR", "1"); } build.define("_GNU_SOURCE", "1"); build.define("_LARGE_FILES", "1"); build.compile("backtrace"); Ok(()) } 複製程式碼

條件編譯

所有的條件編譯都由通過cfg配置實現,cfg支援any、all、not等邏輯謂詞組合。

基本用法

在Cargo.toml中新增[features]段,然後列舉需要組合的feature名,大體上相當於gcc -條件1 -條件2 -條件3 ...

[features]
default = []
metal = ["gfx-backend-metal"]
vulkan = ["gfx-backend-vulkan"]
dx12 = ["gfx-backend-dx12"]
複製程式碼

mod級別條件編譯

實現示例,參考gl_generator.rs

#[cfg(feature = "unstable_generator_utils")]
pub mod generators;
#[cfg(not(feature = "unstable_generator_utils"))]
mod generators;
複製程式碼

編譯特定CPU架構

指定target_arch + CPU架構名稱字串,如#[cfg(target_arch= "x86")]#[cfg(any(target_arch = "arm", target_arch = "x86"))]

參考libstd/os/android/raw.rs

#[cfg(any(target_arch = "arm", target_arch = "x86"))]
mod arch {
    use os::raw::{c_uint, c_uchar, c_ulonglong, c_longlong, c_ulong};
    use os::unix::raw::{uid_t, gid_t};

    #[stable(feature = "raw_ext", since = "1.1.0")]
    pub type dev_t = u64;
    #[stable(feature = "raw_ext", since = "1.1.0")]
    pub type mode_t = u32;

    #[stable(feature = "raw_ext", since = "1.1.0")]
    pub type blkcnt_t = u64;
    #[stable(feature = "raw_ext", since = "1.1.0")]
    pub type blksize_t = u64;
    #[stable(feature = "raw_ext", since = "1.1.0")]
    pub type ino_t = u64;
    #[stable(feature = "raw_ext", since = "1.1.0")]
    pub type nlink_t = u64;
    #[stable(feature = "raw_ext", since = "1.1.0")]
    pub type off_t = u64;
    #[stable(feature = "raw_ext", since = "1.1.0")]
    pub type time_t = i64;
複製程式碼
#[doc(include = "os/raw/char.md")]
#[cfg(any(all(target_os = "linux", any(target_arch = "aarch64",
                                       target_arch = "arm",
                                       target_arch = "powerpc",
                                       target_arch = "powerpc64",
                                       target_arch = "s390x")),
複製程式碼
[target.'cfg(any(target_os = "macos", all(target_os = "ios", target_arch = "aarch64")))'.dependencies.gfx-backend-metal]
git = "https://github.com/gfx-rs/gfx"
version = "0.1"
optional = true

[target.'cfg(target_os = "android")'.dependencies.gfx-backend-vulkan]
git = "https://github.com/gfx-rs/gfx"
version = "0.1"
optional = true

#[target.'cfg(windows)'.dependencies.gfx-backend-dx12]
#git = "https://github.com/gfx-rs/gfx"
#version = "0.1"
#optional = true
複製程式碼

編譯成指定型別二進位制包(.a/.so/.r)

目前還沒找到支援編譯出macOS/iOS支援的.framework辦法。

在Cargo.toml中新增[lib]段,

  • name表示輸出的庫名,最終輸出檔名為lib+name.a或lib+name.so比如libportability.so
  • crate-type表示輸出的二進位制包型別,比如
    • staticlib = .a iOS只認Rust輸出.a,Android可以.a和.so,配置成["staticlib", "cdylib"]在用cargo-lipo時會出警告不支援cdylib,忽略即可。
    • cdylib = .so
    • rlib = 給Rust用的靜態庫
    • dylib = 給Rust用的動態庫
  • path表示庫專案的入口檔案,通常是src/lib.rs,如果改動了這一位置,可通過path = 新位置實現,比如:
[lib]
name = "portability"
crate-type = ["staticlib", "cdylib"]
path = "src/ios/lib.rs"
複製程式碼

SDK開發的“售後服務”

提供.a/.so給業務團隊,這一過程可能會有人為失誤導致大家對接失敗,下面介紹些我們使用的小技巧。

讀取.a靜態庫的iOS版本

在macOS terminal執行如下命令,用/查詢VERSION

otool -lv xyz.a | less
複製程式碼

參考:check-ios-deployment-target-of-a-static-library

nm檢視匯出符號

有時編碼疏忽導致沒給需要匯出的C介面新增#[no_mangle]extern等修飾,或者使用了不合理的優化attribute導致符號被優化掉,此時業務連結我們的庫就會失敗,因此,交付二進位制包前用nm確認符號表是合格的工程師習慣。以下為示例程式碼。

nm -D ./target/release/libportability.so  | grep fun_call_exported_to_c
0000000000003190 T fun_call_exported_to_c
複製程式碼