Use an explicit USE_ORDER entry to control mapping FEATURES=test into
default-enabled USE=test, rather than forcing/masking it depending
on the state of FEATURES.

This makes it possible for users to enable (or disable) USE=test
independently of FEATURES.  An example use case is installing test
dependencies and building test cases without actually running tests
at a particular moment which is something I've been doing quite
frequently with LLVM.
---
 cnf/make.globals                              |   2 +-
 lib/portage/eapi.py                           |   2 +-
 lib/portage/package/ebuild/config.py          |  51 +++++----
 .../tests/resolver/test_features_test_use.py  | 108 +++++++++++-------
 man/make.conf.5                               |   6 +-
 5 files changed, 101 insertions(+), 68 deletions(-)

diff --git a/cnf/make.globals b/cnf/make.globals
index 08a37a534..04a708af8 100644
--- a/cnf/make.globals
+++ b/cnf/make.globals
@@ -107,7 +107,7 @@ CONFIG_PROTECT="/etc"
 CONFIG_PROTECT_MASK="/etc/env.d"
 
 # Disable auto-use
-USE_ORDER="env:pkg:conf:defaults:pkginternal:repo:env.d"
+USE_ORDER="env:pkg:conf:defaults:pkginternal:features:repo:env.d"
 
 # Mode bits for ${WORKDIR} (see ebuild.5).
 PORTAGE_WORKDIR_MODE="0700"
diff --git a/lib/portage/eapi.py b/lib/portage/eapi.py
index 158d58243..5e12e976d 100644
--- a/lib/portage/eapi.py
+++ b/lib/portage/eapi.py
@@ -170,7 +170,7 @@ def _get_eapi_attrs(eapi):
                exports_EBUILD_PHASE_FUNC = (eapi is None or 
eapi_exports_EBUILD_PHASE_FUNC(eapi)),
                exports_PORTDIR = (eapi is None or eapi_exports_PORTDIR(eapi)),
                exports_ECLASSDIR = (eapi is not None and 
eapi_exports_ECLASSDIR(eapi)),
-               feature_flag_test = True,
+               feature_flag_test = False,
                feature_flag_targetroot = (eapi is not None and 
eapi_has_targetroot(eapi)),
                hdepend = (eapi is not None and eapi_has_hdepend(eapi)),
                iuse_defaults = (eapi is None or eapi_has_iuse_defaults(eapi)),
diff --git a/lib/portage/package/ebuild/config.py 
b/lib/portage/package/ebuild/config.py
index 320d9f6c0..e253b680b 100644
--- a/lib/portage/package/ebuild/config.py
+++ b/lib/portage/package/ebuild/config.py
@@ -294,6 +294,7 @@ class config(object):
                        self.configlist = [
                                self.configdict['env.d'],
                                self.configdict['repo'],
+                               self.configdict['features'],
                                self.configdict['pkginternal'],
                                self.configdict['globals'],
                                self.configdict['defaults'],
@@ -461,13 +462,16 @@ class config(object):
                        # back up our incremental variables:
                        self.configdict={}
                        self._use_expand_dict = {}
-                       # configlist will contain: [ env.d, globals, defaults, 
conf, pkg, backupenv, env ]
+                       # configlist will contain: [ env.d, globals, features, 
defaults, conf, pkg, backupenv, env ]
                        self.configlist.append({})
                        self.configdict["env.d"] = self.configlist[-1]
 
                        self.configlist.append({})
                        self.configdict["repo"] = self.configlist[-1]
 
+                       self.configlist.append({})
+                       self.configdict["features"] = self.configlist[-1]
+
                        self.configlist.append({})
                        self.configdict["pkginternal"] = self.configlist[-1]
 
@@ -868,7 +872,7 @@ class config(object):
                        # reasonable defaults; this is important as without 
USE_ORDER,
                        # USE will always be "" (nothing set)!
                        if "USE_ORDER" not in self:
-                               self["USE_ORDER"] = 
"env:pkg:conf:defaults:pkginternal:repo:env.d"
+                               self["USE_ORDER"] = 
"env:pkg:conf:defaults:pkginternal:features:repo:env.d"
                                self.backup_changes("USE_ORDER")
 
                        if "CBUILD" not in self and "CHOST" in self:
@@ -1292,6 +1296,7 @@ class config(object):
                        del self._penv[:]
                        self.configdict["pkg"].clear()
                        self.configdict["pkginternal"].clear()
+                       self.configdict["features"].clear()
                        self.configdict["repo"].clear()
                        self.configdict["defaults"]["USE"] = \
                                " ".join(self.make_defaults_use)
@@ -1452,6 +1457,7 @@ class config(object):
                cp = cpv_getkey(mycpv)
                cpv_slot = self.mycpv
                pkginternaluse = ""
+               feature_use = ""
                iuse = ""
                pkg_configdict = self.configdict["pkg"]
                previous_iuse = pkg_configdict.get("IUSE")
@@ -1650,6 +1656,23 @@ class config(object):
                if has_changed:
                        self.reset(keeping_pkg=1)
 
+               if explicit_iuse is None:
+                       explicit_iuse = frozenset(x.lstrip("+-") for x in 
iuse.split())
+               if eapi_attrs.iuse_effective:
+                       iuse_implicit_match = self._iuse_effective_match
+               else:
+                       iuse_implicit_match = self._iuse_implicit_match
+
+               if "test" in explicit_iuse or iuse_implicit_match("test"):
+                       if "test" in self.features:
+                               feature_use += "test"
+
+               if feature_use != self.configdict["features"].get("USE", ""):
+                       self.configdict["features"]["USE"] = feature_use
+                       # TODO: can we avoid that?
+                       self.reset(keeping_pkg=1)
+                       has_changed = True
+
                env_configdict = self.configdict['env']
 
                # Ensure that "pkg" values are always preferred over "env" 
values.
@@ -1677,11 +1700,8 @@ class config(object):
                # package has different IUSE.
                use = set(self["USE"].split())
                unfiltered_use = frozenset(use)
-               if explicit_iuse is None:
-                       explicit_iuse = frozenset(x.lstrip("+-") for x in 
iuse.split())
 
                if eapi_attrs.iuse_effective:
-                       iuse_implicit_match = self._iuse_effective_match
                        portage_iuse = set(self._iuse_effective)
                        portage_iuse.update(explicit_iuse)
                        if built_use is not None:
@@ -1693,7 +1713,6 @@ class config(object):
                        self.configdict["pkg"]["IUSE_EFFECTIVE"] = \
                                " ".join(sorted(portage_iuse))
                else:
-                       iuse_implicit_match = self._iuse_implicit_match
                        portage_iuse = self._get_implicit_iuse()
                        portage_iuse.update(explicit_iuse)
 
@@ -1729,21 +1748,17 @@ class config(object):
                        self.get("EBUILD_FORCE_TEST") == "1"
 
                if "test" in explicit_iuse or iuse_implicit_match("test"):
-                       if "test" not in self.features:
-                               use.discard("test")
-                       elif restrict_test or \
+                       if "test" in self.features:
+                               if ebuild_force_test and "test" in self.usemask:
+                                       self.usemask = \
+                                               frozenset(x for x in 
self.usemask if x != "test")
+                       if restrict_test or \
                                ("test" in self.usemask and not 
ebuild_force_test):
                                # "test" is in IUSE and USE=test is masked, so 
execution
                                # of src_test() probably is not reliable. 
Therefore,
                                # temporarily disable FEATURES=test just for 
this package.
                                self["FEATURES"] = " ".join(x for x in 
self.features \
                                        if x != "test")
-                               use.discard("test")
-                       else:
-                               use.add("test")
-                               if ebuild_force_test and "test" in self.usemask:
-                                       self.usemask = \
-                                               frozenset(x for x in 
self.usemask if x != "test")
 
                if eapi_attrs.feature_flag_targetroot and \
                        ("targetroot" in explicit_iuse or 
iuse_implicit_match("targetroot")):
@@ -1900,12 +1915,6 @@ class config(object):
                iuse_implicit.add("build")
                iuse_implicit.add("bootstrap")
 
-               # Controlled by FEATURES=test. Make this implicit, so handling
-               # of FEATURES=test is consistent regardless of explicit IUSE.
-               # Users may use use.mask/package.use.mask to control
-               # FEATURES=test for all ebuilds, regardless of explicit IUSE.
-               iuse_implicit.add("test")
-
                return iuse_implicit
 
        def _getUseMask(self, pkg, stable=None):
diff --git a/lib/portage/tests/resolver/test_features_test_use.py 
b/lib/portage/tests/resolver/test_features_test_use.py
index bdd179d7a..da7172c17 100644
--- a/lib/portage/tests/resolver/test_features_test_use.py
+++ b/lib/portage/tests/resolver/test_features_test_use.py
@@ -1,68 +1,88 @@
-# Copyright 2012 Gentoo Foundation
+# Copyright 2012-2018 Gentoo Foundation
 # Distributed under the terms of the GNU General Public License v2
 
 from portage.tests import TestCase
 from portage.tests.resolver.ResolverPlayground import (ResolverPlayground,
        ResolverPlaygroundTestCase)
 
-class FeaturesTestUse(TestCase):
 
-       def testFeaturesTestUse(self):
-               ebuilds = {
-                       "dev-libs/A-1" : {
-                               "IUSE": "test"
-                       },
-                       "dev-libs/B-1" : {
-                               "IUSE": "test foo"
-                       },
-               }
+class TestDepend(TestCase):
+       ebuilds = {
+               "dev-libs/A-1" : {
+                       "IUSE": "test",
+                       "DEPEND": "test? ( dev-libs/B )",
+               },
+               "dev-libs/B-1" : {
+               },
+       }
 
-               installed = {
-                       "dev-libs/A-1" : {
-                               "USE": "",
-                               "IUSE": "test"
-                       },
-                       "dev-libs/B-1" : {
-                               "USE": "foo",
-                               "IUSE": "test foo"
-                       },
-               }
+       installed = {
+               "dev-libs/A-1" : {
+                       "USE": "",
+                       "IUSE": "test",
+                       "DEPEND": "test? ( dev-libs/B )",
+               },
+       }
 
+       def test_default_use_test(self):
+               """
+               Test that FEATURES=test enables USE=test by default.
+               """
                user_config = {
-                       "make.conf" : ("FEATURES=test", "USE=\"-test -foo\"")
+                       "make.conf" : ("FEATURES=test", "USE=\"\"")
                }
-
-               test_cases = (
-
-                       # USE=test state should not trigger --newuse rebuilds, 
as
-                       # specified in bug #373209, comment #3.
-                       ResolverPlaygroundTestCase(
+               test_case = ResolverPlaygroundTestCase(
                                ["dev-libs/A"],
-                               options = {"--newuse": True, "--selective": 
True},
+                               options = {},
                                success = True,
-                               mergelist = []),
+                               mergelist = ["dev-libs/B-1", "dev-libs/A-1"])
+
+               playground = ResolverPlayground(ebuilds=self.ebuilds,
+                       user_config=user_config, debug=False)
+               try:
+                       playground.run_TestCase(test_case)
+                       self.assertEqual(test_case.test_success, True, 
test_case.fail_msg)
+               finally:
+                       playground.cleanup()
 
-                       # USE=-test -> USE=test, with USE=test forced by 
FEATURES=test
-                       ResolverPlaygroundTestCase(
+       def test_no_forced_use_test(self):
+               """
+               Test that FEATURES=test no longer forces USE=test.
+               """
+               user_config = {
+                       "make.conf" : ("FEATURES=test", "USE=\"-test\"")
+               }
+               test_case = ResolverPlaygroundTestCase(
                                ["dev-libs/A"],
                                options = {},
                                success = True,
-                               mergelist = ["dev-libs/A-1"]),
+                               mergelist = ["dev-libs/A-1"])
+
+               playground = ResolverPlayground(ebuilds=self.ebuilds,
+                       user_config=user_config, debug=False)
+               try:
+                       playground.run_TestCase(test_case)
+                       self.assertEqual(test_case.test_success, True, 
test_case.fail_msg)
+               finally:
+                       playground.cleanup()
 
-                       # USE=foo -> USE=-foo, with USE=test forced by 
FEATURES=test
-                       ResolverPlaygroundTestCase(
-                               ["dev-libs/B"],
+       def test_newuse(self):
+               """
+               Test that --newuse now detects USE=test changes.
+               """
+               user_config = {
+                       "make.conf" : ("FEATURES=test", "USE=\"\"")
+               }
+               test_case = ResolverPlaygroundTestCase(
+                               ["dev-libs/A"],
                                options = {"--newuse": True, "--selective": 
True},
                                success = True,
-                               mergelist = ["dev-libs/B-1"]),
-               )
+                               mergelist = ["dev-libs/B-1", "dev-libs/A-1"])
 
-               playground = ResolverPlayground(ebuilds=ebuilds,
-                       installed=installed, user_config=user_config, 
debug=False)
+               playground = ResolverPlayground(ebuilds=self.ebuilds,
+                       user_config=user_config, debug=False)
                try:
-                       for test_case in test_cases:
-                               playground.run_TestCase(test_case)
-                               self.assertEqual(test_case.test_success, True, 
test_case.fail_msg)
+                       playground.run_TestCase(test_case)
+                       self.assertEqual(test_case.test_success, True, 
test_case.fail_msg)
                finally:
                        playground.cleanup()
-
diff --git a/man/make.conf.5 b/man/make.conf.5
index cb0f00237..a4e33923c 100644
--- a/man/make.conf.5
+++ b/man/make.conf.5
@@ -1138,7 +1138,7 @@ This variable contains options that control the build 
behavior of several
 packages.  More information in \fBebuild\fR(5).  Possible USE values
 can be found in \fI/usr/portage/profiles/use.desc\fR.
 .TP
-\fBUSE_ORDER\fR = \fI"env:pkg:conf:defaults:pkginternal:repo:env.d"\fR
+\fBUSE_ORDER\fR = \fI"env:pkg:conf:defaults:pkginternal:features:repo:env.d"\fR
 Determines the precedence of layers in the incremental stacking of the USE
 variable. Precedence decreases from left to right such that env overrides
 pkg, pkg overrides conf, and so forth.
@@ -1167,6 +1167,10 @@ USE from make.defaults and package.use in the profile
 .B pkginternal
 USE from \fBebuild\fR(5) IUSE defaults
 .TP
+.B features
+Flags implied by FEATURES.  Currently includes USE=\fBtest\fR
+for FEATURES=\fBtest\fR.
+.TP
 .B repo
 USE from make.defaults and package.use in the repo's profiles/ top dir
 (e.g. /usr/portage/profiles/package.use) (see \fBportage\fR(5))
-- 
2.18.0


Reply via email to