Debian 软件包制作小记 (3)

IT, 其他
没有评论

第三篇内容会继续上一篇的话题,仍然是有关编译,与前一篇稍有区别的是编译工具。


基于 pbuilder 的源码构建:

在上一篇出现的 dpkg-buildpackage / debuild 非常依赖与当前的系统,也就是说,一个系统发行版就只能编译它专用的安装包。同时由于并非净室(chroot)编译环境,所以有可能会出现编译完成后,有部分依赖没有写入 control 文件的问题。pbuilder 可以很方便的解决这些问题,他能确保软件包能够在不同发行版本中的自动编译器能够干净准确的编译出 deb 安装包。

初始化环境:

首先安装 pbuilder 以及相关的软件包:

$ sudo apt-get install cowdancer cowbuilder pbuilder packaging-dev dh-make ubuntu-keyring debian-archive-keyring

一般来说可以直接使用 pbuilder create 初始化本地 pbuilder chroot 系统,但是这不利于多个 chroot 系统的管理。于是我们可以通过为当前用户添加 ~/.pbuilderrc 的方式 (或者 /etc/pbuilderrc) 达到自定义 chroot 系统构建的目的,以下是一个示例:

#!/bin/sh
## define cowbuilder as pbuilder warpper
PDEBUILD_PBUILDER=cowbuilder

set -e

if [ "$OS" == "debian" ]; then
    MIRRORSITE="http://ftp.de.debian.org/debian/"
    COMPONENTS="main contrib non-free"
    DEBOOTSTRAPOPTS=("${DEBOOTSTRAPOPTS[@]}"
        "--keyring=/usr/share/keyrings/debian-archive-keyring.gpg")
    : ${DIST:="stretch"}
    : ${ARCH:="amd64"}
elif [ "$OS" == "ubuntu" ]; then
    MIRRORSITE="http://de.archive.ubuntu.com/ubuntu/"
    COMPONENTS="main restricted universe multiverse"
    DEBOOTSTRAPOPTS=("${DEBOOTSTRAPOPTS[@]}"
        "--keyring=/usr/share/keyrings/ubuntu-archive-keyring.gpg")
    : ${DIST:="bionic"}
    : ${ARCH:="amd64"}
else
    echo "Unknown OS: $OS"
    exit 1
fi

if [ "$DIST" == "" ]; then
    echo "DIST is not set"
    exit 1
fi

if [ "$ARCH" == "" ]; then
    echo "ARCH is not set"
    exit 1
fi

## define locations
NAME="$OS-$DIST-$ARCH"
USRDISTPATH=/var/cache/pbuilder
DEBOOTSTRAPOPTS=("${DEBOOTSTRAPOPTS[@]}" "--arch=$ARCH")
BASETGZ="$USRDISTPATH/$NAME-base.tgz"
DISTRIBUTION="$DIST"
BUILDRESULT="$USRDISTPATH/$NAME/result"
APTCACHE="$USRDISTPATH/$NAME/aptcache"
BUILDPLACE="$USRDISTPATH/build"
HOOKDIR="$USRDISTPATH/hook.d"
# parallel make
DEBBUILDOPTS=-j$(nproc)

# create local repository if it doesn't already exist,
# such as during initial 'pbuilder create'
if [ ! -d $BUILDRESULT ] ; then
        mkdir -p $BUILDRESULT
        chmod g+rwx $BUILDRESULT
fi
if [ ! -e $BUILDRESULT/Packages ] ; then
        touch $BUILDRESULT/Packages
fi
if [ ! -e $BUILDRESULT/Release ] ; then
        cat << EOF > $BUILDRESULT/Release
Archive: $DIST
Component: main
Origin: pbuilder
Label: pbuilder
Architecture: $ARCH
EOF
 
fi

# create user script hook
mkdir -p $HOOKDIR
# local result package autoload
if [ -d $BUILDRESULT ] ; then
    export DEPSPATH=$BUILDRESULT
    BINDMOUNTS=$DEPSPATH
    DEBS=`ls $DEPSPATH | grep deb | wc -l`
    if [ ${DEBS} != 0 ] ; then
        OTHERMIRROR="deb [trusted=yes] file://$DEPSPATH ./"
        echo "add $DEPSPATH to source"
        cat > $HOOKDIR/D05deps <<'EOF'
#!/bin/sh
apt-get install --assume-yes apt-utils
( cd "$DEPSPATH"; apt-ftparchive packages . > Packages )
apt-get update
EOF
        chmod +x "${HOOKDIR}/D05deps"
    fi
fi
# failure redirect
cat > $HOOKDIR/C10shell <<'EOF'
#!/bin/sh

# invoke shell if build fails.
apt-get install -y --force-yes vim less bash
cd /tmp/buildd/*/debian/..
/bin/bash < /dev/tty > /dev/tty 2> /dev/tty
EOF
chmod +x "${HOOKDIR}/C10shell"

简单解释以下上面的脚本:首先在脚本中定义了 debian 和 ubuntu 的 mirror server,没有指定的话,ubuntu 无法下载 debian 的 chroot,反之亦然。接下去是对诸如文件保存位置,文件名称,钩子/缓存位置的设定。通过这些设定,构建出来的 chroot 归档就有比较规整的名字了。再下去是两个构建钩子。第一个钩子能自动载入生成在 result 中的 deb 包,外部的预编译二进制包也可以同样的自动被载入;第二个钩子是在编译失败时将 chroot 中的 Terminal 自动转发到现在使用的 Terminal 中。

利用这样的一个配置文件,我们就可以很简单的使用如下指令批量构建 chroot 环境了:

sudo OS=ubuntu DIST=bionic ARCH=amd64 pbuilder --create; \
sudo OS=ubuntu DIST=xenial ARCH=amd64 pbuilder --create; \
sudo OS=debian DIST=buster ARCH=amd64 pbuilder --create; \
sudo OS=debian DIST=stretch ARCH=amd64 pbuilder --create; \
sudo OS=debian DIST=jessie ARCH=amd64 pbuilder --create

Tips: 这里的 ARCH 只要 CPU 指令集支持即可进行编译,也就是说在一台运行 amd64 的系统上完全可以编译 i386 的二进制包,arm 平台也是如此。另一点值得注意的是比如 Scaleway 使用的 Cavium ThunderX SoCs 是阉割了 armhf 指令集的,也就是说它只能运行 aarch64 的程序,不可以编译 armhf的二进制包。关于跨构架的编译将在后文说明。

构建简单的二进制包:

pbuilder 需要的是由 dpkg-buildpackage 生成的 .orig.tar.gz.dsc.debian.tar.gz。当然,还有一种形式,是仅有 .orig.tar.gz.dsc:这种打包方式会将 debian 文件夹也存入压缩包,通常是由一些拥有版本管理能力的自动化打包系统生成的,常见于一些 ppa 的 nightly build。

实际的构建指令非常简单:

sudo OS=ubuntu DIST=bionic ARCH=amd64 pbuilder build libtorrent_0.13.6-1build1.dsc

在编译完成后,生成的无 GPG 签名的软件包会被以非 root 属主放置于 /var/cache/pbuilder/$OS-$DIST-$ARCH/result/

需要给生成的 .dsc.changes 文件签名的时候,只需要运行以下指令:

cd /var/cache/pbuilder/$OS-$DIST-$ARCH/result/
debsign libtorrent_0.13.6-1build1.dsc

构建具有本地依赖的二进制包:

当 chroot 系统中的基本依赖可以满足编译条件的时候,一切都非常的简答,但是如果你需要为二进制包添加一个外部的依赖,那么就需要做以下的事:

  • 方法一:

    如果说使用上述的 .pbuilderrc 那么,内含的钩子函数就会在编译的时候生成 Package 和 Release 信息。这些信息能够指导 apt 包管理器通过 deb [trusted=yes] file://$DEPSPATH ./ 的形式找到本地的 deb 二进制包并根据 control 文件中描述的依赖关系选择性安装。

    你可以通过运行以下指令,在编译前更新源列表:

    $ sudo OS=ubuntu DIST=bionic ARCH=amd64 pbuilder update --override-config

    如果依赖仍然没有被载入,那么反复尝试编译和更新源列表即可,通常在 1-2 次更新后就会解决。

  • 方法二:

    手动安装依赖。通常情况下在执行完 build/login 等指令后 pbuilder 的系统会被重置,但是我们可以通过使用以下代码登录到 chroot 环境中进行配置并且保存:

    $ sudo OS=ubuntu DIST=bionic ARCH=amd64 pbuilder --login --save-after-login

    通过 ^D (Control-D)离开这个 shell 时环境会被保存。

完成依赖安装后,其余的编译方式与简单的二进制包完全一致。

Tips: 在编译过程中通常需要注意 control 文件与 chroot 环境的匹配,并非所有的编译规则都可以同时用在多个 chroot 环境中。另外在出现错误进入 chroot 环境使用的 Terminal 以后也可以再次尝试使用 dpkg-buildpackage 命令构建,pbuilder 默认的规则比起 dpkg-buildpackage 更加严格,因此有时候会因为一些小问题导致无法完成编译,使用 dpkg-buildpackage 则可以绕过这些检查。


基于 qemu 的 pbuilder 源码构建:

上文提到的编译都是针对同构架硬件的,也就是说 x86/amd64 平台只能编译 x86/amd64 使用的二进制;arm 平台也只能编译它的二进制。在日常使用中,不难知道,arm 平台(特别是现有的 armel/armhf) 性能是非常孱弱的,如果直接使用它们进行 native 编译,需要的时间是很长的,举例来说,Allwinner 的 A83T 在使用多线程编译 transmission 的时候,使用的时间大约是一小时,如果需要针对不同的系统编译,时间也是成倍增加。解决方法实际上也很简单,使用交叉编译工具进行编译,并且使用 qemu 解决指令集翻译问题。在使用一台 4 线程 VPS (实验对象为 Hetzner Cloud VPS) 的情况下,编译时间则只需要20分钟。

注意:这里需要满足 pbuilder 版本 > 0.23 也就是说 Xenial 版本是没办法用来做 qemu 的跨构架编译的。

初始化环境:

qemu 环境的构建非常简单,通常来说不需要自己编译,可以直接通过以下指令就可以安装:

$ sudo apt-get install qemu binfmt-support qemu-user-static

然后就像普通的 pbuilder 构建一样,这里也需要用到一个专用的 .pbuilderrc

#!/bin/sh
## define cowbuilder as pbuilder warpper
PDEBUILD_PBUILDER=cowbuilder
## must use the apt "satisfydepends" resolver with pbuilder
#  https://wiki.debian.org/CrossCompiling#Building_with_pbuilder
PBUILDERSATISFYDEPENDSCMD="/usr/lib/pbuilder/pbuilder-satisfydepends-apt"
## if pbuilder-satisfydepends-apt not work, try following resolver
#PBUILDERSATISFYDEPENDSCMD="/usr/lib/pbuilder/pbuilder-satisfydepends-experimental"

set -e

if [ "$OS" == "debian" ]; then
    MIRRORSITE="http://ftp.de.debian.org/debian/"
    COMPONENTS="main contrib non-free"
    DEBOOTSTRAPOPTS=("${DEBOOTSTRAPOPTS[@]}"
        "--keyring=/usr/share/keyrings/debian-archive-keyring.gpg")
    : ${DIST:="stretch"}
    : ${ARCH:="armhf"}
elif [ "$OS" == "ubuntu" ]; then
    MIRRORSITE="http://ports.ubuntu.com/"
    COMPONENTS="main restricted universe multiverse"
    DEBOOTSTRAPOPTS=("${DEBOOTSTRAPOPTS[@]}"
        "--keyring=/usr/share/keyrings/ubuntu-archive-keyring.gpg")
    : ${DIST:="bionic"}
    : ${ARCH:="armhf"}
elif [ "$OS" == "raspbian" ]; then
    MIRRORSITE="http://mirror.de.leaseweb.net/raspbian/raspbian"
    COMPONENTS="main contrib non-free"
    DEBOOTSTRAPOPTS=("${DEBOOTSTRAPOPTS[@]}"
        "--keyring=/usr/share/keyrings/raspbian-archive-keyring.gpg")
    : ${DIST:="stretch"}
    : ${ARCH:="armhf"}
else
    echo "Unknown OS: $OS"
    exit 1
fi

if [ "$DIST" == "" ]; then
    echo "DIST is not set"
    exit 1
fi

if [ "$ARCH" == "" ]; then
    echo "ARCH is not set"
    exit 1
fi

if [ "$ARCH" == "armel" ] && [ "$(dpkg --print-architecture)" != "armel" ]; then
    DEBOOTSTRAP="qemu-debootstrap"
fi
if [ "$ARCH" == "armhf" ] && [ "$(dpkg --print-architecture)" != "armhf" ]; then
    DEBOOTSTRAP="qemu-debootstrap"
fi
if [ "$ARCH" == "aarch64" ] && [ "$(dpkg --print-architecture)" != "aarch64" ]; then
    DEBOOTSTRAP="qemu-debootstrap"
fi

## define locations
NAME="$OS-$DIST-$ARCH"
USRDISTPATH=/var/cache/pbuilder
DEBOOTSTRAPOPTS=("${DEBOOTSTRAPOPTS[@]}" "--arch=$ARCH")
BASETGZ="$USRDISTPATH/$NAME-base.tgz"
DISTRIBUTION="$DIST"
BUILDRESULT="$USRDISTPATH/$NAME/result"
APTCACHE="$USRDISTPATH/$NAME/aptcache"
BUILDPLACE="$USRDISTPATH/build"
HOOKDIR="$USRDISTPATH/hook.d"
# parallel make
DEBBUILDOPTS=-j$(nproc)

# create local repository if it doesn't already exist,
# such as during initial 'pbuilder create'
if [ ! -d $BUILDRESULT ] ; then
        mkdir -p $BUILDRESULT
        chmod g+rwx $BUILDRESULT
fi
if [ ! -e $BUILDRESULT/Packages ] ; then
        touch $BUILDRESULT/Packages
fi
if [ ! -e $BUILDRESULT/Release ] ; then
        cat << EOF > $BUILDRESULT/Release
Archive: $DIST
Component: main
Origin: pbuilder
Label: pbuilder
Architecture: $ARCH
EOF
 
fi

# create user script hook
mkdir -p $HOOKDIR
# local result package autoload
if [ -d $BUILDRESULT ] ; then
    export DEPSPATH=$BUILDRESULT
    BINDMOUNTS=$DEPSPATH
    DEBS=`ls $DEPSPATH | grep deb | wc -l`
    if [ ${DEBS} != 0 ] ; then
        OTHERMIRROR="deb [trusted=yes] file://$DEPSPATH ./"
        echo "add $DEPSPATH to source"
        cat > $HOOKDIR/D05deps <<'EOF'
#!/bin/sh
apt-get install --assume-yes apt-utils
( cd "$DEPSPATH"; apt-ftparchive packages . > Packages )
apt-get update
EOF
        chmod +x "${HOOKDIR}/D05deps"
    fi
fi
# failure redirect
cat > $HOOKDIR/C10shell <<'EOF'
#!/bin/sh

# invoke shell if build fails.
apt-get install -y --force-yes vim less bash
cd /tmp/buildd/*/debian/..
/bin/bash < /dev/tty > /dev/tty 2> /dev/tty
EOF
chmod +x "${HOOKDIR}/C10shell"

这个配置的主体内容与 native 编译相同,区别在于:

  1. 使用了pbuilder-satisfydepends-apt 解决依赖关系,有些时候这个包管理器也不能处理好依赖,那么还可以尝试一下 pbuilder-satisfydepends-experimental

  2. 使用到的 repo 也与 native 编译不同

  3. 使用 qemu-debootstrap 部署 chroot 环境。

值得注意的是,如果需要针对 raspbian 做编译,那么应当安装它的密匙串:

$ wget http://archive.raspbian.org/raspbian/pool/main/r/raspbian-archive-keyring/raspbian-archive-keyring_20120528.2_all.deb
$ sudo dpkg -i raspbian-archive-keyring_20120528.2_all.deb

我们使用如下命令初始化 chroot:

sudo OS=ubuntu DIST=bionic ARCH=armhf pbuilder --create; \
sudo OS=ubuntu DIST=xenial ARCH=armhf pbuilder --create; \
sudo OS=debian DIST=stretch ARCH=armhf pbuilder --create; \
sudo OS=debian DIST=jessie ARCH=armhf pbuilder --create; \
sudo OS=raspbian DIST=stretch ARCH=armhf pbuilder --create; \
sudo OS=raspbian DIST=jessie ARCH=armhf pbuilder --create

构建二进制包:

为了区别一个跨构架的编译,我们需要将编译参数改为如下形式:

$ sudo OS=ubuntu DIST=bionic ARCH=armhf pbuilder build --host-arch armhf libtorrent_0.13.6-1build1.dsc

在执行过程中,会启用用户态的 qemu 虚拟机运行编译。构建具有本地依赖的二进制包方法同 native 编译,因此不再赘述。

qemu 由于执行二进制代码的翻译,因此并不会像常见的 KVM 等虚拟机那样高效。但是由于常用的 arm 开发板通常在 CPU 计算能力和 IO 能力方面都与 x86/x64 平台有较大差距,因此这种方法能够确实的提高编译的效率。


参考文档:

https://www.debian.org/doc/manuals/maint-guide/

https://wiki.debian.org/CrossCompiling#Building_with_pbuilder

https://wiki.ubuntu.com/ARM/RootfsFromScratch/QemuDebootstrap

https://wiki.debian.org/PbuilderTricks#How_to_include_local_packages_in_the_build

https://pbuilder-docs.readthedocs.io/en/latest/faq.html

https://www.tolaris.com/2009/04/09/adding-a-local-respository-to-pbuilder/

https://jodal.no/2015/03/08/building-arm-debs-with-pbuilder/

, , ,
上一篇文章
Debian 软件包制作小记 (2)

发表评论

电子邮件地址不会被公开。 必填项已用*标注

Fill out this field
Fill out this field
请输入正确的电子邮件地址。

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据

菜单