From 834434c74c93605d663660b518fd06141541e20a Mon Sep 17 00:00:00 2001 From: develar Date: Sat, 20 Aug 2016 18:17:14 +0200 Subject: [PATCH] feat(nsis): per-machine installer automatically removes per-user installation Closes #621, #672 --- .idea/dictionaries/develar.xml | 1 + docker/nsis-linux.sh | 8 --- docker/nsis.sh | 50 ------------------ docs/Options.md | 1 + src/metadata.ts | 5 ++ src/targets/nsis.ts | 17 +++--- templates/nsis/install.nsh | 23 +++++--- templates/nsis/oneClick.nsh | 1 - test/src/helpers/wine.ts | 2 +- test/src/nsisTest.ts | 95 +++++++++++++++++++++------------- 10 files changed, 91 insertions(+), 112 deletions(-) delete mode 100644 docker/nsis-linux.sh delete mode 100755 docker/nsis.sh diff --git a/.idea/dictionaries/develar.xml b/.idea/dictionaries/develar.xml index 1b86cb090f6..c044210381a 100644 --- a/.idea/dictionaries/develar.xml +++ b/.idea/dictionaries/develar.xml @@ -53,6 +53,7 @@ isbinaryfile joliet keyserver + lcid libappindicator libexec libgcrypt diff --git a/docker/nsis-linux.sh b/docker/nsis-linux.sh deleted file mode 100644 index cbdf0f7b67b..00000000000 --- a/docker/nsis-linux.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env bash - -# docker run -ti --rm -v $PWD:/tmp/nsis buildpack-deps:trusty - -mkdir -p /tmp/scons && curl -L http://prdownloads.sourceforge.net/scons/scons-local-2.5.0.tar.gz | tar -xz -C /tmp/scons -mkdir -p /tmp/nsis && curl -L https://sourceforge.net/projects/nsis/files/NSIS%203/3.0/nsis-3.0-src.tar.bz2/download | tar -xj -C /tmp/nsis --strip-components 1 && cd /tmp/nsis - -python /tmp/scons/scons.py STRIP=0 SKIPSTUBS=all SKIPPLUGINS=all SKIPUTILS=all SKIPMISC=all NSIS_CONFIG_CONST_DATA_PATH=no makensis \ No newline at end of file diff --git a/docker/nsis.sh b/docker/nsis.sh deleted file mode 100755 index a192f48203f..00000000000 --- a/docker/nsis.sh +++ /dev/null @@ -1,50 +0,0 @@ -#!/usr/bin/env bash -set -e - -rm -rf Docs -rm -rf NSIS.chm -rm -rf Examples -rm -rf Plugins/x86-ansi -rm -f makensisw.exe - -# nsProcess plugin -curl -L http://nsis.sourceforge.net/mediawiki/images/1/18/NsProcess.zip > a.zip -7za x a.zip -oa -mv a/Plugin/nsProcessW.dll Plugins/x86-unicode/nsProcess.dll -mv a/Include/nsProcess.nsh Include/nsProcess.nsh -unlink a.zip -rm -rf a - -# UAC plugin -curl -L http://nsis.sourceforge.net/mediawiki/images/8/8f/UAC.zip > a.zip -7za x a.zip -oa -mv a/Plugins/x86-unicode/UAC.dll Plugins/x86-unicode/UAC.dll -mv a/UAC.nsh Include/UAC.nsh -unlink a.zip -rm -rf a - -# WinShell -curl -L http://nsis.sourceforge.net/mediawiki/images/5/54/WinShell.zip > a.zip -7za x a.zip -oa -mv a/Plugins/x86-unicode/WinShell.dll Plugins/x86-unicode/WinShell.dll -unlink a.zip -rm -rf a - -# 7z -curl -L http://nsis.sourceforge.net/mediawiki/images/9/93/Nsis7z.zip > a.zip -7za x a.zip -oa -mv a/Plugins/x86-unicode/nsis7z.dll Plugins/x86-unicode/nsis7z.dll -unlink a.zip -rm -rf a - -# http://nsis.sourceforge.net/SpiderBanner_plug-in -curl -L http://nsis.sourceforge.net/mediawiki/images/4/4c/SpiderBanner_plugin.zip > a.zip -7za x a.zip -oa -mv a/Plugins/x86-unicode/SpiderBanner.dll Plugins/x86-unicode/SpiderBanner.dll -unlink a.zip -rm -rf a - - -dir=${PWD##*/} -rm -rf ../${dir}.7z -7za a -m0=lzma2 -mx=9 -mfb=64 -md=64m -ms=on ../${dir}.7z . diff --git a/docs/Options.md b/docs/Options.md index 4166ccccea6..c78d02ed73d 100644 --- a/docs/Options.md +++ b/docs/Options.md @@ -142,6 +142,7 @@ See [NSIS target notes](https://github.com/electron-userland/electron-builder/wi | installerHeaderIcon | *one-click installer only.* The path to header icon (above the progress bar), relative to the project directory. Defaults to `build/installerHeaderIcon.ico` or application icon. | include | The path to NSIS include script to customize installer. Defaults to `build/installer.nsh`. See [Custom NSIS script](https://github.com/electron-userland/electron-builder/wiki/NSIS#custom-nsis-script). | script | The path to NSIS script to customize installer. Defaults to `build/installer.nsi`. See [Custom NSIS script](https://github.com/electron-userland/electron-builder/wiki/NSIS#custom-nsis-script). +| language | * Hex LCID, defaults to `1033`(`English - United States`, see https://msdn.microsoft.com/en-au/goglobal/bb964664.aspx?f=255&MSPPError=-2147217396). ### `.build.linux` diff --git a/src/metadata.ts b/src/metadata.ts index 58f54449e4b..0424f6f6b98 100755 --- a/src/metadata.ts +++ b/src/metadata.ts @@ -416,6 +416,11 @@ export interface NsisOptions { The path to NSIS script to customize installer. Defaults to `build/installer.nsi`. See [Custom NSIS script](https://github.com/electron-userland/electron-builder/wiki/NSIS#custom-nsis-script). */ readonly script?: string | null + + /* + * Hex LCID, defaults to `1033`(`English - United States`, see https://msdn.microsoft.com/en-au/goglobal/bb964664.aspx?f=255&MSPPError=-2147217396). + */ + readonly language?: string | null } /* diff --git a/src/targets/nsis.ts b/src/targets/nsis.ts index a64b500442d..7d74bb0bac8 100644 --- a/src/targets/nsis.ts +++ b/src/targets/nsis.ts @@ -14,9 +14,9 @@ import semver = require("semver") //noinspection JSUnusedLocalSymbols const __awaiter = require("../util/awaiter") -const NSIS_VERSION = "3.0.0" +const NSIS_VERSION = "3.0.1" //noinspection SpellCheckingInspection -const NSIS_SHA2 = "7741089f3ca13de879f87836156ef785eab49844cacbeeabaeaefd1ade325ee7" +const NSIS_SHA2 = "23280f66c07c923da6f29a3c318377720c8ecd7af4de3755256d1ecf60d07f74" //noinspection SpellCheckingInspection const ELECTRON_BUILDER_NS_UUID = "50e065bc-3134-11e6-9bab-38c9862bdaf3" @@ -119,13 +119,14 @@ export default class NsisTarget extends Target { // Error: invalid VIProductVersion format, should be X.X.X.X // so, we must strip beta const parsedVersion = new semver.SemVer(appInfo.version) + const localeId = this.options.language || "1033" const versionKey = [ - `ProductName "${appInfo.productName}"`, - `ProductVersion "${appInfo.version}"`, - `CompanyName "${appInfo.companyName}"`, - `LegalCopyright "${appInfo.copyright}"`, - `FileDescription "${appInfo.description}"`, - `FileVersion "${appInfo.buildVersion}"`, + `/LANG=${localeId} ProductName "${appInfo.productName}"`, + `/LANG=${localeId} ProductVersion "${appInfo.version}"`, + `/LANG=${localeId} CompanyName "${appInfo.companyName}"`, + `/LANG=${localeId} LegalCopyright "${appInfo.copyright}"`, + `/LANG=${localeId} FileDescription "${appInfo.description}"`, + `/LANG=${localeId} FileVersion "${appInfo.buildVersion}"`, ] use(this.packager.platformSpecificBuildOptions.legalTrademarks, it => versionKey.push(`LegalTrademarks "${it}"`)) diff --git a/templates/nsis/install.nsh b/templates/nsis/install.nsh index d985a7d619b..572254bca58 100644 --- a/templates/nsis/install.nsh +++ b/templates/nsis/install.nsh @@ -23,6 +23,14 @@ ${if} $R0 != "" ExecWait "$R0 /S /KEEP_APP_DATA" ${endif} +${if} $installMode == "all" + # remove per-user installation + ReadRegStr $R0 HKEY_CURRENT_USER "${UNINSTALL_REGISTRY_KEY}" UninstallString + ${if} $R0 != "" + ExecWait "$R0 /S /KEEP_APP_DATA" + ${endif} +${endif} + RMDir /r $INSTDIR SetOutPath $INSTDIR @@ -68,12 +76,13 @@ WinShell::SetLnkAUMI "$desktopLink" "${APP_ID}" !insertmacro customInstall !endif -${IfNot} ${Silent} - !ifdef ONE_CLICK - # otherwise app window will be in backround - HideWindow - !ifdef RUN_AFTER_FINISH +!ifdef ONE_CLICK + !ifdef RUN_AFTER_FINISH + ${IfNot} ${Silent} + # otherwise app window will be in backround + HideWindow Call StartApp - !endif + ${EndIf} !endif -${EndIf} \ No newline at end of file + Quit +!endif diff --git a/templates/nsis/oneClick.nsh b/templates/nsis/oneClick.nsh index fed866a7a92..7c4178029ce 100644 --- a/templates/nsis/oneClick.nsh +++ b/templates/nsis/oneClick.nsh @@ -11,7 +11,6 @@ !endif !endif -AutoCloseWindow true !insertmacro MUI_PAGE_INSTFILES !ifdef BUILD_UNINSTALLER !insertmacro MUI_UNPAGE_INSTFILES diff --git a/test/src/helpers/wine.ts b/test/src/helpers/wine.ts index 5802419e369..de2dc99e715 100644 --- a/test/src/helpers/wine.ts +++ b/test/src/helpers/wine.ts @@ -36,7 +36,7 @@ export class WineManager { this.env = await this.winePreparePromise } - async exec(...args: Array) { + exec(...args: Array) { return exec("wine", args, {env: this.env}) } diff --git a/test/src/nsisTest.ts b/test/src/nsisTest.ts index 0564891e7c3..f0b8eed88fe 100644 --- a/test/src/nsisTest.ts +++ b/test/src/nsisTest.ts @@ -14,7 +14,22 @@ import { WineManager, diff } from "./helpers/wine" const __awaiter = require("out/util/awaiter") const nsisTarget = Platform.WINDOWS.createTarget(["nsis"]) -test("one-click", app({targets: nsisTarget}, {useTempDir: true, signed: true})) + +test("one-click", app({ + targets: Platform.WINDOWS.createTarget(["nsis"], Arch.ia32), + devMetadata: { + build: { + // wine creates incorrect filenames and registry entries for unicode, so, we use ASCII + productName: "TestApp", + } + } +}, { + useTempDir: true, + signed: true, + packed: (projectDir, outDir) => { + return doTest(outDir, true) + } +})) test.ifDevOrLinuxCi("perMachine, no run after finish", app({ targets: Platform.WINDOWS.createTarget(["nsis"], Arch.ia32), @@ -39,51 +54,56 @@ test.ifDevOrLinuxCi("perMachine, no run after finish", app({ let headerIconPath = path.join(projectDir, "build", "foo.ico") return copy(getTestAsset("headerIcon.ico"), headerIconPath) }, - packed: async (projectDir, outDir) => { - if (process.env.CI != null) { - return - } + packed: (projectDir, outDir) => { + return doTest(outDir, false) + }, +})) - const wine = new WineManager() - await wine.prepare() - const driveC = path.join(wine.wineDir, "drive_c") - const driveCWindows = path.join(wine.wineDir, "drive_c", "windows") - const perUserTempDir = path.join(wine.userDir, "Temp") - const walkFilter = (it: string) => { - return it !== driveCWindows && it !== perUserTempDir - } +async function doTest(outDir: string, perUser: boolean) { + if (process.env.DO_WINE !== "true") { + return BluebirdPromise.resolve() + } - function listFiles() { - return walk(driveC, null, walkFilter) - } + const wine = new WineManager() + await wine.prepare() + const driveC = path.join(wine.wineDir, "drive_c") + const driveCWindows = path.join(wine.wineDir, "drive_c", "windows") + const perUserTempDir = path.join(wine.userDir, "Temp") + const walkFilter = (it: string) => { + return it !== driveCWindows && it !== perUserTempDir + } - let fsBefore = await listFiles() + function listFiles() { + return walk(driveC, null, walkFilter) + } - await wine.exec(path.join(outDir, "TestApp Setup 1.1.0.exe"), "/S") + let fsBefore = await listFiles() - const appAsar = path.join(driveC, "Program Files", "TestApp", "resources", "app.asar") - assertThat(JSON.parse(extractFile(appAsar, "package.json").toString())).hasProperties({ - name: "TestApp" - }) + await wine.exec(path.join(outDir, "TestApp Setup 1.1.0.exe"), "/S") - let fsAfter = await listFiles() + const instDir = perUser ? path.join(wine.userDir, "Local Settings", "Application Data", "Programs") : path.join(driveC, "Program Files") + const appAsar = path.join(instDir, "TestApp", "resources", "app.asar") + assertThat(JSON.parse(extractFile(appAsar, "package.json").toString())).hasProperties({ + name: "TestApp" + }) - let fsChanges = diff(fsBefore, fsAfter, driveC) - assertThat(fsChanges.added).isEqualTo(nsisPerMachineInstall) - assertThat(fsChanges.deleted).isEqualTo([]) + let fsAfter = await listFiles() - // run installer again to test uninstall - const appDataFile = path.join(wine.userDir, "Application Data", "TestApp", "doNotDeleteMe") - await outputFile(appDataFile, "app data must be not removed") - fsBefore = await listFiles() - await wine.exec(path.join(outDir, "TestApp Setup 1.1.0.exe")) - fsAfter = await listFiles() + let fsChanges = diff(fsBefore, fsAfter, driveC) + assertThat(fsChanges.added).isEqualTo(nsisPerMachineInstall) + assertThat(fsChanges.deleted).isEqualTo([]) - fsChanges = diff(fsBefore, fsAfter, driveC) - assertThat(fsChanges.added).isEqualTo([]) - assertThat(fsChanges.deleted).isEqualTo([]) - }, -})) + // run installer again to test uninstall + const appDataFile = path.join(wine.userDir, "Application Data", "TestApp", "doNotDeleteMe") + await outputFile(appDataFile, "app data must be not removed") + fsBefore = await listFiles() + await wine.exec(path.join(outDir, "TestApp Setup 1.1.0.exe", "/S")) + fsAfter = await listFiles() + + fsChanges = diff(fsBefore, fsAfter, driveC) + assertThat(fsChanges.added).isEqualTo([]) + assertThat(fsChanges.deleted).isEqualTo([]) +} test.ifNotCiOsx("boring", app({ targets: nsisTarget, @@ -91,6 +111,7 @@ test.ifNotCiOsx("boring", app({ build: { nsis: { oneClick: false, + language: "1031", } } }