Skip to content
New issue

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

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

Already on GitHub? # to your account

Option to Disable Combined NSIS Installer When Building for Multiple Architectures #8298

Closed
PeterDaveHello opened this issue Jul 7, 2024 · 12 comments · Fixed by #8607
Closed

Comments

@PeterDaveHello
Copy link
Contributor

PeterDaveHello commented Jul 7, 2024

  • 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.

@PeterDaveHello
Copy link
Contributor Author

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:

image

image

@mmaietta
Copy link
Collaborator

mmaietta commented Jul 8, 2024

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
})

@PeterDaveHello
Copy link
Contributor Author

Sure we can do it by different approach like using command line to build them separately, but a simple option would be also nice 😄

@mmaietta
Copy link
Collaborator

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'
            }
        ],  
}
Screenshot 2024-07-11 at 11 35 11 AM

@MikesGlitch
Copy link

MikesGlitch commented Aug 15, 2024

+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.

Copy link
Contributor

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.

@mmaietta
Copy link
Collaborator

Revisiting this request. I'll see what I can do but the logic in NsisTarget is extremely complex 😅

@mmaietta
Copy link
Collaborator

mmaietta commented Oct 17, 2024

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}

@alanhamlett
Copy link

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

@mmaietta
Copy link
Collaborator

mmaietta commented Oct 23, 2024

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() {

Copy link
Contributor

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.

@github-actions github-actions bot added the Stale label Dec 23, 2024
@m1rn
Copy link

m1rn commented Dec 24, 2024

Is there any progress?

# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants