diff --git a/bin/isolated-functions.sh b/bin/isolated-functions.sh index cbd93fce97..fb3243e7eb 100644 --- a/bin/isolated-functions.sh +++ b/bin/isolated-functions.sh @@ -8,6 +8,15 @@ if ___eapi_has_version_functions; then source "${PORTAGE_BIN_PATH}/eapi7-ver-funcs.sh" || exit 1 fi +if [[ -v PORTAGE_EBUILD_EXTRA_SOURCE ]]; then + source "${PORTAGE_EBUILD_EXTRA_SOURCE}" || exit 1 + # We delierbately do not unset PORTABE_EBUILD_EXTRA_SOURCE, so + # that it keeps being exported in the environment of this + # process and its child processeses. There, for example portage + # helper like doins, can pick it up and set the PMS variables + # (usually by sourcing isolated-functions.sh). +fi + # We need this next line for "die" and "assert". It expands # It _must_ preceed all the calls to die and assert. shopt -s expand_aliases diff --git a/bin/phase-functions.sh b/bin/phase-functions.sh index 5257101cf4..942da59795 100644 --- a/bin/phase-functions.sh +++ b/bin/phase-functions.sh @@ -20,6 +20,7 @@ PORTAGE_READONLY_VARS="D EBUILD EBUILD_PHASE EBUILD_PHASE_FUNC \ PORTAGE_BUILD_USER PORTAGE_BUNZIP2_COMMAND \ PORTAGE_BZIP2_COMMAND PORTAGE_COLORMAP PORTAGE_CONFIGROOT \ PORTAGE_DEBUG PORTAGE_DEPCACHEDIR PORTAGE_EBUILD_EXIT_FILE \ + PORTAGE_EBUILD_EXTRA_SOURCE \ PORTAGE_ECLASS_LOCATIONS PORTAGE_EXPLICIT_INHERIT \ PORTAGE_GID PORTAGE_GRPNAME PORTAGE_INST_GID PORTAGE_INST_UID \ PORTAGE_INTERNAL_CALLER PORTAGE_IPC_DAEMON PORTAGE_IUSE PORTAGE_LOG_FILE \ diff --git a/cnf/make.globals b/cnf/make.globals index 94eac65684..dba24c73c7 100644 --- a/cnf/make.globals +++ b/cnf/make.globals @@ -81,7 +81,7 @@ FEATURES="assume-digests binpkg-docompress binpkg-dostrip binpkg-logs network-sandbox news parallel-fetch pkgdir-index-trusted pid-sandbox preserve-libs protect-owned qa-unresolved-soname-deps sandbox strict unknown-features-warn unmerge-logs unmerge-orphans userfetch - userpriv usersandbox usersync" + userpriv usersandbox usersync export-pms-vars" # Ignore file collisions in /lib/modules since files inside this directory # are never unmerged, and therefore collisions must be ignored in order for diff --git a/lib/_emerge/EbuildMetadataPhase.py b/lib/_emerge/EbuildMetadataPhase.py index 54177840c7..524a8507b7 100644 --- a/lib/_emerge/EbuildMetadataPhase.py +++ b/lib/_emerge/EbuildMetadataPhase.py @@ -36,6 +36,7 @@ class EbuildMetadataPhase(SubProcess): "repo_path", "settings", "deallocate_config", + "portage_ebuild_extra_source", "write_auxdb", ) + ( "_eapi", @@ -165,6 +166,9 @@ def _async_start_done(self, future): self.cancel() self._was_cancelled() + self.portage_ebuild_extra_source = self.settings.get( + "PORTAGE_EBUILD_EXTRA_SOURCE" + ) if self.deallocate_config is not None and not self.deallocate_config.done(): self.deallocate_config.set_result(self.settings) @@ -191,6 +195,8 @@ def _unregister(self): if self._files is not None: self.scheduler.remove_reader(self._files.ebuild) SubProcess._unregister(self) + if self.portage_ebuild_extra_source: + os.unlink(self.portage_ebuild_extra_source) def _async_waitpid_cb(self, *args, **kwargs): """ diff --git a/lib/portage/const.py b/lib/portage/const.py index c9a71009a7..2049f51311 100644 --- a/lib/portage/const.py +++ b/lib/portage/const.py @@ -183,6 +183,7 @@ "distlocks", "downgrade-backup", "ebuild-locks", + "export-pms-vars", "fail-clean", "fakeroot", "fixlafiles", diff --git a/lib/portage/eapi.py b/lib/portage/eapi.py index 86b27bdbc5..28ea8be4b7 100644 --- a/lib/portage/eapi.py +++ b/lib/portage/eapi.py @@ -1,4 +1,4 @@ -# Copyright 2010-2021 Gentoo Authors +# Copyright 2010-2024 Gentoo Authors # Distributed under the terms of the GNU General Public License v2 import collections @@ -48,6 +48,10 @@ def eapi_supports_prefix(eapi: str) -> bool: return _get_eapi_attrs(eapi).prefix +def eapi_exports_pms_vars(eapi: str) -> bool: + return _get_eapi_attrs(eapi).exports_pms_vars + + def eapi_exports_AA(eapi: str) -> bool: return _get_eapi_attrs(eapi).exports_AA @@ -157,6 +161,7 @@ def eapi_has_sysroot(eapi: str) -> bool: "exports_ECLASSDIR", "exports_KV", "exports_merge_type", + "exports_pms_vars", "exports_PORTDIR", "exports_replace_vars", "feature_flag_test", @@ -198,6 +203,7 @@ class Eapi: "6", "7", "8", + "9", ) _eapi_val: int = -1 @@ -236,6 +242,7 @@ def _get_eapi_attrs(eapi_str: Optional[str]) -> _eapi_attrs: exports_ECLASSDIR=False, exports_KV=False, exports_merge_type=True, + exports_pms_vars=True, exports_PORTDIR=True, exports_replace_vars=True, feature_flag_test=False, @@ -275,6 +282,7 @@ def _get_eapi_attrs(eapi_str: Optional[str]) -> _eapi_attrs: exports_ECLASSDIR=eapi <= Eapi("6"), exports_KV=eapi <= Eapi("3"), exports_merge_type=eapi >= Eapi("4"), + exports_pms_vars=eapi <= Eapi("8"), exports_PORTDIR=eapi <= Eapi("6"), exports_replace_vars=eapi >= Eapi("4"), feature_flag_test=False, diff --git a/lib/portage/package/ebuild/doebuild.py b/lib/portage/package/ebuild/doebuild.py index 1d257d52db..7eee5047ee 100644 --- a/lib/portage/package/ebuild/doebuild.py +++ b/lib/portage/package/ebuild/doebuild.py @@ -84,6 +84,7 @@ from portage.eapi import ( eapi_exports_KV, eapi_exports_merge_type, + eapi_exports_pms_vars, eapi_exports_replace_vars, eapi_has_required_use, eapi_has_src_prepare_and_src_configure, @@ -189,6 +190,57 @@ "RESTRICT", ) +# The following is a set of PMS § 11.1 and § 7.4 without +# - TMPDIR +# - HOME +# because these variables are often assumed to be exported and +# therefore consumed by child processes. +_unexported_pms_vars = frozenset( + # fmt: off + [ + # PMS § 11.1 Defined Variables + "P", + "PF", + "PN", + "CATEGORY", + "PV", + "PR", + "PVR", + "A", + "AA", + "FILESDIR", + "DISTDIR", + "WORKDIR", + "S", + "PORTDIR", + "ECLASSDIR", + "ROOT", + "EROOT", + "SYSROOT", + "ESYSROOT", + "BROOT", + "T", +# "TMPDIR", # EXPORTED: often assumed to be exported and available to child processes +# "HOME", # EXPORTED: often assumed to be exported and available to child processes + "EPREFIX", + "D", + "ED", + "DESTTREE", + "INSDESTTREE", + "EBUILD_PHASE", + "EBUILD_PHASE_FUNC", + "KV", + "MERGE_TYPE", + "REPLACING_VERSIONS", + "REPLACED_BY_VERSION", + # PMS § 7.4 Magic Ebuild-defined Variables + "ECLASS", + "INHERITED", + "DEFINED_PHASES", + ] + # fmt: on +) + def _doebuild_spawn(phase, settings, actionmap=None, **kwargs): """ @@ -2133,9 +2185,57 @@ def spawn( logname_backup = mysettings.configdict["env"].get("LOGNAME") mysettings.configdict["env"]["LOGNAME"] = logname + eapi = mysettings["EAPI"] + + unexported_env_vars = None + if "export-pms-vars" not in mysettings.features or not eapi_exports_pms_vars(eapi): + unexported_env_vars = _unexported_pms_vars + + if unexported_env_vars: + # Starting with EAPI 9 (or if FEATURES="-export-pms-vars"), + # PMS variables should not longer be exported. + orig_env = mysettings.environ() + # Copy since we are potentially removing keys from the dict. + env = orig_env.copy() + + t = env["T"] + # The os.path.isdir() is probably not required, as T should + # exists if we are processing a phase that is in + # _phase_func_map. But better safe than sorry. + if mysettings.get("EBUILD_PHASE") in _phase_func_map.keys() and os.path.isdir( + t + ): + ebuild_extra_source_path = os.path.join(t, ".portage-ebuild-extra-source") + else: + ebuild_extra_source_fd, ebuild_extra_source_path = tempfile.mkstemp( + prefix="portage-ebuild-extra-source-", + ) + # Make sure that the file is writeable by us (see below) + # and that it is world readable. + os.fchmod( + ebuild_extra_source_fd, + stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH, + ) + os.close(ebuild_extra_source_fd) + # The file will be deleted by EbuildMetadataPhase._unregister() + mysettings["PORTAGE_EBUILD_EXTRA_SOURCE"] = ebuild_extra_source_path + + with open(ebuild_extra_source_path, mode="w") as f: + for var_name in unexported_env_vars: + var_value = orig_env.get(var_name) + if var_value is None: + continue + quoted_var_value = shlex.quote(var_value) + f.write(f"{var_name}={quoted_var_value}\n") + del env[var_name] + + env["PORTAGE_EBUILD_EXTRA_SOURCE"] = str(ebuild_extra_source_path) + else: + env = mysettings.environ() + try: if keywords.get("returnpid") or keywords.get("returnproc"): - return spawn_func(mystring, env=mysettings.environ(), **keywords) + return spawn_func(mystring, env=env, **keywords) proc = EbuildSpawnProcess( background=False,