Posts Jekyll 集成 Travis CI
Post
Cancel

Jekyll 集成 Travis CI

前段时间,因为本地push --force覆盖远端 master 分支,导致博客在 GitHub Pages 上编译失败。查看 GitHub Help 的文档 “Viewing Jekyll build error messages”,文中提及可以通过第三方平台执行 build,直接观察错误信息细节,这才开始认识了本文主角:Travis CI,又一个新世界向笔者打开了。

Travis CI 是个提供持续集成的服务平台,对 GitHub 开源项目免费,它可以自定义配置编译、测试到发布全套流程。所以只用来观察编译的日志,实在太浪费了。

在此之前,本站项目推送到 GitHub 之前,都会先干两件事:

  1. 检查每篇文章的最后更新时间
  2. 生成所有 Category 和 Tag 的独立页面

第一点“文章更新时间”可以用第三方 Jekyll 插件实现。直接推送 Jekyll 项目源码到仓库根目录,是由 GitHub Pages 执行构建,但它开启了safe模式,第三方的 Jekyll Plugins 是不允许运行的,所以笔者写了 Python 脚本去完成这两个初始化工作,每次写完文章执行 commit 之后,本地运行一下初始化脚本,然后 push 到远端。初始化脚本会对本地硬盘进行 I/O 操作,理论上会损耗硬件寿命。Mac 价格逐年创新,必须加倍疼爱手上的机器。现在看来,将这两任项务交给 Travis CI,那是再合适不过了。

:Travis CI 只对 GitHub 上的项目提供 CI 服务,其他代码托管平台只能移步。

经过进一步的考究,GitHub Pages 支持静态文件托管,因此可以把站点构建生成的静态站点文件(_site的内容)放到一个空白仓库中,由它开启 Pages 服务即可。站点构建托付给 Travis CI,绕开 GitHub Pages,这意味着可以任意使用第三方 Jekyll Plugins。

最后,项目源码和发布的静态资源分成两个仓库管理,最初的代码库仓库 USER.github.io可以不受约束改成自己喜欢的名字,而且代码库的master分支也解放出来了( GitHub Pages 强制占用 User and Organization Pages sitesmaster分支)。

所以改造工程将包含:

  • 项目源码和项目部署分仓
  • 源码仓库集成 Travis CI
  • GitHub 授权
  • Travis CI 开启服务

GitHub 分仓

Step 1. 在 GitHub 上把USER.github.io仓库重命名为USER-blog,用于存放源码。

Step 2. 本地 Git 配置要做更改:

1
2
3
$ cd /path/to/repository
$ git remote set-url origin git@github.com:USER/USER-blog.git
$ git remote set-url --push origin git@github.com:USER/USER-blog.git

上述命令把本地 git 的fetchpush远程地址修改为新库地址,USER为 GitHub 用户名。

Step 3. GitHub 新建仓库USER.github.io,没有错,替代 Step 1 的旧仓库名称,用于存放构建输出的静态文件。切记为这个仓库开启 Pages 服务。

下面详细介绍源码仓库USER-blog接入 Travis CI 的步骤。

Jekyll 配置

Gemfile

首先要确保项目根目录包含一个Gemfile文件, 没有则新建,首行内容如下:

1
source "https://rubygems.org"

项目中依赖的 Jekyll Plugins,在Gemfile中添加依赖声明:

1
2
3
4
5
group :jekyll_plugins do
  gem "plugin-a"
  gem "plugin-b"
  ...
end

: 如果此前依赖插件声明在_config.ymlplugins数组,虽然 Jekyll 允许 _config.ymlGemfile 同时声明插件,但是bundle install只读Gemfile的内容,建议移除从_config.yml中的plugins变量。

笔者 Gemfile 全部配置如下:

1
2
3
4
5
6
7
8
9
10
11
source "https://rubygems.org"

group :jekyll_plugins do
  gem "jekyll-paginate"
  gem "jekyll-feed", "~> 0.6"
  gem "jekyll-redirect-from"
  gem "jekyll-last-modified-at" # 3rd-party plugins
end

# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]

: 如果此前项目本地模拟 GitHub Pages 执行构建,可能会包含下面一行内容:

1
gem "github-pages", group: :jekyll_plugins

现在不再需要它了,删除即可。

_config.yml

_config.yml 中若没有显式声明exclude,以下文件列表会从默认构建中略过:

1
2
3
4
5
6
7
8
exclude:
   - Gemfile
   - Gemfile.lock
   - node_modules
   - vendor/bundle/
   - vendor/cache/
   - vendor/gems/
   - vendor/ruby/

但是如果自定义了exclude覆盖默认列表,则确保要包含

1
exclude: vendor

因为 Travis CI 的容器会将 gems 包存放在构建路径的vendor目录之下。例如,运行下列命令便会出现这种情况:

1
$ bundle install --deployment

另外,自定义的exclude列表,也应包含对站点无用的其他工程文件:

1
2
3
4
exclude:
  - Gemfile
  - Gemfile.lock
  - README.md

.travis.yml

项目根目录建一个文件,命名为.travis.yml,有了它 Travis 才会自动对项目进行抓取编译等工作。笔者配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
language: ruby
rvm: 2.4.1
install:
  - bundle install --path vendor/bundle 
  - pip install --upgrade pip   # Travis CI asked for it.
  - pip install ruamel.yaml
script: bash ./scripts/cibuild.sh
branches:
  only: master
cache: bundler
notifications:
  email:
    recipients:
        - secure: ENCRYPT_EMAIL_ADDRESS_STRING
    on_success: never
env:
  global:
    - secure: ENCRYPT_KEY_VALUE_PAIRE

上述配置首先languagervm描述了 Travis 虚拟机中的主语言环境。

接着执行流程是:installscript

install部分安装了 Jekyll 环境和后面脚本需要的 python 依赖。

script声明了 Travis 应该执行命令。

特别说明一下env.global的内容,里面是一个 Travis 客户端加密后的 GitHub Token 键值对。cibuild.sh中的向 GitHub 推送代码需要用这个 token 来通过 GitHub 的权限认证。

GitHub Token

GitHub Token 申请可访问设置页面新建,Select scopes 部分选取repo的全部权限。

github-token-scopos

这个 token 十分重要,如果不想让人在自己仓库涂鸦,须谨慎保存。Travis Web 提供Envrionmen 变量的加密存储,为了减少网上泄漏的风险,还是建议自己本地加密,再将加密串写入.travis.yml

本地安装 Travis Client,执行加密:

1
2
$ cd /path/to/repository
$ travis encrypt GITHUB_TOKEN=<your-github-access-token>

: 上述命令命令末端加--add指令,加密结果会自动添加到.travis.yml文件末尾的env.global

将加密结果拷贝到.travis.ymlenv.global下,用secure表明是一个加密字符串。笔者用这个方式还加密了build结果通知的邮件地址。

Coding Token (可选)

如果想同时部署到 Coding Pages,则还要建立一个 Coding 的 Token,官方称作“访问牌令”,创建入口在 Coding 的个人账户即可找到:

Coding-Token

界面几乎是 GitHub 的中文版,选择权限 部分选取 project:depot

Travis 加密

获得的 token 同样也是安全敏感数据,采用本地 Travis-Cli 加密,存入env.global即可:

1
2
3
4
env:
  global:
  - secure: "yHH2GnrRs6aUmqu4t7dRV8L..."  # GitHub token
  - secure: "RTg3SeugMzxrkzfLBU8zHpa..."  # Coding token

cibuild.sh

script部分执行脚本cibuild.sh,Travis 构建和部署的核心逻辑就定义在此。内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#!/bin/bash

GITHUB_DEPOLY=https://${GITHUB_TOKEN}@github.com/cotes2020/cotes2020.github.io.git
CODING_DEPOLY=https://cotes:${CODING_TOKEN}@git.dev.tencent.com/cotes/cotes.coding.me.git

# skip if build is triggered by pull request
if [ $TRAVIS_PULL_REQUEST == "true" ]; then
  echo "this is PR, exiting"
  exit 0
fi

# enable error reporting to the console
set -e

if [ -d "_site" ]; then
  rm -rf _site
fi

## Check lasmod of posts and the Category, Tag pages.
export TZ='Asia/Shanghai' # the lastmod detection needs this
echo "date: $(date)"
python ./scripts/pages_generator.py

# build Jekyll ouput to directory ./_site
JEKYLL_ENV=production bundle exec jekyll build

## Git settings
git config --global user.email "travis@travis-ci.org"
git config --global user.name "Travis-CI"

DEPOLYS=(${GITHUB_DEPOLY} ${CODING_DEPOLY})

for i in "${!DEPOLYS[@]}"
do
  echo "TRAVIS_BUILD_DIR=${TRAVIS_BUILD_DIR}"
  cd ${TRAVIS_BUILD_DIR}

  if [ -d "../repos_${i}" ]; then
    rm -rf ../repos_${i}
  fi

  git clone --depth=1 ${DEPOLYS[${i}]} ../repos_${i}

  rm -rf ../repos_${i}/* && cp -a _site/* ../repos_${i}/
  cd ../repos_${i}/

  git add -A
  git commit -m "Travis-CI automated deployment #${TRAVIS_BUILD_NUMBER}."
  git push ${DEPOLYS[${i}]} master:master

  echo "Push to ${DEPOLYS[${i}]}"
done

bash脚本执行了笔者用 python 实现的 Category 和 Tag 页面生成工具,然后执行 Jekyll 构建。

要注意的一点是,构建需要声明 Jekyll 环境变量JEKYLL_ENV=production,因为它关系到<head>加载 Google Analytic 埋线的逻辑:

1
2
3
{% if jekyll.environment == 'production' %}
  {% include google-analytics.html %}
{% endif %}

成功后把输出的静态文件部署到USER.github.ioUSER.coding.me两个远程仓库。每次 commit message 用 Travis CI 自动生成的构建号${TRAVIS_BUILD_NUMBER}标识。

Travis CI 官网配置

Travis CI 官网只需要用 GitHub 账号授权登陆就行了,仓库管理页面开启选定的仓库,Travis CI 就会开始监听仓库。每次往仓库推送代码,都会触发自动构建、部署:

travis-builds

点击build|passing的徽章,还可以生成指定格式的状态图标链接,如 Markdown 格式:

travis-status-badge

生成地址拷贝到项目 README,可以方便的看到项目构建状态。Travis CI 的 Web 界面上有很多配置小细节,根据个人业务需求作个性化选择。

现在,整个接入工作完毕,从今以后,便可尽情地往源码仓库提交更新,Travis CI 会在背后矜矜业业的工作,构建错误会邮件通知,否则,就会自动部署最新的内容到 Pages 服务。一切悄无声息的进行,岂不美哉。

CI 测试

通常在编写 CI 自动化脚本时,免不了经历开发阶段的测试。本地没法模拟 Travis CI 线上虚拟服务的环境,只能把测试阶段的脚本提交到 Travis CI,这会产生过多无意义的 build 记录。Travis CI 只支持清空 build 内部的输出日志,却不能完全删除某个 build 记录,所以这显然是不太稳妥的选择。

其实可以用一个小技巧避开这种烦恼,首先在 GitHub 上创建工作仓库的一个副本,例如正在开发的项目为AwesomeProj,那么可以先创建一个副本仓库,命名为AwesomProj-CI,然后在这个项目上面集成 Travis CI 服务,集成期间可以尽意地推送到 Travis CI 上测试效果,直至脚本和配置成熟了,再拷贝到原始仓库AwesomeProj,然后删除临时仓库AwesomProj-CI即可,Travis CI 服务器就会自动清掉临时项目的所有记录,而AwesomeProj项目在 Travis CI 上没有任何测试性的 build 记录,纯洁美观。

片尾花絮

修改 Git 远程地址对 Travis 加密的影响

假设你在 Git 仓库调用过 Travis 本地加密命令travis encrypt,那么仓库的.git/config就会被写入变量travis.slug,内容是:

1
username/repository

username为用户名,repository为仓库名。

本地修改 Git 远程地址后,上述travis.slug的值是不会改变的。若此时再调用 Travis CLI 加密,Travis CI 服务端就不能正确解密。 正确的做法应该是:将usernamerepository更改成于新地址一致的内容,再执行travis encrypt:

1
$ git config travis.slug new-username/new-repository

Travis 的两个站点

请注意 Travis CI 有两个官网

.org域名是对开源项目提供服务的,.com域名是对私有项目提供服务。

笔者之前用同一个 GitHub 账号在两个域名都关联了一次。结果悲催了:每次 git push,就会收到 Travis 两个不同 build number 的邮件通知,.org域名的 “passing”,.com域名的 “failling”,因为没发现域名的不同,这个乌龙问题困扰了笔者两三天,后来在 GitHub 取消对.com域名授权就解决了。

参考链接

OLDER POST NEWER POST

Comments powered by Disqus.

Search Results