真的很抱歉咕咕咕了这么久才发文章!因为一直在忙自己项目相关的事儿,就没咋管博客了(汗),不过咱人没失踪!所以!既然拖了辣么久,就决定了要拉来了个大的!(`ヮ´ )
什么是GitHub Actions?
如果项目需要编译、打包后才能变成能够被执行的可发行的软件,通常项目会需要一个CI(Continuous Integration,即持续集成)环境,以在特定事件(如一次提交或者PR)之后,实时对项目进行编译、打包。
比如有些软件的Nightly或Canary版,甚至每提交一次push就自动构建一个版本,这样的版本实际上就是CI版啦。
以往多数人会选择使用Jenkins等框架在本地或者私有服务器上部署,或者通过Travis CI、CircleCI等平台实现云端CI。作为全球最大的代码托管库,GitHub相应地也自己也推出了CI/CD服务,即传说中的GitHub Actions。
对咕咕这样不愿意到处跑去创建项目、复制粘贴ID和KEY的懒人来说,Actions开箱即用的特点,的确太太太太方便了!当然啦,毕竟咱技术能力有限,所以用个CI其实完完全全很够了。
快速上手
如果你的项目的CI要跑的命令很大众,无需过多自定义规则或配置,那就可以直接到官方的插件市场,找别人造好的轮子,导入进你的项目,大功告成!
慢速上手
创建工作流
通常一个大型项目会同时拥有好几个工作流(即workflow),像编译应用、评论审批、编译特定组件之类的工作,多是每一个工作由单独的工作流完成。而我们的小型项目最多也就编译打包下App,一个工作流足矣。
由于Actions的工作流使用YAML文件进行配置,所以你需要了解一些基本的YAML前置常识。当然看不全懂没关系啦,只要有一点了解就行。
给工作流起好一个名字,在项目根目录下创建目录.github/workflows,然后在该目录创建相应的yml配置文件。
如图配置,当我们在向项目push一个commit的时候,就会启动这个工作流。按照预期,GitHub会请求自己托管的专用于Actions环境的服务器(即runner),来执行该工作流的内容。
打开导航栏的“Actions”一项,打开刚刚请求的工作流,在“test”流程的“test01”步骤里,控制台中果然输出了“Hello, world!”这段字符串。
设置名称和触发条件
name
字段是这个工作流的实际名称,用处似乎就是区分不同的工作流而已ᕕ( ᐛ )ᕗ
而on
字段用于设置该工作流执行的触发条件,常见的有:push
、pull_request
、issue_comment
、release
等。
# 当 push commit 时触发工作流
on: push
# 当 push 到 dev 和 main 分支时触发工作流
on:
push:
branches: [dev, main]
# 当 push commit 或新增 PR 时都触发工作流
on: [push, pull_request]
# 手动触发工作流
on: workflow_dispatch
配置工作流程
在jobs
字段中,每一个流程都是独立的,只有数据可以进行迁移(稍后会谈),所以我们需要单独为每个流程设定相关配置。
runs-on
字段用于规定该流程运行的操作系统。一般情况下咱顶多使用以下三个值之一——windows-latest
、ubuntu-latest
、macos-latest
——用于编译不同操作系统下的二进制文件。
接下来就是各个步骤(即steps
字段)的内容!按照之前咱放的示例图,咕咕自己习惯将每个步骤开头冒号前的内容,叫做这个步骤的“步骤ID”,它是独一无二的,相当一个代表这个步骤的变量。
进入到具体的步骤中,首先是name
字段,它能让每个步骤都有一个用于在Actions页面直观展示的名称。
其次就是id
字段,一般推荐用小写字母+短横线连字符的方法命名,用于后面的数据交互!
最后就是run
字段,就和咱在控制台上敲命令一样了,打根竖线再提行,一行写一个命令。
好嘞,完工!
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: 'Test Output'
id: test-output
run: |
echo 'Hello, world!'
在某个步骤使用他人创建的Actions仓库
打个比方,咱跑前端项目需要设置好Node.js环境,但从下载Node二进制包到完成部署,即使在实际操作中也不可能不繁琐,更何况Actions这种需要盲打命令的情况。既然所有工作不可能全部手撸,能重复利用轮子的地方还是多多利用轮子的好!
所以Actions为我们提供了使用他人Actions仓库的功能。在步骤中,通过uses
字段代替原有的run
,咱就能使用他人仓库的Actions脚本,写得就更快,写出来的配置文件就更加简洁易懂。
# 将项目源码 clone 到本地
- name: 🧐 Checkout
id: checkout
uses: actions/checkout@main
with:
fetch-depth: '0'
# 部署 Node.js 环境
- name: 🛠️ Setup Node.js Environment
id: setup-node
uses: actions/setup-node@v4
with:
node-version: latest
如上述示例,uses
字段中填写的应该是GitHub的项目,格式为“项目拥有者/项目名称”,同时需要用“@”符号指定要执行哪一个分支或标签上的脚本,查看相关Actions脚本仓库的分支/标签填入即可。
同时,如上述setup-node
脚本需要传入node-version
参数才能正常执行,所以咱需要通过with
字段来传递参数。
而checkout
就相当于git clone
的封装,故fetch-depth
参数可根据需要改动或去除。咕咕此处添加,是因为当深度为0时才能正常获取项目当前分支的commit数,这是项目所需要的。
既然都用上Node.js了,不妨测试下它是否正常运行。
// test.js 注:此文件在根目录
const v = process.versions
console.log('当前 Node.js 版本:' + v.node)
console.log('当前 v8 引擎版本:' + v.v8)
console.warn('Not hello, world!')
// .github/workflows/test.yml 节选
- name: 👀 Test about Node.js
id: test
run: |
node test.js
完美执行!
接下来我们就可以直接写构建生产版本的命令了!
上传构建好的文件
每次运行工作流的编译/构建好的东西叫作产物(即artifact),GitHub官方提供了一个Actions仓库——upload-artifact,用于给我们上传本次工作流的产物。
此Action脚本一般有两个参数——name
和path
。name
参数规定了这个上传的artifact会展示为什么名字,而path
参数规定了要上传的artifact位于工作目录下哪一个路径,并将它打包。(注:即使这个路径指向一个文件而非文件夹,最终也是以.zip压缩包的形式上传)
由于咱项目打包好的文件位于dist文件夹内,所以最终如下配置:
- name: 🌐 Upload the build
id: upload
uses: actions/upload-artifact@v4
with:
name: dist
path: dist
# 此参数为可选参数,规定产物的有效天数,过期后不可下载
retention-days: 30
不过注意了,此处产物的大小是以原始大小计算,而非压缩后大小计算,真是莫名其妙。
多job信息传递
导出变量
为了在job中使用前一个job获取到的信息,这时我们就需要在step中先将变量导出,再在job的output
字段里将该变量输出。
现在Actions的变量导出方式和Linux中一致,通过echo实现。比如,我们要获取当前时间并导出,则可以:
steps:
- name: Get current time
id: get-time
run: |
TIME_VAR=$(date "+%Y%m%d %H:%M:%S")
echo "TIME_EXPORT=${TIME_VAR}" >> $GITHUB_OUTPUT
然后再在上级的job中通过outputs
字段将其输出,以给其它job使用:
jobs:
# 创建 id 为 setup 的 job,以将导出的数据输出,get-time 步骤就在其中
setup:
name: Setup Information
runs-on: ubuntu-latest
outputs:
# get-time 是导出数据的步骤的 id,TIME_EXPORT 是导出的数据的变量名称
TIME_OUTPUT: ${{ steps.get-time.outputs.TIME_EXPORT }}
steps: ......
其它job中使用变量
Actions允许我们在jobs的env
字段中,预置该job中可用的变量。同样地,咱可以先从needs
字段中接收之前的job输出的内容,再在env
字段中将其设为可用的变量。
jobs:
setup:
......
print:
name: Print Information
runs-on: ubuntu-latest
needs: setup
env:
# 为该 job 的运行环境添加一个名为 TIME_ENV 的变量,变量的值来自于 id 为 setup 的 job 所输出的名为 TIME_OUTPUT 的变量
TIME_ENV: ${{ needs.setup.outputs.TIME_OUTPUT }}
steps: ......
接下来,我们就能在在该job下任一步骤中使用变量了!
steps:
- name: Print Time
id: print-time
run: |
echo ${{ env.TIME_ENV }}
那么我们就能顺利地在控制台看到时间被打印出来了👀
注:上述变量名称可以自定,不一定非要加后缀,此处加上只是为了方便辨析,如果要变量全叫同一个名字也没问题。
实例
我们一般做这样的简单项目,多job信息传递的最大用处就是更改产物上传所显示的名字,以达到辨别版本的目的。
首先在之前“创建生产页面”的Action的基础上,在build之前创建一个id为setup的job,以获取基本信息。
setup:
name: Setup Project Info
runs-on: ubuntu-latest
outputs:
DIST_SUFFIX: ${{ steps.get-suffix.outputs.DIST_SUFFIX }}
steps:
- name: 🧐 Checkout
id: checkout
uses: actions/checkout@v4
with:
fetch-depth: '0'
- name: 📙 Get Package Suffix
id: get-suffix
run: |
# 获取当前版本,此处直接从 package.json 读取
sudo apt-get install jq
VERSION=$(jq -r '.version' ./package.json)
# 获取 commit 数
COMMIT_COUNT=$(git rev-list --count HEAD)
# 获取构建日期
BUILD_DATE=$(date "+%y%m%d")
# 获取构建 commit head 哈希
HEAD_SHA=$(git rev-parse --short HEAD)
echo "DIST_SUFFIX=${TAG_NAME}-${COMMIT_COUNT}-${BUILD_DATE}-${HEAD_SHA}" >> $GITHUB_OUTPUT
build:
name: Build Production Page
needs: setup
env:
DIST_NAME: dist-${{ needs.setup.outputs.DIST_SUFFIX }}
runs-on: ubuntu-latest
steps:
......
- name: 🌐 Upload the build
id: upload
uses: actions/upload-artifact@v4
with:
name: ${{ env.DIST_NAME }}
path: dist
retention-days: 60
可以看到产物名称已经规范ᕕ( ᐛ )ᕗ
好耶!完结撒花!