如何解决不同目标系统的包依赖性?

如何解决不同目标系统的包依赖性?

(TL;DR:在给定初始状态和所需的软件包列表的情况下,如何确定要安装的最小软件包集?)

我正在为无法访问分发镜像的系统创建 Ubuntu 安装程序 ISO。这些是嵌入式设备,只需要基础包和一些本地构建的包加上它们的所有依赖项。例如,我们使用的是 Qt/Xcb,因此libqt5*需要引入各种包。

目前,我正在使用强力方法来确定将哪些内容放入安装程序的 pool/extras 目录中。首先,我使用在回答 debconf 时提到的软件包作为种子d-i pkgsel/include,然后使用以下命令查找递归依赖项bash

# 1. Extract the dependencies from packages/*, removing version
#    conditions and changing separator to newline.
# 2. Find the recursive dependencies of all these packages using
#    package database, extracting Depends and PreDepends.
# 3. For each package we require:
#    a. if we find it in our cache, just copy it into the ISO (using
#       hard link for speed and space)
#    b. else, if we find it already in the ISO, do nothing
#    c. else, pass it through to step 4.
# 4. Search for matching packages in repositories that are not virtual
#    or foreign-architecture.
# 5. Download matching package files to the ISO.
(for i in "$TOP/packages"/*.deb; do dpkg-deb -f $i depends; done) | sed 's/([^()]*)//g;s/[|,]/\n/g'                                     \
  | xargs -r apt-cache --recurse --important depends | sed -re 's/^  (Pre)?Depends://;/[<|:]/d;s/^  *//;' | sort -u                     \
  | ( while read p;                                                                                                                     \
    do test $(find "$TOP/extras" -name "${p}_*.deb" -exec mv '{}' "$REMASTER_HOME"/remaster-iso/pool/extras \; -printf "true" -quit)    \
      || test $(find "$REMASTER_HOME"/remaster-iso/pool/{main,extras} -name "${p}_*.deb" -printf "true" -quit)                          \
      || echo $p;                                                                                                                       \
    done; )                                                                                                                             \
  | uniq | sed -r 's/[^ ]+/!~rforeign!~v^&$/' | xargs -r aptitude -q2 -F '%p' search                                                    \
  | (cd "$REMASTER_HOME"/remaster-iso/pool/extras; xargs -r apt-get download)

这里,$TOP/packages是种子包目录,$TOP/extras是以前下载的包的缓存,$REMASTER_HOME"/remaster-iso是解压后的安装程序 ISO 的根目录。

虽然上述方法有效,但它往往会获取比我们实际需要的更多的内容,因为apt-cache depends报告了每个包的所有替代依赖项。例如,grub-pc取决于debconfcdebconf提供debconf,因此 和debconf以及cdebconf它们的依赖项都包含在内。另外,xserver-xorg取决于xserver-xorg-video-all | xorg-driver-video,所以我的脚本会提取所有视频驱动程序 - 但我已经声明了对 的依赖关系xserver-xorg-video-fbdev,所以我希望它能够满足其自身的要求。

我想要的是相当于aptitude --download-only install ${MY_PACKAGES}在目标系统上运行。但是,当然,在构建主机上运行它会丢失大多数软件包,因为它们已经安装在那里。

我目前正在查看python-apt库,但我找不到任何为目标系统而不是运行它的主机初始化它的示例。有没有人做过类似的事情,我可以复制一下?

或者,还有其他可行的方法吗?我正在考虑创建一个原始的、最小的 chroot 并aptitude在其中运行命令,但我担心这将成为维护负担,并且其他人很难在自己的计算机上进行设置。

我希望可以使用该解决方案(具有新的初始状态)来在要更新任何包时找到必要的依赖项,因为我需要为目标系统的可移动介质写入一致的集(仍然没有访问分发镜像)。

答案1

debootstrap在目录中建立一个干净的新系统,使用 执行该目录中所需的操作chroot,然后获取在 中找到的所有内容chrootdir/var/cache/apt/archives/

在第二个答案后编辑:我刚刚检查了手册,似乎也可以用来debootstrap做大部分(如果不是全部)你想要的事情:它可以在 下运行fakeroot,并且它有--download-only--print-debs选项以及--include将其他包添加到也混合并解决它们的依赖关系。

在阅读源代码之前不确定是否使用两组不同的源代码 - 它似乎也支持这一点:)

例如:

% fakeroot /usr/sbin/debootstrap --variant=minbase --download-only --include=dnsutils wheezy /tmp/foo "http://ftp.us.debian.org/debian file:///local_packages_dir/"
[...]
I: Resolving dependencies of required packages...
I: Resolving dependencies of base packages...
I: Found additional required dependencies: [...]
I: Found additional base dependencies: [...]
[...]

有一点开销:

% du -sh /tmp/foo/var/cache/apt/archives/
40M     /tmp/foo/var/cache/apt/archives/
% du -sh /tmp/foo/
68M     /tmp/foo/

实际的下载可以使用 apt-mirror 之类的工具进行优化,这样就不会每次都从远程位置下载相同的文件。

答案2

这是一个bash脚本而不是python-apt,但我发现dpkg-offline其中包含类似的逻辑。我研究了它的工作原理,并成功创建了一个自己的脚本来进行必要的下载 - 可重复并作为普通非 root 用户:

#!/bin/bash
set -e

die() {
    exec >&2
    printf '%s\n' "$@"
    exit 1
}

test "$UID" != "0" || die \
    "Do not run '$0' as root, because it will break your packages."

test -n "$1" || die \
    "Usage: $0 <package-dir> [package] ..." \
    "  package may be followed by _ - + &m etc; like 'aptitude install'" \
    "  local packages must be listed in package-dir/Packages.gz" \
    "  downloaded packages are saved to package-dir/ubuntu"

PACKAGE_DIR="$1"; shift

BASE_DIR=$(mktemp -d)
trap 'rm -r "$BASE_DIR"' EXIT

(
    cd $BASE_DIR

    # Skeleton directory structure
    mkdir -p \
        etc/apt/preferences.d \
        etc/apt/sources.list.d \
        var/cache/apt/archives \
        var/lib/apt/lists \
        var/lib/aptitude \
        var/lib/dpkg \
        var/log

    # Use GPG keys from host, and create empty dpkg status
    cp /etc/apt/trusted.gpg etc/apt
    touch var/lib/dpkg/status

    cat >etc/apt/sources.list <<-EOF
    # Locally-built packages
    deb file://$PACKAGE_DIR /

    # Upstream distribution to pillage
    deb http://gb.archive.ubuntu.com/ubuntu/ trusty main restricted universe
    EOF

    # Don't let user's ~/.aptitude/* influence anything
    export HOME="$BASE_DIR"
    APT_OPTIONS=(-q2 \
        -o "Apt::Architecture=amd64" \
        -o "Apt::Install-Recommends=0" \
        -o "Dir::Etc=$BASE_DIR/etc/apt" \
        -o "Dir::State=$BASE_DIR/var/cache/apt" \
        -o "Dir::Cache=$BASE_DIR/var/cache/apt" \
        -o "Dir::Cache::Archives=$BASE_DIR/var/cache/apt/archives" \
        -o "Dir::Log=$BASE_DIR/var/log" \
        -o "Dir::State::Lists=$BASE_DIR/var/lib/apt/lists" \
        -o "Dir::State::status=$BASE_DIR/var/lib/dpkg/status" \
        -o "DPkg::Options::--admindir=$BASE_DIR/var/lib/dpkg" \
        )

    APTITUDE="aptitude ${APT_OPTIONS[@]}"

    $APTITUDE update
    $APTITUDE --download-only -y --allow-untrusted install "$@"
)

# We now have all required package files download into cache; put them
# into output directory.
ln -t "$PACKAGE_DIR/ubuntu" -vf var/cache/apt/archives/*.deb

# Create updated package index
( cd "$PACKAGE_DIR" && dpkg-scanpackages -m . | gzip -c >Packages.gz )

我可能可以使用它来创建一个 Python 程序来执行相同的操作,但 shell 脚本可能足以满足我的目的。

相关内容