RUA!

跨平台的静态链接构建

TABLE OF CONTENTS

大多数 Linux 软件都是动态链接的,动态链接可以使我们的程序不需要将所有所需要的库都打包到自身当中去。不过这样也有弊处,当目标系统比较旧,或者压根就没有我们需要的库的时候,我们的二进制文件就无法在目标系统中运行。

安装交叉编译工具

不光光需要使用 rustup 来安装对应的 target,还需要 linker 来帮助构建到目标平台。

macOS

brew tap SergioBenitez/osxct
brew install FiloSottile/musl-cross/musl-cross
brew install SergioBenitez/osxct/x86_64-unknown-linux-gnu
brew install mingw-w64
rustup target add x86_64-unknown-linux-musl
rustup target add x86_64-unknown-linux-gnu
rustup target add x86_64-pc-windows-gnu

Ubuntu

apt-get install -y musl-tools libssl-dev pkg-config libudev-dev

在安装好 target 与 linker 之后,则需要在项目目录下的 .cargo/config 中添加对应平台所需要使用的 linker。

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

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

[target.x86_64-pc-windows-gnu]
linker = "x86_64-w64-mingw32-gcc"

动态链接

TARGET_CC=x86_64-unknown-linux-gnu \
cargo build --release --target x86_64-unknown-linux-gnu

静态链接

TARGET_CC=x86_64-linux-musl-gcc \
RUSTFLAGS="-C linker=x86_64-linux-musl-gcc" \
cargo build --target=x86_64-unknown-linux-musl --release
TARGET_CC=x86_64-linux-musl-gcc \
cargo build --target=x86_64-unknown-linux-musl --release

Windows

cargo build --target=x86_64-pc-windows-gnu --release

Arm Linux

手上的树莓派 3B+ 的 A53 其实是 64 位的,很久之前官方的系统就支持 64 位了。到现在一直都没有好好的发光发热过,如果使用 A53 来编译岂不是暴殄天物。

和编译到 x86 的 linux 一样,只要安装好对应的 target 和设置好对应的 CC 即可。

TARGET_CC=aarch64-linux-musl-cc \
RUSTFLAGS="-C linker=aarch64-linux-musl-cc" \
cargo build --target=aarch64-unknown-linux-musl --release

Should I rust or should I go?

Golang 则更加的省心

GOOS=linux GOARCH=arm64 go build

Openssl

在跨平台时,openssl 可能没有对应平台的链接库,而 openssl 提供了一个简单的解决方案:添加 vendored features。

If the vendored Cargo feature is enabled, the openssl-src crate will be used to compile and statically link to a copy of OpenSSL. The build process requires a C compiler, perl (and perl-core), and make. The OpenSSL version will generally track the newest OpenSSL release, and changes to the version are not considered breaking changes. https://docs.rs/openssl/latest/openssl/#vendored

openssl - Rust
Bindings to OpenSSL
https://docs.rs/openssl/latest/openssl/#vendored
openssl = { version = "0.10.61", features = ["vendored"] }

如果没有直接直接使用openssl 作为依赖,可以单独为其指定 features 而不作为依赖,也不用指定版本。

[dependencies.openssl]
features = ["vendored"]
[dependencies.openssl-sys]
features = ["vendored"]

需要注意的是,在某些镜像中(例如 rust:latest),还需要一些额外的依赖

apt-get install -y musl-tools libssl-dev pkg-config libudev-dev

GitLab CI

相对于其他提供 SaaS 的 CI 来说,Gitlab 最大的好处就是可以安装 gitlab-runner 来使用自己的机器作为 runner,从而不用再额外购买 CI 的执行时长。

Compose

version: '3.6'
services:
  runner:
    container_name: gitlab-runner-1
    image: 'gitlab/gitlab-runner:latest'
    restart: unless-stopped
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - ./runner-1:/etc/gitlab

  runner-2:
    container_name: gitlab-runner-2
    image: 'gitlab/gitlab-runner:latest'
    restart: unless-stopped
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - ./runner-2:/etc/gitlab

使用 compose 来运行多个 runner 是最好不过的选择了。compose file 本身与运行其他服务没有什么区别,唯一需要注意的就是注册 runner 了。

在 compose 启动所有的 runner 后,可以使用 docker exec 来为运行中的容器执行命令,从而执行 runner 中的 gitlab-runner 命令来注册 runner。其注册命令为 gitlab-runner register

docker exec -it gitlab-runner-1 gitlab-runner register

这里是映射了容器中的 /etc/gitlab 目录到本地,runner 注册的配置文件就在该目录。所有除了直接执行容器命令,还可以单独运行一个实例,挂载 ./runner-1 目录来注册。注册完成后配置文件就在该目录下,随后再启动 compose,新启动的 runner 就能读取到刚刚注册的配置文件,从而变成它了。

静态链接的配置文件

较为通用的只用到了 rust 官方镜像的跨平台配置。如果使用了 openssl,则需要添加 vendored features。

gitlab-ci.yml
stages:
    - build

variables:
    APP_NAME: "cymo"

image: "rust:latest"

before_script:
    - apt-get update -y
    - mkdir $HOME/.cargo
    - echo "[source.crates-io]" >> $HOME/.cargo/config
    - echo "replace-with = 'ustc'" >> $HOME/.cargo/config
    - echo "" >> $HOME/.cargo/config
    - echo "[source.ustc]" >> $HOME/.cargo/config
    - echo "registry = \"sparse+https://mirrors.ustc.edu.cn/crates.io-index/\"" >> $HOME/.cargo/config
    - mkdir public

build:linux-gnu-amd64:
    stage: build
    rules:
        - if: $CI_COMMIT_TAG
    script:
        - cargo build --release
        - "mv target/release/$APP_NAME target/release/$APP_NAME-x86_64-unknown-linux-gnu-$CI_COMMIT_TAG"
        - "mv target/release/$APP_NAME-x86_64-unknown-linux-gnu-$CI_COMMIT_TAG public/"
    artifacts:
        paths:
            - "public/$APP_NAME-x86_64-unknown-linux-gnu-$CI_COMMIT_TAG"

build:linux-musl-amd64:
    stage: build
    rules:
        - if: $CI_COMMIT_TAG
    script:
        - apt-get update -y
        - apt-get install -y musl-tools libssl-dev pkg-config libudev-dev
        - rustup target add x86_64-unknown-linux-musl
        - cargo build --release --target x86_64-unknown-linux-musl
        - "mv target/x86_64-unknown-linux-musl/release/$APP_NAME target/x86_64-unknown-linux-musl/release/$APP_NAME-x86_64-unknown-linux-musl-$CI_COMMIT_TAG"
        - "mv target/x86_64-unknown-linux-musl/release/$APP_NAME-x86_64-unknown-linux-musl-$CI_COMMIT_TAG public/"
    artifacts:
        paths:
            - public/$APP_NAME-x86_64-unknown-linux-musl-$CI_COMMIT_TAG

build:windows-amd64:
    stage: build
    rules:
        - if: $CI_COMMIT_TAG
    script:
        - apt-get update -y
        - apt-get install -y g++-mingw-w64-x86-64
        - rustup target add x86_64-pc-windows-gnu
        - rustup toolchain install stable-x86_64-pc-windows-gnu
        - cargo build --release --target x86_64-pc-windows-gnu
        - "mv target/x86_64-pc-windows-gnu/release/$APP_NAME.exe target/x86_64-pc-windows-gnu/release/$APP_NAME-x86_64-pc-windows-gnu-$CI_COMMIT_TAG.exe"
        - "mv target/x86_64-pc-windows-gnu/release/$APP_NAME-x86_64-pc-windows-gnu-$CI_COMMIT_TAG.exe public/"
    artifacts:
        paths:
            - public/$APP_NAME-x86_64-pc-windows-gnu-$CI_COMMIT_TAG.exe

rustdoc:
    stage: build
    rules:
        - if: $CI_COMMIT_TAG
    script:
        - cargo doc --no-deps
    artifacts:
        paths:
            - target/doc

Locally test

docker run --entrypoint bash --rm -w $PWD -v $PWD:$PWD -v /var/run/docker.sock:/var/run/docker.sock gitlab/gitlab-runner:latest -c 'git config --global --add safe.directory "*";gitlab-runner exec docker build:linux-musl-amd64'

在本地运行并测试刚修改的 .gitlab-ci.yml 是必不可少的,不能每次的修改都需要 push 以后再交给 runner 运行。这样不仅麻烦,还会导致 commit 的杂乱无章。

而通过启动一个新的 runner 实例,可以直接将项目挂载到 runner 的容器中来本地执行当前的 .gitlab-ci.yml。本地执行的 runner 是不需要注册的。