From 17c0a821355ef717312dbd79dffe88f9ca217bee Mon Sep 17 00:00:00 2001 From: develar Date: Sun, 14 Aug 2016 21:00:30 +0200 Subject: [PATCH] feat: NSIS sign uninstaller Closes #526 --- .idea/dictionaries/develar.xml | 1 + docs/Options.md | 1 + package.json | 2 +- src/metadata.ts | 5 + src/platformPackager.ts | 4 +- src/targets/LinuxTargetHelper.ts | 7 +- src/targets/nsis.ts | 78 +++++--- src/targets/squirrelWindows.ts | 15 +- src/winPackager.ts | 4 +- templates/nsis/FileAssociation.nsh | 281 ++++++++++++----------------- templates/nsis/boringInstaller.nsh | 149 +++++++-------- templates/nsis/common.nsh | 6 +- templates/nsis/installer.nsi | 210 +++++++++------------ templates/nsis/langs.nsh | 64 +++++++ templates/nsis/multiUser.nsh | 165 ++++------------- templates/nsis/multiUserUi.nsh | 103 +++++------ templates/nsis/oneClick.nsh | 40 +++- templates/nsis/uninstaller.nsh | 71 ++++++++ test/src/helpers/packTester.ts | 23 ++- test/src/helpers/runTests.ts | 15 +- test/src/nsisTest.ts | 32 ++-- test/src/winPackagerTest.ts | 32 ++-- 22 files changed, 637 insertions(+), 671 deletions(-) create mode 100644 templates/nsis/langs.nsh create mode 100644 templates/nsis/uninstaller.nsh diff --git a/.idea/dictionaries/develar.xml b/.idea/dictionaries/develar.xml index c18c70f381..68739d4e80 100644 --- a/.idea/dictionaries/develar.xml +++ b/.idea/dictionaries/develar.xml @@ -100,6 +100,7 @@ tsconfig udbz udro + unassociate unicon userprofile valuename diff --git a/docs/Options.md b/docs/Options.md index 73cd30c4bb..3a2da9eedb 100644 --- a/docs/Options.md +++ b/docs/Options.md @@ -167,6 +167,7 @@ NSIS only, [in progress](https://github.com/electron-userland/electron-builder/i | --- | --- | **ext** | The extension (minus the leading period). e.g. `png` | **name** | The name. e.g. `PNG` +| description | *windows-only.* The description. ## `.directories` diff --git a/package.json b/package.json index 7e2df44c45..16baf3314c 100644 --- a/package.json +++ b/package.json @@ -76,7 +76,7 @@ "hosted-git-info": "^2.1.5", "image-size": "^0.5.0", "isbinaryfile": "^3.0.1", - "lodash.template": "^4.3.0", + "lodash.template": "^4.4.0", "mime": "^1.3.4", "minimatch": "^3.0.3", "normalize-package-data": "^2.3.5", diff --git a/src/metadata.ts b/src/metadata.ts index 29c1b1005a..c5b933b535 100755 --- a/src/metadata.ts +++ b/src/metadata.ts @@ -488,6 +488,11 @@ export interface FileAssociation { The name. e.g. `PNG` */ readonly name: string + + /* + *windows-only.* The description. + */ + readonly description?: string } /* diff --git a/src/platformPackager.ts b/src/platformPackager.ts index ccf6e70fd0..819a7af4fb 100644 --- a/src/platformPackager.ts +++ b/src/platformPackager.ts @@ -122,9 +122,7 @@ export abstract class PlatformPackager return options == null ? Object.create(null) : options } - createTargets(targets: Array, mapper: (name: string, factory: (outDir: string) => Target) => void, cleanupTasks: Array<() => Promise>): void { - throw new Error("not implemented") - } + abstract createTargets(targets: Array, mapper: (name: string, factory: (outDir: string) => Target) => void, cleanupTasks: Array<() => Promise>): void protected getCscPassword(): string { const password = this.options.cscKeyPassword || process.env.CSC_KEY_PASSWORD diff --git a/src/targets/LinuxTargetHelper.ts b/src/targets/LinuxTargetHelper.ts index 63a0935ef7..ff7f3ab294 100644 --- a/src/targets/LinuxTargetHelper.ts +++ b/src/targets/LinuxTargetHelper.ts @@ -19,11 +19,8 @@ export class LinuxTargetHelper { constructor(private packager: PlatformPackager, cleanupTasks: Array<() => Promise>) { const tempDir = path.join(tmpdir(), getTempName("electron-builder-linux")) - this.tempDirPromise = emptyDir(tempDir) - .then(() => { - cleanupTasks.push(() => remove(tempDir)) - return tempDir - }) + this.tempDirPromise = emptyDir(tempDir).thenReturn(tempDir) + cleanupTasks.push(() => remove(tempDir)) this.icons = this.computeDesktopIcons() } diff --git a/src/targets/nsis.ts b/src/targets/nsis.ts index 7092c1876a..33b067d5b1 100644 --- a/src/targets/nsis.ts +++ b/src/targets/nsis.ts @@ -1,15 +1,16 @@ import { WinPackager } from "../winPackager" import { Arch, NsisOptions } from "../metadata" -import { debug, doSpawn, handleProcess, use } from "../util/util" +import { exec, debug, doSpawn, handleProcess, use, getTempName } from "../util/util" import * as path from "path" import { Promise as BluebirdPromise } from "bluebird" import { getBinFromBintray } from "../util/binDownload" import { v5 as uuid5 } from "uuid-1345" import { Target } from "../platformPackager" import { archiveApp } from "./archive" -import { subTask, task } from "../util/log" -import { unlink, readFile } from "fs-extra-p" +import { subTask, task, log } from "../util/log" +import { unlink, readFile, remove } from "fs-extra-p" import semver = require("semver") +import { tmpdir } from "os" //noinspection JSUnusedLocalSymbols const __awaiter = require("../util/awaiter") @@ -28,7 +29,9 @@ export default class NsisTarget extends Target { private archs: Map> = new Map() - constructor(private packager: WinPackager, private outDir: string) { + private readonly nsisTemplatesDir = path.join(__dirname, "..", "..", "templates", "nsis") + + constructor(private packager: WinPackager, private outDir: string, private cleanupTasks: Array<() => Promise>) { super("nsis") this.options = packager.info.devMetadata.build.nsis || Object.create(null) @@ -52,8 +55,8 @@ export default class NsisTarget extends Target { const iconPath = await packager.getIconPath() const appInfo = packager.appInfo const version = appInfo.version - const installerPath = path.join(this.outDir, `${appInfo.productFilename} Setup ${version}.exe`) + const installerPath = path.join(this.outDir, `${appInfo.productFilename} Setup ${version}.exe`) const guid = this.options.guid || await BluebirdPromise.promisify(uuid5)({namespace: ELECTRON_BUILDER_NS_UUID, name: appInfo.id}) const defines: any = { APP_ID: appInfo.id, @@ -122,9 +125,7 @@ export default class NsisTarget extends Target { const commands: any = { OutFile: `"${installerPath}"`, - // LoadLanguageFile: '"${NSISDIR}/Contrib/Language files/English.nlf"', VIProductVersion: `${parsedVersion.major}.${parsedVersion.minor}.${parsedVersion.patch}.${appInfo.buildNumber || "0"}`, - // VIFileVersion: packager.appInfo.buildVersion, VIAddVersionKey: versionKey, } @@ -151,7 +152,29 @@ export default class NsisTarget extends Target { return } - await subTask(`Executing makensis`, this.executeMakensis(defines, commands)) + const customScriptPath = await this.getResource(this.options.script, "installer.nsi") + const script = await readFile(customScriptPath || path.join(this.nsisTemplatesDir, "installer.nsi"), "utf8") + + if (customScriptPath == null) { + const uninstallerPath = path.join(tmpdir(), `${getTempName("electron-builder")}.exe`) + this.cleanupTasks.push(() => remove(uninstallerPath)) + + defines.BUILD_UNINSTALLER = null + defines.UNINSTALLER_OUT_FILE = path.win32.join("Z:", uninstallerPath) + await subTask(`Executing makensis — uninstaller`, this.executeMakensis(defines, commands, false, script)) + const isWin = process.platform === "win32" + await exec(isWin ? installerPath : "wine", isWin ? [] : [installerPath]) + await packager.sign(uninstallerPath) + + delete defines.BUILD_UNINSTALLER + // platform-specific path, not wine + defines.UNINSTALLER_OUT_FILE = uninstallerPath + } + else { + log("Custom NSIS script is used - uninstaller is not signed by electron-builder") + } + + await subTask(`Executing makensis — installer`, this.executeMakensis(defines, commands, true, script)) await packager.sign(installerPath) this.packager.dispatchArtifactCreated(installerPath, `${appInfo.name}-Setup-${version}.exe`) @@ -172,7 +195,7 @@ export default class NsisTarget extends Target { return null } - private async executeMakensis(defines: any, commands: any) { + private async executeMakensis(defines: any, commands: any, isInstaller: boolean, originalScript: string) { const args: Array = ["-WX"] for (let name of Object.keys(defines)) { const value = defines[name] @@ -196,18 +219,18 @@ export default class NsisTarget extends Target { } } - const nsisTemplatesDir = path.join(__dirname, "..", "..", "templates", "nsis") args.push("-") const binDir = process.platform === "darwin" ? "mac" : (process.platform === "win32" ? "Bin" : "linux") const nsisPath = await nsisPathPromise const packager = this.packager + // CFBundleTypeName + // https://developer.apple.com/library/ios/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html#//apple_ref/doc/uid/20001431-101685 + // CFBundleTypeExtensions const fileAssociations = asArray(packager.devMetadata.build.fileAssociations).concat(asArray(packager.platformSpecificBuildOptions.fileAssociations)) - let registerFileAssociationsScript = "" - let unregisterFileAssociationsScript = "" - let script = await readFile((await this.getResource(this.options.script, "installer.nsi")) || path.join(nsisTemplatesDir, "installer.nsi"), "utf8") + let script = originalScript const customInclude = await this.getResource(this.options.include, "installer.nsh") if (customInclude != null) { script = `!include "${customInclude}"\n!addincludedir "${this.packager.buildResourcesDir}"\n${script}` @@ -215,18 +238,25 @@ export default class NsisTarget extends Target { if (fileAssociations.length !== 0) { script = "!include FileAssociation.nsh\n" + script - for (let item of fileAssociations) { - registerFileAssociationsScript += '${RegisterExtension} "$INSTDIR\\${APP_EXECUTABLE_FILENAME}" ' + `"${normalizeExt(item.ext)}" "${item.name}"\n` + if (isInstaller) { + let registerFileAssociationsScript = "" + for (let item of fileAssociations) { + const icon = '"$INSTDIR\\${APP_EXECUTABLE_FILENAME},0"' + const commandText = `"Open with ${this.packager.appInfo.productName}"` + const command = '"$INSTDIR\\${APP_EXECUTABLE_FILENAME} $\\"%1$\\""' + registerFileAssociationsScript += ` !insertmacro APP_ASSOCIATE "${normalizeExt(item.ext)}" "${item.name}" "${item.description || ""}" ${icon} ${commandText} ${command}\n` + } + script = `!macro registerFileAssociations\n${registerFileAssociationsScript}!macroend\n${script}` } - - for (let item of fileAssociations) { - unregisterFileAssociationsScript += "${UnRegisterExtension} " + `"${normalizeExt(item.ext)}" "${item.name}"\n` + else { + let unregisterFileAssociationsScript = "" + for (let item of fileAssociations) { + unregisterFileAssociationsScript += ` !insertmacro APP_UNASSOCIATE "${normalizeExt(item.ext)}" "${item.name}"\n` + } + script = `!macro unregisterFileAssociations\n${unregisterFileAssociationsScript}!macroend\n${script}` } } - script = script.replace("!insertmacro registerFileAssociations", registerFileAssociationsScript) - script = script.replace("!insertmacro unregisterFileAssociations", unregisterFileAssociationsScript) - if (debug.enabled) { process.stdout.write("\n\nNSIS script:\n\n" + script + "\n\n---\nEnd of NSIS script.\n\n") } @@ -236,7 +266,7 @@ export default class NsisTarget extends Target { const childProcess = doSpawn(command, args, { // we use NSIS_CONFIG_CONST_DATA_PATH=no to build makensis on Linux, but in any case it doesn't use stubs as MacOS/Windows version, so, we explicitly set NSISDIR env: Object.assign({}, process.env, {NSISDIR: nsisPath}), - cwd: nsisTemplatesDir, + cwd: this.nsisTemplatesDir, }, true) handleProcess("close", childProcess, command, resolve, reject) @@ -245,9 +275,9 @@ export default class NsisTarget extends Target { } } -// nsis — add leading dot, mac — remove leading dot +// remove leading dot function normalizeExt(ext: string) { - return ext.startsWith(".") ? ext : `.${ext}` + return ext.startsWith(".") ? ext.substring(1) : ext } function asArray(v: n | T | Array): Array { diff --git a/src/targets/squirrelWindows.ts b/src/targets/squirrelWindows.ts index deb42b094d..3e2f502579 100644 --- a/src/targets/squirrelWindows.ts +++ b/src/targets/squirrelWindows.ts @@ -18,7 +18,7 @@ const SW_VERSION = "1.4.4" const SW_SHA2 = "98e1d81c80d7afc1bcfb37f3b224dc4f761088506b9c28ccd72d1cf8752853ba" export default class SquirrelWindowsTarget extends Target { - constructor(private packager: WinPackager) { + constructor(private packager: WinPackager, private cleanupTasks: Array<() => Promise>) { super("squirrel") } @@ -38,17 +38,8 @@ export default class SquirrelWindowsTarget extends Target { const stageDir = path.join(tmpdir(), getTempName("squirrel-windows-builder")) await emptyDir(stageDir) - try { - await buildInstaller(distOptions, installerOutDir, stageDir, setupFileName, this.packager, appOutDir) - } - finally { - try { - await remove(stageDir) - } - catch (e) { - // ignore - } - } + this.cleanupTasks.push(() => remove(stageDir)) + await buildInstaller(distOptions, installerOutDir, stageDir, setupFileName, this.packager, appOutDir) this.packager.dispatchArtifactCreated(path.join(installerOutDir, setupFileName), `${appInfo.name}-Setup-${version}${archSuffix}.exe`) diff --git a/src/winPackager.ts b/src/winPackager.ts index 4e8c1a08d4..5456324cd4 100644 --- a/src/winPackager.ts +++ b/src/winPackager.ts @@ -75,13 +75,13 @@ export class WinPackager extends PlatformPackager { if (name === DEFAULT_TARGET || name === "squirrel") { mapper("squirrel", () => { const targetClass: typeof SquirrelWindowsTarget = require("./targets/squirrelWindows").default - return new targetClass(this) + return new targetClass(this, cleanupTasks) }) } else if (name === "nsis") { mapper(name, outDir => { const targetClass: typeof NsisTarget = require("./targets/nsis").default - return new targetClass(this, outDir) + return new targetClass(this, outDir, cleanupTasks) }) } else { diff --git a/templates/nsis/FileAssociation.nsh b/templates/nsis/FileAssociation.nsh index fa4885e381..711be1a750 100644 --- a/templates/nsis/FileAssociation.nsh +++ b/templates/nsis/FileAssociation.nsh @@ -1,176 +1,117 @@ -/* -_____________________________________________________________________________ - File Association -_____________________________________________________________________________ - Based on code taken from http://nsis.sourceforge.net/File_Association - Usage in script: - 1. !include "FileAssociation.nsh" - 2. [Section|Function] - ${FileAssociationFunction} "Param1" "Param2" "..." $var - [SectionEnd|FunctionEnd] - FileAssociationFunction=[RegisterExtension|UnRegisterExtension] -_____________________________________________________________________________ - ${RegisterExtension} "[executable]" "[extension]" "[description]" -"[executable]" ; executable which opens the file format - ; -"[extension]" ; extension, which represents the file format to open - ; -"[description]" ; description for the extension. This will be display in Windows Explorer. - ; - ${UnRegisterExtension} "[extension]" "[description]" -"[extension]" ; extension, which represents the file format to open - ; -"[description]" ; description for the extension. This will be display in Windows Explorer. - ; -_____________________________________________________________________________ - Macros -_____________________________________________________________________________ - Change log window verbosity (default: 3=no script) - Example: - !include "FileAssociation.nsh" - !insertmacro RegisterExtension - ${FileAssociation_VERBOSE} 4 # all verbosity - !insertmacro UnRegisterExtension - ${FileAssociation_VERBOSE} 3 # no script -*/ - - -!ifndef FileAssociation_INCLUDED -!define FileAssociation_INCLUDED - -!include Util.nsh - -!verbose push -!verbose 3 -!ifndef _FileAssociation_VERBOSE - !define _FileAssociation_VERBOSE 3 -!endif -!verbose ${_FileAssociation_VERBOSE} -!define FileAssociation_VERBOSE `!insertmacro FileAssociation_VERBOSE` -!verbose pop - -!macro FileAssociation_VERBOSE _VERBOSE - !verbose push - !verbose 3 - !undef _FileAssociation_VERBOSE - !define _FileAssociation_VERBOSE ${_VERBOSE} - !verbose pop -!macroend - - - -!macro RegisterExtensionCall _EXECUTABLE _EXTENSION _DESCRIPTION - !verbose push - !verbose ${_FileAssociation_VERBOSE} - Push `${_DESCRIPTION}` - Push `${_EXTENSION}` - Push `${_EXECUTABLE}` - ${CallArtificialFunction} RegisterExtension_ - !verbose pop -!macroend - -!macro UnRegisterExtensionCall _EXTENSION _DESCRIPTION - !verbose push - !verbose ${_FileAssociation_VERBOSE} - Push `${_EXTENSION}` - Push `${_DESCRIPTION}` - ${CallArtificialFunction} UnRegisterExtension_ - !verbose pop +; fileassoc.nsh +; File association helper macros +; Written by Saivert +; +; Features automatic backup system and UPDATEFILEASSOC macro for +; shell change notification. +; +; |> How to use <| +; To associate a file with an application so you can double-click it in explorer, use +; the APP_ASSOCIATE macro like this: +; +; Example: +; !insertmacro APP_ASSOCIATE "txt" "myapp.textfile" "Description of txt files" \ +; "$INSTDIR\myapp.exe,0" "Open with myapp" "$INSTDIR\myapp.exe $\"%1$\"" +; +; Never insert the APP_ASSOCIATE macro multiple times, it is only ment +; to associate an application with a single file and using the +; the "open" verb as default. To add more verbs (actions) to a file +; use the APP_ASSOCIATE_ADDVERB macro. +; +; Example: +; !insertmacro APP_ASSOCIATE_ADDVERB "myapp.textfile" "edit" "Edit with myapp" \ +; "$INSTDIR\myapp.exe /edit $\"%1$\"" +; +; To have access to more options when registering the file association use the +; APP_ASSOCIATE_EX macro. Here you can specify the verb and what verb is to be the +; standard action (default verb). +; +; And finally: To remove the association from the registry use the APP_UNASSOCIATE +; macro. Here is another example just to wrap it up: +; !insertmacro APP_UNASSOCIATE "txt" "myapp.textfile" +; +; |> Note <| +; When defining your file class string always use the short form of your application title +; then a period (dot) and the type of file. This keeps the file class sort of unique. +; Examples: +; Winamp.Playlist +; NSIS.Script +; Photoshop.JPEGFile +; +; |> Tech info <| +; The registry key layout for a file association is: +; HKEY_CLASSES_ROOT +; = <"description"> +; shell +; = <"menu-item text"> +; command = <"command string"> +; + +!macro APP_ASSOCIATE EXT FILECLASS DESCRIPTION ICON COMMANDTEXT COMMAND + ; Backup the previously associated file class + ReadRegStr $R0 HKCR ".${EXT}" "" + WriteRegStr HKCR ".${EXT}" "${FILECLASS}_backup" "$R0" + + WriteRegStr HKCR ".${EXT}" "" "${FILECLASS}" + + WriteRegStr HKCR "${FILECLASS}" "" `${DESCRIPTION}` + WriteRegStr HKCR "${FILECLASS}\DefaultIcon" "" `${ICON}` + WriteRegStr HKCR "${FILECLASS}\shell" "" "open" + WriteRegStr HKCR "${FILECLASS}\shell\open" "" `${COMMANDTEXT}` + WriteRegStr HKCR "${FILECLASS}\shell\open\command" "" `${COMMAND}` !macroend - - - -!define RegisterExtension `!insertmacro RegisterExtensionCall` -!define un.RegisterExtension `!insertmacro RegisterExtensionCall` - -!macro RegisterExtension + +!macro APP_ASSOCIATE_EX EXT FILECLASS DESCRIPTION ICON VERB DEFAULTVERB SHELLNEW COMMANDTEXT COMMAND + ; Backup the previously associated file class + ReadRegStr $R0 HKCR ".${EXT}" "" + WriteRegStr HKCR ".${EXT}" "${FILECLASS}_backup" "$R0" + + WriteRegStr HKCR ".${EXT}" "" "${FILECLASS}" + StrCmp "${SHELLNEW}" "0" +2 + WriteRegStr HKCR ".${EXT}\ShellNew" "NullFile" "" + + WriteRegStr HKCR "${FILECLASS}" "" `${DESCRIPTION}` + WriteRegStr HKCR "${FILECLASS}\DefaultIcon" "" `${ICON}` + WriteRegStr HKCR "${FILECLASS}\shell" "" `${DEFAULTVERB}` + WriteRegStr HKCR "${FILECLASS}\shell\${VERB}" "" `${COMMANDTEXT}` + WriteRegStr HKCR "${FILECLASS}\shell\${VERB}\command" "" `${COMMAND}` !macroend - -!macro un.RegisterExtension + +!macro APP_ASSOCIATE_ADDVERB FILECLASS VERB COMMANDTEXT COMMAND + WriteRegStr HKCR "${FILECLASS}\shell\${VERB}" "" `${COMMANDTEXT}` + WriteRegStr HKCR "${FILECLASS}\shell\${VERB}\command" "" `${COMMAND}` !macroend - -!macro RegisterExtension_ - !verbose push - !verbose ${_FileAssociation_VERBOSE} - - Exch $R2 ;exe - Exch - Exch $R1 ;ext - Exch - Exch 2 - Exch $R0 ;desc - Exch 2 - Push $0 - Push $1 - - ReadRegStr $1 HKCR $R1 "" ; read current file association - StrCmp "$1" "" NoBackup ; is it empty - StrCmp "$1" "$R0" NoBackup ; is it our own - WriteRegStr HKCR $R1 "backup_val" "$1" ; backup current value -NoBackup: - WriteRegStr HKCR $R1 "" "$R0" ; set our file association - - ReadRegStr $0 HKCR $R0 "" - StrCmp $0 "" 0 Skip - WriteRegStr HKCR "$R0" "" "$R0" - WriteRegStr HKCR "$R0\shell" "" "open" - WriteRegStr HKCR "$R0\DefaultIcon" "" "$R2,0" -Skip: - WriteRegStr HKCR "$R0\shell\open\command" "" '"$R2" "%1"' - WriteRegStr HKCR "$R0\shell\edit" "" "Edit $R0" - WriteRegStr HKCR "$R0\shell\edit\command" "" '"$R2" "%1"' - - Pop $1 - Pop $0 - Pop $R2 - Pop $R1 - Pop $R0 - - !verbose pop + +!macro APP_ASSOCIATE_REMOVEVERB FILECLASS VERB + DeleteRegKey HKCR `${FILECLASS}\shell\${VERB}` !macroend - - - -!define UnRegisterExtension `!insertmacro UnRegisterExtensionCall` -!define un.UnRegisterExtension `!insertmacro UnRegisterExtensionCall` - -!macro UnRegisterExtension + + +!macro APP_UNASSOCIATE EXT FILECLASS + ; Backup the previously associated file class + ReadRegStr $R0 HKCR ".${EXT}" `${FILECLASS}_backup` + WriteRegStr HKCR ".${EXT}" "" "$R0" + + DeleteRegKey HKCR `${FILECLASS}` !macroend - -!macro un.UnRegisterExtension + +!macro APP_ASSOCIATE_GETFILECLASS OUTPUT EXT + ReadRegStr ${OUTPUT} HKCR ".${EXT}" "" !macroend - -!macro UnRegisterExtension_ - !verbose push - !verbose ${_FileAssociation_VERBOSE} - - Exch $R1 ;desc - Exch - Exch $R0 ;ext - Exch - Push $0 - Push $1 - - ReadRegStr $1 HKCR $R0 "" - StrCmp $1 $R1 0 NoOwn ; only do this if we own it - ReadRegStr $1 HKCR $R0 "backup_val" - StrCmp $1 "" 0 Restore ; if backup="" then delete the whole key - DeleteRegKey HKCR $R0 - Goto NoOwn - -Restore: - WriteRegStr HKCR $R0 "" $1 - DeleteRegValue HKCR $R0 "backup_val" - DeleteRegKey HKCR $R1 ;Delete key with association name settings - -NoOwn: - - Pop $1 - Pop $0 - Pop $R1 - Pop $R0 - - !verbose pop -!macroend - -!endif # !FileAssociation_INCLUDED \ No newline at end of file + + +; !defines for use with SHChangeNotify +!ifdef SHCNE_ASSOCCHANGED +!undef SHCNE_ASSOCCHANGED +!endif +!define SHCNE_ASSOCCHANGED 0x08000000 +!ifdef SHCNF_FLUSH +!undef SHCNF_FLUSH +!endif +!define SHCNF_FLUSH 0x1000 + +!macro UPDATEFILEASSOC +; Using the system.dll plugin to call the SHChangeNotify Win32 API function so we +; can update the shell. + System::Call "shell32::SHChangeNotify(i,i,i,i) (${SHCNE_ASSOCCHANGED}, ${SHCNF_FLUSH}, 0, 0)" +!macroend \ No newline at end of file diff --git a/templates/nsis/boringInstaller.nsh b/templates/nsis/boringInstaller.nsh index a221621ff4..8ba7334783 100644 --- a/templates/nsis/boringInstaller.nsh +++ b/templates/nsis/boringInstaller.nsh @@ -1,87 +1,78 @@ -# http://nsis.sourceforge.net/Run_an_application_shortcut_after_an_install !include multiUserUi.nsh -Function StartApp - !insertmacro UAC_AsUser_ExecShell "" "$SMPROGRAMS\${PRODUCT_FILENAME}.lnk" "" "" "" -FunctionEnd +!ifndef BUILD_UNINSTALLER + Function StartApp + !insertmacro UAC_AsUser_ExecShell "" "$SMPROGRAMS\${PRODUCT_FILENAME}.lnk" "" "" "" + FunctionEnd -!define MUI_FINISHPAGE_RUN -!define MUI_FINISHPAGE_RUN_FUNCTION "StartApp" + Function GuiInit + !insertmacro UAC_PageElevation_OnGuiInit + FunctionEnd -!define MUI_CUSTOMFUNCTION_GUIINIT GuiInit + !define MUI_FINISHPAGE_RUN + !define MUI_FINISHPAGE_RUN_FUNCTION "StartApp" -!insertmacro MULTIUSER_PAGE_INSTALLMODE -!insertmacro MUI_PAGE_INSTFILES -!insertmacro MUI_PAGE_FINISH + !define MUI_CUSTOMFUNCTION_GUIINIT GuiInit -# uninstall pages -!insertmacro MUI_UNPAGE_INSTFILES + !insertmacro PAGE_INSTALL_MODE + !insertmacro MUI_PAGE_INSTFILES + !insertmacro MUI_PAGE_FINISH +!else + !insertmacro PAGE_INSTALL_MODE + !insertmacro MUI_UNPAGE_INSTFILES +!endif -!insertmacro MUI_LANGUAGE "English" -!insertmacro MUI_LANGUAGE "German" -!insertmacro MUI_LANGUAGE "French" -!insertmacro MUI_LANGUAGE "Spanish" -!insertmacro MUI_LANGUAGE "SpanishInternational" -!insertmacro MUI_LANGUAGE "SimpChinese" -!insertmacro MUI_LANGUAGE "TradChinese" -!insertmacro MUI_LANGUAGE "Japanese" -#!insertmacro MUI_LANGUAGE "Korean" -!insertmacro MUI_LANGUAGE "Italian" -!insertmacro MUI_LANGUAGE "Dutch" -#!insertmacro MUI_LANGUAGE "Danish" -#!insertmacro MUI_LANGUAGE "Swedish" -#!insertmacro MUI_LANGUAGE "Norwegian" -#!insertmacro MUI_LANGUAGE "NorwegianNynorsk" -#!insertmacro MUI_LANGUAGE "Finnish" -#!insertmacro MUI_LANGUAGE "Greek" -!insertmacro MUI_LANGUAGE "Russian" -#!insertmacro MUI_LANGUAGE "Portuguese" -#!insertmacro MUI_LANGUAGE "PortugueseBR" -#!insertmacro MUI_LANGUAGE "Polish" -#!insertmacro MUI_LANGUAGE "Ukrainian" -!insertmacro MUI_LANGUAGE "Czech" -#!insertmacro MUI_LANGUAGE "Slovak" -#!insertmacro MUI_LANGUAGE "Croatian" -#!insertmacro MUI_LANGUAGE "Bulgarian" -#!insertmacro MUI_LANGUAGE "Hungarian" -#!insertmacro MUI_LANGUAGE "Thai" -#!insertmacro MUI_LANGUAGE "Romanian" -#!insertmacro MUI_LANGUAGE "Latvian" -#!insertmacro MUI_LANGUAGE "Macedonian" -#!insertmacro MUI_LANGUAGE "Estonian" -#!insertmacro MUI_LANGUAGE "Turkish" -#!insertmacro MUI_LANGUAGE "Lithuanian" -#!insertmacro MUI_LANGUAGE "Slovenian" -#!insertmacro MUI_LANGUAGE "Serbian" -#!insertmacro MUI_LANGUAGE "SerbianLatin" -#!insertmacro MUI_LANGUAGE "Arabic" -#!insertmacro MUI_LANGUAGE "Farsi" -#!insertmacro MUI_LANGUAGE "Hebrew" -#!insertmacro MUI_LANGUAGE "Indonesian" -#!insertmacro MUI_LANGUAGE "Mongolian" -#!insertmacro MUI_LANGUAGE "Luxembourgish" -#!insertmacro MUI_LANGUAGE "Albanian" -#!insertmacro MUI_LANGUAGE "Breton" -#!insertmacro MUI_LANGUAGE "Belarusian" -#!insertmacro MUI_LANGUAGE "Icelandic" -#!insertmacro MUI_LANGUAGE "Malay" -#!insertmacro MUI_LANGUAGE "Bosnian" -#!insertmacro MUI_LANGUAGE "Kurdish" -#!insertmacro MUI_LANGUAGE "Irish" -#!insertmacro MUI_LANGUAGE "Uzbek" -#!insertmacro MUI_LANGUAGE "Galician" -#!insertmacro MUI_LANGUAGE "Afrikaans" -#!insertmacro MUI_LANGUAGE "Catalan" -#!insertmacro MUI_LANGUAGE "Esperanto" -#!insertmacro MUI_LANGUAGE "Asturian" -#!insertmacro MUI_LANGUAGE "Basque" -#!insertmacro MUI_LANGUAGE "Pashto" -#!insertmacro MUI_LANGUAGE "ScotsGaelic" -#!insertmacro MUI_LANGUAGE "Georgian" -#!insertmacro MUI_LANGUAGE "Vietnamese" -#!insertmacro MUI_LANGUAGE "Welsh" -#!insertmacro MUI_LANGUAGE "Armenian" +!macro initMultiUser UNINSTALLER_FUNCPREFIX + !insertmacro UAC_PageElevation_OnInit -Function GuiInit - !insertmacro UAC_PageElevation_OnGuiInit -FunctionEnd \ No newline at end of file + ${If} ${UAC_IsInnerInstance} + ${AndIfNot} ${UAC_IsAdmin} + # special return value for outer instance so it knows we did not have admin rights + SetErrorLevel 0x666666 + Quit + ${EndIf} + + !ifndef MULTIUSER_INIT_TEXT_ADMINREQUIRED + !define MULTIUSER_INIT_TEXT_ADMINREQUIRED "$(^Caption) requires administrator privileges." + !endif + + !ifndef MULTIUSER_INIT_TEXT_POWERREQUIRED + !define MULTIUSER_INIT_TEXT_POWERREQUIRED "$(^Caption) requires at least Power User privileges." + !endif + + !ifndef MULTIUSER_INIT_TEXT_ALLUSERSNOTPOSSIBLE + !define MULTIUSER_INIT_TEXT_ALLUSERSNOTPOSSIBLE "Your user account does not have sufficient privileges to install $(^Name) for all users of this computer." + !endif + + # checks registry for previous installation path (both for upgrading, reinstall, or uninstall) + StrCpy $hasPerMachineInstallation "0" + StrCpy $hasPerUserInstallation "0" + + # set installation mode to setting from a previous installation + ReadRegStr $perMachineInstallationFolder HKLM "${INSTALL_REGISTRY_KEY}" InstallLocation + ${if} $perMachineInstallationFolder != "" + StrCpy $hasPerMachineInstallation "1" + ${endif} + + ReadRegStr $perUserInstallationFolder HKCU "${INSTALL_REGISTRY_KEY}" InstallLocation + ${if} $perUserInstallationFolder != "" + StrCpy $hasPerUserInstallation "1" + ${endif} + + ${if} $hasPerUserInstallation == "1" + ${andif} $hasPerMachineInstallation == "0" + Call ${UNINSTALLER_FUNCPREFIX}installMode.CurrentUser + ${elseif} $hasPerUserInstallation == "0" + ${andif} $hasPerMachineInstallation == "1" + Call ${UNINSTALLER_FUNCPREFIX}installMode.AllUsers + ${else} + # if there is no installation, or there is both per-user and per-machine + !ifdef INSTALL_MODE_PER_ALL_USERS + Call ${UNINSTALLER_FUNCPREFIX}installMode.AllUsers + !else + Call ${UNINSTALLER_FUNCPREFIX}installMode.CurrentUser + !endif + ${endif} +!macroend + +!include "langs.nsh" \ No newline at end of file diff --git a/templates/nsis/common.nsh b/templates/nsis/common.nsh index d28e84ee16..b81f2ca248 100644 --- a/templates/nsis/common.nsh +++ b/templates/nsis/common.nsh @@ -3,13 +3,13 @@ BrandingText "${PRODUCT_NAME} ${VERSION}" ShowInstDetails nevershow -ShowUninstDetails nevershow +!ifdef BUILD_UNINSTALLER + ShowUninstDetails nevershow +!endif FileBufSize 64 Name "${PRODUCT_NAME}" Unicode true -!define MULTIUSER_INSTALLMODE_INSTDIR "${APP_GUID}" - !define APP_EXECUTABLE_FILENAME "${PRODUCT_FILENAME}.exe" !define UNINSTALL_FILENAME "Uninstall ${PRODUCT_FILENAME}.exe" diff --git a/templates/nsis/installer.nsi b/templates/nsis/installer.nsi index 8ba9571aa2..d2ae571ef7 100644 --- a/templates/nsis/installer.nsi +++ b/templates/nsis/installer.nsi @@ -16,151 +16,113 @@ Var startMenuLink Var desktopLink -Function .onInit - !insertmacro check64BitAndSetRegView - !insertmacro initMultiUser "" "" +!ifdef BUILD_UNINSTALLER + SilentInstall silent +!endif - !ifdef ONE_CLICK - !insertmacro ALLOW_ONLY_ONE_INSTALLER_INSTACE +Function .onInit + !ifdef BUILD_UNINSTALLER + WriteUninstaller "${UNINSTALLER_OUT_FILE}" + # avoid exit code 2 + SetErrorLevel 0 + Quit !else - ${IfNot} ${UAC_IsInnerInstance} - !insertmacro ALLOW_ONLY_ONE_INSTALLER_INSTACE - ${EndIf} - !endif - - InitPluginsDir - - SetCompress off - !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}" - - !ifdef HEADER_ICO - File /oname=$PLUGINSDIR\installerHeaderico.ico "${HEADER_ICO}" - !endif - - !ifmacrodef customInit - !insertmacro customInit - !endif -FunctionEnd - -Section "install" - ${IfNot} ${Silent} - SetDetailsPrint none + !insertmacro check64BitAndSetRegView + !insertmacro initMultiUser "" !ifdef ONE_CLICK - !ifdef HEADER_ICO - SpiderBanner::Show /MODERN /ICON "$PLUGINSDIR\installerHeaderico.ico" - !else - SpiderBanner::Show /MODERN - !endif + !insertmacro ALLOW_ONLY_ONE_INSTALLER_INSTACE + !else + ${IfNot} ${UAC_IsInnerInstance} + !insertmacro ALLOW_ONLY_ONE_INSTALLER_INSTACE + ${EndIf} !endif - ${EndIf} - - !insertmacro CHECK_APP_RUNNING "install" - - RMDir /r $INSTDIR - SetOutPath $INSTDIR - - !ifdef APP_64 - ${If} ${RunningX64} - Nsis7z::Extract "$PLUGINSDIR\app-64.7z" - ${Else} - Nsis7z::Extract "$PLUGINSDIR\app-32.7z" - ${EndIf} - !else - Nsis7z::Extract "$PLUGINSDIR\app-32.7z" - !endif - - WriteUninstaller "${UNINSTALL_FILENAME}" - !insertmacro MULTIUSER_RegistryAddInstallInfo + InitPluginsDir - StrCpy $startMenuLink "$SMPROGRAMS\${PRODUCT_FILENAME}.lnk" - StrCpy $desktopLink "$DESKTOP\${PRODUCT_FILENAME}.lnk" - - # create shortcuts in the start menu and on the desktop - # shortcut for uninstall is bad cause user can choose this by mistake during search, so, we don't add it - CreateShortCut "$startMenuLink" "$INSTDIR\${APP_EXECUTABLE_FILENAME}" "" "$INSTDIR\${APP_EXECUTABLE_FILENAME}" 0 "" "" "${APP_DESCRIPTION}" - CreateShortCut "$desktopLink" "$INSTDIR\${APP_EXECUTABLE_FILENAME}" "" "$INSTDIR\${APP_EXECUTABLE_FILENAME}" 0 "" "" "${APP_DESCRIPTION}" - - WinShell::SetLnkAUMI "$startMenuLink" "${APP_ID}" - WinShell::SetLnkAUMI "$desktopLink" "${APP_ID}" + SetCompress off + !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}" - !insertmacro registerFileAssociations + !ifdef HEADER_ICO + File /oname=$PLUGINSDIR\installerHeaderico.ico "${HEADER_ICO}" + !endif - !ifmacrodef customInstall - !insertmacro customInstall + !ifmacrodef customInit + !insertmacro customInit + !endif !endif +FunctionEnd - ${IfNot} ${Silent} - !ifdef ONE_CLICK - # otherwise app window will be in backround - HideWindow - !ifdef RUN_AFTER_FINISH - Call StartApp +!ifndef BUILD_UNINSTALLER + Section "install" + ${IfNot} ${Silent} + SetDetailsPrint none + + !ifdef ONE_CLICK + !ifdef HEADER_ICO + SpiderBanner::Show /MODERN /ICON "$PLUGINSDIR\installerHeaderico.ico" + !else + SpiderBanner::Show /MODERN + !endif !endif - !endif - ${EndIf} -SectionEnd + ${EndIf} -Function un.onInit - !insertmacro check64BitAndSetRegView + !insertmacro CHECK_APP_RUNNING "install" - ${IfNot} ${Silent} - MessageBox MB_OKCANCEL "Are you sure you want to uninstall ${PRODUCT_NAME}?" IDOK +2 - Quit + RMDir /r $INSTDIR + SetOutPath $INSTDIR - !ifdef ONE_CLICK - # one-click installer executes uninstall section in the silent mode, but we must show message dialog if silent mode was not explicitly set by user (using /S flag) - !insertmacro CHECK_APP_RUNNING "uninstall" - SetSilent silent + !ifdef APP_64 + ${If} ${RunningX64} + Nsis7z::Extract "$PLUGINSDIR\app-64.7z" + ${Else} + Nsis7z::Extract "$PLUGINSDIR\app-32.7z" + ${EndIf} + !else + Nsis7z::Extract "$PLUGINSDIR\app-32.7z" !endif - ${EndIf} - !insertmacro initMultiUser Un un. + File "/oname=${UNINSTALL_FILENAME}" "${UNINSTALLER_OUT_FILE}" - !ifmacrodef customUnInit - !insertmacro customUnInit - !endif -FunctionEnd - -Section "un.install" - SetAutoClose true - - !ifndef ONE_CLICK - # for boring installer we check it here to show progress - !insertmacro CHECK_APP_RUNNING "uninstall" - !endif - - StrCpy $startMenuLink "$SMPROGRAMS\${PRODUCT_FILENAME}.lnk" - StrCpy $desktopLink "$DESKTOP\${PRODUCT_FILENAME}.lnk" - - WinShell::UninstAppUserModelId "${APP_ID}" - WinShell::UninstShortcut "$startMenuLink" - WinShell::UninstShortcut "$desktopLink" + !insertmacro registryAddInstallInfo - Delete "$startMenuLink" - Delete "$desktopLink" + StrCpy $startMenuLink "$SMPROGRAMS\${PRODUCT_FILENAME}.lnk" + StrCpy $desktopLink "$DESKTOP\${PRODUCT_FILENAME}.lnk" - !insertmacro unregisterFileAssociations + # create shortcuts in the start menu and on the desktop + # shortcut for uninstall is bad cause user can choose this by mistake during search, so, we don't add it + CreateShortCut "$startMenuLink" "$INSTDIR\${APP_EXECUTABLE_FILENAME}" "" "$INSTDIR\${APP_EXECUTABLE_FILENAME}" 0 "" "" "${APP_DESCRIPTION}" + CreateShortCut "$desktopLink" "$INSTDIR\${APP_EXECUTABLE_FILENAME}" "" "$INSTDIR\${APP_EXECUTABLE_FILENAME}" 0 "" "" "${APP_DESCRIPTION}" - # delete the installed files - RMDir /r $INSTDIR + WinShell::SetLnkAUMI "$startMenuLink" "${APP_ID}" + WinShell::SetLnkAUMI "$desktopLink" "${APP_ID}" - ${GetParameters} $R0 - ${GetOptions} $R0 "/KEEP_APP_DATA" $R1 - ${If} ${Errors} - RMDir /r "$APPDATA\${PRODUCT_FILENAME}" - ${EndIf} + !ifmacrodef registerFileAssociations + !insertmacro registerFileAssociations + !endif - !insertmacro MULTIUSER_RegistryRemoveInstallInfo + !ifmacrodef customInstall + !insertmacro customInstall + !endif - !ifmacrodef customUnInstall - !insertmacro customUnInstall - !endif -SectionEnd \ No newline at end of file + ${IfNot} ${Silent} + !ifdef ONE_CLICK + # otherwise app window will be in backround + HideWindow + !ifdef RUN_AFTER_FINISH + Call StartApp + !endif + !endif + ${EndIf} + SectionEnd +!else + Section + SectionEnd + !include "uninstaller.nsh" +!endif \ No newline at end of file diff --git a/templates/nsis/langs.nsh b/templates/nsis/langs.nsh new file mode 100644 index 0000000000..8ae0261a00 --- /dev/null +++ b/templates/nsis/langs.nsh @@ -0,0 +1,64 @@ +!insertmacro MUI_LANGUAGE "English" +!insertmacro MUI_LANGUAGE "German" +!insertmacro MUI_LANGUAGE "French" +!insertmacro MUI_LANGUAGE "Spanish" +!insertmacro MUI_LANGUAGE "SpanishInternational" +!insertmacro MUI_LANGUAGE "SimpChinese" +!insertmacro MUI_LANGUAGE "TradChinese" +!insertmacro MUI_LANGUAGE "Japanese" +#!insertmacro MUI_LANGUAGE "Korean" +!insertmacro MUI_LANGUAGE "Italian" +!insertmacro MUI_LANGUAGE "Dutch" +#!insertmacro MUI_LANGUAGE "Danish" +#!insertmacro MUI_LANGUAGE "Swedish" +#!insertmacro MUI_LANGUAGE "Norwegian" +#!insertmacro MUI_LANGUAGE "NorwegianNynorsk" +#!insertmacro MUI_LANGUAGE "Finnish" +#!insertmacro MUI_LANGUAGE "Greek" +!insertmacro MUI_LANGUAGE "Russian" +#!insertmacro MUI_LANGUAGE "Portuguese" +#!insertmacro MUI_LANGUAGE "PortugueseBR" +#!insertmacro MUI_LANGUAGE "Polish" +#!insertmacro MUI_LANGUAGE "Ukrainian" +!insertmacro MUI_LANGUAGE "Czech" +#!insertmacro MUI_LANGUAGE "Slovak" +#!insertmacro MUI_LANGUAGE "Croatian" +#!insertmacro MUI_LANGUAGE "Bulgarian" +#!insertmacro MUI_LANGUAGE "Hungarian" +#!insertmacro MUI_LANGUAGE "Thai" +#!insertmacro MUI_LANGUAGE "Romanian" +#!insertmacro MUI_LANGUAGE "Latvian" +#!insertmacro MUI_LANGUAGE "Macedonian" +#!insertmacro MUI_LANGUAGE "Estonian" +#!insertmacro MUI_LANGUAGE "Turkish" +#!insertmacro MUI_LANGUAGE "Lithuanian" +#!insertmacro MUI_LANGUAGE "Slovenian" +#!insertmacro MUI_LANGUAGE "Serbian" +#!insertmacro MUI_LANGUAGE "SerbianLatin" +#!insertmacro MUI_LANGUAGE "Arabic" +#!insertmacro MUI_LANGUAGE "Farsi" +#!insertmacro MUI_LANGUAGE "Hebrew" +#!insertmacro MUI_LANGUAGE "Indonesian" +#!insertmacro MUI_LANGUAGE "Mongolian" +#!insertmacro MUI_LANGUAGE "Luxembourgish" +#!insertmacro MUI_LANGUAGE "Albanian" +#!insertmacro MUI_LANGUAGE "Breton" +#!insertmacro MUI_LANGUAGE "Belarusian" +#!insertmacro MUI_LANGUAGE "Icelandic" +#!insertmacro MUI_LANGUAGE "Malay" +#!insertmacro MUI_LANGUAGE "Bosnian" +#!insertmacro MUI_LANGUAGE "Kurdish" +#!insertmacro MUI_LANGUAGE "Irish" +#!insertmacro MUI_LANGUAGE "Uzbek" +#!insertmacro MUI_LANGUAGE "Galician" +#!insertmacro MUI_LANGUAGE "Afrikaans" +#!insertmacro MUI_LANGUAGE "Catalan" +#!insertmacro MUI_LANGUAGE "Esperanto" +#!insertmacro MUI_LANGUAGE "Asturian" +#!insertmacro MUI_LANGUAGE "Basque" +#!insertmacro MUI_LANGUAGE "Pashto" +#!insertmacro MUI_LANGUAGE "ScotsGaelic" +#!insertmacro MUI_LANGUAGE "Georgian" +#!insertmacro MUI_LANGUAGE "Vietnamese" +#!insertmacro MUI_LANGUAGE "Welsh" +#!insertmacro MUI_LANGUAGE "Armenian" \ No newline at end of file diff --git a/templates/nsis/multiUser.nsh b/templates/nsis/multiUser.nsh index 71b72f1c3b..551a27f8b0 100644 --- a/templates/nsis/multiUser.nsh +++ b/templates/nsis/multiUser.nsh @@ -3,36 +3,24 @@ !define FOLDERID_UserProgramFiles {5CD7AEE2-2219-4A67-B85D-6C9CE15660CB} !define KF_FLAG_CREATE 0x00008000 -!ifdef UNINSTALL_FILENAME & VERSION & APP_EXECUTABLE_FILENAME & PRODUCT_NAME & COMPANY_NAME & PRODUCT_FILENAME -!else - !error "Should define all variables: UNINSTALL_FILENAME & VERSION & APP_EXECUTABLE_FILENAME & PRODUCT_NAME & COMPANY_NAME & PRODUCT_FILENAME" -!endif - !define INSTALL_REGISTRY_KEY "Software\${APP_GUID}" !define UNINSTALL_REGISTRY_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APP_GUID}" +!define UNINSTALL_DISPLAY_NAME "${PRODUCT_NAME} ${VERSION}" -!ifndef MULTIUSER_INSTALLMODE_DISPLAYNAME - !define MULTIUSER_INSTALLMODE_DISPLAYNAME "${PRODUCT_NAME} ${VERSION}" -!endif - -# Current Install Mode ("AllUsers" or "CurrentUser") -Var MultiUser.InstallMode +# current Install Mode ("AllUsers" or "CurrentUser") +Var installMode !ifndef INSTALL_MODE_PER_ALL_USERS !ifndef ONE_CLICK - Var HasPerUserInstallation ; 0 (false) or 1 (true) - Var HasPerMachineInstallation ; 0 (false) or 1 (true) + Var hasPerUserInstallation + Var hasPerMachineInstallation !endif Var PerUserInstallationFolder - # Sets install mode to "per-user". - !macro MULTIUSER_INSTALLMODE_CURRENTUSER UNINSTALLER_PREFIX UNINSTALLER_FUNCPREFIX - StrCpy $MultiUser.InstallMode CurrentUser - + !macro setInstallModePerUser + StrCpy $installMode CurrentUser SetShellVarContext current - - !if "${UNINSTALLER_PREFIX}" != UN - # http://www.mathiaswestin.net/2012/09/how-to-make-per-user-installation-with.html + !ifndef BUILD_UNINSTALLER StrCpy $0 "$LocalAppData\Programs" # Win7 has a per-user programfiles known folder and this can be a non-default location System::Call 'Shell32::SHGetKnownFolderPath(g "${FOLDERID_UserProgramFiles}",i ${KF_FLAG_CREATE},i0,*i.r2)i.r1' @@ -41,140 +29,62 @@ Var MultiUser.InstallMode StrCpy $0 $1 System::Call 'Ole32::CoTaskMemFree(ir2)' ${EndIf} - StrCpy $Instdir "$0\${PRODUCT_FILENAME}" + StrCpy $INSTDIR "$0\${PRODUCT_FILENAME}" !endif - # Checks registry for previous installation path (both for upgrading, reinstall, or uninstall) - ReadRegStr $PerUserInstallationFolder HKCU "${INSTALL_REGISTRY_KEY}" InstallLocation - ${if} $PerUserInstallationFolder != "" - StrCpy $INSTDIR $PerUserInstallationFolder + # сhecks registry for previous installation path (both for upgrading, reinstall, or uninstall) + ReadRegStr $perUserInstallationFolder HKCU "${INSTALL_REGISTRY_KEY}" InstallLocation + ${if} $perUserInstallationFolder != "" + StrCpy $INSTDIR $perUserInstallationFolder ${endif} - - !ifdef MULTIUSER_INSTALLMODE_${UNINSTALLER_PREFIX}FUNCTION - Call "${MULTIUSER_INSTALLMODE_${UNINSTALLER_PREFIX}FUNCTION}" - !endif !macroend - Function MultiUser.InstallMode.CurrentUser - !insertmacro MULTIUSER_INSTALLMODE_CURRENTUSER "" "" - FunctionEnd - - Function un.MultiUser.InstallMode.CurrentUser - !insertmacro MULTIUSER_INSTALLMODE_CURRENTUSER UN un. - FunctionEnd + !ifndef BUILD_UNINSTALLER + Function installMode.CurrentUser + !insertmacro setInstallModePerUser + FunctionEnd + !endif !endif !ifdef INSTALL_MODE_PER_ALL_USERS_REQUIRED - Var PerMachineInstallationFolder + Var perMachineInstallationFolder # Sets install mode to "per-machine" (all users). - !macro MULTIUSER_INSTALLMODE_ALLUSERS UNINSTALLER_PREFIX UNINSTALLER_FUNCPREFIX + !macro setInstallModePerAllUsers # Install mode initialization - per-machine - StrCpy $MultiUser.InstallMode AllUsers + StrCpy $installMode AllUsers SetShellVarContext all - !if "${UNINSTALLER_PREFIX}" != UN - ;Set default installation location for installer + !ifndef BUILD_UNINSTALLER StrCpy $INSTDIR "$PROGRAMFILES\${PRODUCT_FILENAME}" !endif - ; Checks registry for previous installation path (both for upgrading, reinstall, or uninstall) - ReadRegStr $PerMachineInstallationFolder HKLM "${INSTALL_REGISTRY_KEY}" InstallLocation - ${if} $PerMachineInstallationFolder != "" - StrCpy $INSTDIR $PerMachineInstallationFolder - ${endif} - - !ifdef MULTIUSER_INSTALLMODE_${UNINSTALLER_PREFIX}FUNCTION - Call "${MULTIUSER_INSTALLMODE_${UNINSTALLER_PREFIX}FUNCTION}" - !endif - !macroend - - Function MultiUser.InstallMode.AllUsers - !insertmacro MULTIUSER_INSTALLMODE_ALLUSERS "" "" - FunctionEnd - - Function un.MultiUser.InstallMode.AllUsers - !insertmacro MULTIUSER_INSTALLMODE_ALLUSERS UN un. - FunctionEnd -!endif - -!macro MULTIUSER_INIT_TEXTS - !ifndef MULTIUSER_INIT_TEXT_ADMINREQUIRED - !define MULTIUSER_INIT_TEXT_ADMINREQUIRED "$(^Caption) requires administrator privileges." - !endif - - !ifndef MULTIUSER_INIT_TEXT_POWERREQUIRED - !define MULTIUSER_INIT_TEXT_POWERREQUIRED "$(^Caption) requires at least Power User privileges." - !endif - - !ifndef MULTIUSER_INIT_TEXT_ALLUSERSNOTPOSSIBLE - !define MULTIUSER_INIT_TEXT_ALLUSERSNOTPOSSIBLE "Your user account does not have sufficient privileges to install $(^Name) for all users of this computer." - !endif -!macroend - -!macro initMultiUser UNINSTALLER_PREFIX UNINSTALLER_FUNCPREFIX - !ifdef ONE_CLICK - !ifdef INSTALL_MODE_PER_ALL_USERS - Call ${UNINSTALLER_FUNCPREFIX}MultiUser.InstallMode.AllUsers - !else - Call ${UNINSTALLER_FUNCPREFIX}MultiUser.InstallMode.CurrentUser - !endif - !else - !insertmacro UAC_PageElevation_OnInit - - ${If} ${UAC_IsInnerInstance} - ${AndIfNot} ${UAC_IsAdmin} - # special return value for outer instance so it knows we did not have admin rights - SetErrorLevel 0x666666 - Quit - ${EndIf} - - !insertmacro MULTIUSER_INIT_TEXTS - # checks registry for previous installation path (both for upgrading, reinstall, or uninstall) - StrCpy $HasPerMachineInstallation "0" - StrCpy $HasPerUserInstallation "0" - - # set installation mode to setting from a previous installation - ReadRegStr $PerMachineInstallationFolder HKLM "${INSTALL_REGISTRY_KEY}" InstallLocation - ${if} $PerMachineInstallationFolder != "" - StrCpy $HasPerMachineInstallation "1" - ${endif} - - ReadRegStr $PerUserInstallationFolder HKCU "${INSTALL_REGISTRY_KEY}" InstallLocation - ${if} $PerUserInstallationFolder != "" - StrCpy $HasPerUserInstallation "1" + ReadRegStr $perMachineInstallationFolder HKLM "${INSTALL_REGISTRY_KEY}" InstallLocation + ${if} $perMachineInstallationFolder != "" + StrCpy $INSTDIR $perMachineInstallationFolder ${endif} + !macroend - ${if} $HasPerUserInstallation == "1" - ${andif} $HasPerMachineInstallation == "0" - Call ${UNINSTALLER_FUNCPREFIX}MultiUser.InstallMode.CurrentUser - ${elseif} $HasPerUserInstallation == "0" - ${andif} $HasPerMachineInstallation == "1" - Call ${UNINSTALLER_FUNCPREFIX}MultiUser.InstallMode.AllUsers - ${else} - # if there is no installation, or there is both per-user and per-machine - !ifdef INSTALL_MODE_PER_ALL_USERS - Call ${UNINSTALLER_FUNCPREFIX}MultiUser.InstallMode.AllUsers - !else - Call ${UNINSTALLER_FUNCPREFIX}MultiUser.InstallMode.CurrentUser - !endif - ${endif} + !ifndef BUILD_UNINSTALLER + Function installMode.AllUsers + !insertmacro setInstallModePerAllUsers + FunctionEnd !endif -!macroend +!endif # SHCTX is the hive HKLM if SetShellVarContext all, or HKCU if SetShellVarContext user -!macro MULTIUSER_RegistryAddInstallInfo +!macro registryAddInstallInfo # Write the installation path into the registry WriteRegStr SHCTX "${INSTALL_REGISTRY_KEY}" InstallLocation "$INSTDIR" # Write the uninstall keys for Windows - ${if} $MultiUser.InstallMode == "AllUsers" - WriteRegStr SHCTX "${UNINSTALL_REGISTRY_KEY}" DisplayName "${MULTIUSER_INSTALLMODE_DISPLAYNAME}" + ${if} $installMode == "AllUsers" + WriteRegStr SHCTX "${UNINSTALL_REGISTRY_KEY}" DisplayName "${UNINSTALL_DISPLAY_NAME}" WriteRegStr SHCTX "${UNINSTALL_REGISTRY_KEY}" UninstallString '"$INSTDIR\${UNINSTALL_FILENAME}" /allusers' ${else} - WriteRegStr SHCTX "${UNINSTALL_REGISTRY_KEY}" DisplayName "${MULTIUSER_INSTALLMODE_DISPLAYNAME} (only current user)" + WriteRegStr SHCTX "${UNINSTALL_REGISTRY_KEY}" DisplayName "${UNINSTALL_DISPLAY_NAME} (only current user)" WriteRegStr SHCTX "${UNINSTALL_REGISTRY_KEY}" UninstallString '"$INSTDIR\${UNINSTALL_FILENAME}" /currentuser' ${endif} @@ -187,9 +97,4 @@ Var MultiUser.InstallMode ${GetSize} "$INSTDIR" "/S=0K" $0 $1 $2 IntFmt $0 "0x%08X" $0 WriteRegDWORD SHCTX "${UNINSTALL_REGISTRY_KEY}" "EstimatedSize" "$0" -!macroend - -!macro MULTIUSER_RegistryRemoveInstallInfo - DeleteRegKey SHCTX "${UNINSTALL_REGISTRY_KEY}" - DeleteRegKey SHCTX "${INSTALL_REGISTRY_KEY}" !macroend \ No newline at end of file diff --git a/templates/nsis/multiUserUi.nsh b/templates/nsis/multiUserUi.nsh index 76d4138a02..98c702509b 100755 --- a/templates/nsis/multiUserUi.nsh +++ b/templates/nsis/multiUserUi.nsh @@ -3,82 +3,67 @@ RequestExecutionLevel user -Var HasTwoAvailableOptions ; 0 (false) or 1 (true) +Var HasTwoAvailableOptions Var RadioButtonLabel1 -!macro MULTIUSER_INIT_QUIT UNINSTALLER_FUNCPREFIX - !ifdef MULTIUSER_INIT_${UNINSTALLER_FUNCPREFIX}FUNCTIONQUIT - Call "${MULTIUSER_INIT_${UNINSTALLER_FUNCPREFIX}FUCTIONQUIT}" - !else - Quit - !endif -!macroend - -!macro MULTIUSER_INSTALLMODEPAGE_INTERFACE - Var MultiUser.InstallModePage - Var MultiUser.InstallModePage.Text - Var MultiUser.InstallModePage.AllUsers - Var MultiUser.InstallModePage.CurrentUser - Var MultiUser.InstallModePage.ReturnValue -!macroend - -!macro MULTIUSER_PAGEDECLARATION_INSTALLMODE - !insertmacro MUI_SET MULTIUSER_${MUI_PAGE_UNINSTALLER_PREFIX}INSTALLMODEPAGE "" - !insertmacro MULTIUSER_INSTALLMODEPAGE_INTERFACE - !insertmacro MULTIUSER_FUNCTION_INSTALLMODEPAGE MultiUser.InstallModePre_${MUI_UNIQUEID} MultiUser.InstallModeLeave_${MUI_UNIQUEID} "" "" - !insertmacro MULTIUSER_FUNCTION_INSTALLMODEPAGE MultiUser.InstallModePre_${MUI_UNIQUEID} MultiUser.InstallModeLeave_${MUI_UNIQUEID} UN un. - - PageEx custom - PageCallbacks MultiUser.InstallModePre_${MUI_UNIQUEID} MultiUser.InstallModeLeave_${MUI_UNIQUEID} - Caption " " - PageExEnd - - UninstPage custom un.MultiUser.InstallModePre_${MUI_UNIQUEID} un.MultiUser.InstallModeLeave_${MUI_UNIQUEID} -!macroend - -!macro MULTIUSER_PAGE_INSTALLMODE +!macro PAGE_INSTALL_MODE !insertmacro MUI_PAGE_INIT - !insertmacro MULTIUSER_PAGEDECLARATION_INSTALLMODE + + !insertmacro MUI_SET MULTIUSER_${MUI_PAGE_UNINSTALLER_PREFIX}INSTALLMODEPAGE "" + Var MultiUser.InstallModePage + Var MultiUser.InstallModePage.Text + Var MultiUser.InstallModePage.AllUsers + Var MultiUser.InstallModePage.CurrentUser + Var MultiUser.InstallModePage.ReturnValue + + !ifndef BUILD_UNINSTALLER + !insertmacro FUNCTION_INSTALL_MODE_PAGE_FUNCTION MultiUser.InstallModePre_${MUI_UNIQUEID} MultiUser.InstallModeLeave_${MUI_UNIQUEID} "" + PageEx custom + PageCallbacks MultiUser.InstallModePre_${MUI_UNIQUEID} MultiUser.InstallModeLeave_${MUI_UNIQUEID} + Caption " " + PageExEnd + !else + !insertmacro FUNCTION_INSTALL_MODE_PAGE_FUNCTION MultiUser.InstallModePre_${MUI_UNIQUEID} MultiUser.InstallModeLeave_${MUI_UNIQUEID} un. + UninstPage custom un.multiUser.InstallModePre_${MUI_UNIQUEID} un.MultiUser.InstallModeLeave_${MUI_UNIQUEID} + !endif !macroend -!macro MULTIUSER_FUNCTION_INSTALLMODEPAGE PRE LEAVE UNINSTALLER_PREFIX UNINSTALLER_FUNCPREFIX +!macro FUNCTION_INSTALL_MODE_PAGE_FUNCTION PRE LEAVE UNINSTALLER_FUNCPREFIX Function "${UNINSTALLER_FUNCPREFIX}${PRE}" ${If} ${UAC_IsInnerInstance} ${AndIf} ${UAC_IsAdmin} - # Inner Process (and Admin) - skip selection, inner process is always used for elevation (machine-wide) - Call ${UNINSTALLER_FUNCPREFIX}MultiUser.InstallMode.AllUsers + # inner Process (and Admin) - skip selection, inner process is always used for elevation (machine-wide) + Call ${UNINSTALLER_FUNCPREFIX}installMode.AllUsers Abort ${EndIf} ${GetParameters} $R0 ${GetOptions} $R0 "/allusers" $R1 ${IfNot} ${Errors} - Call ${UNINSTALLER_FUNCPREFIX}MultiUser.InstallMode.AllUsers + Call ${UNINSTALLER_FUNCPREFIX}installMode.AllUsers Abort ${EndIf} ${GetOptions} $R0 "/currentuser" $R1 ${IfNot} ${Errors} - Call ${UNINSTALLER_FUNCPREFIX}MultiUser.InstallMode.CurrentUser + Call ${UNINSTALLER_FUNCPREFIX}installMode.CurrentUser Abort ${EndIf} # If uninstalling, will check if there is both a per-user and per-machine installation. If there is only one, will skip the form. # If uninstallation was invoked from the "add/remove programs" Windows will automatically requests elevation (depending if uninstall keys are in HKLM or HKCU) # so (for uninstallation) just checking UAC_IsAdmin would probably be enought to determine if it's a per-user or per-machine. However, user can run the uninstall.exe from the folder itself - !if "${UNINSTALLER_PREFIX}" == UN - ${if} $HasPerUserInstallation == "1" - ${andif} $HasPerMachineInstallation == "0" - Call ${UNINSTALLER_FUNCPREFIX}MultiUser.InstallMode.CurrentUser + !ifdef BUILD_UNINSTALLER + ${if} $hasPerUserInstallation == "1" + ${andif} $hasPerMachineInstallation == "0" + Call un.installMode.CurrentUser Abort - ${elseif} $HasPerUserInstallation == "0" - ${andif} $HasPerMachineInstallation == "1" - Call ${UNINSTALLER_FUNCPREFIX}MultiUser.InstallMode.AllUsers + ${elseif} $hasPerUserInstallation == "0" + ${andif} $hasPerMachineInstallation == "1" + Call un.installMode.AllUsers Abort ${endif} - !endif - !if "${UNINSTALLER_PREFIX}" == UN !insertmacro MUI_HEADER_TEXT "Choose Uninstallation Options" "Which installation should be removed?" !else !insertmacro MUI_HEADER_TEXT "Choose Installation Options" "Who should this application be installed for?" @@ -87,7 +72,7 @@ Var RadioButtonLabel1 nsDialogs::Create 1018 Pop $MultiUser.InstallModePage - !if "${UNINSTALLER_PREFIX}" != UN + !ifndef BUILD_UNINSTALLER ${NSD_CreateLabel} 0u 0u 300u 20u "Please select whether you wish to make this software available to all users or just yourself" StrCpy $8 "Anyone who uses this computer (&all users)" StrCpy $9 "Only for &me" @@ -128,7 +113,7 @@ Var RadioButtonLabel1 ${NSD_CreateLabel} 0u 110u 280u 50u "" Pop $RadioButtonLabel1 - ${if} $MultiUser.InstallMode == "AllUsers" ; setting defaults + ${if} $installMode == "AllUsers" ; setting defaults SendMessage $MultiUser.InstallModePage.AllUsers ${BM_SETCHECK} ${BST_CHECKED} 0 ; set as default SendMessage $MultiUser.InstallModePage.AllUsers ${BM_CLICK} 0 0 ; trigger click event ${else} @@ -145,7 +130,7 @@ Var RadioButtonLabel1 ${if} $MultiUser.InstallModePage.ReturnValue = ${BST_CHECKED} ${if} ${UAC_IsAdmin} - Call ${UNINSTALLER_FUNCPREFIX}MultiUser.InstallMode.AllUsers + Call ${UNINSTALLER_FUNCPREFIX}installMode.AllUsers ${else} !ifdef MULTIUSER_INSTALLMODE_ALLOW_ELEVATION GetDlgItem $9 $HWNDParent 1 @@ -173,7 +158,7 @@ Var RadioButtonLabel1 !endif ${endif} ${else} - Call ${UNINSTALLER_FUNCPREFIX}MultiUser.InstallMode.CurrentUser + Call ${UNINSTALLER_FUNCPREFIX}installMode.CurrentUser ${endif} !insertmacro MUI_PAGE_FUNCTION_CUSTOM LEAVE @@ -187,22 +172,22 @@ Var RadioButtonLabel1 StrCpy $7 "" ${if} "$1" == "0" ; current user - ${if} $HasPerUserInstallation == "1" - !if "${UNINSTALLER_PREFIX}" != UN - StrCpy $7 "There is already a per-user installation. ($PerUserInstallationFolder)$\r$\nWill reinstall/upgrade." + ${if} $hasPerUserInstallation == "1" + !ifndef BUILD_UNINSTALLER + StrCpy $7 "There is already a per-user installation. ($perUserInstallationFolder)$\r$\nWill reinstall/upgrade." !else - StrCpy $7 "There is a per-user installation. ($PerUserInstallationFolder)$\r$\nWill uninstall." + StrCpy $7 "There is a per-user installation. ($perUserInstallationFolder)$\r$\nWill uninstall." !endif ${else} StrCpy $7 "Fresh install for current user only" ${endif} SendMessage $0 ${BCM_SETSHIELD} 0 0 ; hide SHIELD ${else} ; all users - ${if} $HasPerMachineInstallation == "1" - !if "${UNINSTALLER_PREFIX}" != UN - StrCpy $7 "There is already a per-machine installation. ($PerMachineInstallationFolder)$\r$\nWill reinstall/upgrade." + ${if} $hasPerMachineInstallation == "1" + !ifndef BUILD_UNINSTALLER + StrCpy $7 "There is already a per-machine installation. ($perMachineInstallationFolder)$\r$\nWill reinstall/upgrade." !else - StrCpy $7 "There is a per-machine installation. ($PerMachineInstallationFolder)$\r$\nWill uninstall." + StrCpy $7 "There is a per-machine installation. ($perMachineInstallationFolder)$\r$\nWill uninstall." !endif ${else} StrCpy $7 "Fresh install for all users" diff --git a/templates/nsis/oneClick.nsh b/templates/nsis/oneClick.nsh index 387457595a..47c47a525b 100644 --- a/templates/nsis/oneClick.nsh +++ b/templates/nsis/oneClick.nsh @@ -1,17 +1,21 @@ !ifdef RUN_AFTER_FINISH - Function StartApp - !ifdef INSTALL_MODE_PER_ALL_USERS - !include UAC.nsh - !insertmacro UAC_AsUser_ExecShell "" "$SMPROGRAMS\${PRODUCT_FILENAME}.lnk" "" "" "" - !else - ExecShell "" "$SMPROGRAMS\${PRODUCT_FILENAME}.lnk" - !endif - FunctionEnd + !ifndef BUILD_UNINSTALLER + Function StartApp + !ifdef INSTALL_MODE_PER_ALL_USERS + !include UAC.nsh + !insertmacro UAC_AsUser_ExecShell "" "$SMPROGRAMS\${PRODUCT_FILENAME}.lnk" "" "" "" + !else + ExecShell "" "$SMPROGRAMS\${PRODUCT_FILENAME}.lnk" + !endif + FunctionEnd + !endif !endif AutoCloseWindow true !insertmacro MUI_PAGE_INSTFILES -!insertmacro MUI_UNPAGE_INSTFILES +!ifdef BUILD_UNINSTALLER + !insertmacro MUI_UNPAGE_INSTFILES +!endif !insertmacro MUI_LANGUAGE "English" @@ -19,4 +23,20 @@ AutoCloseWindow true RequestExecutionLevel admin !else RequestExecutionLevel user -!endif \ No newline at end of file +!endif + +!macro initMultiUser UNINSTALLER_FUNCPREFIX + !ifdef INSTALL_MODE_PER_ALL_USERS + !ifdef BUILD_UNINSTALLER + Call un.installMode.AllUsers + !else + Call installMode.AllUsers + !endif + !else + !ifdef BUILD_UNINSTALLER + Call un.installMode.CurrentUser + !else + Call installMode.CurrentUser + !endif + !endif +!macroend \ No newline at end of file diff --git a/templates/nsis/uninstaller.nsh b/templates/nsis/uninstaller.nsh new file mode 100644 index 0000000000..7710bb305c --- /dev/null +++ b/templates/nsis/uninstaller.nsh @@ -0,0 +1,71 @@ +!ifndef INSTALL_MODE_PER_ALL_USERS + Function un.installMode.CurrentUser + !insertmacro setInstallModePerUser + FunctionEnd +!endif + +!ifdef INSTALL_MODE_PER_ALL_USERS_REQUIRED + Function un.installMode.AllUsers + !insertmacro setInstallModePerAllUsers + FunctionEnd +!endif + +Function un.onInit + !insertmacro check64BitAndSetRegView + + ${IfNot} ${Silent} + MessageBox MB_OKCANCEL "Are you sure you want to uninstall ${PRODUCT_NAME}?" IDOK +2 + Quit + + !ifdef ONE_CLICK + # one-click installer executes uninstall section in the silent mode, but we must show message dialog if silent mode was not explicitly set by user (using /S flag) + !insertmacro CHECK_APP_RUNNING "uninstall" + SetSilent silent + !endif + ${EndIf} + + !insertmacro initMultiUser un. + + !ifmacrodef customUnInit + !insertmacro customUnInit + !endif +FunctionEnd + +Section "un.install" + SetAutoClose true + + !ifndef ONE_CLICK + # for boring installer we check it here to show progress + !insertmacro CHECK_APP_RUNNING "uninstall" + !endif + + StrCpy $startMenuLink "$SMPROGRAMS\${PRODUCT_FILENAME}.lnk" + StrCpy $desktopLink "$DESKTOP\${PRODUCT_FILENAME}.lnk" + + WinShell::UninstAppUserModelId "${APP_ID}" + WinShell::UninstShortcut "$startMenuLink" + WinShell::UninstShortcut "$desktopLink" + + Delete "$startMenuLink" + Delete "$desktopLink" + + !ifmacrodef unregisterFileAssociations + !insertmacro unregisterFileAssociations + !endif + + # delete the installed files + RMDir /r $INSTDIR + + ${GetParameters} $R0 + ${GetOptions} $R0 "/KEEP_APP_DATA" $R1 + ${If} ${Errors} + RMDir /r "$APPDATA\${PRODUCT_FILENAME}" + ${EndIf} + + DeleteRegKey SHCTX "${UNINSTALL_REGISTRY_KEY}" + DeleteRegKey SHCTX "${INSTALL_REGISTRY_KEY}" + + !ifmacrodef customUnInstall + !insertmacro customUnInstall + !endif +SectionEnd \ No newline at end of file diff --git a/test/src/helpers/packTester.ts b/test/src/helpers/packTester.ts index 9e0ecba1d3..1e32bdd80f 100755 --- a/test/src/helpers/packTester.ts +++ b/test/src/helpers/packTester.ts @@ -34,20 +34,29 @@ interface AssertPackOptions { readonly expectedDepends?: string readonly useTempDir?: boolean + readonly signed?: boolean readonly npmInstallBefore?: boolean } -export async function assertPack(fixtureName: string, packagerOptions: PackagerOptions, checkOptions?: AssertPackOptions): Promise { - const tempDirCreated = checkOptions == null ? null : checkOptions.tempDirCreated - const useTempDir = fixtureName !== "app-executable-deps" && (tempDirCreated != null || packagerOptions.devMetadata != null || (checkOptions != null && checkOptions.useTempDir) || packagerOptions.targets.values().next().value.values().next().value[0] !== DEFAULT_TARGET) +export function app(packagerOptions: PackagerOptions, checkOptions: AssertPackOptions = {}) { + return () => assertPack("test-app-one", packagerOptions, checkOptions) +} + +export async function assertPack(fixtureName: string, packagerOptions: PackagerOptions, checkOptions: AssertPackOptions = {}): Promise { + if (checkOptions.signed) { + packagerOptions = signed(packagerOptions) + } + + const tempDirCreated = checkOptions.tempDirCreated + const useTempDir = fixtureName !== "app-executable-deps" && (tempDirCreated != null || packagerOptions.devMetadata != null || checkOptions.useTempDir || packagerOptions.targets.values().next().value.values().next().value[0] !== DEFAULT_TARGET) let projectDir = path.join(__dirname, "..", "..", "fixtures", fixtureName) // const isDoNotUseTempDir = platform === "darwin" const customTmpDir = process.env.TEST_APP_TMP_DIR if (useTempDir) { // non-osx test uses the same dir as osx test, but we cannot share node_modules (because tests executed in parallel) - const dir = customTmpDir == null ? path.join(tmpdir(), `${getTempName("electron-builder-test")}`) : path.resolve(customTmpDir) + const dir = customTmpDir == null ? path.join(tmpdir(), "electron-builder-test", `${getTempName()}`) : path.resolve(customTmpDir) if (customTmpDir != null) { log(`Custom temp dir used: ${customTmpDir}`) } @@ -64,7 +73,7 @@ export async function assertPack(fixtureName: string, packagerOptions: PackagerO try { if (tempDirCreated != null) { await tempDirCreated(projectDir) - if (checkOptions != null && checkOptions.npmInstallBefore) { + if (checkOptions.npmInstallBefore) { await spawnNpmProduction("install", projectDir) } } @@ -73,7 +82,7 @@ export async function assertPack(fixtureName: string, packagerOptions: PackagerO projectDir: projectDir, }, packagerOptions), checkOptions) - if (checkOptions != null && checkOptions.packed != null) { + if (checkOptions.packed != null) { await checkOptions.packed(projectDir) } } @@ -83,7 +92,7 @@ export async function assertPack(fixtureName: string, packagerOptions: PackagerO await remove(projectDir) } catch (e) { - console.warn("Cannot delete temporary directory " + projectDir + ": " + (e.stack || e)) + console.warn(`Cannot delete temporary directory ${projectDir}: ${(e.stack || e)}`) } } } diff --git a/test/src/helpers/runTests.ts b/test/src/helpers/runTests.ts index c796637ab3..5df322c6c2 100755 --- a/test/src/helpers/runTests.ts +++ b/test/src/helpers/runTests.ts @@ -1,9 +1,9 @@ import { spawn } from "child_process" import * as path from "path" import { Promise as BluebirdPromise } from "bluebird" -import { copy, emptyDir, outputFile, readdir, readFileSync, readJson, unlink } from "fs-extra-p" +import { copy, emptyDir, outputFile, readdir, readFileSync, readJson, unlink, remove } from "fs-extra-p" import { Platform } from "out/metadata" -import { cpus, homedir } from "os" +import { cpus, homedir, tmpdir } from "os" // we set NODE_PATH in this file, so, we cannot use 'out/awaiter' path here //noinspection JSUnusedLocalSymbols @@ -17,15 +17,17 @@ const downloadElectron: (options: any) => Promise = BluebirdPromise.promisi const packager = require("../../../out/packager") const rootDir = path.join(__dirname, "..", "..", "..") -const testPackageDir = path.join(require("os").tmpdir(), "electron_builder_published") +const testPackageDir = path.join(tmpdir(), "electron_builder_published") const testNodeModules = path.join(testPackageDir, "node_modules") const electronVersion = "1.3.2" async function main() { + const tempTestBaseDir = path.join(tmpdir(), "electron-builder-test") await BluebirdPromise.all([ deleteOldElectronVersion(), downloadAllRequiredElectronVersions(), + emptyDir(tempTestBaseDir), outputFile(path.join(testPackageDir, "package.json"), `{ "private": true, "version": "1.0.0", @@ -42,7 +44,12 @@ async function main() { await exec(["install", "--cache-min", "999999999", "--production", rootDir]) // prune stale packages await exec(["prune", "--production"]) - await runTests() + try { + await runTests() + } + finally { + await remove(tempTestBaseDir) + } } main() diff --git a/test/src/nsisTest.ts b/test/src/nsisTest.ts index 64fc15f9f3..56940fccf9 100644 --- a/test/src/nsisTest.ts +++ b/test/src/nsisTest.ts @@ -1,6 +1,6 @@ import { Platform, Arch } from "out" import test from "./helpers/avaEx" -import { assertPack, signed, getTestAsset } from "./helpers/packTester" +import { assertPack, getTestAsset, app } from "./helpers/packTester" import { copy } from "fs-extra-p" import * as path from "path" import { Promise as BluebirdPromise } from "bluebird" @@ -9,14 +9,10 @@ import { assertThat } from "./helpers/fileAssert" //noinspection JSUnusedLocalSymbols const __awaiter = require("out/util/awaiter") -test("one-click", () => assertPack("test-app-one", signed({ - targets: Platform.WINDOWS.createTarget(["nsis"]), - }), { - useTempDir: true, - } -)) +const nsisTarget = Platform.WINDOWS.createTarget(["nsis"]) +test("one-click", app({targets: nsisTarget}, {useTempDir: true, signed: true})) -test.ifDevOrLinuxCi("perMachine, no run after finish", () => assertPack("test-app-one", { +test.ifDevOrLinuxCi("perMachine, no run after finish", app({ targets: Platform.WINDOWS.createTarget(["nsis"], Arch.ia32, Arch.x64), devMetadata: { build: { @@ -34,8 +30,8 @@ test.ifDevOrLinuxCi("perMachine, no run after finish", () => assertPack("test-ap } })) -test.ifNotCiOsx("boring", () => assertPack("test-app-one", signed({ - targets: Platform.WINDOWS.createTarget(["nsis"]), +test.ifNotCiOsx("boring", app({ + targets: nsisTarget, devMetadata: { build: { nsis: { @@ -43,12 +39,12 @@ test.ifNotCiOsx("boring", () => assertPack("test-app-one", signed({ } } } -}))) +}, {signed: true})) test.ifNotCiOsx("installerHeaderIcon", () => { let headerIconPath: string | null = null return assertPack("test-app-one", { - targets: Platform.WINDOWS.createTarget(["nsis"]), + targets: nsisTarget, effectiveOptionComputed: options => { const defines = options[0] assertThat(defines.HEADER_ICO).isEqualTo(headerIconPath) @@ -66,7 +62,7 @@ test.ifNotCiOsx("installerHeaderIcon", () => { test.ifNotCiOsx("boring, MUI_HEADER", () => { let installerHeaderPath: string | null = null return assertPack("test-app-one", { - targets: Platform.WINDOWS.createTarget(["nsis"]), + targets: nsisTarget, devMetadata: { build: { nsis: { @@ -94,7 +90,7 @@ test.ifNotCiOsx("boring, MUI_HEADER", () => { test.ifNotCiOsx("boring, MUI_HEADER as option", () => { let installerHeaderPath: string | null = null return assertPack("test-app-one", { - targets: Platform.WINDOWS.createTarget(["nsis"]), + targets: nsisTarget, devMetadata: { build: { nsis: { @@ -120,9 +116,7 @@ test.ifNotCiOsx("boring, MUI_HEADER as option", () => { ) }) -test.ifDevOrLinuxCi("custom include", () => assertPack("test-app-one", { - targets: Platform.WINDOWS.createTarget(["nsis"]), -}, { +test.ifDevOrLinuxCi("custom include", () => assertPack("test-app-one", {targets: nsisTarget}, { tempDirCreated: projectDir => copy(getTestAsset("installer.nsh"), path.join(projectDir, "build", "installer.nsh")), packed: projectDir => BluebirdPromise.all([ assertThat(path.join(projectDir, "build", "customHeader")).isFile(), @@ -131,9 +125,7 @@ test.ifDevOrLinuxCi("custom include", () => assertPack("test-app-one", { ]), })) -test.ifDevOrLinuxCi("custom script", () => assertPack("test-app-one", { - targets: Platform.WINDOWS.createTarget(["nsis"]), -}, { +test.ifDevOrLinuxCi("custom script", app({targets: nsisTarget}, { tempDirCreated: projectDir => copy(getTestAsset("installer.nsi"), path.join(projectDir, "build", "installer.nsi")), packed: projectDir => assertThat(path.join(projectDir, "build", "customInstallerScript")).isFile(), })) \ No newline at end of file diff --git a/test/src/winPackagerTest.ts b/test/src/winPackagerTest.ts index 5c14c97cd0..75be6abc41 100755 --- a/test/src/winPackagerTest.ts +++ b/test/src/winPackagerTest.ts @@ -1,6 +1,6 @@ import { Platform, Arch, BuildInfo } from "out" import test from "./helpers/avaEx" -import { assertPack, platform, modifyPackageJson, signed, getTestAsset } from "./helpers/packTester" +import { assertPack, platform, modifyPackageJson, getTestAsset, app } from "./helpers/packTester" import { outputFile, rename, copy } from "fs-extra-p" import * as path from "path" import { WinPackager } from "out/winPackager" @@ -14,24 +14,20 @@ import { ElectronPackagerOptions } from "out/packager/dirPackager" //noinspection JSUnusedLocalSymbols const __awaiter = require("out/util/awaiter") -test.ifNotCiOsx("win", () => assertPack("test-app-one", signed({ - targets: Platform.WINDOWS.createTarget(["default", "zip"]), - }) -)) +test.ifNotCiOsx("win", app({targets: Platform.WINDOWS.createTarget(["default", "zip"])}, {signed: true})) // very slow -test.skip("delta and msi", () => assertPack("test-app-one", { - targets: Platform.WINDOWS.createTarget(null, Arch.ia32), - devMetadata: { - build: { - win: { - remoteReleases: "https://github.com/develar/__test-app-releases", - msi: true, - }, - } - }, - } -)) +test.skip("delta and msi", app({ + targets: Platform.WINDOWS.createTarget(null, Arch.ia32), + devMetadata: { + build: { + win: { + remoteReleases: "https://github.com/develar/__test-app-releases", + msi: true, + }, + } + }, +})) test.ifDevOrWinCi("beta version", () => { const metadata: any = { @@ -148,7 +144,7 @@ class CheckingWinPackager extends WinPackager { this.effectivePackOptions = await this.computePackOptions() const helperClass: typeof SquirrelWindowsTarget = require("out/targets/squirrelWindows").default - this.effectiveDistOptions = await (new helperClass(this).computeEffectiveDistOptions()) + this.effectiveDistOptions = await (new helperClass(this, []).computeEffectiveDistOptions()) await this.sign(this.computeAppOutDir(outDir, arch)) }