Skip to content

Commit f268398

Browse files
committed
Fix Debian package reproducibility
1 parent 675a5bc commit f268398

File tree

1 file changed

+68
-1
lines changed

1 file changed

+68
-1
lines changed

release-automation/build.js

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ require('./patch-electron-builder');
22

33
const fs = require('fs');
44
const pathUtil = require('path');
5+
const childProcess = require('child_process');
56
const builder = require('electron-builder');
67
const electronFuses = require('@electron/fuses');
78

@@ -18,6 +19,51 @@ const ELECTRON_26_FINAL = '26.6.10';
1819
// Electron 32 is the last version to support macOS 10.15
1920
const ELECTRON_32_FINAL = '32.3.3';
2021

22+
/**
23+
* @returns {Date}
24+
*/
25+
const getSourceDateEpoch = () => {
26+
// Used if a better date cannot be obtained for any reason.
27+
// This is from commit 35045e7c0fa4e4e14b2747e967adb4029cedb945.
28+
const ARBITRARY_FALLBACK = 1609809111000;
29+
30+
// If SOURCE_DATE_EPOCH is set externally, use it.
31+
if (process.env.SOURCE_DATE_EPOCH) {
32+
return new Date((+process.env.SOURCE_DATE_EPOCH) * 1000);
33+
}
34+
35+
// Otherwise, try to get the time of the most recent commit.
36+
const gitProcess = childProcess.spawnSync('git', ['log', '-1', '--pretty=%ct']);
37+
38+
if (gitProcess.error) {
39+
if (gitProcess.error === 'ENOENT') {
40+
console.warn('Could not get source date epoch: git is not installed');
41+
return new Date(ARBITRARY_FALLBACK);
42+
}
43+
throw gitProcess.error;
44+
}
45+
46+
if (gitProcess.status !== 0) {
47+
console.warn(`Could not get source date epoch: git returned status ${gitProcess.status}`);
48+
return new Date(ARBITRARY_FALLBACK);
49+
}
50+
51+
const gitStdout = gitProcess.stdout.toString().trim();
52+
if (/^\d+$/.test(gitStdout)) {
53+
return new Date((+gitStdout) * 1000);
54+
}
55+
56+
console.warn(`Could not get source date epoch: git did not return a date`);
57+
return new Date(ARBITRARY_FALLBACK);
58+
};
59+
60+
const sourceDateEpoch = getSourceDateEpoch();
61+
// Ensure that we have a SOURCE_DATE_EPOCH environment variable so that it is available
62+
// to child processes of electron-builder. This is necessary for making the Debian
63+
// packages producibile.
64+
process.env.SOURCE_DATE_EPOCH = Math.round(sourceDateEpoch.getTime() / 1000).toString();
65+
console.log(`Source date epoch: ${sourceDateEpoch.toISOString()} (${process.env.SOURCE_DATE_EPOCH})`);
66+
2167
/**
2268
* @param {string} platformName
2369
* @returns {string} a string that indexes into Arch[...]
@@ -83,8 +129,30 @@ const flipFuses = async (context) => {
83129
await context.packager.addElectronFuses(context, newFuses);
84130
};
85131

132+
/**
133+
* @param {string} directory
134+
* @param {Date} date
135+
*/
136+
const recursivelySetFileTimes = (directory, date) => {
137+
const files = fs.readdirSync(directory);
138+
for (const file of files) {
139+
const filePath = pathUtil.join(directory, file);
140+
const stat = fs.statSync(filePath);
141+
if (stat.isDirectory()) {
142+
recursivelySetFileTimes(filePath, date);
143+
} else {
144+
fs.utimesSync(filePath, date, date);
145+
}
146+
}
147+
fs.utimesSync(directory, date, date);
148+
};
149+
86150
const afterPack = async (context) => {
87151
await flipFuses(context);
152+
153+
// When electron-builder packs the folder, modification times of the files are
154+
// preserved for some formats, so ensure that modification times are reproducible.
155+
recursivelySetFileTimes(context.appOutDir, sourceDateEpoch);
88156
};
89157

90158
const build = async ({
@@ -123,7 +191,6 @@ const build = async ({
123191
tw_warn_legacy: isProduction,
124192
tw_update: isProduction && manageUpdates
125193
},
126-
afterAllArtifactBuild,
127194
afterPack,
128195
...extraConfig,
129196
...await prepare(archName)

0 commit comments

Comments
 (0)