electron-builder
electron-builder copied to clipboard
Option to Disable Combined NSIS Installer When Building for Multiple Architectures
- Electron-Builder Version: 24.13.3
- Node Version: v18.20.3
- Electron Version: 31.1.0
- Electron Type (current, beta, nightly): current
- Target: nsis installer, arch: ia32, x64 on Windows 11
When building for both 32-bit and 64-bit architectures, Electron Builder creates separate installers for each architecture as well as a combined installer. Is there an option to disable the creation of this combined installer?
Currently, I can't find a way on the doc(https://www.electron.build/configuration/nsis#32-bit-64-bit) to only generate the separate 32-bit and 64-bit installers without also producing the combined one. It would be helpful to have an option like nsis.disableCombinedInstaller: true or nsis.enableCombinedInstaller: false to prevent the creation of the combined installer when it's not needed.
Thank you for your assistance.
It seems this feature doesn't just work on 32-bit(x86/ia32) + 64-bit(amd64), as the doc mentioned, but also on arm64+amd64, arm64+amd64+ia32, and maybe other combinations.
These are the screenshots from using 7-zip to open the $PLUGINSDIR\ folder in the NSIS installers in different combinations:
So I must admit I don't have the original insight as to why the installers are always combined. I had a project in the past where it was required to have two distinct installers, one for each bitness, though and I implemented an electron-builder script using the programmatic API. I think it looked something akin to the code below (just make sure your artifact name uses the {arch} macro so that the second build command doesn't overwrite the artifact of the first one.)
https://www.electron.build/api/programmatic-usage
await builder.build({
targets: platform.createTarget("nsis", 'x64')
config: options
})
await builder.build({
targets: platform.createTarget("nsis", 'ia32')
config: options
})
Sure we can do it by different approach like using command line to build them separately, but a simple option would be also nice 😄
Can you share your electron-builder config?
Try this win config for your electron-builder configuration. I was able to build separate installers for x64 and arm64 in this manner.
win: {
target: [
{
target: 'nsis',
arch: 'x64'
},
{
target: 'nsis',
arch: 'arm64'
}
],
}
+1 for having a config value for this.
I'm building better-sqlite3 and it needs to be built once per architecture. The combined installer would take the last built better-sqlite3 which would fail for the other archs.
This issue is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 30 days.
Revisiting this request. I'll see what I can do but the logic in NsisTarget is extremely complex 😅
Update: I've added a configuration property to allow disabling universal builds, but it's only available to nsis. nsis-web requires universal in order for it to be able to determine what build to install. The difficulty in implementation is that nsis-web target extends NsisTarget, so the logic needed to be conditional and overridden for nsis-web target.
The PR introduces a config property buildUniversalInstaller (default true for backward compatibility)
In order to build separate exe's, I needed to update the installer name template to include an arch. Unless a defaultArch is supplied in the electron-builder config, x64 will not have a suffix appended to the file name (backward compatibility) and the other arch builds will have suffix -${arch}.${ext}
target: [ { target: 'nsis', arch: 'x64' }, { target: 'nsis', arch: 'arm64' } ],
That electron-builder.json config for me still builds the universal win32 installer. How do I disable that?
• electron-builder version=24.13.3 os=23.6.0
• loaded configuration file=./desktop-wakatime/electron-builder.json
• writing effective config file=release/builder-effective-config.yaml
• packaging platform=win32 arch=x64 electron=32.0.0 appOutDir=release/win-unpacked
• packaging platform=win32 arch=arm64 electron=32.0.0 appOutDir=release/win-arm64-unpacked
• building target=nsis file=release/wakatime-win32.exe archs=x64, arm64 oneClick=false perMachine=false
• building block map blockMapFile=release/wakatime-win32.exe.blockmap
• building target=nsis file=release/wakatime-win32-x64.exe archs=x64 oneClick=false perMachine=false
• building block map blockMapFile=release/wakatime-win32-x64.exe.blockmap
• building target=nsis file=release/wakatime-win32-arm64.exe archs=arm64 oneClick=false perMachine=false
• building block map blockMapFile=release/wakatime-win32-arm64.exe.blockmap
Would anyone be willing to test out this patch-package?
I'm unable to test for arch combinations including ia32, so I need a person to test with "arm64", "x64", "ia32", as ia32 isn't package-able on my mac (arm64 limitation IIRC)
Would also appreciate anyone trying to test out the updater logic, I'm still verifying that myself as well.
app-builder-lib+26.0.0-alpha.3.patch
diff --git a/node_modules/app-builder-lib/out/codeSign/signManager.js b/node_modules/app-builder-lib/out/codeSign/signManager.js
new file mode 100644
index 0000000..09883b3
--- /dev/null
+++ b/node_modules/app-builder-lib/out/codeSign/signManager.js
@@ -0,0 +1,10 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.SignManager = void 0;
+class SignManager {
+ constructor(packager) {
+ this.packager = packager;
+ }
+}
+exports.SignManager = SignManager;
+//# sourceMappingURL=signManager.js.map
\ No newline at end of file
diff --git a/node_modules/app-builder-lib/out/targets/nsis/NsisTarget.js b/node_modules/app-builder-lib/out/targets/nsis/NsisTarget.js
index e79c555..31aee98 100644
--- a/node_modules/app-builder-lib/out/targets/nsis/NsisTarget.js
+++ b/node_modules/app-builder-lib/out/targets/nsis/NsisTarget.js
@@ -56,8 +56,15 @@ class NsisTarget extends core_1.Target {
}
nsisUtil_1.NsisTargetOptions.resolve(this.options);
}
+ buildUniversalInstaller() {
+ const buildSeparateInstallers = this.options.buildUniversalInstaller === false;
+ return !buildSeparateInstallers;
+ }
build(appOutDir, arch) {
this.archs.set(arch, appOutDir);
+ if (!this.buildUniversalInstaller()) {
+ return this.buildInstaller(new Map().set(arch, appOutDir));
+ }
return Promise.resolve();
}
get isBuildDifferentialAware() {
@@ -94,7 +101,10 @@ class NsisTarget extends core_1.Target {
return await createPackageFileInfo(archiveFile);
}
}
- get installerFilenamePattern() {
+ installerFilenamePattern(primaryArch, defaultArch) {
+ if (!this.buildUniversalInstaller()) {
+ return "${productName} " + (this.isPortable ? "" : "Setup ") + "${version}" + (primaryArch != null ? (0, builder_util_1.getArchSuffix)(primaryArch, defaultArch) : "") + ".${ext}";
+ }
// tslint:disable:no-invalid-template-strings
return "${productName} " + (this.isPortable ? "" : "Setup ") + "${version}.${ext}";
}
@@ -102,8 +112,11 @@ class NsisTarget extends core_1.Target {
return this.name === "portable";
}
async finishBuild() {
+ if (!this.buildUniversalInstaller()) {
+ return this.packageHelper.finishBuild();
+ }
try {
- const { pattern } = this.packager.artifactPatternConfig(this.options, this.installerFilenamePattern);
+ const { pattern } = this.packager.artifactPatternConfig(this.options, this.installerFilenamePattern());
const builds = new Set([this.archs]);
if (pattern.includes("${arch}") && this.archs.size > 1) {
;
@@ -119,12 +132,13 @@ class NsisTarget extends core_1.Target {
}
}
async buildInstaller(archs) {
- var _a, _b;
+ var _a, _b, _c;
const primaryArch = archs.size === 1 ? ((_a = archs.keys().next().value) !== null && _a !== void 0 ? _a : null) : null;
const packager = this.packager;
const appInfo = packager.appInfo;
const options = this.options;
- const installerFilename = packager.expandArtifactNamePattern(options, "exe", primaryArch, this.installerFilenamePattern, false, this.packager.platformSpecificBuildOptions.defaultArch);
+ const defaultArch = (_b = (0, platformPackager_1.chooseNotNull)(this.packager.platformSpecificBuildOptions.defaultArch, this.packager.config.defaultArch)) !== null && _b !== void 0 ? _b : undefined;
+ const installerFilename = packager.expandArtifactNamePattern(options, "exe", primaryArch, this.installerFilenamePattern(primaryArch, defaultArch), false, defaultArch);
const oneClick = options.oneClick !== false;
const installerPath = path.join(this.outDir, installerFilename);
const logFields = {
@@ -160,7 +174,7 @@ class NsisTarget extends core_1.Target {
BUILD_RESOURCES_DIR: packager.info.buildResourcesDir,
APP_PACKAGE_NAME: (0, targetUtil_1.getWindowsInstallationAppPackageName)(appInfo.name),
};
- if ((_b = options.customNsisBinary) === null || _b === void 0 ? void 0 : _b.debugLogging) {
+ if ((_c = options.customNsisBinary) === null || _c === void 0 ? void 0 : _c.debugLogging) {
defines.ENABLE_LOGGING_ELECTRON_BUILDER = null;
}
if (uninstallAppKey !== guid) {
@@ -279,7 +293,7 @@ class NsisTarget extends core_1.Target {
defines.UNINSTALLER_OUT_FILE = definesUninstaller.UNINSTALLER_OUT_FILE;
await this.executeMakensis(defines, commands, sharedHeader + (await this.computeFinalScript(script, true, archs)));
await Promise.all([packager.sign(installerPath), defines.UNINSTALLER_OUT_FILE == null ? Promise.resolve() : (0, fs_extra_1.unlink)(defines.UNINSTALLER_OUT_FILE)]);
- const safeArtifactName = (0, platformPackager_1.computeSafeArtifactNameIfNeeded)(installerFilename, () => this.generateGitHubInstallerName());
+ const safeArtifactName = (0, platformPackager_1.computeSafeArtifactNameIfNeeded)(installerFilename, () => this.generateGitHubInstallerName(primaryArch, defaultArch));
let updateInfo;
if (this.isWebInstaller) {
updateInfo = (0, differentialUpdateInfoBuilder_1.createNsisWebDifferentialUpdateInfo)(installerPath, packageFiles);
@@ -300,10 +314,11 @@ class NsisTarget extends core_1.Target {
isWriteUpdateInfo: !this.isPortable,
});
}
- generateGitHubInstallerName() {
+ generateGitHubInstallerName(primaryArch, defaultArch) {
const appInfo = this.packager.appInfo;
const classifier = appInfo.name.toLowerCase() === appInfo.name ? "setup-" : "Setup-";
- return `${appInfo.name}-${this.isPortable ? "" : classifier}${appInfo.version}.exe`;
+ const archSuffix = !this.buildUniversalInstaller() && primaryArch != null ? (0, builder_util_1.getArchSuffix)(primaryArch, defaultArch) : "";
+ return `${appInfo.name}-${this.isPortable ? "" : classifier}${appInfo.version}${archSuffix}.exe`;
}
get isUnicodeEnabled() {
return this.options.unicode !== false;
diff --git a/node_modules/app-builder-lib/out/targets/nsis/WebInstallerTarget.js b/node_modules/app-builder-lib/out/targets/nsis/WebInstallerTarget.js
index 9b75baa..b7a707d 100644
--- a/node_modules/app-builder-lib/out/targets/nsis/WebInstallerTarget.js
+++ b/node_modules/app-builder-lib/out/targets/nsis/WebInstallerTarget.js
@@ -1,6 +1,7 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.WebInstallerTarget = void 0;
+const builder_util_1 = require("builder-util");
const PublishManager_1 = require("../../publish/PublishManager");
const NsisTarget_1 = require("./NsisTarget");
/** @private */
@@ -27,8 +28,13 @@ class WebInstallerTarget extends NsisTarget_1.NsisTarget {
defines.APP_PACKAGE_URL_IS_INCOMPLETE = null;
defines.APP_PACKAGE_URL = appPackageUrl;
}
- get installerFilenamePattern() {
- // tslint:disable:no-invalid-template-strings
+ buildUniversalInstaller() {
+ if (this.options.buildUniversalInstaller === false) {
+ builder_util_1.log.warn({ buildUniversalInstaller: true }, "only universal builds are supported for nsis-web installers, overriding setting");
+ }
+ return true;
+ }
+ installerFilenamePattern(_primaryArch, _defaultArch) {
return "${productName} Web Setup ${version}.${ext}";
}
generateGitHubInstallerName() {
This issue is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 30 days.
Is there any progress?
would it be possible to do this with the MSI wrapper builder?
I'm trying to create individuals builds for each arch type as well as per machine and per user installers for both msi and exe (nsi) is this something possible?