使用 Travis CI 自动维护软件仓库 (Homebrew)

7 min read

前段时间的一篇文章介绍了如何使用 Homebrew 建立自己的软件仓库,目的是让大家能轻松的发布软件。但同时如何让仓库里的软件保持最新又成为了新的问题。

我一直没有找到合适的答案,直到看到了这个 Issues。受此启发,我编写了一个利用 Travis CI 自动更新 Formula 的 shell 脚本。它的基本原理就是利用 Travis CI 的 Cron Jobs 功能,每天定期运行更新脚本,如果有更新,就根据修改 Formula 脚本,并推送到 Tap (GitHub) 仓库。

不同的仓库需要修改脚本以适配不同的项目,但我会在后文中详细介绍脚本是如何工作的,保证你读完后也能编写出自动维护 Formula 的脚本。

完整的更新脚本参考和 .travis.yml附录里。

手动更新

首先我们要了解一下如何手动更新 Formula。还是以 frp 为例。

假如 frp 发布了新的更新,我们首先要去 frp 的 GitHub Releases 页面获取最新版本的源码。

Get_Source

wget https://github.com/fatedier/frp/archive/v0.25.3.tar.gz

然后利用 sha256sum 计算源码的 sha256 校验值。

$ sha256sum v0.25.3.tar.gz
68281965d04567d55f143b4a4c4d4369c1962937d80484b6b48e96a5dcf0b2e4  v0.25.3.tar.gz

再将计算好的校验码和下载链接替换到 frp.rb 中。

总结一下就是:下载最新的源码 -> 计算校验码 -> 替换下载地址和校验码

而我们要做的就是用脚本来执行上面这一过程,让计算机来做重复又无味的工作。

自动更新

在 Tap 的根目录新建一文件,命名为 auto-update.sh

定义变量 (常量)

首先我们定义一些变量 (常量) 来储存必要的信息。

typeset -l FILE_NAME # 将 $FILE_NAME 设为全部小写

AUTHUR_NAME="fatedier" # 软件拥有者的 GitHub 用户名
FORMULA_NAME="frp" # 软件仓库名
FILE_NAME=$FORMULA_NAME # Formula 的文件名 (等于全小写的 $FORMULA_NAME)

为什么要将 $FORMULA_NAME$FILE_NAME 分开呢?因为软件仓库是允许大写的,但 Formula 的文件名不允许

克隆 Git 仓库

虽然我们有一个存放 Formula 的 Git 仓库了,但总觉得在一个镜像仓库中操作文件更放心一点

git clone https://${GH_REF}

获取最新的版本号

我们需要获取最新版本的版本号,可以通过 GitHub API 来获取,我们先在终端中试验一下

$ curl -s https://api.github.com/repos/fatedier/frp/releases/latest
{
  "url": "https://api.github.com/repos/fatedier/frp/releases/16350972",
  "assets_url": "https://api.github.com/repos/fatedier/frp/releases/16350972/assets",
  "upload_url": "https://uploads.github.com/repos/fatedier/frp/releases/16350972/assets{?name,label}",
  "html_url": "https://github.com/fatedier/frp/releases/tag/v0.25.3",
  "id": 16350972,
  "node_id": "MDc6UmVsZWFzZTE2MzUwOTcy",
  "tag_name": "v0.25.3",
  "target_commitish": "master",
  "name": "v0.25.3",
  "draft": false,
  "author": {
    "login": "fatedier",
    "id": 7346661,
    "node_id": "MDQ6VXNlcjczNDY2NjE=",
    "avatar_url": "https://avatars3.githubusercontent.com/u/7346661?v=4",
    "gravatar_id": "",
    "url": "https://api.github.com/users/fatedier",
    "html_url": "https://github.com/fatedier",
    "followers_url": "https://api.github.com/users/fatedier/followers",
    "following_url": "https://api.github.com/users/fatedier/following{/other_user}",
    "gists_url": "https://api.github.com/users/fatedier/gists{/gist_id}",
    "starred_url": "https://api.github.com/users/fatedier/starred{/owner}{/repo}",
    "subscriptions_url": "https://api.github.com/users/fatedier/subscriptions",
    "organizations_url": "https://api.github.com/users/fatedier/orgs",
    "repos_url": "https://api.github.com/users/fatedier/repos",
    "events_url": "https://api.github.com/users/fatedier/events{/privacy}",
    "received_events_url": "https://api.github.com/users/fatedier/received_events",
    "type": "User",
    "site_admin": false
  },
  ...

返回的结果中就包含了我们需要的版本号。我在用 grep 过滤一下,让大家看清楚一点

$ curl -s https://api.github.com/repos/fatedier/frp/releases/latest
  "tag_name": "v0.25.3",

然后用 cut 处理一下字符串,并加上一个 loop 循环 (排除网络环境的影响) 就可以用了。反映到脚本上是这样的

loop_parser(){
    while true
    do
       result=$(curl -s https://api.github.com/repos/"$AUTHUR_NAME"/"$FORMULA_NAME"/releases/latest | grep "$1" | cut -d '"' -f 4)
       if [ ! -z "$result" ]; then
        echo $result
        break
       fi
    done
}

V_VERSION=$( loop_parser "tag_name" )

下载源文件

然后我们将$V_VERSION (版本号) 与 $AUTHUR_NAME$FORMULA_NAME 等信息组合一下就可以获得源文件的下载链接

DOWNLOAD_URL="https://github.com/$AUTHUR_NAME/$FORMULA_NAME/archive/"$V_VERSION".tar.gz"

curl 下载源文件

curl -L  $DOWNLOAD_URL > $FORMULA_NAME.$V_VERSION.tar.gz

计算校验码

然后就可以计算 sha256 校验码了,还是用 sha256sum 计算,用 cut 处理字符串

V_HASH256=$(sha256sum $FORMULA_NAME.$V_VERSION.tar.gz |cut  -d ' ' -f 1)

替换下载链接和校验码

sed 替换 Formula 中需要更新的信息,如果不需要更新,那么替换后还是原来的 Formula。

# 如果 Formula 中有 version 这一变量的话取消下面的注释
# sed -i "s#^\s*version.*#  version \"$V_VERSION\"#g" homebrew-taps/Formula/$FILE_NAME.rb
sed -i "s#^\s*url.*#  version \"$DOWNLOAD_URL\"#g" homebrew-taps/Formula/$FILE_NAME.rb
sed -i "s#^\s*sha256.*#  sha256 \"$V_HASH256\"#g" homebrew-taps/Formula/$FILE_NAME.rb

推送

将更新好的 Formula 文件推送到 GitHub 上 (${GH_TOKEN}${GH_REF} 这两个变量需要在 Travis CI 中添加)

git commit -am "travis automated update" || exit 0
git push  --quiet "https://${GH_TOKEN}@${GH_REF}" master:master

配置 Travis CI

.travis.yml

这次的 .travis.yml 很简单,只需要做两件事:

  1. 设置 git config

  2. 执行放在根目录的 auto-update.sh

before_install:
  - git config --global user.name "$USERNAME"
  - git config --global user.email "$EMAIL"

script:
  - auto-update.sh

完整的 .travis.yml 文件在附录里。

设置环境变量

需要设置以下环境变量

  • $USERNAME:你想记录在 git log 中的名字

  • $EMAIL:你的邮箱

  • ${GH_TOKEN}:GitHub TOKEN,点满 repo 的所有权限 GitHub TOKEN 申请方法

  • ${GH_REF}:你存放 Formula 的 GitHub 仓库的地址,例如:github.com/:owner/:repo

Set ENV

设置 Cron Jobs

设置 Cron Jobs,让 Travis CI 定期运行脚本

Set Cron Jobs

Branch: master  

Interval: daily  

Options: Always run

附录

.travis.yml

language: generic

sudo: required

before_install:
  - git config --global user.name "$USERNAME"
  - git config --global user.email "$EMAIL"

before_script:
  - chmod +x tools/auto-update.sh

script:
  - tools/auto-update.sh

auto-update.sh

#!/bin/bash

typeset -l FILE_NAME # 将 $FILE_NAME 设为全部小写

AUTHUR_NAME="" # 软件拥有者的 GitHub 用户名
FORMULA_NAME="" # 软件仓库名
FILE_NAME=$FORMULA_NAME # Formula 的文件名 (等于全小写的 $FORMULA_NAME)

log(){
    echo ''
    echo '-------------------------------------'
    echo "$*"
    echo '-------------------------------------'
    echo ''
}

loop_parser(){
    while true
    do
       result=$(curl -s https://api.github.com/repos/"$AUTHUR_NAME"/"$FORMULA_NAME"/releases/latest | grep "$1" | cut -d '"' -f 4)
       if [ ! -z "$result" ]; then
        echo $result
        break
       fi
    done
}

git clone https://github.com/Mogeko/homebrew-taps.git

log "parser $FORMULA_NAME download url"

V_VERSION=$( loop_parser "tag_name" )

if [ -z "$V_VERSION" ]; then

    log 'parser file version error, skip update.'
    exit 0

fi

DOWNLOAD_URL="https://github.com/$AUTHUR_NAME/$FORMULA_NAME/archive/"$V_VERSION".tar.gz"

if [ -z "$DOWNLOAD_URL" ]; then

    log 'parser download url error.'
    exit 0

fi

log "download url: $DOWNLOAD_URL  start downloading..."

# wget -c $DOWNLOAD_URL -O $FORMULA_NAME.$V_VERSION.tar.gz
curl -L  $DOWNLOAD_URL > $FORMULA_NAME.$V_VERSION.tar.gz


if [ ! -e $FORMULA_NAME.$V_VERSION.tar.gz ]; then
    log "file download failed!"
    exit 1
fi

V_HASH256=$(sha256sum $FORMULA_NAME.$V_VERSION.tar.gz |cut  -d ' ' -f 1)

log "file hash: $V_HASH256 parser $FORMULA_NAME version..."

log "file version: $V_VERSION start clone..."

log "update config...."

sed -i "s#^\s*version.*#  version \"$V_VERSION\"#g" homebrew-taps/Formula/$FILE_NAME.rb
sed -i "s#^\s*sha256.*#  sha256 \"$V_HASH256\"#g" homebrew-taps/Formula/$FILE_NAME.rb

git commit -am "travis automated update" || exit 0
git push  --quiet "https://${GITHUB_TOKEN}@${GH_REF}" master:master

相关文章