From 3f43c0ab21afeb7def00418e92e4eda37d591ff3 Mon Sep 17 00:00:00 2001 From: develar Date: Fri, 24 Jun 2016 09:32:12 +0200 Subject: [PATCH] feat(nsis): MUI_HEADERIMAGE #525 --- .gitattributes | 1 + .idea/dictionaries/develar.xml | 1 + docs/Options.md | 1 + src/metadata.ts | 5 ++ src/platformPackager.ts | 4 +- src/targets/nsis.ts | 33 +++++-- .../fixtures/test-app-one/installerHeader.bmp | 3 + test/src/helpers/packTester.ts | 2 +- test/src/winPackagerTest.ts | 90 ++++++++++++++++--- 9 files changed, 120 insertions(+), 20 deletions(-) create mode 100755 test/fixtures/test-app-one/installerHeader.bmp diff --git a/.gitattributes b/.gitattributes index 67ce1e1abc9..977d61ba170 100755 --- a/.gitattributes +++ b/.gitattributes @@ -4,3 +4,4 @@ vendor/**/* filter=lfs diff=lfs merge=lfs -text *.gif filter=lfs diff=lfs merge=lfs -text *.keychain filter=lfs diff=lfs merge=lfs -text +*.bmp filter=lfs diff=lfs merge=lfs -text diff --git a/.idea/dictionaries/develar.xml b/.idea/dictionaries/develar.xml index bdebf788e4b..07ebb857fe3 100644 --- a/.idea/dictionaries/develar.xml +++ b/.idea/dictionaries/develar.xml @@ -30,6 +30,7 @@ graphicsmagick gtar guid + headerimage hicolor hrtime icnsutils diff --git a/docs/Options.md b/docs/Options.md index de05d4704a5..99d20ee95c5 100644 --- a/docs/Options.md +++ b/docs/Options.md @@ -129,6 +129,7 @@ See [NSIS target notes](https://github.com/electron-userland/electron-builder/wi | perMachine | Mark "all users" (per-machine) as default. Not recommended. Defaults to `false`. | allowElevation | Allow requesting for elevation. If false, user will have to restart installer with elevated permissions. Defaults to `true`. | oneClick | One-click installation. Defaults to `true`. +| installerHeader | *boring installer only.* `MUI_HEADERIMAGE`, relative to the project directory. Defaults to `build/installerHeader.bmp` ### `.build.linux` diff --git a/src/metadata.ts b/src/metadata.ts index 7ef292be271..9ef32a93de0 100755 --- a/src/metadata.ts +++ b/src/metadata.ts @@ -368,6 +368,11 @@ export interface NsisOptions { One-click installation. Defaults to `true`. */ readonly oneClick?: boolean | null + + /* + *boring installer only.* `MUI_HEADERIMAGE`, relative to the project directory. Defaults to `build/installerHeader.bmp` + */ + readonly installerHeader?: string | null } /* diff --git a/src/platformPackager.ts b/src/platformPackager.ts index 06c96013bef..f659647a498 100644 --- a/src/platformPackager.ts +++ b/src/platformPackager.ts @@ -44,6 +44,8 @@ export interface PackagerOptions { * Application `package.json` will be still read, but options specified in this object will override. */ readonly appMetadata?: AppMetadata + + readonly effectiveOptionComputed?: (options: any) => boolean } export interface BuildInfo { @@ -70,7 +72,7 @@ export class Target { } export abstract class PlatformPackager { - protected readonly options: PackagerOptions + readonly options: PackagerOptions readonly projectDir: string readonly buildResourcesDir: string diff --git a/src/targets/nsis.ts b/src/targets/nsis.ts index ad7a395704d..4efaf2ac932 100644 --- a/src/targets/nsis.ts +++ b/src/targets/nsis.ts @@ -24,12 +24,12 @@ const ELECTRON_BUILDER_NS_UUID = "50e065bc-3134-11e6-9bab-38c9862bdaf3" const nsisPathPromise = getBin("nsis", NSIS_VERSION, `https://dl.bintray.com/electron-userland/bin/${NSIS_VERSION}.7z`, NSIS_SHA2) export default class NsisTarget extends Target { - private readonly nsisOptions: NsisOptions + private readonly options: NsisOptions constructor(private packager: WinPackager) { super("nsis") - this.nsisOptions = packager.info.devMetadata.build.nsis || Object.create(null) + this.options = packager.info.devMetadata.build.nsis || Object.create(null) } async build(arch: Arch, outDir: string, appOutDir: string) { @@ -43,7 +43,7 @@ export default class NsisTarget extends Target { // const archiveFile = path.join(this.outDir, `.${packager.metadata.name}-${packager.metadata.version}${archSuffix}.7z`) const archiveFile = path.join(outDir, `app.7z`) - const guid = this.nsisOptions.guid || await BluebirdPromise.promisify(uuid5)({namespace: ELECTRON_BUILDER_NS_UUID, name: appInfo.id}) + const guid = this.options.guid || await BluebirdPromise.promisify(uuid5)({namespace: ELECTRON_BUILDER_NS_UUID, name: appInfo.id}) const productName = appInfo.productName const defines: any = { APP_ID: appInfo.id, @@ -60,14 +60,31 @@ export default class NsisTarget extends Target { COMPANY_NAME: appInfo.companyName, } - if (this.nsisOptions.perMachine === true) { + let installerHeader = this.options.installerHeader + if (installerHeader === undefined) { + const resourceList = await packager.resourceList + if (resourceList.includes("installerHeader.bmp")) { + installerHeader = path.join(packager.buildResourcesDir, "installerHeader.bmp") + } + } + else { + installerHeader = path.resolve(packager.projectDir, installerHeader) + } + + if (installerHeader != null) { + defines.MUI_HEADERIMAGE = null + defines.MUI_HEADERIMAGE_RIGHT = null + defines.MUI_HEADERIMAGE_BITMAP = installerHeader + } + + if (this.options.perMachine === true) { defines.MULTIUSER_INSTALLMODE_DEFAULT_ALLUSERS = null } else { defines.MULTIUSER_INSTALLMODE_DEFAULT_CURRENTUSER = null } - if (this.nsisOptions.allowElevation !== false) { + if (this.options.allowElevation !== false) { defines.MULTIUSER_INSTALLMODE_ALLOW_ELEVATION = null } @@ -102,7 +119,7 @@ export default class NsisTarget extends Target { await subTask("Packing app into 7z archive", archiveApp(packager.devMetadata.build.compression, "7z", archiveFile, appOutDir, true)) - const oneClick = this.nsisOptions.oneClick !== false + const oneClick = this.options.oneClick !== false if (oneClick) { defines.ONE_CLICK = null } @@ -110,6 +127,10 @@ export default class NsisTarget extends Target { debug(defines) debug(commands) + if (this.packager.options.effectiveOptionComputed != null && this.packager.options.effectiveOptionComputed([defines, commands])) { + return + } + await subTask(`Executing makensis`, NsisTarget.executeMakensis(defines, commands)) await packager.sign(installerPath) diff --git a/test/fixtures/test-app-one/installerHeader.bmp b/test/fixtures/test-app-one/installerHeader.bmp new file mode 100755 index 00000000000..b434ebf6fb7 --- /dev/null +++ b/test/fixtures/test-app-one/installerHeader.bmp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bd146e39af28cec251d63da453cb8f04b74904a643f4c3dab52ef2b5bcb4bb3a +size 9744 diff --git a/test/src/helpers/packTester.ts b/test/src/helpers/packTester.ts index 17f2f24fabf..1d57a38be59 100755 --- a/test/src/helpers/packTester.ts +++ b/test/src/helpers/packTester.ts @@ -100,7 +100,7 @@ async function packAndCheck(projectDir: string, packagerOptions: PackagerOptions const platformToTarget = await packager.build() - if (packagerOptions.platformPackagerFactory != null) { + if (packagerOptions.platformPackagerFactory != null || packagerOptions.effectiveOptionComputed != null) { return } diff --git a/test/src/winPackagerTest.ts b/test/src/winPackagerTest.ts index 603b5bb14b5..9711ac6104b 100755 --- a/test/src/winPackagerTest.ts +++ b/test/src/winPackagerTest.ts @@ -1,7 +1,7 @@ import { Platform, Arch, BuildInfo, PackagerOptions } from "out" import test from "./helpers/avaEx" import { assertPack, platform, modifyPackageJson, signed } from "./helpers/packTester" -import { move, outputFile } from "fs-extra-p" +import { outputFile, rename } from "fs-extra-p" import * as path from "path" import { WinPackager } from "out/winPackager" import { Promise as BluebirdPromise } from "bluebird" @@ -48,10 +48,76 @@ test.ifNotCiOsx("nsis boring", () => assertPack("test-app-one", _signed({ } )) -// test.ifNotCiOsx("win 32", () => assertPack("test-app-one", signed({ -// targets: Platform.WINDOWS.createTarget(null, Arch.ia32), -// }) -// )) +test.ifNotCiOsx("nsis boring", () => assertPack("test-app-one", _signed({ + targets: Platform.WINDOWS.createTarget(["nsis"]), + devMetadata: { + build: { + nsis: { + oneClick: false, + } + } + } + }), { + useTempDir: true, + } +)) + +test.ifNotCiOsx("nsis boring, MUI_HEADER", () => { + let installerHeaderPath: string | null = null + return assertPack("test-app-one", { + targets: Platform.WINDOWS.createTarget(["nsis"]), + devMetadata: { + build: { + nsis: { + oneClick: false, + } + } + }, + effectiveOptionComputed: options => { + const defines = options[0] + assertThat(defines.MUI_HEADERIMAGE).isEqualTo(null) + assertThat(defines.MUI_HEADERIMAGE_BITMAP).isEqualTo(installerHeaderPath) + assertThat(defines.MUI_HEADERIMAGE_RIGHT).isEqualTo(null) + // speedup, do not build - another MUI_HEADER test will test build + return true + } + }, { + tempDirCreated: projectDir => { + installerHeaderPath = path.join(projectDir, "build", "installerHeader.bmp") + return rename(path.join(projectDir, "installerHeader.bmp"), installerHeaderPath) + } + } + ) +}) + +test.ifNotCiOsx("nsis boring, MUI_HEADER as option", () => { + let installerHeaderPath: string | null = null + return assertPack("test-app-one", { + targets: Platform.WINDOWS.createTarget(["nsis"]), + devMetadata: { + build: { + nsis: { + oneClick: false, + installerHeader: "foo.bmp" + } + } + }, + effectiveOptionComputed: options => { + const defines = options[0] + assertThat(defines.MUI_HEADERIMAGE).isEqualTo(null) + assertThat(defines.MUI_HEADERIMAGE_BITMAP).isEqualTo(installerHeaderPath) + assertThat(defines.MUI_HEADERIMAGE_RIGHT).isEqualTo(null) + // test that we can build such installer + return false + } + }, { + tempDirCreated: projectDir => { + installerHeaderPath = path.join(projectDir, "foo.bmp") + return rename(path.join(projectDir, "installerHeader.bmp"), installerHeaderPath) + } + } + ) +}) // very slow test.ifWinCi("delta and msi", () => assertPack("test-app-one", { @@ -95,17 +161,17 @@ test("detect install-spinner, certificateFile/password", () => { targets: Platform.WINDOWS.createTarget(), platformPackagerFactory: (packager, platform, cleanupTasks) => platformPackager = new CheckingWinPackager(packager, cleanupTasks), devMetadata: { - build: { - win: { - certificatePassword: "pass", - } + build: { + win: { + certificatePassword: "pass", } } + } }, { tempDirCreated: it => { loadingGifPath = path.join(it, "build", "install-spinner.gif") return BluebirdPromise.all([ - move(path.join(it, "install-spinner.gif"), loadingGifPath), + rename(path.join(it, "install-spinner.gif"), loadingGifPath), modifyPackageJson(it, data => { data.build.win = { certificateFile: "secretFile", @@ -123,7 +189,7 @@ test("detect install-spinner, certificateFile/password", () => { }) test.ifNotCiOsx("icon < 256", (t: any) => t.throws(assertPack("test-app-one", platform(Platform.WINDOWS), { - tempDirCreated: projectDir => move(path.join(projectDir, "build", "incorrect.ico"), path.join(projectDir, "build", "icon.ico"), {clobber: true}) + tempDirCreated: projectDir => rename(path.join(projectDir, "build", "incorrect.ico"), path.join(projectDir, "build", "icon.ico")) }), /Windows icon size must be at least 256x256, please fix ".+/)) test.ifNotCiOsx("icon not an image", (t: any) => t.throws(assertPack("test-app-one", platform(Platform.WINDOWS), { @@ -137,7 +203,7 @@ test.ifOsx("custom icon", () => { platformPackagerFactory: (packager, platform, cleanupTasks) => platformPackager = new CheckingWinPackager(packager, cleanupTasks) }, { tempDirCreated: projectDir => BluebirdPromise.all([ - move(path.join(projectDir, "build", "icon.ico"), path.join(projectDir, "customIcon.ico")), + rename(path.join(projectDir, "build", "icon.ico"), path.join(projectDir, "customIcon.ico")), modifyPackageJson(projectDir, data => { data.build.win = { icon: "customIcon"