天天看點

NAPI-RS 是怎麼工作的: 從 NAPI 到 Build Script & FFI

作者:閃念基因

前言

對于 NAPI-RS 來說,大家一定已經不陌生了。和 Neon,WASM-Bindgen 相同,它們均是用來生成對于某種 Binding 的工具庫,前者 Neon 和 NAPI-RS 基本是同類産品,用于生成和 Node 的 Binding。

Binding 是什麼?

這裡的 binding 等價于 Language binding,摘錄一段維基百科中的描述:

In programming and software design, binding is an application programming interface (API) that provides glue code specifically made to allow a programming language to use a foreign library or operating system service (one that is not native to that language). From Wikipedia

大部分同類型的工具的架構都比較類似,對于 NAPI-RS 來說是這樣的:

NAPI-RS 是怎麼工作的: 從 NAPI 到 Build Script & FFI
  • NAPI-SYS:NAPI 的 SYS crate,負責和 Node 通信。社群上通常使用 *-sys 命名這些底層調用的庫。
  • NAPI: NAPI crate 則是對 NAPI-SYS 庫的上層封裝。由于 Sys crate 通常是原生的底層 API,是以基本所有原生庫都會存在一個對語言友好的封裝,進而降低使用者的使用成本與代碼的準确性。

為什麼 Sys crate 通常和 Wrapper crate 分開存在?

對于 Sys crate 來說,它們的工作是和底層的 lib 相綁定,API 的變化通常不會那麼頻繁,而對于 wrapper 層來說它們是極易産生 breaking change 的。當 Sys 和 Wrapper 放在同一個 crate 中則非常容易産生 breaking change,如此時進行大版本更新,則可能會導緻項目中單獨使用 Sys crate 的 Dependency(無論是間接,還是直接)們都需要進行更新,是以這是不合理的。詳情見 Semver Compatability(https://doc.rust-lang.org/cargo/reference/semver.html)

  • NAPI Macro 與 NAPI Macro backend:通常為使用 NAPI 的 Rust API 在編譯時生成相關模闆代碼,解放使用者的雙手。如 NAPI Macro 還做了一些 TS 類型生成的工作。

對于上層的 crate 我們不會在本篇中做過多的介紹,對于它們來說,更多的重心則是放在“怎麼讓使用者降低開發成本”(例如是基于 Macro 的編譯時生成模闆代碼、對 Promise 類型的封裝使得它能夠對接 Rust Future等)與“怎麼讓使用者的代碼變得更加的安全”(例如:對于某些 Opaque 類型的上層封裝)上。

本篇,我們會将更多的目光聚焦于 Sys crate 和 Node 的通信上,因為這是 NAPI 的本質。

NAPI-RS 是怎麼工作的: 從 NAPI 到 Build Script & FFI

總結成一句話來說:NAPI-SYS 和 Node 的通信是建立在 C ABI 之上的 FFI 的調用。

C ABI ?

看到這裡,有些人可能會對這個概念有一些歧義,我們将會在下方做進一步解釋。

我們會以一個簡單的 NAPI-SYS crate 的實作作為結束。同時為了讓整體的銜接不至于太過僵硬,下方會使用另外一個案例進行具體分析。那麼,接下來讓我們詳細展開。

Build Script

文檔:https://doc.rust-lang.org/cargo/reference/build-scripts.html

從編譯的角度來看,當一個 Package 被編譯時,Cargo 會首先編譯這個 build script,再進行後續的編譯操作。你可以認為 Build Script 隻是另一個 Package,并在目前的 Package 編譯前先進行了編譯。事實上确實如此,我們能在 Target 中找到兩組産物,其中一組便是 Build script 的産物。

從事務的角度出發,Build Script 對于 Sys crate 來說,一般會做一些源碼編譯、lib 搜尋相關的事務,而對于 Turbopack 來說,則是進行了注冊、代碼生成相關的事項。總之,盡管它被叫做 build script,而現實世界中,理論上你可以用來對他做任何事情,甚至是發送一個 HTTP 請求或做一些危及計算機安全的事情,Rust 的 Secure code team 還為此發起了相關是否要做 Build-time Sandbox 的讨論 (https://tonyarcieri.com/rust-in-2019-security-maturity-stability#sandboxing-for-code-classprettyprintbuildrsco_2)。

預設情況下,你可以直接在 Package 的根目錄中生成一個帶有 fn main 的 build.rs 來作為 build script:

// build.rs
fn main() {
 
}
           

如果在 build script 的執行過程中發生了 panic,則不會對該 Package 進行後續的編譯流程。

值得注意的是,在 build script 中的一切 print 操作是不會被列印到 stdio 上的

// build.rs
fn main() {
 println!("hello from build.rs"); // 沒有用
}
           

對于 print 來說,你可以在産物檔案夾下 output 檔案中找到對應的輸出,對于 dbg! 的相關輸出則可以在産物檔案夾中的 stderr 檔案中找到(這個 macro 的本質是 stderr 的 output)

對于為什麼不對 print 相關的内容進行控制台的輸出官方(https://github.com/rust-lang/cargo/issues/985#issuecomment-64697754)給出的理由是不想制造更多的噪音。因為我們在 build script 中還有一件大事可以做,那就是調用 print 生成 Cargo Instructions

Cargo Instructions

文檔:https://doc.rust-lang.org/cargo/reference/build-scripts.html#outputs-of-the-build-script

這裡列舉一些常用的 Instructions:

  • cargo:rerun-if-changed=PATH — Tells Cargo when to re-run the script.
  • cargo:rustc-link-arg=FLAG — Passes custom flags to a linker for benchmarks, binaries, cdylib crates, examples, and tests.
  • cargo:rustc-link-lib=LIB — Adds a library to link.
  • cargo:rustc-link-search=[KIND=]PATH — Adds to the library search path.
  • cargo:rustc-cfg=KEY[="VALUE"] — Enables compile-time cfg settings.
  • cargo:rustc-cdylib-link-arg=FLAG — Passes custom flags to a linker for cdylib crates.

除此之外,我們通常也會在 build script 中擷取相關 env 字段,常見的有 OUT_DIR,CARGO_FEATURE_XXX 等等,這些都可以通過 std::env::var 獲得,如果你希望忽略 UTF-8 的校驗,則可以用性能更好的 std::env::var_os 達到幾乎相同的效果。

這些都是日常開發上基本會用到的相關内容,在這之上,對于一個 Sys crate 的編譯來說,我們通常會對本機的 lib 進行查找,進而引導 rustc 完成對該 lib 的 linking。

Pattern

通常情況下,我們會在一個版本号區間内查找系統中存在的 lib 包,如果不存在則進行基于源碼的建構。

這裡我們可以以 libgit2 作為參考,就不在本文中詳細展開了。

libgit2:https://github.com/rust-lang/git2-rs/blob/c5765efabe7dfe5758f875d136ecbf77133d3c95/libgit2-sys/build.rs

Foreign Function Interface (FFI)

A foreign function interface (FFI) is a mechanism by which a program written in one programming language can call routines or make use of services written in another. From Wikepedia

FFI 可以讓跨語言的程式之間完成互相的調用。就像 IPC(Inter-process communication)一樣需要建立一套 protocol,FFI 同樣也是一種滿足了約定的規則(如:Calling conventions 等, ABI)的調用,Rust 支援的 ABI 可以在 https://doc.rust-lang.org/nomicon/ffi.html#foreign-calling-conventions 找到。由于 C 的 ABI 在同一個平台上是相容的,是以大部分庫都是建立在 C ABI 上的。

ABI 和 C ABI?

ABI(Application Binary Interface) 和 API(Application Programming Interface)非常相似,前者描述了 Binary 的相容性,這其中包括了各種資料類型的 size 和 alignment、記憶體布局(Layout)以及系統的調用約定(用來描述例如參數是怎麼被傳遞的等等,例如:x86 calling conventions),甚至包括了 Compiler 等等之間的一緻性(Conformance) 等等。

  • size 和 alignment:https://doc.rust-lang.org/reference/type-layout.html#size-and-alignment
  • x86 calling conventions:https://en.wikipedia.org/wiki/X86_calling_conventions#List_of_x86_calling_conventions
  • 更多:https://web.mit.edu/rhel-doc/3/rhel-gcc-en-3/compatibility.html

在 C 的标準中,其實是沒有對 C ABI 标準的定義的。但對于同一個平台,這些基本是可以被認為是一緻的,是以我們基本可以認為它們是相容的。而對于不同的平台來說,它們系統之間的調用約定可能是不一緻的,是以我們認為它們是不相容的。是以我們在描述 C ABI 的相容性時,都包涵了一個隐式約定:同一平台

FFI

在 Rust 中我們可以這樣來聲明,extern "abi":如 extern "C"

  • extern "abi":https://doc.rust-lang.org/reference/items/external-blocks.html#abi
extern "C" {
  fn napi_create_object(...)
}
           

我們不需要手動定義 unsafe,因為 FFI 的調用永遠是 unsafe 的。

Reverse FFI

同樣的,我們也可以定義對應的 fn 給其他支援該 ABI 的語言調用:

#[no_mangle]
pub extern "C" fn napi_register_module_v1(...) {
  // ...
}
           

需要注意的是,我們需要添加 no_mangle 的标記。否則對應的 symbol name 會被 mangle,而導緻調用方無法尋址。你可以使用 nm 指令驗證這一點:

$ nm <path/to/generated-binary> | grep napi
           

macOS 下 symbol 會帶有一個下劃線,可以看到_napi_register_module_v1 被包含在 Symbol table 中:

0000000000001650 T _napi_register_module_v1           

FFI Safety

有了 ABI 的限制,我們可以得到:隻有有限的值類型才可以完成跨 FFI 邊界的值傳遞(通信),就像 IPC protocol 也有特定的資料結構的要求,那麼,常用的 C ABI 也是一樣,簡單來說,C 裡面無法表達的資料結構,你就不能通過 FFI 這條 Boundary,同樣的對于 Rust 的 Error 也是無法通過 FFI 邊界的,etc。

要标記一個值為 C ABI Compatible,可以使用 #[repr(C)],這會讓 rustc 開啟對應的編譯時檢查,確定這個類型是 FFI Safe 的:

#[repr(C)]
struct some_data_type {
  foo: [u8;0],
  bar: usize
}
           

C的範式還限制了 enum 的傳遞,但可以用 #[repr(u32, i8, etc..)] and #[repr(C)] 來強制将非 C 範式的 enum 擁有特定的 Memory Layout,是以下面兩種類型是可以互相 Interop 的:

#[repr(u8)]
pub enum LineStyle {
    Solid,
    Dotted,
    Dashed,
}
           
enum class LineStyle: uint8_t {
    Solid,
    Dotted,
    Dashed,
}
           

更多資訊:https://doc.rust-lang.org/nomicon/other-reprs.html#reprc

Opaque Type

在 FFI 的互動過程中,有很多值是不希望被通路到其實際内容的。對于熟悉 NAPI 可能了解過 External 類型,它是一個 Opaque Type,這個 Opaque Type 将會通過 FFI 調用擷取到,再通過 FFI 作為參數進行傳遞:

napi_status napi_create_external(napi_env env,
                                 void* data, // 需要包裹的值
                                 napi_finalize finalize_cb,
                                 void* finalize_hint,
                                 napi_value* result) // 生成的 JS 類型 External Type
                                 
napi_status napi_get_value_external(napi_env env,
                                    napi_value value, // 這個 JS ExternalType
                                    void** result) // 擷取到這個值之前被包裹的 Data                                
           

得到這兩組定義後,我們可以将 data 包裹成一個值做為标志存儲在 JS 側,而 JS 側是無法感覺到内部的資料結構的,一個實際的例子可以參考 NAPI-RS External Type(https://napi.rs/docs/concepts/external)

同樣的,我們在 Rust 中也可以定義相關的 Opaque Type:

#[repr(C)]
struct foo_opaque {
 _data: [u8;0],
 _marker: PhantomData<*mut ()> // 标記這個 struct 為 !Send 和 !Sync 的
}

#[no_mangle]
extern "C" fn some_init_function(foo: *const foo_opaque) {
}
           

這樣一來,上述的例子中,在其他語言調用它的時候,你僅能拿到 foo 的指針。

另一個 Opaque Type 的好處在于可以完成類型的區分,我們知道在 C 中,一切任意 Type 的 pointer 都可以用 void 來定義,這在 Rust 中的表示是這樣的:

extern "C" fn some_init_function(foo: *const ::std::os::raw::c_void, 
                                 bar: *const ::std::os::raw::c_void) {
  do_something_with_bar(foo); // 可以編譯!                                 
}
           

但當兩個 pointer 均為 c_void 時,則無法區分,也就丢失了 rustc 編譯時的類型檢查,這是我們希望能夠避免的。

寫一個 *-sys crate

在這一章節,我們将用 libsodium 作為案例編寫一個 libsodium-sys,使其能夠完成簡單的 hasher 的功能。這個 Demo 中将直接使用 rust-bindgen (https://github.com/rust-lang/rust-bindgen)完成 binding 的生成。由于篇幅的關系,我們将不涉及 vendor 時的“從源碼建構”。

準備工作

首先需要安裝 libsodium

# 通過 brew 安裝
$ brew install libsodium

# 通過其他方式進行安裝 https://libsodium.gitbook.io/doc/installation
           

安裝完成後可以通過指令驗證是否成功:

$ pkg-config --libs libsodium           

建立一個 libsodium-sys

Cargo.toml:

[package]
edition = "2021"
name = "libsodium-sys"
version = "0.1.0"

[build-dependencies]
pkg-config = "0.3.1"
bindgen = "0.63.0"
           
  1. 我們通過 pkg-config 查找系統依賴,它可以自動設定 rustc 依賴的參數
  2. bindgen 用于基于 libsodium 的 header 生成 FFI Binding

定義 wrapper.h

#include "sodium.h"
           

我們将需要的 header 檔案 sodium.h 添加到 wrapper.h,rust-bindgen 将會編譯生成 FFI 聲明

編寫 build script

fn main() {
  // 通過 pkg_config 查找 syslib
  let lib = pkg_config::Config::new()
        .atleast_version("1.0.18")
        .probe("libsodium")
        .unwrap();
        
  println!("cargo:rerun-if-changed=wrapper.h");
  
  // The bindgen::Builder is the main entry point
  // to bindgen, and lets you build up options for
  // the resulting bindings.
  let bindings = bindgen::Builder::default()
    // The input header we would like to generate
    // bindings for.
    .header("wrapper.h")
    .clang_args(
        lib.include_paths
            .iter()
            .map(|p| format!("-I{}", p.display())),
     )
    .allowlist_function("crypto_generichash")
    .allowlist_function("sodium_init")
    .allowlist_var("crypto_generichash_.*")
    // Tell cargo to invalidate the built crate whenever any of the
    // included header files changed.
    .parse_callbacks(Box::new(bindgen::CargoCallbacks))
    // Finish the builder and generate the bindings.
    .generate()
    // Unwrap the Result and panic on failure.
    .expect("Unable to generate bindings");

  // Write the bindings to the $OUT_DIR/bindings.rs file.
  let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
  bindings
    .write_to_file(out_path.join("bindings.rs"))
    .expect("Couldn't write bindings!");
}
           
  1. 我們通過 pkg-config 查找并 set rustc flags,将 include_paths 添加到 bindgen 的 clang_args 參數
  2. 同時當 wrapper.h 變化時,我們需要重新執行 build script
  3. allow_list 中添加本次 DEMO 需要用到的 fn, const
  4. 最終的 bindings.rs 我們可以在 OUT_DIR 中找到,它是這樣的:
NAPI-RS 是怎麼工作的: 從 NAPI 到 Build Script &amp; FFI

編寫 binding 并測試

我們可以通過 libsodium 官網的 FFI 定義了解各個字段的作用:Generic hashing(https://libsodium.gitbook.io/doc/hashing/generic_hashing#usage)

#![allow(unused)]
#![allow(non_upper_case_globals)]
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]

mod ffi {
  // 内聯 bindings.rs 的 codegen 的結果到 mod ffi
  include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
}

pub use ffi::*;
           

測試部分可以參考:https://github.com/h-a-n-a/build-script-ffi-and-napi/blob/7244774dcbe34aa16bd504b1285cedef775aa2e1/crates/libsodium-sys/src/lib.rs

Tips

  • 可以使用 pkg-config crate 進行 libs 的查找,查詢成功後會自動添加相應的 cargo instructions,省去了手動添加
  • 使用 Bindgen 生成的代碼是一個“大雜燴”,可以限制導出的内容,如:使用 allowlist 等
  • 不建議在 sys crate 中編寫除 ffi 聲明以外的邏輯,避免 breaking change
  • 可以通過 cargo instructions 暴露相關的 metadata 給依賴方,以保持如全局的 lib 版本統一

寫一個簡單的 napi-sys

在這一章節,我們将建立一個 dynamic library 并調用 napi 完成簡單的注冊,添加子產品導出等功能,并在 Node 中進行測試。

準備工作

我們将會建立兩個 crate,第一個 crate 為 napi-sys 用于聲明一些 Node 給我們提供的 FFI,完整的 FFI 清單可以參考 N-API 文檔。其次,我們将會建立第二個 crate NAPI 用于編寫 binding 的測試。

N-API 文檔:https://nodejs.org/api/n-api.html

用到的 FFI :

  • napi_create_string_utf8

(https://nodejs.org/api/n-api.html#napi_create_string_utf8)

napi_status napi_create_string_utf8(napi_env env,
                                    const char* str,
                                    size_t length,
                                    napi_value* result)
           
  • napi_set_named_property

(https://nodejs.org/api/n-api.html#napi_set_named_property)

napi_status napi_set_named_property(napi_env env,
                                    napi_value object,
                                    const char* utf8Name,
                                    napi_value value);
           

用到的 Reverse FFI :

由于目前插件為 dynamic library,我們需要在 crate NAPI 中導出注冊的鈎子,用于在運作時完成 Module 的注冊:

  • napi_register_module_v1:現在 Register 的版本号為 1,可以參考:https://fossies.org/dox/node-v16.19.0/node__api_8h.html#abbbc1d8ba3fc88c2143eaaaf841cb1ba
  • (https://fossies.org/dox/node-v16.19.0/node__api_8h.html#adcddab11624d90d09d3ac22fa486a812)
napi_value napi_register_module_v1(napi_env env,
                                   napi_value exports)
           

用到的傳回值:

  • napi_status: NAPI 調用成功與否,0 為成功

(https://nodejs.org/api/n-api.html#napi_status)

我們需要在 Rust 側建立一個 named export,它的 key 為 foo,值為 bar,最終的效果是這樣的:

const foo = require("./binding.node").foo;
console.log(foo) // bar           

[live-coding]

完整的代碼示例:https://github.com/h-a-n-a/build-script-ffi-and-napi

可能遇到的問題

  • 在 Clang(macOS 預設) 中你需要使用 -undefined, dynamic_lookup 來标記 linker symbol 查找的行為(在 Runtime 中查找,-C 表示 codegen flags),否則會産生找不到 Symbol 的編譯報錯:
[target.x86_64-apple-darwin]
rustflags = [
  "-C", "link-arg=-undefined",
  "-C", "link-arg=dynamic_lookup",
]

[target.aarch64-apple-darwin]
rustflags = [
  "-C", "link-arg=-undefined",
  "-C", "link-arg=dynamic_lookup",
]
           
NAPI-RS 是怎麼工作的: 從 NAPI 到 Build Script &amp; FFI

圖 1.1: LLVM 架構圖 https://blog.gopheracademy.com/advent-2018/llvm-ir-and-go/

NAPI-RS 是怎麼工作的: 從 NAPI 到 Build Script &amp; FFI

圖 1.2: Linker https://en.wikipedia.org/wiki/Linker_(computing)

Linker 有什麼用?

編譯器的架構:Frontend(C -> Clang) -> LLVM Optimizer -> LLVM Backend(圖1.1)

Linker 的作用(圖 1.2)

  • 由于我們希望生成的是一個基于 C ABI 的 dynamic library,是以需要在 cargo.toml 中标記:
[lib]
crate-type = ["cdylib"]           

Tips

  • 可以用 nm 檢視 binary 中的 Symbol,如:
  • (nm:https://www.ibm.com/docs/en/aix/7.2?topic=n-nm-command)
U _napi_create_string_utf8
00000000000015b0 T _napi_register_module_v1
                 U _napi_set_named_property
           

T 代表 Global text symbol

U 代表 Undefined symbol,這正是我們期望的,它将會在宿主環境中提供,例如我們可以簡單驗證 node 中是否定義了 napi_create_string_utf8:

$ nm $(which node) | grep napi_create_string_utf8
           

我們便能得到對應的 FFI 定義

0000000100087c00 T _napi_create_string_utf8
           

FFI 的定義和聲明的差別是什麼?

在上述例子中,我們的 napi-sys crate 僅僅完成了 FFI 的聲明,就好比你直接引用了 napi 的 header file,而隻有在對應 Node binary 中定義了這些 FFI 後你才能使用。這也是為什麼 FFI 永遠是 unsafe 的原因之一

  • 可以用 file 檢視檔案的類型,如:
file binding.node
           

我們可以得到這是一個 x86_64-apple-darwin(通過 Apple iMac 3.8 GHz 8-Core Intel Core i7 編譯) 的 shared library:

binding.node: Mach-O 64-bit dynamically linked shared library x86_64
           

交叉編譯

通常情況而言,一個平台隻能編譯出目前平台支援的可執行代碼,而交叉編譯則是想解決跨平台編譯的問題。如在 M1(aarch64-apple-darwin) 上編譯出 x86_64-linux-gnu 的代碼(每一種 compiler 的 triple 寫法都不太一樣,這裡列舉了 Rust 的)。Rust 提供了開箱即用的 cross-compilation 支援,你隻需要安裝 target 對應的 toolchain 即可:

$ rustup target add x86_64-unknown-linux-gnu
           

然後使用 Cargo build --target 進行編譯:

$ cargo build --target x86_64-unknown-linux-gnu
           

對于編譯一個項目來說,僅僅支援不同 target 的标準庫是大機率不夠用的,對于不同的 target 你也許需要使用不同的 Linker 等,這些都可以在 .cargo/config.toml 檔案中定義,詳細内容可以參考 The Cargo Book(https://doc.rust-lang.org/cargo/reference/config.html#target)。大緻的設定是這樣的:

[target.x86_64-unknown-linux-gnu]
linker = "x86_64-unknown-linux-gnu-gcc"
           

Linux 的一些 C 标準庫

  • GNU (glibc)
  • Musl

對于 gnu 輸出的一般是動态連結的 binary,需要在使用方的電腦上安裝 glibc。而 musl 則是靜态連結(你可以認為就是一個 Tree-shaked 過的 Bundle)的,Bundle 的體積會變大一些,但優勢在于它不需要任何的 Dependency。

你會發現,如果我們需要 cross-compile 多個平台,則需要完成多個平台的參數的調優(不同的 Compiler 的參數還不太一樣),這讓人非常頭疼。這個時候,Zig cc 可以非常好地幫助我們解決這一系列問題。

Zig

從語言的角度看,Zig 是一個非常輕量級的靜态語言,它沒有Macro 等等。除此之外,它提供了非常好的 C Interoperability,你甚至可以直接 include 一個 C 的 header。除此之外它還是一個 C/C++ Compiler,底層調用的是 Clang,令人吃驚的是它竟然相容了 Clang 和 gcc 的編譯參數!從原理上來說,它承擔了和 Clang 溝通的角色,截獲部分需要的指令,如 --target 等,加以處理後交給 Clang 進行後續的編譯流程,如:

$ zig cc -target x86_64-linux-gnu ...
⬇️
$ clang xxx
           

那麼如何将 Zig 應用到我們的工作流上呢?首先在任意位置建立一個 zcc 的檔案(Zig cc),如:

#!/bin/sh
zig cc -target x86_64-linux-gnu $@
           

在對應的 .cargo/config.toml 中完成對 linker 的設定:

[target.x86_64-unknown-linux-gnu]
linker = "path/to/zcc"
           

調用 Cargo build 即可:

$ cargo build --target x86_64-unknown-linux-gnu           

測試

如果需要對 binding 進行測試,建議還是 follow docker。推薦所有大型項目,能不用交叉編譯就不用,因為最後它們均要完成在各個平台上的測試,以驗證編譯後的産物的正确性。

Reference

Build Script

  • https://doc.rust-lang.org/cargo/reference/build-scripts.html

FFI

  • https://www.youtube.com/watch?v=pePqWoTnSmQ
  • https://doc.rust-lang.org/nightly/nomicon/other-reprs.html#reprc
  • https://doc.rust-lang.org/nightly/nomicon/ffi.html#representing-opaque-structs
  • http://nickdesaulniers.github.io/blog/2016/08/13/object-files-and-symbols/

交叉編譯

  • https://actually.fyi/posts/zig-makes-rust-cross-compilation-just-work/
  • https://doc.rust-lang.org/cargo/reference/config.html#target
  • https://rustc-dev-guide.rust-lang.org/backend/codegen.html
  • https://andrewkelley.me/post/zig-cc-powerful-drop-in-replacement-gcc-clang.html
  • http://www.aosabook.org/en/llvm.html

作者:王舒源

來源:微信公衆号:ByteDance Web Infra

出處:https://mp.weixin.qq.com/s/JtDZjj832h08O-1csh938g

完整的代碼示例:https://github.com/h-a-n-a/build-script-ffi-and-napi

繼續閱讀