Skip to content

Improve reproducibility #1205

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 16 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ jobs:
with:
submodules: recursive
persist-credentials: false
- name: Set SOURCE_DATE_EPOCH
run: echo "SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct)" >> $GITHUB_ENV
- name: Install Node.js
uses: actions/setup-node@v4
with:
Expand All @@ -25,6 +27,14 @@ jobs:
- name: Package
run: |
node release-automation/build.js --debian --tarball --appimage --x64 --armv7l --arm64
env:
DEBUG: electron-builder
- name: Normalize tarballs for reproducibility
run: |
sudo apt-get install -y strip-nondeterminism
strip-nondeterminism dist/*.tar.gz
- name: Debug info
run: node scripts/print-file-tree.js dist
- name: Upload artifacts to GitHub Actions
uses: actions/upload-artifact@v4
with:
Expand All @@ -42,6 +52,8 @@ jobs:
with:
submodules: recursive
persist-credentials: false
- name: Set SOURCE_DATE_EPOCH
run: echo "SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct)" >> $GITHUB_ENV
- name: Install Node.js
uses: actions/setup-node@v4
with:
Expand All @@ -56,6 +68,10 @@ jobs:
run: |
node release-automation/build.js --mac --universal
node release-automation/build.js --mac-legacy-10.13-10.14 --mac-legacy-10.15 --x64
env:
DEBUG: electron-builder
- name: Debug info
run: node scripts/print-file-tree.js dist
- name: Upload artifacts to GitHub Actions
uses: actions/upload-artifact@v4
with:
Expand Down Expand Up @@ -83,6 +99,9 @@ jobs:
run: |
Copy-Item -Path repo -Destination D:\repo -Recurse
Remove-Item -Path repo -Recurse -Force
- name: Set SOURCE_DATE_EPOCH
working-directory: D:\repo
run: echo "SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct)" >> $env:GITHUB_ENV
- name: Install Node.js
uses: actions/setup-node@v4
with:
Expand All @@ -109,6 +128,11 @@ jobs:
node release-automation/build.js --windows --microsoft-store --x64 --ia32 --arm64
node release-automation/build.js --windows-portable --x64
node release-automation/build.js --windows-legacy --ia32 --x64
env:
DEBUG: electron-builder
- name: Debug info
working-directory: D:\repo
run: node scripts/print-file-tree.js dist
- name: Upload artifacts to GitHub Actions
uses: actions/upload-artifact@v4
with:
Expand Down
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,9 @@
"deleteAppDataOnUninstall": false,
"allowToChangeInstallationDirectory": true
},
"msi": {
"artifactName": "${productName}-Setup-${version}-${arch}.${ext}"
},
"mac": {
"artifactName": "${productName}-Setup-${version}.${ext}",
"icon": "build/icon.icns",
Expand Down
9 changes: 6 additions & 3 deletions release-automation/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,16 @@ You must specify which platform to build for:

```
--windows
Create Windows installers for Windows 10 or later.
Create Windows installers for Windows 10 or later using NSIS.

--windows-legacy
Create Windows installers for Windows 7 or later (less secure than --windows).
Create Windows installers for Windows 7 or later using NSIS (less secure than --windows).

--windows-portable
Create portable Windows executables for Windows 10 or later.
Create portable Windows executables for Windows 10 or later using NSIS.

--windows-msi
Create Windows installers for Windows 10 or later using the Windows Installer format.

--windows-dir
Generate executables for Windows but don't package into a single executable file
Expand Down
60 changes: 45 additions & 15 deletions release-automation/build.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ require('./patch-electron-builder');

const fs = require('fs');
const pathUtil = require('path');
const nodeCrypto = require('crypto');
const builder = require('electron-builder');
const electronFuses = require('@electron/fuses');

Expand Down Expand Up @@ -49,18 +48,7 @@ const getArchesToBuild = (platformName) => {
return arches;
};

const afterAllArtifactBuild = (buildResult) => {
for (const artifactPath of buildResult.artifactPaths) {
const data = fs.readFileSync(artifactPath);
const hash = nodeCrypto
.createHash('sha-256')
.update(data)
.digest('hex');
console.log(`${hash} ${artifactPath}`);
}
};

const addElectronFuses = async (context) => {
const flipFuses = async (context) => {
const electronMajorVersion = +context.packager.info.framework.version.split('.')[0];

/** @type {import('@electron/fuses').FuseV1Config} */
Expand Down Expand Up @@ -95,8 +83,44 @@ const addElectronFuses = async (context) => {
await context.packager.addElectronFuses(context, newFuses);
};

/**
* @param {string} directory
* @param {Date} date
*/
const recursivelySetFileTimes = (directory, date) => {
const files = fs.readdirSync(directory);
for (const file of files) {
const filePath = pathUtil.join(directory, file);
const stat = fs.statSync(filePath);
if (stat.isDirectory()) {
recursivelySetFileTimes(filePath, date);
} else {
fs.utimesSync(filePath, date, date);
}
}
fs.utimesSync(directory, date, date);
};

/**
* @returns {Date}
*/
const getSourceDateEpoch = () => {
// Standard variable for defining the time for a build
// https://reproducible-builds.org/docs/source-date-epoch/
if (process.env.SOURCE_DATE_EPOCH) {
return new Date((+process.env.SOURCE_DATE_EPOCH) * 1000);
}

// Otherwise, use an arbitrary constant date to ensure reproducibility.
// This constant is from commit 35045e7c0fa4e4e14b2747e967adb4029cedb945.
// We could instead use the timestamp from last git commit, but that would break
// source builds without git history, and it doesn't seem necessary anyways.
return new Date(1609809111000);
};

const afterPack = async (context) => {
await addElectronFuses(context)
await flipFuses(context);
recursivelySetFileTimes(context.appOutDir, getSourceDateEpoch());
};

const build = async ({
Expand Down Expand Up @@ -135,7 +159,6 @@ const build = async ({
tw_warn_legacy: isProduction,
tw_update: isProduction && manageUpdates
},
afterAllArtifactBuild,
afterPack,
...extraConfig,
...await prepare(archName)
Expand Down Expand Up @@ -180,6 +203,12 @@ const buildWindowsPortable = () => build({
manageUpdates: true
});

const buildWindowsMSI = () => build({
platformName: 'WINDOWS',
platformType: 'msi',
manageUpdates: true
});

const buildWindowsDir = () => build({
platformName: 'WINDOWS',
platformType: 'dir',
Expand Down Expand Up @@ -268,6 +297,7 @@ const run = async () => {
'--windows': buildWindows,
'--windows-legacy': buildWindowsLegacy,
'--windows-portable': buildWindowsPortable,
'--windows-msi': buildWindowsMSI,
'--windows-dir': buildWindowsDir,
'--microsoft-store': buildMicrosoftStore,
'--mac': buildMac,
Expand Down
31 changes: 31 additions & 0 deletions scripts/print-file-tree.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
const pathUtil = require('path');
const fs = require('fs');
const nodeCrypto = require('crypto');

const recursivelyPrint = (directory) => {
const dirStat = fs.statSync(directory);
console.log(pathUtil.join(directory, '/'));
console.log(`\tModified: ${dirStat.mtime.toUTCString()}`);

const children = fs.readdirSync(directory);
for (const child of children) {
const path = pathUtil.join(directory, child);
const childStat = fs.statSync(path);
if (childStat.isFile()) {
console.log(path);
const data = fs.readFileSync(path);
const sha256 = nodeCrypto
.createHash('sha256')
.update(data)
.digest('hex');
console.log(`\tSHA-256: ${sha256}`);
console.log(`\tModified: ${childStat.mtime.toUTCString()}`);
} else {
recursivelyPrint(path);
}
}
};

for (let i = 2; i < process.argv.length; i++) {
recursivelyPrint(process.argv[i]);
}