Skip to content

Commit

Permalink
feat(nsis): 32 bit + 64 bit installer
Browse files Browse the repository at this point in the history
Closes #528 #536
  • Loading branch information
develar committed Jun 26, 2016
1 parent 3f43c0a commit bbd0bd6
Show file tree
Hide file tree
Showing 13 changed files with 89 additions and 36 deletions.
4 changes: 4 additions & 0 deletions docs/NSIS.md
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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"
},
Expand Down
6 changes: 5 additions & 1 deletion src/packager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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())
}
}

Expand Down
6 changes: 5 additions & 1 deletion src/platformPackager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,10 @@ export interface BuildInfo {
export class Target {
constructor(public name: string) {
}

finishBuild(): Promise<any> {
return BluebirdPromise.resolve()
}
}

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

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

Expand Down
6 changes: 4 additions & 2 deletions src/targets/archive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<any> {
export async function archiveApp(compression: CompressionLevel | n, format: string, outFile: string, dirToArchive: string, withoutDir: boolean = false): Promise<string> {
const storeOnly = compression === "store"

if (format.startsWith("tar.")) {
Expand All @@ -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")
Expand Down Expand Up @@ -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
}
37 changes: 25 additions & 12 deletions src/targets/nsis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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")

Expand All @@ -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<Arch, Promise<string>> = 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<any> {
return task("Building NSIS installer", this.buildInstaller()
.then(() => BluebirdPromise.map(this.archs.values(), it => unlink(it))))
}

private async buildInstaller(): Promise<any> {
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
Expand All @@ -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,
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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) {
Expand Down
4 changes: 4 additions & 0 deletions src/targets/squirrelWindows.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
6 changes: 3 additions & 3 deletions src/targets/targetFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<String, Target>, rawList: Array<string> | n, packager: PlatformPackager<any>, cleanupTasks: Array<() => Promise<any>>): Array<Target> {
export function createTargets(nameToTarget: Map<String, Target>, rawList: Array<string> | n, outDir: string, packager: PlatformPackager<any>, cleanupTasks: Array<() => Promise<any>>): Array<Target> {
const result: Array<Target> = []

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)
Expand Down
14 changes: 5 additions & 9 deletions src/winPackager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -51,7 +51,7 @@ export class WinPackager extends PlatformPackager<WinBuildOptions> {
this.iconPath = this.getValidIconPath()
}

createTargets(targets: Array<string>, mapper: (name: string, factory: () => Target) => void, cleanupTasks: Array<() => Promise<any>>): void {
createTargets(targets: Array<string>, mapper: (name: string, factory: (outDir: string) => Target) => void, cleanupTasks: Array<() => Promise<any>>): void {
for (let name of targets) {
if (name === DIR_TARGET) {
continue
Expand All @@ -64,9 +64,9 @@ export class WinPackager extends PlatformPackager<WinBuildOptions> {
})
}
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 {
Expand Down Expand Up @@ -99,10 +99,6 @@ export class WinPackager extends PlatformPackager<WinBuildOptions> {
}

async pack(outDir: string, arch: Arch, targets: Array<Target>, postAsyncTasks: Array<Promise<any>>): Promise<any> {
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)

Expand Down Expand Up @@ -142,7 +138,7 @@ export class WinPackager extends PlatformPackager<WinBuildOptions> {
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
Expand Down
28 changes: 26 additions & 2 deletions templates/nsis/installer.nsi
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
!include "nsProcess.nsh"
!include "allowOnlyOneInstallerInstace.nsh"
!include "checkAppRunning.nsh"
!include x64.nsh

Function StartApp
ExecShell "" "$SMPROGRAMS\${PRODUCT_NAME}.lnk"
Expand All @@ -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

Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions test/fixtures/test-app-one/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions test/src/helpers/packTester.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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}`)
Expand Down
2 changes: 1 addition & 1 deletion test/src/winPackagerTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
Expand Down

0 comments on commit bbd0bd6

Please sign in to comment.