From 44e0fae9d1bb03469fe2758b3d387dc54079f182 Mon Sep 17 00:00:00 2001 From: Tim van der Meij Date: Sun, 16 Jun 2024 16:51:56 +0200 Subject: [PATCH] Implement reproducibility for the release builds The release builds are currently not reproducible because ZIP files record the modification date of files generated during the build process, meaning that two builds from identical source code, made at different times, result in different output. This is undesirable because it makes detecting differences in the output harder, for instance recently during the Gulp 5 efforts, because the modification date differences are irrelevant and could obscure actually important differences in the output during e.g. code changes. Moreover, reprodicibility of build artifacts has become increasingly important; please refer to the Reproducible Builds initiative at https://reproducible-builds.org (note the "Why does it matter?" section specifically) and https://reproducible-builds.org/docs/timestamps which further explains the problem of timestamps in build artifacts. This commit fixes the issue by configuring the ZIP file creation to use the (fixed) date of the last Git commit for which the release is being made. With this the build is fully reproducible so that identical source code builds result in bit-by-bit identical output artifacts. To improve readability we convert the compression method to take a parameter object and use template strings where useful. --- gulpfile.mjs | 39 +++++++++++++++++++++++++++++---------- 1 file changed, 29 insertions(+), 10 deletions(-) diff --git a/gulpfile.mjs b/gulpfile.mjs index 7f2030dced6fb..73cbf6a2c0211 100644 --- a/gulpfile.mjs +++ b/gulpfile.mjs @@ -18,7 +18,7 @@ import { babelPluginPDFJSPreprocessor, preprocessPDFJSCode, } from "./external/builder/babel-plugin-pdfjs-preprocessor.mjs"; -import { exec, spawn, spawnSync } from "child_process"; +import { exec, execSync, spawn, spawnSync } from "child_process"; import autoprefixer from "autoprefixer"; import babel from "@babel/core"; import crypto from "crypto"; @@ -1682,13 +1682,13 @@ gulp.task( ) ); -function compressPublish(targetName, dir) { +function compressPublish({ sourceDirectory, targetFile, modifiedTime }) { return gulp - .src(dir + "**", { encoding: false }) - .pipe(zip(targetName)) + .src(`${sourceDirectory}**`, { encoding: false }) + .pipe(zip(targetFile, { modifiedTime })) .pipe(gulp.dest(BUILD_DIR)) .on("end", function () { - console.log("Built distribution file: " + targetName); + console.log(`Built distribution file: ${targetFile}`); }); } @@ -1701,15 +1701,34 @@ gulp.task( config.stableVersion = version; + // ZIP files record the modification date of the source files, so if files + // are generated during the build process the output is not reproducible. + // To avoid this, the modification dates should be replaced with a fixed + // date, in our case the last Git commit date, so that builds from identical + // source code result in bit-by-bit identical output. The `gulp-zip` library + // supports providing a different modification date to enable reproducible + // builds. Note that the Git command below outputs the last Git commit date + // as a Unix timestamp (in seconds since epoch), but the `Date` constructor + // in JavaScript requires millisecond input, so we have to multiply by 1000. + const lastCommitTimestamp = execSync('git log --format="%at" -n 1') + .toString() + .replace("\n", ""); + const lastCommitDate = new Date(parseInt(lastCommitTimestamp, 10) * 1000); + return ordered([ createStringSource(CONFIG_FILE, JSON.stringify(config, null, 2)).pipe( gulp.dest(".") ), - compressPublish("pdfjs-" + version + "-dist.zip", GENERIC_DIR), - compressPublish( - "pdfjs-" + version + "-legacy-dist.zip", - GENERIC_LEGACY_DIR - ), + compressPublish({ + sourceDirectory: GENERIC_DIR, + targetFile: `pdfjs-${version}-dist.zip`, + modifiedTime: lastCommitDate, + }), + compressPublish({ + sourceDirectory: GENERIC_LEGACY_DIR, + targetFile: `pdfjs-${version}-legacy-dist.zip`, + modifiedTime: lastCommitDate, + }), ]); }) );