diff options
Diffstat (limited to '')
-rwxr-xr-x | scripts/debrepro.sh | 255 |
1 files changed, 255 insertions, 0 deletions
diff --git a/scripts/debrepro.sh b/scripts/debrepro.sh new file mode 100755 index 0000000..fd044d3 --- /dev/null +++ b/scripts/debrepro.sh @@ -0,0 +1,255 @@ +#!/bin/sh + +# debrepro: a reproducibility tester for Debian packages +# +# © 2016 Antonio Terceiro <terceiro@debian.org> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +set -eu + +check_dependencies() { + for optional in disorderfs diffoscope; do + if ! which "$optional" > /dev/null; then + echo "W: $optional not installed, there will be missing functionality" >&2 + fi + done + + local failed='' + for mandatory in faketime; do + if ! which "$mandatory" > /dev/null; then + echo "E: $mandatory not installed, cannot proceed." >&2 + failed=yes + fi + done + if [ -n "$failed" ]; then + exit 3 + fi +} + +usage() { + echo "usage: $0 [OPTIONS] [SOURCEDIR]" + echo "" + echo "Options:" + echo "" + echo " -b,--before-second-build COMMAND Run COMMAND before second build" + echo " (e.g. apply a patch)" + echo " -s,--skip VARIATION Don't perform the named variation" + echo " -h,--help Display this help message and exit" +} + +first_banner=y +banner() { + if [ "$first_banner" = n ]; then + echo + fi + echo "$@" | sed -e 's/./=/g' + echo "$@" + echo "$@" | sed -e 's/./=/g' + echo + first_banner=n +} + +variation() { + echo + echo "# Variation:" "$@" +} + +vary() { + local var="$1" + + for skipped in $skip_variations; do + if [ "$skipped" = "$var" ]; then + return + fi + done + + variation "$var" + local first="$2" + local second="$3" + if [ "$which_build" = 'first' ]; then + if [ -n "$first" ]; then + echo "$first" + fi + else + echo "$second" + fi +} + +create_build_script() { + echo 'set -eu' + + echo + echo "# this script must be run from inside an unpacked Debian source" + echo "# package" + echo + + vary path \ + '' \ + 'export PATH="$PATH":/i/capture/the/path' + + vary user \ + 'export USER=user1' \ + 'export USER=user2' + + vary umask \ + 'umask 0022' \ + 'umask 0002' + + vary locale \ + 'export LC_ALL=C.UTF-8 LANG=C.UTF-8' \ + 'export LC_ALL=pt_BR.UTF-8 LANG=pt_BR.UTF-8' + + vary timezone \ + 'export TZ=GMT+12' \ + 'export TZ=GMT-14' + + if which disorderfs >/dev/null; then + disorderfs_commands='cd .. && +mv source orig && +mkdir source && +disorderfs --shuffle-dirents=yes orig source && +trap "cd .. && fusermount -u source && rmdir source && mv orig source" INT TERM EXIT && +cd source' + vary filesystem-ordering \ + '' \ + "$disorderfs_commands" + fi + + echo 'build_prefix=""' + + vary time \ + '' \ + 'build_prefix="faketime +213days+7hours+13minutes"; export NO_FAKE_STAT=1' + + if [ -n "$timeout" ]; then + echo "build_prefix=\"timeout $timeout \$build_prefix\"" + fi + + echo '${build_prefix:-} dpkg-buildpackage -b -us -uc' +} + + +build() { + export which_build="$1" + mkdir "$tmpdir/build" + + cp -r "$SOURCE" "$tmpdir/build/source" + cd "$tmpdir/build/source" + + if [ "$which_build" = second ] && [ -n "$before_second_build_command" ]; then + banner "I: running before second build: $before_second_build_command" + sh -c "$before_second_build_command" + fi + + create_build_script > ../build.sh + sh ../build.sh + cd - + + mv "$tmpdir/build" "$tmpdir/$which_build" +} + +binmatch() { + cmp --silent "$1" "$2" +} + +compare() { + rc=0 + for first_deb in "$tmpdir"/first/*.deb; do + deb="$(basename "$first_deb")" + second_deb="$tmpdir"/second/"$deb" + if binmatch "$first_deb" "$second_deb"; then + echo "✓ $deb: binaries match" + else + echo "✗ $deb: binaries don't match" + rc=1 + fi + done + if [ "$rc" -ne 0 ]; then + if which diffoscope >/dev/null; then + diffoscope "$tmpdir"/first/*.changes "$tmpdir"/second/*.changes || true + else + echo "I: install diffoscope for a deep comparison between artifacts" + fi + echo "E: package is not reproducible." + fi + return "$rc" +} + +TEMP=$(getopt -n "debrepro" -o 'hs:b:t:' \ + -l 'help,skip:,before-second-build:,timeout:' \ + -- "$@") || (rc=$?; usage >&2; exit $rc) +eval set -- "$TEMP" + +skip_variations="" +before_second_build_command='' +timeout='' +while true; do + case "$1" in + -s|--skip) + case "$2" in + user|path|umask|locale|timezone|filesystem-ordering|time) + skip_variations="$skip_variations $2" + ;; + *) + echo "E: invalid variation name $2" + exit 1 + ;; + esac + shift + ;; + -b|--before-second-build) + before_second_build_command="$2" + shift + ;; + -t|--timeout) + timeout="$2" + shift + ;; + -h|--help) + usage + exit + ;; + --) + shift + break + ;; + esac + shift +done + +SOURCE="${1:-}" +if [ -z "$SOURCE" ]; then + SOURCE="$(pwd)" +fi +if [ ! -f "$SOURCE/debian/changelog" ]; then + echo "E: $SOURCE does not look like a Debian source package" + exit 2 +fi + +tmpdir=$(mktemp --directory --tmpdir debrepro.XXXXXXXXXX) +trap "if [ \$? -eq 0 ]; then rm -rf $tmpdir; else echo; echo 'I: artifacts left in $tmpdir'; fi" INT TERM EXIT + +check_dependencies + +banner "First build" +build first + +banner "Second build" +build second + +banner "Comparing binaries" +compare first second + +# vim:ts=4 sw=4 et |