MacPorts -- 创建自己的 Port

摘要

  • Portfile 是 MacPorts 的安装脚本,对应 Homebrew 的 formula。官方 ports 树里没有的软件,可以放在本机的一个目录里,在 sources.conf 登记之后,用同样的 port install 安装。
  • 本地目录要写成 file://,放在带 [default] 的那一行前面,并加上 [nosync]。Portfile 放在 分类/名字/Portfile。改完在仓库根目录执行 portindex。
  • 下面用 brew -- 创建自己的 Formula 里的 color_echo 1.0.0 作例子。源码是 GitHub tag v1.0.0 的归档,命令文件在 bin/color_echo。安装和日常命令见 MacOS软件包管理器--MacPorts。没有 brew tap 这种命令,个人仓库是一份 Git 上的 ports 树,别人克隆后写进自己的 sources.conf。
  • 本机 MacPorts 是 2.12.7。sources.conf 里的默认行是 ports.tar.gz,不是指南旧示例里的 ports.tar。

本地仓库

  • 官方 ports 树由 selfupdate 覆盖。自己的 Portfile 放在另一个目录,port sync 不去动它。步骤见 Local Portfile Repositories。

  • 编辑 /opt/local/etc/macports/sources.conf。file:// 写在带 [default] 的那一行前面。同一个名字出现多次时,MacPorts 用先出现的那一份。[nosync] 让 selfupdate 跳过这个目录。本机改完是:

1
2
file:///Users/hanqf/ports [nosync]
rsync://rsync.macports.org/macports/release/tarballs/ports.tar.gz [default]
  • 目录名的第一层是 Portfile 里的主分类,第二层是 port 名字。color_echo 放在 sysutils:

1
mkdir -p ~/ports/sysutils/color_echo

Portfile

  • 文件是 Tcl。第一行是编辑器模式标记,后面先声明 PortSystem,GitHub 上的项目再加 github 1.0。字段说明见 Portfile Reference,GitHub 这一组见 PortGroups 里的 github。

  • github.setup 的四个参数是作者、仓库、版本、tag 前缀。1.0.0 配 v,tag 就是 v1.0.0。github.tarball_from 默认是 releases,读的是 Releases 页面上挂的附件。color_echo 用的是 tag 归档,要改成 archive,地址才是 https://github.com/hanqunfeng/color_echo/archive/refs/tags/v1.0.0.tar.gz。

  • 这份归档解压后的目录是 color_echo-1.0.0。archive 会打开 extract.rename,工作目录和这个名字对齐。脚本在 bin/color_echo,没有编译步骤。

  • 创建 ~/ports/sysutils/color_echo/Portfile 文件:

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
# -*- coding: utf-8; mode: tcl; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- vim:ft=Portfile:et:sw=4:ts=4:sts=4

PortSystem 1.0
PortGroup github 1.0

github.setup hanqunfeng color_echo 1.0.0 v
github.tarball_from archive
revision 0

categories sysutils
platforms any
supported_archs noarch
license MIT
maintainers nomaintainer

description Print colored text in the terminal
long_description {*}${description}

checksums rmd160 0123456789abcdef0123456789abcdef01234567 \
sha256 9450952a4b477c83ea2d7e28386d6ae38132bf68c46746aa218c03c21aa75f6d \
size 0

use_configure no
build {}

destroot {
xinstall -m 0755 ${worksrcpath}/bin/color_echo \
${destroot}${prefix}/bin/color_echo
}
  • 各字段含义如下:

    • 第一行:给编辑器看的模式行,标明这是 Tcl / Portfile,缩进 4 空格;MacPorts 本身不解析它。
    • PortSystem 1.0:启用 Portfile 基础设施,每个 Portfile 都要写。
    • PortGroup github 1.0:引入 GitHub 相关辅助命令,后面才能用 github.setup、github.tarball_from。
    • github.setup:四个参数依次是 GitHub 作者、仓库名、版本号、tag 前缀。这里是 hanqunfeng / color_echo / 1.0.0 / v,对应 tag v1.0.0。
    • github.tarball_from archive:从 tag 归档下载;默认 releases 会去 Releases 附件,对不上时要改成 archive。
    • revision:同一上游版本下 Portfile 自己的修订号。只改安装步骤时加 1;换了上游版本则改 github.setup 里的版本,并把 revision 回到 0。
    • categories:软件分类,第一项应和本地目录名一致,这里是 sysutils。
    • platforms any:不限运行平台;纯脚本常见写法。
    • supported_archs noarch:没有按 CPU 架构区分的二进制,arm64 / x86_64 共用同一份。
    • license:上游许可证,这里是 MIT。
    • maintainers:维护者;只在本机用可写 nomaintainer。
    • description:一句话短描述,出现在 port search、port info。
    • long_description:长描述。{*}${description} 表示直接展开复用短描述。
    • checksums:校验下载到的压缩包。rmd160、sha256 是摘要,size 是字节数;三个都针对 .tar.gz 本身。
    • use_configure no:跳过 ./configure。
    • build {}:空的编译阶段,表示没有编译步骤。
    • destroot { ... }:安装阶段。文件先写到 ${destroot}${prefix},MacPorts 登记后才出现在 /opt/local。${worksrcpath} 是解压后的源码目录,xinstall 把 bin/color_echo 装成可执行文件。
  • sha256 与 brew -- 创建自己的 Formula 里同一份 v1.0.0.tar.gz 一致。rmd160 和 size 先占位,下一步用本机算出来的值换掉。只给自己用时,maintainers 写 nomaintainer 即可。

  • 安装阶段写到 ${destroot}${prefix}。MacPorts 登记之后,文件才出现在 /opt/local。${prefix} 在本机是 /opt/local。

校验和

  • 三个值都是压缩包本身的,不是解压后的文件:

1
2
3
4
5
curl -fL -o ~/Downloads/color_echo-1.0.0.tar.gz \
'https://github.com/hanqunfeng/color_echo/archive/refs/tags/v1.0.0.tar.gz' # 下载 tag 对应的源码归档
openssl dgst -sha256 ~/Downloads/color_echo-1.0.0.tar.gz # 算 sha256,写入 Portfile 的 sha256
openssl dgst -rmd160 ~/Downloads/color_echo-1.0.0.tar.gz # 算 rmd160,写入 Portfile 的 rmd160
stat -f%z ~/Downloads/color_echo-1.0.0.tar.gz # 取字节数,写入 Portfile 的 size
  • sha256 的结果应是 9450952a4b477c83ea2d7e28386d6ae38132bf68c46746aa218c03c21aa75f6d。rmd160 写进 Portfile 的第一段,stat 的字节数写到 size。

  • 也可以先按上面的占位保存,再执行 sudo port -v checksum color_echo。对不上时,终端会给出算出来的 rmd160、sha256 和 size。改完 Portfile 后重新 portindex。

索引、检查、安装

  • 在仓库根目录重建索引。这个目录属于自己,不用 sudo:

1
2
3
4
5
6
7
8
9
10
cd ~/ports
portindex
# 输出
Creating port index in /Users/hanqf/ports
Adding port sysutils/color_echo

Total number of ports parsed: 1
Ports successfully parsed: 1
Ports failed: 0
Up-to-date ports skipped: 0
  • 索引建成之后,port 能看到它。port file 用来确认读的是本地这一份:

1
2
3
4
5
port info color_echo          # 查看版本、描述、依赖等
port file color_echo # 确认读的是本地 Portfile
# /Users/hanqf/ports/sysutils/color_echo/Portfile

port lint --nitpick color_echo # 严格检查必填字段、依赖与过时写法
  • lint 检查必填字段、依赖是否存在、有没有过时写法。通过后再装:

1
2
3
sudo port install color_echo  # 安装本地 Portfile 定义的 color_echo
ls -l /opt/local/bin/color_echo
port contents color_echo # 查看这个包装了哪些文件
  • port install 把命令写到 /opt/local/bin,不改当前终端已经读入的 PATH。which 只在这份 PATH 里找。这个终端若是在写好 ~/.zprofile、~/.zshrc 之前打开的,或者不是登录 shell、没读到 ~/.zprofile,which color_echo 会显示找不到,文件其实已经在磁盘上。ls 能看到它,就说明装上了。

  • 新开一个终端再查。PATH 里已经有 /opt/local/bin 时,同一终端里安装后 which 也能看到。结果应是 /opt/local/bin/color_echo。同名命令排在更前面时,见 MacOS软件包管理器--MacPorts 里 ~/.zshrc 末尾再声明 MacPorts 路径的那一节。

1
2
which color_echo
color_echo --help

改过之后

  • 只改了安装步骤、版本号没变,把 revision 加 1。换了上游版本,就改 github.setup 里的版本,并重算校验和,revision 回到 0。

  • Portfile 改完都要重新索引,再升级:

1
2
3
cd ~/ports
portindex
sudo port upgrade color_echo
  • 不再需要时:

1
sudo port uninstall color_echo

个人仓库

  • MacPorts 没有 brew tap user/repo。能分发给别人的,是一个目录结构和本机 ~/ports 相同的 Git 仓库:分类/名字/Portfile。别人克隆下来,在自己的 sources.conf 里加一行 file://,就能 port install。说明见 sources.conf。

  • 把已经写好的 ~/ports 推上去即可。PortIndex 由使用者本机生成,不必提交:

1
2
3
4
5
6
cd ~/ports
git init
git add sysutils/color_echo/Portfile
git commit -m "color_echo: new port"
git remote add origin https://github.com/hanqunfeng/my-ports.git
git push -u origin main
  • 使用者克隆后做一次索引,再把绝对路径写到带 [default] 的那一行前面:

1
2
3
git clone https://github.com/hanqunfeng/my-ports.git ~/my-ports
cd ~/my-ports
portindex
1
2
file:///Users/hanqf/my-ports [nosync]
rsync://rsync.macports.org/macports/release/tarballs/ports.tar.gz [default]
  • [nosync] 时,selfupdate 不会碰这个目录。仓库有更新时,自己 git pull,再在仓库根目录执行 portindex。

  • 去掉 [nosync] 后,selfupdate 发现这是 Git 检出,会向默认远程 pull,并重建索引。执行同步的是 macports 用户,这个目录要让它可写。家目录通常不行,检出放到它能写的路径,例如 /opt/local/var/macports/sources/github.com/hanqunfeng/my-ports。

提交到官方仓库

  • 个人仓库要别人改自己的 sources.conf 才能用。希望不改配置、一条 port install color_echo 就能装到,把 Portfile 交给 macports-ports。步骤就是 fork、单独一个分支、一条提交、开 pull request。耗时间的是这个仓库本身很大,以及 GitHub Actions 编译和别人审 Portfile。Portfile 写法见 Creating a Portfile,提交说明见 CommitMessages 和 Using Git and GitHub。

  • 提交前在本机走通 port lint --nitpick color_echo 和 sudo port install color_echo。准备自己维护这个 port,把 maintainers nomaintainer 改成 maintainers {@hanqunfeng}。留给别人维护就保持 nomaintainer。

  • 官方仓库是 macports/macports-ports。没有推送权限,直接克隆官方地址后,git push 会被拒绝。先在 GitHub 上点 Fork,得到自己的 hanqunfeng/macports-ports,再克隆这一份。只克隆一层历史,避免把整个 ports 树的历史都拉下来:

1
2
3
4
5
git clone --depth 1 https://github.com/hanqunfeng/macports-ports.git
cd macports-ports
git checkout -b color_echo
mkdir -p sysutils/color_echo
cp ~/ports/sysutils/color_echo/Portfile sysutils/color_echo/Portfile
  • 只加入 Portfile。portindex 生成的 PortIndex 属于本机索引,不放进仓库。提交标题先写 port 名字:

1
2
3
git add sysutils/color_echo/Portfile
git commit -m "color_echo: new port"
git push -u origin color_echo
  • 在 GitHub 上对 macports/macports-ports 的 master 开 pull request。Actions 会按这份 Portfile 编译。本例的 pull request

  • 审查意见要改文件时,改在同一条提交上再推:

1
2
3
git add sysutils/color_echo/Portfile
git commit --amend --no-edit
git push --force-with-lease
  • 几天没有回复,可以给 macports-dev 写信,信里放上这个 pull request 的链接。合并之后,selfupdate 就能从官方树里装到 color_echo,本机 sources.conf 里的 file:// 那一行可以删掉。本地目录排在 [default] 前面时,同名 port 仍会用本地这份。