diff --git a/bin/Packager/Nsis/NullsoftInstaller.nsi b/bin/Packager/Nsis/NullsoftInstaller.nsi index 41e118a21..2dbbcd227 100644 --- a/bin/Packager/Nsis/NullsoftInstaller.nsi +++ b/bin/Packager/Nsis/NullsoftInstaller.nsi @@ -1,199 +1,201 @@ ; Copyright 2010 Patrick Spendrin ; Copyright 2016 Kevin Funk ; Copyright Hannah von Reth ; ; Redistribution and use in source and binary forms, with or without ; modification, are permitted provided that the following conditions ; are met: ; 1. Redistributions of source code must retain the above copyright ; notice, this list of conditions and the following disclaimer. ; 2. Redistributions in binary form must reproduce the above copyright ; notice, this list of conditions and the following disclaimer in the ; documentation and/or other materials provided with the distribution. ; ; THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ; ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE ; IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ; ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE ; FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL ; DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS ; OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) ; HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT ; LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY ; OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF ; SUCH DAMAGE. ; registry stuff !define regkey "Software\@{company}\@{productname}" !define uninstkey "Software\Microsoft\Windows\CurrentVersion\Uninstall\@{productname}" !define uninstaller "uninstall.exe" BrandingText "Generated by Craft https://community.kde.org/Craft" ;-------------------------------- XPStyle on ManifestDPIAware true Name "@{productname}" Caption "@{productname} @{version}" OutFile "@{setupname}" !define MULTIUSER_EXECUTIONLEVEL Highest !define MULTIUSER_MUI !define MULTIUSER_INSTALLMODE_COMMANDLINE !define MULTIUSER_INSTALLMODE_DEFAULT_REGISTRY_KEY "${regkey}" !define MULTIUSER_INSTALLMODE_DEFAULT_REGISTRY_VALUENAME "Install_Mode" !define MULTIUSER_INSTALLMODE_INSTDIR "@{productname}" !define MULTIUSER_INSTALLMODE_INSTDIR_REGISTRY_KEY "${regkey}" !define MULTIUSER_INSTALLMODE_INSTDIR_REGISTRY_VALUENAME "Install_Dir" ;Start Menu Folder Page Configuration Var StartMenuFolder !define MUI_STARTMENUPAGE_REGISTRY_ROOT "SHCTX" !define MUI_STARTMENUPAGE_REGISTRY_KEY "${regkey}" !define MUI_STARTMENUPAGE_REGISTRY_VALUENAME "Start Menu Folder" ;!define MULTIUSER_USE_PROGRAMFILES64 @{multiuser_use_programfiles64} ;!define MULTIUSER_USE_PROGRAMFILES64 @{nsis_include_internal} @{nsis_include} !include "MultiUser.nsh" !include "MUI2.nsh" !include "LogicLib.nsh" !include "x64.nsh" !include "process.nsh" ;!define MUI_ICON @{installerIcon} ;!define MUI_ICON !insertmacro MUI_PAGE_WELCOME ;!insertmacro MUI_PAGE_LICENSE @{license} ;!insertmacro MUI_PAGE_LICENSE !insertmacro MULTIUSER_PAGE_INSTALLMODE !insertmacro MUI_PAGE_DIRECTORY !insertmacro MUI_PAGE_STARTMENU Application $StartMenuFolder !define MUI_COMPONENTSPAGE_NODESC ;!insertmacro MUI_PAGE_COMPONENTS @{sections_page} ;!insertmacro MUI_PAGE_COMPONENTS !insertmacro MUI_PAGE_INSTFILES !insertmacro MUI_UNPAGE_CONFIRM !insertmacro MUI_UNPAGE_INSTFILES !define MUI_FINISHPAGE_LINK "Visit project homepage" !define MUI_FINISHPAGE_LINK_LOCATION "@{website}" !insertmacro MUI_PAGE_FINISH !insertmacro MUI_LANGUAGE "English" SetDateSave on SetDatablockOptimize on CRCCheck on SilentInstall normal Function .onInit !insertmacro MULTIUSER_INIT !if @{architecture} == "x64" ${IfNot} ${RunningX64} MessageBox MB_OK|MB_ICONEXCLAMATION "This installer can only be run on 64-bit Windows." Abort ${EndIf} !endif FunctionEnd Function un.onInit !insertmacro MULTIUSER_UNINIT FunctionEnd ;-------------------------------- AutoCloseWindow false ; beginning (invisible) section Section !insertmacro EndProcessWithDialog ExecWait '"$MultiUser.InstDir\${uninstaller}" /S _?=$MultiUser.InstDir' @{preInstallHook} WriteRegStr SHCTX "${regkey}" "Install_Dir" "$INSTDIR" WriteRegStr SHCTX "${MULTIUSER_INSTALLMODE_INSTDIR_REGISTRY_KEY}" "${MULTIUSER_INSTALLMODE_DEFAULT_REGISTRY_VALUENAME}" "$MultiUser.InstallMode" ; write uninstall strings WriteRegStr SHCTX "${uninstkey}" "DisplayName" "@{productname}" WriteRegStr SHCTX "${uninstkey}" "UninstallString" '"$INSTDIR\${uninstaller}"' WriteRegStr SHCTX "${uninstkey}" "DisplayIcon" "$INSTDIR\@{iconname}" WriteRegStr SHCTX "${uninstkey}" "URLInfoAbout" "@{website}" WriteRegStr SHCTX "${uninstkey}" "Publisher" "@{company}" WriteRegStr SHCTX "${uninstkey}" "DisplayVersion" "@{version}" @{registy_hook} SetOutPath $INSTDIR ; package all files, recursively, preserving attributes ; assume files are in the correct places File /a "@{dataPath}" File /a "@{7za}" File /a "@{icon}" nsExec::ExecToLog '"$INSTDIR\7za.exe" x -r -y "$INSTDIR\@{dataName}" -o"$INSTDIR"' Delete "$INSTDIR\7za.exe" Delete "$INSTDIR\@{dataName}" AddSize @{installSize} WriteUninstaller "${uninstaller}" SectionEnd ; create shortcuts Section SetShellVarContext all !insertmacro MUI_STARTMENU_WRITE_BEGIN Application CreateDirectory "$SMPROGRAMS\$StartMenuFolder" SetOutPath $INSTDIR ; for working directory @{shortcuts} CreateShortCut "$SMPROGRAMS\$StartMenuFolder\Uninstall.lnk" "$INSTDIR\uninstall.exe" !insertmacro MUI_STARTMENU_WRITE_END SectionEnd ; allow to define additional sections @{sections} ; Uninstaller ; All section names prefixed by "Un" will be in the uninstaller UninstallText "This will uninstall @{productname}." Section "Uninstall" SetShellVarContext all !insertmacro EndProcessWithDialog ${If} $MultiUser.InstallMode == "CurrentUser" SetShellVarContext current ${EndIf} DeleteRegKey SHCTX "${uninstkey}" DeleteRegKey SHCTX "${regkey}" !insertmacro MUI_STARTMENU_GETFOLDER Application $StartMenuFolder RMDir /r "$SMPROGRAMS\$StartMenuFolder" RMDir /r "$INSTDIR" SectionEnd +; allow to define additional Un.sections +@{un_sections} \ No newline at end of file diff --git a/bin/Packager/NullsoftInstallerPackager.py b/bin/Packager/NullsoftInstallerPackager.py index afd712ce5..7ec27a959 100644 --- a/bin/Packager/NullsoftInstallerPackager.py +++ b/bin/Packager/NullsoftInstallerPackager.py @@ -1,183 +1,184 @@ # -*- coding: utf-8 -*- # Copyright (c) 2010 Patrick Spendrin # Copyright (c) 2010 Andre Heinecke (code taken from the kdepim-ce-package.py) # Copyright Hannah von Reth # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF # SUCH DAMAGE. import os from Utils import CraftHash from Packager.CollectionPackagerBase import * from Packager.PortablePackager import * from Blueprints.CraftVersion import CraftVersion class NullsoftInstallerPackager(PortablePackager): """ Packager for Nullsoft scriptable install system This Packager generates a nsis installer (an executable which contains all files) from the image directories of craft. This way you can be sure to have a clean installer. In your package, you can add regexp whitelists and blacklists (see example files for the fileformat). The files for both white- and blacklists, must be given already in the constructor. You can override the .nsi default script and you will get the following defines given into the nsis generator via commandline if you do not override the attributes of the same name in the dictionary self.defines: setupname: PACKAGENAME-setup-BUILDTARGET.exe PACKAGENAME is the name of the package srcdir: is set to the image directory, where all files from the image directories of all dependencies are gathered. You shouldn't normally have to set this. company: sets the company name used for the registry key of the installer. Default value is "KDE". productname: contains the capitalized PACKAGENAME and the buildTarget of the current package executable: executable is defined empty by default, but it is used to add a link into the start menu. You can add your own defines into self.defines as well. """ @InitGuard.init_once def __init__(self, whitelists=None, blacklists=None): PortablePackager.__init__(self, whitelists, blacklists) self.nsisExe = None self._isInstalled = False def setDefaults(self, defines) -> {}: defines = super().setDefaults(defines) defines.setdefault("defaultinstdir", "$PROGRAMFILES64" if CraftCore.compiler.isX64() else "$PROGRAMFILES") defines.setdefault("multiuser_use_programfiles64", "!define MULTIUSER_USE_PROGRAMFILES64" if CraftCore.compiler.isX64() else "") defines.setdefault("setupname", self.binaryArchiveName(fileType="exe", includeRevision=True)) defines.setdefault("srcdir", self.archiveDir())# deprecated defines.setdefault("registy_hook", "") defines.setdefault("sections", "") + defines.setdefault("un_sections", "") defines.setdefault("sections_page", "") defines.setdefault("preInstallHook", "") if not self.scriptname: self.scriptname = os.path.join(os.path.dirname(__file__), "Nsis", "NullsoftInstaller.nsi") return defines def isNsisInstalled(self): if not self._isInstalled: self._isInstalled = self.__isInstalled() if not self._isInstalled: CraftCore.log.critical("Craft requires Nsis to create a package, please install Nsis\n" "\t'craft nsis'") return False return True def __isInstalled(self): """ check if nsis (Nullsoft scriptable install system) is installed somewhere """ self.nsisExe = CraftCore.cache.findApplication("makensis") if not self.nsisExe: return False return CraftCore.cache.getVersion(self.nsisExe, versionCommand="/VERSION") >= CraftVersion("3.03") def _createShortcut(self, name, target, icon="", parameter="", description="") -> str: return f"""CreateShortCut "$SMPROGRAMS\$StartMenuFolder\\{name}.lnk" "$INSTDIR\\{OsUtils.toNativePath(target)}" "{parameter}" "{icon}" 0 SW_SHOWNORMAL "" "{description}"\n""" def folderSize(self, path): total = 0 for entry in os.scandir(path): if entry.is_file(): total += entry.stat().st_size elif entry.is_dir(): total += self.folderSize(entry.path) return total def generateNSISInstaller(self): """ runs makensis to generate the installer itself """ defines = self.setDefaults(self.defines) defines["dataPath"] = self.setupName defines["dataName"] = os.path.basename(self.setupName) defines["7za"] = CraftCore.cache.findApplication("7za") if CraftCore.compiler.isX64() else CraftCore.cache.findApplication("7za_32") # provide the actual installation size in kb, ignore the 7z size as it gets removed after the install defines["installSize"] = str(int((self.folderSize(self.archiveDir()) - os.path.getsize(self.setupName)) / 1000)) defines["installerIcon"] = f"""!define MUI_ICON "{defines["icon"]}" """ defines["iconname"] = os.path.basename(defines["icon"]) if not defines["license"] == "": defines["license"] = f"""!insertmacro MUI_PAGE_LICENSE "{defines["license"]}" """ shortcuts = [] if "executable" in defines: shortcuts.append(self._createShortcut(defines["productname"], defines["executable"])) del defines["executable"] for short in self.shortcuts: shortcuts.append(self._createShortcut(**short)) defines["shortcuts"] = "".join(shortcuts) if defines.get("sections", None): defines["sections_page"] = "!insertmacro MUI_PAGE_COMPONENTS" # make absolute path for output file if not os.path.isabs(defines["setupname"]): dstpath = self.packageDestinationDir() defines["setupname"] = os.path.join(dstpath, defines["setupname"]) self.setupName = defines["setupname"] CraftCore.debug.new_line() CraftCore.log.debug(f"generating installer {self.setupName}") verboseString = "/V4" if CraftCore.debug.verbose() > 0 else "/V3" defines.setdefault("nsis_include", f"!addincludedir {os.path.dirname(self.scriptname)}") defines["nsis_include_internal"] = f"!addincludedir {os.path.join(os.path.dirname(__file__), 'Nsis')}" cmdDefines = [] configuredScrip = os.path.join(self.workDir(), f"{self.package.name}.nsi") if not utils.configureFile(self.scriptname, configuredScrip, defines): configuredScrip = self.scriptname # this script uses the old behaviour, using defines for key, value in defines.items(): if value is not None: cmdDefines.append(f"/D{key}={value}") if not utils.systemWithoutShell([self.nsisExe, verboseString] + cmdDefines + [configuredScrip], cwd=os.path.abspath(self.packageDir())): CraftCore.log.critical("Error in makensis execution") return False return utils.sign([self.setupName]) def createPackage(self): """ create a package """ if not self.isNsisInstalled(): return False CraftCore.log.debug("packaging using the NullsoftInstallerPackager") if not super().createPackage(): return False if not self.generateNSISInstaller(): return False destDir, archiveName = os.path.split(self.setupName) self._generateManifest(destDir, archiveName) CraftHash.createDigestFiles(self.setupName) return True