Cargo 多平台 CI:用 GitHub Actions 同时跑 Linux、macOS、Windows 构建
Cargo 多平台 CI用 GitHub Actions 同时跑 Linux、macOS、Windows 构建标签技术、Cargo、CI/CD、GitHub Actions、跨平台一、我只在 macOS 上编译了应该都能跑吧结果第二天就收到 IssueWindows 用户说双击没反应Linux 用户说缺少 GLIBC_2.29。那是第一次被现实教育Rust 的跨平台不等于一次编译到处运行。Rust 提供的是源码层面的跨平台能力二进制文件仍然和操作系统、CPU 架构、系统库绑定。这件事之后我才开始认真搭建跨平台的 CI 流水线。这篇文章就记录一下我的 GitHub Actions 配置文件是怎么一步步进化到能同时构建 Linux、macOS、Windows 三个平台 6 种目标每个平台 x86_64 ARM64的。二、CI 流水线的整体设计动手写 YAML 之前先理清楚整个流程。GitHub Actions 一个 Workflow 可以包含多个 Job每个 Job 跑在一个独立的虚拟机上。flowchart TB Push[git push / PR] -- Trigger[触发 GitHub Actions] Trigger -- Lint[Job 1: 代码检查并行] Trigger -- Test[Job 2: 测试并行] Trigger -- Build[Job 3: 编译矩阵] Lint -- LintSteps[cargo fmt --check\ncargo clippy] Test -- TestSteps[cargo test\n多平台测试] Build -- Matrix[矩阵策略] Matrix -- Linux_x86[Ubuntu x86_64] Matrix -- Linux_arm[Ubuntu ARM64] Matrix -- Mac_x86[macOS x86_64] Matrix -- Mac_arm[macOS ARM64] Matrix -- Win_x86[Windows x86_64] Matrix -- Win_arm[Windows ARM64] Linux_x86 -- Collect[收集产物] Linux_arm -- Collect Mac_x86 -- Collect Mac_arm -- Collect Win_x86 -- Collect Win_arm -- Collect Collect -- Release[Job 4: 创建 GitHub Release] Release -- Upload[上传 6 个平台的二进制文件] style Build fill:#e3f2fd style Release fill:#c8e6c9 style Matrix fill:#fff3e0整体分四个阶段有依赖关系Lint 和 Test并行跑先确认代码质量没问题再编译。Build用矩阵策略自动生成 6 个编译任务各平台互不影响。Release必须等 Build 完成收集所有产物后统一发布。三、完整的 GitHub Actions 配置下面是我的.github/workflows/release.yml完整配置每一步都加了注释# # 多平台 CI/CD 工作流 # 触发条件push 到 main 分支或创建 tag如 v1.0.0 # 目标平台Linux/macOS/Windows x x86_64/ARM64 # name: 多平台构建与发布 on: push: branches: [main] tags: [v*] # v1.0.0 这种 tag 会触发正式发布 pull_request: branches: [main] # PR 到 main 也触发但不会发布 env: CARGO_TERM_COLOR: always # Cargo 输出保持彩色便于阅读日志 # 二进制文件的名称根据 Cargo.toml 里的 name 设置 BINARY_NAME: my-rust-cli # # Job 1代码质量检查不使用 cache 加速 # jobs: lint: name: 代码检查 runs-on: ubuntu-latest steps: - uses: actions/checkoutv4 - name: 安装 Rust 工具链 uses: dtolnay/rust-toolchainstable with: components: rustfmt, clippy # 需要这两个组件做格式检查和静态分析 - name: 运行 cargo fmt格式检查 run: cargo fmt --all -- --check # --check只检查不修改格式不对就报错 - name: 运行 cargo clippy代码检查 run: cargo clippy --all-targets --all-features -- -D warnings # -D warnings把所有 clippy 警告当作错误处理 # # Job 2测试 # test: name: 测试 runs-on: ubuntu-latest steps: - uses: actions/checkoutv4 - name: 安装 Rust 工具链 uses: dtolnay/rust-toolchainstable - name: 缓存 Cargo 依赖和编译产物 uses: actions/cachev4 with: path: | ~/.cargo/registry/ # 下载的 crate ~/.cargo/git/ # git 依赖 target/ # 编译产物 key: ${{ runner.os }}-cargo-test-${{ hashFiles(Cargo.lock) }} # key 基于 Cargo.lock 的哈希依赖变化时自动重建缓存 - name: 运行测试 run: cargo test --all-features # # Job 3多平台编译矩阵策略 # build: name: 编译 ${{ matrix.target }} needs: [lint, test] # 等 lint 和 test 通过才编译节省资源 runs-on: ${{ matrix.os }} strategy: # fail-fast: false —— 一个平台编译失败不影响其他平台继续 fail-fast: false matrix: include: # ------------------ Linux 平台 ------------------ - os: ubuntu-latest target: x86_64-unknown-linux-gnu # 使用 glibc 编译兼容大多数 Linux 发行版 artifact_name: linux-amd64 - os: ubuntu-latest target: aarch64-unknown-linux-gnu artifact_name: linux-arm64 # ARM64 交叉编译需要额外安装工具链 setup_cross: true # ------------------ macOS 平台 ------------------ - os: macos-latest target: x86_64-apple-darwin artifact_name: macos-amd64 - os: macos-latest target: aarch64-apple-darwin artifact_name: macos-arm64 # ------------------ Windows 平台 ------------------ - os: windows-latest target: x86_64-pc-windows-msvc artifact_name: windows-amd64 # Windows ARM64 交叉编译在 GitHub Actions 上还不成熟 # 暂时跳过等环境支持后再加回来 # - os: windows-latest # target: aarch64-pc-windows-msvc # artifact_name: windows-arm64 steps: - uses: actions/checkoutv4 - name: 安装 Rust 工具链 uses: dtolnay/rust-toolchainstable with: targets: ${{ matrix.target }} # 安装目标平台的编译目标 # Linux ARM64 交叉编译需要安装 gcc-aarch64 等交叉编译工具 - name: 安装 Linux ARM64 交叉编译依赖 if: matrix.setup_cross true run: | sudo apt-get update sudo apt-get install -y \ gcc-aarch64-linux-gnu \ g-aarch64-linux-gnu \ libc6-dev-arm64-cross - name: 缓存 Cargo 依赖 uses: actions/cachev4 with: path: | ~/.cargo/registry/ ~/.cargo/git/ target/ # 缓存 key 包含平台和目标不同平台/目标的缓存独立 key: ${{ runner.os }}-${{ matrix.target }}-cargo-${{ hashFiles(Cargo.lock) }} - name: 编译Release 模式 run: cargo build --release --target ${{ matrix.target }} # 配置交叉编译的目标链接器仅 Linux ARM64 需要 - name: 配置交叉编译链接器 if: matrix.setup_cross true env: CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER: aarch64-linux-gnu-gcc run: cargo build --release --target ${{ matrix.target }} # Windows 的可执行文件需要 .exe 后缀 - name: 确定产物文件名 id: artifact_name shell: bash run: | if [[ ${{ matrix.os }} windows-latest ]]; then echo binary_name${{ env.BINARY_NAME }}.exe $GITHUB_OUTPUT else echo binary_name${{ env.BINARY_NAME }} $GITHUB_OUTPUT fi - name: 打包产物Linux/macOS 用 tar.gzWindows 用 zip shell: bash run: | cd target/${{ matrix.target }}/release if [[ ${{ matrix.os }} windows-latest ]]; then # Windows 用 zip 格式Windows 用户解压更方便 7z a ../../../${{ matrix.artifact_name }}.zip \ ${{ steps.artifact_name.outputs.binary_name }} else # Linux/macOS 用 tar.gz tar -czf ../../../${{ matrix.artifact_name }}.tar.gz \ ${{ steps.artifact_name.outputs.binary_name }} fi cd ../../.. - name: 上传构件用于后续 Release Job uses: actions/upload-artifactv4 with: name: ${{ matrix.artifact_name }} path: | ${{ matrix.artifact_name }}.tar.gz ${{ matrix.artifact_name }}.zip # 保留 7 天Release 完成后可以删掉 retention-days: 7 # # Job 4创建 GitHub Release 并上传产物 # 只在创建 tag 时触发普通 push 不执行 # release: name: 创建 Release needs: [build] if: startsWith(github.ref, refs/tags/) # 只有 tag push 才执行 runs-on: ubuntu-latest # 需要写入 Release 的权限 permissions: contents: write steps: # 下载所有 build job 产出的构件 - name: 下载所有平台产物 uses: actions/download-artifactv4 with: path: artifacts # artifacts/ 下会按名称分类 # artifacts/linux-amd64/ # artifacts/macos-arm64/ # ... - name: 显示产物结构 run: find artifacts -type f - name: 生成 Release 说明 id: release_body run: | # 从 git tag 中提取版本号去掉 v 前缀 VERSION${GITHUB_REF#refs/tags/v} # 生成 Release 正文 cat release_body.md EOF ## 下载 根据你的操作系统和架构选择对应文件 | 平台 | 架构 | 文件 | |------|------|------| | Linux | x86_64 | linux-amd64.tar.gz | | Linux | ARM64 | linux-arm64.tar.gz | | macOS | x86_64 | macos-amd64.tar.gz | | macOS | ARM64 | macos-arm64.tar.gz | | Windows | x86_64 | windows-amd64.zip | ## 安装 \\\bash # 下载对应平台的压缩包 # Linux/macOS: tar -xzf 平台名.tar.gz chmod x ${{ env.BINARY_NAME }} ./${{ env.BINARY_NAME }} --help # Windows: # 解压 zip 文件双击运行或从命令行执行 \\\ EOF echo body_pathrelease_body.md $GITHUB_OUTPUT - name: 创建 GitHub Release uses: softprops/action-gh-releasev2 with: # 使用 release_body.md 的内容作为 Release 说明 body_path: ${{ steps.release_body.outputs.body_path }} # 自动生成 Release 标题 name: ${{ github.ref_name }} # 标记为非预发布 prerelease: false # 上传 artifacts 目录下所有压缩文件 files: artifacts/**/*.tar.gz artifacts/**/*.zip env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}四、几个踩过的坑坑 1GLIBC 版本不兼容Rust 在 Ubuntu 最新版上编译出来的二进制可能依赖高版本的 GLIBC在 CentOS 7 这些老系统上就跑不了。解决方法用x86_64-unknown-linux-musl编译目标代替gnumusl 是静态链接的不依赖系统的 glibc 版本。或者在较老版本的 Ubuntu如 ubuntu-20.04上编译向后兼容更好。# 用 musl 静态编译的配置 - os: ubuntu-latest target: x86_64-unknown-linux-musl artifact_name: linux-amd64-static # 需要先安装 musl 工具链 - name: 安装 musl 编译工具 run: | sudo apt-get update sudo apt-get install -y musl-tools坑 2ARM64 macOS 交叉编译不需要额外配置好消息macOS 的 ARM64 编译不需要交叉编译工具链。GitHub Actions 的macos-latest跑的是 ARM64 虚拟机直接编译就行。坑 3Windows 编译的 OpenSSL 依赖如果你的 Rust 项目依赖了 OpenSSL很多网络库都会Windows 上编译会找不到头文件。要么用rustls代替native-tls要么在 CI 里安装 OpenSSL。// Cargo.toml 里用 rustls 代替默认的 native-tls # 不用这个 # reqwest { version 0.12, features [native-tls] } # 用这个不依赖系统 OpenSSL跨平台编译更省心 reqwest { version 0.12, default-features false, features [rustls-tls] }flowchart LR A[项目依赖 TLS] -- B{tls 后端选哪个} B --|native-tls| C[依赖系统 OpenSSL] C -- C1[Linux: 装 libssl-dev] C -- C2[macOS: 系统自带] C -- C3[Windows: 需要手动装] B --|rustls| D[纯 Rust 实现] D -- D1[所有平台零配置] D -- D2[跨平台 CI 省心] style C fill:#ffcdd2 style D fill:#c8e6c9坑 4缓存策略设计GitHub Actions 的缓存有 10GB 总容量上限免费版。如果你的矩阵生成 6 个平台每个平台缓存 1-2GB轻松爆容量。我的做法是最频繁变动的 target/ 目录不缓存只缓存 ~/.cargo/registry/ 和 ~/.cargo/git/。依赖下载是编译里最慢的部分缓存它们就能拿到最大收益。我在项目里就遇到过一次缓存 key 没区分平台macOS 和 Linux 的 target/ 缓存互相污染编译报了一堆找不到系统库的错。后来把runner.os加进缓存 key每个平台的缓存完全隔离再没出过问题。还有一点macOS 的 GitHub Actions runner 默认 shell 是 zsh但很多脚本默认写 bash 语法。直接run: cargo build没问题一旦加了if或数组操作就可能报错。建议关键 step 显式指定shell: bash。五、总结这个 CI 配置我从最开始的单平台 30 行 YAML慢慢迭代到了现在的多平台 200 行。每一次改动都是因为踩了一个具体的坑然后去翻文档、查 StackOverflow、看别人的配置学来的。几点最重要的心得矩阵策略是 GitHub Actions 最强大的功能。不需要 6 份重复的配置一份matrix.include搞定。fail-fast: false 很重要。一个平台编译失败不等于你的代码有问题可能只是 CI 环境抽风。缓存策略需要精心设计。缓存 key 的设计、缓存什么不缓存什么直接影响 CI 速度。尽可能用 rustls 代替 native-tls。对于需要跨平台编译的 Rust 项目这能省掉至少 30% 的环境配置工作。GLIBC 版本问题是 Linux 分发的头号陷阱。用 musl 静态编译或者老版本 Ubuntu 编译来解决。我还是自学编程的新手CI/CD 这块是最近才开始认真搞的。上面的配置肯定还有不少可以优化的地方如果你有更好的实践欢迎在评论区分享。