Example #1
0
 def __init__(self, parent_repo):
     self._parent_repo = parent_repo
     self._cached_instances = weakrefs.WeakValCache()
Example #2
0
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
Example #3
0
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
Example #4
0
 def __setstate__(self, state):
     self.__dict__ = state.copy()
     self.__dict__['_cached_instances'] = weakrefs.WeakValCache()
Example #5
0
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)
Example #6
0
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)