diff --git a/docs/NSIS.md b/docs/NSIS.md index 6a64b419b69..5fd6ec63c81 100644 --- a/docs/NSIS.md +++ b/docs/NSIS.md @@ -1,3 +1,7 @@ +# 32 bit + 64 bit + +If you build both ia32 and xha arch, you in any case get one installer. Appropriate arch will be installed automatically. + # GUID vs Application Name Windows requires to use registry keys (e.g. INSTALL/UNINSTALL info). Squirrel.Windows simply uses application name as key. diff --git a/package.json b/package.json index 897bc8ad99b..7cebf6b0169 100644 --- a/package.json +++ b/package.json @@ -81,7 +81,7 @@ "progress-stream": "^1.2.0", "read-package-json": "^2.0.4", "sanitize-filename": "^1.6.0", - "semver": "^5.1.0", + "semver": "^5.1.1", "signcode-tf": "~0.7.3", "source-map-support": "^0.4.0", "update-notifier": "^1.0.2", @@ -116,12 +116,12 @@ "electron-download": "^2.1.2", "json8": "^0.9.0", "plist": "^1.2.0", - "pre-git": "^3.9.1", + "pre-git": "^3.10.0", "semantic-release": "^6.3.0", "should": "^9.0.2", "ts-babel": "^1.0.2", "tsconfig-glob": "^0.4.3", - "tslint": "3.11.0-dev.0", + "tslint": "3.12.0-dev.1", "typescript": "1.9.0-dev.20160620-1.0", "whitespace": "^2.0.0" }, diff --git a/src/packager.ts b/src/packager.ts index 94400d4822d..9dffc249d21 100644 --- a/src/packager.ts +++ b/src/packager.ts @@ -104,7 +104,11 @@ export class Packager implements BuildInfo { checkWineVersion(wineCheck) } - await helper.pack(outDir, arch, createTargets(nameToTarget, targets, helper, cleanupTasks), distTasks) + await helper.pack(outDir, arch, createTargets(nameToTarget, targets, outDir, helper, cleanupTasks), distTasks) + } + + for (let target of nameToTarget.values()) { + distTasks.push(target.finishBuild()) } } diff --git a/src/platformPackager.ts b/src/platformPackager.ts index f659647a498..d5a04fccdc8 100644 --- a/src/platformPackager.ts +++ b/src/platformPackager.ts @@ -69,6 +69,10 @@ export interface BuildInfo { export class Target { constructor(public name: string) { } + + finishBuild(): Promise { + return BluebirdPromise.resolve() + } } export abstract class PlatformPackager { @@ -112,7 +116,7 @@ export abstract class PlatformPackager return options == null ? Object.create(null) : options } - createTargets(targets: Array, mapper: (name: string, factory: () => Target) => void, cleanupTasks: Array<() => Promise>): void { + createTargets(targets: Array, mapper: (name: string, factory: (outDir: string) => Target) => void, cleanupTasks: Array<() => Promise>): void { throw new Error("not implemented") } diff --git a/src/targets/archive.ts b/src/targets/archive.ts index 3954e8dce6b..bd56beeda02 100644 --- a/src/targets/archive.ts +++ b/src/targets/archive.ts @@ -19,7 +19,7 @@ const extToCompressionDescriptor: { [key: string]: CompressionDescriptor; } = { "tar.bz2": new CompressionDescriptor("--bzip2", "BZIP2", "-1"), } -export async function archiveApp(compression: CompressionLevel | n, format: string, outFile: string, dirToArchive: string, withoutDir: boolean = false): Promise { +export async function archiveApp(compression: CompressionLevel | n, format: string, outFile: string, dirToArchive: string, withoutDir: boolean = false): Promise { const storeOnly = compression === "store" if (format.startsWith("tar.")) { @@ -37,7 +37,7 @@ export async function archiveApp(compression: CompressionLevel | n, format: stri stdio: ["ignore", debug.enabled ? "inherit" : "ignore", "inherit"], env: tarEnv }) - return + return outFile } const args = debug7zArgs("a") @@ -76,4 +76,6 @@ export async function archiveApp(compression: CompressionLevel | n, format: stri cwd: withoutDir ? dirToArchive : path.dirname(dirToArchive), stdio: ["ignore", debug.enabled ? "inherit" : "ignore", "inherit"], }) + + return outFile } \ No newline at end of file diff --git a/src/targets/nsis.ts b/src/targets/nsis.ts index 4efaf2ac932..5e71085f31e 100644 --- a/src/targets/nsis.ts +++ b/src/targets/nsis.ts @@ -5,9 +5,10 @@ import * as path from "path" import { Promise as BluebirdPromise } from "bluebird" import { getBin } from "../util/binDownload" import { v5 as uuid5 } from "uuid-1345" -import { getArchSuffix, Target } from "../platformPackager" +import { Target } from "../platformPackager" import { archiveApp } from "./archive" -import { subTask } from "../util/log" +import { subTask, task } from "../util/log" +import { unlink } from "fs-extra-p" import sanitizeFileName = require("sanitize-filename") import semver = require("semver") @@ -26,22 +27,33 @@ const nsisPathPromise = getBin("nsis", NSIS_VERSION, `https://dl.bintray.com/ele export default class NsisTarget extends Target { private readonly options: NsisOptions - constructor(private packager: WinPackager) { + private archs: Map> = new Map() + + constructor(private packager: WinPackager, private outDir: string) { super("nsis") this.options = packager.info.devMetadata.build.nsis || Object.create(null) } - async build(arch: Arch, outDir: string, appOutDir: string) { + async build(arch: Arch, appOutDir: string) { + const packager = this.packager + const archSuffix = arch == Arch.x64 ? "x64": "ia32" + const archiveFile = path.join(this.outDir, `${packager.appInfo.name}-${packager.appInfo.version}-${archSuffix}.nsis.7z`) + this.archs.set(arch, task(`Creating NSIS ${archSuffix} package`, archiveApp(packager.devMetadata.build.compression, "7z", archiveFile, appOutDir, true))) + } + + finishBuild(): Promise { + return task("Building NSIS installer", this.buildInstaller() + .then(() => BluebirdPromise.map(this.archs.values(), it => unlink(it)))) + } + + private async buildInstaller(): Promise { const packager = this.packager const iconPath = await packager.getIconPath() const appInfo = packager.appInfo const version = appInfo.version - const archSuffix = getArchSuffix(arch) - const installerPath = path.join(outDir, `${appInfo.productName} Setup ${version}${archSuffix}.exe`) - // const archiveFile = path.join(this.outDir, `.${packager.metadata.name}-${packager.metadata.version}${archSuffix}.7z`) - const archiveFile = path.join(outDir, `app.7z`) + const installerPath = path.join(this.outDir, `${appInfo.productName} Setup ${version}.exe`) const guid = this.options.guid || await BluebirdPromise.promisify(uuid5)({namespace: ELECTRON_BUILDER_NS_UUID, name: appInfo.id}) const productName = appInfo.productName @@ -51,7 +63,6 @@ export default class NsisTarget extends Target { PRODUCT_NAME: productName, INST_DIR_NAME: sanitizeFileName(productName), APP_DESCRIPTION: appInfo.description, - APP_ARCHIVE: archiveFile, VERSION: version, MUI_ICON: iconPath, @@ -60,6 +71,10 @@ export default class NsisTarget extends Target { COMPANY_NAME: appInfo.companyName, } + for (let [arch, file] of this.archs) { + defines[arch === Arch.x64 ? "APP_64" : "APP_32"] = await file + } + let installerHeader = this.options.installerHeader if (installerHeader === undefined) { const resourceList = await packager.resourceList @@ -117,8 +132,6 @@ export default class NsisTarget extends Target { defines.COMPRESS = "auto" } - await subTask("Packing app into 7z archive", archiveApp(packager.devMetadata.build.compression, "7z", archiveFile, appOutDir, true)) - const oneClick = this.options.oneClick !== false if (oneClick) { defines.ONE_CLICK = null @@ -134,7 +147,7 @@ export default class NsisTarget extends Target { await subTask(`Executing makensis`, NsisTarget.executeMakensis(defines, commands)) await packager.sign(installerPath) - this.packager.dispatchArtifactCreated(installerPath, `${appInfo.name}-Setup-${version}${archSuffix}.exe`) + this.packager.dispatchArtifactCreated(installerPath, `${appInfo.name}-Setup-${version}.exe`) } private static async executeMakensis(defines: any, commands: any) { diff --git a/src/targets/squirrelWindows.ts b/src/targets/squirrelWindows.ts index 993f0518f66..17ec18d7332 100644 --- a/src/targets/squirrelWindows.ts +++ b/src/targets/squirrelWindows.ts @@ -16,6 +16,10 @@ export default class SquirrelWindowsTarget extends Target { } async build(arch: Arch, appOutDir: string) { + if (arch === Arch.ia32) { + warn("For windows consider only distributing 64-bit, see https://github.com/electron-userland/electron-builder/issues/359#issuecomment-214851130") + } + const appInfo = this.packager.appInfo const version = appInfo.version const archSuffix = getArchSuffix(arch) diff --git a/src/targets/targetFactory.ts b/src/targets/targetFactory.ts index d8cc22ba3d0..19328c5c45f 100644 --- a/src/targets/targetFactory.ts +++ b/src/targets/targetFactory.ts @@ -4,13 +4,13 @@ export const commonTargets = ["dir", "zip", "7z", "tar.xz", "tar.lz", "tar.gz", export const DEFAULT_TARGET = "default" export const DIR_TARGET = "dir" -export function createTargets(nameToTarget: Map, rawList: Array | n, packager: PlatformPackager, cleanupTasks: Array<() => Promise>): Array { +export function createTargets(nameToTarget: Map, rawList: Array | n, outDir: string, packager: PlatformPackager, cleanupTasks: Array<() => Promise>): Array { const result: Array = [] - const mapper = (name: string, factory: () => Target) => { + const mapper = (name: string, factory: (outDir: string) => Target) => { let target = nameToTarget.get(name) if (target == null) { - target = factory() + target = factory(outDir) nameToTarget.set(name, target) } result.push(target) diff --git a/src/winPackager.ts b/src/winPackager.ts index 01126114b0b..0a7de7fb971 100644 --- a/src/winPackager.ts +++ b/src/winPackager.ts @@ -3,7 +3,7 @@ import { Promise as BluebirdPromise } from "bluebird" import { PlatformPackager, BuildInfo, getArchSuffix, Target } from "./platformPackager" import { Platform, WinBuildOptions, Arch } from "./metadata" import * as path from "path" -import { log, warn, task } from "./util/log" +import { log, task } from "./util/log" import { deleteFile, open, close, read } from "fs-extra-p" import { sign, SignOptions } from "signcode-tf" import SquirrelWindowsTarget from "./targets/squirrelWindows" @@ -51,7 +51,7 @@ export class WinPackager extends PlatformPackager { this.iconPath = this.getValidIconPath() } - createTargets(targets: Array, mapper: (name: string, factory: () => Target) => void, cleanupTasks: Array<() => Promise>): void { + createTargets(targets: Array, mapper: (name: string, factory: (outDir: string) => Target) => void, cleanupTasks: Array<() => Promise>): void { for (let name of targets) { if (name === DIR_TARGET) { continue @@ -64,9 +64,9 @@ export class WinPackager extends PlatformPackager { }) } else if (name === "nsis") { - mapper(name, () => { + mapper(name, outDir => { const targetClass: typeof NsisTarget = require("./targets/nsis").default - return new targetClass(this) + return new targetClass(this, outDir) }) } else { @@ -99,10 +99,6 @@ export class WinPackager extends PlatformPackager { } async pack(outDir: string, arch: Arch, targets: Array, postAsyncTasks: Array>): Promise { - if (arch === Arch.ia32) { - warn("For windows consider only distributing 64-bit, see https://github.com/electron-userland/electron-builder/issues/359#issuecomment-214851130") - } - const appOutDir = this.computeAppOutDir(outDir, arch) const packOptions = await this.computePackOptions(outDir, appOutDir, arch) @@ -142,7 +138,7 @@ export class WinPackager extends PlatformPackager { promises.push(task(`Building Squirrel.Windows installer`, target.build(arch, appOutDir))) } else if (target instanceof NsisTarget) { - promises.push(task(`Building NSIS installer`, target.build(arch, outDir, appOutDir))) + promises.push(target.build(arch, appOutDir)) } else { const format = target.name diff --git a/templates/nsis/installer.nsi b/templates/nsis/installer.nsi index 51c952a5ba9..3f405a384fc 100644 --- a/templates/nsis/installer.nsi +++ b/templates/nsis/installer.nsi @@ -4,6 +4,7 @@ !include "nsProcess.nsh" !include "allowOnlyOneInstallerInstace.nsh" !include "checkAppRunning.nsh" +!include x64.nsh Function StartApp ExecShell "" "$SMPROGRAMS\${PRODUCT_NAME}.lnk" @@ -25,8 +26,25 @@ Function .onInit !insertmacro ALLOW_ONLY_ONE_INSTALLER_INSTACE InitPluginsDir + + ${If} ${RunningX64} + !ifdef APP_64 + SetRegView 64 + !endif + ${Else} + !ifndef APP_32 + MessageBox MB_OK|MB_ICONEXCLAMATION "64-bit Windows is required." + Quit + !endif + ${EndIf} + SetCompress off - File /oname=$PLUGINSDIR\app.7z "${APP_ARCHIVE}" + !ifdef APP_32 + File /oname=$PLUGINSDIR\app-32.7z "${APP_32}" + !endif + !ifdef APP_64 + File /oname=$PLUGINSDIR\app-64.7z "${APP_64}" + !endif SetCompress "${COMPRESS}" FunctionEnd @@ -47,7 +65,11 @@ Section "install" RMDir /r $INSTDIR SetOutPath $INSTDIR - Nsis7z::Extract "$PLUGINSDIR\app.7z" + ${If} ${RunningX64} + Nsis7z::Extract "$PLUGINSDIR\app-64.7z" + ${Else} + Nsis7z::Extract "$PLUGINSDIR\app-32.7z" + ${EndIf} # <% if(fileAssociation){ %> # specify file association @@ -91,6 +113,8 @@ Section "un.install" # delete the installed files RMDir /r $INSTDIR + RMDir /r "$APPDATA\${PRODUCT_NAME}" + !insertmacro MULTIUSER_RegistryRemoveInstallInfo !ifdef ONE_CLICK diff --git a/test/fixtures/test-app-one/index.js b/test/fixtures/test-app-one/index.js index 4c58c12fcbf..5b76d2c73ee 100644 --- a/test/fixtures/test-app-one/index.js +++ b/test/fixtures/test-app-one/index.js @@ -89,6 +89,8 @@ function createWindow () { // Open the DevTools. mainWindow.webContents.openDevTools(); + mainWindow.webContents.executeJavaScript(`console.log("appData: ${app.getPath("appData")}")`) + // Emitted when the window is closed. mainWindow.on('closed', function() { // Dereference the window object, usually you would store windows diff --git a/test/src/helpers/packTester.ts b/test/src/helpers/packTester.ts index 1d57a38be59..f64654636d2 100755 --- a/test/src/helpers/packTester.ts +++ b/test/src/helpers/packTester.ts @@ -253,8 +253,8 @@ async function checkWindowsResult(packager: Packager, checkOptions: AssertPackOp artifactNames.push(`${appInfo.name}-Setup-${appInfo.version}${archSuffix}.exe`) } else if (target === "nsis") { - expectedFileNames.push(`${productName} Setup ${appInfo.version}${archSuffix}.exe`) - artifactNames.push(`${appInfo.name}-Setup-${appInfo.version}${archSuffix}.exe`) + expectedFileNames.push(`${productName} Setup ${appInfo.version}.exe`) + artifactNames.push(`${appInfo.name}-Setup-${appInfo.version}.exe`) } else { expectedFileNames.push(`${productName}-${appInfo.version}${archSuffix}-win.${target}`) diff --git a/test/src/winPackagerTest.ts b/test/src/winPackagerTest.ts index 9711ac6104b..3132b8ae6cb 100755 --- a/test/src/winPackagerTest.ts +++ b/test/src/winPackagerTest.ts @@ -28,7 +28,7 @@ test.ifNotCiOsx("win", () => assertPack("test-app-one", _signed({ )) test("nsis", () => assertPack("test-app-one", _signed({ - targets: Platform.WINDOWS.createTarget(["nsis"]), + targets: Platform.WINDOWS.createTarget(["nsis"], Arch.ia32, Arch.x64), }), { useTempDir: true, }