如果您准备发布你的第一个版本 Rust 项目,您可能希望将一些包含预构建二进制文件(适用于 macOS、Linux 和 Windows)的资产上传到您的 GitHub 发布页面。这是使您的项目广泛可用的最早步骤之一,并且在设置其他安装方法(如Homebrew)时将有很大帮助。但是,弄清楚交叉编译并不容易,尤其是在通过 GitHub Actions 进行交叉编译时。

在这篇文章结束时,你将拥有一个完全有效的部署管道,它会自动创建版本并将二进制文件上传到适用于 Windows、Linux 和 macOS 的版本。你将有一个如下所示的发布页面:

assets

在此过程中,您甚至可能会学到一些有关 GitHub Actions 的信息!

workflow 流触发器

首先,我们需要运行我们的 Actions 的条件。我们希望我们的发布器在将新 git tag 推送到 GitHub 时自动运行。不仅如此,我们还希望它是一个版本标记(如 1.0.0 或 0.2.4),因此 GitHub 不会在我们推送版本无关的 git tag 时运行我们的操作。

这可以通过以下代码完成:

# .github/workflows/deploy
# Just setting the name of our action
name: Deploy

on:
  push:
    tags:
      # Regex for a version number such as 0.2.1
      - "[0-9]+.[0-9]+.[0-9]+"

好了,说完了,让我们来看看我们的 Actions 实际上会做什么!

构建 Actions

若要跨平台编译二进制文件,我们需要创建一个新 workflow。我们称之为 build-and-upload ,因为此 workflow 还将负责构建二进制文件并将其上传到新版本。workflow 是一系列的步骤,而步骤就是我们的 Actions 所做的任何事情!

定义元数据

让我们开始编写我们的 workflow。首先,我们需要在进入步骤之前定义元数据。

# .github/workflows/deploy.yml
# ...

jobs:
  # ...

  build-and-upload:
    name: Build and upload
    runs-on: ${{ matrix.os }}

    strategy:
      matrix:
        # You can add more, for any target you'd like!
        include:
          - build: linux
            os: ubuntu-latest
            target: x86_64-unknown-linux-musl

          - build: macos
            os: macos-latest
            target: x86_64-apple-darwin

          - build: windows-gnu
            os: windows-latest
            target: x86_64-pc-windows-gnu

您可能会注意到 matrix 矩阵是一个重要的部分,因为这意味着我们的 workflow 将针对矩阵中包含的每个项目运行。您可以看到矩阵在 runs-on 键中使用,这意味着我们的 workflow 每次都会在不同的操作系统上运行,其中操作系统由每个矩阵项的 os 键定义。

安装依赖项

好的,完成后,我们需要开始添加步骤。前两个很简单:只需克隆我们的存储库并安装 Rust!

# .github/workflows/deploy.yml
# ...

build-and-upload:
  # ...
  steps:
    - name: Clone repository
      uses: actions/checkout@v3

    - name: Install Rust
      # Or @nightly if you want
      uses: dtolnay/rust-toolchain@stable
      # Arguments to pass in
      with:
        # Make Rust compile to our target (defined in the matrix)
        targets: ${{ matrix.target }}

这些步骤使用 uses 键。 uses 告诉我们的 Actions 我们正在调用一个外部动作,这是预制的。您甚至可以在 GitHub 上看到我们正在调用的操作! actions/checkout 是一个常见的 Action - 它克隆了我们的存储库。操作的参数使用 with 键传入。我们在 dtolnay/rust-toolchain 中利用这一点来告诉它我们的 Rust 程序应该编译到什么目标。

获取版本

我们需要采取的另一个“准备步骤”是获取版本。我们稍后会使用这些信息!

# .github/workflows/deploy.yml
- name: Get the release version from the tag
  shell: bash
  run: echo "VERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV

您可能会注意到 shell: bash ,而不是 uses 键。这基本上告诉 GitHub Actions 我们将运行一个 shell 脚本。之后,我们使用 run 键来指定我们实际要执行的内容。

脚本本身将标签中的版本放入环境变量 $VERSION 。使用环境变量是获取相互通信步骤的一个非常常见的习惯用法,因为我们现在可以访问我们的版本标记名称(如“0.1.0”),对于此 workflow的其余部分,使用 ${{ env.VERSION }}

构建二进制文件

好的,现在我们可以开始构建二进制文件了。为此,我们将使用 cross,这是一个 Rust 工具,它使用 Docker 跨平台构建您的项目。已经有一个超级简单的 Action 允许我们使用 cross !

让我们把它放在我们的步骤列表中:

# .github/workflows/deploy.yml
- name: Build
  uses: actions-rs/cargo@v1
  with:
    use-cross: true
    command: build
    args: --verbose --release --target ${{ matrix.target }}

此步骤告诉 GitHub Actions ,我们希望使用 cross 构建项目。与 cargo build 类似,我们的二进制文件将以 target//release/<BINARY_NAME> 结尾,其中 是矩阵的 target 键。二进制文件的名称显然取决于您的项目。

压缩二进制文件

我们快完成了!现在我们已经构建了二进制文件,我们需要将它们压缩成 .tar.gz 文件(或 .zip ),以便更容易从我们的资产页面下载它们。让我们通过此步骤执行此操作:

# .github/workflows/deploy.yml
- name: Build archive
  shell: bash
  run: |
    # Replace with the name of your binary
    binary_name="<BINARY_NAME>"

    dirname="$binary_name-${{ env.VERSION }}-${{ matrix.target }}"
    mkdir "$dirname"
    if [ "${{ matrix.os }}" = "windows-latest" ]; then
      mv "target/${{ matrix.target }}/release/$binary_name.exe" "$dirname"
    else
      mv "target/${{ matrix.target }}/release/$binary_name" "$dirname"
    fi

    if [ "${{ matrix.os }}" = "windows-latest" ]; then
      7z a "$dirname.zip" "$dirname"
      echo "ASSET=$dirname.zip" >> $GITHUB_ENV
    else
      tar -czf "$dirname.tar.gz" "$dirname"
      echo "ASSET=$dirname.tar.gz" >> $GITHUB_ENV
    fi

这一步很长,但并不复杂。让我们来看看它是如何工作的:

  • 创建一个我们最终将压缩的目录(如 prj-0.2.1-x86_64-apple-darwin )
  • 将我们构建的二进制文件移动到该目录中。名称因操作系统而异
  • 压缩。Windows 使用 .zip ,Unix 使用 .tar.gz
  • 将该压缩文件的路径放入名称为 $ASSET 的环境中

您可以在此处看到我们的 $VERSION 环境变量正在发挥作用!

上传二进制文件

压缩二进制文件后,我们就可以上传它们了!这是我们 workflow 的最后一步!

# .github/workflows/deploy.yml
- name: Upload the binaries
  uses: softprops/action-gh-release@v1
  with:
    files: |
      ${{ env.ASSET }}

与其他一些步骤相比,这个步骤很容易理解。我们要做的就是使用预制的 softprops/action-gh-release 上传我们的压缩文件(使用我们在上一步中创建的 $ASSET 环境变量)。

我们完成了!每当我们使用 2.1.0 这样的标签推送到 GitHub 时,我们都会创建一个版本,构建我们的跨平台二进制文件,然后上传它们!

总结 + 最终代码

我希望这篇文章内容丰富且有趣,祝您部署未来的项目好运!如果这篇文章有任何问题,请在 GitHub 上提交问题。

这是最后的操作,只是为了更好地衡量:

# .github/workflows/deploy.yml
name: Deploy

on:
  push:
    tags:
      - "[0-9]+.[0-9]+.[0-9]+"

permissions:
  contents: write

jobs:
  build-and-upload:
    name: Build and upload
    runs-on: ${{ matrix.os }}

    strategy:
      matrix:
        # You can add more, for any target you'd like!
        include:
          - build: linux
            os: ubuntu-latest
            target: x86_64-unknown-linux-musl

          - build: macos
            os: macos-latest
            target: x86_64-apple-darwin

    steps:
      - name: Checkout
        uses: actions/checkout@v3

      - name: Get the release version from the tag
        shell: bash
        run: echo "VERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV

      - name: Install Rust
        # Or @nightly if you want
        uses: dtolnay/rust-toolchain@stable
        # Arguments to pass in
        with:
          # Make Rust compile to our target (defined in the matrix)
          targets: ${{ matrix.target }}

      - name: Build
        uses: actions-rs/cargo@v1
        with:
          use-cross: true
          command: build
          args: --verbose --release --target ${{ matrix.target }}

      - name: Build archive
        shell: bash
        run: |
          # Replace with the name of your binary
          binary_name="<BINARY_NAME>"

          dirname="$binary_name-${{ env.VERSION }}-${{ matrix.target }}"
          mkdir "$dirname"
          if [ "${{ matrix.os }}" = "windows-latest" ]; then
            mv "target/${{ matrix.target }}/release/$binary_name.exe" "$dirname"
          else
            mv "target/${{ matrix.target }}/release/$binary_name" "$dirname"
          fi

          if [ "${{ matrix.os }}" = "windows-latest" ]; then
            7z a "$dirname.zip" "$dirname"
            echo "ASSET=$dirname.zip" >> $GITHUB_ENV
          else
            tar -czf "$dirname.tar.gz" "$dirname"
            echo "ASSET=$dirname.tar.gz" >> $GITHUB_ENV
          fi

      - name: Release
        uses: softprops/action-gh-release@v1
        with:
          files: |
            ${{ env.ASSET }}