diff --git a/bin/Blueprints/CraftPackageObject.py b/bin/Blueprints/CraftPackageObject.py index ae7cc5901..420c52f3a 100644 --- a/bin/Blueprints/CraftPackageObject.py +++ b/bin/Blueprints/CraftPackageObject.py @@ -1,231 +1,231 @@ #!/usr/bin/env python import copy import importlib import os import re from CraftConfig import craftSettings from CraftStandardDirs import CraftStandardDirs from CraftDebug import craftDebug from CraftOS.osutils import OsUtils import utils class CraftPackageObject(object): options = None __rootPackage = None _nodes = {}#all nodes _recipes = {}#all recipes, for lookup by package name - IgnoredDirectories = [".git", "__pycache__"] + IgnoredDirectories = [".git", "__pycache__", ".kdev4"] Ignores = re.compile("a^") def __init__(self, path=None): if isinstance(path, CraftPackageObject): self.__dict__ = path.__dict__ return if path: raise Exception("Calling CraftPackageObject(str) directly is not supported," " use CraftPackageObject.get(str) instead.") self.path = None self.children = {} self.source = None self._version = None self._instance = None @property def parentPath(self): if not self.path: return [] split = self.path.rsplit("/", 1) return split[1] if len(split)>1 else "/" @property def parent(self): if not self.path: return "/" return CraftPackageObject._nodes[self.parentPath] @staticmethod def get(path): if isinstance(path, CraftPackageObject): return path CraftPackageObject.root() package = None if path not in CraftPackageObject._nodes: if path in CraftPackageObject._recipes: packages = CraftPackageObject._recipes[path] if len(packages) > 1: craftDebug.log.info(f"Found multiple recipes for {path}") for p in packages: craftDebug.log.info(p) craftDebug.log.info(f"Please use the full path to the recipe.") exit(1) package = packages[0] else: package = CraftPackageObject._nodes[path] return package def _addNode(self, path, blueprintRoot): package = CraftPackageObject() if path: path = utils.normalisePath(path) package.path = path[len(blueprintRoot) + 1:] elif blueprintRoot: path = blueprintRoot else: return None for f in os.listdir(path): fPath = os.path.abspath(os.path.join(path, f)) if os.path.isdir(fPath): if f not in CraftPackageObject.IgnoredDirectories: hasChildren = True child = self._addNode(fPath, blueprintRoot) if child: package.children[child.name] = child elif f.endswith(".py"): if package.source: raise BlueprintException(f"Multiple py files in one directory: {package.source} and {f}", package) if f[:-3] != package.name: raise BlueprintException(f"Recipes must match the name of the directory: {fPath}", package) recipe = os.path.splitext(f)[0] if recipe not in CraftPackageObject._recipes: CraftPackageObject._recipes[recipe] = [] CraftPackageObject._recipes[recipe].append(package) package.source = fPath if package.children: if package.source: raise BlueprintException(f"{package} has has children but also a recipe {package.source}!", package) if path != blueprintRoot: if not package.source and not package.children: craftDebug.log.warning(f"Found an dead branch in {blueprintRoot}/{package.path}\n" f"You might wan't to run \"git clean -xdf\" in that directry.") return None if package.path in CraftPackageObject._nodes: existingNode = CraftPackageObject._nodes[package.path] if not existingNode.isCategory(): raise BlueprintException( f"Found a recipe clash {existingNode.source} and {blueprintRoot}/{package.path}", existingNode) package = existingNode else: CraftPackageObject._nodes[package.path] = package return package @property def name(self): if not self.path: return "[]" split = self.path.rsplit("/", 1) return split[0] if len(split) == 1 else split[1] @staticmethod def rootDirectories(): # this function should return all currently set blueprint directories rootDirs = {utils.normalisePath(CraftStandardDirs.craftRepositoryDir())} if ("Blueprints", "Locations") in craftSettings: for path in craftSettings.getList("Blueprints", "Locations"): rootDirs.add(utils.normalisePath(path)) if os.path.isdir(CraftStandardDirs.blueprintRoot()): for f in os.listdir(CraftStandardDirs.blueprintRoot()): rootDirs.add(utils.normalisePath(os.path.join(CraftStandardDirs.blueprintRoot(), f))) return list(rootDirs) @staticmethod def root(): if not CraftPackageObject.__rootPackage: if ("Blueprints", "Ignores") in craftSettings: CraftPackageObject.Ignores = re.compile("|".join([f"^{entry}$" for entry in craftSettings.get("Blueprints", "Ignores").split(";")])) CraftPackageObject.__rootPackage = root = CraftPackageObject() root.path = "/" for blueprintRoot in CraftPackageObject.rootDirectories(): if not os.path.isdir(blueprintRoot): craftDebug.log.warning(f"{blueprintRoot} does not exist") continue blueprintRoot = utils.normalisePath(os.path.abspath(blueprintRoot)) # create a dummy package to load its children child = root._addNode(None, blueprintRoot) root.children.update(child.children) return CraftPackageObject.__rootPackage @property def instance(self): if not self._instance: craftDebug.log.debug(f"module to import: {self.source}") modulename = os.path.splitext(os.path.basename(self.source))[0].replace('.', '_') loader = importlib.machinery.SourceFileLoader(modulename, self.source) try: mod = loader.load_module() except Exception as e: raise BlueprintException(f"Failed to load file {self.source}", self, e) if not mod is None: mod.CRAFT_CURRENT_MODULE = self pack = mod.Package() self._instance = pack else: raise BlueprintException("Failed to find package", self) return self._instance @property def subinfo(self): return self.instance.subinfo def isVirtualPackage(self): """ check if that package is of VirtualPackageBase """ if self.isCategory(): return False for baseClassObject in self.instance.__class__.__bases__: if baseClassObject.__name__ == 'VirtualPackageBase': return True return False def isCategory(self): return not self.source def isIgnored(self): return self.path and CraftPackageObject.Ignores.match(self.path) @property def version(self): if self.isCategory(): return None if not self._version: self._version = self.instance.version return self._version def __eq__(self, other): if isinstance(other, CraftPackageObject): return self.path == other.path return self.path == other def __str__(self): return self.path or "None" def __hash__(self): return self.path.__hash__() @staticmethod def installables(): #ensure that everything is loaded CraftPackageObject.root() recipes = [] for p in CraftPackageObject._recipes.values(): recipes.extend(p) return recipes class BlueprintException(Exception, CraftPackageObject): def __init__(self, message, package, exception=None): Exception.__init__(self, message) CraftPackageObject.__init__(self, package) self.exception = exception def __str__(self): return "%s failed: %s" % (CraftPackageObject.__str__(self), Exception.__str__(self))