前几个月看到群友在自己的 Unity 的 repo 上用 Github Actions 自动编译可执行文件,当时心想我的项目似乎也可以这样搞。于是刚好这两天心想写篇年终总结啥的,发现自己上个月刚换的电脑没编译博客引擎,顺带把这个 actions 给他整了。
我博客引擎的代码在 nutr1t07/gcwdr,这是一个用 stack 管理的 Haskell 项目。尽管现在 stack 已经被众多 haskeller 所唾弃,但是这个工具对我一个显而易见的新手来说还是非常方便的。stack 会在项目目录下生成 package.yaml
,这是 stack 的项目配置文件。同时 stack 会根据这个文件对应相同构建信息的 cabal 文件 <project-name>.cabal
。实际上,stack 就是利用 cabal 进行构建的。
总之 Github Actions 的市场上有现成的 Haskell CI 可用,不过它是用 cabal 工具来构建的。但是没关系,我的项目文件里有 cabal 的文件给它用。
所以我就直接从上面弄一个模板下来了。他的模板长这样:
name: Haskell CI
on:
push:
branches: [ "master" ]
pull_request:
branches: [ "master" ]
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-haskell@v1
with:
ghc-version: '8.10.3'
cabal-version: '3.2'
- name: Cache
uses: actions/cache@v3
env:
cache-name: cache-cabal
with:
path: ~/.cabal
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/*.cabal') }}-${{ hashFiles('**/cabal.project') }}
restore-keys: |
${{ runner.os }}-build-${{ env.cache-name }}-
${{ runner.os }}-build-
${{ runner.os }}-
- name: Install dependencies
run: |
cabal update
cabal build --only-dependencies --enable-tests --enable-benchmarks
- name: Build
run: cabal build --enable-tests --enable-benchmarks all
- name: Run tests
run: cabal test all
workflow 的语法很好懂,这个模板的意思大概就是:构建一个只要求'读'权限的 action,该 action 在每次 push 和有新 pull request 的时候触发;action 内有一个 job,它将按步骤执行steps里的每一个步骤。
当然这是用来自动构建的 Workflow 文件,它是用来测试项目是否能够成功构建的,如果构建成功,那么它会给你这次 commit/pr 打个勾;否则给你打个叉。
不过在这个基础上我们可以让他进行自动发布可执行文件。比如在每次推送 vX.X.X
的 tag 时候,让他把编译好的可执行文件发布到 release 页上。
github-gh-release
actions 上当然也已经写好了这类应用的包。在过去前辈们用的是 actions/upload-release-asset,不过这个项目在 Nov 9, 2022 的时候已经遗憾归档了。
在归档的项目页面提到有 softprops/action-gh-release 仍在积极维护中。那我们就用这个了。
首先这个包要求项目的写权限,所以得在开头把权限的 read
改成 write
。然后就是调包侠最爱干的事情了,直接在 steps 里加上要发布到 release 页里的文件就行了。
- name: Release
uses: softprops/action-gh-release@v1
with:
files: |
README.txt
xxx-xxx-xxx-binary
但是这个对比 actions/upload-release-asset
比较不人性化的一点就是不能在发布的时候改文件名。如果像我一样想跨平台编译多个可执行文件,就得先用 mv
把文件名改了。比如,我们编译完得到一个 out
文件( Windows 下可能是 out.exe
),我们可以这样写:
- name: Set binary path name (Unix)
run: |
mv ./out ./${{ runner.os }}-out
echo "BINARY_PATH=./${{ runner.os }}-out" >> $GITHUB_ENV
上面这个 step 在 Unix 下工作得很完美。Github 会自动帮你填上 runner.os
里的内容,比如当前 job 在 ubuntu-latest
上运行,那 runner.os
就会是 Linux
。在重命名完文件名后,还需要设置一个环境变量来记录文件的位置,也就是第二行的内容。然而:在 Windows 的 powershell 上,你不能写 $GITHUB_ENV
,而是应该写成 $env:GITHUB_ENV
。所以在 Windows 版本上,我们要改成下面这样:
- if: runner.os == "Windows"
name: Set binary path name (Windows)
run: |
mv ./exe ./windows-exe
echo "BINARY_PATH=./windows-exe" >> $env:GITHUB_ENV
设置完 BINARY_PATH
这个环境变量,接下来就可以进行发布了。
- name: Release
uses: softprops/action-gh-release@v1
with:
files: ${{ env.BINARY_PATH }}
Matrix
如果想要在一个 job 里实现跨平台编译,那么 workflow 提供的 strategy matrix 就可以在此时派上用场。
在 job 内定义要编译的 os matrix ,之后 job 就会依次将 matrix 的字段替换成相应的值并重复执行。比如下面的 job 会执行三次,分别将 ${{ matrix.os }} 替换成 ubuntu-latest, macOS-latest, windows-latest 。
build:
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macOS-latest, windows-latest]
runs-on: ${{ matrix.os }}
这里 strategy 中 fail-fast
字段控制 job 是否在某个 matrix 任务失败后就立刻停止所有任务。我们想要在某个平台编译失败后继续编译其他平台的文件,因此我们将其设置为 false
。
upx-action
通常用 Haskell 写的项目编译出来的可执行文件都非常大。但我们可以在发布之前将编译完成的文件用 upx
1
压缩一遍。令本调包侠非常开心的是同样有现成的包可以用,它叫做 svenstaro/upx-action 。
于是在编译结束后调用一次 upx 就可以了。
- name: Compress binary
uses: svenstaro/upx-action@2.0.1
with:
file: ${{ env.BINARY_PATH }}
经过 upx 压缩的可执行文件或库文件通常能减少 50%~70%
的体积。
拼在一起
就变成了 https://paste.sr.ht/~u2x1/687b34785f784cc2f73715812b9fa443e142426a。
于是万能的 Github Actions 现在会自动在每一次 git push origin v*.*.*
的时候帮你发 release 了。