[libcxx-commits] [libcxx] d085697 - [libc++] Add a new concept of ConfigAction, and use it in the DSL

Louis Dionne via libcxx-commits libcxx-commits at lists.llvm.org
Fri Oct 30 06:27:21 PDT 2020


Author: Louis Dionne
Date: 2020-10-30T09:27:15-04:00
New Revision: d085697013b447abd9a8779eddec606f48626bb9

URL: https://github.com/llvm/llvm-project/commit/d085697013b447abd9a8779eddec606f48626bb9
DIFF: https://github.com/llvm/llvm-project/commit/d085697013b447abd9a8779eddec606f48626bb9.diff

LOG: [libc++] Add a new concept of ConfigAction, and use it in the DSL

This will allow adding bare compiler flags through the new
configuration DSL. Previously, this would have required adding
a Lit feature for each such flag.

Differential Revision: https://reviews.llvm.org/D90429

Added: 
    

Modified: 
    libcxx/test/libcxx/selftest/dsl/dsl.sh.py
    libcxx/utils/libcxx/test/dsl.py
    libcxx/utils/libcxx/test/features.py
    libcxx/utils/libcxx/test/newconfig.py
    libcxx/utils/libcxx/test/params.py

Removed: 
    


################################################################################
diff  --git a/libcxx/test/libcxx/selftest/dsl/dsl.sh.py b/libcxx/test/libcxx/selftest/dsl/dsl.sh.py
index 27c88e9e3ca7..2a51828c6163 100644
--- a/libcxx/test/libcxx/selftest/dsl/dsl.sh.py
+++ b/libcxx/test/libcxx/selftest/dsl/dsl.sh.py
@@ -275,127 +275,100 @@ class TestFeature(SetupConfigs):
     def test_trivial(self):
         feature = dsl.Feature(name='name')
         origSubstitutions = copy.deepcopy(self.config.substitutions)
-        self.assertTrue(feature.isSupported(self.config))
-        feature.enableIn(self.config)
+        actions = feature.getActions(self.config)
+        self.assertTrue(len(actions) == 1)
+        for a in actions:
+            a.applyTo(self.config)
         self.assertEqual(origSubstitutions, self.config.substitutions)
         self.assertIn('name', self.config.available_features)
 
     def test_name_can_be_a_callable(self):
         feature = dsl.Feature(name=lambda cfg: 'name')
-        assert feature.isSupported(self.config)
-        self.assertEqual('name', feature.getName(self.config))
-        feature.enableIn(self.config)
+        for a in feature.getActions(self.config):
+            a.applyTo(self.config)
         self.assertIn('name', self.config.available_features)
 
     def test_name_is_not_a_string_1(self):
         feature = dsl.Feature(name=None)
-        assert feature.isSupported(self.config)
-        self.assertRaises(ValueError, lambda: feature.getName(self.config))
-        self.assertRaises(ValueError, lambda: feature.enableIn(self.config))
+        self.assertRaises(ValueError, lambda: feature.getActions(self.config))
+        self.assertRaises(ValueError, lambda: feature.pretty(self.config))
 
     def test_name_is_not_a_string_2(self):
         feature = dsl.Feature(name=lambda cfg: None)
-        assert feature.isSupported(self.config)
-        self.assertRaises(ValueError, lambda: feature.getName(self.config))
-        self.assertRaises(ValueError, lambda: feature.enableIn(self.config))
+        self.assertRaises(ValueError, lambda: feature.getActions(self.config))
+        self.assertRaises(ValueError, lambda: feature.pretty(self.config))
 
-    def test_getName_when_unsupported(self):
-        feature = dsl.Feature(name='name', when=lambda _: False)
-        assert not feature.isSupported(self.config)
-        self.assertRaises(AssertionError, lambda: feature.getName(self.config))
-
-    def test_adding_compile_flag(self):
-        feature = dsl.Feature(name='name', compileFlag='-foo')
+    def test_adding_action(self):
+        feature = dsl.Feature(name='name', actions=[dsl.AddCompileFlag('-std=c++03')])
         origLinkFlags = copy.deepcopy(self.getSubstitution('%{link_flags}'))
-        assert feature.isSupported(self.config)
-        feature.enableIn(self.config)
+        for a in feature.getActions(self.config):
+            a.applyTo(self.config)
         self.assertIn('name', self.config.available_features)
-        self.assertIn('-foo', self.getSubstitution('%{compile_flags}'))
+        self.assertIn('-std=c++03', self.getSubstitution('%{compile_flags}'))
         self.assertEqual(origLinkFlags, self.getSubstitution('%{link_flags}'))
 
-    def test_compile_flag_can_be_a_callable(self):
+    def test_actions_can_be_a_callable(self):
         feature = dsl.Feature(name='name',
-                              compileFlag=lambda cfg: (self.assertIs(self.config, cfg), '-foo')[1])
-        assert feature.isSupported(self.config)
-        feature.enableIn(self.config)
-        self.assertIn('-foo', self.getSubstitution('%{compile_flags}'))
-
-    def test_adding_link_flag(self):
-        feature = dsl.Feature(name='name', linkFlag='-foo')
-        origCompileFlags = copy.deepcopy(self.getSubstitution('%{compile_flags}'))
-        assert feature.isSupported(self.config)
-        feature.enableIn(self.config)
-        self.assertIn('name', self.config.available_features)
-        self.assertIn('-foo', self.getSubstitution('%{link_flags}'))
-        self.assertEqual(origCompileFlags, self.getSubstitution('%{compile_flags}'))
-
-    def test_link_flag_can_be_a_callable(self):
-        feature = dsl.Feature(name='name',
-                              linkFlag=lambda cfg: (self.assertIs(self.config, cfg), '-foo')[1])
-        assert feature.isSupported(self.config)
-        feature.enableIn(self.config)
-        self.assertIn('-foo', self.getSubstitution('%{link_flags}'))
-
-    def test_adding_both_flags(self):
-        feature = dsl.Feature(name='name', compileFlag='-hello', linkFlag='-world')
-        assert feature.isSupported(self.config)
-        feature.enableIn(self.config)
-        self.assertIn('name', self.config.available_features)
-
-        self.assertIn('-hello', self.getSubstitution('%{compile_flags}'))
-        self.assertNotIn('-world', self.getSubstitution('%{compile_flags}'))
-
-        self.assertIn('-world', self.getSubstitution('%{link_flags}'))
-        self.assertNotIn('-hello', self.getSubstitution('%{link_flags}'))
+                              actions=lambda cfg: (
+                                self.assertIs(self.config, cfg),
+                                [dsl.AddCompileFlag('-std=c++03')]
+                              )[1])
+        for a in feature.getActions(self.config):
+            a.applyTo(self.config)
+        self.assertIn('-std=c++03', self.getSubstitution('%{compile_flags}'))
 
     def test_unsupported_feature(self):
         feature = dsl.Feature(name='name', when=lambda _: False)
-        self.assertFalse(feature.isSupported(self.config))
-        # Also make sure we assert if we ever try to add it to a config
-        self.assertRaises(AssertionError, lambda: feature.enableIn(self.config))
+        self.assertEqual(feature.getActions(self.config), [])
 
     def test_is_supported_gets_passed_the_config(self):
         feature = dsl.Feature(name='name', when=lambda cfg: (self.assertIs(self.config, cfg), True)[1])
-        self.assertTrue(feature.isSupported(self.config))
+        self.assertEqual(len(feature.getActions(self.config)), 1)
+
 
+def _throw():
+    raise ValueError()
 
 class TestParameter(SetupConfigs):
     """
     Tests for libcxx.test.dsl.Parameter
     """
     def test_empty_name_should_blow_up(self):
-        self.assertRaises(ValueError, lambda: dsl.Parameter(name='', choices=['c++03'], type=str, help='', feature=lambda _: None))
+        self.assertRaises(ValueError, lambda: dsl.Parameter(name='', choices=['c++03'], type=str, help='', actions=lambda _: []))
 
     def test_empty_choices_should_blow_up(self):
-        self.assertRaises(ValueError, lambda: dsl.Parameter(name='std', choices=[], type=str, help='', feature=lambda _: None))
+        self.assertRaises(ValueError, lambda: dsl.Parameter(name='std', choices=[], type=str, help='', actions=lambda _: []))
 
     def test_name_is_set_correctly(self):
-        param = dsl.Parameter(name='std', choices=['c++03'], type=str, help='', feature=lambda _: None)
+        param = dsl.Parameter(name='std', choices=['c++03'], type=str, help='', actions=lambda _: [])
         self.assertEqual(param.name, 'std')
 
     def test_no_value_provided_and_no_default_value(self):
-        param = dsl.Parameter(name='std', choices=['c++03'], type=str, help='', feature=lambda _: None)
-        self.assertRaises(ValueError, lambda: param.getFeature(self.config, self.litConfig.params))
+        param = dsl.Parameter(name='std', choices=['c++03'], type=str, help='', actions=lambda _: [])
+        self.assertRaises(ValueError, lambda: param.getActions(self.config, self.litConfig.params))
 
     def test_no_value_provided_and_default_value(self):
         param = dsl.Parameter(name='std', choices=['c++03'], type=str, help='', default='c++03',
-                              feature=lambda std: dsl.Feature(name=std))
-        param.getFeature(self.config, self.litConfig.params).enableIn(self.config)
+                              actions=lambda std: [dsl.AddFeature(std)])
+        for a in param.getActions(self.config, self.litConfig.params):
+            a.applyTo(self.config)
         self.assertIn('c++03', self.config.available_features)
 
     def test_value_provided_on_command_line_and_no_default_value(self):
         self.litConfig.params['std'] = 'c++03'
         param = dsl.Parameter(name='std', choices=['c++03'], type=str, help='',
-                              feature=lambda std: dsl.Feature(name=std))
-        param.getFeature(self.config, self.litConfig.params).enableIn(self.config)
+                              actions=lambda std: [dsl.AddFeature(std)])
+        for a in param.getActions(self.config, self.litConfig.params):
+            a.applyTo(self.config)
         self.assertIn('c++03', self.config.available_features)
 
     def test_value_provided_on_command_line_and_default_value(self):
         """The value provided on the command line should override the default value"""
         self.litConfig.params['std'] = 'c++11'
         param = dsl.Parameter(name='std', choices=['c++03', 'c++11'], type=str, default='c++03', help='',
-                              feature=lambda std: dsl.Feature(name=std))
-        param.getFeature(self.config, self.litConfig.params).enableIn(self.config)
+                              actions=lambda std: [dsl.AddFeature(std)])
+        for a in param.getActions(self.config, self.litConfig.params):
+            a.applyTo(self.config)
         self.assertIn('c++11', self.config.available_features)
         self.assertNotIn('c++03', self.config.available_features)
 
@@ -403,8 +376,9 @@ def test_value_provided_in_config_and_default_value(self):
         """The value provided in the config should override the default value"""
         self.config.std ='c++11'
         param = dsl.Parameter(name='std', choices=['c++03', 'c++11'], type=str, default='c++03', help='',
-                              feature=lambda std: dsl.Feature(name=std))
-        param.getFeature(self.config, self.litConfig.params).enableIn(self.config)
+                              actions=lambda std: [dsl.AddFeature(std)])
+        for a in param.getActions(self.config, self.litConfig.params):
+            a.applyTo(self.config)
         self.assertIn('c++11', self.config.available_features)
         self.assertNotIn('c++03', self.config.available_features)
 
@@ -413,42 +387,45 @@ def test_value_provided_in_config_and_on_command_line(self):
         self.config.std = 'c++11'
         self.litConfig.params['std'] = 'c++03'
         param = dsl.Parameter(name='std', choices=['c++03', 'c++11'], type=str, help='',
-                              feature=lambda std: dsl.Feature(name=std))
-        param.getFeature(self.config, self.litConfig.params).enableIn(self.config)
+                              actions=lambda std: [dsl.AddFeature(std)])
+        for a in param.getActions(self.config, self.litConfig.params):
+            a.applyTo(self.config)
         self.assertIn('c++03', self.config.available_features)
         self.assertNotIn('c++11', self.config.available_features)
 
-    def test_feature_is_None(self):
+    def test_no_actions(self):
         self.litConfig.params['std'] = 'c++03'
         param = dsl.Parameter(name='std', choices=['c++03'], type=str, help='',
-                              feature=lambda _: None)
-        feature = param.getFeature(self.config, self.litConfig.params)
-        self.assertIsNone(feature)
+                              actions=lambda _: [])
+        actions = param.getActions(self.config, self.litConfig.params)
+        self.assertEqual(actions, [])
 
     def test_boolean_value_parsed_from_trueish_string_parameter(self):
         self.litConfig.params['enable_exceptions'] = "True"
         param = dsl.Parameter(name='enable_exceptions', choices=[True, False], type=bool, help='',
-                              feature=lambda exceptions: None if exceptions else ValueError())
-        self.assertIsNone(param.getFeature(self.config, self.litConfig.params))
+                              actions=lambda exceptions: [] if exceptions else _throw())
+        self.assertEqual(param.getActions(self.config, self.litConfig.params), [])
 
     def test_boolean_value_from_true_boolean_parameter(self):
         self.litConfig.params['enable_exceptions'] = True
         param = dsl.Parameter(name='enable_exceptions', choices=[True, False], type=bool, help='',
-                              feature=lambda exceptions: None if exceptions else ValueError())
-        self.assertIsNone(param.getFeature(self.config, self.litConfig.params))
+                              actions=lambda exceptions: [] if exceptions else _throw())
+        self.assertEqual(param.getActions(self.config, self.litConfig.params), [])
 
     def test_boolean_value_parsed_from_falseish_string_parameter(self):
         self.litConfig.params['enable_exceptions'] = "False"
         param = dsl.Parameter(name='enable_exceptions', choices=[True, False], type=bool, help='',
-                              feature=lambda exceptions: None if exceptions else dsl.Feature(name="-fno-exceptions"))
-        param.getFeature(self.config, self.litConfig.params).enableIn(self.config)
+                              actions=lambda exceptions: [] if exceptions else [dsl.AddFeature("-fno-exceptions")])
+        for a in param.getActions(self.config, self.litConfig.params):
+            a.applyTo(self.config)
         self.assertIn('-fno-exceptions', self.config.available_features)
 
     def test_boolean_value_from_false_boolean_parameter(self):
         self.litConfig.params['enable_exceptions'] = False
         param = dsl.Parameter(name='enable_exceptions', choices=[True, False], type=bool, help='',
-                              feature=lambda exceptions: None if exceptions else dsl.Feature(name="-fno-exceptions"))
-        param.getFeature(self.config, self.litConfig.params).enableIn(self.config)
+                              actions=lambda exceptions: [] if exceptions else [dsl.AddFeature("-fno-exceptions")])
+        for a in param.getActions(self.config, self.litConfig.params):
+            a.applyTo(self.config)
         self.assertIn('-fno-exceptions', self.config.available_features)
 
 

diff  --git a/libcxx/utils/libcxx/test/dsl.py b/libcxx/utils/libcxx/test/dsl.py
index 6a02ae634907..14d3a0eff3c4 100644
--- a/libcxx/utils/libcxx/test/dsl.py
+++ b/libcxx/utils/libcxx/test/dsl.py
@@ -217,6 +217,124 @@ def featureTestMacros(config, flags=''):
   return {m: int(v.rstrip('LlUu')) for (m, v) in allMacros.items() if m.startswith('__cpp_')}
 
 
+def _addToSubstitution(substitutions, key, value):
+  return [(k, v + ' ' + value) if k == key else (k, v) for (k, v) in substitutions]
+
+
+class ConfigAction(object):
+  """
+  This class represents an action that can be performed on a Lit TestingConfig
+  object.
+
+  Examples of such actions are adding or modifying substitutions, Lit features,
+  etc. This class only provides the interface of such actions, and it is meant
+  to be subclassed appropriately to create new actions.
+  """
+  def applyTo(self, config):
+    """
+    Applies the action to the given configuration.
+
+    This should modify the configuration object in place, and return nothing.
+
+    If applying the action to the configuration would yield an invalid
+    configuration, and it is possible to diagnose it here, this method
+    should produce an error. For example, it should be an error to modify
+    a substitution in a way that we know for sure is invalid (e.g. adding
+    a compiler flag when we know the compiler doesn't support it). Failure
+    to do so early may lead to 
diff icult-to-diagnose issues down the road.
+    """
+    pass
+
+  def pretty(self, config, litParams):
+    """
+    Returns a short and human-readable string describing what this action does.
+
+    This is used for logging purposes when running the test suite, so it should
+    be kept concise.
+    """
+    pass
+
+
+class AddFeature(ConfigAction):
+  """
+  This action defines the given Lit feature when running the test suite.
+
+  The name of the feature can be a string or a callable, in which case it is
+  called with the configuration to produce the feature name (as a string).
+  """
+  def __init__(self, name):
+    self._name = name
+
+  def _getName(self, config):
+    name = self._name(config) if callable(self._name) else self._name
+    if not isinstance(name, str):
+      raise ValueError("Lit feature did not resolve to a string (got {})".format(name))
+    return name
+
+  def applyTo(self, config):
+    config.available_features.add(self._getName(config))
+
+  def pretty(self, config, litParams):
+    return 'add Lit feature {}'.format(self._getName(config))
+
+
+class AddFlag(ConfigAction):
+  """
+  This action adds the given flag to the %{flags} substitution.
+
+  The flag can be a string or a callable, in which case it is called with the
+  configuration to produce the actual flag (as a string).
+  """
+  def __init__(self, flag):
+    self._getFlag = lambda config: flag(config) if callable(flag) else flag
+
+  def applyTo(self, config):
+    flag = self._getFlag(config)
+    assert hasCompileFlag(config, flag), "Trying to enable flag {}, which is not supported".format(flag)
+    config.substitutions = _addToSubstitution(config.substitutions, '%{flags}', flag)
+
+  def pretty(self, config, litParams):
+    return 'add {} to %{{flags}}'.format(self._getFlag(config))
+
+
+class AddCompileFlag(ConfigAction):
+  """
+  This action adds the given flag to the %{compile_flags} substitution.
+
+  The flag can be a string or a callable, in which case it is called with the
+  configuration to produce the actual flag (as a string).
+  """
+  def __init__(self, flag):
+    self._getFlag = lambda config: flag(config) if callable(flag) else flag
+
+  def applyTo(self, config):
+    flag = self._getFlag(config)
+    assert hasCompileFlag(config, flag), "Trying to enable compile flag {}, which is not supported".format(flag)
+    config.substitutions = _addToSubstitution(config.substitutions, '%{compile_flags}', flag)
+
+  def pretty(self, config, litParams):
+    return 'add {} to %{{compile_flags}}'.format(self._getFlag(config))
+
+
+class AddLinkFlag(ConfigAction):
+  """
+  This action adds the given flag to the %{link_flags} substitution.
+
+  The flag can be a string or a callable, in which case it is called with the
+  configuration to produce the actual flag (as a string).
+  """
+  def __init__(self, flag):
+    self._getFlag = lambda config: flag(config) if callable(flag) else flag
+
+  def applyTo(self, config):
+    flag = self._getFlag(config)
+    assert hasCompileFlag(config, flag), "Trying to enable link flag {}, which is not supported".format(flag)
+    config.substitutions = _addToSubstitution(config.substitutions, '%{link_flags}', flag)
+
+  def pretty(self, config, litParams):
+    return 'add {} to %{{link_flags}}'.format(self._getFlag(config))
+
+
 class Feature(object):
   """
   Represents a Lit available feature that is enabled whenever it is supported.
@@ -226,7 +344,7 @@ class Feature(object):
   control whether a Feature is enabled -- it should be enabled whenever it
   is supported.
   """
-  def __init__(self, name, compileFlag=None, linkFlag=None, when=lambda _: True):
+  def __init__(self, name, actions=None, when=lambda _: True):
     """
     Create a Lit feature for consumption by a test suite.
 
@@ -236,17 +354,13 @@ def __init__(self, name, compileFlag=None, linkFlag=None, when=lambda _: True):
         callable, in which case it is passed the TestingConfig and should
         generate a string representing the name of the feature.
 
-    - compileFlag
-        An optional compile flag to add when this feature is added to a
-        TestingConfig. If provided, this must be a string representing a
-        compile flag that will be appended to the end of the %{compile_flags}
-        substitution of the TestingConfig.
-
-    - linkFlag
-        An optional link flag to add when this feature is added to a
-        TestingConfig. If provided, this must be a string representing a
-        link flag that will be appended to the end of the %{link_flags}
-        substitution of the TestingConfig.
+    - actions
+        An optional list of ConfigActions to apply when the feature is supported.
+        An AddFeature action is always created regardless of any actions supplied
+        here -- these actions are meant to perform more than setting a corresponding
+        Lit feature (e.g. adding compiler flags). If 'actions' is a callable, it
+        is called with the current configuration object to generate the actual
+        list of actions.
 
     - when
         A callable that gets passed a TestingConfig and should return a
@@ -257,52 +371,35 @@ def __init__(self, name, compileFlag=None, linkFlag=None, when=lambda _: True):
         supported.
     """
     self._name = name
-    self._compileFlag = compileFlag
-    self._linkFlag = linkFlag
+    self._actions = [] if actions is None else actions
     self._isSupported = when
 
-  def isSupported(self, config):
-    """
-    Return whether the feature is supported by the given TestingConfig.
-    """
-    return self._isSupported(config)
-
-  def getName(self, config):
-    """
-    Return the name of the feature.
-
-    It is an error to call `f.getName(cfg)` if the feature `f` is not supported.
-    """
-    assert self.isSupported(config), \
-      "Trying to get the name of a feature that is not supported in the given configuration"
+  def _getName(self, config):
     name = self._name(config) if callable(self._name) else self._name
     if not isinstance(name, str):
       raise ValueError("Feature did not resolve to a name that's a string, got {}".format(name))
     return name
 
-  def enableIn(self, config):
+  def getActions(self, config):
     """
-    Enable a feature in a TestingConfig.
+    Return the list of actions associated to this feature.
 
-    The name of the feature is added to the set of available features of
-    `config`, and any compile or link flags provided upon construction of
-    the Feature are added to the end of the corresponding substitution in
-    the config.
-
-    It is an error to call `f.enableIn(cfg)` if the feature `f` is not
-    supported in that TestingConfig (i.e. if `not f.isSupported(cfg)`).
+    If the feature is not supported, an empty list is returned.
+    If the feature is supported, an `AddFeature` action is automatically added
+    to the returned list of actions, in addition to any actions provided on
+    construction.
     """
-    assert self.isSupported(config), \
-      "Trying to enable feature {} that is not supported in the given configuration".format(self._name)
+    if not self._isSupported(config):
+      return []
+    else:
+      actions = self._actions(config) if callable(self._actions) else self._actions
+      return [AddFeature(self._getName(config))] + actions
 
-    addTo = lambda subs, sub, flag: [(s, x + ' ' + flag) if s == sub else (s, x) for (s, x) in subs]
-    if self._compileFlag:
-      compileFlag = self._compileFlag(config) if callable(self._compileFlag) else self._compileFlag
-      config.substitutions = addTo(config.substitutions, '%{compile_flags}', compileFlag)
-    if self._linkFlag:
-      linkFlag = self._linkFlag(config) if callable(self._linkFlag) else self._linkFlag
-      config.substitutions = addTo(config.substitutions, '%{link_flags}', linkFlag)
-    config.available_features.add(self.getName(config))
+  def pretty(self, config):
+    """
+    Returns the Feature's name.
+    """
+    return self._getName(config)
 
 
 def _str_to_bool(s):
@@ -339,11 +436,13 @@ class Parameter(object):
   that use that `config` object.
 
   Parameters can have multiple possible values, and they can have a default
-  value when left unspecified. They can also have a Feature associated to them,
-  in which case the Feature is added to the TestingConfig if the parameter is
-  enabled. It is an error if the Parameter is enabled but the Feature associated
-  to it is not supported, for example trying to set the compilation standard to
-  C++17 when `-std=c++17` is not supported by the compiler.
+  value when left unspecified. They can also have any number of ConfigActions
+  associated to them, in which case the actions will be performed on the
+  TestingConfig if the parameter is enabled. Depending on the actions
+  associated to a Parameter, it may be an error to enable the Parameter
+  if some actions are not supported in the given configuration. For example,
+  trying to set the compilation standard to C++23 when `-std=c++23` is not
+  supported by the compiler would be an error.
 
   One important point is that Parameters customize the behavior of the test
   suite in a bounded way, i.e. there should be a finite set of possible choices
@@ -356,7 +455,7 @@ class Parameter(object):
   compiler), it can be handled in the `lit.cfg`, but it shouldn't be
   represented with a Parameter.
   """
-  def __init__(self, name, choices, type, help, feature, default=None):
+  def __init__(self, name, choices, type, help, actions, default=None):
     """
     Create a Lit parameter to customize the behavior of a test suite.
 
@@ -380,10 +479,11 @@ def __init__(self, name, choices, type, help, feature, default=None):
         A string explaining the parameter, for documentation purposes.
         TODO: We should be able to surface those from the Lit command-line.
 
-    - feature
+    - actions
         A callable that gets passed the parsed value of the parameter (either
         the one passed on the command-line or the default one), and that returns
-        either None or a Feature.
+        a list of ConfigAction to perform given the value of the parameter.
+        All the ConfigAction must be supported in the given configuration.
 
     - default
         An optional default value to use for the parameter when no value is
@@ -403,20 +503,13 @@ def __init__(self, name, choices, type, help, feature, default=None):
     self._parse = lambda x: (_str_to_bool(x) if type is bool and isinstance(x, str)
                                              else type(x))
     self._help = help
-    self._feature = feature
+    self._actions = actions
     self._default = default
 
-  @property
-  def name(self):
+  def _getValue(self, config, litParams):
     """
-    Return the name of the parameter.
-
-    This is the name that can be used to set the parameter on the command-line
-    when running Lit.
+    Return the value of the parameter given the configuration objects.
     """
-    return self._name
-
-  def getFeature(self, config, litParams):
     param = getattr(config, self.name, None)
     param = litParams.get(self.name, param)
     if param is None and self._default is None:
@@ -425,4 +518,26 @@ def getFeature(self, config, litParams):
     value = self._parse(param) if param is not None else getDefault()
     if value not in self._choices:
       raise ValueError("Got value '{}' for parameter '{}', which is not in the provided set of possible choices: {}".format(value, self.name, self._choices))
-    return self._feature(value)
+    return value
+
+  @property
+  def name(self):
+    """
+    Return the name of the parameter.
+
+    This is the name that can be used to set the parameter on the command-line
+    when running Lit.
+    """
+    return self._name
+
+  def getActions(self, config, litParams):
+    """
+    Return the list of actions associated to this value of the parameter.
+    """
+    return self._actions(self._getValue(config, litParams))
+
+  def pretty(self, config, litParams):
+    """
+    Return a pretty representation of the parameter's name and value.
+    """
+    return "{}={}".format(self.name, self._getValue(config, litParams))

diff  --git a/libcxx/utils/libcxx/test/features.py b/libcxx/utils/libcxx/test/features.py
index 53509d95ae21..cacbf6dac0ea 100644
--- a/libcxx/utils/libcxx/test/features.py
+++ b/libcxx/utils/libcxx/test/features.py
@@ -14,11 +14,19 @@
 _isGCC        = lambda cfg: '__GNUC__' in compilerMacros(cfg) and '__clang__' not in compilerMacros(cfg)
 
 DEFAULT_FEATURES = [
-  Feature(name='fcoroutines-ts', compileFlag='-fcoroutines-ts',
+  Feature(name='fcoroutines-ts',
           when=lambda cfg: hasCompileFlag(cfg, '-fcoroutines-ts') and
-                           featureTestMacros(cfg, flags='-fcoroutines-ts').get('__cpp_coroutines', 0) >= 201703),
+                           featureTestMacros(cfg, flags='-fcoroutines-ts').get('__cpp_coroutines', 0) >= 201703,
+          actions=[AddCompileFlag('-fcoroutines-ts')]),
+
+  Feature(name='thread-safety',
+          when=lambda cfg: hasCompileFlag(cfg, '-Werror=thread-safety'),
+          actions=[AddCompileFlag('-Werror=thread-safety')]),
+
+  Feature(name='diagnose-if-support',
+          when=lambda cfg: hasCompileFlag(cfg, '-Wuser-defined-warnings'),
+          actions=[AddCompileFlag('-Wuser-defined-warnings')]),
 
-  Feature(name='thread-safety',                 when=lambda cfg: hasCompileFlag(cfg, '-Werror=thread-safety'), compileFlag='-Werror=thread-safety'),
   Feature(name='has-fblocks',                   when=lambda cfg: hasCompileFlag(cfg, '-fblocks')),
   Feature(name='-fsized-deallocation',          when=lambda cfg: hasCompileFlag(cfg, '-fsized-deallocation')),
   Feature(name='-faligned-allocation',          when=lambda cfg: hasCompileFlag(cfg, '-faligned-allocation')),
@@ -30,7 +38,6 @@
   Feature(name='has-fobjc-arc',                 when=lambda cfg: hasCompileFlag(cfg, '-xobjective-c++ -fobjc-arc') and
                                                                  sys.platform.lower().strip() == 'darwin'), # TODO: this doesn't handle cross-compiling to Apple platforms.
   Feature(name='objective-c++',                 when=lambda cfg: hasCompileFlag(cfg, '-xobjective-c++ -fobjc-arc')),
-  Feature(name='diagnose-if-support',           when=lambda cfg: hasCompileFlag(cfg, '-Wuser-defined-warnings'), compileFlag='-Wuser-defined-warnings'),
   Feature(name='modules-support',               when=lambda cfg: hasCompileFlag(cfg, '-fmodules')),
   Feature(name='non-lockfree-atomics',          when=lambda cfg: sourceBuilds(cfg, """
                                                                   #include <atomic>
@@ -87,9 +94,11 @@
             # FIXME: This is a hack that should be fixed using module maps.
             # If modules are enabled then we have to lift all of the definitions
             # in <__config_site> onto the command line.
-            compileFlag=lambda cfg, m=macro: '-Wno-macro-redefined -D{}'.format(m) + (
-              '={}'.format(compilerMacros(cfg)[m]) if compilerMacros(cfg)[m] else ''
-            )
+            actions=lambda cfg, m=macro: [
+              AddCompileFlag('-Wno-macro-redefined -D{}'.format(m) + (
+                '={}'.format(compilerMacros(cfg)[m]) if compilerMacros(cfg)[m] else ''
+              ))
+            ]
     )
   ]
 

diff  --git a/libcxx/utils/libcxx/test/newconfig.py b/libcxx/utils/libcxx/test/newconfig.py
index 8996484ba20b..644438724f7e 100644
--- a/libcxx/utils/libcxx/test/newconfig.py
+++ b/libcxx/utils/libcxx/test/newconfig.py
@@ -13,23 +13,25 @@ def _getSubstitution(substitution, config):
   raise ValueError('Substitution {} is not in the config.'.format(substitution))
 
 def configure(parameters, features, config, lit_config):
-  # Apply parameters to the configuration first, since parameters are things
-  # that we request explicitly and which might influence what features are
-  # implicitly made available next.
+  # Apply the actions supplied by parameters to the configuration first, since
+  # parameters are things that we request explicitly and which might influence
+  # what features are implicitly made available next.
   for param in parameters:
-    feature = param.getFeature(config, lit_config.params)
-    if feature:
-      feature.enableIn(config)
-      lit_config.note("Enabling Lit feature '{}' as a result of parameter '{}'".format(feature.getName(config), param.name))
+    actions = param.getActions(config, lit_config.params)
+    for action in actions:
+      action.applyTo(config)
+      lit_config.note("Applied '{}' as a result of parameter '{}'".format(
+        action.pretty(config, lit_config.params),
+        param.pretty(config, lit_config.params)))
 
   # Then, apply the automatically-detected features.
-  printFeatures = []
   for feature in features:
-    if feature.isSupported(config):
-      feature.enableIn(config)
-      printFeatures.append(feature.getName(config))
-  printFeatures = ["'{}'".format(f) for f in sorted(printFeatures)]
-  lit_config.note("Enabling implicitly detected Lit features {}".format(', '.join(printFeatures)))
+    actions = feature.getActions(config)
+    for action in actions:
+      action.applyTo(config)
+      lit_config.note("Applied '{}' as a result of implicitly detected feature '{}'".format(
+        action.pretty(config, lit_config.params),
+        feature.pretty(config)))
 
   # Print the basic substitutions
   for sub in ('%{cxx}', '%{flags}', '%{compile_flags}', '%{link_flags}', '%{exec}'):

diff  --git a/libcxx/utils/libcxx/test/params.py b/libcxx/utils/libcxx/test/params.py
index 94686a66b05a..8e6e4234d6ed 100644
--- a/libcxx/utils/libcxx/test/params.py
+++ b/libcxx/utils/libcxx/test/params.py
@@ -15,40 +15,54 @@
   Parameter(name='std', choices=_allStandards, type=str,
             help="The version of the standard to compile the test suite with.",
             default=lambda cfg: next(s for s in reversed(_allStandards) if hasCompileFlag(cfg, '-std='+s)),
-            feature=lambda std:
-              Feature(name=std, compileFlag='-std={}'.format(std),
-                      when=lambda cfg: hasCompileFlag(cfg, '-std={}'.format(std)))),
+            actions=lambda std: [
+              AddFeature(std),
+              AddCompileFlag('-std={}'.format(std)),
+            ]),
 
   Parameter(name='enable_exceptions', choices=[True, False], type=bool, default=True,
             help="Whether to enable exceptions when compiling the test suite.",
-            feature=lambda exceptions: None if exceptions else
-              Feature(name='no-exceptions', compileFlag='-fno-exceptions')),
+            actions=lambda exceptions: [] if exceptions else [
+              AddFeature('no-exceptions'),
+              AddCompileFlag('-fno-exceptions')
+            ]),
 
   Parameter(name='enable_rtti', choices=[True, False], type=bool, default=True,
             help="Whether to enable RTTI when compiling the test suite.",
-            feature=lambda rtti: None if rtti else
-              Feature(name='no-rtti', compileFlag='-fno-rtti')),
+            actions=lambda rtti: [] if rtti else [
+              AddFeature('no-rtti'),
+              AddCompileFlag('-fno-rtti')
+            ]),
 
   Parameter(name='stdlib', choices=['libc++', 'libstdc++', 'msvc'], type=str, default='libc++',
             help="The C++ Standard Library implementation being tested.",
-            feature=lambda stdlib: Feature(name=stdlib)),
+            actions=lambda stdlib: [
+              AddFeature(stdlib)
+            ]),
 
   # Parameters to enable or disable parts of the test suite
   Parameter(name='enable_filesystem', choices=[True, False], type=bool, default=True,
             help="Whether to enable tests for the C++ <filesystem> library.",
-            feature=lambda filesystem: None if filesystem else
-              Feature(name='c++filesystem-disabled')),
+            actions=lambda filesystem: [] if filesystem else [
+              AddFeature('c++filesystem-disabled')
+            ]),
 
   Parameter(name='enable_experimental', choices=[True, False], type=bool, default=False,
-          help="Whether to enable tests for experimental C++ libraries (typically Library Fundamentals TSes).",
-          feature=lambda experimental: None if not experimental else
-            Feature(name='c++experimental', linkFlag='-lc++experimental')),
+            help="Whether to enable tests for experimental C++ libraries (typically Library Fundamentals TSes).",
+            actions=lambda experimental: [] if not experimental else [
+              AddFeature('c++experimental'),
+              AddLinkFlag('-lc++experimental')
+            ]),
 
   Parameter(name='long_tests', choices=[True, False], type=bool, default=True,
             help="Whether to enable tests that take longer to run. This can be useful when running on a very slow device.",
-            feature=lambda enabled: Feature(name='long_tests') if enabled else None),
+            actions=lambda enabled: [] if not enabled else [
+              AddFeature('long_tests')
+            ]),
 
   Parameter(name='enable_debug_tests', choices=[True, False], type=bool, default=True,
             help="Whether to enable tests that exercise the libc++ debugging mode.",
-            feature=lambda enabled: None if enabled else Feature(name='libcxx-no-debug-mode')),
+            actions=lambda enabled: [] if enabled else [
+              AddFeature('libcxx-no-debug-mode')
+            ]),
 ]


        


More information about the libcxx-commits mailing list