Swift 编写命令行工具并使用 GitHub Action 自动发布到 Homebrew

如何使用 Swift Package Manager(SPM) 开发一个命令行工具,并使用 GitHub Action 自动构建发布到 Homebrew

为了达到上述要求,我们需要做到以下:

  1. 本地使用 SPM 开发一个命令行工具 CLI (Package)
  2. 配置 github workflow,使用 GitHub Action 配置自动构建发布二进制文件
  3. 自动发布到 Homebrew

Build a Command-line Tool

这里我们实现一个读取文件内容的命令行工具,功能类似于 linux cat 命令,就叫它 swiftcat 吧。

参考官方教程

SPM 工程

创建 SPM 工程:

bash 复制代码
mkdir swiftcat
cd swiftcat
swift package init --type executable

执行完上述命令后会得到 SPM 工程,工程文件目录如下:

bash 复制代码
├── Package.swift
└── Sources
    └── main.swift

Package.swift

Package.swift 是该 package 的配置文件,可以直接使用 Xcode 打开,运行该 SPM 工程,进行代码编写、调试。

Xcode 如何设置命令行参数进行调试?

Edit Scheme > Run > Arguments > Arguments Passed On Launch 然后添加参数。

默认内容如下:

swift 复制代码
// swift-tools-version: 5.9
// The swift-tools-version declares the minimum version of Swift required to build this package.
​
import PackageDescription
​
let package = Package(
    name: "swiftcat",
    targets: [
        // Targets are the basic building blocks of a package, defining a module or a test suite.
        // Targets can depend on other targets in this package and products from dependencies.
        .executableTarget(
            name: "swiftcat"),
    ]
)

添加依赖

Package 可以添加依赖,使用别人已经开发好的包,这里我们添加苹果官方的 swift-argument-parser(后面会用到),这样我们的 Package.swift 就更新为如下内容: 一般添加完依赖后,Xcode 会自动解析拉取依赖,如果没有或者因为网络拉取出错,我们可以手动在 Xcode 状态栏,点击 File > Packages > Resolve Package Versions 手动触发一下解析。

swift 复制代码
// swift-tools-version: 5.9
// The swift-tools-version declares the minimum version of Swift required to build this package.
​
import PackageDescription
​
let package = Package(
    name: "swiftcat",
    dependencies: [
        .package(url: "https://github.com/apple/swift-argument-parser", from: "1.0.0")
    ],
    targets: [
        // Targets are the basic building blocks of a package, defining a module or a test suite.
        // Targets can depend on other targets in this package and products from dependencies.
        .executableTarget(
            name: "swiftcat", dependencies: [
                .product(name: "ArgumentParser", package: "swift-argument-parser")
            ]),
    ]
)

main.swift

程序入口,你也可以删除该文件,另建 swift 文件,然后配置 @main 入口,实现 static main() 函数,main.swift@main 只能存在一个。 这里我们删除了 main.swift,新建了 ReadFile.swift 文件,并且使用上面依赖的swift-argument-parser直接写命令行实现相关代码。 如下 文件所示:

swift 复制代码
import Foundation
import ArgumentParser
​
@main
struct SwiftCat: ParsableCommand {
    static var configuration: CommandConfiguration {
        let conf = CommandConfiguration(commandName: "swiftcat")
        return conf
    }
    
    @Argument(help: "文件路径")
    var filePath: String
    
    // 读取文件内容
    func run() throws {
        let fileContents = try String(contentsOfFile: filePath, encoding: .utf8)
        print(fileContents)
    }
}

filePath 是唯一的参数,传入待读取的文件路径,并且使用@Argument 包装, @Argument是直接赋值的位置参数,跟在参数最后位置,如果有多个,按声明顺序赋值。其他还有 标记 @Flag 可选@Option 类型参数。

ParsableCommand 内部实现了 static main() 函数,并且调用了 run() 函数,我们只需要在 `run()`` 函数内部实现逻辑就行。

ReadFile run() 函数就是我们这个 Package 核心代码了,代码很简陋,仅仅就是演示一下。

run cli

最后在终端来试一下 ,如果能正常读取文件内容,那么该命令行就算编码完成了。

bash 复制代码
# 读取文件内容
swift run swiftcat <filepath>

也可以直接在本地尝试编译,或者直接使用 Xcode run

bash 复制代码
# 命令行编译
swift build -c release

最后在 GitHub 新建仓库,并将此工程代码 push 到该仓库。

GitHub Action

当我们完成代码编写,接下来就是 GitHub workflow 配置了。

生成 yml 配置文件

在 GitHub 仓库主页点击 Actions > New workflow > Swift Configuration ,保存生成的 yml 格式文件,提交到仓库。

或者直接在本地仓库根目录新建一个 ci.yml 文件,保存在 .github/workflows ,push 到远程仓库。

自动构建二进制文件并发布

将上述的 yml 配置文件内容更新为如下

yml 复制代码
name: "swiftcat CI"
  
on:
  push:
    tags:
      - "*"
​
jobs:
  build:
    runs-on: macos-13
    permissions:
      contents: write
    steps:
      - name: Swift version setup
        uses: swift-actions/setup-swift@v1
      - name: Get Swift version
        run: swift --version
      - name: Checkout
        uses: actions/checkout@v4
      - name: Build
        run: swift build -c release --arch arm64 --arch x86_64 --product swiftcat
      - name: Compress archive
        run: tar -czf ${{ github.ref_name }}.tar.gz -C .build/apple/Products/Release swiftcat
      - name: Relese
        uses: softprops/action-gh-release@v1
        with:
          files: ${{ github.ref_name }}.tar.gz

上述配置,当我们推送任何 tag 到该仓库时,就会起一个 build job 进行编译构建,生成一个兼容 arm64x86_64 的二进制文件,然后压缩该二进制文件,发布到仓库的 release 界面。

这个时候我们可以手动打个 tag 0.0.1,推到远程,build 就会执行,不出意外的话就会发布成功。

发布成功后进入到该仓库 Release 界面,找到二进制压缩文件 tar.gz 的下载链接,复制该链接,下文需要用。

Homebrew

The Missing Package Manager for macOS (or Linux)

homewbrew 官方仓库有审核,像我们这种小众的demo式软件,不一定让我们进入他们的官方源, 所以这里我们新建一个自己的仓库。

Homebrew 仓库

  1. github 创建 homebrew 仓库,命名格式为 homebrew-<repo>,仓库名字需要以 homebrew- 作为前缀,这里我直接命名为 homebrew-tap

  2. 将仓库添加到本地

    bash 复制代码
    brew tap jacinzhang/homebrew-tap
  3. 切到仓库目录下

    bash 复制代码
    cd $(brew --repo)/Library/Taps/jacinzhang/homebrew-tap
  4. 仓库根目录下创建Formula文件夹

    bash 复制代码
    mkdir Formula

这里需要创建一个 Formula 文件夹,否则后续的 Github Action 自动上传 Homebrew 会报错找不到目录。

构建 Formula

  • 创建一个新的 formula
bash 复制代码
# 创建一个新的 Formula,链接就是上述构建成功二进制文件链接
brew create --tap jacinzhang/homebrew-tap https://github.com/jacinzhang/swiftcat/releases/download/0.0.1/0.0.1.tar.gz

上述命令会生成一个 swiftcat.rb 文件,并且会自动打开。

使用brew audit --new swiftcat 检验一下该文件,提示我们删掉注释,再修改更新一下内容,得到如下:

swift 复制代码
    class Swiftcat < Formula
      desc "CLI"
      homepage "https://github.com/jacinzhang/swiftcat"
      url "https://github.com/jacinzhang/swiftcat/releases/download/0.0.1/0.0.1.tar.gz"
      version "0.0.1"
      sha256 "6502c461d6e8547c5566ebd1d5f48745a26f17e06ebde6094efaebbf6fa5fc2d"
      license ""
    ​
      def install
        bin.install "swiftcat"
      end
    ​
      test do
        system "false"
      end
    end
  • 保存文件,提交到 GitHub 远程仓库
bash 复制代码
cd $(brew --repo)/Library/Taps/jacinzhang/homebrew-tap
git add --all
git commit -m 'add swiftcat'
git push
  • 安装
bash 复制代码
# 安装
brew tap jacinzhang/homebrew-tap && brew install jacinzhang/homebrew-tap/swiftcat

至此,我们已经能够在任意一台电脑使用 swiftcat 这个命令行工具了。

手动更新 Formula

  • 更新新版本信息:
bash 复制代码
brew edit jacinzhang/homebrew-tap/swiftcat
  • 获取新版本的 sha256
bash 复制代码
brew fetch jacinzhang/homebrew-tap/swiftcat --build-from-sourc
  • 更新 swiftcat.rb 中 sha256

  • 提交到远程仓库

至此我们已可以:

  • cli 的自动构建发布
  • 手动更新到 homebrew

但是,我们的期望是 swiftcat 有版本更新时,能自动推送到 jacinzhang/homebrew-tap 仓库,然后随时能在本地通过 homebrew 更新。

自动更新 Formula

自动更新的话,我们需要在 swiftcat 这个仓库完成构建发布后,有权限将信息更新到 jacinzhang/homebrew-tap 这个仓库。

为此,我们需要用到 bump-homebrew-formula-action 这个 github action,fastlane 也使用这个工具更新到 homebrew

按照其教程所示,我们需要进行如下:

  1. generate a new Personal Access Token (classic) here

  2. 添加到 swiftcat 仓库的 secrets 中,注意是 Repository secrets

  1. 在上述命令行源码仓库的 ci.yml 配置文件中添加 Action
yml 复制代码
- name: Bump Homebrew formula
  uses: mislav/bump-homebrew-formula-action@v3
  with:
    formula-name: swiftcat
    homebrew-tap: jacinzhang/homebrew-tap
    download-url: https://github.com/jacinzhang/swiftcat/releases/download/${{ github.ref_name }}/${{ github.ref_name }}.tar.gz
  env:
    COMMITTER_TOKEN: ${{ secrets.COMMITTER_TOKEN }}

其中 COMMITTER_TOKEN 是步骤2中添加 token 时起的名字。

  1. 最后提交一下,push 到远程仓库。

最后我们打一个 tag,推到远程,测试一下 build job 是成功执行了。

如果成功执行,我们就可以直接通过 homebrew 安装、更新 swiftcat 了。

bash 复制代码
# 更新
brew upgrade swiftcat

jacinzhang/homebrew-tap

swiftcat

相关推荐
GoppViper15 小时前
golang学习笔记29——golang 中如何将 GitHub 最新提交的版本设置为 v1.0.0
笔记·git·后端·学习·golang·github·源代码管理
贩卖纯净水.1 天前
白月光git
git·github
AI逍遥子1 天前
如何从github上clone项目
github
iBaoxing1 天前
如何在 Fork 的 GitHub 项目中保留自己的修改并同步上游更新?github_fork_update
github
The Mr.Nobody2 天前
打通最后一公里:使用CDN加速GitHub Page的访问
github
Amagi.2 天前
如何将本地项目上传到GitHub(SSH连接)
github
白总Server2 天前
php语言基本语法
开发语言·ide·后端·golang·rust·github·php
网安詹姆斯2 天前
网络安全(黑客技术)2024年三个月自学计划
网络·数据结构·python·mysql·安全·web安全·github
爱吃番茄的小狐狸2 天前
Docker镜像下载-使用github action- 解决无法下载docker镜像的问题
docker·容器·github
毅凉2 天前
git笔记
gitee·github·gitcode