diff --git a/bin/Packager/NullsoftInstallerPackager.py b/bin/Packager/NullsoftInstallerPackager.py index 2746c378f..62d4264ed 100644 --- a/bin/Packager/NullsoftInstallerPackager.py +++ b/bin/Packager/NullsoftInstallerPackager.py @@ -1,211 +1,211 @@ # -*- 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 Blueprints.CraftVersion import CraftVersion class NullsoftInstallerPackager(CollectionPackagerBase): """ 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): CollectionPackagerBase.__init__(self, whitelists, blacklists) self.nsisExe = None self._isInstalled = False def _setDefaults(self, defines): defines = dict(defines) defines.setdefault("architecture", CraftCore.compiler.architecture) defines.setdefault("company", "KDE") defines.setdefault("defaultinstdir", "$PROGRAMFILES64" if CraftCore.compiler.isX64() else "$PROGRAMFILES") defines.setdefault("icon", os.path.join(CraftCore.standardDirs.craftBin(), "data", "icons", "craft.ico")) defines.setdefault("license", "") - defines.setdefault("productname", self.package.name.capitalize()) + defines.setdefault("productname", self.subinfo.displayName if self.subinfo.displayName else self.package.name.capitalize()) defines.setdefault("setupname", self.binaryArchiveName(fileType="exe", includeRevision=True)) defines.setdefault("srcdir", self.archiveDir()) defines.setdefault("version", self.sourceRevision() if self.subinfo.hasSvnTarget() else self.version) defines.setdefault("website", self.subinfo.webpage if self.subinfo.webpage else "https://community.kde.org/Craft") defines.setdefault("registy_hook", "") # runtime distributable files defines.setdefault("vcredist", self.getVCRedistLocation()) if not self.scriptname: self.scriptname = os.path.join(os.path.dirname(__file__), "NullsoftInstaller.nsi") return defines def isNsisInstalled(self): if not self._isInstalled: self._isInstalled = self.__isInstalled() if not self._isInstalled: CraftCore.log.critical("could not find installed nsis package, " "you can install it using craft nsis or" "download and install it from " "http://sourceforge.net/projects/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") @staticmethod def getVCRuntimeLibrariesLocation(): """ Note: For MSVC, only: Return base directory for VC runtime distributable libraries """ if "VCToolsRedistDir" in os.environ: return os.environ["VCToolsRedistDir"] _path = os.path.join(os.path.dirname(shutil.which("cl.exe")), "..", "redist") if not os.path.exists(_path): _path = os.path.join(os.path.dirname(shutil.which("cl.exe")), "..", "..", "redist") return _path @staticmethod def getVCRedistLocation(): if not CraftCore.compiler.isMSVC(): return "none" _file = None if CraftCore.compiler.isMSVC(): arch = "x86" if CraftCore.compiler.isX64(): arch = "x64" # TODO: This needs to be made more fail-safe: the version numbers can change with any VS upgrade... if CraftCore.compiler.isMSVC2015(): _file = os.path.join(NullsoftInstallerPackager.getVCRuntimeLibrariesLocation(), "1033", f"vcredist_{arch}.exe") elif CraftCore.compiler.isMSVC2017(): for name in [f"vcredist_{arch}.exe", f"vc_redist.{arch}.exe"]: _file = os.path.join(os.environ["VCTOOLSREDISTDIR"], name) if os.path.isfile(_file): break if not os.path.isfile(_file): CraftCore.debug.new_line() CraftCore.log.critical( "Assuming we can't find a c++ redistributable because the user hasn't got one. Must be fixed manually.") return _file def _createShortcut(self, name, target, icon="", parameter="", description="") -> str: return f"""CreateShortCut "${{startmenu}}\\{name}.lnk" "$INSTDIR\\{OsUtils.toNativePath(target)}" "{parameter}" "{icon}" 0 SW_SHOWNORMAL "" "{description}"\n""" def generateNSISInstaller(self): """ runs makensis to generate the installer itself """ defines = self._setDefaults(self.defines) 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"]}" """ defines["HKLM"] = "HKLM32" if CraftCore.compiler.isX86() else "HKLM64" defines["HKCR"] = "HKCR32" if CraftCore.compiler.isX86() else "HKCR64" defines["HKCU"] = "HKCU32" if CraftCore.compiler.isX86() else "HKCU64" 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) # make absolute path for output file if not os.path.isabs(defines["setupname"]): dstpath = self.packageDestinationDir() defines["setupname"] = os.path.join(dstpath, defines["setupname"]) CraftCore.debug.new_line() CraftCore.log.debug("generating installer %s" % defines["setupname"]) verboseString = "/V4" if CraftCore.debug.verbose() > 0 else "/V3" 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 None if not utils.sign([defines["setupname"]]): return None return defines["setupname"] def createPackage(self): """ create a package """ if not self.isNsisInstalled(): return False CraftCore.log.debug("packaging using the NullsoftInstallerPackager") if CraftCore.compiler.isMSVC(): # we use the redist installer self.ignoredPackages.append("libs/runtime") if not self.internalCreatePackage(): return False setupname = self.generateNSISInstaller() if not setupname: return False destDir, archiveName = os.path.split(setupname) self._generateManifest(destDir, archiveName) CraftHash.createDigestFiles(setupname) return True diff --git a/bin/Packager/QtIFPackager.py b/bin/Packager/QtIFPackager.py index d59441a7e..a9b2ddb4b 100644 --- a/bin/Packager/QtIFPackager.py +++ b/bin/Packager/QtIFPackager.py @@ -1,115 +1,115 @@ # -*- coding: utf-8 -*- # 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 datetime import cgi from Packager.SevenZipPackager import * from Utils import CraftHash from Blueprints.CraftVersion import * from Package.VirtualPackageBase import VirtualPackageBase from CraftOS.osutils import OsUtils class QtIFPackager(SevenZipPackager): @InitGuard.init_once def __init__(self): SevenZipPackager.__init__(self) self.__resources = os.path.join(os.path.dirname(__file__), "QtIF") self.__packageDir = os.path.join(self.packageDestinationDir(), "qtif") self.__sdkMode = OsUtils.isWin() and CraftCore.settings.getboolean("QtSDK", "Enabled", False) if self.__sdkMode: win = "win32" if CraftCore.compiler.isX86() else "win64" self.__depPrefix = f"qt.qt5.{CraftCore.settings.get('QtSDK', 'Version').replace('.', '')}.{win}_{CraftCore.settings.get('QtSDK', 'Compiler')}.kde" @property def _date(self): return datetime.datetime.utcnow().strftime("%Y-%m-%d") def qtifyFy(self, package): if not self.__sdkMode: path = f"kde.{package.path}" else: path = f"{self.__depPrefix}.{package.name}" return path.replace("/", ".").replace("-", "_") def _addPackage(self) -> bool: if self.__sdkMode: imagePrefix = os.path.join(CraftCore.settings.get("QtSDK", "Version"), CraftCore.settings.get("QtSDK", "Compiler")) utils.cleanDirectory(self.archiveDir()) utils.copyDir(self.imageDir(), os.path.join(self.archiveDir(), imagePrefix)) dstpath = os.path.join(self.__packageDir, "image", self.qtifyFy(self.package)) if not self._compress("data.7z", self.imageDir() if not self.__sdkMode else self.archiveDir(), os.path.join(dstpath, "data"), createDigests=False): return False deps = [] for x in self.subinfo.runtimeDependencies.keys(): # TODO: # this filter not installed packages but also packages that are virtual on this platform package = CraftPackageObject.get(x) if isinstance(package.instance, VirtualPackageBase): continue if os.path.exists(package.instance.imageDir()): deps += [self.qtifyFy(package)] data = {"VERSION" : str(CraftVersion(self.version).strictVersion), - "NAME" : self.package.name, + "NAME" : self.subinfo.displayName if self.subinfo.displayName else self.package.name, "DESCRIPTION" : cgi.escape(f"{self.package.name} {self.version}
{self.subinfo.description}" + (f"
{self.subinfo.webpage}" if self.subinfo.webpage else "")), "DATE" : self._date, "DEPENDENCIES" : ", ".join(deps)} if not utils.configureFile(os.path.join(self.__resources, "package.xml"), os.path.join(dstpath, "meta", "package.xml"), data): return False #data = {} #if not utils.configureFile(os.path.join(resources, "installscript.qs"), os.path.join(dstpath, "meta", "installscript.qs"), data): if not utils.copyFile(os.path.join(self.__resources, "installscript.qs"), os.path.join(dstpath, "meta", "installscript.qs"), linkOnly=False): return False return True def __initKDEPrefix(self): data = {"VERSION" : "0.0", "NAME" : "KDE", "DESCRIPTION" : cgi.escape(f"KDE
The KDE Community is a free software community dedicated to creating an open and user-friendly computing experience, offering an advanced graphical desktop, a wide variety of applications for communication, work, education and entertainment and a platform to easily build new applications upon. We have a strong focus on finding innovative solutions to old and new problems, creating a vibrant atmosphere open for experimentation.
https://www.kde.org/"), "DATE" : self._date, "DEPENDENCIES" : ""} dstpath = os.path.join(self.__packageDir, "image", (self.__depPrefix if self.__sdkMode else "kde")) if not utils.configureFile(os.path.join(self.__resources, "package.xml"), os.path.join(dstpath, "meta", "package.xml"), data): return False return utils.copyFile(os.path.join(self.__resources, "installscript.qs"), os.path.join(dstpath, "meta", "installscript.qs"), linkOnly=False) def createPackage(self): if not self.__initKDEPrefix(): return False return self._addPackage() diff --git a/bin/info.py b/bin/info.py index f270aa46b..0a3f1e03a 100644 --- a/bin/info.py +++ b/bin/info.py @@ -1,256 +1,257 @@ ## # # @package this module contains the information class # the current work here is to access members only # by methods to be able to separate the access from # the definition import VersionInfo from Utils import CraftHash, CraftManifest from options import * from CraftDebug import deprecated class infoclass(object): """this module contains the information class""" def __init__(self, parent): ### package options self.parent = parent self.options = Options(parent.package) self.versionInfo = VersionInfo.VersionInfo(self) self.targets = {} self.archiveNames = {} # Specifiy that the fetched source should be placed into a # subdirectory of the default source directory self.targetInstSrc = {} # Specifiy that the default source directory should have a suffix after # the package name. This is usefull for package which needs different sources. self.targetSrcSuffix = {} self.targetConfigurePath = {} self.targetInstallPath = {} self.targetDigests = {} self.targetDigestsX64 = {} self.targetDigestUrls = {} ## \todo prelimary self.svnTargets = {} self.patchLevel = {} # runtimeDependencies and buildDependencies are not different when looking # at the build process itself, they will only make a difference when getting # output of the dependencies self.runtimeDependencies = {} self.buildDependencies = {} self.packagingDependencies = {} + self.displayName = "" self.description = "" # tag words describing the package self.tags = "" # a url to the projects webpage self.webpage = "" self.patchToApply = {} # key: target. Value: list(['patchname', patchdepth]) or ('patchname',patchdepth) self.svnTargets = {} self.svnServer = None # this will result in the use of the default server (either anonsvn.kde.org or svn.kde.org) self._defaultTarget = None self.buildTarget = "" self.registerOptions() self.setTargets() self.setBuildTarget() self.setBuildOptions() # do this after buildTarget is set so that some dependencies can be set depending on self.buildTarget self.setDependencies() @property @deprecated("self.parent") def package(self) -> CraftPackageObject: return self.parent @property def defaultTarget(self) -> str: target = self.options.dynamic.version # TODO: legacy behaviour if ("BlueprintVersions", self.parent.package.path) in CraftCore.settings: target = CraftCore.settings.get("BlueprintVersions", self.parent.package.path) CraftCore.log.warning(f"You are using the depreaced:\n" f"[BlueprintVersions]\n" f"{self.parent.package.path} = {target}\n\n" f"Please use CraftOptions.ini\n" f"[{self.parent.package.path}]\n" f"version = {target}") if target in self.targets or target in self.svnTargets: return target if target: CraftCore.log.warning(f"You defined an invalid target for {self.parent.package.path}") return self._defaultTarget @defaultTarget.setter def defaultTarget(self, value): self._defaultTarget = value def registerOptions(self): """calls to self.options.dynamic.registerOption #self.options.dynamic.registerOption("fullKDevelop", False) """ pass def setDependencies(self): """default method for setting dependencies, override to set individual targets""" def setTargets(self): """default method for setting targets, override to set individual targets""" def setBuildTarget(self, buildTarget=None): """setup current build target""" self.buildTarget = self.defaultTarget if not buildTarget == None: self.buildTarget = buildTarget if not self.buildTarget in self.targets and not self.buildTarget in self.svnTargets: self.buildTarget = self.defaultTarget def setBuildOptions(self): """default method for setting build options, override to set individual targets""" return def hasTarget(self) -> bool: """return true if archive targets for the currently selected build target is available""" return self.buildTarget in self.targets def target(self) -> str: """return archive target""" if self.buildTarget in self.targets: return self.targets[self.buildTarget] return "" def archiveName(self) -> [str]: """returns the archive file name""" if self.buildTarget in self.archiveNames: return self.archiveNames[self.buildTarget] if type(self.targets[self.buildTarget]) == list: return [os.path.split(x)[-1] for x in self.targets[self.buildTarget]] else: return [os.path.split(self.targets[self.buildTarget])[-1]] def hasSvnTarget(self) -> bool: """return true if version system based target for the currently selected build target is available""" return self.buildTarget in self.svnTargets def svnTarget(self) -> str: """return version system based target for the currently selected build target""" if self.buildTarget in self.svnTargets: return self.svnTargets[self.buildTarget] return "" def targetSourceSuffix(self) -> str: """return local source path suffix for the recent target""" if self.buildTarget in self.targetSrcSuffix: return self.targetSrcSuffix[self.buildTarget] def hasTargetSourcePath(self) -> bool: """return true if relative path appendable to local source path is given for the recent target""" return self.buildTarget in self.targetInstSrc def targetSourcePath(self) -> str: """return relative path appendable to local source path for the recent target""" if self.buildTarget in self.targetInstSrc: return self.targetInstSrc[self.buildTarget] def hasConfigurePath(self) -> bool: """return true if relative path appendable to local source path is given for the recent target""" return self.buildTarget in self.targetConfigurePath def configurePath(self) -> str: """return relative path appendable to local source path for the recent target""" if (self.hasTarget() or self.hasSvnTarget()) and \ self.buildTarget in self.targetConfigurePath: return self.targetConfigurePath[self.buildTarget] def hasInstallPath(self) -> bool: """return true if relative path appendable to local install path is given for the recent target""" return self.buildTarget in self.targetInstallPath def installPath(self) -> str: """return relative path appendable to local install path for the recent target""" if self.buildTarget in self.targetInstallPath: return self.targetInstallPath[self.buildTarget] CraftCore.log.critical("no install path for this build target defined") def hasPatches(self) -> bool: """return state for having patches for the recent target""" return (self.hasTarget() or self.hasSvnTarget()) and self.buildTarget in self.patchToApply def patchesToApply(self) -> [tuple]: """return patch informations for the recent build target""" if self.hasPatches(): out = self.patchToApply[self.buildTarget] return out if type(out) == list else [out] return [("", "")] def hasTargetDigests(self) -> bool: """return state if target has digest(s) for the recent build target""" if CraftCore.compiler.isX64() and self.buildTarget in self.targetDigestsX64: return True return self.buildTarget in self.targetDigests def targetDigest(self) -> ([str], CraftHash.HashAlgorithm): """return digest(s) for the recent build target. The return value could be a string or a list""" if self.hasTargetDigests(): if CraftCore.compiler.isX64() and self.buildTarget in self.targetDigestsX64: out = self.targetDigestsX64[self.buildTarget] else: out = self.targetDigests[self.buildTarget] if type(out) == str: out = [out] if not type(out) == tuple: out = (out, CraftHash.HashAlgorithm.SHA1) return out return None def hasTargetDigestUrls(self) -> bool: """return state if target has digest url(s) for the recent build target""" return self.buildTarget in self.targetDigestUrls def targetDigestUrl(self) -> ([str], CraftHash.HashAlgorithm): """return digest url(s) for the recent build target. The return value could be a string or a list""" if self.hasTargetDigestUrls(): out = self.targetDigestUrls[self.buildTarget] if type(out) == str: out = [out] if not type(out) == tuple: out = (out, CraftHash.HashAlgorithm.getAlgorithmFromFile(out[0])) return out return None def addCachedAutotoolsBuild(self, packageName): if not CraftCore.compiler.isMSVC(): return self.versionInfo.setDefaultValues() package = CraftPackageObject._allLeaves.get(packageName, None) if not package: CraftCore.log.warning(f"Failed to find {packageName}") return False packageName = package.path if package: self.description = package.subinfo.description for key, url in self.targets.items(): if url.endswith("/"): url = url[:-1] json = CraftCore.cache.cacheJsonFromUrl(f"{url}/manifest.json") if not json: raise BlueprintException("Failed to load manifest", package) manifest = CraftManifest.CraftManifest.fromJson(json) if not packageName in manifest.packages[f"windows-mingw_{CraftCore.compiler.bits}-gcc"]: CraftCore.log.warning(f"Failed to find {packageName} on {url}") return data = manifest.packages[f"windows-mingw_{CraftCore.compiler.bits}-gcc"][packageName].latest self.targets[key] = f"{url}/{data.fileName}" self.targetDigests[key] = (data.checksum, CraftHash.HashAlgorithm.SHA256)