diff --git a/build-in-container-inner.sh b/build-in-container-inner.sh index 3a6b39054..16f708477 100755 --- a/build-in-container-inner.sh +++ b/build-in-container-inner.sh @@ -40,6 +40,15 @@ for repo in $repos; do fi done +# Pin embedded build timestamps so two builds of the same source produce +# identical binaries. Honored by OpenSSL, Apache httpd, Postgres, Python +# (.pyc mtimes), dpkg-buildpackage, and rpmbuild. +if [ -z "$SOURCE_DATE_EPOCH" ]; then + SOURCE_DATE_EPOCH=$(git -C "$BASEDIR/core" log -1 --format=%ct) +fi +export SOURCE_DATE_EPOCH +echo "SOURCE_DATE_EPOCH=$SOURCE_DATE_EPOCH" + install_mission_portal_deps() ( set -e @@ -53,12 +62,12 @@ install_mission_portal_deps() ( if [ -f "$BASEDIR/mission-portal/composer.json" ]; then echo "Installing Mission Portal PHP dependencies..." - (cd "$BASEDIR/mission-portal" && php /usr/bin/composer.phar install --no-dev --ignore-platform-reqs) + (cd "$BASEDIR/mission-portal" && php /usr/bin/composer.phar install --no-dev --ignore-platform-reqs --prefer-dist) fi if [ -f "$BASEDIR/nova/api/http/composer.json" ]; then echo "Installing Nova API PHP dependencies..." - (cd "$BASEDIR/nova/api/http" && php /usr/bin/composer.phar install --no-dev --ignore-platform-reqs) + (cd "$BASEDIR/nova/api/http" && php /usr/bin/composer.phar install --no-dev --ignore-platform-reqs --prefer-dist) fi if [ -f "$BASEDIR/mission-portal/public/themes/default/bootstrap/cfengine_theme.less" ]; then @@ -70,8 +79,13 @@ install_mission_portal_deps() ( if [ -f "$BASEDIR/mission-portal/ldap/composer.json" ]; then echo "Installing LDAP API PHP dependencies..." - (cd "$BASEDIR/mission-portal/ldap" && php /usr/bin/composer.phar install --no-dev --ignore-platform-reqs) + (cd "$BASEDIR/mission-portal/ldap" && php /usr/bin/composer.phar install --no-dev --ignore-platform-reqs --prefer-dist) fi + + # Composer falls back to git clone when GitHub's anonymous zipball + # rate limit is hit, leaving non-reproducible .git directories in the + # vendor tree. Strip them. + find "$BASEDIR/mission-portal" "$BASEDIR/nova/api/http" -type d -name .git -path '*/vendor/*' -exec rm -rf {} + ) # === Step runner with failure reporting === diff --git a/deps-packaging/php/0002-Honor-SOURCE_DATE_EPOCH-in-phar.patch b/deps-packaging/php/0002-Honor-SOURCE_DATE_EPOCH-in-phar.patch new file mode 100644 index 000000000..3a84695a9 --- /dev/null +++ b/deps-packaging/php/0002-Honor-SOURCE_DATE_EPOCH-in-phar.patch @@ -0,0 +1,94 @@ +From: Lars Erik Wik +Subject: [PATCH] Honor SOURCE_DATE_EPOCH in phar timestamps + +Phar embeds wall-clock time(NULL) into every manifest entry, making +phar.phar non-reproducible. Honor SOURCE_DATE_EPOCH (the standard +reproducible-builds mechanism) when set; fall back to time(NULL). + +Signed-off-by: Lars Erik Wik +--- + ext/phar/phar.c | 2 +- + ext/phar/phar_internal.h | 14 ++++++++++++++ + ext/phar/tar.c | 2 +- + ext/phar/util.c | 2 +- + ext/phar/zip.c | 2 +- + 5 files changed, 19 insertions(+), 3 deletions(-) + +diff --git a/ext/phar/phar.c b/ext/phar/phar.c +--- a/ext/phar/phar.c ++++ b/ext/phar/phar.c +@@ -2950,7 +2950,7 @@ + 4: metadata-len + +: metadata + */ +- mytime = time(NULL); ++ mytime = phar_source_date_epoch(); + phar_set_32(entry_buffer, entry->uncompressed_filesize); + phar_set_32(entry_buffer+4, mytime); + phar_set_32(entry_buffer+8, entry->compressed_filesize); +diff --git a/ext/phar/phar_internal.h b/ext/phar/phar_internal.h +--- a/ext/phar/phar_internal.h ++++ b/ext/phar/phar_internal.h +@@ -22,12 +22,26 @@ + #endif + + #include ++#include + #include "php.h" + #include "tar.h" + #include "pharzip.h" + #include "zend_hash.h" + #include "ext/spl/spl_directory.h" + ++/* Reproducible-builds: honor SOURCE_DATE_EPOCH when set, else wall-clock time. */ ++static inline time_t phar_source_date_epoch(void) { ++ const char *epoch = getenv("SOURCE_DATE_EPOCH"); ++ if (epoch && *epoch) { ++ char *end; ++ long long t = strtoll(epoch, &end, 10); ++ if (*end == '\0' && t >= 0) { ++ return (time_t)t; ++ } ++ } ++ return time(NULL); ++} ++ + /* PHP_ because this is public information via MINFO */ + #define PHP_PHAR_API_VERSION "1.1.1" + /* x.y.z maps to 0xyz0 */ +diff --git a/ext/phar/tar.c b/ext/phar/tar.c +--- a/ext/phar/tar.c ++++ b/ext/phar/tar.c +@@ -977,7 +977,7 @@ + char *buf, *signature, sigbuf[8]; + + entry.flags = PHAR_ENT_PERM_DEF_FILE; +- entry.timestamp = time(NULL); ++ entry.timestamp = phar_source_date_epoch(); + entry.is_modified = 1; + entry.is_crc_checked = 1; + entry.is_tar = 1; +diff --git a/ext/phar/util.c b/ext/phar/util.c +--- a/ext/phar/util.c ++++ b/ext/phar/util.c +@@ -700,7 +700,7 @@ + + phar_add_virtual_dirs(phar, path, path_len); + etemp.is_modified = 1; +- etemp.timestamp = time(0); ++ etemp.timestamp = phar_source_date_epoch(); + etemp.is_crc_checked = 1; + etemp.phar = phar; + etemp.filename = zend_string_init(path, path_len, false); +diff --git a/ext/phar/zip.c b/ext/phar/zip.c +--- a/ext/phar/zip.c ++++ b/ext/phar/zip.c +@@ -1253,7 +1253,7 @@ + + pass.error = &temperr; + entry.flags = PHAR_ENT_PERM_DEF_FILE; +- entry.timestamp = time(NULL); ++ entry.timestamp = phar_source_date_epoch(); + entry.is_modified = 1; + entry.is_zip = true; + entry.phar = phar; diff --git a/deps-packaging/php/cfbuild-php.spec b/deps-packaging/php/cfbuild-php.spec index d75aa112f..f9e4806ad 100644 --- a/deps-packaging/php/cfbuild-php.spec +++ b/deps-packaging/php/cfbuild-php.spec @@ -25,6 +25,8 @@ then patch -p1 < %{_topdir}/SOURCES/0001-Disable-fancy-intrinsics-stuff.patch fi +patch -p1 < %{_topdir}/SOURCES/0002-Honor-SOURCE_DATE_EPOCH-in-phar.patch + %if %{?rhel}%{!?rhel:0} == 8 CFLAGS="-fPIE" LDFLAGS="-pie" diff --git a/deps-packaging/php/debian/rules b/deps-packaging/php/debian/rules index 759498d0e..d35d1b49e 100755 --- a/deps-packaging/php/debian/rules +++ b/deps-packaging/php/debian/rules @@ -12,6 +12,8 @@ build: build-stamp build-stamp: dh_testdir + patch -p1 < $(CURDIR)/0002-Honor-SOURCE_DATE_EPOCH-in-phar.patch + ./configure --prefix=$(PREFIX)/httpd/php \ --with-config-file-scan-dir=$(PREFIX)/httpd/php/lib \ --without-apxs2 \