├── .github └── workflows │ ├── make.yml │ └── repro.yml ├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── buildinfo ├── docs ├── asciidoc.conf ├── repro.8.txt └── repro.conf.5.txt ├── examples └── repro.conf.example └── repro.in /.github/workflows/make.yml: -------------------------------------------------------------------------------- 1 | name: Makefile CI 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - uses: actions/checkout@v2 15 | 16 | - name: Install dependencies 17 | run: sudo apt update && sudo apt install -y asciidoc 18 | 19 | - name: Run make 20 | run: make 21 | -------------------------------------------------------------------------------- /.github/workflows/repro.yml: -------------------------------------------------------------------------------- 1 | name: archlinux-repro 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | schedule: 9 | - cron: '0 9 * * 1' 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | strategy: 15 | fail-fast: false 16 | matrix: 17 | pkg: 18 | - archlinux-repro 19 | - which 20 | 21 | steps: 22 | - uses: actions/checkout@v2 23 | 24 | - name: Install dependencies 25 | run: sudo apt update && sudo apt install -y asciidoc systemd-container 26 | 27 | - name: Install archlinux-repro 28 | run: sudo make install 29 | 30 | - name: Fetch pkg 31 | run: docker run --rm -v $PWD/pkg:/var/cache/pacman/pkg archlinux pacman -Syw ${{ matrix.pkg }} --noconfirm 32 | 33 | - name: Remove signatures 34 | run: sudo rm ./pkg/*.sig 35 | 36 | - name: Test repro 37 | run: repro ./pkg/${{ matrix.pkg }}-* 38 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | repro 2 | docs/*.8 3 | docs/*.5 4 | releases/* 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 2 | 3 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 4 | 5 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 6 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PROGNM ?= archlinux-repro 2 | PREFIX ?= /usr/local 3 | BINDIR ?= $(PREFIX)/bin 4 | SHRDIR ?= $(PREFIX)/share 5 | DOCDIR ?= $(PREFIX)/share/doc 6 | MANDIR ?= $(PREFIX)/share/man 7 | CONFDIR ?= /etc 8 | MANS = $(basename $(wildcard docs/*.txt)) 9 | 10 | TAG = $(shell git describe --abbrev=0 --tags) 11 | 12 | all: man repro 13 | man: $(MANS) 14 | $(MANS): 15 | 16 | docs/repro.%: docs/repro.%.txt docs/asciidoc.conf 17 | a2x --no-xmllint --asciidoc-opts="-f docs/asciidoc.conf" -d manpage -f manpage -D docs $< 18 | 19 | repro: repro.in 20 | m4 -DREPRO_VERSION=$(TAG) -DREPRO_CONFIG_DIR=$(CONFDIR)/$(PROGNM) $< >$@ 21 | chmod 755 $@ 22 | 23 | .PHONY: install 24 | install: repro man 25 | install -Dm755 repro $(DESTDIR)$(BINDIR)/$(PROGNM) 26 | ln -s $(PROGNM) $(DESTDIR)$(BINDIR)/repro 27 | install -Dm755 buildinfo -t $(DESTDIR)$(BINDIR) 28 | install -Dm644 examples/* -t $(DESTDIR)$(DOCDIR)/$(PROGNM) 29 | for manfile in $(MANS); do \ 30 | install -Dm644 $$manfile -t $(DESTDIR)$(MANDIR)/man$${manfile##*.}; \ 31 | done; 32 | install -Dm644 LICENSE -t $(DESTDIR)$(SHRDIR)/licenses/$(PROGNM) 33 | 34 | .PHONY: uninstall 35 | uninstall: 36 | rm $(DESTDIR)$(BINDIR)/repro 37 | rm $(DESTDIR)$(BINDIR)/buildinfo 38 | rm -rf $(DESTDIR)$(DOCDIR)/$(PROGNM) 39 | for manfile in $(MANS); do \ 40 | rm $(DESTDIR)$(MANDIR)/man$${manfile##*.}/$${manfile##*/}; \ 41 | done; 42 | rm -rf $(DESTDIR)$(SHRDIR)/licenses/$(PROGNM) 43 | 44 | .PHONY: clean 45 | clean: 46 | rm -f repro $(MANS) 47 | 48 | .PHONY: tag 49 | tag: 50 | git describe --exact-match >/dev/null 2>&1 || git tag -s $(shell date +%Y%m%d) 51 | git push --tags 52 | 53 | .PHONY: release 54 | release: 55 | mkdir -p releases 56 | git -c tar.tar.gz.command='gzip -cn' archive --prefix=${PROGNM}-${TAG}/ -o releases/${PROGNM}-${TAG}.tar.gz ${TAG} 57 | gpg --detach-sign -o releases/${PROGNM}-${TAG}.tar.gz.sig releases/${PROGNM}-${TAG}.tar.gz 58 | gh release create -t "Release: ${TAG}" ${TAG} releases/${PROGNM}-${TAG}.tar.gz.sig releases/${PROGNM}-${TAG}.tar.gz 59 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | archlinux-repro 2 | =============== 3 | 4 | `archlinux-repro` is intended to be a tool for users to verify packages distributed by Arch Linux. 5 | 6 | The current goals are: 7 | - Recreate packages given a `.BUILDINFO` file, or `.pkg.tar.xz` 8 | - Download and verify needed packages from `archive.archlinux.org` 9 | - Be a simple and easily auditable code 10 | - Distribution independent. One should be able to verify Arch packages on Debian. 11 | 12 | Work in progress. Please read the code before using. 13 | 14 | ## Dependencies 15 | 16 | * asciidoc (make) 17 | * coreutils 18 | * curl 19 | * gnupg 20 | * systemd 21 | * bsdtar 22 | * zstd 23 | -------------------------------------------------------------------------------- /buildinfo: -------------------------------------------------------------------------------- 1 | #!/usr/bin/bash 2 | # shellcheck disable=SC2155 3 | 4 | # Copyright (c) 2018 Robin Broda 5 | # 6 | # Permission is hereby granted, free of charge, to any person obtaining a copy 7 | # of this software and associated documentation files (the "Software"), to deal 8 | # in the Software without restriction, including without limitation the rights 9 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | # copies of the Software, and to permit persons to whom the Software is 11 | # furnished to do so, subject to the following conditions: 12 | # 13 | # The above copyright notice and this permission notice shall be included in all 14 | # copies or substantial portions of the Software. 15 | # 16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | # SOFTWARE. 23 | 24 | declare -i format=1 builddate 25 | declare -a buildenv options installed 26 | declare -A data 27 | 28 | shopt -s extglob 29 | 30 | # Desc: Parses line into current global state 31 | # 1: line 32 | function parse_line () { 33 | IFS=$' = ' read -r key val <<< "${1}" 34 | 35 | case "${key}" in 36 | "format") 37 | if [[ ${val} -ne 1 && ${val} -ne 2 ]]; then 38 | echo "incompatible format (want ${format}, got ${val})" >&2 39 | exit 1 40 | fi 41 | data["${key}"]="${val}" 42 | ;; 43 | "buildenv") buildenv+=("${val}");; 44 | "options") options+=("${val}");; 45 | "installed") installed+=("${val}");; 46 | *) 47 | data["${key}"]="${val}" 48 | ;; 49 | esac 50 | } 51 | 52 | # Desc: Parses buildinfo from string into current global state 53 | # STDIN: .BUILDINFO 54 | function parse () { 55 | while read -r line; do 56 | parse_line "${line}" 57 | done 58 | } 59 | 60 | # Desc: Parses buildinfo from file into current global state 61 | # 1: .BUILDINFO path 62 | function parse_buildinfo () { 63 | parse < "${1}" 64 | } 65 | 66 | # Desc: Parses buildinfo from provided package into current global state 67 | # 1: Package path 68 | function parse_package () { 69 | parse <<< "$(tar xOf "${1}" .BUILDINFO 2>/dev/null)" 70 | } 71 | 72 | readonly archive_url="${ARCH_ARCHIVE_CACHE:-https://archive.archlinux.org/packages}" 73 | 74 | # Desc: get ALA link for given package 75 | # 1: Package 76 | # 2: Package compression 77 | function get_archive_link () { 78 | local pkg="$(rev <<< "${1}")" 79 | local ext="${2}" 80 | local pkgname="$(cut -d'-' -f4- <<< "${pkg}" | rev)" 81 | echo "${archive_url}/${1:0:1}/${pkgname}/${1}.pkg.tar.${ext}" 82 | } 83 | 84 | # Desc: check validity of archive URL 85 | # 1: Package archive link 86 | # Return: check result 87 | function verify_archive_link () { 88 | curl -IfLlso /dev/null "${1}" 89 | return $? 90 | } 91 | 92 | # Desc: Download package to $2 if not present and check signature using pacman's keyring 93 | # 1: Package archive link 94 | # 2: Target directory 95 | function download_archive_package () { 96 | local cachename 97 | local cachedir=$(readlink -e "${2}") 98 | if [ -f "${2}/${1}.pkg.tar.xz" ] && [ -f "${2}/${1}.pkg.tar.xz.sig" ]; then 99 | echo "Hit cache for ${2}/${1}.pkg.tar.xz" >&2 100 | echo "${2}/${1}.pkg.tar.xz" 101 | elif [ -f "${2}/${1}.pkg.tar.zst" ] && [ -f "${2}/${1}.pkg.tar.zst.sig" ]; then 102 | echo "Hit cache for ${2}/${1}.pkg.tar.zst" >&2 103 | echo "${2}/${1}.pkg.tar.zst" 104 | else 105 | local pwd="$(pwd)" 106 | local workdir="$(mktemp -d)" 107 | cd "${workdir}" || exit 1 108 | for ext in zst xz; do 109 | local target="$(get_archive_link "${1}" "$ext")" 110 | if verify_archive_link "${target}"; then 111 | local filename="$(basename ${target})" 112 | echo "Downloading ${filename}" >&2 113 | if curl -sSfL --remote-name-all "${target}" "${target}.sig"; then 114 | mv "${filename}"{,.sig} "${cachedir}/" 115 | echo "${2}/${filename}" 116 | else 117 | echo "Couldn't download ${filename}" >&2 118 | echo "check ${workdir}" >&2 119 | exit 1 120 | fi 121 | break 122 | fi 123 | done 124 | cd "${pwd}" || exit 1 125 | rm -r "${workdir}" 126 | fi 127 | } 128 | 129 | declare action 130 | while [ "$#" -gt 1 ]; do 131 | case "${1}" in 132 | -i) 133 | action="info" 134 | shift 1 135 | ;; 136 | -p) 137 | action="packages" 138 | shift 1 139 | ;; 140 | -d) 141 | action="download" 142 | shift 1 143 | ;; 144 | -f) 145 | action="field" 146 | shift 1 147 | ;; 148 | -ff) 149 | action="field_all" 150 | shift 1 151 | ;; 152 | -*) 153 | echo "unknown option: ${1}" >&2 154 | exit 1 155 | ;; 156 | *) 157 | break 158 | ;; 159 | esac 160 | done 161 | 162 | if [[ "${action}" == "" ]]; then 163 | echo "no option given" >&2 164 | exit 1 165 | fi 166 | 167 | hash tar zstd 2>/dev/null || { echo "Require tar and zstd in path! Aborting..."; exit 1; } 168 | 169 | declare file="$(readlink -e "${@: -1}")" 170 | set -- "${@:1:$(($#-1))}" 171 | if [[ "${file}" =~ \.pkg ]]; then 172 | parse_package "${file}" 173 | else 174 | parse_buildinfo "${file}" 175 | fi 176 | 177 | case "${action}" in 178 | "info") 179 | echo -e "Name : ${data[pkgname]}" 180 | echo -e "Base Name : ${data[pkgbase]}" 181 | echo -e "Version : ${data[pkgver]}" 182 | echo -e "Checksum (sha256) : ${data[pkgbuild_sha256sum]}" 183 | echo -e "Packager : ${data[packager]}" 184 | echo -e "Build Date : ${data[builddate]}" 185 | echo -e "Build Directory : ${data[builddir]}" 186 | echo -e "Build Environment : ${buildenv[*]}" 187 | echo -e "Build Tool : ${data[buildtool]}" 188 | echo -e "Build Tool Version : ${data[buildtoolver]}" 189 | echo -e "Build Directory : ${data[builddir]}" 190 | echo -e "Options : ${options[*]}" 191 | echo -e "Installed Packages : ${#installed[@]}" 192 | ;; 193 | "packages") 194 | for ipkg in "${installed[@]}"; do 195 | get_archive_link "${ipkg}" 196 | done 197 | ;; 198 | "field") 199 | case "${1}" in 200 | "buildenv") 201 | echo -e "${buildenv[*]}" 202 | ;; 203 | "options") 204 | echo -e "${options[*]}" 205 | ;; 206 | "installed") 207 | echo -e "${installed[*]}" 208 | ;; 209 | *) 210 | echo -e "${data[${1}]}" 211 | ;; 212 | esac 213 | ;; 214 | "field_all") 215 | for field in pkgname pkgbase pkgver pkgbuild_sha256sum packager builddate builddir format buildtool buildtoolver; do 216 | echo -e "${field}=${data[${field}]}" 217 | done 218 | echo -e "buildenv=${buildenv[*]}" 219 | echo -e "options=${options[*]}" 220 | echo -e "installed=${installed[*]}" 221 | ;; 222 | "download") 223 | if [ "$#" -gt 0 ]; then 224 | readonly target_dir="${1}" 225 | shift 1 226 | if [[ -d "${target_dir}" ]]; then 227 | for ipkg in "${installed[@]}"; do 228 | download_archive_package "${ipkg}" "${target_dir}" 229 | done 230 | else 231 | echo "Target directory doesn't exist" >&2 232 | exit 1 233 | fi 234 | else 235 | echo "Target directory not specified" >&2 236 | exit 1 237 | fi 238 | ;; 239 | esac 240 | -------------------------------------------------------------------------------- /docs/asciidoc.conf: -------------------------------------------------------------------------------- 1 | ## linkman: macro 2 | # Inspired by/borrowed from the GIT source tree at Documentation/asciidoc.conf 3 | # 4 | # Usage: linkman:command[manpage-section] 5 | # 6 | # Note, {0} is the manpage section, while {target} is the command. 7 | # 8 | # Show man link as: (
); if section is defined, else just show 9 | # the command. 10 | 11 | [macros] 12 | (?su)[\\]?(?Plinkman):(?P\S*?)\[(?P.*?)\]= 13 | 14 | [attributes] 15 | asterisk=* 16 | plus=+ 17 | caret=^ 18 | startsb=[ 19 | endsb=] 20 | backslash=\ 21 | tilde=~ 22 | apostrophe=' 23 | backtick=` 24 | litdd=-- 25 | 26 | ifdef::backend-docbook[] 27 | [linkman-inlinemacro] 28 | {0%{target}} 29 | {0#} 30 | {0#{target}{0}} 31 | {0#} 32 | endif::backend-docbook[] 33 | 34 | ifdef::backend-xhtml11[] 35 | [linkman-inlinemacro] 36 | {target}{0?({0})} 37 | endif::backend-xhtml11[] 38 | -------------------------------------------------------------------------------- /docs/repro.8.txt: -------------------------------------------------------------------------------- 1 | repro(8) 2 | ======== 3 | 4 | Name 5 | ---- 6 | repro - reproducible builds utility 7 | 8 | 9 | Synopsis 10 | -------- 11 | 'repro' [options] 12 | 13 | 14 | Description 15 | ----------- 16 | 'repro' is a script to help with verifying and checking reproducibility of 17 | packages. It builds a linkman:systemd-nspawn[1] container with the Arch Linux 18 | bootstrap image to build packages in a clean chroot. 19 | 20 | It takes a package as an argument and attempts to recreate the package with 21 | the information provided with linkman:BUILDINFO[5]. It will use the Arch Linux 22 | archive to fetch packages with the correct pkgver. 23 | 24 | 25 | Options 26 | ------- 27 | *-h*:: 28 | Print help. 29 | 30 | *-d*:: 31 | When enabled linkman:diffoscope[1] is run on the produced packages. 32 | `DIFFOSCOPE` can be set in linkman:repro.conf[5] to change the 33 | diffoscope command. 34 | 35 | *-f*:: 36 | When enabled, `repro` check for a PKGBUILD file in the current working 37 | directory. This one is used to build the package instead of checking out 38 | a PKGBUILD from asp. 39 | 40 | *-n*:: 41 | When enabled makepkg runs with --nocheck. 42 | 43 | *-o*:: 44 | Set the output directory for the rebuilt packages. Defaults to 45 | `./build`. 46 | 47 | See Also 48 | -------- 49 | linkman:makepkg[8] linkman:repro.conf[5] 50 | -------------------------------------------------------------------------------- /docs/repro.conf.5.txt: -------------------------------------------------------------------------------- 1 | repro.conf(5) 2 | ============= 3 | 4 | Name 5 | ---- 6 | repro.conf - repro configuration file 7 | 8 | 9 | Synopsis 10 | -------- 11 | /etc/archlinux-repro/repro.conf, $XDG_CONFIG_HOME/archlinux-repro/repro.conf, ~/.repro.conf 12 | 13 | 14 | Description 15 | ----------- 16 | Configuration options for repro are stored in repro.conf. This file is sourced 17 | by repro on launch. All the variables, regardless of being described below, are 18 | exported to the build environment. 19 | 20 | The system-wide configuration file is found in /etc/archlinux-repro/repro.conf. 21 | Induvidual options can be override on a per-user basis in 22 | $XDG_CONFIG_HOME/devtools-repro/repro.conf or ~/.repro.conf, with the former 23 | taking priority. 24 | 25 | 26 | Options 27 | ------- 28 | **BOOTSTRAPMIRROR=**"https://mirror.archlinux.no/iso/latest":: 29 | Download location of the bootstrap image used for creating the container. 30 | 31 | **BUILDDIRECTORY=**"/var/lib/repro":: 32 | Location where the containers should be stored. 33 | 34 | **DIFFOSCOPE=**"diffoscope":: 35 | Sets the linkman:diffoscope[1] command to be ran when unreproducible packages are 36 | encountered. 37 | 38 | **HOSTMIRROR=**"http://mirror.neuf.no/archlinux/\$repo/os/\$arch":: 39 | The mirror used inside the container by pacman. 40 | 41 | **IMGDIRECTORY=**"/tmp/arch_img":: 42 | Location where the Arch Linux bootstrap image should be downloaded to. 43 | 44 | **MAKEFLAGS=**"-j$(nproc)":: 45 | Configure MAKEFLAGS on the node. The default is set for optimization purposes. 46 | 47 | **ARCH_ARCHIVE_CACHE=**"https://archive.archlinux.org/packages":: 48 | Configure the archive mirror to use when downloading archived packages. 49 | 50 | **NOCHECK=**0:: 51 | Decide if makepkg should be run with or without --nocheck inside the container. 52 | Note: This could affect the build process. 53 | 54 | **CACHEDIR=**cache:: 55 | Choose the cache directory location. The default creates a cache in the working directory. 56 | 57 | **OUTDIR=**./build:: 58 | Choose the build output directory. The default creates `./build` in the working directory. 59 | 60 | See Also 61 | -------- 62 | linkman:repro[8] 63 | -------------------------------------------------------------------------------- /examples/repro.conf.example: -------------------------------------------------------------------------------- 1 | BOOTSTRAPMIRROR="https://mirror.archlinux.no/iso/latest" 2 | 3 | BUILDDIRECTORY="/var/lib/repo" 4 | 5 | CONFIGDIR="/etc/devtools-repro" 6 | 7 | DIFFOSCOPE="diffoscope" 8 | 9 | HOSTMIRROR="http://mirror.neuf.no/archlinux/\$repo/os/\$arch" 10 | 11 | IMGDIRECTORY="/tmp/arch_img" 12 | -------------------------------------------------------------------------------- /repro.in: -------------------------------------------------------------------------------- 1 | #!/usr/bin/bash 2 | 3 | VERSION='REPRO_VERSION' 4 | 5 | set -eE -o pipefail 6 | 7 | if ((DEBUG)); then 8 | set -x 9 | fi 10 | 11 | # Ensure umask is set correctly 12 | umask 022 13 | 14 | BUILDDIRECTORY=/var/lib/repro 15 | 16 | KEYRINGCACHE="${BUILDDIRECTORY}/keyring" 17 | 18 | BOOTSTRAPMIRROR="https://geo.mirror.pkgbuild.com/iso/latest" 19 | BOOTSTRAP_IMG=archlinux-bootstrap-"$(uname -m)".tar 20 | CONFIGDIR='REPRO_CONFIG_DIR' 21 | 22 | HOSTMIRROR="https://geo.mirror.pkgbuild.com/\$repo/os/\$arch" 23 | 24 | ARCHIVEURL="${ARCH_ARCHIVE_CACHE:-https://archive.archlinux.org/packages}" 25 | 26 | IMGDIRECTORY=$(mktemp -dt XXXXXXXXXX.arch_img) 27 | trap "{ rm -r $IMGDIRECTORY; }" EXIT 28 | 29 | DIFFOSCOPE="diffoscope" 30 | 31 | # Turn on/off check in repro 32 | NOCHECK=${NOCHECK:-0} 33 | 34 | # Check if systemd >=242 35 | if [ $(systemd-nspawn --version | grep -m 1 -Eo '[0-9]+' | head -1) -ge 242 ]; then 36 | ISSYSTEMD242=1 37 | fi 38 | 39 | CACHEDIR="${CACHEDIR:-cache}" 40 | OUTDIR="${OUTDIR:-./build}" 41 | 42 | # Default options 43 | run_diffoscope=0 44 | 45 | # By default we don't assume a PKGBUILD 46 | pkgbuild_file=0 47 | 48 | makepkg_args=( 49 | --syncdeps 50 | --clean 51 | --noconfirm 52 | --skippgpcheck 53 | ) 54 | 55 | # Desc: Escalates privileges 56 | orig_argv=("$0" "$@") 57 | src_owner=${SUDO_USER:-$USER} 58 | function check_root() { 59 | local keepenv=$1 60 | (( EUID == 0 )) && return 61 | if type -P sudo >/dev/null; then 62 | exec sudo --preserve-env=$keepenv -- "${orig_argv[@]}" 63 | else 64 | exec su root -c "$(printf ' %q' "${orig_argv[@]}")" 65 | fi 66 | } 67 | 68 | # Use a private gpg keyring 69 | function gpg() { 70 | command gpg --homedir="$BUILDDIRECTORY/_gnupg" "$@" 71 | } 72 | 73 | function init_gnupg() { 74 | mkdir -p "$BUILDDIRECTORY/" 75 | mkdir -p --mode 700 "$BUILDDIRECTORY/_gnupg" 76 | 77 | # Ensure signing key is available 78 | # This works on debian 79 | KEYID=3E80CA1A8B89F69CBA57D98A76A5EF9054449A5C 80 | if [ -z "$(gpg --armor --export "$KEYID" 2>/dev/null)" ]; then 81 | gpg --keyserver=keys.openpgp.org --recv-keys "$KEYID" 82 | fi 83 | } 84 | 85 | # Desc: Sets the appropriate colors for output 86 | function colorize() { 87 | # test if stdout is a tty 88 | if [ -t 1 ]; then 89 | # prefer terminal safe colored and bold text when tput is supported 90 | if tput setaf 0 &>/dev/null; then 91 | ALL_OFF="$(tput sgr0)" 92 | BOLD="$(tput bold)" 93 | BLUE="${BOLD}$(tput setaf 4)" 94 | GREEN="${BOLD}$(tput setaf 2)" 95 | RED="${BOLD}$(tput setaf 1)" 96 | YELLOW="${BOLD}$(tput setaf 3)" 97 | else 98 | ALL_OFF="\e[0m" 99 | BOLD="\e[1m" 100 | BLUE="${BOLD}\e[34m" 101 | GREEN="${BOLD}\e[32m" 102 | RED="${BOLD}\e[31m" 103 | YELLOW="${BOLD}\e[33m" 104 | fi 105 | else 106 | # stdout is piped, disable colors 107 | ALL_OFF="" 108 | BOLD="" 109 | BLUE="" 110 | GREEN="" 111 | RED="" 112 | YELLOW="" 113 | fi 114 | readonly ALL_OFF BOLD BLUE GREEN RED YELLOW 115 | } 116 | 117 | # Desc: Message format 118 | function msg() { 119 | local mesg=$1; shift 120 | # shellcheck disable=SC2059 121 | printf "${GREEN}==>${ALL_OFF}${BOLD} ${mesg}${ALL_OFF}\n" "$@" >&2 122 | } 123 | 124 | # Desc: Sub-message format 125 | function msg2() { 126 | local mesg=$1; shift 127 | # shellcheck disable=SC2059 128 | printf "${BLUE} ->${ALL_OFF}${BOLD} ${mesg}${ALL_OFF}\n" "$@" >&2 129 | } 130 | 131 | # Desc: Warning format 132 | function warning() { 133 | local mesg=$1; shift 134 | # shellcheck disable=SC2059 135 | printf "${YELLOW}==> $(gettext "WARNING:")${ALL_OFF}${BOLD} ${mesg}${ALL_OFF}\n" "$@" >&2 136 | } 137 | 138 | # Desc: Error format 139 | function error() { 140 | local mesg=$1; shift 141 | # shellcheck disable=SC2059 142 | printf "${RED}==> $(gettext "ERROR:")${ALL_OFF}${BOLD} ${mesg}${ALL_OFF}\n" "$@" >&2 143 | } 144 | 145 | ## 146 | # usage : nlock( $fd, $file, $message, [ $message_arguments... ] ) 147 | # 148 | # Desc: non-blocking exclusive (write) lock 149 | ## 150 | nlock() { 151 | # Only reopen the FD if it wasn't handed to us 152 | if ! [[ "/dev/fd/$1" -ef "$2" ]]; then 153 | mkdir -p -- "$(dirname -- "$2")" 154 | eval "exec $1>"'"$2"' 155 | fi 156 | 157 | flock -n "$1" 158 | } 159 | 160 | ## 161 | # usage : lock( $fd, $file, $message, [ $message_arguments... ] ) 162 | # 163 | # Desc: normal - blocking exclusive (write) lock 164 | ## 165 | lock() { 166 | # Only reopen the FD if it wasn't handed to us 167 | if ! [[ "/dev/fd/$1" -ef "$2" ]]; then 168 | mkdir -p -- "$(dirname -- "$2")" 169 | eval "exec $1>"'"$2"' 170 | fi 171 | 172 | flock "$1" 173 | } 174 | 175 | ## 176 | # usage : slock( $fd, $file, $message, [ $message_arguments... ] ) 177 | # 178 | # Desc: blocking shared (read) lock 179 | ## 180 | slock() { 181 | # Only reopen the FD if it wasn't handed to us 182 | if ! [[ "/dev/fd/$1" -ef "$2" ]]; then 183 | mkdir -p -- "$(dirname -- "$2")" 184 | eval "exec $1>"'"$2"' 185 | fi 186 | 187 | flock -s "$1" 188 | } 189 | 190 | ## 191 | # usage : lock_close( $fd ) 192 | ## 193 | lock_close() { 194 | local fd=$1 195 | # https://github.com/koalaman/shellcheck/issues/862 196 | # shellcheck disable=2034 197 | exec {fd}>&- 198 | } 199 | 200 | # Desc: Executes an command inside a given nspawn container 201 | # 1: Container name 202 | # 2: Command to execute 203 | function exec_nspawn(){ 204 | local container=$1 205 | 206 | # EPHEMERAL in systemd-nspawn uses implicit overlayfs mounts to provide 207 | # the container. If the root container is being updated or files are in 208 | # the lower directory disappear the results are unspecified and might 209 | # cause weird behaviour. 210 | # 211 | # Thus we acquire read locks on the build container to ensure nothing gets 212 | # a write lock. The code is weird because the locking mechanism here is 213 | # implicit as opposed to explicit in the top level of cmd_check. 214 | if ((EPHEMERAL)); then 215 | slock 8 "$BUILDDIRECTORY/$container.lock" 216 | fi 217 | systemd-nspawn -q \ 218 | --as-pid2 \ 219 | --register=no \ 220 | ${EPHEMERAL:+--ephemeral} \ 221 | ${ISSYSTEMD242:+--pipe} \ 222 | ${MAX_MEMORY:+--property="MemoryMax=${MAX_MEMORY}"} \ 223 | -E "PATH=/usr/local/sbin:/usr/local/bin:/usr/bin" \ 224 | -D "$BUILDDIRECTORY/$container" "${@:2}" 225 | if ((EPHEMERAL)); then 226 | lock_close 8 "$BUILDDIRECTORY/$container.lock" 227 | fi 228 | } 229 | 230 | # Desc: Removes the root container 231 | function cleanup_root_volume(){ 232 | warning "Removing root container..." 233 | rm -rf "$BUILDDIRECTORY/root" 234 | } 235 | 236 | # Desc: Removes a given snapshot 237 | # 1: Snapshot name 238 | function remove_snapshot (){ 239 | local build=$1 240 | msg2 "Delete snapshot for $build..." 241 | rm -rf "${BUILDDIRECTORY:?}/${build}" 242 | trap - ERR INT 243 | } 244 | 245 | # Desc: Creates a snapshot of the root container 246 | # 1: name of container 247 | function create_snapshot (){ 248 | local build="$1" 249 | trap "{ remove_snapshot \"$build\" ; exit 1; }" ERR INT 250 | msg2 "Create snapshot for $build..." 251 | mkdir -p "${BUILDDIRECTORY}/${build}/startdir" 252 | touch "$BUILDDIRECTORY/$build" 253 | } 254 | 255 | # Desc: Build a package inside a container 256 | # 1: Container name 257 | # 2: Container buildpath 258 | function build_package(){ 259 | local build=$1 260 | local builddir=${2:-"/startdir"} 261 | local args="" 262 | if ((pkgbuild_file)); then 263 | args=--bind="${PWD}:/startdir" 264 | fi 265 | exec_nspawn "$build" \ 266 | bash <<-__END__ 267 | set -e 268 | install -d -o builduser -g builduser /pkgdest 269 | install -d -o builduser -g builduser /srcpkgdest 270 | install -d -o builduser -g builduser /build 271 | __END__ 272 | exec_nspawn "$build" $args sudo -iu builduser bash -c ". /etc/profile; . /env; cd /startdir; makepkg ${makepkg_args[*]}" 273 | mkdir -p "$OUTDIR" 274 | for pkgfile in "$BUILDDIRECTORY/$build"/pkgdest/*; do 275 | mv "$pkgfile" "$OUTDIR/" 276 | done 277 | chown -R "$src_owner" "$OUTDIR" 278 | } 279 | 280 | # Desc: Sets up a container with the correct files 281 | function init_chroot(){ 282 | mkdir -p "$BUILDDIRECTORY" 283 | 284 | # Always lock first. Otherwise we might end up... 285 | # - doing the same thing again - if using test/lock/mkdir 286 | # - with empty directory in the follow-up lock - if using test/mkdir/lock 287 | lock 9 "$BUILDDIRECTORY"/root.lock 288 | if [ ! -d "$BUILDDIRECTORY"/root ]; then 289 | init_gnupg 290 | 291 | if ! compgen -G "$IMGDIRECTORY/$bootstrap_img"* > /dev/null; then 292 | msg "Downloading bootstrap image..." 293 | 294 | for ext in zst gz; do 295 | bootstrap_img="$BOOTSTRAP_IMG.$ext" 296 | ( cd "$IMGDIRECTORY" && curl -f --remote-name-all "$BOOTSTRAPMIRROR/$bootstrap_img"{,.sig} ) 297 | if ! gpg --verify "$IMGDIRECTORY/$bootstrap_img.sig" "$IMGDIRECTORY/$bootstrap_img"; then 298 | error "Can't verify image" 299 | exit 1 300 | fi 301 | break 302 | done 303 | fi 304 | 305 | msg "Preparing chroot" 306 | trap '{ cleanup_root_volume; exit 1; }' ERR 307 | trap '{ cleanup_root_volume; trap - INT; kill -INT $$; }' INT 308 | 309 | msg2 "Extracting image into container..." 310 | mkdir -p "$BUILDDIRECTORY/root" 311 | tar xvf "$IMGDIRECTORY/$bootstrap_img" -C "$BUILDDIRECTORY/root" --strip-components=1 > /dev/null 312 | 313 | printf 'Server = %s\n' "$HOSTMIRROR" > "$BUILDDIRECTORY"/root/etc/pacman.d/mirrorlist 314 | sed -i "s/LocalFileSigLevel.*//g" "$BUILDDIRECTORY/root/etc/pacman.conf" 315 | 316 | systemd-machine-id-setup --root="$BUILDDIRECTORY"/root 317 | msg2 "Setting up keyring, this might take a while..." 318 | exec_nspawn root pacman-key --init &> /dev/null 319 | exec_nspawn root pacman-key --populate archlinux &> /dev/null 320 | touch "$BUILDDIRECTORY/root/.repro-2" 321 | else 322 | if [ ! -f "$BUILDDIRECTORY/root/.repro-2" ]; then 323 | error "Please delete $BUILDDIRECTORY and initialize the chroots again" 324 | exit 1 325 | fi 326 | msg "Reusing existing container" 327 | fi 328 | lock_close 9 329 | 330 | if nlock 9 "$BUILDDIRECTORY"/root.lock; then 331 | msg "Updating container" 332 | printf 'Server = %s\n' "$HOSTMIRROR" > "$BUILDDIRECTORY"/root/etc/pacman.d/mirrorlist 333 | exec_nspawn root pacman -Sy --noconfirm archlinux-keyring 334 | exec_nspawn root pacman -Su --noconfirm 335 | lock_close 9 336 | else 337 | msg "Couldn't acquire container lock, didn't update." 338 | fi 339 | trap - ERR INT 340 | } 341 | 342 | # Desc: Reproduces a package 343 | function cmd_check(){ 344 | local cachedir="${CACHEDIR}" 345 | 346 | trap - ERR INT 347 | 348 | declare -A buildinfo 349 | while IFS=$'=' read -r key value; do 350 | [[ "${key}" = [#!]* ]] || [[ "${key}" = "" ]] || buildinfo["${key}"]="${value}" 351 | done <<< "$(buildinfo -ff "${pkg}")" 352 | packager="${buildinfo[packager]}" 353 | builddir="${buildinfo[builddir]}" 354 | pkgver="${buildinfo[pkgver]}" 355 | pkgbase=${buildinfo[pkgbase]} 356 | options=${buildinfo[options]} 357 | buildenv=${buildinfo[buildenv]} 358 | format=${buildinfo[format]} 359 | installed=${buildinfo[installed]} 360 | 361 | pkgbuild_sha256sum="${buildinfo[pkgbuild_sha256sum]}" 362 | SOURCE_DATE_EPOCH="${buildinfo[builddate]}" 363 | BUILDTOOL=${buildinfo[buildtool]} 364 | BUILDTOOLVER=${buildinfo[buildtoolver]} 365 | 366 | DEVTOOLS="current devtools (fallback)" 367 | DEVTOOLS_PKG="devtools" 368 | if [[ -z "${BUILDTOOL}" ]] || [[ "${BUILDTOOL}" = makepkg ]]; then 369 | DEVTOOLS="devtools-20210202-3-any" 370 | DEVTOOLS_PKG="$ARCHIVEURL/d/devtools/${DEVTOOLS}.pkg.tar.zst" 371 | elif [[ "${BUILDTOOL}" = devtools ]] ; then 372 | DEVTOOLS="${BUILDTOOL}-${BUILDTOOLVER}" 373 | DEVTOOLS_PKG="$ARCHIVEURL/${BUILDTOOL:0:1}/${BUILDTOOL}/${DEVTOOLS}.pkg.tar${pkg##*tar}" 374 | fi 375 | msg2 "Using devtools version: %s" "${DEVTOOLS}" 376 | 377 | if [[ ${format} -ne 1 && ${format} -ne 2 ]]; then 378 | error "unsupported BUILDINFO format or no format definition found, aborting rebuild" 379 | exit 1 380 | fi 381 | 382 | msg2 "Preparing packages" 383 | mkdir -p "${cachedir}" 384 | mapfile -t packages < <(buildinfo -d "${cachedir}" "${pkg}") 385 | msg2 "Finished preparing packages" 386 | 387 | msg "Starting build..." 388 | local build="${pkgbase}_$$" 389 | create_snapshot "$build" 0 390 | 391 | local build_root_dir="$BUILDDIRECTORY/${build}" 392 | 393 | # Father I have sinned 394 | if ((!pkgbuild_file)); then 395 | msg2 "Fetching PKGBUILD from git..." 396 | 397 | # Lock the cachedir as we might have a race condition with pacman -S and the cachedir 398 | lock 9 "${cachedir}.lock" 399 | 400 | EPHEMERAL=1 exec_nspawn root --bind="${build_root_dir}/startdir:/startdir" --bind="$(readlink -e ${cachedir}):/var/cache/pacman/pkg" \ 401 | bash <<-__END__ 402 | shopt -s globstar 403 | 404 | pacman -S devtools --noconfirm --needed 405 | 406 | if ! pkgctl repo clone --protocol https --switch "$pkgver" "$pkgbase"; then 407 | echo "ERROR: Failed checkout $pkgbase" >&2 408 | exit 1 409 | fi 410 | 411 | if ! echo "$pkgbuild_sha256sum $pkgbase/PKGBUILD" | sha256sum -c; then 412 | echo "ERROR: Failed to find commit this was built with (PKGBUILD checksum didn't match)" >&2 413 | exit 1 414 | fi 415 | 416 | mv ./$pkgbase/* /startdir 417 | __END__ 418 | lock_close 9 "${cachedir}.lock" 419 | elif [[ -r "PKGBUILD" ]]; then 420 | if [[ "$(sha256sum PKGBUILD | awk '{print $1}')" != "$pkgbuild_sha256sum" ]]; then 421 | error "PKGBUILD doesn't match the checksum" 422 | exit 1 423 | fi 424 | else 425 | error "No PKGBUILD file present!" 426 | exit 1 427 | fi 428 | 429 | # buildinfo returns packages with absolute paths to the location 430 | # this strips the paths and adds "cache/" prefix 431 | packages=(${packages[@]##*/}) 432 | packages=(${packages[@]/#/cache\/}) 433 | 434 | # shellcheck disable=SC2086 435 | keyring_package="$(printf -- '%s\n' ${installed[*]} | grep -E "archlinux-keyring")" 436 | 437 | mkdir -p "$KEYRINGCACHE" 438 | 439 | # Always lock first. Otherwise we might end up... 440 | # - doing the same thing again - if using test/lock/mkdir 441 | # - with empty directory in the follow-up lock - if using test/mkdir/lock 442 | lock 9 "$KEYRINGCACHE/$keyring_package.lock" 443 | if [ ! -d "$KEYRINGCACHE/$keyring_package" ]; then 444 | msg2 "Setting up $keyring_package in keyring cache, this might take a while..." 445 | 446 | # shellcheck disable=SC2086 447 | keyring=$(printf -- '%s\n' ${packages[*]} | grep -E "archlinux-keyring") 448 | EPHEMERAL=1 exec_nspawn root --bind="${build_root_dir}:/mnt" --bind="$(readlink -e "${cachedir}"):/cache" bash -c \ 449 | 'pacstrap -U /mnt -dd "$@"' -bash "${keyring}" &>/dev/null 450 | 451 | mkdir -p "$KEYRINGCACHE/$keyring_package" 452 | trap "{ rm -rf $KEYRINGCACHE/$keyring_package ; exit 1; }" ERR INT 453 | 454 | # We have to rewind time for gpg when building a package so that 455 | # signatures which were valid at the time the package was built 456 | # are still considered valid now, even if e.g. one of the keys 457 | # has since expired. 458 | # 459 | # However, gpg is finicky about time. Signatures which appear to 460 | # be created in the future, or by a key created in the future, 461 | # will be ignored. Keys which appear to be created in the future 462 | # cannot be signed. To make things work we need to create the 463 | # local master key and sign everything at a time after every key 464 | # in the keyring exists, but before any packages that depend on 465 | # it could have been built. 466 | # 467 | # Do this by using precisely the time that the keyring package 468 | # was built. 469 | 470 | keyring_build_date="$(buildinfo -f builddate "${cachedir}/${keyring##*/}")" 471 | 472 | # Note that while we leave faked-system-time in gpg.conf, this 473 | # will be overridden during the actual build by adding another 474 | # faked-system-time line to the end of the file, which takes 475 | # precedence. 476 | EPHEMERAL=1 exec_nspawn root \ 477 | --bind="$KEYRINGCACHE/$keyring_package:/mnt" \ 478 | --bind="${build_root_dir}/usr/share/pacman/keyrings:/usr/share/pacman/keyrings" \ 479 | -E PACMAN_KEYRING_DIR=/mnt \ 480 | bash &> /dev/null <<-__END__ 481 | echo "faked-system-time ${keyring_build_date}" >> /mnt/gpg.conf 482 | pacman-key --init 483 | pacman-key --populate archlinux 484 | __END__ 485 | trap - ERR INT 486 | else 487 | msg2 "Found $keyring_package in keyring cache" 488 | fi 489 | lock_close 9 "$KEYRINGCACHE/$keyring_package.lock" 490 | 491 | # Acquire shared locks for keyring as it could still be initialized at this point 492 | slock 9 "$KEYRINGCACHE/$keyring_package.lock" 493 | msg "Installing packages" 494 | # shellcheck disable=SC2086 495 | EPHEMERAL=1 exec_nspawn root \ 496 | --bind="${build_root_dir}:/mnt" \ 497 | --bind-ro="$KEYRINGCACHE/$keyring_package:/gnupg" \ 498 | --bind="$(readlink -e ${cachedir}):/var/cache/pacman/pkg" \ 499 | --bind="$(readlink -e ${cachedir}):/cache" \ 500 | bash -bash "${packages[@]}" <<-__END__ 501 | set -e 502 | rm --recursive /etc/pacman.d/gnupg/ 503 | cp --target-directory=/etc/pacman.d/ --recursive /gnupg 504 | echo "faked-system-time ${SOURCE_DATE_EPOCH}" >> /etc/pacman.d/gnupg/gpg.conf 505 | pacstrap -G -U /mnt --needed "\$@" 506 | 507 | echo "Installing devtools from $DEVTOOLS_PKG" 508 | # Ignore all dependencies since we only want the file 509 | # Saves us a few seconds and doesn't download a bunch of things 510 | # we are getting rid off 511 | if [[ "$DEVTOOLS_PKG" == https* ]]; then 512 | pacman --noconfirm --needed -Udd "$DEVTOOLS_PKG" 513 | else 514 | pacman --noconfirm --needed -Sddu "$DEVTOOLS_PKG" 515 | fi 516 | 517 | if [[ -f /usr/share/devtools/makepkg.conf.d/x86_64.conf ]]; then 518 | cp -v /usr/share/devtools/makepkg.conf.d/x86_64.conf /mnt/etc/makepkg.conf 519 | elif [[ -f /usr/share/devtools/makepkg-x86_64.conf ]]; then 520 | cp -v /usr/share/devtools/makepkg-x86_64.conf /mnt/etc/makepkg.conf 521 | else 522 | echo "Failed to find the makepkg.conf location, please report to archlinux-repro" 523 | exit 1 524 | fi 525 | __END__ 526 | lock_close 9 "$KEYRINGCACHE/$keyring_package.lock" 527 | 528 | # Setup makepkg.conf 529 | { 530 | printf 'MAKEFLAGS="%s"\n' "${MAKEFLAGS:--j$(nproc)}" 531 | printf 'PKGDEST=/pkgdest\n' 532 | printf 'SRCPKGDEST=/srcpkgdest\n' 533 | printf 'BUILDDIR=%s\n' "${builddir}" 534 | printf 'PACKAGER=%s\n' "${packager@Q}" 535 | printf 'OPTIONS=(%s)\n' "${options}" 536 | printf 'BUILDENV=(%s)\n' "${buildenv}" 537 | printf 'COMPRESSZST=(zstd -c -T0 --ultra -20 -)\n' 538 | printf 'PKGEXT=".pkg.tar%s"\n' "${pkg##*tar}" 539 | } >> "$build_root_dir/etc/makepkg.conf" 540 | 541 | # Setup environment variables for makepkg 542 | { 543 | printf 'export SOURCE_DATE_EPOCH="%s"\n' "${SOURCE_DATE_EPOCH}" 544 | printf 'export BUILDTOOL="%s"\n' "${BUILDTOOL}" 545 | printf 'export BUILDTOOLVER="%s"\n' "${BUILDTOOLVER}" 546 | } >> "$build_root_dir/env" 547 | 548 | printf '%s.UTF-8 UTF-8\n' en_US de_DE > "$build_root_dir/etc/locale.gen" 549 | printf 'LANG=en_US.UTF-8\n' > "$build_root_dir/etc/locale.conf" 550 | exec_nspawn "$build" locale-gen 551 | 552 | printf 'builduser ALL = NOPASSWD: /usr/bin/pacman\n' > "$build_root_dir/etc/sudoers.d/builduser-pacman" 553 | exec_nspawn "$build" useradd -m -s /bin/bash -d /build builduser 554 | echo "keyserver-options auto-key-retrieve" | install -Dm644 /dev/stdin "$build_root_dir/build/.gnupg/gpg.conf" 555 | exec_nspawn "$build" chown -R builduser /build/.gnupg /startdir 556 | exec_nspawn "$build" chmod 700 /build/.gnupg 557 | 558 | build_package "$build" "$builddir" 559 | remove_snapshot "$build" 560 | chown -R "$src_owner" "${cachedir}" 561 | 562 | msg "Comparing hashes..." 563 | if diff -q -- "$pkg" "$OUTDIR/$(basename "$pkg")" > /dev/null ; then 564 | msg "Package is reproducible!" 565 | exit 0 566 | else 567 | error "Package is not reproducible" 568 | if ((run_diffoscope)); then 569 | PYTHONIOENCODING=utf-8 $DIFFOSCOPE "$pkg" "$OUTDIR/$(basename "$pkg")" || true 570 | fi 571 | exit 1 572 | fi 573 | } 574 | 575 | # Desc: Prints the help section 576 | function print_help() { 577 | cat <<__END__ 578 | Usage: 579 | repro [options] 580 | 581 | General Options: 582 | -h Print this help message 583 | -d Run diffoscope if packages are not reproducible 584 | -f Use the local PKGBUILD for building 585 | -n Run makepkg with --nocheck 586 | -M Set an upper limit for RAM use on the build container (e.g. 12G) 587 | -V Print version information 588 | -o Set the output directory (default: ./build) 589 | __END__ 590 | } 591 | 592 | function print_version() { 593 | echo "repro ${VERSION}" 594 | } 595 | 596 | function parse_args() { 597 | while getopts :hdnfM:Vo: arg; do 598 | case $arg in 599 | h) print_help; exit 0;; 600 | V) print_version; exit 0;; 601 | f) pkgbuild_file=1;; 602 | d) run_diffoscope=1;; 603 | n) NOCHECK=1;; 604 | M) MAX_MEMORY="$OPTARG";; 605 | o) OUTDIR="$OPTARG";; 606 | *) error "unknown argument ${OPTARG}" ; print_help; exit 1;; 607 | esac 608 | done 609 | 610 | if ((NOCHECK)); then 611 | makepkg_args+=(--nocheck) 612 | fi 613 | 614 | # Save command args (such as path to .pkg.tar.xz file) 615 | shift $((OPTIND-1)) 616 | 617 | if [[ $# != 1 ]]; then 618 | error "too many packages provided" 619 | print_help 620 | exit 1 621 | fi 622 | 623 | pkg="$@" 624 | if [[ ! -f "${pkg}" ]]; then 625 | error "argument provided ${pkg} isn't a valid file" 626 | print_help 627 | exit 1 628 | fi 629 | } 630 | 631 | function source_conf() { 632 | local repro_conf 633 | local xdg_repro_dir 634 | 635 | repro_conf=$CONFIGDIR/repro.conf 636 | if [[ -r $repro_conf ]]; then 637 | # shellcheck source=/dev/null 638 | source "$repro_conf" 639 | fi 640 | 641 | xdg_repro_dir="${XDG_CONFIG_HOME:-$HOME/.config}/archlinux-repro" 642 | if [[ -r "$xdg_repro_dir/repro.conf" ]]; then 643 | # shellcheck source=/dev/null 644 | source "$xdg_repro_dir/repro.conf" 645 | elif [[ -r "$HOME/.repro.conf" ]]; then 646 | # shellcheck source=/dev/null 647 | source "$HOME/.repro.conf" 648 | fi 649 | } 650 | 651 | colorize 652 | source_conf 653 | parse_args "$@" 654 | check_root NOCHECK,MAKEFLAGS,DEBUG,CACHEDIR 655 | print_version 656 | init_chroot 657 | cmd_check 658 | --------------------------------------------------------------------------------