Published on

Rust 交叉编译 OSX 二进制失败原因分析

Authors
  • avatar
    Name
    ttyS3
    Twitter

缘由

起因还是在ArchLinux下交叉编译 git-cz OSX 二进制失败。

这个问题老灯折腾了很久,搞得几乎都要放弃了。

因为在 Linux 下交叉编译 darwin 平台的教程并不太多,能找到的资料也不尽是相同或相似的问题。

但老灯之前在 Fedora 33 及 Ubuntu 21.04 beta 下都能成功编译,因此,这个周末正好有时间 ,还是坚持分析了一下原因。

问题描述

git-cz 依赖 libgit2 的 rust 绑定库 ( https://github.com/rust-lang/git2-rs ),C 绑定部分代码是里面的子包 libgit2-sys ( https://crates.io/crates/libgit2-sys ) , 因此最后链接阶段,在交叉编译时,肯定是要链接 libgit2 的静态库的(交叉编译时一般都是静态链接,如果动态链接会很麻烦。 比如golang也是,且 golang 在交叉编译时甚至是直接默认关闭 cgo 的)

在编译完成最后 的link阶段报错:

Compiling git2 v0.13.17 error: linking with /usr/local/darwin-ndk-x86_64/bin/x86_64-apple-darwin20.4-clang failed: exit code: 1

cargo 加上 --verbose 参数后,得到如下信息:

= note: ld: library not found for -lgit2 clang-11: error: linker command failed with exit code 1 (use -v to see invocation)

以老灯的经验判断,应该是链接的时候没有找到 libgit2 的静态库。

但是这个库应该跟 ArchLinux 系统的静态库没有关系。因为 Linux 的二进制文件是 ELF 格式的,而不是苹果的Mach-O 64-bit x86_64.

pkg-config-rs 和 cc-rs

于是老灯去查看了一下 libgit2-sys 的源码,发现其实它是会自动编译 libgit2 的,并且它的Build-Dependencies 依赖有 https://crates.io/crates/pkg-confighttps://crates.io/crates/cc

关于 pkg-config-rs 和 cc-rs 支持的环境变量配置,这里老灯直接把官方文档里的摘抄过来,老灯觉得这里的描述几乎全部是重要的。

我们看下pkg-config-rs的介绍:

A simple library meant to be used as a build dependency with Cargo packages in order to use the system pkg-config tool (if available) to determine where a library is located.

External configuration via target-scoped environment variables

In cross-compilation context, it is useful to manage separately PKG_CONFIG_PATH and a few other variables for the host and the target platform.

The supported variables are: PKG_CONFIG_PATH, PKG_CONFIG_LIBDIR, and PKG_CONFIG_SYSROOT_DIR.

Each of these variables can also be supplied with certain prefixes and suffixes, in the following prioritized order:

  1. <var>_<target> - for example, PKG_CONFIG_PATH_x86_64-unknown-linux-gnu
  2. <var>_<target_with_underscores> - for example, PKG_CONFIG_PATH_x86_64_unknown_linux_gnu
  3. <build-kind>_<var> - for example, HOST_PKG_CONFIG_PATH or TARGET_PKG_CONFIG_PATH
  4. <var> - a plain PKG_CONFIG_PATH

This crate will allow pkg-config to be used in cross-compilation if PKG_CONFIG_SYSROOT_DIR or PKG_CONFIG is set. You can set PKG_CONFIG_ALLOW_CROSS=1 to bypass the compatibility check, but please note that enabling use of pkg-config in cross-compilation without appropriate sysroot and search paths set is likely to break builds.

Some Rust sys crates support building vendored libraries from source, which may be a work around for lack of cross-compilation support in pkg-config.

这里最关键的是后面那段话。

如果设置了PKG_CONFIG_SYSROOT_DIRPKG_CONFIG,则此crate将允许pkg-config用于交叉编译。 您可以设置PKG_CONFIG_ALLOW_CROSS=1 以绕过兼容性检查,但是请注意,在没有适当的sysroot和搜索路径设置的情况下, 在交叉编译中启用pkg-config可能会破坏构建。

一些Rust sys crates支持从源码构建vendor库,这可能是由于pkg-config缺乏交叉编译支持而采用的变通方案。

libgit2-sys 就属于那种支持从源码构建vendor库的sys crate, 构建过程中它会自动 clone libgit2 的源码然后编译成静态lib.

cc-rs 的介绍是:

A build-time dependency for Cargo build scripts to assist in invoking the native C compiler to compile native C code into a static archive to be linked into Rust code.

External configuration via environment variables

To control the programs and flags used for building, the builder can set a number of different environment variables.

  • CFLAGS - a series of space separated flags passed to compilers. Note that individual flags cannot currently contain spaces, so doing something like: -L=foo\ bar is not possible.
  • CC - the actual C compiler used. Note that this is used as an exact executable name, so (for example) no extra flags can be passed inside this variable, and the builder must ensure that there aren't any trailing spaces. This compiler must understand the -c flag. For certain TARGETs, it also is assumed to know about other flags (most common is -fPIC).
  • AR - the ar (archiver) executable to use to build the static library.
  • CRATE_CC_NO_DEFAULTS - the default compiler flags may cause conflicts in some cross compiling scenarios. Setting this variable will disable the generation of default compiler flags.
  • CXX... - see C++ Support.

Each of these variables can also be supplied with certain prefixes and suffixes, in the following prioritized order:

  1. <var>_<target> - for example, CC_x86_64-unknown-linux-gnu
  2. <var>_<target_with_underscores> - for example, CC_x86_64_unknown_linux_gnu
  3. <build-kind>_<var> - for example, HOST_CC or TARGET_CFLAGS
  4. <var> - a plain CC, AR as above.

If none of these variables exist, cc-rs uses built-in defaults

In addition to the above optional environment variables, cc-rs has some functions with hard requirements on some variables supplied by cargo's build-script driver that it has the TARGET, OUT_DIR, OPT_LEVEL, and HOST variables.

在交叉编译时,为了让编译出来的目标文件是我们需要的,我们一定要设置 CCCXX , 如果在全局设置,比如设置在 .zshrc 文件中的话,那么,直接用 CC 肯定是不行,因为大部分 Makefile 也用到这的环境变量,这会破坏整个系统的构建。关于这一点, cc-rs 早就想到了,允许以 <var>_<target><var>_<target_with_underscores> 的方式按 target 来设置。比如这里我们要的是 x86_64-apple-darwin, 则设置 CC_x86_64-apple-darwin 就OK了,如果觉得下划线+连接符的方式别扭,也可以用 CC_x86_64_apple_darwin

ok, 文档了解得差不多了,我们再回过头来看问题。

问题解决过程

事实上我遇到这个问题的时候,并没有看这些文档,如果早看到上面这些文档,可能问题会得到更快的解决。

我采用的解决办法是,写一个最小化的 demo, 分别在能成功交叉编译出darwin二进制的机器 和 不能成功编译的机器执行,并记录详细的编译命令到日志,然后用对比工具对比二者在编译过程的命令差异。

这个方法确实有点笨,但是确实有效发现并解决了问题。

cargo new libgit2-demo

main.rs 内容如下:

use git2::Repository;

fn main() {
    let repo = match Repository::init("/tmp/hello.git") {
        Ok(repo) => repo,
        Err(e) => panic!("failed to init: {}", e),
    };

    println!("success init repo");
}

Cargo.toml 内容如下:

[package]
name = "libgit2-demo"
version = "0.1.0"
authors = ["荒野無燈 <*@ttys3.dev>"]
edition = "2018"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
git2 = { version = "0.13.17", default-features = false, features = [] }

Makefile 如下:

.PHONY: all clean darwin

all: darwin

darwin: export PKG_CONFIG_ALLOW_CROSS=1
darwin: export CC=o64-clang
darwin: export CXX=o64-clang++
darwin: export LIBZ_SYS_STATIC=1
darwin:
    PATH=/usr/local/darwin-ndk-x86_64/bin:$$PATH \
         cargo build --target=x86_64-apple-darwin --release --verbose 2>&1 | tee arch.log

clean:
    cargo clean

测试环境分别是 Ubuntu 21.04 beta 和 ArchLinux, 机器 CPU 分别是 9代 Intel 和 8代 Intel.

通过对比生成的日志文件 ok.log 和 arch.log, 发现在构建执行 rustc --crate-name libgit2_sys ... 时有个明显的差异。

ok.log

 Running `rustc --crate-name libgit2_sys --edition=2018 /home/ttys3/.cargo/registry/src/mirrors.sjtug.sjtu.edu.cn-4f7dbcce21e258a2/libgit2-sys-0.12.18+1.1.0/lib.rs --error-format=json --json=diagnostic-rendered-ansi,artifacts --crate-type lib --emit=dep-info,metadata,link -C opt-level=3 -C embed-bitcode=no -C metadata=177d6c810f4d7b82 -C extra-filename=-177d6c810f4d7b82 --out-dir /home/ttys3/repo/rust/libgit2-demo/target/x86_64-apple-darwin/release/deps --target x86_64-apple-darwin -C linker=/usr/local/darwin-ndk-x86_64/bin/x86_64-apple-darwin20.4-clang -L dependency=/home/ttys3/repo/rust/libgit2-demo/target/x86_64-apple-darwin/release/deps -L dependency=/home/ttys3/repo/rust/libgit2-demo/target/release/deps --extern libc=/home/ttys3/repo/rust/libgit2-demo/target/x86_64-apple-darwin/release/deps/liblibc-d39c361e04f43fde.rmeta --extern libz_sys=/home/ttys3/repo/rust/libgit2-demo/target/x86_64-apple-darwin/release/deps/liblibz_sys-87e6342ea6338869.rmeta --cap-lints allow -L native=/home/ttys3/repo/rust/libgit2-demo/target/x86_64-apple-darwin/release/build/libgit2-sys-8d26052ab24e9383/out/build -l static=git2 -l iconv -l framework=Security -l framework=CoreFoundation -L native=/home/ttys3/repo/rust/libgit2-demo/target/x86_64-apple-darwin/release/build/libz-sys-cffd7e6d65b74042/out/build`

arch.log

Running `rustc --crate-name libgit2_sys --edition=2018 /home/ttys3/.cargo/registry/src/mirrors.sjtug.sjtu.edu.cn-4f7dbcce21e258a2/libgit2-sys-0.12.18+1.1.0/lib.rs --error-format=json --json=diagnostic-rendered-ansi,artifacts --crate-type lib --emit=dep-info,metadata,link -C opt-level=3 -C embed-bitcode=no -C metadata=177d6c810f4d7b82 -C extra-filename=-177d6c810f4d7b82 --out-dir /home/ttys3/repo/rust/libgit2-demo/target/x86_64-apple-darwin/release/deps --target x86_64-apple-darwin -C linker=/usr/local/darwin-ndk-x86_64/bin/x86_64-apple-darwin20.4-clang -L dependency=/home/ttys3/repo/rust/libgit2-demo/target/x86_64-apple-darwin/release/deps -L dependency=/home/ttys3/repo/rust/libgit2-demo/target/release/deps --extern libc=/home/ttys3/repo/rust/libgit2-demo/target/x86_64-apple-darwin/release/deps/liblibc-d39c361e04f43fde.rmeta --extern libz_sys=/home/ttys3/repo/rust/libgit2-demo/target/x86_64-apple-darwin/release/deps/liblibz_sys-87e6342ea6338869.rmeta --cap-lints allow -L native=/usr/lib -l git2 -L native=/home/ttys3/repo/rust/libgit2-demo/target/x86_64-apple-darwin/release/build/libz-sys-cffd7e6d65b74042/out/build`

这一行太长,直接这样看肯定是很难一眼分辨的,因此借助 beyond compare:

可以很快发现关键区别:

arch.log:
-L native=/usr/lib -l git2

ok.log:
-L native=/home/ttys3/repo/rust/libgit2-demo/target/x86_64-apple-darwin/release/build/libgit2-sys-8d26052ab24e9383/out/build -l static=git2 -l iconv -l framework=Security -l framework=CoreFoundation

可以看到,链接出错的机器,native库的路径不是 libgit2-sys 通过 Darwin toolchain 生成的lib路径,而是使用了系统库的路径(-L native=/usr/lib),然后 libgit2 也没有使用静态链接,而是动态的(-l git2)。

而正常编译的机器,则是静态链接 (-l static=git2)且静态库的路径指向正确(-L native=/home/ttys3/repo/rust/libgit2-demo/target/x86_64-apple-darwin/release/build/libgit2-sys-8d26052ab24e9383/out/build

我们看下正常的目录结构:

❯ lsd --tree --depth=2 /home/ttys3/repo/rust/libgit2-demo/target/x86_64-apple-darwin/release/build/libgit2-sys-8d26052ab24e9383/out/build/
 build
├──  libgit2
│  ├──  deps
│  └──  src
└──  libgit2.a

如果再查看 /home/ttys3/repo/rust/libgit2-demo/target/x86_64-apple-darwin/release/build/libgit2-sys-8d26052ab24e9383/out/build/libgit2/deps 会发现,连依赖都是自带编译的:

 deps
├──  http-parser
└──  pcre

问题产生的真正原因

那么在 ArchLinux 下面为什么就编译失败了呢?难道是因为系统已经存在库了?

一查果然发现,老灯在通过pacman安装 starship 的时候自动安装了 libgit2 的动态库。

❯ pacman -Qo /usr/lib/libgit2.so
/usr/lib/libgit2.so is owned by libgit2 1:1.1.0-1
sudo pacman -R libgit2
checking dependencies...
error: failed to prepare transaction (could not satisfy dependencies)
:: removing libgit2 breaks dependency 'libgit2.so=1.1-64' required by starship

然后一查 Ubuntu 21.04 beta那边,根本就没有安装这个库。

于是老灯直接卸载了 starship 和 libgit2, 发现果然能成功通过编译了。

问题到这里,好像已经解决了?

不行,这只是work around, 不是solution.

注意到前面我们提到 pkg-config-rs 文档中说的 PKG_CONFIG_ALLOW_CROSS=1 在没有恰当地设置的情况下使用,会容易引出链接问题。没错,这次的问题其实就是Makefile中这一句 export PKG_CONFIG_ALLOW_CROSS=1引起的,移除这一行,问题成功解决。

也就是说,指定 PKG_CONFIG_ALLOW_CROSS=1 时,pkg-config-rs 会在交叉编译时也启用 pkg-config, 而在没有指定交叉编译的target的 pkg-config 路径时, rust 就使用直接了系统(host) 的 pkg-config path了,这个在ArchLinux下面是 /usr/lib/pkgconfig,那么凡是这里能找到的库,它都不会再进行编译了。因此这里出问题时,libgit2-sys实际上没有编译而是直接使用了系统的libgit2.so文件,而这个文件因为是Linux ELF格式的,并不能被交叉编译器所使用。

如果真的要在交叉编译时启用 pkg-config,则应该指定 /usr/local/darwin-ndk-x86_64/bin/x86_64-apple-darwin20.4-pkg-config,这个在pkg-config-rs中可以通过 环境变量PKG_CONFIG来设置。PKG_CONFIG_PATH 倒是可以设置。

环境变量PKG_CONFIG_PATH是用来设置.pc文件的搜索路径的,pkg-config按照设置路径的先后顺序进行搜索,直到找到指定的.pc 文件为止。

用于指定 pkg-config可执行文件环境变量PKG_CONFIG相关代码:

let exe = self
            .env_var_os("PKG_CONFIG")
            .unwrap_or_else(|| OsString::from("pkg-config"));

PKG_CONFIG_ALLOW_CROSS 相关代码:

/// Represents all reasons `pkg-config` might not succeed or be run at all.
#[derive(Debug)]
pub enum Error {
    /// Aborted because of `*_NO_PKG_CONFIG` environment variable.
    ///
    /// Contains the name of the responsible environment variable.
    EnvNoPkgConfig(String),

    /// Detected cross compilation without a custom sysroot.
    ///
    /// Ignore the error with `PKG_CONFIG_ALLOW_CROSS=1`,
    /// which may let `pkg-config` select libraries
    /// for the host's architecture instead of the target's.
    CrossCompilation,

    /// Failed to run `pkg-config`.
    ///
    /// Contains the command and the cause.
    Command { command: String, cause: io::Error },

    /// `pkg-config` did not exit sucessfully.
    ///
    /// Contains the command and output.
    Failure { command: String, output: Output },

    #[doc(hidden)]
    // please don't match on this, we're likely to add more variants over time
    __Nonexhaustive,
}


/// Run `pkg-config` to find the library `name`.
    ///
    /// This will use all configuration previously set to specify how
    /// `pkg-config` is run.
    pub fn probe(&self, name: &str) -> Result<Library, Error> {
        let abort_var_name = format!("{}_NO_PKG_CONFIG", envify(name));
        if self.env_var_os(&abort_var_name).is_some() {
            return Err(Error::EnvNoPkgConfig(abort_var_name));
        } else if !self.target_supported() {
            return Err(Error::CrossCompilation);
        }

        let mut library = Library::new();

        let output = run(self.command(name, &["--libs", "--cflags"]))?;
        library.parse_libs_cflags(name, &output, self);

        let output = run(self.command(name, &["--modversion"]))?;
        library.parse_modversion(str::from_utf8(&output).unwrap());

        Ok(library)
    }

    pub fn target_supported(&self) -> bool {
        let target = env::var_os("TARGET").unwrap_or_default();
        let host = env::var_os("HOST").unwrap_or_default();

        // Only use pkg-config in host == target situations by default (allowing an
        // override).
        if host == target {
            return true;
        }

        // pkg-config may not be aware of cross-compilation, and require
        // a wrapper script that sets up platform-specific prefixes.
        match self.targetted_env_var("PKG_CONFIG_ALLOW_CROSS") {
            // don't use pkg-config if explicitly disabled
            Some(ref val) if val == "0" => false,
            Some(_) => true,
            None => {
                // if not disabled, and pkg-config is customized,
                // then assume it's prepared for cross-compilation
                self.targetted_env_var("PKG_CONFIG").is_some()
                    || self.targetted_env_var("PKG_CONFIG_SYSROOT_DIR").is_some()
            }
        }
    }

    fn targetted_env_var(&self, var_base: &str) -> Option<OsString> {
        match (env::var("TARGET"), env::var("HOST")) {
            (Ok(target), Ok(host)) => {
                let kind = if host == target { "HOST" } else { "TARGET" };
                let target_u = target.replace("-", "_");

                self.env_var_os(&format!("{}_{}", var_base, target))
                    .or_else(|| self.env_var_os(&format!("{}_{}", var_base, target_u)))
                    .or_else(|| self.env_var_os(&format!("{}_{}", kind, var_base)))
                    .or_else(|| self.env_var_os(var_base))
            }
            (Err(env::VarError::NotPresent), _) | (_, Err(env::VarError::NotPresent)) => {
                self.env_var_os(var_base)
            }
            (Err(env::VarError::NotUnicode(s)), _) | (_, Err(env::VarError::NotUnicode(s))) => {
                panic!(
                    "HOST or TARGET environment variable is not valid unicode: {:?}",
                    s
                )
            }
        }
    }

也就是说,如果你配置了 PKG_CONFIG_ALLOW_CROSS=1 则不管三七二十一,直接不其它加判断,给你使用pkg-config 来找库, 即使在交叉编译到Mac时找到的库是是Linux 格式的。

probe() 通过调用 pkg-config (pkg-config可执行文件路径可通过环境变量配置)来获取include 头包含路径信息和链接库路径信息。

对于我们的例子来说,如果设置了 LIBGIT2_NO_PKG_CONFIG=1 或 target被判断为不支持pkg-config (交叉编译的情况下,不做任何配置则默认是不支持)就直接不使用pkg-config了。否则就使用pkg-config去找头文件和库.

targetted_env_var() 用于支持带 target 的环境变量,同时会默认回退到不带 target 的环境变量。比如 target 是 x86_64-apple-darwin 时,会先后尝试 :

  1. PKG_CONFIG_ALLOW_CROSS_x86_64-apple-darwin
  2. PKG_CONFIG_ALLOW_CROSS_x86_64_apple_darwin
  3. TARGET_PKG_CONFIG_ALLOW_CROSS (非交叉编译时为 HOST_PKG_CONFIG_ALLOW_CROSS)
  4. PKG_CONFIG_ALLOW_CROSS

关于是否使用静态链接

我们看下 libgit2-sys 的 build.rs 构建脚本

    let https = env::var("CARGO_FEATURE_HTTPS").is_ok();
    let ssh = env::var("CARGO_FEATURE_SSH").is_ok();
    let zlib_ng_compat = env::var("CARGO_FEATURE_ZLIB_NG_COMPAT").is_ok();

    // To use zlib-ng in zlib-compat mode, we have to build libgit2 ourselves.
    if !zlib_ng_compat {
        // 由于老灯并没有设置 CARGO_FEATURE_ZLIB_NG_COMPAT
        // 因此会执行到这里来
        let mut cfg = pkg_config::Config::new();
        // 老灯错误地启用了 PKG_CONFIG_ALLOW_CROSS=1 导致 pkg-config-rs 
        // 错误地返回了 Linux 系统的 libgit2 库
        if let Ok(lib) = cfg.atleast_version("1.1.0").probe("libgit2") {
            for include in &lib.include_paths {
                println!("cargo:root={}", include.display());
            }
            return;
        }
    }

可以看到 pkg_config::Config::new() 使用了 pkg-config-rs 的默认配置,我们看下默认配置是什么样的:

/// Creates a new set of configuration options which are all initially set
    /// to "blank".
    pub fn new() -> Config {
        Config {
            statik: None,
            min_version: Bound::Unbounded,
            max_version: Bound::Unbounded,
            extra_args: vec![],
            print_system_cflags: true,
            print_system_libs: true,
            cargo_metadata: true,
            env_metadata: true,
        }
    }


没错,默认是 statik: None 也就是默认是动态链接的。

那那些 static 连接参数是怎么生成的呢?

is_static() 先判断配置,如果配置是 false, 则会继续进行 infer 推断. 以libgit2 为例:

LIBGIT2_STATIC=1 则启用静态

LIBGIT2_DYNAMIC=1 则启用动态

PKG_CONFIG_ALL_STATIC=1 则启用静态

PKG_CONFIG_ALL_DYNAMIC=1 则启用动态

    fn is_static(&self, name: &str) -> bool {
        self.statik.unwrap_or_else(|| self.infer_static(name))
    }

    fn infer_static(&self, name: &str) -> bool {
        let name = envify(name);
        if self.env_var_os(&format!("{}_STATIC", name)).is_some() {
            true
        } else if self.env_var_os(&format!("{}_DYNAMIC", name)).is_some() {
            false
        } else if self.env_var_os("PKG_CONFIG_ALL_STATIC").is_some() {
            true
        } else if self.env_var_os("PKG_CONFIG_ALL_DYNAMIC").is_some() {
            false
        } else {
            false
        }
    }

执行pkg-config 的时候会根据 is_static() 的结果传递 --static 参数给pkg-config:

    fn command(&self, name: &str, args: &[&str]) -> Command {
        let exe = self
            .env_var_os("PKG_CONFIG")
            .unwrap_or_else(|| OsString::from("pkg-config"));
        let mut cmd = Command::new(exe);
        if self.is_static(name) {
            cmd.arg("--static");
        }
        cmd.args(args).args(&self.extra_args);
        // ... 
  }

最终生成的是 rustc-link-lib=static=xxxx

    fn parse_libs_cflags(&mut self, name: &str, output: &[u8], config: &Config) {
        let mut is_msvc = false;
        if let Ok(target) = env::var("TARGET") {
            if target.contains("msvc") {
                is_msvc = true;
            }
        }

        let system_roots = if cfg!(target_os = "macos") {
            vec![PathBuf::from("/Library"), PathBuf::from("/System")]
        } else {
            let sysroot = config
                .env_var_os("PKG_CONFIG_SYSROOT_DIR")
                .or_else(|| config.env_var_os("SYSROOT"))
                .map(PathBuf::from);

            if cfg!(target_os = "windows") {
                if let Some(sysroot) = sysroot {
                    vec![sysroot]
                } else {
                    vec![]
                }
            } else {
                vec![sysroot.unwrap_or_else(|| PathBuf::from("/usr"))]
            }
        };

        let mut dirs = Vec::new();
        let statik = config.is_static(name);

        let words = split_flags(output);

        // Handle single-character arguments like `-I/usr/include`
        let parts = words
            .iter()
            .filter(|l| l.len() > 2)
            .map(|arg| (&arg[0..2], &arg[2..]));
        for (flag, val) in parts {
            match flag {
                "-L" => {
                    let meta = format!("rustc-link-search=native={}", val);
                    config.print_metadata(&meta);
                    dirs.push(PathBuf::from(val));
                    self.link_paths.push(PathBuf::from(val));
                }
                "-F" => {
                    let meta = format!("rustc-link-search=framework={}", val);
                    config.print_metadata(&meta);
                    self.framework_paths.push(PathBuf::from(val));
                }
                "-I" => {
                    self.include_paths.push(PathBuf::from(val));
                }
                "-l" => {
                    // These are provided by the CRT with MSVC
                    if is_msvc && ["m", "c", "pthread"].contains(&val) {
                        continue;
                    }

                    if statik && is_static_available(val, &system_roots, &dirs) {
                        let meta = format!("rustc-link-lib=static={}", val);
                        config.print_metadata(&meta);
                    } else {
                        let meta = format!("rustc-link-lib={}", val);
                        config.print_metadata(&meta);
                    }

                    self.libs.push(val.to_string());
                }
                "-D" => {
                    let mut iter = val.split('=');
                    self.defines.insert(
                        iter.next().unwrap().to_owned(),
                        iter.next().map(|s| s.to_owned()),
                    );
                }
                _ => {}
            }
        }

        // Handle multi-character arguments with space-separated value like `-framework foo`
        let mut iter = words.iter().flat_map(|arg| {
            if arg.starts_with("-Wl,") {
                arg[4..].split(',').collect()
            } else {
                vec![arg.as_ref()]
            }
        });
        while let Some(part) = iter.next() {
            match part {
                "-framework" => {
                    if let Some(lib) = iter.next() {
                        let meta = format!("rustc-link-lib=framework={}", lib);
                        config.print_metadata(&meta);
                        self.frameworks.push(lib.to_string());
                    }
                }
                "-isystem" | "-iquote" | "-idirafter" => {
                    if let Some(inc) = iter.next() {
                        self.include_paths.push(PathBuf::from(inc));
                    }
                }
                _ => (),
            }
        }
    }

## Refs

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

https://github.com/rust-lang/git2-rs/blob/7912c90991444abb00f9d0476939d48bc368516b/libgit2-sys/build.rs#L13

https://doc.rust-lang.org/cargo/reference/config.html#target

https://wapl.es/rust/2019/02/17/rust-cross-compile-linux-to-macos.html

https://docs.rs/pkg-config/0.3.19/pkg_config/

https://github.com/rust-lang/git2-rs