diff --git a/bin/Blueprints/CraftDependencyPackage.py b/bin/Blueprints/CraftDependencyPackage.py index 2b2797d74..365425bdc 100644 --- a/bin/Blueprints/CraftDependencyPackage.py +++ b/bin/Blueprints/CraftDependencyPackage.py @@ -1,109 +1,121 @@ from collections import OrderedDict from enum import unique, Enum, IntFlag from Blueprints.CraftPackageObject import CraftPackageObject, BlueprintException from Blueprints.CraftVersion import CraftVersion from CraftCore import CraftCore +from Package import VirtualPackageBase +from info import DependencyRequirementType @unique class DependencyType(IntFlag): Runtime = 0x1 << 0 Buildtime = 0x1 << 1 # TODO: rename as we now have more build types Both = Runtime | Buildtime Packaging = 0x1 << 3 All = ~0 class CraftDependencyPackage(CraftPackageObject): _packageCache = dict() @unique class State(Enum): Unvisited = 0 Visiting = 1 Visited = 2 def __init__(self, path): CraftPackageObject.__init__(self, path) self._depenendencyType = None self.dependencies = [] # tuple (name, required version) self.state = CraftDependencyPackage.State.Unvisited @property def depenendencyType(self): return self._depenendencyType @depenendencyType.setter def depenendencyType(self, depenendencyType): if self._depenendencyType != depenendencyType: self._depenendencyType = depenendencyType self.dependencies = [] self.__resolveDependencies() def __resolveDependencies(self): CraftCore.log.debug(f"resolving package {self}") if not self.isCategory(): subinfo = self.subinfo if self.depenendencyType & DependencyType.Runtime: self.dependencies.extend(self.__readDependenciesForChildren(subinfo.runtimeDependencies.items())) if self.depenendencyType & DependencyType.Buildtime: self.dependencies.extend(self.__readDependenciesForChildren(subinfo.buildDependencies.items())) if self.depenendencyType & DependencyType.Packaging: self.dependencies.extend(self.__readDependenciesForChildren(subinfo.packagingDependencies.items())) else: self.dependencies.extend(self.__readDependenciesForChildren([(x, None) for x in self.children.values()])) def __readDependenciesForChildren(self, deps : [(str, str)]) -> []: children = [] if deps: for packaheName, requiredVersion in deps: if (packaheName, self.depenendencyType) not in CraftDependencyPackage._packageCache: package = CraftPackageObject.get(packaheName) if not package: raise BlueprintException(f"Failed to resolve {packaheName} as a dependency of {self}", self) + if isinstance(requiredVersion, tuple): + requiredVersion, type = requiredVersion + if type == DependencyRequirementType.Required: + if not bool(package.categoryInfo.compiler & CraftCore.compiler.compiler): + raise BlueprintException(f"{self} requries {package}, but it is not supported on {CraftCore.compiler.compiler}",self) + if not bool(package.categoryInfo.platforms & CraftCore.compiler.platform): + raise BlueprintException(f"{self} requries {package}, but it is not supported on {CraftCore.compiler.platform}",self) + if package.isIgnored() or isinstance(package.instance, VirtualPackageBase.VirtualPackageBase): + raise BlueprintException(f"{self} requries {package}, but it is ignored",self) + if requiredVersion and requiredVersion != None and CraftVersion(package.version) < CraftVersion(requiredVersion): raise BlueprintException(f"{self} requries {package} version {requiredVersion!r} but {package.version!r} is installed", self) p = CraftDependencyPackage(package) CraftCore.log.debug(f"adding package {packaheName}") CraftDependencyPackage._packageCache[(packaheName, self.depenendencyType)] = p p.depenendencyType = self.depenendencyType else: p = CraftDependencyPackage._packageCache[(packaheName, self.depenendencyType)] children.append(p) return children def __getDependencies(self, depenendencyType, ignoredPackages): """ returns all dependencies """ if self.isIgnored(): return [] self.depenendencyType = depenendencyType depList = [] self.state = CraftDependencyPackage.State.Visiting for p in self.dependencies: if p.state != CraftDependencyPackage.State.Unvisited: continue if not p.isIgnored() \ and (not ignoredPackages or p.path not in ignoredPackages): depList.extend(p.__getDependencies(depenendencyType & ~DependencyType.Packaging, ignoredPackages)) if depenendencyType & DependencyType.Packaging: depList.extend(p.__getDependencies(depenendencyType, ignoredPackages)) if self.state != CraftDependencyPackage.State.Visited: self.state = CraftDependencyPackage.State.Visited if not self.isCategory(): depList.append(self) return list(OrderedDict.fromkeys(depList)) def getDependencies(self, depType=DependencyType.All, ignoredPackages=None): self.depenendencyType = depType for p in CraftDependencyPackage._packageCache.values(): #reset visited state p.state = CraftDependencyPackage.State.Unvisited return self.__getDependencies(depType, ignoredPackages) diff --git a/bin/info.py b/bin/info.py index 4400a989e..fe7cb2643 100644 --- a/bin/info.py +++ b/bin/info.py @@ -1,264 +1,270 @@ ## # # @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 +from enum import Enum, unique import VersionInfo from Utils import CraftHash, CraftManifest from options import * from CraftDebug import deprecated +@unique +class DependencyRequirementType(Enum): + Optional = 0 + Required = 1 + 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)