def copyFiles(self, srcDir, destDir) -> bool: """ Copy the binaries for the Package from srcDir to the imageDir directory """ CraftCore.log.debug("Copying %s -> %s" % (srcDir, destDir)) filesToSign = [] # Only sign all files on Windows. On MacOS we recursively sign the whole .app Folder doSign = CraftCore.compiler.isWindows and CraftCore.settings.getboolean("CodeSigning", "Enabled", False) if doSign and CraftCore.settings.getboolean("CodeSigning", "SignCache", False): # files from the cache are already signed doSign = os.path.samefile(srcDir, self.imageDir()) for entry in utils.filterDirectoryContent(srcDir, self.whitelisted, self.blacklisted, handleAppBundleAsFile=True): if not self._filterQtBuildType(entry): continue entry_target = os.path.join(destDir, os.path.relpath(entry, srcDir)) if os.path.isfile(entry) or os.path.islink(entry): if not utils.copyFile(entry, entry_target, linkOnly=False): return False if utils.isBinary(entry_target): if doSign: filesToSign.append(entry_target) else: # .app or .dSYM assert CraftCore.compiler.isMacOS if not utils.copyDir(entry, entry_target, linkOnly=False): return False if filesToSign: if not CodeSign.signWindows(filesToSign): return False return True
def createPackage(self): """ create a package """ CraftCore.log.debug("packaging using the MacDMGPackager") defines = self.setDefaults(self.defines) # TODO: provide an image with dbg files if not self.internalCreatePackage(defines): return False appPath = self.getMacAppPath(defines) if not appPath: return False archive = os.path.normpath(self.archiveDir()) CraftCore.log.info(f"Packaging {appPath}") dmgDest = defines["setupname"] if os.path.exists(dmgDest): utils.deleteFile(dmgDest) appName = defines['appname'] + ".app" if not utils.system(["create-dmg", "--volname", os.path.basename(dmgDest), # Add a drop link to /Applications: "--icon", appName, "140", "150", "--app-drop-link", "350", "150", dmgDest, appPath]): return False if not CodeSign.signMacPackage(dmgDest): return False CraftHash.createDigestFiles(dmgDest) return True
def createPackage(self): """ create a package """ CraftCore.log.debug("packaging using the MacDMGPackager") if not CraftCore.cache.findApplication("packagesutil"): CraftCore.log.critical( "Craft requires dev-utils/packagesdev to create a package, please install dev-utils/packagesdev\n" "\t'craft dev-utils/packagesdev'") return False defines = self.setDefaults(self.defines) if not "pkgproj" in defines: CraftCore.log.error( "Cannot not create .pkg because no .pkgproj was defined.") return False if not self.internalCreatePackage( defines, seperateSymbolFiles=True, packageSymbols=True): return False packageDest = Path(defines["setupname"]) if packageDest.exists(): utils.deleteFile(packageDest) pkgprojPath = defines["pkgproj"] # set output file basename packagesutil = CraftCore.cache.findApplication("packagesutil") if not utils.system([ packagesutil, '--file', pkgprojPath, 'set', 'project', 'name', packageDest.stem ]): return False packagesbuild = CraftCore.cache.findApplication("packagesbuild") if not utils.system([ packagesbuild, "-v", '--reference-folder', os.path.dirname(self.getMacAppPath(defines)), '--build-folder', packageDest.parent, pkgprojPath ]): return False if not CodeSign.signMacPackage(packageDest): return False CraftHash.createDigestFiles(packageDest) return True
def __createSideloadAppX(self, defines) -> bool: def appendToPublisherString(publisher: [str], field: str, key: str) -> None: data = CraftCore.settings.get("CodeSigning", key, "") if data: publisher += [f"{field}={data}"] publisher = [] appendToPublisherString(publisher, "CN", "CommonName") appendToPublisherString(publisher, "O", "Organization") appendToPublisherString(publisher, "STREET", "Street") appendToPublisherString(publisher, "L", "Locality") appendToPublisherString(publisher, "S", "State") appendToPublisherString(publisher, "PostalCode", "PostalCode") appendToPublisherString(publisher, "C", "Country") defines["publisher"] = ", ".join(publisher) setupName = os.path.join(self.packageDestinationDir(), "{0}-sideload{1}".format(*os.path.splitext(os.path.basename(defines["setupname"])))) defines["setupname"] = setupName return self.__createAppX(defines) and CodeSign.signWindows([setupName])
def generateNSISInstaller(self, defines): """ runs makensis to generate the installer itself """ defines["dataPath"] = defines["setupname"] defines["dataName"] = os.path.basename(defines["dataPath"]) defines["setupname"] = str(Path(defines["setupname"]).with_suffix(".exe")) 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(defines["dataPath"])) / 1000)) defines["estimated_size"] = str(int(int(os.path.getsize(defines["dataPath"])) / 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"]}" """ if not defines["readme"] == "": defines["readme"] = f"""!insertmacro MUI_FINISHPAGE_SHOWREADME "{defines["readme"]}" """ shortcuts = [] if "executable" in defines: shortcuts.append(self._createShortcut(defines["productname"], defines["executable"])) del defines["executable"] for short in defines["shortcuts"]: shortcuts.append(self._createShortcut(**short)) if shortcuts: defines["shortcuts"] = NullsoftInstallerPackager.SHORTCUT_SECTION.format(shortcuts = "".join(shortcuts)) if defines.get("sections", None): defines["sections_page"] = "!insertmacro MUI_PAGE_COMPONENTS" uninstallDirs = set() uninstallFiles = ["\\uninstall.exe", f"\\{defines['iconname']}"] prefixLength = len(self.archiveDir()) for f in utils.filterDirectoryContent(self.archiveDir()): f = Path(f[prefixLength:]) uninstallFiles.append(f) d = f.parent while d not in uninstallDirs: uninstallDirs.add(d) d = d.parent defines["uninstallFiles"] = "\n".join([f"Delete \"$INSTDIR{f}\"" for f in uninstallFiles]) defines["uninstallDirs"] = "\n".join([f"RMDir \"$INSTDIR{x}\"" for x in sorted(uninstallDirs, reverse=True)]) CraftCore.debug.new_line() CraftCore.log.debug(f"generating installer {defines['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 CodeSign.signWindows([defines["setupname"]])
def internalCreatePackage(self, defines, seperateSymbolFiles=False, packageSymbols=False): """ create a package """ CraftCore.log.debug("packaging using the MacDMGPackager") # TODO: provide an image with dbg files if not super().internalCreatePackage( defines, seperateSymbolFiles=seperateSymbolFiles, packageSymbols=packageSymbols): return False appPath = self.getMacAppPath(defines) if not appPath: return False archive = Path(self.archiveDir()) CraftCore.log.info(f"Packaging {appPath}") CraftCore.log.info("Clean up frameworks") for framework in utils.filterDirectoryContent( archive / "lib", handleAppBundleAsFile=True, whitelist=lambda x, root: x.name.endswith(".framework"), blacklist=lambda x, root: True): rubbish = [] framework = Path(framework) rubbish += glob.glob(str(framework / "*.prl")) rubbish += glob.glob(str(framework / "Headers")) for r in rubbish: r = Path(r) if r.is_symlink(): if framework not in r.parents: raise Exception(f"Evil symlink detected: {r}") utils.deleteFile(r) r = r.resolve() if r.is_dir(): utils.rmtree(r) else: utils.deleteFile(r) targetLibdir = os.path.join(appPath, "Contents", "Frameworks") utils.createDir(targetLibdir) moveTargets = [ (archive / "lib/plugins", appPath / "Contents/PlugIns"), (archive / "plugins", appPath / "Contents/PlugIns"), (archive / "share", appPath / "Contents/Resources"), (archive / "qml", appPath / "Contents/Resources/qml"), (archive / "translations", appPath / "Contents/Resources/Translations"), (archive / "bin", appPath / "Contents/MacOS"), (archive / "libexec", appPath / "Contents/MacOS"), (archive / "lib/libexec/kf5", appPath / "Contents/MacOS"), (archive / "lib/libexec", appPath / "Contents/MacOS"), (archive / "lib", targetLibdir), ] if archive not in appPath.parents: moveTargets += [(os.path.join(archive, "bin"), os.path.join(appPath, "Contents", "MacOS"))] for src, dest in moveTargets: if os.path.exists(src): if not utils.mergeTree(src, dest): return False dylibbundler = MacDylibBundler(appPath) with utils.ScopedEnv({ 'DYLD_FALLBACK_LIBRARY_PATH': targetLibdir + ":" + os.path.join(CraftStandardDirs.craftRoot(), "lib") }): CraftCore.log.info("Bundling main binary dependencies...") mainBinary = Path(appPath, "Contents", "MacOS", defines['appname']) if not dylibbundler.bundleLibraryDependencies(mainBinary): return False binaries = list( utils.filterDirectoryContent( os.path.join(appPath, "Contents", "MacOS"), whitelist=lambda x, root: utils.isBinary( x.path) and x.name != defines["appname"], blacklist=lambda x, root: True)) for binary in binaries: CraftCore.log.info(f"Bundling dependencies for {binary}...") binaryPath = Path(binary) if not dylibbundler.bundleLibraryDependencies(binaryPath): return False # Fix up the library dependencies of files in Contents/Frameworks/ CraftCore.log.info("Bundling library dependencies...") if not dylibbundler.fixupAndBundleLibsRecursively( "Contents/Frameworks"): return False CraftCore.log.info("Bundling plugin dependencies...") if not dylibbundler.fixupAndBundleLibsRecursively( "Contents/PlugIns"): return False macdeployqt_multiple_executables_command = [ "macdeployqt", appPath, "-always-overwrite", "-verbose=1" ] for binary in binaries: macdeployqt_multiple_executables_command.append( f"-executable={binary}") if "qmldirs" in self.defines.keys() and isinstance( self.defines["qmldirs"], list): for qmldir in self.defines["qmldirs"]: macdeployqt_multiple_executables_command.append( f"-qmldir={qmldir}") if not utils.system(macdeployqt_multiple_executables_command): return False # macdeployqt might just have added some explicitly blacklisted files blackList = Path(self.packageDir(), "mac_blacklist.txt") if blackList.exists(): pattern = [self.read_blacklist(str(blackList))] # use it as whitelist as we want only matches, ignore all others matches = utils.filterDirectoryContent( appPath, whitelist=lambda x, root: utils.regexFileFilter( x, root, pattern), blacklist=lambda x, root: True) for f in matches: CraftCore.log.info(f"Remove blacklisted file: {f}") utils.deleteFile(f) # macdeployqt adds some more plugins so we fix the plugins after calling macdeployqt dylibbundler.checkedLibs = set( ) # ensure we check all libs again (but # we should not need to make any changes) CraftCore.log.info( "Fixing plugin dependencies after macdeployqt...") if not dylibbundler.fixupAndBundleLibsRecursively( "Contents/PlugIns"): return False CraftCore.log.info( "Fixing library dependencies after macdeployqt...") if not dylibbundler.fixupAndBundleLibsRecursively( "Contents/Frameworks"): return False # Finally sanity check that we don't depend on absolute paths from the builder CraftCore.log.info( "Checking for absolute library paths in package...") found_bad_dylib = False # Don't exit immeditately so that we log all the bad libraries before failing: if not dylibbundler.areLibraryDepsOkay(mainBinary): found_bad_dylib = True CraftCore.log.error( "Found bad library dependency in main binary %s", mainBinary) for binary in binaries: binaryPath = Path(binary) if not dylibbundler.areLibraryDepsOkay(binaryPath): found_bad_dylib = True CraftCore.log.error( "Found bad library dependency in binary %s", binaryPath) if not dylibbundler.checkLibraryDepsRecursively( "Contents/Frameworks"): CraftCore.log.error( "Found bad library dependency in bundled libraries") found_bad_dylib = True if not dylibbundler.checkLibraryDepsRecursively( "Contents/PlugIns"): CraftCore.log.error( "Found bad library dependency in bundled plugins") found_bad_dylib = True if found_bad_dylib: CraftCore.log.error( "Cannot not create .dmg since the .app contains a bad library depenency!" ) return False return CodeSign.signMacApp(appPath)
def generateInnoInstaller(self, defines): """ runs ISCC to generate the installer itself """ defines["setupname"] = str(Path(defines["setupname"]).with_suffix(".exe")) if not defines["license"] == "": defines["license"] = f"""LicenseFile="{defines["license"]}" """ if not defines["readme"] == "": defines["readme"] = f"""InfoBeforeFile="{defines["readme"]}" """ shortcuts = [] if "executable" in defines: shortcuts.append(self._createShortcut(defines["productname"], defines["executable"])) else: # Needed below, for registering file associations # This will error out, if neither executable, nor any shortcuts have been set, but that is certainly an error, anyway defines["executable"] = defines["shortcuts"][0]["target"] for short in defines["shortcuts"]: shortcuts.append(self._createShortcut(**short)) if shortcuts: defines["shortcuts"] = "".join(shortcuts) tasks = [] registry_keys = [] for key in defines["registry_keys"]: registry_keys.append(f"""Root: HKA; Subkey: ; ValueType: string; ValueName: "{key["name"]}"; ValueData: "{key["value"]}" """) for ftype in defines["file_types"]: ftype_id = self.subinfo.displayName + ftype ftype_id = ftype_id.replace(".", "_") tasks.append(f"""Name: "{ftype_id}"; Description: "{{cm:AssocFileExtension,{self.subinfo.displayName},{ftype}}}";""") registry_keys.append(f"""Root: HKA; Subkey: "Software\\Classes\\{ftype}\\OpenWithProgids"; ValueType: string; ValueName: "{ftype_id}"; ValueData: ""; Flags: uninsdeletevalue ; Tasks: {ftype_id}""") registry_keys.append(f"""Root: HKA; Subkey: "Software\\Classes\\{ftype_id}"; ValueType: string; ValueName: ""; ValueData: "{ftype} file"; Flags: uninsdeletekey ; Tasks: {ftype_id}""") registry_keys.append(f"""Root: HKA; Subkey: "Software\\Classes\\{ftype_id}\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{{app}}\\{defines["executable"]},0" ; Tasks: {ftype_id}""") registry_keys.append(f"""Root: HKA; Subkey: "Software\\Classes\\{ftype_id}\\shell\\open\\command"; ValueType: string; ValueName: ""; ValueData: \"""{{app}}\\{defines["executable"]}"" ""%1""\" ; Tasks: {ftype_id}""") registry_keys.append(f"""Root: HKA; Subkey: "Software\\Classes\\Applications\\{os.path.basename(defines["executable"])}\\SupportedTypes"; ValueType: string; ValueName: "{ftype}"; ValueData: "" ; Tasks: {ftype_id}""") if registry_keys: defines["registry"] = "[Registry]\n" + "\n".join(registry_keys) defines["associations"] = "ChangesAssociations=yes" else: defines["registry"] = "" defines["associations"] = "" if tasks: defines["tasks"] = "[TASKS]\n" + "\n".join(tasks) else: defines["tasks"] = "" CraftCore.debug.new_line() CraftCore.log.debug(f"generating installer {defines['setupname']}") verboseString = "/V4" if CraftCore.debug.verbose() > 0 else "/V1" cmdDefines = [] cmdDefines.append(f"""/O{os.path.dirname(defines["setupname"])}""") cmdDefines.append(f"""/F{str(Path(os.path.basename(defines["setupname"])).with_suffix(""))}""") configuredScrip = os.path.join(self.workDir(), f"{self.package.name}.iss") utils.configureFile(self.scriptname, configuredScrip, defines) if not utils.systemWithoutShell([self.innoExe, verboseString] + cmdDefines + [configuredScrip], cwd=os.path.abspath(self.packageDir())): CraftCore.log.critical("Error in ISCC execution") return False return CodeSign.signWindows([defines["setupname"]])
def internalPostInstall(self): if not super().internalPostInstall(): return False # fix absolute symlinks for sym in utils.filterDirectoryContent(self.installDir(), lambda x, root: x.is_symlink(), lambda x, root: True, allowBadSymlinks=True): target = Path(os.readlink(sym)) if target.is_absolute(): sym = Path(sym) target = Path(self.imageDir()) / target.relative_to( CraftCore.standardDirs.craftRoot()) sym.unlink() # we can't use relative_to here sym.symlink_to(os.path.relpath(target, sym.parent)) # a post install routine to fix the prefix (make things relocatable) newPrefix = OsUtils.toUnixPath(CraftCore.standardDirs.craftRoot()) oldPrefixes = [self.subinfo.buildPrefix] if CraftCore.compiler.isWindows: oldPrefixes += [OsUtils.toMSysPath(self.subinfo.buildPrefix)] files = utils.filterDirectoryContent( self.installDir(), whitelist=lambda x, root: Path( x).suffix in BuildSystemBase.PatchableFile, blacklist=lambda x, root: True) if not self.patchInstallPrefix(files, oldPrefixes, newPrefix): return False binaryFiles = list( utils.filterDirectoryContent( self.installDir(), lambda x, root: utils.isBinary(x.path), lambda x, root: True)) if (CraftCore.compiler.isMacOS and os.path.isdir(self.installDir())): for f in binaryFiles: if os.path.islink(f): continue # replace the old prefix or add it if missing craft_rpath = os.path.join(newPrefix, "lib") if not utils.system([ "install_name_tool", "-rpath", os.path.join(self.subinfo.buildPrefix, "lib"), craft_rpath, f ], logCommand=False, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL): CraftCore.log.info(f"Adding rpath {craft_rpath} to {f}") utils.system( ["install_name_tool", "-add_rpath", craft_rpath, f], logCommand=False) # update prefix if self.subinfo.buildPrefix != newPrefix: if os.path.splitext(f)[1] in {".dylib", ".so"}: # fix dylib id with io.StringIO() as log: utils.system(["otool", "-D", f], stdout=log, logCommand=False) oldId = log.getvalue().strip().split("\n") # the first line is the file name # the second the id, if we only get one line, there is no id to fix if len(oldId) == 2: oldId = oldId[1].strip() newId = oldId.replace(self.subinfo.buildPrefix, newPrefix) if newId != oldId: if not utils.system( ["install_name_tool", "-id", newId, f], logCommand=False): return False # fix dependencies for dep in utils.getLibraryDeps(f): if dep.startswith(self.subinfo.buildPrefix): newDep = dep.replace(self.subinfo.buildPrefix, newPrefix) if newDep != dep: if not utils.system([ "install_name_tool", "-change", dep, newDep, f ], logCommand=False): return False # Install pdb files on MSVC if they are not found next to the dll # skip if we are a release build or from cache if not self.subinfo.isCachedBuild: if self.buildType() in {"RelWithDebInfo", "Debug"}: if CraftCore.compiler.isMSVC(): for f in binaryFiles: if not os.path.exists(f"{os.path.splitext(f)[0]}.pdb"): pdb = utils.getPDBForBinary(f) if not pdb: CraftCore.log.warning( f"Could not find a PDB for {f}") continue if not os.path.exists(pdb): CraftCore.log.warning( f"PDB {pdb} for {f} does not exist") continue pdbDestination = os.path.join( os.path.dirname(f), os.path.basename(pdb)) CraftCore.log.info( f"Install pdb: {pdbDestination} for {os.path.basename(f)}" ) utils.copyFile(pdb, pdbDestination, linkOnly=False) else: if not self.subinfo.options.package.disableStriping: for f in binaryFiles: utils.strip(f) # sign the binaries if we can if CraftCore.compiler.isWindows and CraftCore.settings.getboolean( "CodeSigning", "SignCache", False): if not CodeSign.signWindows(binaryFiles): return False return True
def internalCreatePackage(self, defines, seperateSymbolFiles=False, packageSymbols=False): """ create a package """ CraftCore.log.debug("packaging using the MacDMGPackager") # TODO: provide an image with dbg files if not super().internalCreatePackage( defines, seperateSymbolFiles=seperateSymbolFiles, packageSymbols=packageSymbols): return False appPath = self.getMacAppPath(defines) if not appPath: return False archive = Path(self.archiveDir()) CraftCore.log.info(f"Packaging {appPath}") CraftCore.log.info("Clean up frameworks") for framework in utils.filterDirectoryContent( archive / "lib", handleAppBundleAsFile=True, whitelist=lambda x, root: x.name.endswith(".framework"), blacklist=lambda x, root: True): rubbish = [] framework = Path(framework) rubbish += framework.rglob("*.prl") rubbish += framework.rglob("Headers") for r in rubbish: r = Path(r) if r.is_symlink(): if framework not in r.parents: raise Exception(f"Evil symlink detected: {r}") utils.deleteFile(r) r = r.resolve() if r.is_dir(): utils.rmtree(r) else: utils.deleteFile(r) targetLibdir = os.path.join(appPath, "Contents", "Frameworks") utils.createDir(targetLibdir) moveTargets = [ (archive / "lib/plugins", appPath / "Contents/PlugIns"), (archive / "plugins", appPath / "Contents/PlugIns"), (archive / "share", appPath / "Contents/Resources"), (archive / "qml", appPath / "Contents/Resources/qml"), (archive / "translations", appPath / "Contents/Resources/Translations"), (archive / "bin", appPath / "Contents/MacOS"), (archive / "libexec", appPath / "Contents/MacOS"), (archive / "lib/libexec/kf5", appPath / "Contents/MacOS"), (archive / "lib/libexec", appPath / "Contents/MacOS"), (archive / "lib", targetLibdir), ] self._addQtConf(appPath) if archive not in appPath.parents: moveTargets += [(os.path.join(archive, "bin"), os.path.join(appPath, "Contents", "MacOS"))] for src, dest in moveTargets: if os.path.exists(src): if not utils.mergeTree(src, dest): return False dylibbundler = MacDylibBundler(appPath, self.externalLibs) CraftCore.log.info("Bundling main binary dependencies...") binaries = list( utils.filterDirectoryContent( os.path.join(appPath, "Contents"), whitelist=lambda x, root: utils.isBinary(x.path), blacklist=lambda x, root: True)) for binary in binaries: CraftCore.log.info(f"Bundling dependencies for {binary}...") binaryPath = Path(binary) if not dylibbundler.bundleLibraryDependencies(binaryPath): return False # Finally sanity check that we don't depend on absolute paths from the builder CraftCore.log.info("Checking for absolute library paths in package...") found_bad_dylib = False # Don't exit immeditately so that we log all the bad libraries before failing: for binary in binaries: binaryPath = Path(binary) if not dylibbundler.areLibraryDepsOkay(binaryPath): found_bad_dylib = True CraftCore.log.error( "Found bad library dependency in binary %s", binaryPath) if found_bad_dylib: CraftCore.log.error( "Cannot not create .dmg since the .app contains a bad library depenency!" ) return False return CodeSign.signMacApp(appPath)