def __init__(self, parent_repo): self._parent_repo = parent_repo self._cached_instances = weakrefs.WeakValCache()
class EAPI(object): known_eapis = weakrefs.WeakValCache() __metaclass__ = klass.immutable_instance def __init__(self, magic, phases, default_phases, metadata_keys, mandatory_keys, tracked_attributes, optionals, ebd_env_options=None): sf = object.__setattr__ sf(self, "magic", magic) sf(self, "phases", mappings.ImmutableDict(phases)) sf(self, "phases_rev", mappings.ImmutableDict((v, k) for k, v in self.phases.iteritems())) # we track the phases that have a default implementation- # this is primarily due to DEFINED_PHASES cache values # not including it. sf(self, "default_phases", frozenset(default_phases)) sf(self, "mandatory_keys", frozenset(mandatory_keys)) sf(self, "metadata_keys", frozenset(metadata_keys)) sf(self, "tracked_attributes", frozenset(tracked_attributes)) d = dict(eapi_optionals) d.update(optionals) sf(self, 'options', optionals_cls(d)) if ebd_env_options is None: ebd_env_options = {} sf(self, "ebd_env_options", mappings.ImmutableDict(ebd_env_options)) @classmethod def register(cls, *args, **kwds): ret = cls(*args, **kwds) pre_existing = cls.known_eapis.get(ret.magic) if pre_existing is not None: raise ValueError( "eapi magic %s is already known/instantiated- %r" % (ret.magic, pre_existing)) cls.known_eapis[ret.magic] = ret return ret @klass.jit_attr def is_supported(self): if EAPI.known_eapis.get(self.magic) is not None: if not self.options.is_supported: logger.warning("EAPI %s isn't fully supported" % self.magic) return True return False @classmethod def get_unsupported_eapi(cls, magic): return cls(magic, (), (), (), (), (), {'is_supported': False}) @klass.jit_attr def atom_kls(self): return partial(atom.atom, eapi=int(self.magic)) def interpret_cache_defined_phases(self, sequence): phases = set(sequence) if not self.options.trust_defined_phases_cache: if not phases: # run them all; cache was generated # by a pm that didn't support DEFINED_PHASES return frozenset(self.phases) phases.discard("-") return frozenset(phases) def get_ebd_env(self): d = {} for k, converter in self.ebd_env_options.iteritems(): d["PKGCORE_%s" % (k.upper(), )] = converter( getattr(self.options, k)) d["EAPI"] = str(self.magic) return d
class EAPI(object): known_eapis = weakrefs.WeakValCache() unknown_eapis = weakrefs.WeakValCache() __metaclass__ = klass.immutable_instance def __init__(self, magic, parent, phases, default_phases, metadata_keys, mandatory_keys, tracked_attributes, archive_suffixes, optionals, ebd_env_options=None): sf = object.__setattr__ sf(self, "_magic", str(magic)) sf(self, "_parent", parent) sf(self, "phases", mappings.ImmutableDict(phases)) sf(self, "phases_rev", mappings.ImmutableDict((v, k) for k, v in self.phases.iteritems())) # We track the phases that have a default implementation- this is # primarily due to DEFINED_PHASES cache values not including it. sf(self, "default_phases", frozenset(default_phases)) sf(self, "mandatory_keys", frozenset(mandatory_keys)) sf(self, "metadata_keys", frozenset(metadata_keys)) sf(self, "tracked_attributes", frozenset(tracked_attributes)) sf(self, "archive_suffixes", frozenset(archive_suffixes)) sf(self, "archive_suffixes_re", '(?:%s)' % '|'.join(map(re.escape, archive_suffixes))) d = dict(eapi_optionals) d.update(optionals) sf(self, 'options', _optionals_cls(d)) if ebd_env_options is None: ebd_env_options = {} sf(self, "ebd_env_options", mappings.ImmutableDict(ebd_env_options)) @classmethod def register(cls, *args, **kwds): eapi = cls(*args, **kwds) pre_existing = cls.known_eapis.get(eapi._magic) if pre_existing is not None: raise ValueError( "EAPI %s is already known/instantiated- %r" % (eapi._magic, pre_existing)) if bash_version() < eapi.options.bash_compat: # hard exit if the system doesn't have an adequate bash installed raise SystemExit( "EAPI %s requires >=bash-%s, system version: %s" % ( eapi, eapi.options.bash_compat, bash_version())) cls.known_eapis[eapi._magic] = eapi return eapi @klass.jit_attr def is_supported(self): """Check if an EAPI is supported.""" if EAPI.known_eapis.get(self._magic) is not None: if not self.options.is_supported: logger.warning("EAPI %s isn't fully supported", self) return True return False @klass.jit_attr def atom_kls(self): return partial(atom.atom, eapi=int(self._magic)) def interpret_cache_defined_phases(self, sequence): phases = set(sequence) if not self.options.trust_defined_phases_cache: if not phases: # run them all; cache was generated # by a pm that didn't support DEFINED_PHASES return frozenset(self.phases) phases.discard("-") return frozenset(phases) def __str__(self): return self._magic @property def inherits(self): """Yield an EAPI's inheritance tree. Note that this assumes a simple, linear inheritance tree. """ yield self parent = self._parent while parent is not None: yield parent parent = parent._parent def get_ebd_env(self): """Return EAPI options passed to the ebd environment.""" d = {} for k, converter in self.ebd_env_options.iteritems(): d["PKGCORE_%s" % (k.upper(),)] = converter(getattr(self.options, k)) d["EAPI"] = self._magic return d
def __setstate__(self, state): self.__dict__ = state.copy() self.__dict__['_cached_instances'] = weakrefs.WeakValCache()
class EAPI(metaclass=klass.immutable_instance): known_eapis = weakrefs.WeakValCache() unknown_eapis = weakrefs.WeakValCache() def __init__(self, magic, parent=None, phases=(), default_phases=(), mandatory_keys=(), dep_keys=(), metadata_keys=(), tracked_attributes=(), archive_exts=(), optionals=None, ebd_env_options=None): sf = object.__setattr__ sf(self, "_magic", str(magic)) sf(self, "_parent", parent) sf(self, "phases", mappings.ImmutableDict(phases)) sf(self, "phases_rev", mappings.ImmutableDict((v, k) for k, v in self.phases.items())) # We track the phases that have a default implementation- this is # primarily due to DEFINED_PHASES cache values not including it. sf(self, "default_phases", frozenset(default_phases)) sf(self, "mandatory_keys", frozenset(mandatory_keys)) sf(self, "dep_keys", frozenset(dep_keys)) sf(self, "metadata_keys", (frozenset(mandatory_keys) | frozenset(dep_keys) | frozenset(metadata_keys))) sf(self, "tracked_attributes", (frozenset(tracked_attributes) | frozenset(x.lower() for x in dep_keys))) sf(self, "archive_exts", frozenset(archive_exts)) if optionals is None: optionals = {} sf(self, 'options', _optionals_cls(optionals)) if ebd_env_options is None: ebd_env_options = () sf(self, "_ebd_env_options", ebd_env_options) @classmethod def register(cls, *args, **kwds): eapi = cls(*args, **kwds) pre_existing = cls.known_eapis.get(eapi._magic) if pre_existing is not None: raise ValueError( f"EAPI '{eapi}' is already known/instantiated- {pre_existing!r}" ) if (getattr(eapi.options, 'bash_compat', False) and bash_version() < eapi.options.bash_compat): # hard exit if the system doesn't have an adequate bash installed raise SystemExit( f"EAPI '{eapi}' requires >=bash-{eapi.options.bash_compat}, " f"system version: {bash_version()}") cls.known_eapis[eapi._magic] = eapi # generate EAPI bash libs when running from git repo eapi.bash_libs() return eapi @klass.jit_attr def is_supported(self): """Check if an EAPI is supported.""" if EAPI.known_eapis.get(self._magic) is not None: if not self.options.is_supported: logger.warning(f"EAPI '{self}' isn't fully supported") sys.stderr.flush() return True return False @klass.jit_attr def bash_funcs(self): """Internally implemented EAPI specific functions to skip when exporting.""" funcs = pjoin(const.EBD_PATH, '.generated', 'funcs', self._magic) if not os.path.exists(funcs): # we're probably running in a cacheless git repo, so generate a cached version try: os.makedirs(os.path.dirname(funcs), exist_ok=True) with open(funcs, 'w') as f: subprocess.run([ pjoin(const.EBD_PATH, 'generate_eapi_func_list'), self._magic ], cwd=const.EBD_PATH, stdout=f) except (IOError, subprocess.CalledProcessError) as e: raise Exception( f"failed to generate list of EAPI '{self}' specific functions: {str(e)}" ) with open(funcs, 'r') as f: return frozenset(line.strip() for line in f) @klass.jit_attr def bash_cmds_deprecated(self): """EAPI specific commands deprecated for this EAPI.""" cmds = pjoin(const.EBD_PATH, '.generated', 'cmds', self._magic, 'deprecated') if not os.path.exists(cmds): # we're probably running in a cacheless git repo, so generate a cached version try: os.makedirs(os.path.dirname(cmds), exist_ok=True) with open(cmds, 'w') as f: subprocess.run([ pjoin(const.EBD_PATH, 'generate_eapi_cmd_list'), '-d', self._magic ], cwd=const.EBD_PATH, stdout=f) except (IOError, subprocess.CalledProcessError) as e: raise Exception( f'failed to generate list of EAPI {self} deprecated commands: {str(e)}' ) with open(cmds, 'r') as f: return frozenset(line.strip() for line in f) @klass.jit_attr def bash_cmds_banned(self): """EAPI specific commands banned for this EAPI.""" cmds = pjoin(const.EBD_PATH, '.generated', 'cmds', self._magic, 'banned') if not os.path.exists(cmds): # we're probably running in a cacheless git repo, so generate a cached version try: os.makedirs(os.path.dirname(cmds), exist_ok=True) with open(cmds, 'w') as f: subprocess.run([ pjoin(const.EBD_PATH, 'generate_eapi_cmd_list'), '-b', self._magic ], cwd=const.EBD_PATH, stdout=f) except (IOError, subprocess.CalledProcessError) as e: raise Exception( f'failed to generate list of EAPI {self} banned commands: {str(e)}' ) with open(cmds, 'r') as f: return frozenset(line.strip() for line in f) def bash_libs(self): """Generate internally implemented EAPI specific bash libs required by the ebd.""" eapi_global_lib = pjoin(const.EBD_PATH, '.generated', 'libs', self._magic, 'global') script = pjoin(const.EBD_PATH, 'generate_eapi_lib') # skip generation when installing as the install process takes care of it if not os.path.exists(script): return if not os.path.exists(eapi_global_lib): try: os.makedirs(os.path.dirname(eapi_global_lib), exist_ok=True) with open(eapi_global_lib, 'w') as f: subprocess.run([script, '-s', 'global', self._magic], cwd=const.EBD_PATH, stdout=f) except (IOError, subprocess.CalledProcessError) as e: raise Exception( f"failed to generate EAPI '{self}' global lib: {str(e)}") for phase in self.phases.values(): eapi_lib = pjoin(const.EBD_PATH, '.generated', 'libs', self._magic, phase) if not os.path.exists(eapi_lib): try: os.makedirs(os.path.dirname(eapi_lib), exist_ok=True) with open(eapi_lib, 'w') as f: subprocess.run([script, '-s', phase, self._magic], cwd=const.EBD_PATH, stdout=f) except (IOError, subprocess.CalledProcessError) as e: raise Exception( f"failed to generate EAPI '{self}' phase {phase} lib: {str(e)}" ) @klass.jit_attr def archive_exts_regex_pattern(self): """Regex pattern for supported archive extensions.""" pattern = '|'.join(map(re.escape, self.archive_exts)) if self.options.unpack_case_insensitive: return f'(?i:({pattern}))' return f'({pattern})' @klass.jit_attr def archive_exts_regex(self): """Regex matching strings ending with supported archive extensions.""" return re.compile(rf'{self.archive_exts_regex_pattern}$') @klass.jit_attr def valid_slot_regex(self): """Regex matching valid SLOT values.""" valid_slot = r'[A-Za-z0-9_][A-Za-z0-9+_.-]*' if self.options.sub_slotting: valid_slot += rf'(/{valid_slot})?' return re.compile(rf'^{valid_slot}$') @klass.jit_attr def atom_kls(self): return partial(atom.atom, eapi=self._magic) def interpret_cache_defined_phases(self, sequence): phases = set(sequence) if not self.options.trust_defined_phases_cache: if not phases: # run them all; cache was generated # by a pm that didn't support DEFINED_PHASES return frozenset(self.phases) phases.discard("-") return frozenset(phases) def __str__(self): return self._magic @property def inherits(self): """Yield an EAPI's inheritance tree. Note that this assumes a simple, linear inheritance tree. """ yield self parent = self._parent while parent is not None: yield parent parent = parent._parent @klass.jit_attr def helpers(self): """Phase to directory mapping for EAPI specific helpers to add to $PATH.""" paths = defaultdict(list) for eapi in self.inherits: paths['global'].append(pjoin(const.EBUILD_HELPERS_PATH, 'common')) helper_dir = pjoin(const.EBUILD_HELPERS_PATH, eapi._magic) for dirpath, dirnames, filenames in os.walk(helper_dir): if not filenames: continue if dirpath == helper_dir: paths['global'].append(dirpath) else: phase = os.path.basename(dirpath) if phase in self.phases_rev: paths[phase].append(dirpath) else: raise ValueError(f'unknown phase: {phase!r}') return mappings.ImmutableDict((k, tuple(v)) for k, v in paths.items()) @klass.jit_attr def ebd_env(self): """Dictionary of EAPI options passed to the ebd environment.""" d = {} for k in self._ebd_env_options: d[f"PKGCORE_{k.upper()}"] = str(getattr(self.options, k)).lower() d["PKGCORE_EAPI_INHERITS"] = ' '.join(x._magic for x in self.inherits) d["EAPI"] = self._magic return mappings.ImmutableDict(d)
class EAPI(object, metaclass=klass.immutable_instance): known_eapis = weakrefs.WeakValCache() unknown_eapis = weakrefs.WeakValCache() def __init__(self, magic, parent=None, phases=(), default_phases=(), metadata_keys=(), mandatory_keys=(), tracked_attributes=(), archive_suffixes=(), optionals=None, ebd_env_options=None): sf = object.__setattr__ sf(self, "_magic", str(magic)) sf(self, "_parent", parent) sf(self, "phases", mappings.ImmutableDict(phases)) sf(self, "phases_rev", mappings.ImmutableDict((v, k) for k, v in self.phases.items())) # We track the phases that have a default implementation- this is # primarily due to DEFINED_PHASES cache values not including it. sf(self, "default_phases", frozenset(default_phases)) sf(self, "mandatory_keys", frozenset(mandatory_keys)) sf(self, "metadata_keys", frozenset(metadata_keys)) sf(self, "tracked_attributes", frozenset(tracked_attributes)) sf(self, "archive_suffixes", frozenset(archive_suffixes)) if optionals is None: optionals = {} sf(self, 'options', _optionals_cls(optionals)) if ebd_env_options is None: ebd_env_options = () sf(self, "_ebd_env_options", ebd_env_options) @classmethod def register(cls, *args, **kwds): eapi = cls(*args, **kwds) pre_existing = cls.known_eapis.get(eapi._magic) if pre_existing is not None: raise ValueError( f"EAPI '{eapi}' is already known/instantiated- {pre_existing!r}" ) if (getattr(eapi.options, 'bash_compat', False) and bash_version() < eapi.options.bash_compat): # hard exit if the system doesn't have an adequate bash installed raise SystemExit( f"EAPI '{eapi}' requires >=bash-{eapi.options.bash_compat}, " f"system version: {bash_version()}") cls.known_eapis[eapi._magic] = eapi return eapi @klass.jit_attr def is_supported(self): """Check if an EAPI is supported.""" if EAPI.known_eapis.get(self._magic) is not None: if not self.options.is_supported: logger.warning(f"EAPI '{self}' isn't fully supported") sys.stderr.flush() return True return False @klass.jit_attr def bash_funcs(self): """Internally implemented EAPI specific functions to skip when exporting.""" try: eapi_funcs = pjoin(const.EBD_PATH, 'generated', 'funcnames', self._magic) with open(eapi_funcs, 'r') as f: funcs = f.readlines() except FileNotFoundError: # we're running in the git repo and need to generate the list on the fly ret, funcs = spawn_get_output([ pjoin(const.EBD_PATH, 'generate_eapi_func_list'), self._magic ]) if ret != 0: raise Exception( f"failed to generate list of EAPI '{self}' specific functions" ) return tuple(x.strip() for x in funcs) @klass.jit_attr def archive_suffixes_re(self): if self.options.unpack_case_insensitive: flags = re.IGNORECASE else: flags = 0 archive_suffixes = '|'.join(map(re.escape, self.archive_suffixes)) return re.compile(rf'({archive_suffixes})$', flags=flags) @klass.jit_attr def atom_kls(self): return partial(atom.atom, eapi=self._magic) def interpret_cache_defined_phases(self, sequence): phases = set(sequence) if not self.options.trust_defined_phases_cache: if not phases: # run them all; cache was generated # by a pm that didn't support DEFINED_PHASES return frozenset(self.phases) phases.discard("-") return frozenset(phases) def __str__(self): return self._magic @property def inherits(self): """Yield an EAPI's inheritance tree. Note that this assumes a simple, linear inheritance tree. """ yield self parent = self._parent while parent is not None: yield parent parent = parent._parent @klass.jit_attr def helpers(self): """Phase to directory mapping for EAPI specific helpers to add to $PATH.""" paths = defaultdict(list) for eapi in self.inherits: paths['global'].append(pjoin(const.EBUILD_HELPERS_PATH, 'common')) helper_dir = pjoin(const.EBUILD_HELPERS_PATH, eapi._magic) for dirpath, dirnames, filenames in os.walk(helper_dir): if not filenames: continue if dirpath == helper_dir: paths['global'].append(dirpath) else: phase = os.path.basename(dirpath) if phase in self.phases_rev: paths[phase].append(dirpath) else: raise ValueError(f'unknown phase: {phase!r}') return mappings.ImmutableDict((k, tuple(v)) for k, v in paths.items()) @klass.jit_attr def ebd_env(self): """Dictionary of EAPI options passed to the ebd environment.""" d = {} for k in self._ebd_env_options: d[f"PKGCORE_{k.upper()}"] = str(getattr(self.options, k)).lower() d["PKGCORE_EAPI_INHERITS"] = tuple(x._magic for x in self.inherits) d["EAPI"] = self._magic return mappings.ImmutableDict(d)