diff options
Diffstat (limited to 'src/makerepropkg.in')
-rw-r--r-- | src/makerepropkg.in | 270 |
1 files changed, 270 insertions, 0 deletions
diff --git a/src/makerepropkg.in b/src/makerepropkg.in new file mode 100644 index 0000000..b271f25 --- /dev/null +++ b/src/makerepropkg.in @@ -0,0 +1,270 @@ +#!/bin/bash +# +# makerepropkg - rebuild a package to see if it is reproducible +# +# Copyright (c) 2019 by Eli Schwartz <eschwartz@archlinux.org> +# +# SPDX-License-Identifier: GPL-3.0-or-later + +m4_include(lib/common.sh) +m4_include(lib/archroot.sh) + +source /usr/share/makepkg/util/config.sh +source /usr/share/makepkg/util/message.sh + +declare -A buildinfo +declare -a buildenv buildopts installed installpkgs + +archiveurl='https://archive.archlinux.org/packages' +buildroot=/var/lib/archbuild/reproducible +diffoscope=0 + +chroot=$USER +[[ -n ${SUDO_USER:-} ]] && chroot=$SUDO_USER +[[ -z "$chroot" || $chroot = root ]] && chroot=copy + +parse_buildinfo() { + local line var val + + while read -r line; do + var="${line%% = *}" + val="${line#* = }" + case ${var} in + buildenv) + buildenv+=("${val}") + ;; + options) + buildopts+=("${val}") + ;; + installed) + installed+=("${val}") + ;; + *) + buildinfo["${var}"]="${val}" + ;; + esac + done +} + +get_pkgfile() { + local cdir=${cache_dirs[0]} + local pkgfilebase=${1} + local mode=${2} + local pkgname=${pkgfilebase%-*-*-*} + local pkgfile ext + + # try without downloading + if [[ ${mode} != localonly ]] && get_pkgfile "${pkgfilebase}" localonly; then + return 0 + fi + + for ext in .zst .xz ''; do + pkgfile=${pkgfilebase}.pkg.tar${ext} + + for c in "${cache_dirs[@]}"; do + if [[ -f ${c}/${pkgfile} ]]; then + cdir=${c} + break + fi + done + + for f in "${pkgfile}" "${pkgfile}.sig"; do + if [[ ! -f "${cdir}/${f}" ]]; then + if [[ ${mode} = localonly ]]; then + continue 2 + fi + msg2 "retrieving '%s'..." "${f}" >&2 + curl -Llf -# -o "${cdir}/${f}" "${archiveurl}/${pkgname:0:1}/${pkgname}/${f}" || continue 2 + fi + done + printf '%s\n' "file://${cdir}/${pkgfile}" + return 0 + done + + return 1 +} + +get_makepkg_conf() { + local fname=${1} + local makepkg_conf="${2}" + if ! buildtool_file=$(get_pkgfile "${fname}"); then + error "failed to retrieve ${fname}" + return 1 + fi + msg2 "using makepkg.conf from ${fname}" + bsdtar xOqf "${buildtool_file/file:\/\//}" usr/share/devtools/makepkg-x86_64.conf > "${makepkg_conf}" + return 0 +} + +usage() { + cat << __EOF__ +usage: ${BASH_SOURCE[0]##*/} [options] <package_file> + +Run this script in a PKGBUILD dir to build a package inside a +clean chroot while attempting to reproduce it. The package file +will be used to derive metadata needed for reproducing the +package, including the .PKGINFO as well as the buildinfo. + +For more details see https://reproducible-builds.org/ + +OPTIONS + -d Run diffoscope if the package is unreproducible + -c <dir> Set pacman cache + -M <file> Location of a makepkg config file + -l <chroot> The directory name to use as the chroot namespace + Useful for maintaining multiple copies + Default: $chroot + -h Show this usage message +__EOF__ +} + +while getopts 'dM:c:l:h' arg; do + case "$arg" in + d) diffoscope=1 ;; + M) archroot_args+=(-M "$OPTARG") ;; + c) cache_dirs+=("$OPTARG") ;; + l) chroot="$OPTARG" ;; + h) usage; exit 0 ;; + *|?) usage; exit 1 ;; + esac +done +shift $((OPTIND - 1)) + +check_root + +[[ -f PKGBUILD ]] || { error "No PKGBUILD in current directory."; exit 1; } + +# without arguments, get list of packages from PKGBUILD +if [[ -z $1 ]]; then + mapfile -t pkgnames < <(source PKGBUILD; pacman -Sddp --print-format '%r/%n' "${pkgname[@]}") + wait $! || { + error "No package file specified and failed to retrieve package names from './PKGBUILD'." + plain "Try '${BASH_SOURCE[0]##*/} -h' for more information." >&2 + exit 1 + } + msg "Reproducing all pkgnames listed in ./PKGBUILD" + set -- "${pkgnames[@]}" +fi + +# check each package to see if it's a file, and if not, try to download it +# using pacman -Sw, and get the filename from there +splitpkgs=() +for p in "$@"; do + if [[ -f ${p} ]]; then + splitpkgs+=("${p}") + else + pkgfile_remote=$(pacman -Sddp "${p}" 2>/dev/null) || { error "package name '%s' not in repos" "${p}"; exit 1; } + pkgfile=${pkgfile_remote#file://} + if [[ ! -f ${pkgfile} ]]; then + msg "Downloading package '%s' into pacman's cache" "${pkgfile}" + sudo pacman -Swdd --noconfirm --logfile /dev/null "${p}" || exit 1 + pkgfile_remote=$(pacman -Sddp "${p}" 2>/dev/null) + pkgfile="${pkgfile_remote#file://}" + fi + splitpkgs+=("${pkgfile}") + fi +done + +for f in "${splitpkgs[@]}"; do + if ! bsdtar -tqf "${f}" .BUILDINFO >/dev/null 2>&1; then + error "file is not a valid pacman package: '%s'" "${f}" + exit 1 + fi +done + +if (( ${#cache_dirs[@]} == 0 )); then + mapfile -t cache_dirs < <(pacman-conf CacheDir) +fi + +ORIG_HOME=${HOME} +IFS=: read -r _ _ _ _ _ HOME _ < <(getent passwd "${SUDO_USER:-$USER}") +load_makepkg_config +HOME=${ORIG_HOME} +[[ -d ${SRCDEST} ]] || SRCDEST=${PWD} + +parse_buildinfo < <(bsdtar -xOqf "${splitpkgs[0]}" .BUILDINFO) +export SOURCE_DATE_EPOCH="${buildinfo[builddate]}" +PACKAGER="${buildinfo[packager]}" +BUILDDIR="${buildinfo[builddir]}" +BUILDTOOL="${buildinfo[buildtool]}" +BUILDTOOLVER="${buildinfo[buildtoolver]}" +PKGEXT=${splitpkgs[0]#${splitpkgs[0]%.pkg.tar*}} + +# nuke and restore reproducible testenv +namespace="$buildroot/$chroot" +lock 9 "${namespace}.lock" "Locking chroot namespace '%s'" "${namespace}" +for copy in "${namespace}"/*/; do + [[ -d ${copy} ]] || continue + subvolume_delete_recursive "${copy}" +done +rm -rf --one-file-system "${namespace}" +(umask 0022; mkdir -p "${namespace}") + +for fname in "${installed[@]}"; do + if ! allpkgfiles+=("$(get_pkgfile "${fname}")"); then + error "failed to retrieve ${fname}" + exit 1 + fi +done + +trap 'rm -rf $TEMPDIR' EXIT INT TERM QUIT +TEMPDIR=$(mktemp -d --tmpdir makerepropkg.XXXXXXXXXX) + +makepkg_conf="${TEMPDIR}/makepkg.conf" +# anything before buildtool support is pinned to the last none buildtool aware release +if [[ -z "${BUILDTOOL}" ]]; then + get_makepkg_conf "devtools-20210202-3-any" "${makepkg_conf}" || exit 1 +# prefere to assume devtools up until matching makepkg version so repository packages remain reproducible +elif [[ "${BUILDTOOL}" = makepkg ]] && (( $(vercmp "${BUILDTOOLVER}" 6.0.1) <= 0 )); then + get_makepkg_conf "devtools-20210202-3-any" "${makepkg_conf}" || exit 1 +# all devtools builds +elif [[ "${BUILDTOOL}" = devtools ]] && get_makepkg_conf "${BUILDTOOL}-${BUILDTOOLVER}" "${makepkg_conf}"; then + true +# fallback to current makepkg.conf +else + warning "Unknown buildtool (${BUILDTOOL}-${BUILDTOOLVER}), using fallback" + makepkg_conf=@pkgdatadir@/makepkg-x86_64.conf +fi +printf '%s\n' "${allpkgfiles[@]}" | mkarchroot -M "${makepkg_conf}" -U "${archroot_args[@]}" "${namespace}/root" - || exit 1 + +# use makechrootpkg to prep the build directory +makechrootpkg -r "${namespace}" -l build -- --packagelist || exit 1 + +# set detected makepkg.conf options +{ + for var in PACKAGER BUILDDIR BUILDTOOL BUILDTOOLVER PKGEXT; do + printf '%s=%s\n' "${var}" "${!var@Q}" + done + printf 'OPTIONS=(%s)\n' "${buildopts[*]@Q}" + printf 'BUILDENV=(%s)\n' "${buildenv[*]@Q}" +} >> "${namespace}/build"/etc/makepkg.conf +install -d -o "${SUDO_UID:-$UID}" -g "$(id -g "${SUDO_UID:-$UID}")" "${namespace}/build/${BUILDDIR}" + +# kick off the build +arch-nspawn "${namespace}/build" \ + --bind="${PWD}:/startdir" \ + --bind="${SRCDEST}:/srcdest" \ + /chrootbuild -C --noconfirm --log --holdver --skipinteg +ret=$? + +if (( ${ret} == 0 )); then + msg2 "built succeeded! built packages can be found in ${namespace}/build/pkgdest" + msg "comparing artifacts..." + + for pkgfile in "${splitpkgs[@]}"; do + comparefiles=("${pkgfile}" "${namespace}/build/pkgdest/${pkgfile##*/}") + if cmp -s "${comparefiles[@]}"; then + msg2 "Package '%s' successfully reproduced!" "${pkgfile}" + else + ret=1 + warning "Package '%s' is not reproducible. :(" "${pkgfile}" + sha256sum "${comparefiles[@]}" + if (( diffoscope )); then + diffoscope "${comparefiles[@]}" + fi + fi + done +fi + +# return failure from chrootbuild, or the reproducibility status +exit ${ret} |