自安装了ArchLinux以来,准确的来说是从AUR安装东西以来,就一直想学PKGBUILD的写法。但是因为太懒没有动力,就一直拖着。直到最近想装个东西,但它没提供Arch包,这才整了整活。

跟着其他教程弄了半天,总算装上能用了,但写得肯定不够规范,以后有空了再更新吧~

PKGBUILD和AUR

使用ArchLinux的朋友可能不陌生,AUR(Arch User Repository)是一个用户自行打包、分享软件的平台,其安全性和稳定性虽然没法保证。但一般来说,只要安装有人气的包就没大问题。对于官方来说,也会将符合一些条件的包收录到官方库里面去。是ArchLinux社区的重要组成部分(这说法好官方哈哈哈哈哈)。

这无穷无尽的软件想要都塞给官方来保存,人又不是网盘,顶不住这么折腾,于是PKGBUILD就是一个很好的解决方式。

简单来说,PKGBUILD这个文件(对,它只是一个文本文件罢了)负责记录:

  • 从哪来:软件数据从哪里下载
  • 到哪去:怎么安装,安装到哪

大陆网络有时会发现无法下载AUR的包,如果不是PKGBUILD写错了,那么很大概率是连不上作者指定的下载源(你懂的)

这么一来,只要写好一份PKGBUILD,并在下载源(比如本地,或者Github之类的)放好软件数据,就可以交给makepkg命令来安装了,nice!

而如果要提交上AUR给大伙瞧瞧,还得在里面写好软件版本、作者信息、许可证、完整性检验等等更规范的玩意,当然也得负责后续更新以及bug的处理。当然,本篇并不关注这些,咱只想装个AUR里面没有的包罢了。

PKGBUILD结构

前面说到,PKGBUILD实际上就是一个文本文档,它的结构在Wiki里已经有比较好的说明了。

不妨先把/usr/share/pacman/PKGBUILD.proto这个模板复制一份到某个空文件夹里面(这个文件夹就是后续的操作目录),去掉后缀。
当然,在我的简单需求下,并不是所有的参数都需要用到,只需要关注:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
pkgname=XXX         # 包名
pkgver=0.0.1 # 版本
pkgrel=1 # 版本发布号(每个版本从1开始,相当于小版本)
arch=('x86_64') # 支持的CPU架构
license=('unknown') # 许可证(我只是自用,所以unknown就好)
depends=() # 依赖的其他包

prepare() {
# 【准备阶段】
# 写下载软件源文件的东西
}

build() {
# 【编译阶段】
# 从下载好的玩意里编译出可安装的文件,一般以make结尾
}

package() {
# 【安装阶段】
# 安装到指定目录
}

模板里其他部分并不是不重要,如果需要提交AUR的话还是必须了解所有功能的,但对于我来说已经够用了

注意到这整个安装过程分了好几个阶段,它们实际上是会按顺序来的。换句话说,如果你喜欢,也可以把所有步骤全扔到比如package()里面,也同样能跑。
但之所以这么分,是因为我们的安装不一定会走完全流程,比如:

  • 已经下载好了源文件,直接本地编译就行,那就不用跑prepare()
  • 已经编译好了,只想重装一下,那就不用跑prepare()build()

正因为有这些更灵活的需求,我还是建议尽量分开各个步骤。

安装,更新与卸载

安装

写好了PKGBUILD之后,在PKGBUILD所在的文件夹里面,可以使用makepkg命令安装:

1
$ makepkg -si    # 加-s自动安装依赖包, -i编译后安装

而且你还会发现搞定后多出了一个src文件夹,一个pkg文件夹,和一个*.pkg.tar.zst文件:

  • src:下载及预处理后的软件源文件;
  • pkg:一个软件安装虚拟环境(沙箱?),如果安装成功,会被复制到根目录去;
  • *.pkg.tar.zst:打包好的二进制文件,以后可以使用pacman -U直接安装。

所以简单来说,除了最后一个可以保存以便重新安装,其他都没啥用。

更新

更新十分简单,只需要修改PKGBUILD的版本相关参数(包括pkgver,pkgrel等),重新运行makepkg即可。

卸载

对于安装到正确位置的软件包,都能被Pacman正确识别到信息,然后和别的软件包一样使用sudo pacman -R就好啦~

举个栗子:Terminus Player

吐槽:网上搜的大多数教程具体的例子都不给,感觉看着太难受。。

这里简单举个例子,以我想要安装的Terminus Player为例。

命令行安装

在正式编写PKGBUILD之前,我推荐要先在命令行走一遍安装流程。
Github简介内,作者给出了命令行的安装方式,虽然是Ubuntu的,但我们完全可以借鉴这个步骤。

首先我习惯新开一个文件夹在里面操作,不妨就叫terminus好了。由于系统不同,我们直接跳过所有依赖包,开始它的安装步骤,即:

1
2
3
4
5
6
7
8
# pwd: terminus
$ git clone git://github.com/iwalton3/jellyfin-media-player
$ cd jellyfin-media-player
$ ./download_webclient.sh
$ cd build
$ cmake ..
$ make
$ sudo make install

在我的电脑上,进行第三行命令运行./download_webclient.sh时报了错误,少了个包:mpv,这简单,用Pacman给它装上,再跑一遍。

细心的话可以发现,其实Github作者给的安装步骤里是有类似mpv的东西。但对于像我一样使用另一个系统的朋友,还是建议先直接安装,有提示缺漏再补。(以避免安装错误或者不必要的安装。)

安装顺利完成,留意一下安装路径(因为不一定符合要求)是在/usr/local里面:

1
2
3
4
# ...(前略)
-- Installing: /usr/local/share/jellyfinmediaplayer/web-client/extension/jmpUpdatePlugin.js
-- Installing: /usr/local/bin/jellyfinmediaplayer
-- Set runtime path of "/usr/local/bin/jellyfinmediaplayer" to "/lib"

由于我更希望和其他软件一起存在/usr里面,这样可以方便管理,因此可以给make install增加参数make DESTDIR="/" install(但在PKGBUILD里面需要写成${pkgdir}来代表根目录,而不是/,后面会提到)

尝试打开,又发现了错误(雾):

1
2
3
4
5
6
7
8
9
10
11
libpng warning: iCCP: known incorrect sRGB profile
Logging to /home/hjh/.local/share/jellyfinmediaplayer/logs/jellyfinmediaplayer.log
[1209/180802.041012:ERROR:resource_bundle.cc(947)] Failed to load /home/hjh/.Jellyfin Media Player/qtwebengine_resources_100p.pak
Some features may not be available.
[1209/180802.041050:ERROR:resource_bundle.cc(947)] Failed to load /home/hjh/.Jellyfin Media Player/qtwebengine_resources_200p.pak
Some features may not be available.
[1209/180802.041056:ERROR:resource_bundle.cc(947)] Failed to load /home/hjh/.Jellyfin Media Player/qtwebengine_resources.pak
Some features may not be available.
[1209/180802.041082:WARNING:resource_bundle_qt.cpp(119)] locale_file_path.empty() for locale
[18005:18005:1209/180802.124821:ERROR:extension_system_qt.cpp(121)] Failed to parse extension manifest.
[1] 18005 segmentation fault (core dumped) jellyfinmediaplayer

好家伙,这我就只能交给谷歌了(此处花了几乎一个小时),终于在这里找到了方法,需要把cmake那一行命令加多一个参数:

1
$ cmake -DQTROOT="{qt所在的文件夹}" ..

卸载(手动删除)掉之前装上的东西:

1
2
3
4
5
$ sudo rm -rf /usr/local/bin/jellyfinmediaplayer 
$ sudo rm -rf /usr/local/share/jellyfinmediaplayer
$ sudo rm -rf /usr/local/resources/qtwebengine_devtools_resources.pak
$ sudo rm -rf /usr/local/share/applications/com.github.iwalton3.jellyfin-media-player.desktop
$ sudo rm -rf /usr/local/share/metainfo/com.github.iwalton3.jellyfin-media-player.appdata.xml

重新安装成功,可以使用,nice!

实际上到这里也可以直接使用了,但如果要管理软件会比较麻烦(比如卸载的话,要手动去删除软件)…

编写PKGBUILD

有了成功的经验,我们便可以开始编写PKGBUILD了,新建一个文件terminus/PKGBUILD
我直接把写好的放出来,大家对照着看解释即可:

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
pkgname="terminus"
pkgver=0.0.1
pkgrel=1
arch=("x86_64")
depends=("mpv" "qt6-base" "cmake") # 填上之前缺少的包
license=("unknown")

prepare() { # 从Github下载软件源文件
mkdir "${srcdir}/${pkgname}-${pkgver}"
cd "${srcdir}/${pkgname}-${pkgver}"
git clone https://github.com/Terminus-Media/jellyfin-media-player.git
cd jellyfin-media-player
./download_webclient.sh
rm -rf .github # 干掉git clone下来的.github,非必要
}

build() { # 编译并构建可安装文件
cd "${srcdir}/${pkgname}-${pkgver}/jellyfin-media-player/build"
cmake -DQTROOT="${srcdir}/${pkgname}-${pkgver}/jellyfin-media-player/qt" ..
make
}

package() { # 安装
cd "${srcdir}/${pkgname}-${pkgver}/jellyfin-media-player/build"
make DESTDIR="${pkgdir}" install
}

我在这里用到了很多诸如${srcdir}的玩意,这些是PKGBUILD的“暗号”,具体来说:

  • ${srcdir}:安装文件夹,实际上是与PKGBUILD同级的terminus/src文件夹(开始安装后会出现),可看作是安装过程中的根目录;
  • ${pkgdir}:只应在package()里使用,对应着外部的根目录;
  • ${pkgname}:前面定义的包名
  • ${pkgver}:前面定义的版本

现在用makepkg来安装:

1
2
# pwd: terminus
$ makepkg -si

注意:安装过程中有可能出现这个INSTALL路径错误:

1
2
3
4
CMake Error at src/cmake_install.cmake:50 (file):
file INSTALL cannot find
"terminus/src/terminus-0.0.1/jellyfin-media-player/build/src/../dist":
No such file or directory.

具体原因我还没搞清楚,但可以通过rm -rf src pkg删掉已有文件后重新来一遍解决。

完事!

修复PGP: marginal trust / invalid问题

我在某一次安装的时候遇到了类似这样的问题:

1
2
3
error: XXXXXX: signature from "XXXXX" is marginal trust
:: File /var/cache/pacman/pkg/XXXXXXXXXXX.pkg.tar.zst is corrupted (invalid or corrupted package (PGP signature)).
Do you want to delete it? [Y/n]

根据他的提示删除PGP签名又提示失败:

1
error: failed to commit transaction (invalid or corrupted package)

其原理还没怎么搞懂,但至少是PGP签名出了问题。
这里提到一种解决办法是检测并刷新所有签名:

1
$ sudo pacman-key --refresh-keys

注意:这行命令会运行很久很久,甚至超过半小时。但对系统负载不高,可以干别的。

等待PGP刷新结束即可重新makepkg -si