From 5959cb1b25d1d0d7c87fd982df72dfd2212f80a3 Mon Sep 17 00:00:00 2001 From: Zac Medico Date: Fri, 20 Aug 2010 17:11:38 -0700 Subject: [PATCH] Bug #44796 - Add support for /etc/portage/package.env. As documented in man/portage.5: Per-package environment variable settings. Entries refer to environment files that are placed in the /etc/portage/env/ directory and have the same format as make.conf(5). Format: - comment lines begin with # (no inline comments) - one DEPEND atom per line followed by name(s) of environment file(s) Example: sys-libs/glibc glibc.conf --- man/make.conf.5 | 3 + man/portage.5 | 22 +++- pym/portage/package/ebuild/config.py | 162 ++++++++++++++++++++++++--- 3 files changed, 171 insertions(+), 16 deletions(-) diff --git a/man/make.conf.5 b/man/make.conf.5 index cbedc3d19..0827db11a 100644 --- a/man/make.conf.5 +++ b/man/make.conf.5 @@ -19,6 +19,9 @@ and ACCEPT_KEYWORDS. Incremental variables are propagated down from make.defaults to make.globals to make.conf to the environment settings. Clearing these variables requires a clear\-all as in: export USE="\-*" +.br +In order to create per\-package environment settings, refer to +\fBpackage.env\fR in \fBportage\fR(5). .SH "VARIABLES" .TP \fBACCEPT_CHOSTS\fR = \fI[space delimited list of CHOST values]\fR diff --git a/man/portage.5 b/man/portage.5 index 3a472a8ac..7edeb171e 100644 --- a/man/portage.5 +++ b/man/portage.5 @@ -51,6 +51,7 @@ make.conf mirrors modules package.accept_keywords +package.env package.keywords package.license package.mask @@ -538,6 +539,24 @@ package.accept_keywords like this: games-fps/quake3-demo x86 +.TP +.BR package.env +Per\-package environment variable settings. Entries refer to +environment files that are placed in the \fB/etc/portage/env/\fR +directory and have the same format as \fBmake.conf\fR(5). + +.I Format: +.nf +\- comment lines begin with # (no inline comments) +\- one DEPEND atom per line followed by name(s) of environment file(s) +.fi + +.I Example: +.nf +# use environment variables from /etc/portage/env/glibc.conf for the glibc package +sys\-libs/glibc glibc.conf +.fi + .TP .BR package.license This will allow ACCEPT_LICENSE to be augmented for a single package. @@ -873,7 +892,8 @@ games\-emulation/xmess:net \- Adds network support .TP .BR make.globals The global default settings for Portage. This comes from the portage package -itself. Settings in \fBmake.conf\fR override values here. The format +itself. Settings in \fBmake.conf\fR or \fBpackage.env\fR +override values here. The format is described extensivly in \fBmake.conf\fR(5). .RE .TP diff --git a/pym/portage/package/ebuild/config.py b/pym/portage/package/ebuild/config.py index d6cfe7125..7d824c47f 100644 --- a/pym/portage/package/ebuild/config.py +++ b/pym/portage/package/ebuild/config.py @@ -392,7 +392,8 @@ class config(object): self.locked = 0 self.mycpv = None self._setcpv_args_hash = None - self.puse = [] + self.puse = "" + self._penv = [] self.modifiedkeys = [] self.uvlist = [] self._accept_chost_re = None @@ -433,6 +434,7 @@ class config(object): self.useforce_list = clone.useforce_list self.usemask_list = clone.usemask_list self._iuse_implicit_match = clone._iuse_implicit_match + self._non_user_variables = clone._non_user_variables self.user_profile_dir = copy.deepcopy(clone.user_profile_dir) self.local_config = copy.deepcopy(clone.local_config) @@ -454,6 +456,7 @@ class config(object): self.useforce = copy.deepcopy(clone.useforce) self.puseforce_list = copy.deepcopy(clone.puseforce_list) self.puse = copy.deepcopy(clone.puse) + self._penv = copy.deepcopy(clone._penv) self.make_defaults_use = copy.deepcopy(clone.make_defaults_use) self.pkgprofileuse = copy.deepcopy(clone.pkgprofileuse) self.mycpv = copy.deepcopy(clone.mycpv) @@ -474,6 +477,7 @@ class config(object): self.lookuplist.reverse() self._use_expand_dict = copy.deepcopy(clone._use_expand_dict) self.backupenv = self.configdict["backupenv"] + self._backupenv = copy.deepcopy(clone._backupenv) self.pusedict = copy.deepcopy(clone.pusedict) self.pkeywordsdict = copy.deepcopy(clone.pkeywordsdict) self._pkeywords_list = copy.deepcopy(clone._pkeywords_list) @@ -488,6 +492,8 @@ class config(object): self._license_groups = copy.deepcopy(clone._license_groups) self._accept_properties = copy.deepcopy(clone._accept_properties) self._ppropertiesdict = copy.deepcopy(clone._ppropertiesdict) + self._penvdict = copy.deepcopy(clone._penvdict) + self._expand_map = copy.deepcopy(clone._expand_map) else: @@ -528,8 +534,8 @@ class config(object): self.incrementals = INCREMENTALS else: self.incrementals = config_incrementals - if not isinstance(self.incrementals, tuple): - self.incrementals = tuple(self.incrementals) + if not isinstance(self.incrementals, frozenset): + self.incrementals = frozenset(self.incrementals) self.module_priority = ("user", "default") self.modules = {} @@ -722,6 +728,7 @@ class config(object): # interaction with the calling environment that might # lead to unexpected results. expand_map = {} + self._expand_map = expand_map env_d = getconfig(os.path.join(eroot, "etc", "profile.env"), expand=expand_map) @@ -805,7 +812,7 @@ class config(object): else: self.make_defaults_use.append("") self.mygcfg = stack_dicts(mygcfg_dlists, - incrementals=INCREMENTALS) + incrementals=self.incrementals) if self.mygcfg is None: self.mygcfg = {} self.configlist.append(self.mygcfg) @@ -825,6 +832,12 @@ class config(object): profile_only_variables = self.configdict["defaults"].get( "PROFILE_ONLY_VARIABLES", "").split() profile_only_variables = stack_lists([profile_only_variables]) + non_user_variables = set() + non_user_variables.update(profile_only_variables) + non_user_variables.update(self._env_blacklist) + non_user_variables = frozenset(non_user_variables) + self._non_user_variables = non_user_variables + for k in profile_only_variables: self.mygcfg.pop(k, None) @@ -871,6 +884,7 @@ class config(object): self.pkeywordsdict = portage.dep.ExtendedAtomDict(dict) self._plicensedict = portage.dep.ExtendedAtomDict(dict) self._ppropertiesdict = portage.dep.ExtendedAtomDict(dict) + self._penvdict = portage.dep.ExtendedAtomDict(dict) self.punmaskdict = portage.dep.ExtendedAtomDict(list) # locations for "categories" and "arch.list" files @@ -982,6 +996,29 @@ class config(object): for k, v in propdict.items(): self._ppropertiesdict.setdefault(k.cp, {})[k] = v + #package.env + penvdict = grabdict_package(os.path.join( + abs_user_config, "package.env"), recursive=1, allow_wildcard=True) + v = penvdict.pop("*/*", None) + if v is not None: + global_wildcard_conf = {} + self._grab_pkg_env(v, global_wildcard_conf) + incrementals = self.incrementals + conf_configdict = self.configdict["conf"] + for k, v in global_wildcard_conf.items(): + if k in incrementals: + if k in conf_configdict: + conf_configdict[k] = \ + conf_configdict[k] + " " + v + else: + conf_configdict[k] = v + else: + conf_configdict[k] = v + expand_map[k] = v + + for k, v in penvdict.items(): + self._penvdict.setdefault(k.cp, {})[k] = v + self._local_repo_configs = {} self._local_repo_conf_path = \ os.path.join(abs_user_config, 'repos.conf') @@ -1140,6 +1177,12 @@ class config(object): self.features.add('chflags') self["FEATURES"] = " ".join(sorted(self.features)) + # We make a backup of backupenv, before the original + # FEATURES setting is overwritten. This backup provides + # access to negative FEATURES incrementals from the + # environment, useful for overriding FEATURES + # settings from package.env. + self._backupenv = self.backupenv.copy() self.backup_changes("FEATURES") if 'parse-eapi-ebuild-head' in self.features: _validate_cache_for_unsupported_eapis = False @@ -1351,6 +1394,7 @@ class config(object): if not keeping_pkg: self.mycpv = None self.puse = "" + del self._penv[:] self.configdict["pkg"].clear() self.configdict["pkginternal"].clear() self.configdict["defaults"]["USE"] = \ @@ -1550,11 +1594,9 @@ class config(object): aux_keys = self._setcpv_aux_keys - # Discard any existing metadata from the previous package, but - # preserve things like USE_EXPAND values and PORTAGE_USE which - # might be reused. - for k in aux_keys: - pkg_configdict.pop(k, None) + # Discard any existing metadata and package.env settings from + # the previous package instance. + pkg_configdict.clear() pkg_configdict["CATEGORY"] = cat pkg_configdict["PF"] = pf @@ -1628,16 +1670,74 @@ class config(object): self.configdict["pkg"]["PKGUSE"] = self.puse[:] # For saving to PUSE file self.configdict["pkg"]["USE"] = self.puse[:] # this gets appended to USE + oldpenv = self._penv + self._penv = [] + cpdict = self._penvdict.get(cp) + if cpdict: + penv_matches = _ordered_by_atom_specificity(cpdict, cpv_slot) + if penv_matches: + for x in penv_matches: + self._penv.extend(x) + + protected_pkg_keys = set(pkg_configdict) + protected_pkg_keys.discard('USE') + + # If there are _any_ package.env settings for this package + # then it automatically triggers config.reset(), in order + # to account for possible incremental interaction between + # package.use, package.env, and overrides from the calling + # environment (configdict['env']). + if oldpenv != self._penv or self._penv: + has_changed = True + # USE is special because package.use settings override + # it. Discard any package.use settings here and they'll + # be added back later. + pkg_configdict.pop('USE', None) + self._grab_pkg_env(self._penv, pkg_configdict, + protected_keys=protected_pkg_keys) + + # Now add package.use settings, which override USE from + # package.env + if self.puse: + if 'USE' in pkg_configdict: + pkg_configdict['USE'] = \ + pkg_configdict['USE'] + " " + self.puse + else: + pkg_configdict['USE'] = self.puse + if has_changed: self.reset(keeping_pkg=1,use_cache=use_cache) + env_configdict = self.configdict['env'] + if 'FEATURES' not in pkg_configdict: + env_configdict['FEATURES'] = self.backupenv['FEATURES'] + else: + # Now stack FEATURES manually, since self.regenerate() + # avoid restacking it, in case features have been + # internally disabled by portage. + features_stack = [] + features_stack.append(self.features) + pkg_features = pkg_configdict.get('FEATURES') + if pkg_features: + features_stack.append(pkg_features.split()) + # Note that this is a special _backupenv that provides + # access to negative FEATURES incrementals from the + # environment, useful for overriding FEATURES + # settings from package.env. + env_features = self._backupenv.get('FEATURES') + if env_features: + features_stack.append(env_features.split()) + + env_configdict['FEATURES'] = \ + " ".join(sorted(stack_lists(features_stack))) + # TODO: Update self.features, so package.env can + # effect features on the python side. + # Ensure that "pkg" values are always preferred over "env" values. # This must occur _after_ the above reset() call, since reset() # copies values from self.backupenv. - env_configdict = self.configdict['env'] - for k in pkg_configdict: - if k != 'USE': - env_configdict.pop(k, None) + for k in protected_pkg_keys: + env_configdict.pop(k, None) lazy_vars = self._lazy_vars(built_use, self) env_configdict.addLazySingleton('ACCEPT_LICENSE', @@ -1661,7 +1761,7 @@ class config(object): portage_iuse.update(explicit_iuse) # PORTAGE_IUSE is not always needed so it's lazily evaluated. - self.configdict["pkg"].addLazySingleton( + self.configdict["env"].addLazySingleton( "PORTAGE_IUSE", _lazy_iuse_regex, portage_iuse) ebuild_force_test = self.get("EBUILD_FORCE_TEST") == "1" @@ -1726,9 +1826,41 @@ class config(object): # attribute since we still want to be able to see global USE # settings for things like emerge --info. - self.configdict["pkg"]["PORTAGE_USE"] = \ + self.configdict["env"]["PORTAGE_USE"] = \ " ".join(sorted(x for x in use if x[-2:] != '_*')) + def _grab_pkg_env(self, penv, container, protected_keys=None): + if protected_keys is None: + protected_keys = () + abs_user_config = os.path.join( + self['PORTAGE_CONFIGROOT'], USER_CONFIG_PATH) + non_user_variables = self._non_user_variables + # Make a copy since we don't want per-package settings + # to pollute the global expand_map. + expand_map = self._expand_map.copy() + incrementals = self.incrementals + for envname in penv: + penvfile = os.path.join(abs_user_config, "env", envname) + penvconfig = getconfig(penvfile, expand=expand_map) + if penvconfig is None: + writemsg("!!! %s references non-existent file: %s\n" % \ + (os.path.join(abs_user_config, 'package.env'), penvfile), + noiselevel=-1) + else: + for k, v in penvconfig.items(): + if k in protected_keys or \ + k in non_user_variables: + writemsg("!!! Illegal variable " + \ + "'%s' assigned in '%s'\n" % \ + (k, penvfile), noiselevel=-1) + elif k in incrementals: + if k in container: + container[k] = container[k] + " " + v + else: + container[k] = v + else: + container[k] = v + def _get_implicit_iuse(self): """ Some flags are considered to -- 2.26.2