diff --git a/bin/BuildSystem/BuildSystemBase.py b/bin/BuildSystem/BuildSystemBase.py index 5bed6971d..27f5d06e8 100644 --- a/bin/BuildSystem/BuildSystemBase.py +++ b/bin/BuildSystem/BuildSystemBase.py @@ -1,255 +1,255 @@ # # copyright (c) 2009 Ralf Habacker # """ \package BuildSystemBase""" import glob import io import multiprocessing import os import re import subprocess from CraftBase import * from CraftOS.osutils import OsUtils class BuildSystemBase(CraftBase): """provides a generic interface for build systems and implements all stuff for all build systems""" debug = True def __init__(self, typeName=""): """constructor""" CraftBase.__init__(self) self.supportsNinja = False self.supportsCCACHE = CraftCore.settings.getboolean("Compile", "UseCCache", False) and CraftCore.compiler.isGCCLike() self.supportsClang = True self.buildSystemType = typeName @property def makeProgram(self) -> str: if self.subinfo.options.make.supportsMultijob: if self.supportsNinja and CraftCore.settings.getboolean("Compile", "UseNinja", False): return "ninja" if ("Compile", "MakeProgram") in CraftCore.settings: CraftCore.log.debug("set custom make program: %s" % CraftCore.settings.get("Compile", "MakeProgram", "")) return CraftCore.settings.get("Compile", "MakeProgram", "") elif not self.subinfo.options.make.supportsMultijob: if "MAKE" in os.environ: del os.environ["MAKE"] if OsUtils.isWin(): if CraftCore.compiler.isMSVC() or CraftCore.compiler.isIntel(): return "nmake" elif CraftCore.compiler.isMinGW(): return "mingw32-make" else: CraftCore.log.critical(f"unknown {CraftCore.compiler} compiler") elif OsUtils.isUnix(): return "make" def compile(self): """convencience method - runs configure() and make()""" configure = getattr(self, 'configure') make = getattr(self, 'make') return configure() and make() def configureSourceDir(self): """returns source dir used for configure step""" # pylint: disable=E1101 # this class never defines self.source, that happens only # in MultiSource. sourcedir = self.sourceDir() if self.subinfo.hasConfigurePath(): sourcedir = os.path.join(sourcedir, self.subinfo.configurePath()) return sourcedir def configureOptions(self, defines=""): """return options for configure command line""" if self.subinfo.options.configure.args != None: defines += " %s" % self.subinfo.options.configure.args if self.supportsCCACHE: defines += " %s" % self.ccacheOptions() if CraftCore.compiler.isClang() and self.supportsClang: defines += " %s" % self.clangOptions() return defines def makeOptions(self, args): """return options for make command line""" defines = [] if self.subinfo.options.make.ignoreErrors: defines.append("-i") if self.makeProgram == "ninja": if CraftCore.debug.verbose() > 0: defines.append("-v") else: if self.subinfo.options.make.supportsMultijob: defines.append(f"-j{multiprocessing.cpu_count()}") if CraftCore.debug.verbose() > 0: defines += ["VERBOSE=1", "V=1"] if args: defines.append(args) return " ".join(defines) def configure(self): return True def make(self): return True def install(self) -> bool: return self.cleanImage() def unittest(self): """running unittests""" return True def ccacheOptions(self): return "" def clangOptions(self): return "" def _fixInstallPrefix(self, prefix=CraftStandardDirs.craftRoot()): CraftCore.log.debug(f"Begin: fixInstallPrefix {self}: {prefix}") def stripPath(path): rootPath = os.path.splitdrive(path)[1] if rootPath.startswith(os.path.sep) or rootPath.startswith("/"): rootPath = rootPath[1:] return rootPath badPrefix = os.path.join(self.installDir(), stripPath(prefix)) if os.path.exists(badPrefix) and not os.path.samefile(self.installDir(), badPrefix): if not utils.mergeTree(badPrefix, self.installDir()): return False if CraftCore.settings.getboolean("QtSDK", "Enabled", False): qtDir = os.path.join(CraftCore.settings.get("QtSDK", "Path"), CraftCore.settings.get("QtSDK", "Version"), CraftCore.settings.get("QtSDK", "Compiler")) path = os.path.join(self.installDir(), stripPath(qtDir)) if os.path.exists(path) and not os.path.samefile(self.installDir(), path): if not utils.mergeTree(path, self.installDir()): return False if stripPath(prefix): oldPrefix = OsUtils.toUnixPath(stripPath(prefix)).split("/", 1)[0] utils.rmtree(os.path.join(self.installDir(), oldPrefix)) CraftCore.log.debug(f"End: fixInstallPrefix {self}") return True # TODO: port oldPath to regexp to match path with \ and / def patchInstallPrefix(self, files : [str], oldPaths : [str]=None, newPath : str=CraftCore.standardDirs.craftRoot()) -> bool: if isinstance(oldPaths, str): oldPaths = [oldPaths] elif not oldPaths: oldPaths = [self.subinfo.buildPrefix] newPathWin = OsUtils.toNativePath(newPath) newPathUnix = OsUtils.toUnixPath(newPath) newPath = newPathUnix for fileName in files: if not os.path.exists(fileName): CraftCore.log.warning(f"File {fileName} not found.") return False with open(fileName, "rb") as f: content = f.read() dirty = False for oldPath in oldPaths: assert os.path.isabs(oldPath) if CraftCore.compiler.isWindows: _, ext = os.path.splitext(fileName) # keep unix path sep or use unix path sep for specific type # prl and pri files might use \\\\ as sep which can be replaced by a / but not by a single \\ if oldPath[2] == "/" or ext in {".prl", ".pri"}: newPath = newPathUnix else: # keep windows sep newPath = newPathWin oldPathBinary = oldPath.encode() if oldPath != newPath and oldPathBinary in content: dirty = True CraftCore.log.info(f"Patching {fileName}: replacing {oldPath} with {newPath}") content = content.replace(oldPathBinary, newPath.encode()) if dirty: with utils.makeWritable(fileName): with open(fileName, "wb") as f: f.write(content) return True def internalPostInstall(self): if not super().internalPostInstall(): return False # 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 += [self.subinfo.buildPrefix.replace("\\", "\\\\") ,OsUtils.toUnixPath(self.subinfo.buildPrefix), OsUtils.toMSysPath(self.subinfo.buildPrefix)] pattern = [re.compile("^.*(pc|pri|prl|cmake|bat|cmd|ini|pl)$")] files = utils.filterDirectoryContent(self.installDir(), whitelist=lambda x, root: utils.regexFileFilter(x, root, pattern), blacklist=lambda x, root: True) if not self.patchInstallPrefix(files, oldPrefixes, newPrefix): return False if (CraftCore.compiler.isMacOS and os.path.isdir(self.installDir())): files = utils.filterDirectoryContent(self.installDir(), lambda x, root: utils.isBinary(x.path), lambda x, root: True) for f in files: if os.path.islink(f): continue _, ext = os.path.splitext(f) if ext in {".dylib", ".so"}: # fix dylib id with io.StringIO() as log: utils.system(["otool", "-D", f], stdout=log) 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) # TODO: meh, maybe there is a better way newId = newId.replace("@rpath", os.path.join(newPrefix, "lib")) if newId != oldId: if not utils.system(["install_name_tool", "-id", newId, f]): return False else: # add rpath # TODO: only call add rpath if its not set yet, calling it twice causes an error utils.system(["install_name_tool", "-add_rpath", os.path.join(newPrefix, "lib"), f]) # fix dependencies for dep in utils.getLibraryDeps(f): if dep.startswith(self.subinfo.buildPrefix): newDep = dep.replace(self.subinfo.buildPrefix, newPrefix) if not utils.system(["install_name_tool", "-change", dep, newDep, f]): 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 CraftCore.compiler.isMSVC() and self.subinfo.buildPrefix == CraftCore.standardDirs.craftRoot() and self.buildType() in {"RelWithDebInfo", "Debug"}: + if not self.subinfo.isCachedBuild and CraftCore.compiler.isMSVC() and self.buildType() in {"RelWithDebInfo", "Debug"}: files = utils.filterDirectoryContent(self.installDir(), lambda x, root: utils.isBinary(x.path), lambda x, root: True) for f in files: 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) return True diff --git a/bin/Package/PackageBase.py b/bin/Package/PackageBase.py index 04a36c16c..89df93d61 100644 --- a/bin/Package/PackageBase.py +++ b/bin/Package/PackageBase.py @@ -1,261 +1,262 @@ # # copyright (c) 2009 Ralf Habacker # from CraftBase import * from CraftCompiler import * from InstallDB import * from Blueprints.CraftPackageObject import * from Utils import CraftHash, GetFiles, CraftChoicePrompt from Utils.CraftManifest import CraftManifest import json class PackageBase(CraftBase): """ provides a generic interface for packages and implements the basic stuff for all packages """ # uses the following instance variables # todo: place in related ...Base # rootdir -> CraftBase # package -> PackageBase # force -> PackageBase # category -> PackageBase # version -> PackageBase # packagedir -> PackageBase # imagedir -> PackageBase def __init__(self): CraftCore.log.debug("PackageBase.__init__ called") CraftBase.__init__(self) def qmerge(self): """mergeing the imagedirectory into the filesystem""" ## \todo is this the optimal place for creating the post install scripts ? if self.package.isInstalled: self.unmerge() copiedFiles = [] # will be populated by the next call if not utils.copyDir(self.imageDir(), CraftCore.standardDirs.craftRoot(), copiedFiles=copiedFiles): return False # add package to installed database -> is this not the task of the manifest files ? revision = self.sourceRevision() package = CraftCore.installdb.addInstalled(self.package, self.version, revision=revision) fileList = self.getFileListFromDirectory(CraftCore.standardDirs.craftRoot(), copiedFiles) package.addFiles(fileList) package.install() if (CraftCore.settings.getboolean("Packager", "CreateCache") or CraftCore.settings.getboolean("Packager", "UseCache")): package.setCacheVersion(self.cacheVersion()) return True def unmerge(self): """unmergeing the files from the filesystem""" CraftCore.log.debug("Packagebase unmerge called") packageList = CraftCore.installdb.getInstalledPackages(self.package) for package in packageList: fileList = package.getFilesWithHashes() self.unmergeFileList(CraftCore.standardDirs.craftRoot(), fileList) package.uninstall() return True def strip(self, fileName): """strip debugging informations from shared libraries and executables - mingw only!!! """ if self.subinfo.options.package.disableStriping or CraftCore.compiler.isMSVC() or not CraftCore.compiler.isGCCLike(): CraftCore.log.warning(f"Skipping stripping of {fileName} -- either disabled or unsupported with this compiler") return True if OsUtils.isMac(): CraftCore.log.debug(f"Skipping stripping of files on macOS -- not implemented") return True if os.path.isabs(fileName): filepath = fileName else: CraftCore.log.warning("Please pass an absolute file path to strip") basepath = os.path.join(self.installDir()) filepath = os.path.join(basepath, "bin", fileName) return utils.system(["strip", "-s", filepath]) def createImportLibs(self, pkgName): """create the import libraries for the other compiler(if ANSI-C libs)""" basepath = os.path.join(self.installDir()) utils.createImportLibs(pkgName, basepath) def printFiles(self): packageList = CraftCore.installdb.getInstalledPackages(self.package) for package in packageList: fileList = package.getFiles() fileList.sort() for file in fileList: print(file[0]) return True def getAction(self, cmd=None): if not cmd: command = sys.argv[1] options = None # print sys.argv if (len(sys.argv) > 2): options = sys.argv[2:] else: command = cmd options = None # \todo options are not passed through by craft.py fix it return [command, options] def execute(self, cmd=None): """called to run the derived class this will be executed from the package if the package is started on its own it shouldn't be called if the package is imported as a python module""" CraftCore.log.debug("PackageBase.execute called. args: %s" % sys.argv) command, _ = self.getAction(cmd) return self.runAction(command) def fetchBinary(self, downloadRetriesLeft=3) -> bool: if self.subinfo.options.package.disableBinaryCache: return False for url in [self.cacheLocation()] + self.cacheRepositoryUrls(): CraftCore.log.debug(f"Trying to restore {self} from cache: {url}.") if url == self.cacheLocation(): fileUrl = f"{url}/manifest.json" if os.path.exists(fileUrl): with open(fileUrl, "rt", encoding="UTF-8") as f: manifest = CraftManifest.fromJson(json.load(f)) else: continue else: manifest = CraftManifest.fromJson(CraftCore.cache.cacheJsonFromUrl(f"{url}/manifest.json")) fileEntry = manifest.get(str(self)).files files = [] for f in fileEntry: if f.version == self.version: files.append(f) latest = None if not files: CraftCore.log.debug(f"Could not find {self}={self.version} in {url}") continue latest = files[0] if url != self.cacheLocation(): downloadFolder = self.cacheLocation(os.path.join(CraftCore.standardDirs.downloadDir(), "cache")) else: downloadFolder = self.cacheLocation() localArchiveAbsPath = OsUtils.toNativePath(os.path.join(downloadFolder, latest.fileName)) localArchivePath, localArchiveName = os.path.split(localArchiveAbsPath) if url != self.cacheLocation(): if not os.path.exists(localArchiveAbsPath): os.makedirs(localArchivePath, exist_ok=True) fUrl = f"{url}/{latest.fileName}" if not GetFiles.getFile(fUrl, localArchivePath, localArchiveName): CraftCore.log.warning(f"Failed to fetch {fUrl}") return False elif not os.path.isfile(localArchiveAbsPath): continue if not CraftHash.checkFilesDigests(localArchivePath, [localArchiveName], digests=latest.checksum, digestAlgorithm=CraftHash.HashAlgorithm.SHA256): CraftCore.log.warning(f"Hash did not match, {localArchiveName} might be corrupted") if downloadRetriesLeft and CraftChoicePrompt.promptForChoice("Do you want to delete the files and redownload them?", [("Yes", True), ("No", False)], default="Yes"): return utils.deleteFile(localArchiveAbsPath) and self.fetchBinary(downloadRetriesLeft=downloadRetriesLeft-1) return False self.subinfo.buildPrefix = latest.buildPrefix + self.subinfo.isCachedBuild = True if not (self.cleanImage() and utils.unpackFile(localArchivePath, localArchiveName, self.imageDir()) and self.internalPostInstall() and self.postInstall() and self.qmerge() and self.internalPostQmerge() and self.postQmerge()): return False return True return False @staticmethod def getFileListFromDirectory(imagedir, filePaths): """ create a file list containing hashes """ ret = [] algorithm = CraftHash.HashAlgorithm.SHA256 for filePath in filePaths: relativeFilePath = os.path.relpath(filePath, imagedir) digest = algorithm.stringPrefix() + CraftHash.digestFile(filePath, algorithm) ret.append((relativeFilePath, digest)) return ret @staticmethod def unmergeFileList(rootdir, fileList): """ delete files in the fileList if has matches """ for filename, filehash in fileList: fullPath = os.path.join(rootdir, os.path.normcase(filename)) if os.path.isfile(fullPath) or os.path.islink(fullPath): if filehash: algorithm = CraftHash.HashAlgorithm.getAlgorithmFromPrefix(filehash) currentHash = algorithm.stringPrefix() + CraftHash.digestFile(fullPath, algorithm) if not filehash or currentHash == filehash: OsUtils.rm(fullPath, True) else: CraftCore.log.warning( f"We can't remove {fullPath} as its hash has changed," f" that usually implies that the file was modified or replaced") elif not os.path.isdir(fullPath) and os.path.lexists(fullPath): CraftCore.log.debug(f"Remove a dead symlink {fullPath}") OsUtils.rm(fullPath, True) elif not os.path.isdir(fullPath): CraftCore.log.warning("file %s does not exist" % fullPath) containingDir = os.path.dirname(fullPath) if os.path.exists(containingDir) and not os.listdir(containingDir): CraftCore.log.debug(f"Delete empty dir {containingDir}") utils.rmtree(containingDir) def runAction(self, command): functions = {"fetch": "fetch", "cleanimage": "cleanImage", "cleanbuild": "cleanBuild", "unpack": "unpack", "compile": "compile", "configure": "configure", "make": "make", "install": ["install", "internalPostInstall"], "post-install": "postInstall", "test": "unittest", "qmerge": ["qmerge", "internalPostQmerge"], "post-qmerge": "postQmerge", "unmerge": "unmerge", "package": "createPackage", "createpatch": "createPatch", "print-files": "printFiles", "checkdigest": "checkDigest", "fetch-binary": "fetchBinary"} if command in functions: try: steps = functions[command] if not isinstance(steps, list): steps = [steps] for step in steps: if not getattr(self, step)(): return False except AttributeError as e: raise BlueprintException(str(e), self.package, e) else: CraftCore.log.error("command %s not understood" % command) return False return True diff --git a/bin/info.py b/bin/info.py index d3c7c3670..4400a989e 100644 --- a/bin/info.py +++ b/bin/info.py @@ -1,263 +1,264 @@ ## # # @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(subinfo=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 = {} # the build prefix, may differ for for cached files self.buildPrefix = CraftCore.standardDirs.craftRoot() + self.isCachedBuild = False # 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.parent.package.name 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: name = self.archiveNames[self.buildTarget] return name if isinstance(name, list) else [name] 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 isinstance(out, str): out = [out] if not isinstance(out, tuple): out = (out, CraftHash.HashAlgorithm.getAlgorithmFromFile(out[0])) elif not isinstance(out[0], list): out = ([out[0]], out[1]) 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)