前言
有时候某些软件的构建往往会依赖自身进行编译,这时候就需要使用Bootstrap构建方法。
本教程仅简单列举一些新人相对容易理解的、常见的Bootstrap构建方法,对于复杂的问题,可能需要额外的具体研究。
注意事项
首次通过“不干净”的方法生成的软件包请勿上传,请将这一文件添加到本地仓库,再使用官方的构建文件(或者基于官方构建文件应用了我们补丁集中的补丁的),进行正式的打包,然后上传,保证构建的可复现性。
正如本文展示的案例所见,实际的自举过程可能并不如我们想象的那么顺利,可能会遇到大量我们预料之外的问题,因此需要有耐心和细心,保持观察力,逐步排查并解决问题。
上游给出Bootstrap构建方法的情况
某些软件包官方会给出Bootstrap构建方法,因此我们需要注意查看官方文档中是否有相关说明。
- 案例:Vala
利用其他发行版的二进制包自举
如果某个软件包在Loong Arch Linux上还没有,但是其他支持LoongArch的发行版上已经构建好了二进制包,我们可以尝试使用其他发行版的二进制包构建。我们同样可以构建一个-bootstrap软件包,然后在build()或者package()函数中,使用bsdtar解压其他发行版的二进制包,直接安装到$pkgdir中。(同样需要注意添加provides字段)
这一方法容易遇到一些问题:
- 不同发行版的路径结构可能不同,有时候可能需要手动调整
- 比如
/usr/lib和/usr/lib64的区别 - 比如
/usr/bin和/usr/local/bin的区别
- 比如
- 不同发行版的二进制包可能有不同的依赖
- 可能会遇到链接到不存在的库的问题
- 包括依赖缺失或者soname不同
- 需要手动调整或者导入依赖包
- 可能会遇到链接到不存在的库的问题
- 案例:GHC
利用有“功能替代”作用的其他软件包构建
某些软件包一般是需要自身构建的,但是有时候我们可以利用有“功能替代”作用的其他软件包构建。
一个非常常见的例子就是gcc系列的编译器与llvm系列的编译器功能上往往有相互对应或者近似相互对应的关系。
当然,有时候直接代替构建可能会出现问题,还需要根据具体情况调整编译参数、引入组件、临时禁用功能等。
- 案例:LDC
例子
vala:上游给出Bootstrap构建方法的情况
Vala是一种为GObject设计的高级编程语言,Vala编译器会将Vala代码编译成C代码,然后再编译成二进制文件。Vala编译器本身也是用Vala语言编写的,因此在编译Vala编译器时,我们需要使用Bootstrap构建方法。Vala官方给出了Bootstrap构建方法:Vala官方会直接发布包含Vala代码及其生成的中间C代码的tarball,我们只需要解压这个tarball,然后再禁用Vala编译,指定使用C编译器编译即可。
因此,我们只需要对Arch Linux官方的vala软件包的PKGBUILD文件进行修改,得到vala-bootstrap软件包的PKGBUILD文件即可。
- 修改包名并增加
provides字段 - 修改
source字段,使用Vala官方发布的tarball替代git源码 - 去掉对
vala的依赖 - 根据Vala的Bootstrap文档,在构建时指定
VALAC=/no-valac
_pkgname=vala
pkgname="$_pkgname-bootstrap"
pkgver=0.56.17
pkgrel=1
pkgdesc='Compiler for the GObject type system'
url='https://wiki.gnome.org/Projects/Vala'
arch=(x86_64)
license=(LGPL-2.1-or-later)
depends=(
gcc
glib2
glibc
graphviz
gtk-doc
pkg-config
ttf-font
)
makedepends=(
autoconf-archive
git
help2man
libxslt
)
checkdepends=(
dbus
gobject-introspection
libx11
)
provides=(
libvala{,doc}-${pkgver%.*}.so
valadoc
$_pkgname
)
conflicts=(valadoc)
replaces=(valadoc)
source=("http://download.gnome.org/sources/vala/${pkgver%.*}/vala-$pkgver.tar.xz")
b2sums=('c4b8d5b7c810893728f82d2cbbf2f0dd70ad17bd4eeb323ab5d31d99f37b5a5508b7d2447f0249f3a925013d7110bb6f145b32c833b990b15f18d9949035293f')
build() {
cd "vala-$pkgver"
VALAC=/no-valac ./configure --prefix=/usr
sed -i -e 's/ -shared / -Wl,-O1,--as-needed\0/g' libtool
make
}
check() {
cd "vala-$pkgver"
make check
}
package() {
cd "vala-$pkgver"
make DESTDIR="$pkgdir" install
}
这样的vala-bootstrap软件包构建完成后,我们可以将其添加到本地仓库,然后使用vala-bootstrap软件包构建vala软件包。最后,我们再将得到的干净的vala软件包上传。
ghc:利用其他发行版的二进制包自举
GHC是Haskell语言的主要编译器,它的自举是出了名的复杂。此前,Arch Linux官方出于种种原因,此前GHC的版本一直被锁定在了9.4.4,而GHC直到9.6才开始支持LoongArch,因此我们一直没有构建。直到最近(2025年10月),Arch Linux官方才将GHC升级到了9.6.6版本,Arch Linux for Loong64也终于可以构建GHC了。
好在,也正是因为Arch Linux的GHC升级较慢,在我们移植之时,Debian就早已构建好了GHC 9.6.6的二进制包,因此我们可以将Debian的GHC二进制包作为基础,构建一个ghc-bootstrap软件包,然后再使用这个软件包构建干净的ghc软件包。
基础自举依赖包
编写-botstrap系列软件包的PKGBUILD文件,用于将.deb文件解包并重新打包成Arch Linux的软件包。由于自举可以认为是一次性的,我们并不用太在乎这几个PKGBUILD文件的规范性和可维护性。
我们需要集成ghc的构建依赖,从Debian中获取,构建包括ghc-bootstrap、alex-bootstrap、happy-bootstrap、haskell-hscolour-bootstrap、haskell-hadrian-bootstrap这几个软件包。
ghc-bootstrap的PKGBUILD文件。注意Debian的GHC依赖了LLVM18。此外,Debian的GHC并没有拆包,都放到了一个.deb文件中,因此我们只需要解压这个.deb文件即可,并在provides中按照Arch Linux的情况列出所有GHC提供的组件。
shopt -s extglob
pkgbase=ghc-bootstrap
pkgname=(ghc-bootstrap)
pkgver=9.6.6
pkgrel=1
pkgdesc='The Glasgow Haskell Compiler'
arch=('x86_64')
url='https://www.haskell.org/ghc/'
license=('custom')
source=("https://snapshot.debian.org/archive/debian-ports/20250217T194911Z/pool-loong64/main/g/ghc/ghc_9.6.6-4_loong64.deb"
ghc-rebuild-doc-index.hook ghc-register.hook ghc-unregister.hook)
sha512sums=('a92d46ac343718d36e9ce59db549e236c51423bb2835f103045a3cfbb02be0def75f3e38176c41baba9fb8be00d34a45a7795f0ba7b321cebde4521b66585df6'
'd69e5222d1169c4224a2b69a13e57fdd574cb1b5932b15f4bc6c7d269a9658dd87acb1be81f52fbcf3cb64f96978b9943d10cee2c21bff0565aaa93a5d35fcae'
'5f659651d8e562a4dcaae0f821d272d6e9c648b645b1d6ab1af61e4dd690dc5a4b9c6846753b7f935963f001bb1ae1f40cd77731b71ef5a8dbc079a360aa3f8f'
'3bdbd05c4a2c4fce4adf6802ff99b1088bdfad63da9ebfc470af9e271c3dd796f86fba1cf319d8f4078054d85c6d9e6a01f79994559f24cc77ee1a25724af2e6')
depends=('gcc' 'gmp' 'libffi' 'numactl' 'perl' 'llvm18')
provides=(
'haskell-array=0.5.4.0'
'haskell-base=4.17.2.1'
'haskell-binary=0.8.9.1'
'haskell-bytestring=0.11.5.3'
'haskell-cabal=3.8.1.0'
'haskell-cabal-syntax=3.8.1.0'
'haskell-containers=0.6.7'
'haskell-deepseq=1.4.8.0'
'haskell-directory=1.3.7.1'
'haskell-exceptions=0.10.5'
'haskell-filepath=1.4.2.2'
'haskell-ghc-bignum=1.3'
'haskell-ghc-boot=9.4.8'
'haskell-ghc-boot-th=9.4.8'
'haskell-ghc-compact=0.1.0.0'
'haskell-ghc-heap=9.4.8'
'haskell-ghc-prim=0.9.1'
'haskell-haddock=2.27.0'
'haskell-haskeline=0.8.2'
'haskell-hpc=0.6.1.0'
'haskell-hp2ps=0.1'
'haskell-hpc-bin=0.68'
'haskell-hsc2hs=0.68.8'
'haskell-integer-gmp=1.1'
'haskell-libiserv=9.4.8'
'haskell-mtl=2.2.2'
'haskell-parsec=3.1.16.1'
'haskell-pretty=1.1.3.6'
'haskell-process=1.6.18.0'
'haskell-stm=2.5.1.0'
'haskell-template-haskell=2.19.0.0'
'haskell-terminfo=0.4.1.5'
'haskell-text=2.0.2'
'haskell-time=1.12.2'
'haskell-transformers=0.5.6.2'
'haskell-unix=2.7.3'
'haskell-xhtml=3000.2.2.1'
'haskell-ghc-pkg=9.4.8'
"haskell-ghc=$pkgver"
"haskell-ghci=$pkgver"
ghc
ghc-libs
ghc-static
)
replaces=(
'haskell-array'
'haskell-base'
'haskell-binary'
'haskell-bytestring'
'haskell-cabal'
'haskell-cabal-syntax'
'haskell-containers'
'haskell-deepseq'
'haskell-directory'
'haskell-exceptions'
'haskell-filepath'
'haskell-ghc-bignum'
'haskell-ghc-boot'
'haskell-ghc-boot-th'
'haskell-ghc-compact'
'haskell-ghc-heap'
'haskell-ghci'
'haskell-ghc-prim'
'haskell-haddock'
'haskell-haskeline'
'haskell-hpc'
'haskell-hp2ps'
'haskell-hpc-bin'
'haskell-hsc2hs'
'haskell-integer-gmp'
'haskell-libiserv'
'haskell-mtl'
'haskell-parsec'
'haskell-pretty'
'haskell-process'
'haskell-stm'
'haskell-template-haskell'
'haskell-terminfo'
'haskell-text'
'haskell-time'
'haskell-transformers'
'haskell-unix'
'haskell-xhtml'
'haskell-ghc-pkg'
"haskell-ghc"
)
conflicts=('haskell-ghci')
backup=('usr/share/libalpm/hooks/ghc-rebuild-doc-index.hook'
'usr/share/libalpm/hooks/ghc-register.hook'
'usr/share/libalpm/hooks/ghc-unregister.hook')
package() {
tar -xf "$srcdir/data.tar.xz" -C "${pkgdir}"
install -Dm644 "$srcdir/ghc-rebuild-doc-index.hook" -t \
"$pkgdir/usr/share/libalpm/hooks/"
install -Dm644 "$srcdir/ghc-register.hook" -t \
"$pkgdir/usr/share/libalpm/hooks/"
install -Dm644 "$srcdir/ghc-unregister.hook" -t \
"$pkgdir/usr/share/libalpm/hooks/"
}
alex-bootstrap的PKGBUILD文件
pkgname=alex-bootstrap
provides=(alex)
pkgver=3.4.0.0
pkgrel=1
pkgdesc='Lexical analyser generator for Haskell'
arch=(x86_64)
url='https://hackage.haskell.org/package/alex'
license=(BSD-3-Clause)
depends=(ghc-libs)
source=("https://snapshot.debian.org/archive/debian-ports/20240919T192048Z/pool-loong64/main/a/alex/alex_3.4.0.1-1_loong64.deb")
sha512sums=('825efb12b9b10c725c1f76a8228e7e6da22c5cad2f8b213bc3b0b05f7da8565bc3afaee27cf70c0264199cc938011b6258779b392030a3608f1763ba73816523')
package() {
tar -xf "$srcdir/data.tar.xz" -C "${pkgdir}"
}
happy-bootstrap的PKGBUILD文件
pkgname=happy-bootstrap
provides=(happy haskell-happy-lib)
pkgver=2.1.5
pkgrel=1
pkgdesc="The Parser Generator for Haskell"
url="https://hackage.haskell.org/package/happy"
arch=('x86_64')
license=("BSD-2-Clause")
depends=('ghc-libs')
source=("https://snapshot.debian.org/archive/debian-ports/20240531T134713Z/pool-loong64/main/h/happy/happy_1.20.1.1-1%2Bb1_loong64.deb")
sha512sums=('d2096ac5020966a664affe9d8c5547adc1ced53978225779a2c3c8e0e7235e2ab016bb3e2a4bab6ea42ec5e8ea527195e4a546b13e8f9c12ee4bea58d89b4996')
package() {
tar -xf "$srcdir/data.tar.xz" -C "${pkgdir}"
}
haskell-hscolour-bootstrap的PKGBUILD文件
_hkgname=hscolour
pkgname=haskell-hscolour-bootstrap
provides=(haskell-hscolour)
pkgver=1.25
pkgrel=3
pkgdesc="Colourise Haskell code."
url="http://code.haskell.org/~malcolm/hscolour/"
license=("LGPL")
arch=('x86_64')
depends=('ghc-libs')
makedepends=('ghc')
source=("https://snapshot.debian.org/archive/debian-ports/20240917T132308Z/pool-loong64/main/h/hscolour/libghc-hscolour-dev_1.25-1_loong64.deb")
sha512sums=('c7daf013cc8ed230a4f75201f2add0ffb4c4a1380f7f303e3611107da0330f2dd0f3d0e2172e306c9b3f52817997e1e0aa0201d5edf6f268d5575e273defebab')
package() {
tar -xf "$srcdir/data.tar.xz" -C "${pkgdir}"
}
haskell-hadrian-bootstrap的PKGBUILD文件
_hkgname=hadrian
pkgname=haskell-hadrian-bootstrap
provides=(haskell-hadrian)
_ghcver=9.4.8
pkgver=0.1.0.0+$_ghcver
pkgrel=121
pkgdesc="GHC build system"
url="https://gitlab.haskell.org/ghc/ghc"
license=("BSD")
arch=('x86_64')
depends=('ghc-libs')
source=("https://snapshot.debian.org/archive/debian-ports/20241008T013019Z/pool-loong64/main/h/haskell-hadrian/hadrian_9.6.6-2_loong64.deb")
sha256sums=('b7a0193fa299e46a757b9f5a4bc5e3bc4aad0ecca5058d87a8ff41429bf86ead')
package() {
tar -xf "$srcdir/data.tar.xz" -C "${pkgdir}"
}
GHC的后端
GHC支持NCG和LLVM后端,其中Arch Linux官方使用的是NCG后端,但可能需要等到GHC 9.12才会有NCG的龙架构支持。因此,我们目前只能使用LLVM后端。
从Arch Linux官方获取GHC的PKGBUILD文件,在package_ghc函数下的depends字段添加llvm18,保证安装GHC时也安装LLVM18。
然而,GHC对于LLVM版本存在要求。事实上,GHC 9.6.6只支持LLVM 11-15,并不支持LLVM 18。但LLVM直到17才开始支持龙架构,而我们的维护仓库中最低也只有LLVM18,因此我们只能想办法强制使用LLVM18。
首先,在prepare函数中更改GHC构建时检测LLVM的版本要求,以允许使用LLVM18:
sed -i 's/LlvmMaxVersion=[0-9]\+ \(# not inclusive\)/LlvmMaxVersion=19 \1/' configure.ac
autoreconf -fiv
当然,就这样掩耳盗铃地简单修改肯定是不行的,我们还需要实际解决LLVM版本间的兼容问题。GHC会调用LLVM的CLI进行编译和优化,通过测试可以发现其使用了LLVM18中不再支持的某些选项,需要对此调整适配。
这里我们还有一些小技巧,可以充分利用已有的资源:之前在使用Debian的二进制包自举的时候已经发现,Debian的GHC 9.6.6二进制包依赖了LLVM18,因此我们可以推断:Debian必然有什么办法让GHC 9.6.6能够正常使用LLVM18。
在Debian的源码仓库中查找,我们不难找到GHC 9.9.6的补丁集,查看其中看起来高度相关的补丁llvm-new-pass-manager,发现正是我们想要的。
现在,在Debian的工作基础上,我们只需要导入并应用这一补丁即可。
跨发行版的安装后事务问题
Debian和Arch Linux的安装后事务处理机制不同,Debian使用dpkg的postinst和prerm脚本,而Arch Linux使用libalpm的hook机制。这导致了直接从二进制包解压出来的GHC安装后实际上还需要手动在root下运行ghc-pkg recache才能够正常使用。
对此,我们可以先不使用Arch Linux的干净nspawn容器的构建流程,暂时先手动进入nspawn构建容器环境中(可以直接从标准构建流程的容器快照得到,参见笔者之前的博客),以root身份运行:
ghc-pkg recache
然后切换到普通用户,这里是archbuild的builduser用户:
sudo -u builduser bash
复制相关构建文件到容器中,手动使用makepkg -A进行构建。在这一步,反正我们由Debian的二进制首次构建的软件包无论如何是不能直接上传的,因此即使这里引入了不干净的因素也无妨,后续使用本次构建出来的软件包进行干净构建即可。
跨发行版的工具链名称问题
使用来自Debian的GHC二进制包自举时,还会遇到因为工具链名称导致的问题:GHC尝试运行loongarch64-linux-gnu-ar作为ar工具,loongarch64-linux-gnu-ld作为ld工具,但在Arch Linux中,这些工具名称为ar/loongarch64-linux-gnu-gcc-ar和ld.bfd。笔者经过若干尝试,发现无论是设置AR、LD环境变量,还是运行autoreconf重新生成配置文件,都无法解决这个问题。
最终,笔者选择了在build函数中创建符号链接的方式解决这个问题:
mkdir -p "${srcdir}/bin"
ln -sf "$(which loongarch64-linux-gnu-gcc-ar)" "${srcdir}/bin/loongarch64-linux-gnu-ar"
ln -sf "$(which ld.bfd)" "${srcdir}/bin/loongarch64-linux-gnu-ld"
export PATH="$PATH:${srcdir}/bin"
这样在Arch Linux环境下完成一次GHC的构建后,我们得到的新的GHC软件包将使用正确的工具链名称,在后续补丁中也就无需集成这个Hack。
常规适配
在PKGBUILD的package_ghc打包函数中,Arch Linux上游通过$CARCH来引用文件名中的架构名称,这对于x86_64没有问题,$CARCH与$(uname -m)相同,但是对于Arch Linux for Loong64来说,$CARCH的值是loong64,而$(uname -m)的值是loongarch64,而GHC实际使用的是loongarch64,因此还需要将这里的$CARCH替换为$(uname -m)。
构建后的问题:功能缺失
经过上述步骤,我们终于成功构建出了GHC 9.6.6软件包。然而,在构建第一个Haskell包时,笔者即发现,GHC的功能并不完整,缺乏GHCi的支持,导致报错:
ghc: panic! (the 'impossible' happened)
GHC version 9.6.6
link: GHC not built to link this way: LinkInMemory
CallStack (from HasCallStack):
panic, called at compiler/GHC/Driver/Pipeline.hs:390:22 in ghc:GHC.Driver.Pipeline
笔者再次注意到,Debian事实上已经构建了不少的Haskell包,可能有值得参考的经验。再次浏览Debian的GHC 9.9.6的补丁集,笔者注意到Debian应用了hadrian-enable-interpreter这一简单补丁,禁用了Hadrian中的对启用平台的检测,从而强制在所有平台上启用了GHCi的支持。
根据Debian的说法,现在有很多平台的GHCi支持都是被错误地禁用了的。笔者便应用了这一补丁,重新构建GHC,终于得到了功能完整的GHC 9.6.6软件包。
ldc:利用有“功能替代”作用的其他软件包构建
LDC编译器是基于LLVM的D语言编译器。Loong Arch Linux在还没有LDC时就已经有了GDC(GDC是基于GCC的D语言编译器)。因此我们可以利用GDC构建LDC。
然而,如果我们直接将makedepends中的ldc替换为gcc-d就构建,会出现以下问题:
CMake Error at cmake/Modules/FindDCompiler.cmake:80 (message):
No supported D compiler (ldmd2, dmd, gdmd) found! Try setting the
'D_COMPILER' variable or 'DMD' environment variable.
这是因为LDC的CMake脚本在检测D编译器时,只支持ldmd2、dmd和gdmd这些接受D语言参考实现dmd参数风格的编译器,而实际上gcc-d并没有提供gdmd(不同的是ldc是提供ldmd2的)。
PS: 对于新手来说,上面这条知识本身可能不是那么关键,因为它已经被记录在我的教程里面了,很容易查阅到。但是笔者是怎么知道的呢?在遇到相关问题时知道怎么查/怎么做可能才更关键。笔者此前也对D语言编译器相关的一切完全不了解,遇到这个问题时,笔者首先去AUR查找了gdmd,找到了gdmd-git这个包,包的描述是DMD-like wrapper for GDC,即gdmd是GDC的DMD的包装器。再去专门查找一下相关知识,即可知道想要构建ldc,仅仅有gcc-d是不够的,还需要gdmd,因此笔者打包了AUR的gdmd-git并添加到本地仓库,然后在ldc的PKGBUILD文件中添加了gdmd到makedepends中。
从AUR获取gdmd-git并构建、添加到本地仓库,且在修改的ldc的PKGBUILD文件中额外添加gdmd到makedepends后,即可尝试构建ldc。
然而,这样直接构建时又会遇到问题。Arch Linux的ldc软件包的PKGBUILD文件中指定了一些编译参数:
...
-DD_COMPILER_FLAGS="-link-defaultlib-shared=false -linker=lld --flto=thin" \
-DADDITIONAL_DEFAULT_LDC_SWITCHES="\"-link-defaultlib-shared\"," \
...
经过尝试,我们可以发现,gdmd并不能接受这些参数,进而报错。我们可以在build()函数中,临时禁用这些参数再次构建。
构建完成后,我们可以将ldc软件包添加到本地仓库,然后使用这一用gcc-d构建的ldc软件包,利用Arch Linux官方的ldc的PKGBUILD文件,构建干净的ldc软件包。
不幸的是,我们又会遇到一个与架构及发行版环境有关的问题,即ldc这一软件包本身需要Patch。如果直接按照官方的PKGBUILD文件构建,在check阶段会报错:
/usr/bin/ld: /build/ldc/src/ldc/build/lib/libdruntime-ldc-unittest-shared.so: undefined reference to `__atomic_compare_exchange_16'
/usr/bin/ld: /build/ldc/src/ldc/build/lib/libdruntime-ldc-unittest-shared.so: undefined reference to `__atomic_load_16'
/usr/bin/ld: /build/ldc/src/ldc/build/lib/libdruntime-ldc-unittest-shared.so: undefined reference to__atomic_store_16'
collect2: error: ld returned 1 exit status
可见,libatomic库的链接问题导致了这一测试用例的构建失败。此时需要在CMake配置时显式指定链接到libatomic库:
...
-DLD_FLAGS="-Wl,--no-as-needed -latomic -Wl,--as-needed" \
...
然后再次构建,即得到了干净且测试全部通过的ldc软件包。上传补丁和ldc软件包即可。
附注:这一链接问题是ldc的一个bug,ldc的测试用到的druntime中使用了16字节的原子操作,这一操作在某些架构下(比如x86_64)可以内联为一条指令,但是在某些架构下(比如loon64和riscv64)则需要调用libatomic库中的函数。因此,这一问题在loon64上会暴露出来。经过讨论,ldc的维护者已经在开发分支中修复了这一问题,对于loong64与riscv64架构会默认链接libatomic库。因此,这一问题在未来的版本中将不再出现。参见这一PR。