Repository: ESMCI/cime

Issue:
New Component Specific User Mods
 We need the ability for a component to set user mods for other components in regular expression matching capability. As an example CAM needs to make sure that all orbital settings are set to specific values for ALL aquaplanet compsets. The current usermods attachment to a compset would mean that effectively 60 new compsets need to be added to ensure this dependence with the current scheme. This PR would eliminate the need to introduce many compsets via the introduction of the following simple new entry in CAM's config_component.xml.

 ```
<entry id="CAM_USER_MODS">
    <type>char</type>
    <valid_values></valid_values>
    <values>
      <value compset="DOCN%SAQP">$SRCROOT/components/cam/cime_config/user_mods/foo</value>
      <value compset="DOCN%PAQP">$SRCROOT/components/cam/cime_config/user_mods/foo</value>
    </values>
  </entry>
```



Discussion:

Resolving patch:
diff --git a/scripts/lib/CIME/XML/compsets.py b/scripts/lib/CIME/XML/compsets.py
--- a/scripts/lib/CIME/XML/compsets.py
+++ b/scripts/lib/CIME/XML/compsets.py
@@ -37,14 +37,9 @@ def get_compset_match(self, name):
                 science_support_nodes = self.get_nodes("science_support", root=node)
                 for node in science_support_nodes:
                     science_support.append(node.get("grid"))
-                user_mods_node = self.get_optional_node("user_mods", root=node)
-                if user_mods_node is not None:
-                    user_mods = user_mods_node.text
-                else:
-                    user_mods = None
                 logger.debug("Found node match with alias: {} and lname: {}".format(alias, lname))
-                return (lname, alias, science_support, user_mods)
-        return (None, None, [False], None)
+                return (lname, alias, science_support)
+        return (None, None, [False])
 
     def get_compset_var_settings(self, compset, grid):
         '''
diff --git a/scripts/lib/CIME/case.py b/scripts/lib/CIME/case.py
--- a/scripts/lib/CIME/case.py
+++ b/scripts/lib/CIME/case.py
@@ -72,6 +72,7 @@ def __init__(self, case_root=None, read_only=True):
         self._env_files_that_need_rewrite = set()
         self._read_only_mode = True
         self._force_read_only = read_only
+        self._principle_component = None
 
         self._env_entryid_files = []
         self._env_generic_files = []
@@ -98,7 +99,6 @@ def __init__(self, case_root=None, read_only=True):
         self._is_env_loaded = False
         # these are user_mods as defined in the compset
         # Command Line user_mods are handled seperately
-        self._user_mods = None
         self.thread_count = None
         self.total_tasks = None
         self.tasks_per_node = None
@@ -430,7 +430,7 @@ def _set_compset_and_pesfile(self, compset_name, files, user_compset=False, pesf
             # If the file exists, read it and see if there is a match for the compset alias or longname
             if (os.path.isfile(compsets_filename)):
                 compsets = Compsets(compsets_filename)
-                match, compset_alias, science_support, self._user_mods = compsets.get_compset_match(name=compset_name)
+                match, compset_alias, science_support = compsets.get_compset_match(name=compset_name)
                 if match is not None:
                     if pesfile is None:
                         self._pesfile = files.get_value("PES_SPEC_FILE"     , {"component":component})
@@ -443,13 +443,11 @@ def _set_compset_and_pesfile(self, compset_name, files, user_compset=False, pesf
                     user_mods_dir     = files.get_value("USER_MODS_DIR"     , {"component":component}, resolved=False)
                     self.set_lookup_value("COMPSETS_SPEC_FILE" ,
                                    files.get_value("COMPSETS_SPEC_FILE", {"component":component}, resolved=False))
+                    self._principle_component = component.upper()
                     self.set_lookup_value("TESTS_SPEC_FILE"    , tests_filename)
                     self.set_lookup_value("TESTS_MODS_DIR"     , tests_mods_dir)
                     self.set_lookup_value("USER_MODS_DIR"      , user_mods_dir)
-                    compset_info = "Compset longname is %s"%(match)
-                    if self._user_mods is not None:
-                        compset_info += " with user_mods directory %s"%(self._user_mods)
-                    logger.info(compset_info)
+                    logger.info("Compset longname is %s"%(match))
                     logger.info("Compset specification file is %s" %(compsets_filename))
                     logger.info("Pes     specification file is %s" %(pesfile))
                     return compset_alias, science_support
@@ -557,6 +555,7 @@ def _get_component_config_data(self, files):
             if result is not None:
                 del self.lookups[key]
 
+
     def _setup_mach_pes(self, pecount, ninst, machine_name, mpilib):
         #--------------------------------------------
         # pe layout
@@ -853,7 +852,8 @@ def get_compset_var_settings(self):
         for name, value in matches:
             if len(value) > 0:
                 logger.debug("Compset specific settings: name is %s and value is %s"%(name,value))
-                self.set_value(name, value)
+                self.set_lookup_value(name, value)
+
 
     def set_initial_test_values(self):
         testobj = self.get_env("test")
@@ -973,11 +973,13 @@ def create_caseroot(self, clone=False):
         for newdir in newdirs:
             os.makedirs(newdir)
 
+        user_mods = self.get_value("%s_USER_MODS"%(self._principle_component))
+
         # Open a new README.case file in $self._caseroot
         append_status(" ".join(sys.argv), "README.case", caseroot=self._caseroot)
         compset_info = "Compset longname is %s"%(self.get_value("COMPSET"))
-        if self._user_mods is not None:
-            compset_info += " with user_mods directory %s"%(self._user_mods)
+        if user_mods is not None:
+            compset_info += " with user_mods directory %s"%(user_mods)
         append_status(compset_info,
                       "README.case", caseroot=self._caseroot)
         append_status("Compset specification file is %s" %
@@ -992,8 +994,8 @@ def create_caseroot(self, clone=False):
             comp_grid = "%s_GRID"%component_class
             append_status("%s is %s"%(comp_grid,self.get_value(comp_grid)),
                           "README.case", caseroot=self._caseroot)
-        if self._user_mods is not None:
-            note = "This compset includes user_mods %s"%self._user_mods
+        if user_mods is not None:
+            note = "This compset includes user_mods %s"%user_mods
             append_status(note, "README.case", caseroot=self._caseroot)
             logger.info(note)
         if not clone:
@@ -1006,14 +1008,11 @@ def apply_user_mods(self, user_mods_dir=None):
         or they can be in the compset definition, or both.
         """
 
-        if self._user_mods is None:
-            compset_user_mods_resolved = None
-        else:
-            compset_user_mods_resolved = self.get_resolved_value(self._user_mods)
+        component_user_mods = self.get_value("%s_USER_MODS"%(self._principle_component))
 
         # This looping order will lead to the specified user_mods_dir taking
         # precedence over self._user_mods, if there are any conflicts.
-        for user_mods in (compset_user_mods_resolved, user_mods_dir):
+        for user_mods in (component_user_mods, user_mods_dir):
             if user_mods is not None:
                 if os.path.isabs(user_mods):
                     user_mods_path = user_mods


Test patch: