Skip to content

Commit bbd0bd6

Browse files
committed
feat(nsis): 32 bit + 64 bit installer
Closes #528 #536
1 parent 3f43c0a commit bbd0bd6

File tree

13 files changed

+89
-36
lines changed

13 files changed

+89
-36
lines changed

docs/NSIS.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
# 32 bit + 64 bit
2+
3+
If you build both ia32 and xha arch, you in any case get one installer. Appropriate arch will be installed automatically.
4+
15
# GUID vs Application Name
26

37
Windows requires to use registry keys (e.g. INSTALL/UNINSTALL info). Squirrel.Windows simply uses application name as key.

package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@
8181
"progress-stream": "^1.2.0",
8282
"read-package-json": "^2.0.4",
8383
"sanitize-filename": "^1.6.0",
84-
"semver": "^5.1.0",
84+
"semver": "^5.1.1",
8585
"signcode-tf": "~0.7.3",
8686
"source-map-support": "^0.4.0",
8787
"update-notifier": "^1.0.2",
@@ -116,12 +116,12 @@
116116
"electron-download": "^2.1.2",
117117
"json8": "^0.9.0",
118118
"plist": "^1.2.0",
119-
"pre-git": "^3.9.1",
119+
"pre-git": "^3.10.0",
120120
"semantic-release": "^6.3.0",
121121
"should": "^9.0.2",
122122
"ts-babel": "^1.0.2",
123123
"tsconfig-glob": "^0.4.3",
124-
"tslint": "3.11.0-dev.0",
124+
"tslint": "3.12.0-dev.1",
125125
"typescript": "1.9.0-dev.20160620-1.0",
126126
"whitespace": "^2.0.0"
127127
},

src/packager.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,11 @@ export class Packager implements BuildInfo {
104104
checkWineVersion(wineCheck)
105105
}
106106

107-
await helper.pack(outDir, arch, createTargets(nameToTarget, targets, helper, cleanupTasks), distTasks)
107+
await helper.pack(outDir, arch, createTargets(nameToTarget, targets, outDir, helper, cleanupTasks), distTasks)
108+
}
109+
110+
for (let target of nameToTarget.values()) {
111+
distTasks.push(target.finishBuild())
108112
}
109113
}
110114

src/platformPackager.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,10 @@ export interface BuildInfo {
6969
export class Target {
7070
constructor(public name: string) {
7171
}
72+
73+
finishBuild(): Promise<any> {
74+
return BluebirdPromise.resolve()
75+
}
7276
}
7377

7478
export abstract class PlatformPackager<DC extends PlatformSpecificBuildOptions> {
@@ -112,7 +116,7 @@ export abstract class PlatformPackager<DC extends PlatformSpecificBuildOptions>
112116
return options == null ? Object.create(null) : options
113117
}
114118

115-
createTargets(targets: Array<string>, mapper: (name: string, factory: () => Target) => void, cleanupTasks: Array<() => Promise<any>>): void {
119+
createTargets(targets: Array<string>, mapper: (name: string, factory: (outDir: string) => Target) => void, cleanupTasks: Array<() => Promise<any>>): void {
116120
throw new Error("not implemented")
117121
}
118122

src/targets/archive.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ const extToCompressionDescriptor: { [key: string]: CompressionDescriptor; } = {
1919
"tar.bz2": new CompressionDescriptor("--bzip2", "BZIP2", "-1"),
2020
}
2121

22-
export async function archiveApp(compression: CompressionLevel | n, format: string, outFile: string, dirToArchive: string, withoutDir: boolean = false): Promise<any> {
22+
export async function archiveApp(compression: CompressionLevel | n, format: string, outFile: string, dirToArchive: string, withoutDir: boolean = false): Promise<string> {
2323
const storeOnly = compression === "store"
2424

2525
if (format.startsWith("tar.")) {
@@ -37,7 +37,7 @@ export async function archiveApp(compression: CompressionLevel | n, format: stri
3737
stdio: ["ignore", debug.enabled ? "inherit" : "ignore", "inherit"],
3838
env: tarEnv
3939
})
40-
return
40+
return outFile
4141
}
4242

4343
const args = debug7zArgs("a")
@@ -76,4 +76,6 @@ export async function archiveApp(compression: CompressionLevel | n, format: stri
7676
cwd: withoutDir ? dirToArchive : path.dirname(dirToArchive),
7777
stdio: ["ignore", debug.enabled ? "inherit" : "ignore", "inherit"],
7878
})
79+
80+
return outFile
7981
}

src/targets/nsis.ts

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@ import * as path from "path"
55
import { Promise as BluebirdPromise } from "bluebird"
66
import { getBin } from "../util/binDownload"
77
import { v5 as uuid5 } from "uuid-1345"
8-
import { getArchSuffix, Target } from "../platformPackager"
8+
import { Target } from "../platformPackager"
99
import { archiveApp } from "./archive"
10-
import { subTask } from "../util/log"
10+
import { subTask, task } from "../util/log"
11+
import { unlink } from "fs-extra-p"
1112
import sanitizeFileName = require("sanitize-filename")
1213
import semver = require("semver")
1314

@@ -26,22 +27,33 @@ const nsisPathPromise = getBin("nsis", NSIS_VERSION, `https://dl.bintray.com/ele
2627
export default class NsisTarget extends Target {
2728
private readonly options: NsisOptions
2829

29-
constructor(private packager: WinPackager) {
30+
private archs: Map<Arch, Promise<string>> = new Map()
31+
32+
constructor(private packager: WinPackager, private outDir: string) {
3033
super("nsis")
3134

3235
this.options = packager.info.devMetadata.build.nsis || Object.create(null)
3336
}
3437

35-
async build(arch: Arch, outDir: string, appOutDir: string) {
38+
async build(arch: Arch, appOutDir: string) {
39+
const packager = this.packager
40+
const archSuffix = arch == Arch.x64 ? "x64": "ia32"
41+
const archiveFile = path.join(this.outDir, `${packager.appInfo.name}-${packager.appInfo.version}-${archSuffix}.nsis.7z`)
42+
this.archs.set(arch, task(`Creating NSIS ${archSuffix} package`, archiveApp(packager.devMetadata.build.compression, "7z", archiveFile, appOutDir, true)))
43+
}
44+
45+
finishBuild(): Promise<any> {
46+
return task("Building NSIS installer", this.buildInstaller()
47+
.then(() => BluebirdPromise.map(this.archs.values(), it => unlink(it))))
48+
}
49+
50+
private async buildInstaller(): Promise<any> {
3651
const packager = this.packager
3752

3853
const iconPath = await packager.getIconPath()
3954
const appInfo = packager.appInfo
4055
const version = appInfo.version
41-
const archSuffix = getArchSuffix(arch)
42-
const installerPath = path.join(outDir, `${appInfo.productName} Setup ${version}${archSuffix}.exe`)
43-
// const archiveFile = path.join(this.outDir, `.${packager.metadata.name}-${packager.metadata.version}${archSuffix}.7z`)
44-
const archiveFile = path.join(outDir, `app.7z`)
56+
const installerPath = path.join(this.outDir, `${appInfo.productName} Setup ${version}.exe`)
4557

4658
const guid = this.options.guid || await BluebirdPromise.promisify(uuid5)({namespace: ELECTRON_BUILDER_NS_UUID, name: appInfo.id})
4759
const productName = appInfo.productName
@@ -51,7 +63,6 @@ export default class NsisTarget extends Target {
5163
PRODUCT_NAME: productName,
5264
INST_DIR_NAME: sanitizeFileName(productName),
5365
APP_DESCRIPTION: appInfo.description,
54-
APP_ARCHIVE: archiveFile,
5566
VERSION: version,
5667

5768
MUI_ICON: iconPath,
@@ -60,6 +71,10 @@ export default class NsisTarget extends Target {
6071
COMPANY_NAME: appInfo.companyName,
6172
}
6273

74+
for (let [arch, file] of this.archs) {
75+
defines[arch === Arch.x64 ? "APP_64" : "APP_32"] = await file
76+
}
77+
6378
let installerHeader = this.options.installerHeader
6479
if (installerHeader === undefined) {
6580
const resourceList = await packager.resourceList
@@ -117,8 +132,6 @@ export default class NsisTarget extends Target {
117132
defines.COMPRESS = "auto"
118133
}
119134

120-
await subTask("Packing app into 7z archive", archiveApp(packager.devMetadata.build.compression, "7z", archiveFile, appOutDir, true))
121-
122135
const oneClick = this.options.oneClick !== false
123136
if (oneClick) {
124137
defines.ONE_CLICK = null
@@ -134,7 +147,7 @@ export default class NsisTarget extends Target {
134147
await subTask(`Executing makensis`, NsisTarget.executeMakensis(defines, commands))
135148
await packager.sign(installerPath)
136149

137-
this.packager.dispatchArtifactCreated(installerPath, `${appInfo.name}-Setup-${version}${archSuffix}.exe`)
150+
this.packager.dispatchArtifactCreated(installerPath, `${appInfo.name}-Setup-${version}.exe`)
138151
}
139152

140153
private static async executeMakensis(defines: any, commands: any) {

src/targets/squirrelWindows.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ export default class SquirrelWindowsTarget extends Target {
1616
}
1717

1818
async build(arch: Arch, appOutDir: string) {
19+
if (arch === Arch.ia32) {
20+
warn("For windows consider only distributing 64-bit, see https://github.com/electron-userland/electron-builder/issues/359#issuecomment-214851130")
21+
}
22+
1923
const appInfo = this.packager.appInfo
2024
const version = appInfo.version
2125
const archSuffix = getArchSuffix(arch)

src/targets/targetFactory.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,13 @@ export const commonTargets = ["dir", "zip", "7z", "tar.xz", "tar.lz", "tar.gz",
44
export const DEFAULT_TARGET = "default"
55
export const DIR_TARGET = "dir"
66

7-
export function createTargets(nameToTarget: Map<String, Target>, rawList: Array<string> | n, packager: PlatformPackager<any>, cleanupTasks: Array<() => Promise<any>>): Array<Target> {
7+
export function createTargets(nameToTarget: Map<String, Target>, rawList: Array<string> | n, outDir: string, packager: PlatformPackager<any>, cleanupTasks: Array<() => Promise<any>>): Array<Target> {
88
const result: Array<Target> = []
99

10-
const mapper = (name: string, factory: () => Target) => {
10+
const mapper = (name: string, factory: (outDir: string) => Target) => {
1111
let target = nameToTarget.get(name)
1212
if (target == null) {
13-
target = factory()
13+
target = factory(outDir)
1414
nameToTarget.set(name, target)
1515
}
1616
result.push(target)

src/winPackager.ts

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { Promise as BluebirdPromise } from "bluebird"
33
import { PlatformPackager, BuildInfo, getArchSuffix, Target } from "./platformPackager"
44
import { Platform, WinBuildOptions, Arch } from "./metadata"
55
import * as path from "path"
6-
import { log, warn, task } from "./util/log"
6+
import { log, task } from "./util/log"
77
import { deleteFile, open, close, read } from "fs-extra-p"
88
import { sign, SignOptions } from "signcode-tf"
99
import SquirrelWindowsTarget from "./targets/squirrelWindows"
@@ -51,7 +51,7 @@ export class WinPackager extends PlatformPackager<WinBuildOptions> {
5151
this.iconPath = this.getValidIconPath()
5252
}
5353

54-
createTargets(targets: Array<string>, mapper: (name: string, factory: () => Target) => void, cleanupTasks: Array<() => Promise<any>>): void {
54+
createTargets(targets: Array<string>, mapper: (name: string, factory: (outDir: string) => Target) => void, cleanupTasks: Array<() => Promise<any>>): void {
5555
for (let name of targets) {
5656
if (name === DIR_TARGET) {
5757
continue
@@ -64,9 +64,9 @@ export class WinPackager extends PlatformPackager<WinBuildOptions> {
6464
})
6565
}
6666
else if (name === "nsis") {
67-
mapper(name, () => {
67+
mapper(name, outDir => {
6868
const targetClass: typeof NsisTarget = require("./targets/nsis").default
69-
return new targetClass(this)
69+
return new targetClass(this, outDir)
7070
})
7171
}
7272
else {
@@ -99,10 +99,6 @@ export class WinPackager extends PlatformPackager<WinBuildOptions> {
9999
}
100100

101101
async pack(outDir: string, arch: Arch, targets: Array<Target>, postAsyncTasks: Array<Promise<any>>): Promise<any> {
102-
if (arch === Arch.ia32) {
103-
warn("For windows consider only distributing 64-bit, see https://github.com/electron-userland/electron-builder/issues/359#issuecomment-214851130")
104-
}
105-
106102
const appOutDir = this.computeAppOutDir(outDir, arch)
107103
const packOptions = await this.computePackOptions(outDir, appOutDir, arch)
108104

@@ -142,7 +138,7 @@ export class WinPackager extends PlatformPackager<WinBuildOptions> {
142138
promises.push(task(`Building Squirrel.Windows installer`, target.build(arch, appOutDir)))
143139
}
144140
else if (target instanceof NsisTarget) {
145-
promises.push(task(`Building NSIS installer`, target.build(arch, outDir, appOutDir)))
141+
promises.push(target.build(arch, appOutDir))
146142
}
147143
else {
148144
const format = target.name

templates/nsis/installer.nsi

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
!include "nsProcess.nsh"
55
!include "allowOnlyOneInstallerInstace.nsh"
66
!include "checkAppRunning.nsh"
7+
!include x64.nsh
78

89
Function StartApp
910
ExecShell "" "$SMPROGRAMS\${PRODUCT_NAME}.lnk"
@@ -25,8 +26,25 @@ Function .onInit
2526
!insertmacro ALLOW_ONLY_ONE_INSTALLER_INSTACE
2627

2728
InitPluginsDir
29+
30+
${If} ${RunningX64}
31+
!ifdef APP_64
32+
SetRegView 64
33+
!endif
34+
${Else}
35+
!ifndef APP_32
36+
MessageBox MB_OK|MB_ICONEXCLAMATION "64-bit Windows is required."
37+
Quit
38+
!endif
39+
${EndIf}
40+
2841
SetCompress off
29-
File /oname=$PLUGINSDIR\app.7z "${APP_ARCHIVE}"
42+
!ifdef APP_32
43+
File /oname=$PLUGINSDIR\app-32.7z "${APP_32}"
44+
!endif
45+
!ifdef APP_64
46+
File /oname=$PLUGINSDIR\app-64.7z "${APP_64}"
47+
!endif
3048
SetCompress "${COMPRESS}"
3149
FunctionEnd
3250

@@ -47,7 +65,11 @@ Section "install"
4765
RMDir /r $INSTDIR
4866
SetOutPath $INSTDIR
4967

50-
Nsis7z::Extract "$PLUGINSDIR\app.7z"
68+
${If} ${RunningX64}
69+
Nsis7z::Extract "$PLUGINSDIR\app-64.7z"
70+
${Else}
71+
Nsis7z::Extract "$PLUGINSDIR\app-32.7z"
72+
${EndIf}
5173

5274
# <% if(fileAssociation){ %>
5375
# specify file association
@@ -91,6 +113,8 @@ Section "un.install"
91113
# delete the installed files
92114
RMDir /r $INSTDIR
93115

116+
RMDir /r "$APPDATA\${PRODUCT_NAME}"
117+
94118
!insertmacro MULTIUSER_RegistryRemoveInstallInfo
95119

96120
!ifdef ONE_CLICK

0 commit comments

Comments
 (0)