创建一个brew-formula

1. 背景

我们有些情况下需要写一些shell脚本进行执行某些操作(mac环境),一般我们需要进入shell脚本目录在进行执行,或者可以通过alias添加一个别名等方案规避找shell脚本的麻烦。但是当有多个人比如一个团队都需要使用你这个脚本,普通方法就是把脚本给到其他人,其他人再在本地配置使用,但是这种方法多少有些麻烦。一个比较好的解决方案是基于brew创建一个Formula, 其他人可以通过brew install的方式进行安装直接使用,极大简化麻烦。

2. 如何创建及测试

2.1. 示例场景

我在使用brew安装某些应用的使用,由于依赖特别多,可能经常因为超时(brew install时很多安装包(tarball)放在git上)卡在某个不重复的操作上,我不得不盯着安装过程,失败时重新执行安装命令,很麻烦。解决方案:我基于brew封装一个可以重试的脚本叫‘brew-install-with-retry.sh', 并把这个脚本制作一个Formula.

2.2. 前置条件

本地安装了homebrewruby

  • homebrew安装 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
  • 通过brew安装ruby brew install ruby

2.3. 创建脚本

我的脚本是brew-install-with-retry.sh,内容如下

#!/bin/bash

# 脚本中的版本号
VERSION="1.0.0"

# 显示版本号
show_version() {
  echo "brew-install-with-retry version ${VERSION}"
}

# 如果未指定任何参数,则显示帮助信息
if [ "$#" -eq 0 ]; then
  echo "用法: $0 -p 包名 [-a 重试次数]"
  echo "  -p 包名: 要安装的包名"
  echo "  -a 重试次数: 可选参数,安装失败时的重试次数,默认为3"
  exit 1
fi

# 处理命令行参数
while [[ "$#" -gt 0 ]]; do
  case $1 in
    --version|-V) show_version; exit 0;;
    *)            echo "Unknown parameter passed: $1"; exit 1;;
  esac
  shift
done

# 定义默认的重试次数
DEFAULT_MAX_ATTEMPTS=3

# 使用getopts处理命令行参数
while getopts "p:a:" opt; do
  case $opt in
    p)
      PACKAGE="$OPTARG"
      ;;
    a)
      MAX_ATTEMPTS="$OPTARG"
      ;;
    \?)
      echo "无效的选项: -$OPTARG" >&2
      exit 1
      ;;
    :)
      echo "选项 -$OPTARG 需要一个参数。" >&2
      exit 1
      ;;
  esac
done

# 如果没有提供重试次数,则使用默认值
if [ -z "$MAX_ATTEMPTS" ]; then
  MAX_ATTEMPTS=$DEFAULT_MAX_ATTEMPTS
fi

# 当前重试次数
CURRENT_ATTEMPT=0

# 检查是否提供了包名
if [ -z "$PACKAGE" ]; then
  echo "错误:必须提供要安装的包名。"
  echo "用法: $0 -p 包名 [-a 重试次数]"
  exit 1
fi

# 开始安装
until brew install $PACKAGE || [$CURRENT_ATTEMPT -eq $MAX_ATTEMPTS ]
do
  echo "尝试安装 $PACKAGE, 当前尝试次数:$CURRENT_ATTEMPT"
  ((CURRENT_ATTEMPT++))
  if [ $CURRENT_ATTEMPT -eq$MAX_ATTEMPTS ]; then
    echo "已达到最大尝试次数 $MAX_ATTEMPTS,安装失败。"
    exit 1
  fi
  sleep 5 # 等待5秒再重试
done

echo "$PACKAGE 安装成功!"

2.4. 创建homebrew-tap及formula

  • 在github上创建一个homebrew-tap,并创建目录Formula (home brew的规范
  • 项目clone到本地
  • 在Formula目录中创建formula(formula需要用ruby语言编写),命名为brew-install-with-retry.rb

    class BrewInstallWithRetry < Formula
            desc "A script to install packages with retry logic"
            homepage "https://github.com/leon-fly/brew-install-with-retry"
            url "https://github.com/leon-fly/formula/raw/master/brew-install-with-retry/archive/brew-install-with-retry-1.0.0.tar.gz"
            sha256 "7c12edfef69db79006a3b853b433a76c182865e98f80fabc730b12b48fc4d347"
            license "MIT"        
            def install
            bin.install "brew-install-with-retry.sh" => "brew-install-with-retry"
            end
    
            test do
            system "#{bin}/brew-install-with-retry", "--version"
            end
        end
    • 其中url为brew-install-with-retry.sh脚本的tar包仓库路径(此处上传到了github上),tar包生成命令

      tar -czvf brew-install-with-retry.tar.gz brew-install-with-retry.sh

    • sha256 为 tar包通过sha256计算的值,命令为

      shasum -a 256 brew-install-with-retry-1.0.0.tar.gz

    • def install和test部分需要调整为脚本对应的名称信息

2.5. 本地测试formula

首先确保shell脚本是正确的,接下来通过源码方式测试formula(看是否可以正常安装)

brew install --build-from-source brew-install-with-retry.rb

安装成功尝试是否能正常使用

brew-install-with-retry --version

2.6. 提交formula最终验证

  • 将formula代码提交到github

  • 通过非源码方式安装(这种方式也就时其他人使用时采用的方式)

    • 通过brew tab <user>/tap 添加Tap, 此处我github的user id是leon-fly

    brew tab leon-fly/tap

    • 使用brew install执行安装

    brew install brew-install-with-retry

3. 避坑

  1. 如果是用github托管安装包(tar包),url路径使用带raw的而不是blob,否则会出现sha256码不一致的情况
  2. 测试formula时可能多次调试,会有重新安装的情况,需要卸载干净,包括cache中的安装包都需要删除。否则也可能出现sha256不一致或者formula没生效的情况