2019.2.2 改標題linux
文檔列表見:Rust 移動端跨平臺複雜圖形渲染項目開發系列總結(目錄)android
build.rs可實現本項目編譯前的額外操做,好比代碼生成、調用cmake/clang/gcc/ndk-build等編譯所依賴的C/C++庫、讀取C/C++頭文件生成FFI文件給Rust項目使用等等,至關於Rust寫的shell腳本。 爲了讓編譯過程更可控,一般輸出日誌表示經過了某一階段,或遇到什麼錯誤,Cargo支持build.rs編譯時輸出不一樣類型的語句,好比warning、error等,好比:ios
println!("cargo:warning=Error failed with {:?}.", some_reason);
複製代碼
目前沒找到輸出info級別日誌的辦法,經實踐println!("cargo:info={:?}, some_status);
沒法在控制檯輸出信息。git
如下代碼摘自glsl-to-spirv。github
use std::process::Command;
// Try to initialize submodules. Don't care if it fails, since this code also runs for
// the crates.io package.
let _ = Command::new("git")
.arg("submodule")
.arg("update")
.arg("--init")
.status();
複製代碼
目前我看到比較完整的參考是官方的libstd/build.rs,編譯咱們業務所需的第三方庫的命令幾乎均可以從那找到「靈感」,下面貼出核心代碼段鎮宅,關鍵操做是build_libbacktrace()
,經過cc::Build
實例把須要編譯的C/C++代碼聲明起來,理論上支持正則匹配文件名與路徑 。shell
#![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")
// ... 更多條件
{
let _ = build_libbacktrace(&target);
}
if target.contains("linux") {
// ... 一系列操做系統判斷及println!
}
}
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")
// ...一堆.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(())
}
複製代碼
如下代碼參考自rustdroid-nativemacos
use std::{env, path::PathBuf, process};
fn main() {
establish_ndk();
establish_ndk_toolchain();
}
fn establish_ndk() {
match find_ndk_path() {
None => println!("cargo:warning=NDK path not found"),
Some(path) => println!("cargo:warning=NDK path found at {}", path.to_string_lossy()),
};
}
fn establish_ndk_toolchain() {
match find_ndk_toolchain_path() {
None => println!("cargo:warning=NDK_TOOLCHAIN path not found"),
Some(path) => println!(
"cargo:warning=NDK_TOOLCHAIN path found at {}",
path.to_string_lossy()
),
};
}
fn command_which_ndk_build_path() -> Option<PathBuf> {
let mut cmd = process::Command::new("sh"); // mut due to API limitation
cmd.arg("-c").arg("which ndk-build");
match cmd.output() {
Err(e) => {
println!(
"cargo:warning=Error executing process command <{:?}>: {}",
cmd, e
);
None
}
Ok(o) => match String::from_utf8(o.stdout) {
Err(e) => {
println!("cargo:warning=Error parsing command output as UTF-8: {}", e);
None
}
Ok(s) => PathBuf::from(&s)
.parent()
.and_then(|p| Some(p.to_path_buf())),
},
}
}
fn path_from_string(pathname: &str) -> Option<PathBuf> {
// TODO: @@@ FUTURE RUST FEATURE
//Some(PathBuf::from(pathname)).filter(|p| p.exists())
let path = PathBuf::from(&pathname);
if path.exists() {
Some(path)
} else {
None
}
}
fn path_from_env_var(varname: &'static str) -> Option<PathBuf> {
match env::var(varname) {
Ok(s) => path_from_string(&s),
Err(_) => None,
}
}
fn path_with_ndk_build(path: &PathBuf) -> Option<PathBuf> {
// TODO: @@@ FUTURE RUST FEATURE
//path.filter(|p| p.join("ndk-build").exists())
if path.join("ndk-build").exists() {
Some(path.clone())
} else {
None
}
}
fn path_with_ndk_bundle_ndk_build(path: &PathBuf) -> Option<PathBuf> {
path_with_ndk_build(&path.join("ndk-bundle"))
}
fn path_with_ndk_build_from_env_var(varname: &'static str) -> Option<PathBuf> {
path_from_env_var(&varname).and_then(|p| path_with_ndk_build(&p))
}
fn path_with_ndk_bundle_ndk_build_from_env_var(varname: &'static str) -> Option<PathBuf> {
path_from_env_var(&varname).and_then(|p| path_with_ndk_bundle_ndk_build(&p))
}
fn find_ndk_path_from_ndk_env_vars() -> Option<PathBuf> {
// TODO: @@@ REFACTOR INTO ITERATION OF COLLECTION
path_with_ndk_build_from_env_var("ANDROID_NDK_HOME").or_else(|| {
path_with_ndk_build_from_env_var("ANDROID_NDK_ROOT").or_else(|| {
path_with_ndk_build_from_env_var("NDK_HOME").or_else(|| {
path_with_ndk_build_from_env_var("NDK_ROOT") // NVIDIA CodeWorks
.or_else(|| path_with_ndk_build_from_env_var("NDKROOT"))
})
})
}) // NVIDIA CodeWorks
}
fn find_ndk_path_from_sdk_env_vars() -> Option<PathBuf> {
// TODO: @@@ REFACTOR INTO ITERATION OF COLLECTION
path_with_ndk_bundle_ndk_build_from_env_var("ANDROID_SDK_HOME")
.or_else(|| path_with_ndk_bundle_ndk_build_from_env_var("ANDROID_SDK_ROOT"))
.or_else(|| path_with_ndk_bundle_ndk_build_from_env_var("ANDROID_HOME"))
}
fn find_ndk_path_from_env_vars() -> Option<PathBuf> {
find_ndk_path_from_ndk_env_vars().or_else(|| find_ndk_path_from_sdk_env_vars())
}
fn find_ndk_version_build_path(path: &PathBuf) -> Option<PathBuf> {
//println!("cargo:warning=find_ndk_version_build_path() pathname: {:?}", pathname);
if let Ok(iter) = path.read_dir() {
for entry in iter {
if let Ok(entry) = entry {
let path = entry.path();
//println!("cargo:warning=searching path: {:?}", path);
if path.join("ndk-build").exists() {
return Some(path);
}
}
}
}
None
}
fn find_ndk_path_from_known_installations() -> Option<PathBuf> {
env::home_dir().and_then(|home| {
path_with_ndk_bundle_ndk_build(
// Android Studio on GNU/Linux
&home.join(".android").join("sdk"),
)
.or_else(|| {
path_with_ndk_bundle_ndk_build(
// Android Studio on macOS
&home.join("Library").join("Android").join("sdk"),
)
})
.or_else(|| {
find_ndk_version_build_path(
// NVIDIA CodeWorks
&home.join("NVPACK"),
)
})
})
}
fn find_ndk_path() -> Option<PathBuf> {
command_which_ndk_build_path()
.or_else(|| find_ndk_path_from_env_vars())
.or_else(|| find_ndk_path_from_known_installations())
}
fn find_ndk_toolchain_path() -> Option<PathBuf> {
path_from_env_var("NDK_TOOLCHAIN")
}
複製代碼
glslang-sys/build.rswindows
缺點:沒對應到最新的glslang項目。優勢:使用文件後綴匹配須要編譯的文件,避免硬編碼。八卦:此項目做者是Google員工,他還開發了cargo-lipo項目,極大地方便了Rust編譯iOS庫,剛接觸Rust時我啥都不懂,還給他提了一個錯誤的issue,致使Josh和他討論了一段時間。bash
glsl-to-spirv 直接用glslang自帶CMakeList.txt,此方案對於快速迭代且持續維護的開源項目是很好的選擇,下降build.rs編寫、維護成本。 架構
缺點:硬編碼參與編譯的文件列表。優勢:這是Josh的項目,工程組織上比前面glslang-sys項目更成熟,很值得參考。
編譯Metal的.shader文件爲.metallib,避免運行時編譯,提升性能。值得參考的地方是,如何在build.rs中調用XCode編譯工具鏈。
use std::fs;
fn main() {
fs::create_dir_all("./dir1/dir2/dir3"); // 1
fs::create_dir_all("./../lib"); // 2
}
複製代碼
//1
在build.rs同級目錄中建立出dir1/dir2/dir3所需的全部目錄。好比,dir一、dir2都不存在,則fs::create_dir_all()
會自動建立它們,而後建立出dir3。//2
在build.rs上級目錄建立lib目錄。結論:fs::create_dir_all()
要注意路徑的區別。
參考:How to check if a directory exists and create a new one if it doesn't in Rust?
好比目前Rust項目還不支持直接編譯成iOS/macOS支持的.framework,咱們還得用腳本把.a和.h打包進.framework給客戶,若是有編譯後操做支持就很是棒了,遺憾的是,目前尚未,經 @我傻逼我自豪(茶包) 兄提醒,這事已經在討論了cargo/issue。
全部的條件編譯都由經過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"]
複製代碼
實現示例,參考gl-rs/gl_generator/lib.rs
#[cfg(feature = "unstable_generator_utils")]
pub mod generators;
#[cfg(not(feature = "unstable_generator_utils"))]
mod generators;
複製代碼
指定target_arch + CPU架構名稱字符串,如#[cfg(target_arch= "x86")]
,#[cfg(any(target_arch = "arm", target_arch = "x86"))]
。
#[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
複製代碼
編譯時指定例如cargo build --features metal --target aarch64-apple-ios --release
可編譯relase版64位iOS靜態庫,同時將feature爲gfx-backend-metal的代碼打包進來(須要配置前面的features
段)。
同理,cargo build --features vulkan --target aarch64-linux-android --release
可編譯relase版64位Android靜態庫,同時將feature爲gfx-backend-vulkan(須要配置前面的features
段)。
目前還沒找到支持編譯出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
= .sorlib
= 給Rust用的靜態庫dylib
= 給Rust用的動態庫path
表示庫項目的入口文件,一般是src/lib.rs,若是改動了這一位置,可經過path = 新位置實現,好比:[lib]
name = "portability"
crate-type = ["staticlib", "cdylib"]
path = "src/ios/lib.rs"
複製代碼
提供.a/.so給業務團隊,這一過程可能會有人爲失誤致使你們對接失敗,下面介紹些咱們使用的小技巧。
在macOS terminal執行以下命令,用/
查找VERSION
。
otool -lv xyz.a | less
複製代碼
參考:check-ios-deployment-target-of-a-static-library
有時編碼疏忽致使沒給須要導出的C接口添加#[no_mangle]
和extern
等修飾,或者使用了不合理的優化attribute致使符號被優化掉,此時業務連接咱們的庫就會失敗,所以,交付二進制包前用nm確認符號表是合格的工程師習慣。參考:How do I list the symbols in a .so file。如下爲macOS示例代碼。
nm -D ./target/release/libportability.so | grep fun_call_exported_to_c
0000000000003190 T fun_call_exported_to_c
複製代碼
nm -g ./target/release/libportability.a | grep glActiveTexture
000000000000190c T _glActiveTexture
複製代碼
The Rust philosophy is to prefer explicit over implicit. Rust will only export symbols that are publicly accessible from the root crate. This makes it very easy to inspect the public interface of a crate without crawling through all files: just follow the pub from the root. In your case, the symbol rle_new is publicly accessible to anyone having access to the rle module (such as sibling modules), but the rle module itself is not publicly accessible in the root crate.
The simplest solution is to selectively export this symbol:
pub use rle::rle_new; 複製代碼
所以,對於在非lib.rs中標識#[no_mangle]
的函數,若是忘了在lib.rs中pub use它,打包成C庫或rlib仍是找不到且出現以下編譯警告。解決辦法就是在lib.rs中要麼pub use 模塊::*
或pub use 模塊::{符號名1, 符號名2}
。
warning: function is marked #[no_mangle], but not exported
--> src/portability/gl_es/src/c_abi/mod.rs:785:1
|
785 | / pub extern "C" fn glViewport(x: GLint, y: GLint, width: GLsizei, height: GLsizei) {
786 | | unimplemented!()
787 | | }
| |_^
|
= help: try exporting the item with a `pub use` statement
複製代碼
rustc --print target-list
複製代碼
好比,rustc --print target-list | grep ios
沒有內容,得用rustup component add
ios相關的CPU架構,而後才能交叉編譯iOS的庫,其餘平臺也是如此。