From 550d25fc93d087186438bbbf1cc7aacbfd6bbc8f Mon Sep 17 00:00:00 2001
From: Bertrand SIMONNET <bsimonnet@chromium.org>
Date: Tue, 29 Jul 2014 15:14:40 -0700
Subject: [PATCH] Make package.env and env profile specific

Allows profiles to override environment variables throught package.env and env.
package.env becomes a per-profile configuration like package.use and
package.mask.

The environment bash file invoked by package.env must me placed in the same
profile.

Change-Id: I16bcd75790213d2204f83d4aa6e8b910f8829b6e
---
 bin/ebuild.sh                        | 78 ++++++++++++++++++++++--------------
 pym/portage/const.py                 |  1 +
 pym/portage/package/ebuild/config.py | 55 ++++++++++++++-----------
 3 files changed, 80 insertions(+), 54 deletions(-)

diff --git a/bin/ebuild.sh b/bin/ebuild.sh
index be044e0..e188df7 100755
--- a/bin/ebuild.sh
+++ b/bin/ebuild.sh
@@ -67,11 +67,11 @@ unset BASH_ENV
 # earlier portage versions do not detect a version change in this case
 # (9999 to 9999) and therefore they try execute an incompatible version of
 # ebuild.sh during the upgrade.
-export PORTAGE_BZIP2_COMMAND=${PORTAGE_BZIP2_COMMAND:-bzip2} 
+export PORTAGE_BZIP2_COMMAND=${PORTAGE_BZIP2_COMMAND:-bzip2}
 
 # These two functions wrap sourcing and calling respectively.  At present they
 # perform a qa check to make sure eclasses and ebuilds and profiles don't mess
-# with shell opts (shopts).  Ebuilds/eclasses changing shopts should reset them 
+# with shell opts (shopts).  Ebuilds/eclasses changing shopts should reset them
 # when they are done.
 
 __qa_source() {
@@ -275,7 +275,7 @@ inherit() {
 		set +f
 
 		__qa_source "$location" || die "died sourcing $location in inherit()"
-		
+
 		#turn off glob expansion
 		set -f
 
@@ -290,7 +290,7 @@ inherit() {
 
 		[ "${B_IUSE+set}"     = set ] && IUSE="${B_IUSE}"
 		[ "${B_IUSE+set}"     = set ] || unset IUSE
-		
+
 		[ "${B_REQUIRED_USE+set}"     = set ] && REQUIRED_USE="${B_REQUIRED_USE}"
 		[ "${B_REQUIRED_USE+set}"     = set ] || unset REQUIRED_USE
 
@@ -372,42 +372,60 @@ __source_all_bashrcs() {
 		restore_IFS
 		for x in "${path_array[@]}" ; do
 			[ -f "$x/profile.bashrc" ] && __qa_source "$x/profile.bashrc"
+			[[ " ${FEATURES} " == *" per-profile-env "* ]] && \
+				__source_env_files "$x/env"
 		done
 	fi
 
-	if [ -r "${PORTAGE_BASHRC}" ] ; then
-		if [ "$PORTAGE_DEBUG" != "1" ] || [ "${-/x/}" != "$-" ]; then
-			source "${PORTAGE_BASHRC}"
-		else
-			set -x
-			source "${PORTAGE_BASHRC}"
-			set +x
-		fi
-	fi
+	# The user's bashrc is the ONLY non-portage bit of code
+	# that can change shopts without a QA violation.
+	__try_source "${PORTAGE_BASHRC}" "no_qa"
 
 	if [[ $EBUILD_PHASE != depend ]] ; then
-		# The user's bashrc is the ONLY non-portage bit of code that can
-		# change shopts without a QA violation.
-		for x in "${PM_EBUILD_HOOK_DIR}"/${CATEGORY}/{${PN},${PN}:${SLOT%/*},${P},${PF}}; do
-			if [ -r "${x}" ]; then
-				# If $- contains x, then tracing has already been enabled
-				# elsewhere for some reason. We preserve it's state so as
-				# not to interfere.
-				if [ "$PORTAGE_DEBUG" != "1" ] || [ "${-/x/}" != "$-" ]; then
-					source "${x}"
-				else
-					set -x
-					source "${x}"
-					set +x
-				fi
-			fi
-		done
+		__source_env_files "${PM_EBUILD_HOOK_DIR}" "no_qa"
 	fi
 
 	[ ! -z "${OCC}" ] && export CC="${OCC}"
 	[ ! -z "${OCXX}" ] && export CXX="${OCXX}"
 }
 
+# @FUNCTION: __source_env_files
+# @DESCRIPTION:
+# Source the files relevant to the current package from the given path.
+# If $2 is not 'no_qa', all the scripts are sourced with __qa_source.
+__source_env_files() {
+	for x in "${1}"/${CATEGORY}/{${PN},${PN}:${SLOT%/*},${P},${PF}}; do
+		__try_source "${x}" "${2}"
+	done
+}
+
+# @FUNCTION: __try_source
+# @DESCRIPTION:
+# If the path given as argument exists, source the file while preserving
+# $-.
+# If $2 is not 'no_qa', the file is sourced with __qa_source.
+__try_source() {
+	if [[ -r "$1" ]]; then
+		# If $- contains x, then tracing has already been enabled
+		# elsewhere for some reason. We preserve it's state so as
+		# not to interfere.
+		if [[ "$PORTAGE_DEBUG" != "1" ]] || [[ "${-/x/}" != "$-" ]]; then
+			if [[ "${2}" == "no_qa" ]]; then
+				source "${x}"
+			else
+				__qa_source "${x}"
+			fi
+		else
+			set -x
+			if [[ "${2}" == "no_qa" ]]; then
+				source "${x}"
+			else
+				__qa_source "${x}"
+			fi
+			set +x
+		fi
+	fi
+}
 # === === === === === === === === === === === === === === === === === ===
 # === === === === === functions end, main part begins === === === === ===
 # === === === === === === === === === === === === === === === === === ===
@@ -572,7 +590,7 @@ if ! has "$EBUILD_PHASE" clean cleanrm ; then
 		PDEPEND+="${PDEPEND:+ }${E_PDEPEND}"
 		HDEPEND+="${HDEPEND:+ }${E_HDEPEND}"
 		REQUIRED_USE+="${REQUIRED_USE:+ }${E_REQUIRED_USE}"
-		
+
 		unset ECLASS E_IUSE E_REQUIRED_USE E_DEPEND E_RDEPEND E_PDEPEND E_HDEPEND \
 			__INHERITED_QA_CACHE
 
diff --git a/pym/portage/const.py b/pym/portage/const.py
index aab6e8a..3043656 100644
--- a/pym/portage/const.py
+++ b/pym/portage/const.py
@@ -166,6 +166,7 @@ SUPPORTED_FEATURES       = frozenset([
 	"notitles",
 	"parallel-fetch",
 	"parallel-install",
+	"per-profile-env",
 	"prelink-checksums",
 	"preserve-libs",
 	"protect-owned",
diff --git a/pym/portage/package/ebuild/config.py b/pym/portage/package/ebuild/config.py
index f639e14..6c695fe 100644
--- a/pym/portage/package/ebuild/config.py
+++ b/pym/portage/package/ebuild/config.py
@@ -719,28 +719,38 @@ class config(object):
 					self._paccept_restrict.setdefault(k.cp, {})[k] = v
 
 				#package.env
-				penvdict = grabdict_package(os.path.join(
-					abs_user_config, "package.env"), recursive=1, allow_wildcard=True, \
-					allow_repo=True, verify_eapi=False)
-				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
+				envlocations = [v.location for v in profiles_complex]
+				envlocations.append(os.path.join(self['PORTAGE_CONFIGROOT'], USER_CONFIG_PATH))
+				penvdicts = [(grabdict_package(os.path.join(x, "package.env"),
+								recursive=1, allow_wildcard=True,
+								allow_repo=True, verify_eapi=False), x) \
+				 for x in envlocations]
+
+				for (penvdict, location) in penvdicts:
+					v = penvdict.pop("*/*", None)
+					if v is not None:
+						global_wildcard_conf = {}
+						v = [os.path.join(location, 'env', envname) \
+							for envname in v]
+						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
-						else:
-							conf_configdict[k] = v
-						expand_map[k] = v
+								expand_map[k] = v
 
-				for k, v in penvdict.items():
-					self._penvdict.setdefault(k.cp, {})[k] = v
+					for k, v in penvdict.items():
+						penvfiles = [os.path.join(location, 'env', envname) \
+							for envname in v]
+						self._penvdict.setdefault(k.cp, {})\
+							.setdefault(k, []).extend(penvfiles)
 
 			#getting categories from an external file now
 			self.categories = [grabfile(os.path.join(x, "categories")) \
@@ -1692,18 +1702,15 @@ class config(object):
 		# setcpv triggers lazy instantiation of things like _use_manager.
 		_eapi_cache.clear()
 
-	def _grab_pkg_env(self, penv, container, protected_keys=None):
+	def _grab_pkg_env(self, penvfiles, 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)
+		for penvfile in penvfiles:
 			penvconfig = getconfig(penvfile, tolerant=self._tolerant,
 				allow_sourcing=True, expand=expand_map)
 			if penvconfig is None:
-- 
2.1.0.rc2.206.gedb03e5

