From bb7175cdec2fbf64cbade33c4ebda5c973dcb0ae Mon Sep 17 00:00:00 2001 From: Jian Sun Date: Wed, 22 Mar 2023 23:39:30 -0600 Subject: [PATCH 001/390] Add Jim's changes for Gust's GPU options, based on https://github.com/jedwards4b/cime/compare/28b7431..3f4b1ab modified: CIME/Tools/Makefile modified: CIME/XML/env_batch.py modified: CIME/XML/env_mach_specific.py modified: CIME/build.py modified: CIME/case/case.py modified: CIME/data/config/xml_schemas/config_machines.xsd modified: CIME/data/config/xml_schemas/env_mach_specific.xsd modified: CIME/scripts/create_newcase.py modified: CIME/test_scheduler.py modified: CIME/tests/test_unit_case.py --- CIME/Tools/Makefile | 3 + CIME/XML/env_batch.py | 3 + CIME/XML/env_mach_specific.py | 3 +- CIME/build.py | 14 ++++ CIME/case/case.py | 76 ++++++++++--------- .../config/xml_schemas/config_machines.xsd | 4 + .../config/xml_schemas/env_mach_specific.xsd | 4 + CIME/scripts/create_newcase.py | 18 +++++ CIME/test_scheduler.py | 9 ++- CIME/tests/test_unit_case.py | 4 + 10 files changed, 101 insertions(+), 37 deletions(-) diff --git a/CIME/Tools/Makefile b/CIME/Tools/Makefile index 8cf5ba1104e..d01eeebd0d6 100644 --- a/CIME/Tools/Makefile +++ b/CIME/Tools/Makefile @@ -613,6 +613,9 @@ endif # Remove arch flag if it exists F90_LDFLAGS := $(filter-out -arch%,$(LDFLAGS)) +ifdef GPUFLAGS + F90_LDFLAGS += $(GPUFLAGS) +endif # Machine stuff to appear last on the link step ifndef MLIBS diff --git a/CIME/XML/env_batch.py b/CIME/XML/env_batch.py index 21fba92ad90..5a960119cbe 100644 --- a/CIME/XML/env_batch.py +++ b/CIME/XML/env_batch.py @@ -620,6 +620,9 @@ def _process_args(self, case, submit_arg_nodes, job): if name: if "$" in name: rflag = self._resolve_argument(case, flag, name, job) + # This is to prevent -gpu_type=none in qsub args + if rflag.endswith("=none"): + continue if len(rflag) > len(flag): submitargs += " {}".format(rflag) else: diff --git a/CIME/XML/env_mach_specific.py b/CIME/XML/env_mach_specific.py index 03e84f0faee..4652f2a7d0a 100644 --- a/CIME/XML/env_mach_specific.py +++ b/CIME/XML/env_mach_specific.py @@ -320,7 +320,8 @@ def _compute_resource_actions(self, resource_nodes, case, job=None): def _compute_actions(self, nodes, child_tag, case, job=None): result = [] # list of tuples ("name", "argument") - compiler, mpilib = case.get_value("COMPILER"), case.get_value("MPILIB") + compiler = case.get_value("COMPILER") + mpilib = case.get_value("MPILIB") for node in nodes: if self._match_attribs(self.attrib(node), case, job=job): diff --git a/CIME/build.py b/CIME/build.py index 0e232396f3a..99506ff9bf7 100644 --- a/CIME/build.py +++ b/CIME/build.py @@ -239,6 +239,20 @@ def get_standard_cmake_args(case, sharedpath): cmake_args += " -Dcompile_threaded={} ".format( stringify_bool(case.get_build_threaded()) ) + # check settings for GPU + gpu_type = case.get_value("GPU_TYPE") + gpu_offload = case.get_value("GPU_OFFLOAD") + if gpu_type != "none": + expect( + gpu_offload != "none", + "Both GPU_TYPE and GPU_OFFLOAD must be defined if either is", + ) + cmake_args += f" -DGPU_TYPE={gpu_type} -DGPU_OFFLOAD={gpu_offload}" + else: + expect( + gpu_offload == "none", + "Both GPU_TYPE and GPU_OFFLOAD must be defined if either is", + ) ocn_model = case.get_value("COMP_OCN") atm_model = case.get_value("COMP_ATM") diff --git a/CIME/case/case.py b/CIME/case/case.py index 4924baf8cda..1000f944acd 100644 --- a/CIME/case/case.py +++ b/CIME/case/case.py @@ -123,6 +123,7 @@ def __init__(self, case_root=None, read_only=True, record=False, non_local=False self._env_generic_files = [] self._files = [] self._comp_interface = None + self.gpu_enabled = None self._non_local = non_local self.read_xml() @@ -451,6 +452,12 @@ def get_values(self, item, attribute=None, resolved=True, subgroup=None): return [] def get_value(self, item, attribute=None, resolved=True, subgroup=None): + if item == "GPU_ENABLED": + if self.gpu_enabled == None: + if self.get_value("GPU_TYPE") != "none": + self.gpu_enabled = True + return "true" if self.gpu_enabled else "false" + result = None for env_file in self._files: # Wait and resolve in self rather than in env_file @@ -1262,6 +1269,8 @@ def configure( extra_machines_dir=None, case_group=None, ngpus_per_node=0, + gpu_type=None, + gpu_offload=None, ): expect( @@ -1384,14 +1393,19 @@ def configure( if not dmax: dmax = machobj.get_value(name) if dmax: + print(f"here name is {name} and dmax is {dmax}") self.set_value(name, dmax) elif name == "MAX_GPUS_PER_NODE": logger.debug( - "Variable {} not defined for machine {}".format(name, machine_name) + "Variable {} not defined for machine {} and compiler {}".format( + name, machine_name, compiler + ) ) else: logger.warning( - "Variable {} not defined for machine {}".format(name, machine_name) + "Variable {} not defined for machine {} and compiler {}".format( + name, machine_name, compiler + ) ) machdir = machobj.get_machines_dir() @@ -1509,47 +1523,37 @@ def configure( self.set_value("TEST", True) # ---------------------------------------------------------------------------------------------------------- - # Sanity check: - # 1. We assume that there is always a string "gpu" in the compiler name if we want to enable GPU - # 2. For compilers without the string "gpu" in the name: - # 2.1. the ngpus-per-node argument would not update the NGPUS_PER_NODE XML variable, as long as - # the MAX_GPUS_PER_NODE XML variable is not defined (i.e., this argument is not in effect). - # 2.2. if the MAX_GPUS_PER_NODE XML variable is defined, then the ngpus-per-node argument - # must be set to 0. Otherwise, an error will be triggered. - # 3. For compilers with the string "gpu" in the name: - # 3.1. if ngpus-per-node argument is smaller than 0, an error will be triggered. - # 3.2. if ngpus_per_node argument is larger than the value of MAX_GPUS_PER_NODE, the NGPUS_PER_NODE + # Sanity check for a GPU run: + # 1. GPU_TYPE and GPU_OFFLOAD must both be defined to use GPUS + # 2. if ngpus_per_node argument is larger than the value of MAX_GPUS_PER_NODE, the NGPUS_PER_NODE # XML variable in the env_mach_pes.xml file would be set to MAX_GPUS_PER_NODE automatically. - # 3.3. if ngpus-per-node argument is equal to 0, it will be updated to 1 automatically. + # 3. if ngpus-per-node argument is equal to 0, it will be updated to 1 automatically. # ---------------------------------------------------------------------------------------------------------- max_gpus_per_node = self.get_value("MAX_GPUS_PER_NODE") - if max_gpus_per_node: - if "gpu" in compiler: - if not ngpus_per_node: - ngpus_per_node = 1 - logger.warning( - "Setting ngpus_per_node to 1 for compiler {}".format(compiler) - ) - expect( - ngpus_per_node > 0, - " ngpus_per_node is expected > 0 for compiler {}; current value is {}".format( - compiler, ngpus_per_node - ), - ) - else: - expect( - ngpus_per_node == 0, - " ngpus_per_node is expected = 0 for compiler {}; current value is {}".format( - compiler, ngpus_per_node - ), - ) + if gpu_type: + expect( + max_gpus_per_node, + f"GPUS are not defined for machine={machine_name} and compiler={compiler}", + ) + expect( + gpu_offload, + "Both gpu-type and gpu-offload must be defined if either is defined", + ) + self.set_value("GPU_TYPE", gpu_type) + self.set_value("GPU_OFFLOAD", gpu_offload) + self.gpu_enabled = True if ngpus_per_node >= 0: self.set_value( "NGPUS_PER_NODE", - ngpus_per_node + max(1, ngpus_per_node) if ngpus_per_node <= max_gpus_per_node else max_gpus_per_node, ) + elif gpu_offload: + expect( + False, + "Both gpu-type and gpu-offload must be defined if either is defined", + ) self.initialize_derived_attributes() @@ -2354,6 +2358,8 @@ def create( extra_machines_dir=None, case_group=None, ngpus_per_node=0, + gpu_type=None, + gpu_offload=None, ): try: # Set values for env_case.xml @@ -2427,6 +2433,8 @@ def create( extra_machines_dir=extra_machines_dir, case_group=case_group, ngpus_per_node=ngpus_per_node, + gpu_type=gpu_type, + gpu_offload=gpu_offload, ) self.create_caseroot() diff --git a/CIME/data/config/xml_schemas/config_machines.xsd b/CIME/data/config/xml_schemas/config_machines.xsd index d6e3c280a93..d5cc7d5c597 100644 --- a/CIME/data/config/xml_schemas/config_machines.xsd +++ b/CIME/data/config/xml_schemas/config_machines.xsd @@ -6,6 +6,8 @@ + + @@ -249,6 +251,8 @@ + + diff --git a/CIME/data/config/xml_schemas/env_mach_specific.xsd b/CIME/data/config/xml_schemas/env_mach_specific.xsd index f86c6b9f6e1..7778635592b 100644 --- a/CIME/data/config/xml_schemas/env_mach_specific.xsd +++ b/CIME/data/config/xml_schemas/env_mach_specific.xsd @@ -9,6 +9,8 @@ + + @@ -102,6 +104,8 @@ + + diff --git a/CIME/scripts/create_newcase.py b/CIME/scripts/create_newcase.py index 3faea5d6553..ee3df32dc76 100755 --- a/CIME/scripts/create_newcase.py +++ b/CIME/scripts/create_newcase.py @@ -269,6 +269,18 @@ def parse_command_line(args, cimeroot, description): help="Specify number of GPUs used for simulation. ", ) + parser.add_argument( + "--gpu-type", + default=None, + help="Specify type of GPU hardware - currently supported are v100, a100, mi250", + ) + + parser.add_argument( + "--gpu-offload", + default=None, + help="Specify gpu offload method - currently supported are openacc, openmp, combined", + ) + args = CIME.utils.parse_args_and_handle_standard_logging_options(args, parser) if args.srcroot is not None: @@ -345,6 +357,8 @@ def parse_command_line(args, cimeroot, description): args.extra_machines_dir, args.case_group, args.ngpus_per_node, + args.gpu_type, + args.gpu_offload, ) @@ -382,6 +396,8 @@ def _main_func(description=None): extra_machines_dir, case_group, ngpus_per_node, + gpu_type, + gpu_offload, ) = parse_command_line(sys.argv, cimeroot, description) if script_root is None: @@ -447,6 +463,8 @@ def _main_func(description=None): extra_machines_dir=extra_machines_dir, case_group=case_group, ngpus_per_node=ngpus_per_node, + gpu_type=gpu_type, + gpu_offload=gpu_offload, ) # Called after create since casedir does not exist yet diff --git a/CIME/test_scheduler.py b/CIME/test_scheduler.py index a657e2a6b39..d6de0801cfa 100644 --- a/CIME/test_scheduler.py +++ b/CIME/test_scheduler.py @@ -661,8 +661,13 @@ def _create_newcase_phase(self, test): pesize = case_opt[1:] create_newcase_cmd += " --pecount {}".format(pesize) elif case_opt.startswith("G"): - ngpus_per_node = case_opt[1:] - create_newcase_cmd += " --ngpus-per-node {}".format(ngpus_per_node) + if "-" in case_opt: + ngpus_per_node, gpu_type, gpu_offload = case_opt[1:].split("-") + else: + error = "GPU test argument format is ngpus_per_node-gpu_type-gpu_offload" + self._log_output(test, error) + return False, error + create_newcase_cmd += " --ngpus-per-node {} --gpu-type {} --gpu-offload {}".format(ngpus_per_node,gpu_type,gpu_offload) elif case_opt.startswith("V"): self._cime_driver = case_opt[1:] create_newcase_cmd += " --driver {}".format(self._cime_driver) diff --git a/CIME/tests/test_unit_case.py b/CIME/tests/test_unit_case.py index ed473cea21f..dd4d18edf66 100755 --- a/CIME/tests/test_unit_case.py +++ b/CIME/tests/test_unit_case.py @@ -251,6 +251,8 @@ def test_copy( extra_machines_dir=None, case_group=None, ngpus_per_node=0, + gpu_type=None, + gpu_offload=None, ) create_caseroot.assert_called() apply_user_mods.assert_called() @@ -326,6 +328,8 @@ def test_create( extra_machines_dir=None, case_group=None, ngpus_per_node=0, + gpu_type=None, + gpu_offload=None, ) create_caseroot.assert_called() apply_user_mods.assert_called() From 6667b4061546c807e7f7bfab0c9874e0d1b2b700 Mon Sep 17 00:00:00 2001 From: Jian Sun Date: Thu, 23 Mar 2023 11:11:21 -0600 Subject: [PATCH 002/390] Change the settings for Gust GPU nodes modified: CIME/XML/env_mach_pes.py modified: CIME/case/case.py modified: CIME/config.py modified: doc/source/users_guide/cime-customize.rst --- CIME/XML/env_mach_pes.py | 6 ++++- CIME/case/case.py | 32 ++++++++++++++++++----- CIME/config.py | 2 +- doc/source/users_guide/cime-customize.rst | 2 +- 4 files changed, 32 insertions(+), 10 deletions(-) diff --git a/CIME/XML/env_mach_pes.py b/CIME/XML/env_mach_pes.py index c7635573f95..f5dfa68e889 100644 --- a/CIME/XML/env_mach_pes.py +++ b/CIME/XML/env_mach_pes.py @@ -167,11 +167,15 @@ def get_tasks_per_node(self, total_tasks, max_thread_count): "totaltasks > 0 expected, totaltasks = {}".format(total_tasks), ) if self._comp_interface == "nuopc" and self.get_value("ESMF_AWARE_THREADING"): - tasks_per_node = self.get_value("MAX_MPITASKS_PER_NODE") + if self.get_value("NGPUS_PER_NODE") > 0: + tasks_per_node = self.get_value("MAX_CPUTASKS_PER_GPU_NODE") + else: + tasks_per_node = self.get_value("MAX_MPITASKS_PER_NODE") else: tasks_per_node = min( self.get_value("MAX_TASKS_PER_NODE") // max_thread_count, self.get_value("MAX_MPITASKS_PER_NODE"), + self.get_value("MAX_CPUTASKS_PER_GPU_NODE"), total_tasks, ) return tasks_per_node if tasks_per_node > 0 else 1 diff --git a/CIME/case/case.py b/CIME/case/case.py index 1000f944acd..26eacfe2b1f 100644 --- a/CIME/case/case.py +++ b/CIME/case/case.py @@ -123,7 +123,7 @@ def __init__(self, case_root=None, read_only=True, record=False, non_local=False self._env_generic_files = [] self._files = [] self._comp_interface = None - self.gpu_enabled = None + self.gpu_enabled = False self._non_local = non_local self.read_xml() @@ -276,6 +276,9 @@ def initialize_derived_attributes(self): if max_gpus_per_node: self.ngpus_per_node = self.get_value("NGPUS_PER_NODE") + # update the maximum MPI tasks for a GPU node (could differ from a pure-CPU node) + if self.ngpus_per_node > 0: + max_mpitasks_per_node = self.get_value("MAX_CPUTASKS_PER_GPU_NODE") self.tasks_per_numa = int(math.ceil(self.tasks_per_node / 2.0)) smt_factor = max( @@ -453,7 +456,7 @@ def get_values(self, item, attribute=None, resolved=True, subgroup=None): def get_value(self, item, attribute=None, resolved=True, subgroup=None): if item == "GPU_ENABLED": - if self.gpu_enabled == None: + if not self.gpu_enabled: if self.get_value("GPU_TYPE") != "none": self.gpu_enabled = True return "true" if self.gpu_enabled else "false" @@ -1353,6 +1356,7 @@ def configure( and "MPILIB" not in x and "MAX_MPITASKS_PER_NODE" not in x and "MAX_TASKS_PER_NODE" not in x + and "MAX_CPUTASKS_PER_GPU_NODE" not in x and "MAX_GPUS_PER_NODE" not in x ] @@ -1387,6 +1391,7 @@ def configure( for name in ( "MAX_TASKS_PER_NODE", "MAX_MPITASKS_PER_NODE", + "MAX_CPUTASKS_PER_GPU_NODE", "MAX_GPUS_PER_NODE", ): dmax = machobj.get_value(name, {"compiler": compiler}) @@ -1395,6 +1400,12 @@ def configure( if dmax: print(f"here name is {name} and dmax is {dmax}") self.set_value(name, dmax) + elif name == "MAX_CPUTASKS_PER_GPU_NODE": + logger.debug( + "Variable {} not defined for machine {} and compiler {}".format( + name, machine_name, compiler + ) + ) elif name == "MAX_GPUS_PER_NODE": logger.debug( "Variable {} not defined for machine {} and compiler {}".format( @@ -2057,11 +2068,18 @@ def get_mpirun_cmd(self, job=None, allow_unresolved_envvars=True, overrides=None ngpus_per_node = self.get_value("NGPUS_PER_NODE") if ngpus_per_node and ngpus_per_node > 0 and config.gpus_use_set_device_rank: - # 1. this setting is tested on Casper only and may not work on other machines - # 2. need to be revisited in the future for a more adaptable implementation - rundir = self.get_value("RUNDIR") - output_name = rundir + "/set_device_rank.sh" - mpi_arg_string = mpi_arg_string + " " + output_name + " " + if self.get_value("MACH") == "Gust": + mpi_arg_string = mpi_arg_string + " get_local_rank " + else: + # this wrapper script only works with OpenMPI library + # has been tested on Casper + expect( + self.get_value("MPILIB") == "openmpi", + "The wrapper script only works with OpenMPI library; {} is currently used".format(self.get_value("MPILIB")), + ) + rundir = self.get_value("RUNDIR") + output_name = rundir + "/set_device_rank.sh" + mpi_arg_string = mpi_arg_string + " " + output_name + " " return self.get_resolved_value( "{} {} {} {}".format( diff --git a/CIME/config.py b/CIME/config.py index 8491b2f3f2e..9666439cb2e 100644 --- a/CIME/config.py +++ b/CIME/config.py @@ -180,7 +180,7 @@ def __init__(self): self._set_attribute( "gpus_use_set_device_rank", True, - desc="If set to `True` and NGPUS_PER_NODE > 0 then `$RUNDIR/set_device_rank.sh` is appended when the MPI run command is generated.", + desc="If set to `True` and NGPUS_PER_NODE > 0 then `$RUNDIR/set_device_rank.sh` or `get_local_rank` (a global script on Gust) is appended when the MPI run command is generated.", ) self._set_attribute( "test_custom_project_machine", diff --git a/doc/source/users_guide/cime-customize.rst b/doc/source/users_guide/cime-customize.rst index ed90e21472a..2c65d1ab954 100644 --- a/doc/source/users_guide/cime-customize.rst +++ b/doc/source/users_guide/cime-customize.rst @@ -44,7 +44,7 @@ default_short_term_archiving True bool If set to `Tr driver_choices ('mct', 'nuopc') tuple Sets the available driver choices for the model. driver_default nuopc str Sets the default driver for the model. enable_smp True bool If set to `True` then `SMP=` is added to model compile command. -gpus_use_set_device_rank True bool If set to `True` and NGPUS_PER_NODE > 0 then `$RUNDIR/set_device_rank.sh` is appended when the MPI run command is generated. +gpus_use_set_device_rank True bool If set to `True` and NGPUS_PER_NODE > 0 then `$RUNDIR/set_device_rank.sh` or `get_local_rank` (a global script on Gust) is appended when the MPI run command is generated. make_case_run_batch_script False bool If set to `True` and case is not a test then `case.run.sh` is created in case directory from `$MACHDIR/template.case.run.sh`. mct_path {srcroot}/libraries/mct str Sets the path to the mct library. serialize_sharedlib_builds True bool If set to `True` then the TestScheduler will use `proc_pool + 1` processors to build shared libraries otherwise a single processor is used. From e0d625ef3e404a77a49c741072bc7d9fdf8bb3ef Mon Sep 17 00:00:00 2001 From: Jian Sun Date: Thu, 23 Mar 2023 22:16:25 -0600 Subject: [PATCH 003/390] Add missing definition of max_cputasks_per_gpu_node modified: CIME/data/config/xml_schemas/config_machines.xsd --- CIME/data/config/xml_schemas/config_machines.xsd | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CIME/data/config/xml_schemas/config_machines.xsd b/CIME/data/config/xml_schemas/config_machines.xsd index d5cc7d5c597..53bd359a503 100644 --- a/CIME/data/config/xml_schemas/config_machines.xsd +++ b/CIME/data/config/xml_schemas/config_machines.xsd @@ -58,6 +58,7 @@ + @@ -168,6 +169,9 @@ + + - + + + + + @@ -50,6 +50,7 @@ + diff --git a/CIME/hist_utils.py b/CIME/hist_utils.py index 32b22abbdbc..83f738dda95 100644 --- a/CIME/hist_utils.py +++ b/CIME/hist_utils.py @@ -71,6 +71,8 @@ def copy_histfiles(case, suffix, match_suffix=None): comments = "Copying hist files to suffix '{}'\n".format(suffix) num_copied = 0 for model in _iter_model_file_substrs(case): + if case.get_value("TEST") and archive.exclude_testing(model): + continue comments += " Copying hist files for model '{}'\n".format(model) test_hists = archive.get_latest_hist_files( casename, model, rundir, suffix=match_suffix, ref_case=ref_case diff --git a/CIME/tests/test_unit_hist_utils.py b/CIME/tests/test_unit_hist_utils.py new file mode 100644 index 00000000000..1412ba3c02e --- /dev/null +++ b/CIME/tests/test_unit_hist_utils.py @@ -0,0 +1,66 @@ +import io +import unittest +from unittest import mock + +from CIME.hist_utils import copy_histfiles +from CIME.XML.archive import Archive + + +class TestHistUtils(unittest.TestCase): + @mock.patch("CIME.hist_utils.safe_copy") + def test_copy_histfiles_exclude(self, safe_copy): + case = mock.MagicMock() + + case.get_env.return_value.get_latest_hist_files.side_effect = [ + ["/tmp/testing.cpl.hi.nc"], + ["/tmp/testing.atm.hi.nc"] + ] + + case.get_env.return_value.exclude_testing.side_effect = [True, False] + + case.get_value.side_effect = [ + "/tmp", # RUNDIR + None, # RUN_REFCASE + "testing", # CASE + True, # TEST + True # TEST + ] + + case.get_compset_components.return_value = ["atm"] + + test_files = [ + "testing.cpl.hi.nc", + ] + + with mock.patch("os.listdir", return_value=test_files): + comments, num_copied = copy_histfiles(case, "base") + + assert num_copied == 1 + + @mock.patch("CIME.hist_utils.safe_copy") + def test_copy_histfiles(self, safe_copy): + case = mock.MagicMock() + + case.get_env.return_value.get_latest_hist_files.return_value = [ + "/tmp/testing.cpl.hi.nc", + ] + + case.get_env.return_value.exclude_testing.return_value = False + + case.get_value.side_effect = [ + "/tmp", # RUNDIR + None, # RUN_REFCASE + "testing", # CASE + True, # TEST + ] + + case.get_compset_components.return_value = [] + + test_files = [ + "testing.cpl.hi.nc", + ] + + with mock.patch("os.listdir", return_value=test_files): + comments, num_copied = copy_histfiles(case, "base") + + assert num_copied == 1 diff --git a/CIME/tests/test_unit_xml_archive_base.py b/CIME/tests/test_unit_xml_archive_base.py index 1a5039c7f57..44d035a3418 100644 --- a/CIME/tests/test_unit_xml_archive_base.py +++ b/CIME/tests/test_unit_xml_archive_base.py @@ -22,6 +22,18 @@ """ +EXCLUDE_TEST_CONFIG = """ + + unique\.name\.unique.nc + + + unique\.name\.unique.nc + + + unique\.name\.unique.nc + +""" + class TestXMLArchiveBase(unittest.TestCase): @contextmanager @@ -32,6 +44,23 @@ def _setup_environment(self, test_files): yield temp_dir + def test_exclude_testing(self): + archiver = ArchiveBase() + + archiver.read_fd(io.StringIO(EXCLUDE_TEST_CONFIG)) + + # no attribute + assert not archiver.exclude_testing("eam") + + # not in config + assert not archiver.exclude_testing("mpassi") + + # set false + assert not archiver.exclude_testing("mpasso") + + # set true + assert archiver.exclude_testing("cpl") + def test_match_files(self): archiver = ArchiveBase() From e9d1b0835767b4fc9f66f81a97817409637c2233 Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Wed, 5 Jul 2023 13:37:58 -0700 Subject: [PATCH 092/390] Fixes formatting --- CIME/tests/test_unit_hist_utils.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/CIME/tests/test_unit_hist_utils.py b/CIME/tests/test_unit_hist_utils.py index 1412ba3c02e..fe6d4866c34 100644 --- a/CIME/tests/test_unit_hist_utils.py +++ b/CIME/tests/test_unit_hist_utils.py @@ -13,17 +13,17 @@ def test_copy_histfiles_exclude(self, safe_copy): case.get_env.return_value.get_latest_hist_files.side_effect = [ ["/tmp/testing.cpl.hi.nc"], - ["/tmp/testing.atm.hi.nc"] + ["/tmp/testing.atm.hi.nc"], ] case.get_env.return_value.exclude_testing.side_effect = [True, False] case.get_value.side_effect = [ - "/tmp", # RUNDIR - None, # RUN_REFCASE + "/tmp", # RUNDIR + None, # RUN_REFCASE "testing", # CASE - True, # TEST - True # TEST + True, # TEST + True, # TEST ] case.get_compset_components.return_value = ["atm"] @@ -48,10 +48,10 @@ def test_copy_histfiles(self, safe_copy): case.get_env.return_value.exclude_testing.return_value = False case.get_value.side_effect = [ - "/tmp", # RUNDIR - None, # RUN_REFCASE + "/tmp", # RUNDIR + None, # RUN_REFCASE "testing", # CASE - True, # TEST + True, # TEST ] case.get_compset_components.return_value = [] From 75c7fbe6626dd3d55d3742c572d5738719a93631 Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Wed, 5 Jul 2023 13:49:42 -0700 Subject: [PATCH 093/390] Refactors exclude_testing method --- CIME/XML/archive_base.py | 40 ++++++++++++++++++++++++---------------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/CIME/XML/archive_base.py b/CIME/XML/archive_base.py index dcb1f8b46d4..1463d1ea460 100644 --- a/CIME/XML/archive_base.py +++ b/CIME/XML/archive_base.py @@ -9,32 +9,40 @@ class ArchiveBase(GenericXML): - def get_entry(self, compname): - """ - Returns an xml node corresponding to compname in comp_archive_spec - """ - return self.scan_optional_child( - "comp_archive_spec", attributes={"compname": compname} - ) - def exclude_testing(self, compname): """ Checks if component should be excluded from testing. """ + value = self._get_attribute(compname, "exclude_testing") - entry = self.get_entry(compname) - - if entry is None: + if value is None: return False - attrs = self.attrib(entry) + return convert_to_type(value, "logical") - value = attrs.get("exclude_testing", None) + def _get_attribute(self, compname, attr_name): + attrib = self.get_entry_attributes(compname) - if not value: - return False + if attrib is None: + return None - return convert_to_type(value, "logical") + return attrib.get(attr_name, None) + + def get_entry_attributes(self, compname): + entry = self.get_entry(compname) + + if entry is None: + return None + + return self.attrib(entry) + + def get_entry(self, compname): + """ + Returns an xml node corresponding to compname in comp_archive_spec + """ + return self.scan_optional_child( + "comp_archive_spec", attributes={"compname": compname} + ) def _get_file_node_text(self, attnames, archive_entry): """ From b5b80e9b15c96891e171352d1ce67887399db522 Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Thu, 6 Jul 2023 19:38:27 -0700 Subject: [PATCH 094/390] Links CIME developer's guide in user guide --- doc/source/users_guide/testing.rst | 57 +++--------------------------- 1 file changed, 5 insertions(+), 52 deletions(-) diff --git a/doc/source/users_guide/testing.rst b/doc/source/users_guide/testing.rst index 5c0339fa38b..8ea7c29467c 100644 --- a/doc/source/users_guide/testing.rst +++ b/doc/source/users_guide/testing.rst @@ -450,56 +450,9 @@ If this test will only be run as a single test, you can now create a test name and follow the individual_ test instructions for create_test. If you want this test to be part of a suite, then it must be described in the relevant testlists_YYY.xml file. -=============================== -CIME's scripts regression tests -=============================== -.. _`CIME's scripts regression tests`: +====================== +CIME Developer's guide +====================== +.. _`CIME Developer's guide`: -**$CIMEROOT/scripts/lib/CIME/tests/scripts_regression_tests.py** is the suite of internal tests we run -for the stand-alone CIME testing. With no arguments, it will run the full suite. You can limit testing to a specific -test class or even a specific test within a test class. - -**CCSM_CPRNC should be defined in config_machines.xml for your system prior to running scripts_regression_tests.py** - -Run full suite:: - - python scripts/lib/CIME/tests/scripts_regression_tests.py - -Run a test class:: - - python scripts/lib/CIME/tests/scripts_regression_tests.py CIME.tests.test_unit_case - -Run a specific test:: - - python scripts/lib/CIME/tests/scripts_regression_tests.py CIME.tests.test_unit_case.TestCaseSubmit.test_check_case - -If a test fails, the unittest module that drives scripts_regression_tests wil note the failure, but -won't print the output of the test until testing has completed. When there are failures for a -test, the case directories for that test will not be cleaned up so that the user can do a post-mortem -analysis. The user will be notified of the specific directories that will be left for them to -examine. - -The test suite can also be ran with `pytest` and `pytest-cov`. After the test suite is done running -a coverage report will be presented. - -Install dependencies:: - - python -m pip install pytest pytest-cov - -Run full suite:: - - pytest -vvv - -Run just unit tests:: - - pytest -vvv scripts/lib/CIME/tests/test_unit* - -Run a test class:: - - pytest -vvv scripts/lib/CIME/tests/test_unit_case.py - -Run a specific test:: - - pytest -vvv scripts/lib/CIME/tests/test_unit_case.py::TestCaseSubmit::test_check_case - -More description can be found in https://github.com/ESCOMP/ctsm/wiki/System-Testing-Guide +The CIME Developer's guide can be found on the project's GitHub `wiki `_. From e288589bd622c95f3e4a33bfb953456a3e87358c Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Thu, 6 Jul 2023 19:42:06 -0700 Subject: [PATCH 095/390] Fixes creating directory for input data --- docker/entrypoint.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index 0743181a4db..c89d6a9d375 100755 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -106,6 +106,8 @@ function init_e3sm() { update_cime "${install_path}/cime" + mkdir -p /storage/inputdata + rsync -vr /cache/ /storage/inputdata/ cd "${install_path}/cime" From bc8b1a3a1daa419950e5bd7b1b04614d7732b271 Mon Sep 17 00:00:00 2001 From: Bill Sacks Date: Fri, 7 Jul 2023 15:26:46 -0600 Subject: [PATCH 096/390] Fix gen_domain installation instructions --- tools/mapping/gen_domain_files/INSTALL | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/mapping/gen_domain_files/INSTALL b/tools/mapping/gen_domain_files/INSTALL index 71d7caefcae..b9d474e0cc9 100644 --- a/tools/mapping/gen_domain_files/INSTALL +++ b/tools/mapping/gen_domain_files/INSTALL @@ -3,7 +3,7 @@ HOW TO BUILD ============ (1) $ cd src -(2) $ ../../../configure --macros-format Makefile --mpilib mpi-serial +(2) $ ../../../../CIME/scripts/configure --macros-format Makefile --mpilib mpi-serial Bash users: (3) $ (. ./.env_mach_specific.sh ; gmake) csh users: From a608d9930fbc0eda44786645bd76be6520487f3c Mon Sep 17 00:00:00 2001 From: Bill Sacks Date: Fri, 7 Jul 2023 15:27:03 -0600 Subject: [PATCH 097/390] Use standard Fortran functions for getting command-line arguments Resolves ESMCI/cime#4455 --- .../gen_domain_files/src/gen_domain.F90 | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/tools/mapping/gen_domain_files/src/gen_domain.F90 b/tools/mapping/gen_domain_files/src/gen_domain.F90 index f0b97f20e4e..b06f1e7442a 100644 --- a/tools/mapping/gen_domain_files/src/gen_domain.F90 +++ b/tools/mapping/gen_domain_files/src/gen_domain.F90 @@ -53,7 +53,7 @@ program fmain set_omask = .false. ! Make sure we have arguments - nargs = iargc() + nargs = command_argument_count() if (nargs == 0) then write(6,*)'invoke gen_domain -h for usage' stop @@ -64,47 +64,47 @@ program fmain n = 1 do while (n <= nargs) arg = ' ' - call getarg (n, arg) + call get_command_argument (n, arg) n = n + 1 select case (arg) case ('-m') ! input mapping file - call getarg (n, arg) + call get_command_argument (n, arg) n = n + 1 fmap = trim(arg) cmdline = trim(cmdline) // ' -m ' // trim(arg) case ('-o') ! output ocean grid name - call getarg (n, arg) + call get_command_argument (n, arg) n = n + 1 fn1_out = trim(arg) cmdline = trim(cmdline) // ' -o ' // trim(arg) case ('-l') ! output land grid name - call getarg (n, arg) + call get_command_argument (n, arg) n = n + 1 fn2_out = trim(arg) cmdline = trim(cmdline) // ' -l ' // trim(arg) case ('-p') ! set pole on this grid [0,1,2] - call getarg (n, arg) + call get_command_argument (n, arg) n = n + 1 set_fv_pole_yc = ichar(trim(arg))-48 write(6,*)'set_fv_pole_yc is ',set_fv_pole_yc case ('--fminval') ! set fminval (min allowable land fraction) - call getarg (n, arg) + call get_command_argument (n, arg) n = n + 1 read(arg,*) fminval case ('--fmaxval') ! set fminval (min allowable land fraction) - call getarg (n, arg) + call get_command_argument (n, arg) n = n + 1 read(arg,*) fmaxval case ('-c') ! user comment - call getarg (n, arg) + call get_command_argument (n, arg) n = n + 1 usercomment = trim(arg) case ('--set-omask') From 2878fbc62f2039a3532fe1a75e5526987369ce4a Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Thu, 13 Jul 2023 15:36:41 -0700 Subject: [PATCH 098/390] Fixes docs workflow --- .github/workflows/docs.yml | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index c7d10f3de44..cd818a8dd53 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -9,6 +9,8 @@ on: branches: - master + workflow_dispatch: + permissions: contents: read jobs: @@ -20,9 +22,8 @@ jobs: steps: - uses: actions/checkout@v3 with: - fetch-depth: 0 - lfs: true - - uses: tj-actions/changed-files@v32 + fetch-depth: 2 + - uses: tj-actions/changed-files@v37 id: changed-check with: files: doc/** @@ -31,9 +32,7 @@ jobs: contents: write # for peaceiris/actions-gh-pages to push pull-requests: write # to comment on pull requests needs: check-changes - if: | - needs.check-changes.outputs.any_changed == 'true' && - github.event.pull_request.head.repo.full_name == github.repository + if: needs.check-changes.outputs.any_changed == 'true' name: Build and deploy documentation runs-on: ubuntu-latest steps: @@ -61,7 +60,9 @@ jobs: run: | make BUILDDIR=${PWD}/_build -C doc/ html - name: Push PR preview - if: ${{ github.event_name == 'pull_request' }} + if: | + github.event_name == 'pull_request' && + github.event.pull_request.head.repo.full_name == github.repository uses: peaceiris/actions-gh-pages@v3 with: github_token: ${{secrets.GITHUB_TOKEN}} From 585138e899d1356abd52abdd479c0c685dc4b3b3 Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Thu, 13 Jul 2023 15:41:15 -0700 Subject: [PATCH 099/390] Test commit --- doc/source/index.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/source/index.rst b/doc/source/index.rst index 89cc7155218..acaaa14f96e 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -11,6 +11,8 @@ The Common Infrastructure for Modeling the Earth (CIME - pronounced Earth system models, data and stub model components, a driver and associated tools and libraries. +Test + Table of contents ----------------- .. toctree:: From e6baa7c369d3ac8c2ea122776091cf5613ea7004 Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Thu, 13 Jul 2023 15:49:33 -0700 Subject: [PATCH 100/390] Adds path filtering --- .github/workflows/docs.yml | 4 ++++ .github/workflows/testing.yml | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index cd818a8dd53..479ed00a349 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -4,10 +4,14 @@ on: push: branches: - master + paths: + - 'doc/**' pull_request: branches: - master + paths: + - 'doc/**' workflow_dispatch: diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index b2d6c00e09e..fe526c49553 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -4,10 +4,14 @@ on: push: branches: - master + paths: + - 'CIME/**' pull_request: branches: - master + paths: + - 'CIME/**' concurrency: group: ${{ github.ref }} From a7e08ab530bc302898d8a7a839754e05d51104cb Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Thu, 13 Jul 2023 15:57:50 -0700 Subject: [PATCH 101/390] Skip testing workflow when only docs are updated --- .github/workflows/testing.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index fe526c49553..e92b0b8c337 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -4,14 +4,14 @@ on: push: branches: - master - paths: - - 'CIME/**' + paths-ignore: + - 'doc/**' pull_request: branches: - master - paths: - - 'CIME/**' + paths-ignore: + - 'doc/**' concurrency: group: ${{ github.ref }} From a3c59c824e9ad2f73f9f9433149434e4b25bf84e Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Thu, 13 Jul 2023 16:26:34 -0700 Subject: [PATCH 102/390] Removes check-changes and reorders jobs --- .github/workflows/docs.yml | 65 +++++++++++++++----------------------- 1 file changed, 26 insertions(+), 39 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 479ed00a349..5a450876543 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -18,25 +18,40 @@ on: permissions: contents: read jobs: - check-changes: - name: Check for changes to documentation + cleanup: + permissions: + contents: write # for git push + name: Cleanup branch previews runs-on: ubuntu-latest - outputs: - any_changed: ${{ steps.changed-check.outputs.any_changed }} + if: ${{ github.event_name == 'push' }} steps: - uses: actions/checkout@v3 with: - fetch-depth: 2 - - uses: tj-actions/changed-files@v37 - id: changed-check - with: - files: doc/** + ref: 'gh-pages' + fetch-depth: 0 + lfs: true + path: gh-pages + - name: Remove branch previews + run: | + for name in `ls branch/` + do + if [[ -z "$(git show-ref --quiet ${name})" ]] + then + git rm -rf branch/${name} + fi + done + - name: Commit and push local changes to gh-pages + run: | + git config user.name github-actions[bot] + git config user.email github-actions[bot]@users.noreply.github.com + git commit -m "Clean up branch previews" + git push build-and-deploy: permissions: contents: write # for peaceiris/actions-gh-pages to push pull-requests: write # to comment on pull requests - needs: check-changes - if: needs.check-changes.outputs.any_changed == 'true' + needs: cleanup + if: ${{ always() }} name: Build and deploy documentation runs-on: ubuntu-latest steps: @@ -104,31 +119,3 @@ jobs: destination_dir: './versions/master/html' user_name: 'github-actions[bot]' user_email: 'github-actions[bot]@users.noreply.github.com' - cleanup: - permissions: - contents: write # for git push - needs: build-and-deploy - name: Cleanup branch previews - runs-on: ubuntu-latest - if: ${{ github.event_name == 'push' }} - steps: - - uses: actions/checkout@v3 - with: - ref: 'gh-pages' - fetch-depth: 0 - lfs: true - - name: Remove branch previews - run: | - for name in `ls branch/` - do - if [[ -z "$(git show-ref --quiet ${name})" ]] - then - git rm -rf branch/${name} - fi - done - - name: Commit and push local changes to gh-pages - run: | - git config user.name github-actions[bot] - git config user.email github-actions[bot]@users.noreply.github.com - git commit -m "Clean up branch previews" - git push From 22a036366deeb93babb53959f5d5de2563a7b9cf Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Thu, 13 Jul 2023 16:31:12 -0700 Subject: [PATCH 103/390] Test change --- doc/source/index.rst | 2 -- 1 file changed, 2 deletions(-) diff --git a/doc/source/index.rst b/doc/source/index.rst index acaaa14f96e..89cc7155218 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -11,8 +11,6 @@ The Common Infrastructure for Modeling the Earth (CIME - pronounced Earth system models, data and stub model components, a driver and associated tools and libraries. -Test - Table of contents ----------------- .. toctree:: From baf870133eaa403f0cdf22bb6d31939e87839bfd Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Thu, 13 Jul 2023 16:36:50 -0700 Subject: [PATCH 104/390] Quick fix --- .github/workflows/docs.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 5a450876543..098cd4c8c01 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -33,6 +33,8 @@ jobs: path: gh-pages - name: Remove branch previews run: | + pushd $GITHUB_WORKSPACE/gh-pages + for name in `ls branch/` do if [[ -z "$(git show-ref --quiet ${name})" ]] @@ -40,8 +42,7 @@ jobs: git rm -rf branch/${name} fi done - - name: Commit and push local changes to gh-pages - run: | + git config user.name github-actions[bot] git config user.email github-actions[bot]@users.noreply.github.com git commit -m "Clean up branch previews" From 5a6a47bd88edfbffc12203f426843ddff43b787b Mon Sep 17 00:00:00 2001 From: Mariana Vertenstein Date: Mon, 17 Jul 2023 13:09:49 +0200 Subject: [PATCH 105/390] udpate for having WW3 define compsets for testing --- CIME/data/config/cesm/config_files.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/CIME/data/config/cesm/config_files.xml b/CIME/data/config/cesm/config_files.xml index e5e38b42b62..47266bf5605 100644 --- a/CIME/data/config/cesm/config_files.xml +++ b/CIME/data/config/cesm/config_files.xml @@ -314,6 +314,7 @@ $COMP_ROOT_DIR_OCN/cime_config/config_compsets.xml $COMP_ROOT_DIR_OCN/cime_config/config_compsets.xml $COMP_ROOT_DIR_OCN/cime_config/config_compsets.xml + $COMP_ROOT_DIR_WAV/cime_config/config_compsets.xml case_last env_case.xml From 777fc3cac517f3b0f4e8da1b5eb0c326d48b70c9 Mon Sep 17 00:00:00 2001 From: Jim Edwards Date: Mon, 17 Jul 2023 08:25:07 -0600 Subject: [PATCH 106/390] replace PROJECT arg with nothing if PROJECT not required --- CIME/XML/env_batch.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/CIME/XML/env_batch.py b/CIME/XML/env_batch.py index f12db7e2f0f..507f04c247b 100644 --- a/CIME/XML/env_batch.py +++ b/CIME/XML/env_batch.py @@ -607,7 +607,6 @@ def _process_args(self, case, submit_arg_nodes, job, resolve=True): flag, name = self._get_argument(case, arg) except ValueError: continue - if self._batchtype == "cobalt" and job == "case.st_archive": if flag == "-n": name = "task_count" @@ -964,8 +963,19 @@ def _submit_single_job( return - submitargs = case.get_value("BATCH_COMMAND_FLAGS", subgroup=job) + submitargs = case.get_value("BATCH_COMMAND_FLAGS", subgroup=job, resolved=False) + + project = case.get_value("PROJECT", subgroup=job) + if not project: + # If there is no project then we need to remove the project flag + # slurm defines --account only on machines that require it, so this strip isn't required + if batch_system == "pbs" or batch_system=="cobalt": + submitargs = submitargs.replace("-A","") + elif batch_system == "lsf": + submitargs = submitargs.replace("-P","") + + if dep_jobs is not None and len(dep_jobs) > 0: logger.debug("dependencies: {}".format(dep_jobs)) if allow_fail: @@ -1023,7 +1033,8 @@ def _submit_single_job( if mail_type: mail_type = mail_type.split(",") # pylint: disable=no-member - + + if mail_type: mail_type_flag = self.get_value("batch_mail_type_flag", subgroup=None) if mail_type_flag is not None: From 3fe2e88096420dd869585e8b488600d80fa1d318 Mon Sep 17 00:00:00 2001 From: Jim Edwards Date: Mon, 17 Jul 2023 08:53:26 -0600 Subject: [PATCH 107/390] apply black --- CIME/XML/env_batch.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/CIME/XML/env_batch.py b/CIME/XML/env_batch.py index 507f04c247b..93545a146a0 100644 --- a/CIME/XML/env_batch.py +++ b/CIME/XML/env_batch.py @@ -970,12 +970,11 @@ def _submit_single_job( if not project: # If there is no project then we need to remove the project flag # slurm defines --account only on machines that require it, so this strip isn't required - if batch_system == "pbs" or batch_system=="cobalt": - submitargs = submitargs.replace("-A","") + if batch_system == "pbs" or batch_system == "cobalt": + submitargs = submitargs.replace("-A", "") elif batch_system == "lsf": - submitargs = submitargs.replace("-P","") - - + submitargs = submitargs.replace("-P", "") + if dep_jobs is not None and len(dep_jobs) > 0: logger.debug("dependencies: {}".format(dep_jobs)) if allow_fail: @@ -1033,8 +1032,7 @@ def _submit_single_job( if mail_type: mail_type = mail_type.split(",") # pylint: disable=no-member - - + if mail_type: mail_type_flag = self.get_value("batch_mail_type_flag", subgroup=None) if mail_type_flag is not None: From 4167d9a6342b415ac1e73f114189cabf325ddc8e Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Wed, 19 Jul 2023 08:42:52 -0700 Subject: [PATCH 108/390] Adds missing conda packages --- docker/Dockerfile | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index b2499b47ce5..7b4e2260cea 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -44,6 +44,8 @@ RUN mamba install --yes -c conda-forge \ rm -rf /opt/conda/pkgs/* # Install version locked packages +# gcc, gxx, gfortran provide symlinks for x86_64-conda-linux-gnu-* +# ar and ranlib are not symlinked RUN mamba install --yes -c conda-forge \ libnetcdf=${LIBNETCDF_VERSION}=*openmpi* \ netcdf-fortran=${NETCDF_FORTRAN_VERSION}=*openmpi* \ @@ -51,7 +53,10 @@ RUN mamba install --yes -c conda-forge \ gcc_linux-64=${GCC_VERSION} \ gxx_linux-64=${GCC_VERSION} \ openmpi-mpifort \ - gfortran_linux-64=${GCC_VERSION} && \ + gfortran_linux-64=${GCC_VERSION} \ + gcc \ + gxx \ + gfortran && \ rm -rf /opt/conda/pkgs/* && \ ln -sf /opt/conda/bin/x86_64-conda-linux-gnu-ar /opt/conda/bin/ar && \ ln -sf /opt/conda/bin/x86_64-conda-linux-gnu-ranlib /opt/conda/bin/ranlib From 283988c7e67dee25c527d21960486450cdaead15 Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Wed, 19 Jul 2023 08:48:44 -0700 Subject: [PATCH 109/390] Removes duplicates from gnu.cmake and path specific set's there are now correct symlinks for compilers --- docker/docker.cmake | 66 +++------------------------------------------ 1 file changed, 3 insertions(+), 63 deletions(-) diff --git a/docker/docker.cmake b/docker/docker.cmake index eb95c7113ce..cf367dcfd9e 100644 --- a/docker/docker.cmake +++ b/docker/docker.cmake @@ -1,71 +1,11 @@ -set(AR "/opt/conda/bin/x86_64-conda-linux-gnu-ar") -string(APPEND CFLAGS " -mcmodel=medium") -if (compile_threaded) - string(APPEND CFLAGS " -fopenmp") -endif() -if (DEBUG) - string(APPEND CFLAGS " -g -Wall -fbacktrace -fcheck=bounds -ffpe-trap=invalid,zero,overflow") -endif() -if (NOT DEBUG) - string(APPEND CFLAGS " -O") -endif() -if (COMP_NAME STREQUAL csm_share) - string(APPEND CFLAGS " -std=c99") -endif() string(APPEND CXXFLAGS " -std=c++14") -if (compile_threaded) - string(APPEND CXXFLAGS " -fopenmp") -endif() -if (DEBUG) - string(APPEND CXXFLAGS " -g -Wall -fbacktrace") -endif() -if (NOT DEBUG) - string(APPEND CXXFLAGS " -O") -endif() -if (COMP_NAME STREQUAL cism) - string(APPEND CMAKE_OPTS " -D CISM_GNU=ON") -endif() -string(APPEND CMAKE_OPTS " -D CMAKE_AR=/opt/conda/bin/x86_64-conda-linux-gnu-ar") -string(APPEND CMAKE_OPTS " -DCMAKE_Fortran_COMPILER_RANLIB=/opt/conda/bin/x86_64-conda-linux-gnu-ranlib") -string(APPEND CMAKE_OPTS " -DCMAKE_C_COMPILER_RANLIB=/opt/conda/bin/x86_64-conda-linux-gnu-ranlib") -string(APPEND CMAKE_OPTS " -DCMAKE_CXX_COMPILER_RANLIB=/opt/conda/bin/x86_64-conda-linux-gnu-ranlib") -string(APPEND CPPDEFS " -DFORTRANUNDERSCORE -DNO_R16 -DCPRGNU") -if (DEBUG) - string(APPEND CPPDEFS " -DYAKL_DEBUG") -endif() -set(SLIBS "-L/opt/conda/lib -lnetcdf -lnetcdff") -set(CXX_LIBS "-lstdc++") -set(CXX_LINKER "FORTRAN") -string(APPEND FC_AUTO_R8 " -fdefault-real-8") -string(APPEND FFLAGS " -I/opt/conda/include -mcmodel=medium -fconvert=big-endian -ffree-line-length-none -ffixed-line-length-none") -if (compile_threaded) - string(APPEND FFLAGS " -fopenmp") -endif() -if (DEBUG) - string(APPEND FFLAGS " -g -Wall -fbacktrace -fcheck=bounds -ffpe-trap=zero,overflow") -endif() -if (NOT DEBUG) - string(APPEND FFLAGS " -O") -endif() -string(APPEND FFLAGS_NOOPT " -O0") -string(APPEND FIXEDFLAGS " -ffixed-form") -string(APPEND FREEFLAGS " -ffree-form") -set(HAS_F2008_CONTIGUOUS "FALSE") -if (compile_threaded) - string(APPEND LDFLAGS " -fopenmp") -endif() -set(SLIBS " -L/opt/conda/lib -lnetcdff -lnetcdf") +string(APPEND CXX_LIBS " -lstdc++") +string(APPEND SLIBS " -L/opt/conda/lib -lnetcdf -lnetcdff") +string(APPEND FFLAGS " -I/opt/conda/include") set(MPI_PATH "/opt/conda") -set(MPICC "/opt/conda/bin/mpicc") -set(MPICXX "/opt/conda/bin/mpicxx") -set(MPIFC "/opt/conda/bin/mpif90") set(NETCDF_C_PATH "/opt/conda") set(NETCDF_FORTRAN_PATH "/opt/conda") set(PNETCDF_PATH "/opt/conda") -set(SCC "/opt/conda/bin/x86_64-conda-linux-gnu-gcc") -set(SCXX "/opt/conda/bin/x86_64-conda-linux-gnu-g++") -set(SFC "/opt/conda/bin/x86_64-conda-linux-gnu-gfortran") -set(SUPPORTS_CXX "TRUE") if (CMAKE_Fortran_COMPILER_VERSION VERSION_GREATER_EQUAL 10) string(APPEND FFLAGS " -fallow-argument-mismatch -fallow-invalid-boz ") endif() From f2cd932ca4bea8c01abde1ca2005fd31da824974 Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Wed, 19 Jul 2023 10:02:43 -0700 Subject: [PATCH 110/390] Adds comment --- CIME/scripts/create_newcase.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CIME/scripts/create_newcase.py b/CIME/scripts/create_newcase.py index 3faea5d6553..eb82d392994 100755 --- a/CIME/scripts/create_newcase.py +++ b/CIME/scripts/create_newcase.py @@ -239,6 +239,8 @@ def parse_command_line(args, cimeroot, description): parser.add_argument( "--driver", + # use get_cime_default_driver rather than config.driver_default as it considers + # environment, user config then config.driver_default default=get_cime_default_driver(), choices=drv_choices, help=drv_help, From 9708936ef211a8fb553dd7a367cf58e489645db5 Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Wed, 19 Jul 2023 10:08:38 -0700 Subject: [PATCH 111/390] Fixes using driver_choices from model customize config --- CIME/scripts/create_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CIME/scripts/create_test.py b/CIME/scripts/create_test.py index 44ccb1687be..65fcc03b359 100755 --- a/CIME/scripts/create_test.py +++ b/CIME/scripts/create_test.py @@ -219,7 +219,7 @@ def parse_command_line(args, description): parser.add_argument( "--driver", - choices=("mct", "nuopc", "moab"), + choices=model_config.driver_choices, help="Override driver specified in tests and use this one.", ) From c71f9eaf54805e2dc983108ac53a071492f746e8 Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Wed, 19 Jul 2023 10:14:44 -0700 Subject: [PATCH 112/390] Adds --driver and deprecates --comp_interface --- CIME/scripts/query_config.py | 14 +++++++++++--- CIME/utils.py | 7 +++++++ 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/CIME/scripts/query_config.py b/CIME/scripts/query_config.py index 674713e0485..b746087911f 100755 --- a/CIME/scripts/query_config.py +++ b/CIME/scripts/query_config.py @@ -8,7 +8,7 @@ from CIME.Tools.standard_script_setup import * import re -from CIME.utils import expect +from CIME.utils import expect, get_cime_default_driver, deprecate_action from CIME.XML.files import Files from CIME.XML.component import Component from CIME.XML.compsets import Compsets @@ -314,8 +314,16 @@ def parse_command_line(args, description): parser.add_argument( "--comp_interface", - choices=supported_comp_interfaces, + choices=supported_comp_interfaces, # same as config.driver_choices default="mct", + action=deprecate_action(", use --driver argument"), + help="DEPRECATED: Use --driver argument", + ) + + parser.add_argument( + "--driver", + choices=config.driver_choices, + default=get_cime_default_driver(), help="Coupler/Driver interface", ) @@ -332,7 +340,7 @@ def parse_command_line(args, description): args.machines, args.long, args.xml, - files[args.comp_interface], + files[args.driver], ) diff --git a/CIME/utils.py b/CIME/utils.py index b79fcd5116e..78bbf324045 100644 --- a/CIME/utils.py +++ b/CIME/utils.py @@ -8,6 +8,7 @@ import importlib.util import errno, signal, warnings, filecmp import stat as statlib +from argparse import Action from contextlib import contextmanager from distutils import file_util @@ -21,6 +22,12 @@ GLOBAL = {} +def deprecate_action(message): + class ActionStoreDeprecated(Action): + def __call__(self, parser, namespace, values, option_string=None): + raise DeprecationWarning(f"{option_string} is deprecated{message}") + return ActionStoreDeprecated + def import_from_file(name, file_path): loader = importlib.machinery.SourceFileLoader(name, file_path) From 76add4d90b71877b42b9c34c69bc5bb5126cb939 Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Wed, 19 Jul 2023 11:58:06 -0700 Subject: [PATCH 113/390] Fixes black formatting --- CIME/scripts/query_config.py | 2 +- CIME/utils.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CIME/scripts/query_config.py b/CIME/scripts/query_config.py index b746087911f..88d2151d1c1 100755 --- a/CIME/scripts/query_config.py +++ b/CIME/scripts/query_config.py @@ -314,7 +314,7 @@ def parse_command_line(args, description): parser.add_argument( "--comp_interface", - choices=supported_comp_interfaces, # same as config.driver_choices + choices=supported_comp_interfaces, # same as config.driver_choices default="mct", action=deprecate_action(", use --driver argument"), help="DEPRECATED: Use --driver argument", diff --git a/CIME/utils.py b/CIME/utils.py index 78bbf324045..4f0e133ae73 100644 --- a/CIME/utils.py +++ b/CIME/utils.py @@ -26,8 +26,10 @@ def deprecate_action(message): class ActionStoreDeprecated(Action): def __call__(self, parser, namespace, values, option_string=None): raise DeprecationWarning(f"{option_string} is deprecated{message}") + return ActionStoreDeprecated + def import_from_file(name, file_path): loader = importlib.machinery.SourceFileLoader(name, file_path) From 33bfa2a8faacf22a848764532ebf25d769a19216 Mon Sep 17 00:00:00 2001 From: James Foucar Date: Mon, 24 Jul 2023 10:03:14 -0600 Subject: [PATCH 114/390] Add support for MPASO_USE_PETSC --- CIME/build.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CIME/build.py b/CIME/build.py index 675c19f002b..c99d66f0f90 100644 --- a/CIME/build.py +++ b/CIME/build.py @@ -1116,6 +1116,7 @@ def _case_build_impl( ninst_build = case.get_value("NINST_BUILD") smp_value = case.get_value("SMP_VALUE") clm_use_petsc = case.get_value("CLM_USE_PETSC") + mpaso_use_petsc = case.get_value("MPASO_USE_PETSC") cism_use_trilinos = case.get_value("CISM_USE_TRILINOS") mali_use_albany = case.get_value("MALI_USE_ALBANY") mach = case.get_value("MACH") @@ -1140,7 +1141,7 @@ def _case_build_impl( # the future there may be others -- so USE_PETSC will be true if # ANY of those are true. - use_petsc = clm_use_petsc + use_petsc = clm_use_petsc or mpaso_use_petsc case.set_value("USE_PETSC", use_petsc) # Set the overall USE_TRILINOS variable to TRUE if any of the From 226053ab5e1cd72a27e72d674e5489b57af21d07 Mon Sep 17 00:00:00 2001 From: James Foucar Date: Mon, 24 Jul 2023 11:36:15 -0600 Subject: [PATCH 115/390] Fix --- CIME/build.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CIME/build.py b/CIME/build.py index c99d66f0f90..c48db5ea24a 100644 --- a/CIME/build.py +++ b/CIME/build.py @@ -1141,7 +1141,7 @@ def _case_build_impl( # the future there may be others -- so USE_PETSC will be true if # ANY of those are true. - use_petsc = clm_use_petsc or mpaso_use_petsc + use_petsc = bool(clm_use_petsc) or bool(mpaso_use_petsc) case.set_value("USE_PETSC", use_petsc) # Set the overall USE_TRILINOS variable to TRUE if any of the From b0c93839b210ca06215bfdd4e111485b300b14d7 Mon Sep 17 00:00:00 2001 From: James Edwards Date: Mon, 24 Jul 2023 13:36:51 -0600 Subject: [PATCH 116/390] improve the account remove and add support for slurm --- CIME/XML/env_batch.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/CIME/XML/env_batch.py b/CIME/XML/env_batch.py index 93545a146a0..780166e4898 100644 --- a/CIME/XML/env_batch.py +++ b/CIME/XML/env_batch.py @@ -969,11 +969,16 @@ def _submit_single_job( if not project: # If there is no project then we need to remove the project flag - # slurm defines --account only on machines that require it, so this strip isn't required - if batch_system == "pbs" or batch_system == "cobalt": + if ( + batch_system == "pbs" + or batch_system == "cobalt" + and " -A " in submitargs + ): submitargs = submitargs.replace("-A", "") - elif batch_system == "lsf": + elif batch_system == "lsf" and " -P " in submitargs: submitargs = submitargs.replace("-P", "") + elif batch_system == "slurm" and " --account " in submitargs: + submitargs = submitargs.replace("--account", "") if dep_jobs is not None and len(dep_jobs) > 0: logger.debug("dependencies: {}".format(dep_jobs)) From fcdf8dfc5a94522639285539dfdbf29fa85eaa9e Mon Sep 17 00:00:00 2001 From: James Edwards Date: Mon, 24 Jul 2023 13:45:41 -0600 Subject: [PATCH 117/390] improve the account remove and add support for slurm --- CIME/XML/env_batch.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/CIME/XML/env_batch.py b/CIME/XML/env_batch.py index 780166e4898..9cf4136b539 100644 --- a/CIME/XML/env_batch.py +++ b/CIME/XML/env_batch.py @@ -970,10 +970,8 @@ def _submit_single_job( if not project: # If there is no project then we need to remove the project flag if ( - batch_system == "pbs" - or batch_system == "cobalt" - and " -A " in submitargs - ): + batch_system == "pbs" or batch_system == "cobalt" + ) and " -A " in submitargs: submitargs = submitargs.replace("-A", "") elif batch_system == "lsf" and " -P " in submitargs: submitargs = submitargs.replace("-P", "") From 0008b643cac581102137617fe1462701e9b4c14e Mon Sep 17 00:00:00 2001 From: Mariana Vertenstein Date: Wed, 26 Jul 2023 13:52:53 +0200 Subject: [PATCH 118/390] added ww3dev config_pes.xml search --- CIME/data/config/cesm/config_files.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/CIME/data/config/cesm/config_files.xml b/CIME/data/config/cesm/config_files.xml index 47266bf5605..a83773d2335 100644 --- a/CIME/data/config/cesm/config_files.xml +++ b/CIME/data/config/cesm/config_files.xml @@ -339,6 +339,7 @@ $COMP_ROOT_DIR_OCN/cime_config/config_pes.xml $COMP_ROOT_DIR_OCN/cime_config/config_pes.xml $COMP_ROOT_DIR_OCN/cime_config/config_pes.xml + $COMP_ROOT_DIR_WAV/cime_config/config_pes.xml case_last env_case.xml From ca2386bdad06dcff82a2301bbf9dc89133f9f170 Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Sat, 29 Jul 2023 00:39:15 -0700 Subject: [PATCH 119/390] Adds skip-submit argument to case.build --- CIME/SystemTests/system_tests_common.py | 13 +++++++++++++ CIME/Tools/case.build | 11 +++++++++++ CIME/test_status.py | 11 +++++++++++ CIME/tests/test_unit_test_status.py | 14 ++++++++++++++ 4 files changed, 49 insertions(+) diff --git a/CIME/SystemTests/system_tests_common.py b/CIME/SystemTests/system_tests_common.py index ea757d1220c..e977e6f08e9 100644 --- a/CIME/SystemTests/system_tests_common.py +++ b/CIME/SystemTests/system_tests_common.py @@ -154,6 +154,7 @@ def build( ninja=False, dry_run=False, separate_builds=False, + skip_submit=False, ): """ Do NOT override this method, this method is the framework that @@ -164,6 +165,9 @@ def build( self._ninja = ninja self._dry_run = dry_run self._user_separate_builds = separate_builds + + was_run_pend = self._test_status.current_is(RUN_PHASE, TEST_PEND_STATUS) + for phase_name, phase_bool in [ (SHAREDLIB_BUILD_PHASE, not model_only), (MODEL_BUILD_PHASE, not sharedlib_only), @@ -202,6 +206,15 @@ def build( comments=("time={:d}".format(int(time_taken))), ) + # Building model while job is queued and awaiting run + if ( + skip_submit + and was_run_pend + and self._test_status.current_is(SUBMIT_PHASE, TEST_PEND_STATUS) + ): + with self._test_status: + self._test_status.set_status(SUBMIT_PHASE, TEST_PASS_STATUS) + return success def build_phase(self, sharedlib_only=False, model_only=False): diff --git a/CIME/Tools/case.build b/CIME/Tools/case.build index c8e4d54c467..4edf177198e 100755 --- a/CIME/Tools/case.build +++ b/CIME/Tools/case.build @@ -80,6 +80,14 @@ def parse_command_line(args, description): help="Build each component one at a time, separately, with output going to separate logs", ) + parser.add_argument( + "--skip-submit", + action="store_true", + help="Sets the current test phase to RUN, skipping the SUBMIT phase. This " + "may be useful if rebuilding the model while this test is in the batch queue. " + "ONLY USE IF A TEST CASE, OTHERWISE IGNORED.", + ) + parser.add_argument( "--dry-run", action="store_true", @@ -173,6 +181,7 @@ def parse_command_line(args, description): args.separate_builds, args.ninja, args.dry_run, + args.skip_submit, ) @@ -191,6 +200,7 @@ def _main_func(description): separate_builds, ninja, dry_run, + skip_submit, ) = parse_command_line(sys.argv, description) success = True @@ -234,6 +244,7 @@ def _main_func(description): ninja=ninja, dry_run=dry_run, separate_builds=separate_builds, + skip_submit=skip_submit, ) else: diff --git a/CIME/test_status.py b/CIME/test_status.py index 20d2cef93a9..90714631eb8 100644 --- a/CIME/test_status.py +++ b/CIME/test_status.py @@ -274,6 +274,17 @@ def get_status(self, phase): def get_comment(self, phase): return self._phase_statuses[phase][1] if phase in self._phase_statuses else None + def current_is(self, phase, status): + try: + latest = self.get_latest_phase() + except KeyError: + return False + + return latest == phase and self.get_status(phase) == status + + def get_latest_phase(self): + return list(self._phase_statuses.keys())[-1] + def phase_statuses_dump( self, prefix="", skip_passes=False, skip_phase_list=None, xfails=None ): diff --git a/CIME/tests/test_unit_test_status.py b/CIME/tests/test_unit_test_status.py index 0b79c8bac6a..9b3036801fc 100755 --- a/CIME/tests/test_unit_test_status.py +++ b/CIME/tests/test_unit_test_status.py @@ -41,6 +41,20 @@ def _set_phase_to_status(self, phase, status): with self._ts: self._ts.set_status(phase, status) + def test_get_latest_phase(self): + assert self._ts.get_latest_phase() == test_status.RUN_PHASE + + def test_current_is(self): + assert self._ts.current_is(test_status.RUN_PHASE, test_status.TEST_PASS_STATUS) + + assert not self._ts.current_is( + test_status.RUN_PHASE, test_status.TEST_PEND_STATUS + ) + + assert not self._ts.current_is( + test_status.SUBMIT_PHASE, test_status.TEST_PASS_STATUS + ) + # ------------------------------------------------------------------------ # Tests of TestStatus.phase_statuses_dump # ------------------------------------------------------------------------ From a5779ab9c319b039663b5cd662877a1055279ec7 Mon Sep 17 00:00:00 2001 From: James Edwards Date: Tue, 1 Aug 2023 07:28:56 -0600 Subject: [PATCH 120/390] if the config_machines.xml file cannot be found give a meaningful error --- CIME/XML/grids.py | 4 ++++ CIME/XML/machines.py | 5 ++++- CIME/tests/test_unit_xml_machines.py | 4 ++-- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/CIME/XML/grids.py b/CIME/XML/grids.py index 819838edddd..b98a89b963b 100644 --- a/CIME/XML/grids.py +++ b/CIME/XML/grids.py @@ -25,6 +25,10 @@ def __init__(self, infile=None, files=None, comp_interface=None): infile = files.get_value("GRIDS_SPEC_FILE") logger.debug(" Grid specification file is {}".format(infile)) schema = files.get_schema("GRIDS_SPEC_FILE") + expect( + os.path.isfile(infile) and os.access(infile, os.R_OK), + f"ERROR: grid file not found {infile}", + ) try: GenericXML.__init__(self, infile, schema) except: diff --git a/CIME/XML/machines.py b/CIME/XML/machines.py index 54c6edea515..1b45cf5b580 100644 --- a/CIME/XML/machines.py +++ b/CIME/XML/machines.py @@ -41,9 +41,12 @@ def __init__(self, infile=None, files=None, machine=None, extra_machines_dir=Non logger.debug("Verifying using schema {}".format(schema)) self.machines_dir = os.path.dirname(infile) + if os.path.exists(infile): + checked_files.append(infile) + else: + expect(False, f"file not found {infile}") GenericXML.__init__(self, infile, schema) - checked_files.append(infile) # Append the contents of $HOME/.cime/config_machines.xml if it exists. # diff --git a/CIME/tests/test_unit_xml_machines.py b/CIME/tests/test_unit_xml_machines.py index df6b9ce90d3..3f47a02b968 100644 --- a/CIME/tests/test_unit_xml_machines.py +++ b/CIME/tests/test_unit_xml_machines.py @@ -140,8 +140,8 @@ class TestUnitXMLMachines(unittest.TestCase): def setUp(self): Machines._FILEMAP = {} - - self.machine = Machines() + # read_only=False for github testing + self.machine = Machines(machine="centos7-linux") self.machine.read_fd(io.StringIO(MACHINE_TEST_XML)) From 565712540458fdaf6854dd40fe7046313a1930e7 Mon Sep 17 00:00:00 2001 From: James Edwards Date: Tue, 1 Aug 2023 08:03:18 -0600 Subject: [PATCH 121/390] remove redundent ERROR --- CIME/XML/grids.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CIME/XML/grids.py b/CIME/XML/grids.py index b98a89b963b..e34aacf2d01 100644 --- a/CIME/XML/grids.py +++ b/CIME/XML/grids.py @@ -27,7 +27,7 @@ def __init__(self, infile=None, files=None, comp_interface=None): schema = files.get_schema("GRIDS_SPEC_FILE") expect( os.path.isfile(infile) and os.access(infile, os.R_OK), - f"ERROR: grid file not found {infile}", + f" grid file not found {infile}", ) try: GenericXML.__init__(self, infile, schema) From 2031fb5912d70293011ae57080cd1031dba105cd Mon Sep 17 00:00:00 2001 From: David <36640545+daianzhuo@users.noreply.github.com> Date: Wed, 2 Aug 2023 17:17:01 +0100 Subject: [PATCH 122/390] Update create-a-case.rst with how to use SourceMods Emphasised two points on how to use SourceMods to modify a case. 1. The name of the xmlchange shell file should be shell_commands 2. SourceMods changes do not need to have the same strucutre as the component source codes --- doc/source/users_guide/create-a-case.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/doc/source/users_guide/create-a-case.rst b/doc/source/users_guide/create-a-case.rst index 71b39b5cbd8..4089be063c8 100644 --- a/doc/source/users_guide/create-a-case.rst +++ b/doc/source/users_guide/create-a-case.rst @@ -207,6 +207,12 @@ As an example, the directory could contain the following files: :: > shell_commands (this would contain ./xmlchange commands) > SourceMods/src.cam/dyncomp.F90 +It is important to note that the file containing the **xmlchange** +commands must be named ``shell_commands`` in order for it to be recognised +and run upon case creation. + +The structure of the component directories do not need to be the same as in the component source code. As an example, should the user want to modify the ``src/dynamics/eul/dyncomp.F90`` file within the CAM source code, the modified file should be put into the ``SourceMods/src.cam`` directly. There is no need to mimic the source code structure, such as ``SourceMods/src.cam/dynamics/eul``. + When the user calls **create_newcase** with the ``--user-mods-dir`` pointing to the full pathname of the directory containing these changes, then the ``CASEROOT`` will be created with these changes applied. From 08d33b3f790b4604a8d91d7ba683756773f6c242 Mon Sep 17 00:00:00 2001 From: David <36640545+daianzhuo@users.noreply.github.com> Date: Wed, 2 Aug 2023 17:24:18 +0100 Subject: [PATCH 123/390] Wrap lines in create-a-case.rst --- doc/source/users_guide/create-a-case.rst | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/doc/source/users_guide/create-a-case.rst b/doc/source/users_guide/create-a-case.rst index 4089be063c8..c9257da19a6 100644 --- a/doc/source/users_guide/create-a-case.rst +++ b/doc/source/users_guide/create-a-case.rst @@ -211,7 +211,12 @@ It is important to note that the file containing the **xmlchange** commands must be named ``shell_commands`` in order for it to be recognised and run upon case creation. -The structure of the component directories do not need to be the same as in the component source code. As an example, should the user want to modify the ``src/dynamics/eul/dyncomp.F90`` file within the CAM source code, the modified file should be put into the ``SourceMods/src.cam`` directly. There is no need to mimic the source code structure, such as ``SourceMods/src.cam/dynamics/eul``. +The structure of the component directories do not need to be the +same as in the component source code. As an example, should the user +want to modify the ``src/dynamics/eul/dyncomp.F90`` file within the +CAM source code, the modified file should be put into the directory +``SourceMods/src.cam`` directly. There is no need to mimic the source +code structure, such as ``SourceMods/src.cam/dynamics/eul``. When the user calls **create_newcase** with the ``--user-mods-dir`` pointing to the full pathname of the directory containing these changes, then the ``CASEROOT`` will be From 234185085a0d392a4d986769bf6a8d71d7084f2b Mon Sep 17 00:00:00 2001 From: Jian Sun Date: Sun, 6 Aug 2023 22:02:12 -0600 Subject: [PATCH 124/390] fix the usage of wrapper script for Derecho --- CIME/case/case.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CIME/case/case.py b/CIME/case/case.py index fffa1e51d08..ec552d57bec 100644 --- a/CIME/case/case.py +++ b/CIME/case/case.py @@ -2107,7 +2107,7 @@ def get_mpirun_cmd(self, job=None, allow_unresolved_envvars=True, overrides=None ngpus_per_node = self.get_value("NGPUS_PER_NODE") if ngpus_per_node and ngpus_per_node > 0 and config.gpus_use_set_device_rank: - if self.get_value("MACH") == "gust": + if self.get_value("MACH") == "gust" or self.get_value("MACH") == "derecho": mpi_arg_string = mpi_arg_string + " get_local_rank " else: # this wrapper script only works with OpenMPI library From de5476ab9aa83563f8fde727f538be593438b171 Mon Sep 17 00:00:00 2001 From: Jian Sun Date: Mon, 7 Aug 2023 12:09:08 -0600 Subject: [PATCH 125/390] update description for Derecho --- CIME/config.py | 2 +- doc/source/users_guide/cime-customize.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CIME/config.py b/CIME/config.py index 9666439cb2e..0c0a8cc04a2 100644 --- a/CIME/config.py +++ b/CIME/config.py @@ -180,7 +180,7 @@ def __init__(self): self._set_attribute( "gpus_use_set_device_rank", True, - desc="If set to `True` and NGPUS_PER_NODE > 0 then `$RUNDIR/set_device_rank.sh` or `get_local_rank` (a global script on Gust) is appended when the MPI run command is generated.", + desc="If set to `True` and NGPUS_PER_NODE > 0 then `$RUNDIR/set_device_rank.sh` or `get_local_rank` (a global script on Derecho/Gust) is appended when the MPI run command is generated.", ) self._set_attribute( "test_custom_project_machine", diff --git a/doc/source/users_guide/cime-customize.rst b/doc/source/users_guide/cime-customize.rst index 2c65d1ab954..58dbe9a6a40 100644 --- a/doc/source/users_guide/cime-customize.rst +++ b/doc/source/users_guide/cime-customize.rst @@ -44,7 +44,7 @@ default_short_term_archiving True bool If set to `Tr driver_choices ('mct', 'nuopc') tuple Sets the available driver choices for the model. driver_default nuopc str Sets the default driver for the model. enable_smp True bool If set to `True` then `SMP=` is added to model compile command. -gpus_use_set_device_rank True bool If set to `True` and NGPUS_PER_NODE > 0 then `$RUNDIR/set_device_rank.sh` or `get_local_rank` (a global script on Gust) is appended when the MPI run command is generated. +gpus_use_set_device_rank True bool If set to `True` and NGPUS_PER_NODE > 0 then `$RUNDIR/set_device_rank.sh` or `get_local_rank` (a global script on Derecho/Gust) is appended when the MPI run command is generated. make_case_run_batch_script False bool If set to `True` and case is not a test then `case.run.sh` is created in case directory from `$MACHDIR/template.case.run.sh`. mct_path {srcroot}/libraries/mct str Sets the path to the mct library. serialize_sharedlib_builds True bool If set to `True` then the TestScheduler will use `proc_pool + 1` processors to build shared libraries otherwise a single processor is used. From f6c42fd12bd4a27f13f3755c147430d437cc1275 Mon Sep 17 00:00:00 2001 From: Jian Sun Date: Tue, 8 Aug 2023 09:25:24 -0600 Subject: [PATCH 126/390] use a generic way to apply the MPI wrapper script --- CIME/case/case.py | 17 +++-------- CIME/case/case_setup.py | 28 ------------------- CIME/config.py | 5 ---- .../config/xml_schemas/config_machines.xsd | 4 +++ doc/source/users_guide/cime-customize.rst | 1 - 5 files changed, 8 insertions(+), 47 deletions(-) diff --git a/CIME/case/case.py b/CIME/case/case.py index ec552d57bec..df881db3201 100644 --- a/CIME/case/case.py +++ b/CIME/case/case.py @@ -2106,19 +2106,10 @@ def get_mpirun_cmd(self, job=None, allow_unresolved_envvars=True, overrides=None mpi_arg_string += " : " ngpus_per_node = self.get_value("NGPUS_PER_NODE") - if ngpus_per_node and ngpus_per_node > 0 and config.gpus_use_set_device_rank: - if self.get_value("MACH") == "gust" or self.get_value("MACH") == "derecho": - mpi_arg_string = mpi_arg_string + " get_local_rank " - else: - # this wrapper script only works with OpenMPI library - # has been tested on Casper - expect( - self.get_value("MPILIB") == "openmpi", - "The wrapper script only works with OpenMPI library; {} is currently used".format(self.get_value("MPILIB")), - ) - rundir = self.get_value("RUNDIR") - output_name = rundir + "/set_device_rank.sh" - mpi_arg_string = mpi_arg_string + " " + output_name + " " + if ngpus_per_node and ngpus_per_node > 0: + mpi_gpu_run_script = self.get_value("MPI_GPU_WRAPPER_SCRIPT") + if mpi_gpu_run_script: + mpi_arg_string = mpi_arg_string + " " + mpi_gpu_run_script return self.get_resolved_value( "{} {} {} {}".format( diff --git a/CIME/case/case_setup.py b/CIME/case/case_setup.py index aa8fb8b6b6c..d45c775b74a 100644 --- a/CIME/case/case_setup.py +++ b/CIME/case/case_setup.py @@ -482,31 +482,3 @@ def case_setup(self, clean=False, test_mode=False, reset=False, keep=None): caseroot=caseroot, is_batch=is_batch, ) - - # put the following section here to make sure the rundir is generated first - machdir = self.get_value("MACHDIR") - mach = self.get_value("MACH") - ngpus_per_node = self.get_value("NGPUS_PER_NODE") - overrides = {} - overrides["ngpus_per_node"] = ngpus_per_node - input_template = os.path.join(machdir, "mpi_run_gpu.{}".format(mach)) - if os.path.isfile(input_template): - # update the wrapper script that sets the device id for each MPI rank - output_text = transform_vars( - open(input_template, "r").read(), case=self, overrides=overrides - ) - - # write it out to the run dir - rundir = self.get_value("RUNDIR") - output_name = os.path.join(rundir, "set_device_rank.sh") - logger.info("Creating file {}".format(output_name)) - with open(output_name, "w") as f: - f.write(output_text) - - # make the wrapper script executable - if os.path.isfile(output_name): - os.system("chmod +x " + output_name) - else: - expect( - False, "The file {} is not written out correctly.".format(output_name) - ) diff --git a/CIME/config.py b/CIME/config.py index 0c0a8cc04a2..3cef6cc0530 100644 --- a/CIME/config.py +++ b/CIME/config.py @@ -177,11 +177,6 @@ def __init__(self): False, desc="If set to `True` then COMP_ROOT_DIR_CPL is set using UFS_DRIVER if defined.", ) - self._set_attribute( - "gpus_use_set_device_rank", - True, - desc="If set to `True` and NGPUS_PER_NODE > 0 then `$RUNDIR/set_device_rank.sh` or `get_local_rank` (a global script on Derecho/Gust) is appended when the MPI run command is generated.", - ) self._set_attribute( "test_custom_project_machine", "melvin", diff --git a/CIME/data/config/xml_schemas/config_machines.xsd b/CIME/data/config/xml_schemas/config_machines.xsd index e8c24144b7e..b025c4039e0 100644 --- a/CIME/data/config/xml_schemas/config_machines.xsd +++ b/CIME/data/config/xml_schemas/config_machines.xsd @@ -61,6 +61,7 @@ + @@ -178,6 +179,9 @@ + + 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 VI>74yuLHae@H)Wjz_-wW{{yE?CY}HQ diff --git a/CIME/non_py/cprnc/test_inputs/clm2.h1.subset.test.nc b/CIME/non_py/cprnc/test_inputs/clm2.h1.subset.test.nc deleted file mode 100644 index db606c4b1353bffaa0564c44c7332f922910a823..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 321512 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

+V_x-0jmay_A6Z-|IzyhIAbPE|?siXn-<_VSr# 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 F_&<1SMs@%I diff --git a/CIME/non_py/cprnc/test_inputs/control.nc b/CIME/non_py/cprnc/test_inputs/control.nc deleted file mode 100644 index d9c0ce6a5c40e1ecdd7ff21469de814b81289ae4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 808 zcmcgqK~4fO6#WRJh>*D804~|82VfcuM{qlivB4%~Fw?@C1F&%60a&{92pq$uNAL)~ zS6VY6VM~+O|N4Lb`~81r@-S_Q2+;%Dgk4lHau#^$$upqi&0O=)SXbcZK+2&GS05nH zmX-02p1ofGMbB!jU7<@y;SSJ6Xan1}`&#Ke$20~>qgvGd6^U5X8A9iSJie1U?BSSB zy2tnP4WFAXR2ATD(x~3i8E_GPezxpy<%#tef$qL zW8@>$|AOtt`gh>0b;|nRjr#Yeug`YFtW(dlbn@F~`7L&=@meH@&9UX=TVMzLh$sQB ofg7B`Lwpzg@F_Ue6R-x}fi3R9$NZ27uHIq{e}keQQ&GPnf5k;-d;kCd diff --git a/CIME/non_py/cprnc/test_inputs/control_0d.nc b/CIME/non_py/cprnc/test_inputs/control_0d.nc deleted file mode 100644 index d48586f228227df9b602bbf078ef71c2666df522..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 616 zcmZ`$OHRWu5OrEqP(fnLaxRc4b`n%9RV-SugKoM+GA6auipGu-x25;spd5j5rKB;0 zQC=qVdmiV_=1&3f?FPdk;&Q#It*MT#Iv^zYTJ99gRSqnLIOE9!rwmw36An6;C4;aA zt7^NKm55iOQgS<`Q4G1!wlZ7HMLHkW(#yqOEOBMBw{Wi35LejyhCX=w#y8pzBXgEg diff --git a/CIME/non_py/cprnc/test_inputs/control_attributes.nc b/CIME/non_py/cprnc/test_inputs/control_attributes.nc deleted file mode 100644 index a26bc9464151956abd572d0407ea278669d5bc07..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 892 zcmcgqJ5Iwu5Sq=f^q^xYaFqJl^h${>(n^{M@Y#ratz9plpKJ9 z_tv{4K}wsIp65SrcGjc&aYsam2Us+~WBToyll;3K^wio#&5#!R;9JA?WNb96$F?u0!%)fv@NI`L>7V ze*mX{GcP#<_Y%hKKPz_&kr<8`pP2d*SOII1M1XVP5;k-jUpYT~37p9jumIkHC34_n Sy{&_uyt&v1B>kVjx5zKead;^J diff --git a/CIME/non_py/cprnc/test_inputs/control_char.nc b/CIME/non_py/cprnc/test_inputs/control_char.nc deleted file mode 100644 index 7b4567908ea935cdfe5e1dd25675595e928cbddf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 92 zcmZ>EabskF04^W}VzU>Q6vgMH=7B_6Km-(U18I;vYi=b_1w;_07pjDT3CIBHiAYRJ GP5}UWAqW)! diff --git a/CIME/non_py/cprnc/test_inputs/control_floatDoubleNan.nc b/CIME/non_py/cprnc/test_inputs/control_floatDoubleNan.nc deleted file mode 100644 index 1e99d3281e6ce69a5262b2e698b78a5540f1dabb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 916 zcmc&yKTE?v9KD!EYi+^7!Lf*72NC~*AnHj&addJG5sw;3Q^=)V^`kgC`Vkx)9Qq;r z5Dt#Mm;6pjpp%0?czN%a%e{Mdmwq_#lu|?s@QHMsfz(o|8bvZPZ02eWdKqwY z=)-CQ)WIZ63d=W@U;Ew+b7O2|VoT!&(8SOS*iWmqT%JW99p{ma^Wp&$QS{k>&nva$ zq!0J7$j5!h{LJtUUMY+%rg08go(1R6?}_JA-;*2fj1p^;Y#2@BbRq}EbMx-{y;X24 z8YF3Y`#U&4M%_b(8MrAK4xzc;Uo&ujUU+T1o!R*ZbUjCXoc#@!{Q|ozN6`P6#SgK* zyHCf=!+Q_(0iLMKC#5u4hozm*R@Fa#SFHDh-Mka&PleBfFDm{4EpKbf diff --git a/CIME/non_py/cprnc/test_inputs/control_int.nc b/CIME/non_py/cprnc/test_inputs/control_int.nc deleted file mode 100644 index 13913cf90e3264421b9dcae2455680c76b00156c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 820 zcmc&yQEI|K5M7PYG&NO3p&g75n+VyBXYsR#=%)wq0KI@GNZ(7c z5(@s-g_oJ^?7Vq9v+p14ju3(f0gur83`TS-Ke6Ea=Y;&Fi05cU20N*w9ncK{| d+GC9;U<^zEyq~HD&oGbi5y03Ccn0Rc;ul0jW<3A^ diff --git a/CIME/non_py/cprnc/test_inputs/control_multipleTimes_someTimeless.nc b/CIME/non_py/cprnc/test_inputs/control_multipleTimes_someTimeless.nc deleted file mode 100644 index 9ebf4579c9260ace2849df3ad681ae04d94bac46..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1616 zcmcgs!EVz)5Z$zKQ$kyM-~;y91C0`=(AJka3aYpOmx!w|Vbxk}?8>`|)LTD+V~_oY zegPlCfluJbk@p<0l>`!UKw0T|W@mQi&FoCle|=dKLTm%gQf}+mLKPnOI!mc<0d<>< z6gOCV0Bw!y#Mmsqr49Sq<6x}h+xS-cXR`A$Xtx9ZmGs-4ZXna)7bA~*@^~VxGIkQ@ zFG}glVV2rF)6r4pmV;o=6_`1&lbIq&0Lw_=>*-=Ii9M_syt`E@p-kapV0iXLnV!e(V zs@MTAE8ab%^D#fpP|;7wtq`B^4)`WS4|-1mA@C8n1HJ+eLR>%(1%OX+{$YA2`~g4g hy+c5*e=fwGwRkz{SnKkKIJ-_kqw4UX^54{y1)cBF0*83KRK4Qy+!##5@bI;s6bgz0T7Oet88p!yrCM9clAc>P+q&<+EdP)c33e@N? zgN5MN?uuJ2UcYGL(`a$)yxwSYIeJjM`oybCuDzMyqd@8~@U5C0CESt004XSiKXt77 zuFj*&t9@Q|f7JO_hDy?fu*z~2IKf=V+1%07P`G19E@nd!B6k2#zl6+9w~t_YIBeFe zT7!0s9RpGC(HxTbQ2)fU3+ZVEf<<#Fjm0DsLZOpTPmOGRPGM2THseP#Onm>VQ%vdz zSJ4B|UGq48hM3br>7`387*C2+FL{&SccOf0<|*aLiL-S5g&-yBD>dv^j=&2}&-4A; zU6OiEa>Og}ZX)JmZws9IrL_<5JNP8t^fdeQevW%2Px{Z(TbTJErZ3lwbwE1!>JZ-m uTi}B+n*k>P#~$3|x3Lb*hG^ddYv2{w;5X3dyg3JM&*R)1BI)nICu3i)vX;XD diff --git a/CIME/non_py/cprnc/test_inputs/copy.nc b/CIME/non_py/cprnc/test_inputs/copy.nc deleted file mode 100644 index d9c0ce6a5c40e1ecdd7ff21469de814b81289ae4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 808 zcmcgqK~4fO6#WRJh>*D804~|82VfcuM{qlivB4%~Fw?@C1F&%60a&{92pq$uNAL)~ zS6VY6VM~+O|N4Lb`~81r@-S_Q2+;%Dgk4lHau#^$$upqi&0O=)SXbcZK+2&GS05nH zmX-02p1ofGMbB!jU7<@y;SSJ6Xan1}`&#Ke$20~>qgvGd6^U5X8A9iSJie1U?BSSB zy2tnP4WFAXR2ATD(x~3i8E_GPezxpy<%#tef$qL zW8@>$|AOtt`gh>0b;|nRjr#Yeug`YFtW(dlbn@F~`7L&=@meH@&9UX=TVMzLh$sQB ofg7B`Lwpzg@F_Ue6R-x}fi3R9$NZ27uHIq{e}keQQ&GPnf5k;-d;kCd diff --git a/CIME/non_py/cprnc/test_inputs/copy_char.nc b/CIME/non_py/cprnc/test_inputs/copy_char.nc deleted file mode 100644 index 7b4567908ea935cdfe5e1dd25675595e928cbddf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 92 zcmZ>EabskF04^W}VzU>Q6vgMH=7B_6Km-(U18I;vYi=b_1w;_07pjDT3CIBHiAYRJ GP5}UWAqW)! diff --git a/CIME/non_py/cprnc/test_inputs/cpl.hi.subset.control.nc b/CIME/non_py/cprnc/test_inputs/cpl.hi.subset.control.nc deleted file mode 100644 index c83d8cc499df198fd10f94ca398e8310a4182876..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 27876 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 M=bo1||KGa&KZms_P5=M^ diff --git a/CIME/non_py/cprnc/test_inputs/cpl.hi.subset.test.nc b/CIME/non_py/cprnc/test_inputs/cpl.hi.subset.test.nc deleted file mode 100644 index 3fdd331ba42bcac6182f193cd80cc209671b74d1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 27872 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 diff --git a/CIME/non_py/cprnc/test_inputs/diffs_in_attribute.nc b/CIME/non_py/cprnc/test_inputs/diffs_in_attribute.nc deleted file mode 100644 index b81f05122af673ecbe9ea48424e7d5bf3bb4e636..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 892 zcmc&yJ5Iwu6xJpqv2FI%~0nl^h${>(n^{M@Y#ratz9plpKJ9 zne{G?kkZ0RllPf@Z{9vA&3a*6M0#{hy&|OLq7xpqZf(+&Jf_;NE%b=tTF16 zX%S z+QRnpc7FfHeLY6LM^RSh8HG6PbA~=2^xfOphkM!A$KT`poZ$=h$f(*ZWd+ynjP<*3 z;vRp$iFZc}UDvvpr%RbPZt?sMy=*c*hKJ}Y z>wnO{ho=8#-f{=rOBlERS#i4(B1R&{PfULeY=Es0i2zQ4bHwm1zj1lw5+suuumWCz WHSQqCdRqrSd2!quEc5Tcrx4$zt#}*& diff --git a/CIME/non_py/cprnc/test_inputs/diffs_in_fill.nc b/CIME/non_py/cprnc/test_inputs/diffs_in_fill.nc deleted file mode 100644 index 07b62e33948df317c245f350ba1352f79d43d3ec..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 808 zcmcgqJ5B>J5S@HBBq5O~H~?i@$N^$2C`X`O7GVWT&IYZW)Hy&33J#Exk|XRfC^-U0 z;JwWnt&q}Yq_^`tW54l?cYeQUi3l+SI)q#2aMG@L7RVEz8|=z((XdPK9uUt^hc*Jp zlTB`eXXL0idN#|#7@wNVQ@9KCk=ny|+_6@?&nb;@)X3LGc)>v!b*`cFPF~-WI_%+; zPTa@)^9~>I7s2?j%?i+J7ChhViSzm26Zei+)_a>T({1K9^^iET*3*14fFGs{>)gX% zvY8{Fp#CR(zpj4=&RXZJ|JA5JJ%4?+Tg^K5OwSql|y=tppHaOj8d zLpV74UUFALpp%0?czN%a%e{MdmtNTSgb+jv@QHMkfy7FoV?!MP4U-HNn@Vj!uL5ok zeOPUPIv8h((R@?-wV%`=S4u}J)--MaO$^Pzep;^O@+|Y{IFD?Un`cZ!(Ps-jZ`3v? zeYl5ZKJGi_XNC{(no-(J;vBL#3(l|ZiRV+_lN;|06RneM5KZEAY!8a(<~{bh>)=+@ zPtx>ZF<2d=?jpk++_V`^pt;^(GjM-ic&EJm`PCb$(Epgl53#o~WA_AtYGhF?|QW0^~0tZUwLj?4XBcN%RG8mpuKk{hnpq2umJVKC;Z4 gLCG`A_#==ZGU0;((-l5za8RjVE_OC diff --git a/CIME/non_py/cprnc/test_inputs/diffs_in_vals.nc b/CIME/non_py/cprnc/test_inputs/diffs_in_vals.nc deleted file mode 100644 index 16411c4881a3e7ba88710ce10d4b4ad98175eb19..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 808 zcmchVJx;?w5QR4+fe;djf&);d1uozvC`X_*j#vdtj)T0L)F~+_C^$e$N{*0YP;vy0 z!1o**Sx9NK((^pe?7aE0$9I#KF-GWvwva7yj5#WK62!Zp6VkcUOXHluk3pRy9i0IX z?=JH+czJeugGV^w4eVo>F#izdY|UAc$H+!YDwBzYdRjCV*BwV&9eKy zU^6B@qW&kgSJl6UtJWdwe>Ljg9KOEBEq7hdEGwtJZ8y5&$+0iSMA%z47OlYsY{54% q1J1!E@9;9dt^D{bPWu4X;0XZ}|6dWKWB}ecvC^-U0 z;C;tN7E;=*^gVxPcK+fq9&zzOQ|us~rdf9X z7i`DG$JGDC_UroBaMe0w{jXO2o5R;vyVb0lo@M3K+XmqkUsm*DOo+W@V&MjCK?%Nz r8E_6R*~6=NS^M$nEushZ8}J6UoC!e*R~|fi28!`FMDjD0=*O5})8l8w diff --git a/CIME/non_py/cprnc/test_inputs/diffs_in_vals_and_extra_and_missing.nc b/CIME/non_py/cprnc/test_inputs/diffs_in_vals_and_extra_and_missing.nc deleted file mode 100644 index e29e072a24c1b57d1c7fbae13df98ce662326308..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 820 zcmc(cF;2rk5JfjQfe;djf&);d1#tkIpd5kL7_kbL90%E()F~+`DP3{^N{SqVl7a(p z1m1VN$U;gBD}B%R%+BAxvy-c7#~33FKpA{~-tf-O3Tw;QCZ&YCpik+U7-ZYCyq{efBd?J!3U^NF5r- zbo@T~mxov4fwQGsB?Yveh5Flf(tLj3$=65ov@Fwn7O#?Q*$hf^>)cMphr~fVO|$I! zr`WEEkMZzE>^C0H;X3cHJ$$x2T>OuRz`vQlyP{^HckYE}d|A<>F(L7i$%SiBferYe pX23BxgL^8SoYw!dr_K;J5-3N~zn%WCd`FHST%s0m(XS@Ia diff --git a/CIME/non_py/cprnc/test_inputs/diffs_in_vals_and_fill.nc b/CIME/non_py/cprnc/test_inputs/diffs_in_vals_and_fill.nc deleted file mode 100644 index 6db4782e6ae6a0614e3e9754a26b8150b76ca6fe..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 808 zcmcgqJ8r^25FG+efPj>O15nZuF5nFjM@Z`!v4WKr6L}YEmnl-D$N?x*<_I}P${Zm_ z$a}UovLewk((^p;?7aEd^ZP|hM2J4nChRVQlZ%q4o;(6N-fT5r8g>nS34|Q#&;|f` zw9kxpJgM{sE4|LOc8N|Mg*!kOp%?hJt!jmSPH7B-Mz+iS28l50jG*&IUiV2I&TvX6 z+++Xj;T`_UYv&JX4qEPl`OTTQpZ_!Q?r>|IGub*hq;_8~i92g8=aU|MKUo-SAO3>P z82JSCKj6D{{abL>I%oZ_M*Z3O>vP<4*I{OQ9`f5p@f1&(dKQV{@367B0FJ;X@GZh! rs~g-I<6VHFiYah424dh5D1cYsh&axeU*>_Ur#gneLGc`0Q9mNT^+RX{ diff --git a/CIME/non_py/cprnc/test_inputs/extra_variables.nc b/CIME/non_py/cprnc/test_inputs/extra_variables.nc deleted file mode 100644 index 2d33ab90796ca6493e4cf1206035c23024d5a837..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1164 zcmc&zOHRWu5OqlcrL+-eVe!zbaC6JQOz02TJY$NV}ETs}83{9Tg% K58(Un_x%7!tD1}e diff --git a/CIME/non_py/cprnc/test_inputs/int_missing.nc b/CIME/non_py/cprnc/test_inputs/int_missing.nc deleted file mode 100644 index 139b358d57fdb6b4a62ebb7a64a6eb1264fd8ab1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 648 zcmcgqOA5j;6pYpS7eN#R#kEVtjY~o45!}{-K?AWu6MI&#;R))DZKNn}eK38Kyks(Y z>2?=Xgb+j=8o60`Sb_7re|tFK rOxHV?>4P5q%@W@{zG|3eFB<^*DO!O0r#R0s-=Yr;fDteTCcx|odx%VA diff --git a/CIME/non_py/cprnc/test_inputs/lon_differs.nc b/CIME/non_py/cprnc/test_inputs/lon_differs.nc deleted file mode 100644 index bb42924691291b34b3f8bd7cf774543dfd5b67df..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 884 zcmc(cJx;?w5QR55h7b~of&-+Wpya2aK*A;{6*a9f!U~og2gOe69D$ND1qDZ-^f4$o z0wo0n-?P`qLeRoU&+qNdyxH;Y@O;!V#t2>D2+1^stPXQ&7qs(u5^2$hE%Z95J?f+A z0cm%h#(Ab4W7a_LCK}J8C<~%6Q{%SwNIk$#Qk_-L^IMO8-6NgO@>`6A`s~o>QQFR_ zKINJ4PqWl@n!iKoGrW;US-uEo$nyW?msg>AbU)w!sm}K&ahApDI9P{*em~toQ|ql=?|?6Hx-`av z{e2kWvG$+f6)4c}@GESLIopM|!2o^1K6_=oyhT+buRj1D!0TUurS;yUSJq3u^x!Rf UvM#-MulRsckrrj3%wC$@*zY?;q;FqGp&^(3}DP zSER708h8^)U?)RB0k=R6JOeKxm#|g*{-KnQ#~J^R`h;Hq{PtvsxG!uV%XsK!JZr4S aE5_6Se81Ia9A#S`3b9+|#SfwUHQ#T?6`a@r diff --git a/CIME/non_py/cprnc/test_inputs/multipleTimes_someTimeless_extra_and_missing.nc b/CIME/non_py/cprnc/test_inputs/multipleTimes_someTimeless_extra_and_missing.nc deleted file mode 100644 index d2718a86de5d7b3c05025f2af2d0eebd97eed49e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1660 zcmcgs!EVz)5OtH5me7hG_<*?)q}Gj{&{9hXM~Na1Krazj<0eaDVedxXbwqFZ1U`U6 zKZOGy!YA+vym9Qv32l)Obmixn-PxTtvz~FUKiIM?s}3{@S&u^l)!N~CtR(9-VB18A z;0eY)z-znHNE?-2@fzl}r^9JRZ^M*ahn%+8aqy7aa?U-Hqg2Q+5p+B!Bea=^sT)Ye zPLGOtyO>jw#U`4?VhE!Sv`TU<7Fq}5vq{4sl;a?Yw2tJ&mZQcn6JWg}_XRo6y;jR} zPnosNte-R;cPpeNnM)%jO{(HxjScQ@HmrqlBWY`$bd}~tmSxr}nreQ3N8j<(s2eMg!u0wBx|oSHL_ukqi&RHS zQpY~CPpSDDH82Y&f?m?pn3;ESB1g*BA8i~wqMjO8ER)DgvrBuV65hmk#ypRJ2S8ch zT=zV2iG=;t964Aj{t1vy9vP?3aE-sJXG&#~yt2 zAs=s4K9=&nVp;m+-rS6hZ9EQ&CJ5$XoE6Q-~S8|zKDegnB% z!~r$`7jSR$xUU%aUiQkr0(8##zGL_b&hj6}d6)c9^Sa@0_8od@;~4o{=64EN^YQW* zz&Fe4Lhlmb10R3|@CCTGtXI%|3&1Pif4w~8|9~Cq-9vziJ&UF7PJcgf2;*P-^>eTP J!7px-$1k|aV$T2o diff --git a/CIME/non_py/cprnc/test_inputs/noTime_diffs_in_vals_and_fill.nc b/CIME/non_py/cprnc/test_inputs/noTime_diffs_in_vals_and_fill.nc deleted file mode 100644 index c3006345ed7044a027f10b5a09c0a14e8dc0cd30..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 716 zcmcgqJ8r^25FIcUKO?2c0a8*!L&sV~oFJlg9A!l;Ee3fPDhiH}BT%HsF*rcdr_2#@ zguG{a5sH+Sk)G%I&FmYG?b-djAyxE{nm}OIIhBg1jy&Y0jy^K)eV;!cK_d^hdFCt# zf*#ScUKU#0M5mT!Eue$a1UWF3KhF{Wc?`TqzAoGg8WHrlg^xmRJ8j)AEkMhem(LVH z`}<7n8>}*GvwWHCQnS@ms=XRdvtbv!m&`L`7Uv>&_BHY$Mm->R>e(Z3?t87-`-|D* zE6;wb_w@Ug{|YpIlW=+vQPIbzQpn^-5x!IPEfNcG1KdFeFYBjThZaMOAAtk#3LMb` UO`X7b4m{ofti1!&zknZ+UlHbCq5uE@ diff --git a/CIME/non_py/cprnc/test_inputs/noTime_extra_and_missing.nc b/CIME/non_py/cprnc/test_inputs/noTime_extra_and_missing.nc deleted file mode 100644 index c6c2d7909005206289354294c6d87cb2f12c8272..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 952 zcmcgr!EVz)5Oo`>me5KZIB=X}kQx`q1zJmpM6MzZKrRv2Ws@wig}rO-u0wm{6Ziy< zd`3Tm0|!2VPvDImqX@y3RbJ-pZ061E&Lq8>?gj1Y!|VgSLVETbG~8(B>_6r`aG2%J z8+!xV_`lDynqEswqllt7iK8UGq|xK(G9kURO3PBwa6?`>zmYaR-I8&5Ppg??ax= z?u_{W`T0sm-Ee{Poq7GQ{GYeEn*R_uze`gm?EZ^SK`;gx<7>?P3vdhk2!crf+y@Bz WaqDmP;FAh~^5iRU3w#592Ei|HSG!06 diff --git a/CIME/non_py/cprnc/test_inputs/vals_differ_by_1.1.nc b/CIME/non_py/cprnc/test_inputs/vals_differ_by_1.1.nc deleted file mode 100644 index ddc52582461243dc558e51d8a3bb8432647ba19a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 808 zcmcgqJ5Iwu6x@)Qgy2XN)QB=A-~+I$pj-fn))=t~D{UO)-K5R|Qc!RJN=lB913*Q? zF{n~A<9H(rDQ#97zwvu-=Y8zK&9EVaAi6-4u+tny9F#oso{e7osKZ7yy}xW zoMD$v$YcNP;T^W^we$0=04;aH`}LW)pZ_y4w>L4)nS7kiGdru6#GN%B2K^)0ZaOs9 z-fe^R7o7Ado&2_uyu=gddinV!W8gMPBssPw zzroj|k%C{H!Ct_JT)6;NLMU)`4oKi0SO71;8{+VlIVX4rSI<=pe}h^9*oyiU;tz|N BaNYm_ diff --git a/CIME/non_py/cprnc/test_inputs/vals_differ_by_1.1_somewhere.nc b/CIME/non_py/cprnc/test_inputs/vals_differ_by_1.1_somewhere.nc deleted file mode 100644 index 703507681b5f8cb41328675947a1dfac19489707..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 808 zcmcgqF-`+95ZsU)gpf!SJb*G~fCq3^P@X`0T!fWaaxUoXq|O5H&~6H?k(8t-`5JL}!M@x!DgB18{p6Lwj_$VuR(C(nS6Hw(=}V_krs0x5?&Tz!B% zTUEw8diHw#XFaR6c7-k-g*!kOp;y?p-PcO*IixX28r8D)b0lI>=L$OS(Z{`kT|o}bUZwV?G+Pa?c-mt z86zK}{wHiV*1rX3tz*{zYSh0wetotZW}SMbrIX(_%Wtq_-ETy4*c@9F%|VA@(ap!XN&*< diff --git a/CIME/non_py/cprnc/test_inputs/vals_differ_by_varying_amounts.nc b/CIME/non_py/cprnc/test_inputs/vals_differ_by_varying_amounts.nc deleted file mode 100644 index f42d2952af261692b0e30de3ebfbe4062755cba5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 808 zcmcgqy-LGS7`?HL#@K>`n}d#>1oZ*Dmq>jB*BJ4(2GX?T-gYjCn}dUkP7WfS#78J} z>J#Wo2rllPlO_^`uKnQTJGtL?&-arAr)@_FLHIz8Nct(9Sj%~2$z7ms9u5>rk+JrhX6s`jvLU-`BWKpZ^XPHK`)JXf8y~aftb#|ch zKwk7o9nP>!r_5vj>|p}GVYRWNCCu_W%yxeQvH z@P62iljQs_SdEdlQ2!m?E9xJCv(_r>e^%-ruf9IV&39eSOwU7pTUQ?92|F1JA)T`~ z8GQKi26*js0) then - dimname_str(1:1) = '(' - dlen=2 - - do j=1,ndims - dimname_str(dlen:) = trim(dims(dimids(j))%name)//',' - dlen=dlen+ len_trim(dims(dimids(j))%name) + 1 - end do - dimname_str(dlen-1:dlen-1) = ')' - end if - - -end subroutine get_dimname_str - -subroutine get_dim_str(ndims,loc,dim_str) - integer, intent(in) :: ndims - integer, intent(in) :: loc(:) - character(len=*),intent(out) :: dim_str - - integer :: dlen - integer :: j - - dim_str = ' ' - - if(ndims>0) then - dim_str(1:1) = '(' - dlen=2 - - do j=1,ndims - write(dim_str(dlen:),'(i6,a)') loc(j),',' - - dlen=len_trim(dim_str)+1 - end do - dim_str(dlen-1:dlen-1) = ')' - end if - - -end subroutine get_dim_str - - - -subroutine checknf90(ierr,returnflag,err_str) - use netcdf, only : nf90_noerr, nf90_strerror - integer, intent(in) :: ierr - logical, optional, intent(in) :: returnflag - character(len=*), optional, intent(in) :: err_str - - if(ierr/=NF90_NOERR) then - print *, trim(nf90_strerror(ierr)) - if(present(err_str)) then - print *, trim(err_str) - end if - if(present(returnflag)) then - if(returnflag) return - end if -#ifdef AIX - call xl__trbk() -#endif - stop - - end if - - - -end subroutine checknf90 - - - - -end module utils diff --git a/Externals_cime.cfg b/Externals_cime.cfg new file mode 100644 index 00000000000..e88e1ca54b2 --- /dev/null +++ b/Externals_cime.cfg @@ -0,0 +1,9 @@ +[cprnc] +tag = v1.0.1 +protocol = git +repo_url = https://github.com/ESMCI/cprnc +local_path = CIME/non_py/cprnc +required = True + +[externals_description] +schema_version = 1.0.01 From 44f038c5de8f3400af8595579ac53461b8f2ce43 Mon Sep 17 00:00:00 2001 From: James Edwards Date: Mon, 27 Nov 2023 19:22:39 -0700 Subject: [PATCH 246/390] add cprnc checkout --- .github/workflows/testing.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index ab759966c93..4f19d1a0d76 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -40,6 +40,10 @@ jobs: uses: actions/setup-python@v2 with: python-version: 3.9 + - name: Checkout cprnc + uses: actions/checkout@v2 + repository: https://github.com/ESMCI/cprnc + path: CIME/non_py/cprnc # Offical action is deprecated in favor of pre-commit.ci # Should evaulate switching or just running manually. # - name: Runs pre-commit action From b0121c699274a2619e6cb42f476e07db8812d394 Mon Sep 17 00:00:00 2001 From: James Edwards Date: Mon, 27 Nov 2023 19:24:28 -0700 Subject: [PATCH 247/390] add cprnc checkout --- .github/workflows/testing.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index 4f19d1a0d76..f0de6cc08d8 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -42,8 +42,9 @@ jobs: python-version: 3.9 - name: Checkout cprnc uses: actions/checkout@v2 - repository: https://github.com/ESMCI/cprnc - path: CIME/non_py/cprnc + with: + repository: https://github.com/ESMCI/cprnc + path: CIME/non_py/cprnc # Offical action is deprecated in favor of pre-commit.ci # Should evaulate switching or just running manually. # - name: Runs pre-commit action From 0572ee592b889a85a1bc87c030aa9e9068a8b2c4 Mon Sep 17 00:00:00 2001 From: James Edwards Date: Tue, 28 Nov 2023 06:00:54 -0700 Subject: [PATCH 248/390] add cprnc checkout --- .github/workflows/testing.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index f0de6cc08d8..f42c732ada6 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -43,7 +43,7 @@ jobs: - name: Checkout cprnc uses: actions/checkout@v2 with: - repository: https://github.com/ESMCI/cprnc + repository: ESMCI/cprnc path: CIME/non_py/cprnc # Offical action is deprecated in favor of pre-commit.ci # Should evaulate switching or just running manually. From e681c948e0e567d0713cc68415190a132e85cf7e Mon Sep 17 00:00:00 2001 From: James Edwards Date: Tue, 28 Nov 2023 07:23:49 -0700 Subject: [PATCH 249/390] need to get cprnc in container --- .github/workflows/testing.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index f42c732ada6..75b44baa7c0 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -40,11 +40,6 @@ jobs: uses: actions/setup-python@v2 with: python-version: 3.9 - - name: Checkout cprnc - uses: actions/checkout@v2 - with: - repository: ESMCI/cprnc - path: CIME/non_py/cprnc # Offical action is deprecated in favor of pre-commit.ci # Should evaulate switching or just running manually. # - name: Runs pre-commit action @@ -94,6 +89,9 @@ jobs: init_cime + # Get cprnc + git clone https://github.com/ESMCI/cprnc CIME/non_py/cprnc + pytest -vvv --cov=CIME --machine docker --no-fortran-run CIME/tests/test_unit* # Run system tests @@ -146,6 +144,8 @@ jobs: init_cime fi + # Get cprnc + git clone https://github.com/ESMCI/cprnc CIME/non_py/cprnc source /opt/conda/etc/profile.d/conda.sh conda activate base From 563967232e75c5d0efaaa5d4a3ca60b219b982f1 Mon Sep 17 00:00:00 2001 From: James Edwards Date: Tue, 28 Nov 2023 08:22:05 -0700 Subject: [PATCH 250/390] need to add CMAKE_Fortran_FLAGS to Makefile --- tools/mapping/gen_domain_files/src/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/mapping/gen_domain_files/src/Makefile b/tools/mapping/gen_domain_files/src/Makefile index 5a12b7daec5..a6fa89bf9bc 100644 --- a/tools/mapping/gen_domain_files/src/Makefile +++ b/tools/mapping/gen_domain_files/src/Makefile @@ -122,7 +122,7 @@ OBJS := gen_domain.o # Append user defined compiler and load flags to Makefile defaults CFLAGS += $(USER_CFLAGS) -I$(INC_NETCDF) -FFLAGS += $(USER_FFLAGS) -I$(MOD_NETCDF) -I$(INC_NETCDF) +FFLAGS += $(USER_FFLAGS) -I$(MOD_NETCDF) -I$(INC_NETCDF) $(CMAKE_Fortran_FLAGS) LDFLAGS += $(USER_LDFLAGS) # Set user specified linker From 58943e86374d8151d1aa2b3aefd116da0bd0d189 Mon Sep 17 00:00:00 2001 From: James Edwards Date: Tue, 28 Nov 2023 08:48:46 -0700 Subject: [PATCH 251/390] correct cprnc directory --- .github/workflows/testing.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index 75b44baa7c0..b32a1dda464 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -145,7 +145,7 @@ jobs: fi # Get cprnc - git clone https://github.com/ESMCI/cprnc CIME/non_py/cprnc + git clone https://github.com/ESMCI/cprnc $INSTALL_PATH/CIME/non_py/cprnc source /opt/conda/etc/profile.d/conda.sh conda activate base From 82149a62db29738e247bdf1807638d8cbb9d7b47 Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Thu, 30 Nov 2023 18:07:30 -0800 Subject: [PATCH 252/390] Updates cprnc buildlib to check /externals for source --- CIME/build_scripts/buildlib.cprnc | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/CIME/build_scripts/buildlib.cprnc b/CIME/build_scripts/buildlib.cprnc index 5e6708da133..51426de27c6 100755 --- a/CIME/build_scripts/buildlib.cprnc +++ b/CIME/build_scripts/buildlib.cprnc @@ -6,7 +6,7 @@ sys.path.append(os.path.join(_CIMEROOT, "CIME", "Tools")) from standard_script_setup import * from CIME import utils -from CIME.utils import run_bld_cmd_ensure_logging +from CIME.utils import run_bld_cmd_ensure_logging, CIMEError from CIME.case import Case from CIME.build import get_standard_cmake_args @@ -63,10 +63,35 @@ def buildlib(bldroot, installpath, case): ) cmake_args = get_standard_cmake_args(case, "ignore_sharedpath") + os.environ["CIMEROOT"] = cimeroot - cmake_cmd = ". ./.env_mach_specific.sh && NETCDF=$(dirname $(dirname $(which nf-config))) cmake {cmake_args} -DMPILIB=mpi-serial -DDEBUG=FALSE -C Macros.cmake {cimeroot}/CIME/non_py/cprnc -DCMAKE_PREFIX_PATH={dest_path} -DBLDROOT={bldroot}".format( - cimeroot=cimeroot, dest_path=installpath, cmake_args=cmake_args, bldroot=bldroot + + srcroot = case.get_value("SRCROOT") + + cprnc_src_root = None + candidate_paths = ( + os.path.join(cimeroot, "CIME/non_py/cprnc"), + os.path.join(srcroot, "externals/cprnc"), ) + + for candidate in candidate_paths: + if os.path.exists(candidate): + cprnc_src_root = candidate + + break + else: + logger.debug("{!r} is not a valid cprnc source path") + + if cprnc_src_root is None: + raise CIMEError("Could not find a valid cprnc source directory") + + cmake_cmd = ". ./.env_mach_specific.sh && NETCDF=$(dirname $(dirname $(which nf-config))) cmake {cmake_args} -DMPILIB=mpi-serial -DDEBUG=FALSE -C Macros.cmake {cprnc_src_root} -DCMAKE_PREFIX_PATH={dest_path} -DBLDROOT={bldroot}".format( + cprnc_src_root=cprnc_src_root, + dest_path=installpath, + cmake_args=cmake_args, + bldroot=bldroot, + ) + run_bld_cmd_ensure_logging(cmake_cmd, logger, from_dir=bldroot) gmake_cmd = case.get_value("GMAKE") From 2c54a3275ec02589b83a59f229498975ce5a12f5 Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Thu, 30 Nov 2023 18:09:38 -0800 Subject: [PATCH 253/390] Removes cprnc clone to ensure each module correctly uses their own mechanism to checkout --- .github/workflows/testing.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index b32a1dda464..397b7e2d691 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -145,7 +145,7 @@ jobs: fi # Get cprnc - git clone https://github.com/ESMCI/cprnc $INSTALL_PATH/CIME/non_py/cprnc + # git clone https://github.com/ESMCI/cprnc $INSTALL_PATH/CIME/non_py/cprnc source /opt/conda/etc/profile.d/conda.sh conda activate base From c73d5be5f806d623f82a882ebf2c3823a1fcc236 Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Fri, 1 Dec 2023 12:28:38 -0800 Subject: [PATCH 254/390] Adds cprnc as submodule --- .gitmodules | 3 +++ CIME/non_py/cprnc | 1 + 2 files changed, 4 insertions(+) create mode 160000 CIME/non_py/cprnc diff --git a/.gitmodules b/.gitmodules index e69de29bb2d..6114b9d8449 100644 --- a/.gitmodules +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "CIME/non_py/cprnc"] + path = CIME/non_py/cprnc + url = git@github.com:ESMCI/cprnc.git diff --git a/CIME/non_py/cprnc b/CIME/non_py/cprnc new file mode 160000 index 00000000000..d17494561ac --- /dev/null +++ b/CIME/non_py/cprnc @@ -0,0 +1 @@ +Subproject commit d17494561ace91fc86387c83591c0382a2c79566 From 3654a9c10f5dc2324c54ae8c54dbd921bd1b6943 Mon Sep 17 00:00:00 2001 From: James Edwards Date: Fri, 1 Dec 2023 15:37:00 -0700 Subject: [PATCH 255/390] try this --- .gitmodules | 3 --- CIME/non_py/cprnc | 1 - docker/entrypoint.sh | 4 ++++ 3 files changed, 4 insertions(+), 4 deletions(-) delete mode 160000 CIME/non_py/cprnc diff --git a/.gitmodules b/.gitmodules index 6114b9d8449..e69de29bb2d 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +0,0 @@ -[submodule "CIME/non_py/cprnc"] - path = CIME/non_py/cprnc - url = git@github.com:ESMCI/cprnc.git diff --git a/CIME/non_py/cprnc b/CIME/non_py/cprnc deleted file mode 160000 index d17494561ac..00000000000 --- a/CIME/non_py/cprnc +++ /dev/null @@ -1 +0,0 @@ -Subproject commit d17494561ace91fc86387c83591c0382a2c79566 diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index c89d6a9d375..439510c01a2 100755 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -135,6 +135,10 @@ function init_cesm() { update_cime "${install_path}/cime/" cd "${install_path}/cime" +# Need to run manage_externals again incase branch changes externals instructions + "${install_path}/manage_externals/checkout_externals -e Externals_cime.cfg" + + } ####################################### From 2530fe45827bc53b335fadc31e4e8b9baabe463d Mon Sep 17 00:00:00 2001 From: James Edwards Date: Fri, 1 Dec 2023 15:56:20 -0700 Subject: [PATCH 256/390] need this in init_cime too --- docker/entrypoint.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index 439510c01a2..f4c5c86e1d6 100755 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -168,6 +168,8 @@ function init_cime() { update_cime "${install_path}" cd "${install_path}" + + "/src/CESM/manage_externals/checkout_externals -e Externals_cime.cfg" } if [[ ! -e "${HOME}/.cime" ]] From aa30ec6caaa697ec3ecced468c9502eff3050462 Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Fri, 1 Dec 2023 14:56:53 -0800 Subject: [PATCH 257/390] Adds cprnc submodule --- .gitmodules | 3 +++ CIME/non_py/cprnc | 1 + 2 files changed, 4 insertions(+) create mode 160000 CIME/non_py/cprnc diff --git a/.gitmodules b/.gitmodules index e69de29bb2d..13f9ecb952f 100644 --- a/.gitmodules +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "CIME/non_py/cprnc"] + path = CIME/non_py/cprnc + url = git@github.com:ESMCI/cprnc diff --git a/CIME/non_py/cprnc b/CIME/non_py/cprnc new file mode 160000 index 00000000000..d17494561ac --- /dev/null +++ b/CIME/non_py/cprnc @@ -0,0 +1 @@ +Subproject commit d17494561ace91fc86387c83591c0382a2c79566 From e367c84c003542f8266427f3685255a7fd378d30 Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Fri, 1 Dec 2023 14:57:46 -0800 Subject: [PATCH 258/390] Updates init_e3sm to recursively init submodules --- docker/entrypoint.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index 439510c01a2..39d19555749 100755 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -99,7 +99,7 @@ function init_e3sm() { extras=" --depth 1" fi - git submodule update --init ${extras} + git submodule update --init --recursive ${extras} fi fixup_mct "${install_path}/externals/mct" From 442530495f7d1f3cab7316833e0055cce5fb0919 Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Fri, 1 Dec 2023 14:58:10 -0800 Subject: [PATCH 259/390] Fixes running checkout_externals in init_cime --- docker/entrypoint.sh | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index 39d19555749..b5eaece7292 100755 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -135,10 +135,9 @@ function init_cesm() { update_cime "${install_path}/cime/" cd "${install_path}/cime" -# Need to run manage_externals again incase branch changes externals instructions + + # Need to run manage_externals again incase branch changes externals instructions "${install_path}/manage_externals/checkout_externals -e Externals_cime.cfg" - - } ####################################### @@ -168,6 +167,9 @@ function init_cime() { update_cime "${install_path}" cd "${install_path}" + # + # Need to run manage_externals again incase branch changes externals instructions + "${install_path}/manage_externals/checkout_externals -e Externals_cime.cfg" } if [[ ! -e "${HOME}/.cime" ]] From 686acf2f42471e885fcaf4ebab9948b7bcf3a118 Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Fri, 1 Dec 2023 16:06:33 -0800 Subject: [PATCH 260/390] Updates container entrypoint.sh --- docker/entrypoint.sh | 39 +++++++++++++++++++++++++++++++++++---- 1 file changed, 35 insertions(+), 4 deletions(-) diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index b5eaece7292..be97b70ab85 100755 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -1,6 +1,9 @@ #!/bin/bash -set -x +if [[ -n "${DEBUG}" ]] +then + set -x +fi readonly INIT=${INIT:-"true"} readonly UPDATE_CIME=${UPDATE_CIME:-"false"} @@ -24,6 +27,8 @@ function clone_repo() { extras="${extras} --depth 1" fi + echo "Cloning branch ${branch} of ${repo} into ${path} using ${flags}" + git clone -b "${branch}" ${extras} "${repo}" "${path}" || true } @@ -38,11 +43,15 @@ function fixup_mct { # TODO make PR to fix if [[ ! -e "${mct_path}/mct/Makefile.bak" ]] then + echo "Fixing AR variable in ${mct_path}/mct/Makefile" + sed -i".bak" "s/\$(AR)/\$(AR) \$(ARFLAGS)/g" "${mct_path}/mct/Makefile" fi if [[ ! -e "${mct_path}/mpeu/Makefile.bak" ]] then + echo "Fixing AR variable in ${mct_path}/mpeu/Makefile" + sed -i".bak" "s/\$(AR)/\$(AR) \$(ARFLAGS)/g" "${mct_path}/mpeu/Makefile" fi } @@ -77,6 +86,8 @@ function update_cime() { # Creates an environment with E3SM source. ####################################### function init_e3sm() { + echo "Setting up E3SM" + export CIME_MODEL="e3sm" local extras="" @@ -91,6 +102,8 @@ function init_e3sm() { if [[ ! -e "${PWD}/.gitmodules.bak" ]] then + echo "Convering git@github.com to https://github.com urls in ${PWD}/.gitmodules" + sed -i".bak" "s/git@github.com:/https:\/\/github.com\//g" "${PWD}/.gitmodules" fi @@ -99,7 +112,17 @@ function init_e3sm() { extras=" --depth 1" fi - git submodule update --init --recursive ${extras} + echo "Initializing submodules in ${PWD}" + + git submodule update --init ${extras} + + pushd cime/ + + echo "Initializing submodules in ${PWD}" + + git submodule update --init ${extras} + + popd fi fixup_mct "${install_path}/externals/mct" @@ -108,6 +131,8 @@ function init_e3sm() { mkdir -p /storage/inputdata + echo "Copying cached inputdata from /cache to /storage/inputdata" + rsync -vr /cache/ /storage/inputdata/ cd "${install_path}/cime" @@ -117,6 +142,8 @@ function init_e3sm() { # Creates an environment with CESM source. ####################################### function init_cesm() { + echo "Setting up CESM" + export CIME_MODEL="cesm" local install_path="${INSTALL_PATH:-/src/CESM}" @@ -128,6 +155,8 @@ function init_cesm() { cd "${install_path}" + echo "Checking out externals" + "${install_path}/manage_externals/checkout_externals" fixup_mct "${install_path}/libraries/mct" @@ -135,7 +164,7 @@ function init_cesm() { update_cime "${install_path}/cime/" cd "${install_path}/cime" - + # Need to run manage_externals again incase branch changes externals instructions "${install_path}/manage_externals/checkout_externals -e Externals_cime.cfg" } @@ -145,6 +174,8 @@ function init_cesm() { # Similar to old github actions environment. ####################################### function init_cime() { + echo "Settig up CIME" + export CIME_MODEL="cesm" export ESMFMKFILE="/opt/conda/lib/esmf.mk" @@ -167,7 +198,7 @@ function init_cime() { update_cime "${install_path}" cd "${install_path}" - # + # Need to run manage_externals again incase branch changes externals instructions "${install_path}/manage_externals/checkout_externals -e Externals_cime.cfg" } From cef9f89bd13f3c1a9b9a20bc41d5edfbd3de8ad2 Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Fri, 1 Dec 2023 16:31:07 -0800 Subject: [PATCH 261/390] Fixes black formatting --- docker/entrypoint.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index 856e3aaffeb..496ede2a20a 100755 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -131,7 +131,7 @@ function init_e3sm() { mkdir -p /storage/inputdata - echo "Copying cached inputdata from /cache to /storage/inputdata" + echo "Copying cached inputdata from /cache to /storage/inputdata" rsync -vr /cache/ /storage/inputdata/ From 7c7b91466961bff9bb6b9a55eb4c0a30595a409f Mon Sep 17 00:00:00 2001 From: James Edwards Date: Fri, 1 Dec 2023 17:47:23 -0700 Subject: [PATCH 262/390] use cprnc as a submodule --- Externals_cime.cfg | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/Externals_cime.cfg b/Externals_cime.cfg index e88e1ca54b2..04d7306e241 100644 --- a/Externals_cime.cfg +++ b/Externals_cime.cfg @@ -1,9 +1,7 @@ -[cprnc] -tag = v1.0.1 +[CIME/non_py/cprnc] protocol = git -repo_url = https://github.com/ESMCI/cprnc -local_path = CIME/non_py/cprnc +from_submodule=True required = True [externals_description] -schema_version = 1.0.01 +schema_version = 1.0.0 From 195109161b3af32c4d17673e6f34805d0108c053 Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Fri, 1 Dec 2023 16:51:27 -0800 Subject: [PATCH 263/390] Updates entrypoint.sh --- docker/entrypoint.sh | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index 496ede2a20a..9eb72342ac3 100755 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -115,14 +115,6 @@ function init_e3sm() { echo "Initializing submodules in ${PWD}" git submodule update --init ${extras} - - pushd cime/ - - echo "Initializing submodules in ${PWD}" - - git submodule update --init ${extras} - - popd fi fixup_mct "${install_path}/externals/mct" @@ -136,6 +128,10 @@ function init_e3sm() { rsync -vr /cache/ /storage/inputdata/ cd "${install_path}/cime" + + echo "Initializing submodules in ${PWD}" + + git submodule update --init ${extras} } ####################################### @@ -166,7 +162,7 @@ function init_cesm() { cd "${install_path}/cime" # Need to run manage_externals again incase branch changes externals instructions - "${install_path}/manage_externals/checkout_externals -e Externals_cime.cfg" + "/src/CESM/manage_externals/checkout_externals -e Externals_cime.cfg" } ####################################### @@ -199,6 +195,7 @@ function init_cime() { cd "${install_path}" + # Need to run manage_externals again incase branch changes externals instructions "/src/CESM/manage_externals/checkout_externals -e Externals_cime.cfg" } From 4092e2f858466eda04d2645b7006d9504ecfe410 Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Fri, 1 Dec 2023 17:32:47 -0800 Subject: [PATCH 264/390] Temporarily replace checkout_externals --- docker/entrypoint.sh | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index 9eb72342ac3..e57ed141718 100755 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -129,6 +129,13 @@ function init_e3sm() { cd "${install_path}/cime" + if [[ ! -e "${PWD}/.gitmodules.bak" ]] + then + echo "Convering git@github.com to https://github.com urls in ${PWD}/.gitmodules" + + sed -i".bak" "s/git@github.com:/https:\/\/github.com\//g" "${PWD}/.gitmodules" + fi + echo "Initializing submodules in ${PWD}" git submodule update --init ${extras} @@ -162,7 +169,16 @@ function init_cesm() { cd "${install_path}/cime" # Need to run manage_externals again incase branch changes externals instructions - "/src/CESM/manage_externals/checkout_externals -e Externals_cime.cfg" + # "${install_path}/manage_externals/checkout_externals -e cime/Externals_cime.cfg" + + if [[ ! -e "${PWD}/.gitmodules.bak" ]] + then + echo "Convering git@github.com to https://github.com urls in ${PWD}/.gitmodules" + + sed -i".bak" "s/git@github.com:/https:\/\/github.com\//g" "${PWD}/.gitmodules" + fi + + git submodule update --init } ####################################### @@ -196,7 +212,16 @@ function init_cime() { cd "${install_path}" # Need to run manage_externals again incase branch changes externals instructions - "/src/CESM/manage_externals/checkout_externals -e Externals_cime.cfg" + # "${install_path}/manage_externals/checkout_externals -e cime/Externals_cime.cfg" + + if [[ ! -e "${PWD}/.gitmodules.bak" ]] + then + echo "Convering git@github.com to https://github.com urls in ${PWD}/.gitmodules" + + sed -i".bak" "s/git@github.com:/https:\/\/github.com\//g" "${PWD}/.gitmodules" + fi + + git submodule update --init } if [[ ! -e "${HOME}/.cime" ]] From 35e15b0ddaee07889597a606c77bd6580505d9ef Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Fri, 1 Dec 2023 18:00:44 -0800 Subject: [PATCH 265/390] Fixes clone cprnc after its been already present from submodule --- .github/workflows/testing.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index 397b7e2d691..5744552646c 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -89,9 +89,6 @@ jobs: init_cime - # Get cprnc - git clone https://github.com/ESMCI/cprnc CIME/non_py/cprnc - pytest -vvv --cov=CIME --machine docker --no-fortran-run CIME/tests/test_unit* # Run system tests From d6a3da2c0f90f5e55fad65dc78fcd652539e198d Mon Sep 17 00:00:00 2001 From: James Edwards Date: Mon, 4 Dec 2023 12:39:31 -0700 Subject: [PATCH 266/390] fix for test_gen_domain --- .../gen_domain_files/test_gen_domain.sh | 43 +++++++++---------- 1 file changed, 21 insertions(+), 22 deletions(-) diff --git a/tools/mapping/gen_domain_files/test_gen_domain.sh b/tools/mapping/gen_domain_files/test_gen_domain.sh index b80d482ce13..06e995a8c62 100755 --- a/tools/mapping/gen_domain_files/test_gen_domain.sh +++ b/tools/mapping/gen_domain_files/test_gen_domain.sh @@ -81,12 +81,11 @@ fi # Build the cprnc executable (for comparison of netcdf files) echo "" >> ${test_log} echo "Building cprnc in ${PWD}/builds ..." >> ${test_log} -cp ${cime_root}/CIME/non_py/cprnc/*.F90 . -cp ${cime_root}/CIME/non_py/cprnc/Makefile . -cp ${cime_root}/CIME/non_py/cprnc/Depends . -cp ${cime_root}/CIME/non_py/cprnc/*.in . -(. .env_mach_specific.sh && make GENF90=${cime_root}/CIME/non_py/externals/genf90/genf90.pl) >> ${test_log} 2>&1 -if [ ! -f cprnc ]; then +mkdir ${PWD}/builds/cprnc +cd ${PWD}/builds/cprnc +cmake -DCMAKE_INSTALL_PREFIX=${PWD} ${cime_root}/CIME/non_py/cprnc +make install +if [ ! -f bin/cprnc ]; then echo "ERROR building cprnc" >&2 echo "cat ${test_log} for more info" >&2 exit 1 @@ -104,33 +103,33 @@ for baseline in ${ocn_baseline} ${lnd_baseline}; do # and adding in datestring for current day and .nc file extension. testfile=`basename ${baseline} | rev | cut -d. -f3- | rev`.${datestring}.nc if [ ! -f ${testfile} ]; then - echo "ERROR: ${testfile} not generated" >&2 - echo "cat ${test_log} for more info" >&2 - exit 1 + echo "ERROR: ${testfile} not generated" >&2 + echo "cat ${test_log} for more info" >&2 + exit 1 fi # Compare against baseline and print report from cprnc comparison echo "Comparing $testfile against ${baseline}..." - (. builds/.env_mach_specific.sh && ./builds/cprnc -m ${testfile} ${baseline}) >> ${test_log} 2>&1 + (. builds/.env_mach_specific.sh && ./builds/bin/cprnc -m ${testfile} ${baseline}) >> ${test_log} 2>&1 # Check results last=`tail -n3 ${test_log}` if [[ ${last} =~ "STOP" ]]; then - echo ${last} >&2 - echo "Error running cprnc" >&2 - echo "cat ${test_log} for more info" >&2 - exit 1 + echo ${last} >&2 + echo "Error running cprnc" >&2 + echo "cat ${test_log} for more info" >&2 + exit 1 fi if [[ ${last} =~ "DIFFERENT" ]]; then - echo ${last} >&2 - echo ${baseline} DIFFERENT FROM ${testfile} >&2 - echo "cat ${test_log} for more info" >&2 - exit 1 + echo ${last} >&2 + echo ${baseline} DIFFERENT FROM ${testfile} >&2 + echo "cat ${test_log} for more info" >&2 + exit 1 fi if ! [[ ${last} =~ "IDENTICAL" ]]; then - echo ${last} >&2 - echo "undetermined output from cprnc" >&2 - echo "cat ${test_log} for more info" >&2 - exit 1 + echo ${last} >&2 + echo "undetermined output from cprnc" >&2 + echo "cat ${test_log} for more info" >&2 + exit 1 fi done From ef22a0c4ac45e926850ccbfc2f1976455d5c750b Mon Sep 17 00:00:00 2001 From: Azamat Mametjanov Date: Mon, 4 Dec 2023 17:32:32 -0600 Subject: [PATCH 267/390] Return latest value in read_baseline_file Also add black formatting and update unit tests. --- CIME/baselines/performance.py | 12 ++++--- CIME/tests/test_unit_baselines_performance.py | 33 ++++++++----------- CIME/tests/test_unit_system_tests.py | 10 +++--- 3 files changed, 25 insertions(+), 30 deletions(-) diff --git a/CIME/baselines/performance.py b/CIME/baselines/performance.py index 71d357cedf6..98bb170436d 100644 --- a/CIME/baselines/performance.py +++ b/CIME/baselines/performance.py @@ -428,9 +428,11 @@ def read_baseline_file(baseline_file): Value stored in baseline file without comments. """ with open(baseline_file) as fd: - lines = [x.strip().split(" ")[-1] for x in fd.readlines() if not x.startswith("#")] + lines = [ + x.strip().split(" ")[-1] for x in fd.readlines() if not x.startswith("#") + ] - return "\n".join(lines) + return lines[-1] def _perf_compare_throughput_baseline(case, baseline, tolerance): @@ -474,7 +476,7 @@ def _perf_compare_throughput_baseline(case, baseline, tolerance): below_tolerance = diff < tolerance info = "Throughput changed by {:.2f}%: baseline={:.3f} sypd, tolerance={:d}%, current={:.3f} sypd".format( - diff * 100, baseline, int(tolerance * 100), current + diff * 100, baseline, int(tolerance * 100), current ) if below_tolerance: comment = "TPUTCOMP: " + info @@ -531,8 +533,8 @@ def _perf_compare_memory_baseline(case, baseline, tolerance): if diff is not None: below_tolerance = diff < tolerance - info = "Memory usage highwater changed by {:.2f}%: baseline={:.3f} sypd, tolerance={:d}%, current={:.3f} sypd".format( - diff * 100, baseline, int(tolerance * 100), current + info = "Memory usage highwater changed by {:.2f}%: baseline={:.3f} MB, tolerance={:d}%, current={:.3f} MB".format( + diff * 100, baseline, int(tolerance * 100), current ) if below_tolerance: comment = "MEMCOMP: " + info diff --git a/CIME/tests/test_unit_baselines_performance.py b/CIME/tests/test_unit_baselines_performance.py index 1422f3412b8..02ed39dc688 100644 --- a/CIME/tests/test_unit_baselines_performance.py +++ b/CIME/tests/test_unit_baselines_performance.py @@ -155,35 +155,29 @@ def test_get_cpl_mem_usage(self, isfile): def test_read_baseline_file_multi_line(self): with mock.patch( "builtins.open", - mock.mock_open(read_data="#comment about data\n1000.0\n2000.0\n"), + mock.mock_open( + read_data="sha:1df0 date:2023 1000.0\nsha:3b05 date:2023 2000.0" + ), ) as mock_file: baseline = performance.read_baseline_file("/tmp/cpl-mem.log") mock_file.assert_called_with("/tmp/cpl-mem.log") - assert baseline == "1000.0\n2000.0" + assert baseline == "2000.0" def test_read_baseline_file_content(self): with mock.patch( - "builtins.open", mock.mock_open(read_data="1000.0") + "builtins.open", mock.mock_open(read_data="sha:1df0 date:2023 1000.0") ) as mock_file: baseline = performance.read_baseline_file("/tmp/cpl-mem.log") mock_file.assert_called_with("/tmp/cpl-mem.log") assert baseline == "1000.0" - def test_read_baseline_file(self): - with mock.patch("builtins.open", mock.mock_open(read_data="")) as mock_file: - baseline = performance.read_baseline_file("/tmp/cpl-mem.log") - - mock_file.assert_called_with("/tmp/cpl-mem.log") - assert baseline == "" - def test_write_baseline_file(self): with mock.patch("builtins.open", mock.mock_open()) as mock_file: performance.write_baseline_file("/tmp/cpl-tput.log", "1000") - mock_file.assert_called_with("/tmp/cpl-tput.log", "w") - mock_file.return_value.write.assert_called_with("1000") + mock_file.assert_called_with("/tmp/cpl-tput.log", "a") @mock.patch("CIME.baselines.performance.get_cpl_throughput") @mock.patch("CIME.baselines.performance.get_latest_cpl_logs") @@ -368,7 +362,7 @@ def test_perf_compare_throughput_baseline_no_tolerance( assert below_tolerance assert ( comment - == "TPUTCOMP: Computation time changed by -0.80% relative to baseline" + == "TPUTCOMP: Throughput changed by -0.80%: baseline=500.000 sypd, tolerance=10%, current=504.000 sypd" ) @mock.patch("CIME.baselines.performance._perf_get_throughput") @@ -399,7 +393,8 @@ def test_perf_compare_throughput_baseline_above_threshold( assert not below_tolerance assert ( - comment == "Error: TPUTCOMP: Computation time increase > 5% from baseline" + comment + == "Error: TPUTCOMP: Throughput changed by 49.60%: baseline=1000.000 sypd, tolerance=5%, current=504.000 sypd" ) @mock.patch("CIME.baselines.performance._perf_get_throughput") @@ -431,7 +426,7 @@ def test_perf_compare_throughput_baseline( assert below_tolerance assert ( comment - == "TPUTCOMP: Computation time changed by -0.80% relative to baseline" + == "TPUTCOMP: Throughput changed by -0.80%: baseline=500.000 sypd, tolerance=5%, current=504.000 sypd" ) @mock.patch("CIME.baselines.performance.get_cpl_mem_usage") @@ -466,7 +461,7 @@ def test_perf_compare_memory_baseline_no_baseline( assert below_tolerance assert ( comment - == "MEMCOMP: Memory usage highwater has changed by 0.00% relative to baseline" + == "MEMCOMP: Memory usage highwater changed by 0.00%: baseline=0.000 MB, tolerance=5%, current=1003.000 MB" ) @mock.patch("CIME.baselines.performance.get_cpl_mem_usage") @@ -557,7 +552,7 @@ def test_perf_compare_memory_baseline_no_tolerance( assert below_tolerance assert ( comment - == "MEMCOMP: Memory usage highwater has changed by 0.30% relative to baseline" + == "MEMCOMP: Memory usage highwater changed by 0.30%: baseline=1000.000 MB, tolerance=10%, current=1003.000 MB" ) @mock.patch("CIME.baselines.performance.get_cpl_mem_usage") @@ -592,7 +587,7 @@ def test_perf_compare_memory_baseline_above_threshold( assert not below_tolerance assert ( comment - == "Error: Memory usage increase >5% from baseline's 1000.000000 to 2003.000000" + == "Error: MEMCOMP: Memory usage highwater changed by 100.30%: baseline=1000.000 MB, tolerance=5%, current=2003.000 MB" ) @mock.patch("CIME.baselines.performance.get_cpl_mem_usage") @@ -627,7 +622,7 @@ def test_perf_compare_memory_baseline( assert below_tolerance assert ( comment - == "MEMCOMP: Memory usage highwater has changed by 0.30% relative to baseline" + == "MEMCOMP: Memory usage highwater changed by 0.30%: baseline=1000.000 MB, tolerance=5%, current=1003.000 MB" ) def test_get_latest_cpl_logs_found_multiple(self): diff --git a/CIME/tests/test_unit_system_tests.py b/CIME/tests/test_unit_system_tests.py index 609460fe9c0..22825fd5559 100644 --- a/CIME/tests/test_unit_system_tests.py +++ b/CIME/tests/test_unit_system_tests.py @@ -509,16 +509,14 @@ def test_generate_baseline(self): with open(baseline_dir / "cpl-tput.log") as fd: lines = fd.readlines() - assert len(lines) == 2 - assert re.match("# sha:.* date:.*", lines[0]) - assert lines[1] == "719.635" + assert len(lines) == 1 + assert re.match("sha:.* date:.* (\d+\.\d+)", lines[0]) with open(baseline_dir / "cpl-mem.log") as fd: lines = fd.readlines() - assert len(lines) == 2 - assert re.match("# sha:.* date:.*", lines[0]) - assert lines[1] == "1673.89" + assert len(lines) == 1 + assert re.match("sha:.* date:.* (\d+\.\d+)", lines[0]) def test_kwargs(self): case = mock.MagicMock() From ce0ae658b50656c7df15c49030dcb6424bbf7a7d Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Tue, 5 Dec 2023 15:59:47 -0800 Subject: [PATCH 268/390] Fixes customizing baselines --- CIME/SystemTests/system_tests_common.py | 4 +- CIME/baselines/performance.py | 148 +++++++++++++----- CIME/tests/test_unit_baselines_performance.py | 83 ++++------ CIME/tests/test_unit_system_tests.py | 24 +-- doc/source/users_guide/testing.rst | 8 +- 5 files changed, 156 insertions(+), 111 deletions(-) diff --git a/CIME/SystemTests/system_tests_common.py b/CIME/SystemTests/system_tests_common.py index a9c61b28c24..568f3b3647d 100644 --- a/CIME/SystemTests/system_tests_common.py +++ b/CIME/SystemTests/system_tests_common.py @@ -28,7 +28,7 @@ from CIME.locked_files import LOCKED_DIR, lock_file, is_locked from CIME.baselines.performance import ( get_latest_cpl_logs, - _perf_get_memory, + perf_get_memory_list, perf_compare_memory_baseline, perf_compare_throughput_baseline, perf_write_baseline, @@ -806,7 +806,7 @@ def perf_check_for_memory_leak(case, tolerance): for cpllog in latestcpllogs: try: - memlist = _perf_get_memory(case, cpllog) + memlist = perf_get_memory_list(case, cpllog) except RuntimeError: return False, "insufficient data for memleak test" diff --git a/CIME/baselines/performance.py b/CIME/baselines/performance.py index 98bb170436d..ad7895f3eff 100644 --- a/CIME/baselines/performance.py +++ b/CIME/baselines/performance.py @@ -121,25 +121,25 @@ def perf_write_baseline(case, basegen_dir, throughput=True, memory=True): if throughput: try: - tput = perf_get_throughput(case, config) + tput, mode = perf_get_throughput(case, config) except RuntimeError as e: logger.debug("Could not get throughput: {0!s}".format(e)) else: baseline_file = os.path.join(basegen_dir, "cpl-tput.log") - write_baseline_file(baseline_file, tput) + write_baseline_file(baseline_file, tput, mode) logger.info("Updated throughput baseline to {!s}".format(tput)) if memory: try: - mem = perf_get_memory(case, config) + mem, mode = perf_get_memory(case, config) except RuntimeError as e: logger.info("Could not get memory usage: {0!s}".format(e)) else: baseline_file = os.path.join(basegen_dir, "cpl-mem.log") - write_baseline_file(baseline_file, mem) + write_baseline_file(baseline_file, mem, mode) logger.info("Updated memory usage baseline to {!s}".format(mem)) @@ -184,16 +184,11 @@ def perf_get_throughput(case, config): Model throughput. """ try: - tput = config.perf_get_throughput(case) + tput, mode = config.perf_get_throughput(case) except AttributeError: - tput = _perf_get_throughput(case) + tput, mode = _perf_get_throughput(case) - if tput is None: - raise RuntimeError("Could not get default throughput") from None - - tput = str(tput) - - return tput + return tput, mode def perf_get_memory(case, config): @@ -215,19 +210,14 @@ def perf_get_memory(case, config): Model memory usage. """ try: - mem = config.perf_get_memory(case) + mem, mode = config.perf_get_memory(case) except AttributeError: - mem = _perf_get_memory(case) - - if mem is None: - raise RuntimeError("Could not get default memory usage") from None - - mem = str(mem[-1][1]) + mem, mode = _perf_get_memory(case) - return mem + return mem, mode -def write_baseline_file(baseline_file, value): +def write_baseline_file(baseline_file, value, mode="a"): """ Writes value to `baseline_file`. @@ -237,13 +227,11 @@ def write_baseline_file(baseline_file, value): Path to the baseline file. value : str Value to write. + mode : str + Mode to open file with. """ - commit_hash = get_current_commit(repo=get_src_root()) - - timestamp = get_timestamp(timestamp_format="%Y-%m-%d_%H:%M:%S") - - with open(baseline_file, "a") as fd: - fd.write(f"sha:{commit_hash} date:{timestamp} {value}\n") + with open(baseline_file, mode) as fd: + fd.write(value) def _perf_get_memory(case, cpllog=None): @@ -269,6 +257,17 @@ def _perf_get_memory(case, cpllog=None): RuntimeError If not enough sample were found. """ + memlist = perf_get_memory_list(case, cpllog) + + if memlist is None: + raise RuntimeError("Could not get default memory usage") from None + + value = _format_baseline(memlist[-1][1]) + + return value, "a" + + +def perf_get_memory_list(case, cpllog): if cpllog is None: cpllog = get_latest_cpl_logs(case) else: @@ -316,7 +315,12 @@ def _perf_get_throughput(case): logger.debug("Could not parse throughput from coupler log") - return tput + if tput is None: + raise RuntimeError("Could not get default throughput") from None + + value = _format_baseline(tput) + + return value, "a" def get_latest_cpl_logs(case): @@ -428,11 +432,9 @@ def read_baseline_file(baseline_file): Value stored in baseline file without comments. """ with open(baseline_file) as fd: - lines = [ - x.strip().split(" ")[-1] for x in fd.readlines() if not x.startswith("#") - ] + lines = [x.strip() for x in fd.readlines() if not x.startswith("#") and x != ""] - return lines[-1] + return "\n".join(lines) def _perf_compare_throughput_baseline(case, baseline, tolerance): @@ -457,13 +459,20 @@ def _perf_compare_throughput_baseline(case, baseline, tolerance): comment : str provides explanation from comparison. """ - current = _perf_get_throughput(case) + current, _ = _perf_get_throughput(case) + + try: + current = float(_parse_baseline(current)) + except (ValueError, TypeError): + comment = "Could not compare throughput to baseline, as baseline had no value." + + return None, comment try: # default baseline is stored as single float - baseline = float(baseline) - except ValueError: - comment = "Could not compare throughput to baseline, as basline had no value." + baseline = float(_parse_baseline(baseline)) + except (ValueError, TypeError): + comment = "Could not compare throughput to baseline, as baseline had no value." return None, comment @@ -509,16 +518,21 @@ def _perf_compare_memory_baseline(case, baseline, tolerance): provides explanation from comparison. """ try: - current = _perf_get_memory(case) + current, _ = _perf_get_memory(case) except RuntimeError as e: return None, str(e) - else: - current = current[-1][1] + + try: + current = float(_parse_baseline(current)) + except (ValueError, TypeError): + comment = "Could not compare throughput to baseline, as baseline had no value." + + return None, comment try: # default baseline is stored as single float - baseline = float(baseline) - except ValueError: + baseline = float(_parse_baseline(baseline)) + except (ValueError, TypeError): baseline = 0.0 try: @@ -542,3 +556,55 @@ def _perf_compare_memory_baseline(case, baseline, tolerance): comment = "Error: MEMCOMP: " + info return below_tolerance, comment + + +def _format_baseline(value): + """ + Encodes value with default baseline format. + + Default format: + sha: date: + + Parameters + ---------- + value : str + Baseline value to encode. + + Returns + ------- + value : str + Baseline entry. + """ + commit_hash = get_current_commit(repo=get_src_root()) + + timestamp = get_timestamp(timestamp_format="%Y-%m-%d_%H:%M:%S") + + return f"sha:{commit_hash} date:{timestamp} {value}\n" + + +def _parse_baseline(data): + """ + Parses default baseline format. + + Default format: + sha: date: + + Parameters + ---------- + data : str + Containing contents of baseline file. + + Returns + ------- + value : str + Value of the latest blessed baseline. + """ + lines = data.split("\n") + lines = [x for x in lines if x != ""] + + try: + value = lines[-1].strip().split(" ")[-1] + except IndexError: + value = None + + return value diff --git a/CIME/tests/test_unit_baselines_performance.py b/CIME/tests/test_unit_baselines_performance.py index 02ed39dc688..1564541ba9a 100644 --- a/CIME/tests/test_unit_baselines_performance.py +++ b/CIME/tests/test_unit_baselines_performance.py @@ -28,22 +28,9 @@ def create_mock_case(tempdir, get_latest_cpl_logs=None): class TestUnitBaselinesPerformance(unittest.TestCase): - @mock.patch("CIME.baselines.performance._perf_get_memory") - def test_perf_get_memory_default_no_value(self, _perf_get_memory): - _perf_get_memory.return_value = None - - case = mock.MagicMock() - - config = mock.MagicMock() - - config.perf_get_memory.side_effect = AttributeError - - with self.assertRaises(RuntimeError): - performance.perf_get_memory(case, config) - @mock.patch("CIME.baselines.performance._perf_get_memory") def test_perf_get_memory_default(self, _perf_get_memory): - _perf_get_memory.return_value = [(1, 1000)] + _perf_get_memory.return_value = ("1000", "a") case = mock.MagicMock() @@ -53,35 +40,22 @@ def test_perf_get_memory_default(self, _perf_get_memory): mem = performance.perf_get_memory(case, config) - assert mem == "1000" + assert mem == ("1000", "a") def test_perf_get_memory(self): case = mock.MagicMock() config = mock.MagicMock() - config.perf_get_memory.return_value = "1000" + config.perf_get_memory.return_value = ("1000", "a") mem = performance.perf_get_memory(case, config) - assert mem == "1000" - - @mock.patch("CIME.baselines.performance._perf_get_throughput") - def test_perf_get_throughput_default_no_value(self, _perf_get_throughput): - _perf_get_throughput.return_value = None - - case = mock.MagicMock() - - config = mock.MagicMock() - - config.perf_get_throughput.side_effect = AttributeError - - with self.assertRaises(RuntimeError): - performance.perf_get_throughput(case, config) + assert mem == ("1000", "a") @mock.patch("CIME.baselines.performance._perf_get_throughput") def test_perf_get_throughput_default(self, _perf_get_throughput): - _perf_get_throughput.return_value = 100 + _perf_get_throughput.return_value = ("100", "a") case = mock.MagicMock() @@ -91,18 +65,18 @@ def test_perf_get_throughput_default(self, _perf_get_throughput): tput = performance.perf_get_throughput(case, config) - assert tput == "100" + assert tput == ("100", "a") def test_perf_get_throughput(self): case = mock.MagicMock() config = mock.MagicMock() - config.perf_get_throughput.return_value = "100" + config.perf_get_throughput.return_value = ("100", "a") tput = performance.perf_get_throughput(case, config) - assert tput == "100" + assert tput == ("100", "a") def test_get_cpl_throughput_no_file(self): throughput = performance.get_cpl_throughput("/tmp/cpl.log") @@ -162,7 +136,7 @@ def test_read_baseline_file_multi_line(self): baseline = performance.read_baseline_file("/tmp/cpl-mem.log") mock_file.assert_called_with("/tmp/cpl-mem.log") - assert baseline == "2000.0" + assert baseline == "sha:1df0 date:2023 1000.0\nsha:3b05 date:2023 2000.0" def test_read_baseline_file_content(self): with mock.patch( @@ -171,7 +145,7 @@ def test_read_baseline_file_content(self): baseline = performance.read_baseline_file("/tmp/cpl-mem.log") mock_file.assert_called_with("/tmp/cpl-mem.log") - assert baseline == "1000.0" + assert baseline == "sha:1df0 date:2023 1000.0" def test_write_baseline_file(self): with mock.patch("builtins.open", mock.mock_open()) as mock_file: @@ -187,9 +161,8 @@ def test__perf_get_throughput(self, get_latest_cpl_logs, get_cpl_throughput): with tempfile.TemporaryDirectory() as tempdir: case, _, _, baseline_root = create_mock_case(tempdir, get_latest_cpl_logs) - tput = performance._perf_get_throughput(case) - - assert tput == None + with self.assertRaises(RuntimeError): + performance._perf_get_throughput(case) @mock.patch("CIME.baselines.performance.get_cpl_mem_usage") @mock.patch("CIME.baselines.performance.get_latest_cpl_logs") @@ -199,9 +172,8 @@ def test__perf_get_memory_override(self, get_latest_cpl_logs, get_cpl_mem_usage) with tempfile.TemporaryDirectory() as tempdir: case, _, _, baseline_root = create_mock_case(tempdir, get_latest_cpl_logs) - mem = performance._perf_get_memory(case, "/tmp/override") - - assert mem == None + with self.assertRaises(RuntimeError): + performance._perf_get_memory(case, "/tmp/override") @mock.patch("CIME.baselines.performance.get_cpl_mem_usage") @mock.patch("CIME.baselines.performance.get_latest_cpl_logs") @@ -211,9 +183,8 @@ def test__perf_get_memory(self, get_latest_cpl_logs, get_cpl_mem_usage): with tempfile.TemporaryDirectory() as tempdir: case, _, _, baseline_root = create_mock_case(tempdir, get_latest_cpl_logs) - mem = performance._perf_get_memory(case) - - assert mem == None + with self.assertRaises(RuntimeError): + performance._perf_get_memory(case) @mock.patch("CIME.baselines.performance.write_baseline_file") @mock.patch("CIME.baselines.performance.perf_get_memory") @@ -264,9 +235,9 @@ def test_write_baseline_runtimeerror( def test_perf_write_baseline( self, perf_get_throughput, perf_get_memory, write_baseline_file ): - perf_get_throughput.return_value = "100" + perf_get_throughput.return_value = ("100", "a") - perf_get_memory.return_value = "1000" + perf_get_memory.return_value = ("1000", "a") with tempfile.TemporaryDirectory() as tempdir: case, _, _, baseline_root = create_mock_case(tempdir) @@ -275,8 +246,12 @@ def test_perf_write_baseline( perf_get_throughput.assert_called() perf_get_memory.assert_called() - write_baseline_file.assert_any_call(str(baseline_root / "cpl-tput.log"), "100") - write_baseline_file.assert_any_call(str(baseline_root / "cpl-mem.log"), "1000") + write_baseline_file.assert_any_call( + str(baseline_root / "cpl-tput.log"), "100", "a" + ) + write_baseline_file.assert_any_call( + str(baseline_root / "cpl-mem.log"), "1000", "a" + ) @mock.patch("CIME.baselines.performance._perf_get_throughput") @mock.patch("CIME.baselines.performance.read_baseline_file") @@ -309,7 +284,7 @@ def test_perf_compare_throughput_baseline_no_baseline( ): read_baseline_file.return_value = "" - _perf_get_throughput.return_value = 504 + _perf_get_throughput.return_value = ("504", "a") with tempfile.TemporaryDirectory() as tempdir: case, _, _, baseline_root = create_mock_case(tempdir, get_latest_cpl_logs) @@ -330,7 +305,7 @@ def test_perf_compare_throughput_baseline_no_baseline( assert below_tolerance is None assert ( comment - == "Could not compare throughput to baseline, as basline had no value." + == "Could not compare throughput to baseline, as baseline had no value." ) @mock.patch("CIME.baselines.performance._perf_get_throughput") @@ -341,7 +316,7 @@ def test_perf_compare_throughput_baseline_no_tolerance( ): read_baseline_file.return_value = "500" - _perf_get_throughput.return_value = 504 + _perf_get_throughput.return_value = ("504", "a") with tempfile.TemporaryDirectory() as tempdir: case, _, _, baseline_root = create_mock_case(tempdir, get_latest_cpl_logs) @@ -373,7 +348,7 @@ def test_perf_compare_throughput_baseline_above_threshold( ): read_baseline_file.return_value = "1000" - _perf_get_throughput.return_value = 504 + _perf_get_throughput.return_value = ("504", "a") with tempfile.TemporaryDirectory() as tempdir: case, _, _, baseline_root = create_mock_case(tempdir, get_latest_cpl_logs) @@ -405,7 +380,7 @@ def test_perf_compare_throughput_baseline( ): read_baseline_file.return_value = "500" - _perf_get_throughput.return_value = 504 + _perf_get_throughput.return_value = ("504", "a") with tempfile.TemporaryDirectory() as tempdir: case, _, _, baseline_root = create_mock_case(tempdir, get_latest_cpl_logs) diff --git a/CIME/tests/test_unit_system_tests.py b/CIME/tests/test_unit_system_tests.py index 22825fd5559..1c05bed45be 100644 --- a/CIME/tests/test_unit_system_tests.py +++ b/CIME/tests/test_unit_system_tests.py @@ -70,12 +70,12 @@ def create_mock_case(tempdir, idx=None, cpllog_data=None): class TestUnitSystemTests(unittest.TestCase): @mock.patch("CIME.SystemTests.system_tests_common.load_coupler_customization") @mock.patch("CIME.SystemTests.system_tests_common.append_testlog") - @mock.patch("CIME.SystemTests.system_tests_common._perf_get_memory") + @mock.patch("CIME.SystemTests.system_tests_common.perf_get_memory_list") @mock.patch("CIME.SystemTests.system_tests_common.get_latest_cpl_logs") def test_check_for_memleak_runtime_error( self, get_latest_cpl_logs, - _perf_get_memory, + perf_get_memory_list, append_testlog, load_coupler_customization, ): @@ -83,7 +83,7 @@ def test_check_for_memleak_runtime_error( AttributeError ) - _perf_get_memory.side_effect = RuntimeError + perf_get_memory_list.side_effect = RuntimeError with tempfile.TemporaryDirectory() as tempdir: caseroot = Path(tempdir) / "caseroot" @@ -120,12 +120,12 @@ def test_check_for_memleak_runtime_error( @mock.patch("CIME.SystemTests.system_tests_common.load_coupler_customization") @mock.patch("CIME.SystemTests.system_tests_common.append_testlog") - @mock.patch("CIME.SystemTests.system_tests_common._perf_get_memory") + @mock.patch("CIME.SystemTests.system_tests_common.perf_get_memory_list") @mock.patch("CIME.SystemTests.system_tests_common.get_latest_cpl_logs") def test_check_for_memleak_not_enough_samples( self, get_latest_cpl_logs, - _perf_get_memory, + perf_get_memory_list, append_testlog, load_coupler_customization, ): @@ -133,7 +133,7 @@ def test_check_for_memleak_not_enough_samples( AttributeError ) - _perf_get_memory.return_value = [ + perf_get_memory_list.return_value = [ (1, 1000.0), (2, 0), ] @@ -173,12 +173,12 @@ def test_check_for_memleak_not_enough_samples( @mock.patch("CIME.SystemTests.system_tests_common.load_coupler_customization") @mock.patch("CIME.SystemTests.system_tests_common.append_testlog") - @mock.patch("CIME.SystemTests.system_tests_common._perf_get_memory") + @mock.patch("CIME.SystemTests.system_tests_common.perf_get_memory_list") @mock.patch("CIME.SystemTests.system_tests_common.get_latest_cpl_logs") def test_check_for_memleak_found( self, get_latest_cpl_logs, - _perf_get_memory, + perf_get_memory_list, append_testlog, load_coupler_customization, ): @@ -186,7 +186,7 @@ def test_check_for_memleak_found( AttributeError ) - _perf_get_memory.return_value = [ + perf_get_memory_list.return_value = [ (1, 1000.0), (2, 2000.0), (3, 3000.0), @@ -230,12 +230,12 @@ def test_check_for_memleak_found( @mock.patch("CIME.SystemTests.system_tests_common.load_coupler_customization") @mock.patch("CIME.SystemTests.system_tests_common.append_testlog") - @mock.patch("CIME.SystemTests.system_tests_common._perf_get_memory") + @mock.patch("CIME.SystemTests.system_tests_common.perf_get_memory_list") @mock.patch("CIME.SystemTests.system_tests_common.get_latest_cpl_logs") def test_check_for_memleak( self, get_latest_cpl_logs, - _perf_get_memory, + perf_get_memory_list, append_testlog, load_coupler_customization, ): @@ -243,7 +243,7 @@ def test_check_for_memleak( AttributeError ) - _perf_get_memory.return_value = [ + perf_get_memory_list.return_value = [ (1, 3040.0), (2, 3002.0), (3, 3030.0), diff --git a/doc/source/users_guide/testing.rst b/doc/source/users_guide/testing.rst index 40868d2bbdd..061c62e3152 100644 --- a/doc/source/users_guide/testing.rst +++ b/doc/source/users_guide/testing.rst @@ -441,10 +441,12 @@ The following pseudo code is an example of this customization.:: ------- str Storing throughput value. + str + Open baseline file for writing. """ current = analyze_throughput(...) - return json.dumps(current) + return json.dumps(current), "w" def perf_get_memory(case): """ @@ -457,10 +459,12 @@ The following pseudo code is an example of this customization.:: ------- str Storing memory value. + str + Open baseline file for writing. """ current = analyze_memory(case) - return json.dumps(current) + return json.dumps(current), "w" def perf_compare_throughput_baseline(case, baseline, tolerance): """ From 4cd48d4d3206d76191b898803e80486b5347e414 Mon Sep 17 00:00:00 2001 From: James Edwards Date: Wed, 6 Dec 2023 07:34:19 -0700 Subject: [PATCH 269/390] allow an external install of the mpi-serial library --- CIME/Tools/Makefile | 14 ++++++++++---- CIME/XML/machines.py | 11 +++++++++-- CIME/build_scripts/buildlib.mpi-serial | 5 +++++ CIME/non_py/src/timing/Makefile | 6 +++++- 4 files changed, 29 insertions(+), 7 deletions(-) diff --git a/CIME/Tools/Makefile b/CIME/Tools/Makefile index c663b1edf26..5c5fbc1de9c 100644 --- a/CIME/Tools/Makefile +++ b/CIME/Tools/Makefile @@ -373,7 +373,13 @@ ifeq ($(strip $(MPILIB)), mpi-serial) MPIFC := $(SFC) MPICC := $(SCC) MPICXX := $(SCXX) - CONFIG_ARGS += MCT_PATH=$(SHAREDLIBROOT)/$(SHAREDPATH)/mct/mpi-serial + ifndef MPI_SERIAL_PATH + CONFIG_ARGS += MCT_PATH=$(SHAREDLIBROOT)/$(SHAREDPATH)/mct/mpi-serial + else + CONFIG_ARGS += MCT_PATH=$(MPI_SERIAL_PATH) + INC_MPI := $(MPI_SERIAL_PATH)/include + LIB_MPI := $(MPI_SERIAL_PATH)/lib + endif else CC := $(MPICC) FC := $(MPIFC) @@ -567,9 +573,9 @@ ifdef MPAS_LIBDIR # used to build the MPAS dycore if needed. libmpas: cam_abortutils.o physconst.o $(MAKE) -C $(MPAS_LIBDIR) CC="$(CC)" FC="$(FC)" PIODEF="$(PIODEF)" \ - FFLAGS='$(FREEFLAGS) $(FFLAGS)' GPUFLAGS='$(GPUFLAGS)' \ - CASEROOT='$(CASEROOT)' COMPILER='$(COMPILER)' MACH='$(MACH)' \ - FCINCLUDES='$(INCLDIR) $(INCS) -I$(ABS_INSTALL_SHAREDPATH)/include -I$(ABS_ESMF_PATH)/include' + FFLAGS='$(FREEFLAGS) $(FFLAGS)' GPUFLAGS='$(GPUFLAGS)' \ + CASEROOT='$(CASEROOT)' COMPILER='$(COMPILER)' MACH='$(MACH)' \ + FCINCLUDES='$(INCLDIR) $(INCS) -I$(ABS_INSTALL_SHAREDPATH)/include -I$(ABS_ESMF_PATH)/include' dyn_comp.o: libmpas dyn_grid.o: libmpas diff --git a/CIME/XML/machines.py b/CIME/XML/machines.py index 1b45cf5b580..1ef33bd5d35 100644 --- a/CIME/XML/machines.py +++ b/CIME/XML/machines.py @@ -12,7 +12,14 @@ class Machines(GenericXML): - def __init__(self, infile=None, files=None, machine=None, extra_machines_dir=None): + def __init__( + self, + infile=None, + files=None, + machine=None, + extra_machines_dir=None, + read_only=True, + ): """ initialize an object if a filename is provided it will be used, @@ -46,7 +53,7 @@ def __init__(self, infile=None, files=None, machine=None, extra_machines_dir=Non else: expect(False, f"file not found {infile}") - GenericXML.__init__(self, infile, schema) + GenericXML.__init__(self, infile, schema, read_only=read_only) # Append the contents of $HOME/.cime/config_machines.xml if it exists. # diff --git a/CIME/build_scripts/buildlib.mpi-serial b/CIME/build_scripts/buildlib.mpi-serial index 83ad88367fd..7aaad973d80 100755 --- a/CIME/build_scripts/buildlib.mpi-serial +++ b/CIME/build_scripts/buildlib.mpi-serial @@ -50,6 +50,11 @@ def buildlib(bldroot, installpath, case): ############################################################################### caseroot = case.get_value("CASEROOT") srcroot = case.get_value("SRCROOT") + # check to see if MPI_SERIAL is installed + with open(os.path.join(caseroot, "Macros.make"), "r") as f: + for line in f: + if "MPI_SERIAL_PATH" in line: + return customize_path = os.path.join(srcroot, "cime_config", "customize") diff --git a/CIME/non_py/src/timing/Makefile b/CIME/non_py/src/timing/Makefile index 89f3b4a7bed..7b0505fbffa 100644 --- a/CIME/non_py/src/timing/Makefile +++ b/CIME/non_py/src/timing/Makefile @@ -52,7 +52,11 @@ ifeq ($(strip $(MPILIB)), mpi-serial) FC := $(SFC) MPIFC := $(SFC) MPICC := $(SCC) - INCLDIR += -I$(GPTL_LIBDIR)/../mct/mpi-serial + ifdef MPI_SERIAL_PATH + INCLDIR += -I$(MPI_SERIAL_PATH)/include + else + INCLDIR += -I$(GPTL_LIBDIR)/../mct/mpi-serial + endif else CC := $(MPICC) FC := $(MPIFC) From 5b8788b95e2c4b98f623190ccf8285de36600c80 Mon Sep 17 00:00:00 2001 From: James Edwards Date: Wed, 6 Dec 2023 08:23:36 -0700 Subject: [PATCH 270/390] currently only reads the first Macros file found, this fixes that so that it reads all available Macros files --- CIME/tests/test_sys_unittest.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CIME/tests/test_sys_unittest.py b/CIME/tests/test_sys_unittest.py index af43654454c..de079218e2f 100755 --- a/CIME/tests/test_sys_unittest.py +++ b/CIME/tests/test_sys_unittest.py @@ -37,8 +37,8 @@ def _has_unit_test_support(self): for macro_to_check in macros_to_check: if os.path.exists(macro_to_check): macro_text = open(macro_to_check, "r").read() - - return "PFUNIT_PATH" in macro_text + if "PFUNIT_PATH" in macro_text: + return True return False From 4d70147a32e853f00f88b9ad5ff54aba3d02c9c3 Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Wed, 6 Dec 2023 10:01:55 -0800 Subject: [PATCH 271/390] Fixes passing compiler/comp-interace to run_tests.py --- CIME/tests/test_sys_unittest.py | 35 +++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/CIME/tests/test_sys_unittest.py b/CIME/tests/test_sys_unittest.py index de079218e2f..238c7355469 100755 --- a/CIME/tests/test_sys_unittest.py +++ b/CIME/tests/test_sys_unittest.py @@ -16,22 +16,29 @@ def setUpClass(cls): cls._testroot = os.path.join(cls.TEST_ROOT, "TestUnitTests") cls._testdirs = [] - def _has_unit_test_support(self): - if self.TEST_COMPILER is None: - compiler = self.MACHINE.get_default_compiler() - else: - compiler = self.TEST_COMPILER + def setUp(self): + super().setUp() + + self._driver = utils.get_cime_default_driver() + self._has_pfunit = self._has_unit_test_support() - mach = self.MACHINE.get_machine_name() + def _has_unit_test_support(self): cmake_macros_dir = Files().get_value("CMAKE_MACROS_DIR") macros_to_check = [ - os.path.join(cmake_macros_dir, "{}_{}.cmake".format(compiler, mach)), - os.path.join(cmake_macros_dir, "{}.cmake".format(mach)), os.path.join( - os.environ.get("HOME"), ".cime", "{}_{}.cmake".format(compiler, mach) + cmake_macros_dir, + "{}_{}.cmake".format(self._compiler, self._machine), + ), + os.path.join(cmake_macros_dir, "{}.cmake".format(self._machine)), + os.path.join( + os.environ.get("HOME"), + ".cime", + "{}_{}.cmake".format(self._compiler, self._machine), + ), + os.path.join( + os.environ.get("HOME"), ".cime", "{}.cmake".format(self._machine) ), - os.path.join(os.environ.get("HOME"), ".cime", "{}.cmake".format(mach)), ] for macro_to_check in macros_to_check: @@ -44,7 +51,7 @@ def _has_unit_test_support(self): def test_a_unit_test(self): cls = self.__class__ - if not self._has_unit_test_support(): + if not self._has_pfunit: self.skipTest( "Skipping TestUnitTest - PFUNIT_PATH not found for the default compiler on this machine" ) @@ -59,8 +66,7 @@ def test_a_unit_test(self): test_spec_dir = os.path.join( os.path.dirname(unit_test_tool), "Examples", "interpolate_1d", "tests" ) - args = "--build-dir {} --test-spec-dir {}".format(test_dir, test_spec_dir) - args += " --machine {}".format(self.MACHINE.get_machine_name()) + args = f"--build-dir {test_dir} --test-spec-dir {test_spec_dir} --machine {self._machine} --compiler {self._compiler} --comp-interface {self._driver}" utils.run_cmd_no_fail("{} {}".format(unit_test_tool, args)) cls._do_teardown.append(test_dir) @@ -83,8 +89,7 @@ def test_b_cime_f90_unit_tests(self): test_spec_dir, "scripts", "fortran_unit_testing", "run_tests.py" ) ) - args = "--build-dir {} --test-spec-dir {}".format(test_dir, test_spec_dir) - args += " --machine {}".format(self.MACHINE.get_machine_name()) + args = f"--build-dir {test_dir} --test-spec-dir {test_spec_dir} --machine {self._machine} --compiler {self._compiler} --comp-interface {self._driver}" utils.run_cmd_no_fail("{} {}".format(unit_test_tool, args)) cls._do_teardown.append(test_dir) From 68742fa7854deb4758ef28700d7d682a7d6f67a5 Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Wed, 6 Dec 2023 10:22:11 -0800 Subject: [PATCH 272/390] Fixes loading model customize earlier --- CIME/tests/scripts_regression_tests.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CIME/tests/scripts_regression_tests.py b/CIME/tests/scripts_regression_tests.py index 039f13c7718..66f4c015298 100755 --- a/CIME/tests/scripts_regression_tests.py +++ b/CIME/tests/scripts_regression_tests.py @@ -44,6 +44,7 @@ from CIME.provenance import get_test_success, save_test_success from CIME import utils from CIME.tests.base import BaseTestCase +from CIME.config import Config os.environ["CIME_GLOBAL_WALLTIME"] = "0:05:00" @@ -151,6 +152,9 @@ def configure_tests( ): config = CIME.utils.get_cime_config() + customize_path = os.path.join(utils.get_src_root(), "cime_config", "customize") + Config.load(customize_path) + if timeout: BaseTestCase.GLOBAL_TIMEOUT = str(timeout) From de51de3a67f69bc157f64fb012c30de2d845926e Mon Sep 17 00:00:00 2001 From: Bill Sacks Date: Wed, 6 Dec 2023 15:52:49 -0700 Subject: [PATCH 273/390] Stop running cpl7 unit tests --- CMakeLists.txt | 6 ------ 1 file changed, 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 94546b8e405..f44bb99217a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -96,7 +96,6 @@ endif() if (EXISTS ${SRC_ROOT}/share/src) add_subdirectory(${SRC_ROOT}/share/src share_src) - add_subdirectory(${SRC_ROOT}/components/cpl7/mct_shr mct_src) add_subdirectory(${SRC_ROOT}/share/unit_test_stubs/util csm_share_stubs) include_directories(${SRC_ROOT}/share/include) else() @@ -115,9 +114,4 @@ else() endif() # Now the actual test directories. -if (EXISTS ${SRC_ROOT}/components/cpl7/driver/unit_test) - add_subdirectory(${SRC_ROOT}/components/cpl7/driver/unit_test unit_test) -else() - add_subdirectory(${SRC_ROOT}/driver-mct/unit_test unit_test) -endif() add_subdirectory(${SRC_ROOT}/share/test/unit ${CMAKE_BINARY_DIR}/unittests) From 263a8ba8c096fcf8708e356083588f1002a9fd04 Mon Sep 17 00:00:00 2001 From: Bill Sacks Date: Wed, 6 Dec 2023 16:32:23 -0700 Subject: [PATCH 274/390] No longer build mct for unit tests This was only needed by the cpl7 unit tests; since we are no longer building / running the cpl7 unit tests, we can stop building mct, too. --- CMakeLists.txt | 67 -------------------------------------------------- 1 file changed, 67 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f44bb99217a..b8de549f4ed 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -22,73 +22,6 @@ include_directories(${NetCDF_C_INCLUDE_DIRS} ${NetCDF_Fortran_INCLUDE_DIRS}) # TODO: Some of the below should be done in the relevant directories, not in # this top level CMakeLists. -# ------------------------------------------------------------------------ -# Build mct -# ------------------------------------------------------------------------ -if (EXISTS ${SRC_ROOT}/libraries/mct) - set(MCT_ROOT "${SRC_ROOT}/libraries/mct") -else() - set(MCT_ROOT "${SRC_ROOT}/externals/mct") -endif() - -if (USE_MPI_SERIAL) - set(ENABLE_MPI_SERIAL "--enable-mpiserial") -else() - set(ENABLE_MPI_SERIAL "") -endif() - -ExternalProject_add(mct_project - PREFIX ${CMAKE_CURRENT_BINARY_DIR} - SOURCE_DIR ${MCT_ROOT} - BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}/mct - CONFIGURE_COMMAND ${MCT_ROOT}/configure ${ENABLE_MPI_SERIAL} --enable-debugging --prefix=${CMAKE_CURRENT_BINARY_DIR} CC=${CMAKE_C_COMPILER} FC=${CMAKE_Fortran_COMPILER} CFLAGS=${CFLAGS} FCFLAGS=${FFLAGS} SRCDIR=${MCT_ROOT} DEBUG="-g" - BUILD_COMMAND $(MAKE) SRCDIR=${MCT_ROOT} - # Leave things in rather than "installing", because we have - # no need to move things around inside of the CMake binary directory. Also, - # mpi-serial doesn't install properly in the out-of-source build - INSTALL_COMMAND : - ) -# This copy_makefiles step is needed because mct currently doesn't support an -# out-of-source build. I am replicating what is done for the CIME system build. -ExternalProject_add_step(mct_project copy_makefiles - DEPENDEES configure - DEPENDERS build - WORKING_DIRECTORY - COMMAND cp -p /Makefile . - COMMAND mkdir -p mct - COMMAND cp -p /mct/Makefile mct/ - COMMAND mkdir -p mpeu - COMMAND cp -p /mpeu/Makefile mpeu/ - ) -if (USE_MPI_SERIAL) - ExternalProject_add_step(mct_project copy_mpi_serial_files - DEPENDEES configure - DEPENDERS build - WORKING_DIRECTORY - COMMAND mkdir -p mpi-serial - COMMAND cp -p /mpi-serial/Makefile mpi-serial/ - COMMAND cp /mpi-serial/mpif.h mpi-serial/ - COMMAND cp /mpi-serial/mpi.h mpi-serial/ - ) -endif() - -# Tell cmake to look for libraries & mod files here, because this is where we built libraries -include_directories(${CMAKE_CURRENT_BINARY_DIR}/mct/mct) -include_directories(${CMAKE_CURRENT_BINARY_DIR}/mct/mpeu) -link_directories(${CMAKE_CURRENT_BINARY_DIR}/mct/mct) -link_directories(${CMAKE_CURRENT_BINARY_DIR}/mct/mpeu) -if (USE_MPI_SERIAL) - # We need to list the mpi-serial include directory before system-level - # directories so that we're sure to use mpi-serial's mpif.h instead of - # an mpif.h from a system path. - include_directories(BEFORE ${CMAKE_CURRENT_BINARY_DIR}/mct/mpi-serial) - link_directories(BEFORE ${CMAKE_CURRENT_BINARY_DIR}/mct/mpi-serial) -endif() - -# ------------------------------------------------------------------------ -# Done MCT build -# ------------------------------------------------------------------------ - # Now a bunch of includes for share code. # csm_share (we don't build it here because it seems to be built differently From f10d65605774b2bff69c432c4c7977db1f299612 Mon Sep 17 00:00:00 2001 From: Chris Fischer Date: Wed, 6 Dec 2023 17:06:19 -0700 Subject: [PATCH 275/390] Revert PR 4497 --- CIME/case/case_setup.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/CIME/case/case_setup.py b/CIME/case/case_setup.py index c9017db915b..e05bf29ccbe 100644 --- a/CIME/case/case_setup.py +++ b/CIME/case/case_setup.py @@ -422,14 +422,6 @@ def _case_setup_impl( run_cmd_no_fail( "{}/cime_config/cism.template {}".format(glcroot, caseroot) ) - if comp == "cam": - camroot = case.get_value("COMP_ROOT_DIR_ATM") - logger.debug("Running cam.case_setup.py") - run_cmd_no_fail( - "python {cam}/cime_config/cam.case_setup.py {cam} {case}".format( - cam=camroot, case=caseroot - ) - ) _build_usernl_files(case, "drv", "cpl") From de9a478cfd64dc352bcfa4a0b43be04fd0e43573 Mon Sep 17 00:00:00 2001 From: James Edwards Date: Thu, 7 Dec 2023 08:49:42 -0700 Subject: [PATCH 276/390] more changes for mpi-serial --- CIME/Tools/Makefile | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/CIME/Tools/Makefile b/CIME/Tools/Makefile index 5c5fbc1de9c..b11e0135589 100644 --- a/CIME/Tools/Makefile +++ b/CIME/Tools/Makefile @@ -601,10 +601,12 @@ ifdef LAPACK_LIBDIR SLIBS += -L$(LAPACK_LIBDIR) -llapack -lblas endif ifdef LIB_MPI - ifndef MPI_LIB_NAME - SLIBS += -L$(LIB_MPI) -lmpi - else - SLIBS += -L$(LIB_MPI) -l$(MPI_LIB_NAME) + ifndef MPI_SERIAL_PATH + ifndef MPI_LIB_NAME + SLIBS += -L$(LIB_MPI) -lmpi + else + SLIBS += -L$(LIB_MPI) -l$(MPI_LIB_NAME) + endif endif endif @@ -925,12 +927,21 @@ GENF90 ?= $(CIMEROOT)/CIME/non_py/externals/genf90/genf90.pl .SUFFIXES: .F90 .F .f90 .f .c .cpp .o .in ifeq ($(MPILIB),mpi-serial) - MPISERIAL = $(INSTALL_SHAREDPATH)/lib/libmpi-serial.a - MLIBS += -L$(INSTALL_SHAREDPATH)/lib -lmpi-serial - CMAKE_OPTS += -DMPI_C_INCLUDE_PATH=$(INSTALL_SHAREDPATH)/include \ + ifdef MPI_SERIAL_PATH + MPISERIAL = $(MPI_SERIAL_PATH)/lib/libmpi-serial.a + MLIBS += -L$(MPI_SERIAL_PATH)/lib -lmpi-serial + CMAKE_OPTS += -DMPI_C_INCLUDE_PATH=$(MPI_SERIAL_PATH)/include \ + -DMPI_Fortran_INCLUDE_PATH=$(MPI_SERIAL_PATH)/include \ + -DMPI_C_LIBRARIES=$(MPI_SERIAL_PATH)/lib/libmpi-serial.a \ + -DMPI_Fortran_LIBRARIES=$(MPI_SERIAL_PATH)/lib/libmpi-serial.a + else + MPISERIAL = $(INSTALL_SHAREDPATH)/lib/libmpi-serial.a + MLIBS += -L$(INSTALL_SHAREDPATH)/lib -lmpi-serial + CMAKE_OPTS += -DMPI_C_INCLUDE_PATH=$(INSTALL_SHAREDPATH)/include \ -DMPI_Fortran_INCLUDE_PATH=$(INSTALL_SHAREDPATH)/include \ -DMPI_C_LIBRARIES=$(INSTALL_SHAREDPATH)/lib/libmpi-serial.a \ -DMPI_Fortran_LIBRARIES=$(INSTALL_SHAREDPATH)/lib/libmpi-serial.a + endif endif $(MCTLIBS) : $(MPISERIAL) From b206acb9a0b1d0b7a556fc865bb9c8e196b331e0 Mon Sep 17 00:00:00 2001 From: Chris Fischer Date: Fri, 8 Dec 2023 11:34:17 -0700 Subject: [PATCH 277/390] Add back changes from PR4497 --- CIME/case/case_setup.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CIME/case/case_setup.py b/CIME/case/case_setup.py index e05bf29ccbe..6bb16e46e4a 100644 --- a/CIME/case/case_setup.py +++ b/CIME/case/case_setup.py @@ -422,6 +422,14 @@ def _case_setup_impl( run_cmd_no_fail( "{}/cime_config/cism.template {}".format(glcroot, caseroot) ) + if comp == "cam": + camroot = case.get_value("COMP_ROOT_DIR_ATM") + logger.debug("Running cam.case_setup.py") + run_cmd_no_fail( + "python {cam}/cime_config/cam.case_setup.py {cam} {case}".format( + cam=camroot, case=caseroot + ) + ) _build_usernl_files(case, "drv", "cpl") From dee0cb9dfcbaf190780b8d0825d86032794ae44a Mon Sep 17 00:00:00 2001 From: James Edwards Date: Mon, 11 Dec 2023 07:29:26 -0700 Subject: [PATCH 278/390] fix submodule reference for cprnc --- CIME/non_py/cprnc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CIME/non_py/cprnc b/CIME/non_py/cprnc index d17494561ac..5e2ca5bb5a2 160000 --- a/CIME/non_py/cprnc +++ b/CIME/non_py/cprnc @@ -1 +1 @@ -Subproject commit d17494561ace91fc86387c83591c0382a2c79566 +Subproject commit 5e2ca5bb5a2956339b60f270df868593e954420b From cf6b3f022c4aa247b0d213e1d4fb7bdcf1bcc818 Mon Sep 17 00:00:00 2001 From: James Foucar Date: Mon, 11 Dec 2023 16:06:19 -0700 Subject: [PATCH 279/390] Update to latest cprnc Something is up with the old sha, d17494561ace91fc86387c83591c0382a2c79566 Jenkins jobs are having trouble fetching it and I don't see it in the commit history of the main branch. I also don't see it when I do a fresh clone. --- CIME/non_py/cprnc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CIME/non_py/cprnc b/CIME/non_py/cprnc index d17494561ac..5e2ca5bb5a2 160000 --- a/CIME/non_py/cprnc +++ b/CIME/non_py/cprnc @@ -1 +1 @@ -Subproject commit d17494561ace91fc86387c83591c0382a2c79566 +Subproject commit 5e2ca5bb5a2956339b60f270df868593e954420b From 4ba6edd612bf67d4480c8a18fc4f5089783afbee Mon Sep 17 00:00:00 2001 From: James Foucar Date: Wed, 13 Dec 2023 15:32:23 -0700 Subject: [PATCH 280/390] Update cprnc submodule --- CIME/non_py/cprnc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CIME/non_py/cprnc b/CIME/non_py/cprnc index 5e2ca5bb5a2..d6971c90d09 160000 --- a/CIME/non_py/cprnc +++ b/CIME/non_py/cprnc @@ -1 +1 @@ -Subproject commit 5e2ca5bb5a2956339b60f270df868593e954420b +Subproject commit d6971c90d09d1bed5f3797eef383299b4fec1c0a From d68b8af328440d5562c9e930400bb74bbf3609ac Mon Sep 17 00:00:00 2001 From: James Edwards Date: Thu, 14 Dec 2023 15:48:08 -0700 Subject: [PATCH 281/390] remove SMP_PRESENT and replace with BUILD_THREADED --- CIME/BuildTools/configure.py | 4 ++-- CIME/Tools/Makefile | 2 +- CIME/build.py | 2 +- CIME/case/case_setup.py | 4 ++-- CIME/data/config/xml_schemas/env_mach_specific.xsd | 4 ++-- CIME/tests/test_sys_cime_case.py | 4 ++-- CIME/tests/test_unit_xml_machines.py | 8 ++++---- 7 files changed, 14 insertions(+), 14 deletions(-) diff --git a/CIME/BuildTools/configure.py b/CIME/BuildTools/configure.py index 4cd9059e5db..7cb884a625f 100755 --- a/CIME/BuildTools/configure.py +++ b/CIME/BuildTools/configure.py @@ -145,13 +145,13 @@ def __init__(self, compiler, mpilib, debug, comp_interface, threading=False): "DEBUG": debug, "COMP_INTERFACE": comp_interface, "PIO_VERSION": 2, - "SMP_PRESENT": threading, + "BUILD_THREADED": threading, "MODEL": get_model(), "SRCROOT": get_src_root(), } def get_build_threaded(self): - return self.get_value("SMP_PRESENT") + return self.get_value("BUILD_THREADED") def get_case_root(self): """Returns the root directory for this case.""" diff --git a/CIME/Tools/Makefile b/CIME/Tools/Makefile index b11e0135589..e3ddb9c2042 100644 --- a/CIME/Tools/Makefile +++ b/CIME/Tools/Makefile @@ -48,7 +48,7 @@ ifeq ($(strip $(SMP)),TRUE) THREADDIR = threads compile_threaded = TRUE else - ifeq ($(strip $(SMP_PRESENT)),TRUE) + ifeq ($(strip $(BUILD_THREADED)),TRUE) THREADDIR = threads compile_threaded = TRUE else diff --git a/CIME/build.py b/CIME/build.py index b8d481b80d8..3f5c57ca998 100644 --- a/CIME/build.py +++ b/CIME/build.py @@ -45,7 +45,7 @@ "OS", "PIO_VERSION", "SHAREDLIBROOT", - "SMP_PRESENT", + "BUILD_THREADED", "USE_ESMF_LIB", "USE_MOAB", "CAM_CONFIG_OPTS", diff --git a/CIME/case/case_setup.py b/CIME/case/case_setup.py index e05bf29ccbe..0ff856f5673 100644 --- a/CIME/case/case_setup.py +++ b/CIME/case/case_setup.py @@ -345,7 +345,7 @@ def _case_setup_impl( case.initialize_derived_attributes() - case.set_value("SMP_PRESENT", case.get_build_threaded()) + case.set_value("BUILD_THREADED", case.get_build_threaded()) else: case.check_pelayouts_require_rebuild(models) @@ -361,7 +361,7 @@ def _case_setup_impl( cost_per_node = case.get_value("COSTPES_PER_NODE") case.set_value("COST_PES", case.num_nodes * cost_per_node) threaded = case.get_build_threaded() - case.set_value("SMP_PRESENT", threaded) + case.set_value("BUILD_THREADED", threaded) if threaded and case.total_tasks * case.thread_count > cost_per_node: smt_factor = max( 1.0, int(case.get_value("MAX_TASKS_PER_NODE") / cost_per_node) diff --git a/CIME/data/config/xml_schemas/env_mach_specific.xsd b/CIME/data/config/xml_schemas/env_mach_specific.xsd index 7778635592b..3c7a3a0d679 100644 --- a/CIME/data/config/xml_schemas/env_mach_specific.xsd +++ b/CIME/data/config/xml_schemas/env_mach_specific.xsd @@ -11,7 +11,7 @@ - + @@ -138,7 +138,7 @@ - + diff --git a/CIME/tests/test_sys_cime_case.py b/CIME/tests/test_sys_cime_case.py index 6ca9a92168e..dabea7d9600 100644 --- a/CIME/tests/test_sys_cime_case.py +++ b/CIME/tests/test_sys_cime_case.py @@ -236,7 +236,7 @@ def test_cime_case_build_threaded_1(self): ) with Case(casedir, read_only=False) as case: - build_threaded = case.get_value("SMP_PRESENT") + build_threaded = case.get_value("BUILD_THREADED") self.assertFalse(build_threaded) build_threaded = case.get_build_threaded() @@ -254,7 +254,7 @@ def test_cime_case_build_threaded_2(self): ) with Case(casedir, read_only=False) as case: - build_threaded = case.get_value("SMP_PRESENT") + build_threaded = case.get_value("BUILD_THREADED") self.assertTrue(build_threaded) build_threaded = case.get_build_threaded() diff --git a/CIME/tests/test_unit_xml_machines.py b/CIME/tests/test_unit_xml_machines.py index 831359380fe..d051a5d7d3e 100644 --- a/CIME/tests/test_unit_xml_machines.py +++ b/CIME/tests/test_unit_xml_machines.py @@ -78,10 +78,10 @@ /opt/ubuntu/pe/netcdf-hdf5parallel/4.8.1.3/gnu/9.1/ $SHELL{dirname $(dirname $(which pnetcdf_version))} - + 128M - + cores @@ -126,10 +126,10 @@ /opt/ubuntu/pe/netcdf-hdf5parallel/4.8.1.3/gnu/9.1/ $SHELL{dirname $(dirname $(which pnetcdf_version))} - + 128M - + cores From a637ec77e386f94aee6043226ac0ec7f1f743863 Mon Sep 17 00:00:00 2001 From: James Edwards Date: Mon, 11 Dec 2023 09:33:51 -0700 Subject: [PATCH 282/390] v3 machines now working --- CIME/XML/machines.py | 84 +++++++++++++++++++++++--- CIME/data/config/cesm/config_files.xml | 3 +- 2 files changed, 76 insertions(+), 11 deletions(-) diff --git a/CIME/XML/machines.py b/CIME/XML/machines.py index 1ef33bd5d35..9152e1524d1 100644 --- a/CIME/XML/machines.py +++ b/CIME/XML/machines.py @@ -55,6 +55,8 @@ def __init__( GenericXML.__init__(self, infile, schema, read_only=read_only) + self.version = self.get_version() + # Append the contents of $HOME/.cime/config_machines.xml if it exists. # # Also append the contents of a config_machines.xml file in the directory given by @@ -91,7 +93,7 @@ def __init__( machine is not None, f"Could not initialize machine object from {', '.join(checked_files)}. This machine is not available for the target CIME_MODEL.", ) - self.set_machine(machine) + self.set_machine(machine, schema=schema) def get_child(self, name=None, attributes=None, root=None, err_msg=None): if root is None: @@ -150,6 +152,7 @@ def probe_machine_name(self, warn=True): names_not_found = [] nametomatch = socket.getfqdn() + machine = self._probe_machine_name_one_guess(nametomatch) if machine is None: @@ -177,8 +180,13 @@ def _probe_machine_name_one_guess(self, nametomatch): Find a matching regular expression for nametomatch in the NODENAME_REGEX field in the file. First match wins. Returns None if no match is found. """ + if self.version < 3: + return self._probe_machine_name_one_guess_v2(nametomatch) + else: + return self._probe_machine_name_one_guess_v3(nametomatch) + + def _probe_machine_name_one_guess_v2(self, nametomatch): - machine = None nodes = self.get_children("machine") for node in nodes: @@ -222,7 +230,53 @@ def _probe_machine_name_one_guess(self, nametomatch): return machine - def set_machine(self, machine): + def _probe_machine_name_one_guess_v3(self, nametomatch): + + node = self.get_child("NODENAME_REGEX", root=self.root) + + for child in self.get_children(root=node): + machtocheck = self.get(child, "MACH") + regex_str = self.text(child) + logger.debug( + "machine is {} regex {}, nametomatch {}".format( + machtocheck, regex_str, nametomatch + ) + ) + + if regex_str is not None: + # an environment variable can be used + if regex_str.startswith("$ENV"): + machine_value = self.get_resolved_value( + regex_str, allow_unresolved_envvars=True + ) + logger.debug("machine_value is {}".format(machine_value)) + if not machine_value.startswith("$ENV"): + try: + match, this_machine = machine_value.split(":") + except ValueError: + expect( + False, + "Bad formation of NODENAME_REGEX. Expected envvar:value, found {}".format( + regex_str + ), + ) + if match == this_machine: + machine = machtocheck + break + else: + regex = re.compile(regex_str) + if regex.match(nametomatch): + logger.debug( + "Found machine: {} matches {}".format( + machtocheck, nametomatch + ) + ) + machine = machtocheck + break + + return machine + + def set_machine(self, machine, schema=None): """ Sets the machine block in the Machines object @@ -235,15 +289,20 @@ def set_machine(self, machine): CIMEError: ERROR: No machine trump found """ if machine == "Query": - self.machine = machine - elif self.machine != machine or self.machine_node is None: - self.machine_node = super(Machines, self).get_child( - "machine", - {"MACH": machine}, - err_msg="No machine {} found".format(machine), + return machine + elif self.version == 3: + GenericXML.read( + self, + os.path.join(self.machines_dir, machine, "config_machines.xml"), + schema=schema, ) - self.machine = machine + self.machine_node = super(Machines, self).get_child( + "machine", + {"MACH": machine}, + err_msg="No machine {} found".format(machine), + ) + self.machine = machine return machine # pylint: disable=arguments-differ @@ -292,6 +351,11 @@ def get_field_from_list(self, listname, reqval=None, attributes=None): """ expect(self.machine_node is not None, "Machine object has no machine defined") supported_values = self.get_value(listname, attributes=attributes) + logger.debug( + "supported values for {} on {} is {}".format( + listname, self.machine, supported_values + ) + ) # if no match with attributes, try without if supported_values is None: supported_values = self.get_value(listname, attributes=None) diff --git a/CIME/data/config/cesm/config_files.xml b/CIME/data/config/cesm/config_files.xml index b3346815bd5..75a9520dc72 100644 --- a/CIME/data/config/cesm/config_files.xml +++ b/CIME/data/config/cesm/config_files.xml @@ -43,7 +43,8 @@ case_last env_case.xml file containing machine specifications for target model primary component (for documentation only - DO NOT EDIT) - $CIMEROOT/CIME/data/config/xml_schemas/config_machines.xsd + $CIMEROOT/CIME/data/config/xml_schemas/config_machines.xsd + $CIMEROOT/CIME/data/config/xml_schemas/config_machines_version3.xsd From ba073b7b86250a370b0ab33ae50dd18cbdf76bb9 Mon Sep 17 00:00:00 2001 From: James Edwards Date: Mon, 11 Dec 2023 09:36:12 -0700 Subject: [PATCH 283/390] v3 machines now working --- CIME/XML/machines.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/CIME/XML/machines.py b/CIME/XML/machines.py index 9152e1524d1..435ad13a026 100644 --- a/CIME/XML/machines.py +++ b/CIME/XML/machines.py @@ -291,11 +291,15 @@ def set_machine(self, machine, schema=None): if machine == "Query": return machine elif self.version == 3: - GenericXML.read( - self, - os.path.join(self.machines_dir, machine, "config_machines.xml"), - schema=schema, + machines_file = os.path.join( + self.machines_dir, machine, "config_machines.xml" ) + if os.path.isfile(machines_file): + GenericXML.read( + self, + machines_file, + schema=schema, + ) self.machine_node = super(Machines, self).get_child( "machine", {"MACH": machine}, From 7bd75224b6a6acb3dcd00da80dbb4617a0179efb Mon Sep 17 00:00:00 2001 From: James Edwards Date: Mon, 11 Dec 2023 11:03:38 -0700 Subject: [PATCH 284/390] remove cori from tests, fix unassigned var error --- CIME/XML/machines.py | 2 +- CIME/tests/test_sys_test_scheduler.py | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/CIME/XML/machines.py b/CIME/XML/machines.py index 435ad13a026..e3fd542e2c3 100644 --- a/CIME/XML/machines.py +++ b/CIME/XML/machines.py @@ -188,7 +188,7 @@ def _probe_machine_name_one_guess(self, nametomatch): def _probe_machine_name_one_guess_v2(self, nametomatch): nodes = self.get_children("machine") - + machine = None for node in nodes: machtocheck = self.get(node, "MACH") logger.debug("machine is " + machtocheck) diff --git a/CIME/tests/test_sys_test_scheduler.py b/CIME/tests/test_sys_test_scheduler.py index b53809ba306..3dfd4b62124 100755 --- a/CIME/tests/test_sys_test_scheduler.py +++ b/CIME/tests/test_sys_test_scheduler.py @@ -20,22 +20,22 @@ def test_chksum(self, strftime): # pylint: disable=unused-argument self.skipTest("Skipping chksum test. Depends on CESM settings") ts = test_scheduler.TestScheduler( - ["SEQ_Ln9.f19_g16_rx1.A.cori-haswell_gnu"], - machine_name="cori-haswell", + ["SEQ_Ln9.f19_g16_rx1.A.perlmutter_gnu"], + machine_name="perlmutter", chksum=True, test_root="/tests", ) with mock.patch.object(ts, "_shell_cmd_for_phase") as _shell_cmd_for_phase: ts._run_phase( - "SEQ_Ln9.f19_g16_rx1.A.cori-haswell_gnu" + "SEQ_Ln9.f19_g16_rx1.A.perlmutter_gnu" ) # pylint: disable=protected-access _shell_cmd_for_phase.assert_called_with( - "SEQ_Ln9.f19_g16_rx1.A.cori-haswell_gnu", + "SEQ_Ln9.f19_g16_rx1.A.perlmutter_gnu", "./case.submit --skip-preview-namelist --chksum", "RUN", - from_dir="/tests/SEQ_Ln9.f19_g16_rx1.A.cori-haswell_gnu.00:00:00", + from_dir="/tests/SEQ_Ln9.f19_g16_rx1.A.perlmutter_gnu.00:00:00", ) def test_a_phases(self): From 08657694dd9c005a741189a667b3e6223de0fdf6 Mon Sep 17 00:00:00 2001 From: James Edwards Date: Mon, 11 Dec 2023 11:07:09 -0700 Subject: [PATCH 285/390] remove cori from tests, fix unassigned var error --- CIME/tests/test_unit_case.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/CIME/tests/test_unit_case.py b/CIME/tests/test_unit_case.py index 8a1190456c2..820bd9ac91c 100755 --- a/CIME/tests/test_unit_case.py +++ b/CIME/tests/test_unit_case.py @@ -232,14 +232,14 @@ def test_copy( self.srcroot, "A", "f19_g16_rx1", - machine_name="cori-haswell", + machine_name="perlmutter", ) # Check that they're all called configure.assert_called_with( "A", "f19_g16_rx1", - machine_name="cori-haswell", + machine_name="perlmutter", project=None, pecount=None, compiler=None, @@ -309,14 +309,14 @@ def test_create( self.srcroot, "A", "f19_g16_rx1", - machine_name="cori-haswell", + machine_name="perlmutter", ) # Check that they're all called configure.assert_called_with( "A", "f19_g16_rx1", - machine_name="cori-haswell", + machine_name="perlmutter", project=None, pecount=None, compiler=None, From 059f36db4d8ef9e349188742a51979ee26667642 Mon Sep 17 00:00:00 2001 From: James Edwards Date: Mon, 11 Dec 2023 14:01:13 -0700 Subject: [PATCH 286/390] v3 list available --- CIME/XML/machines.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/CIME/XML/machines.py b/CIME/XML/machines.py index e3fd542e2c3..55d55423e8a 100644 --- a/CIME/XML/machines.py +++ b/CIME/XML/machines.py @@ -137,10 +137,19 @@ def list_available_machines(self): Return a list of machines defined for a given CIME_MODEL """ machines = [] - nodes = self.get_children("machine") - for node in nodes: - mach = self.get(node, "MACH") - machines.append(mach) + if self.version < 3: + nodes = self.get_children("machine") + for node in nodes: + mach = self.get(node, "MACH") + machines.append(mach) + else: + machines = [ + os.path.basename(f.path) + for f in os.scandir(self.machines_dir) + if f.is_dir() + ] + machines.remove("cmake_macros") + machines.sort() return machines def probe_machine_name(self, warn=True): From a58a5d69973d81c611d3f954d43f17a500613fc9 Mon Sep 17 00:00:00 2001 From: James Edwards Date: Tue, 12 Dec 2023 13:21:55 -0700 Subject: [PATCH 287/390] add config_machines_version3 schema file --- CIME/XML/files.py | 3 + CIME/XML/generic_xml.py | 12 +- CIME/XML/machines.py | 19 +- .../xml_schemas/config_machines_version3.xsd | 330 ++++++++++++++++++ 4 files changed, 352 insertions(+), 12 deletions(-) create mode 100644 CIME/data/config/xml_schemas/config_machines_version3.xsd diff --git a/CIME/XML/files.py b/CIME/XML/files.py index 26843409ed0..e616e47c413 100644 --- a/CIME/XML/files.py +++ b/CIME/XML/files.py @@ -136,7 +136,10 @@ def set_value(self, vid, value, subgroup=None, ignore_type=False): def get_schema(self, nodename, attributes=None): node = self.get_optional_child("entry", {"id": nodename}) + schemanode = self.get_optional_child("schema", root=node, attributes=attributes) + logger.debug("Found schema for {} {}".format(nodename, self.text(schemanode))) + if schemanode is not None: logger.debug("Found schema for {}".format(nodename)) return self.get_resolved_value(self.text(schemanode)) diff --git a/CIME/XML/generic_xml.py b/CIME/XML/generic_xml.py index a45ca766ee7..387e8b8c4d4 100644 --- a/CIME/XML/generic_xml.py +++ b/CIME/XML/generic_xml.py @@ -8,7 +8,7 @@ import xml.etree.ElementTree as ET # pylint: disable=import-error -from distutils.spawn import find_executable +from shutil import which import getpass from copy import deepcopy from collections import namedtuple @@ -126,8 +126,10 @@ def read(self, infile, schema=None): logger.debug("read: {}".format(infile)) with open(infile, "r", encoding="utf-8") as fd: self.read_fd(fd) - - if schema is not None and self.get_version() > 1.0: + version = str(self.get_version()) + if type(schema) is dict: + self.validate_xml_file(infile, schema[version]) + elif schema is not None and self.get_version() > 1.0: self.validate_xml_file(infile, schema) logger.debug("File version is {}".format(str(self.get_version()))) @@ -472,7 +474,7 @@ def write(self, outfile=None, force_write=False): xmlstr = self.get_raw_record() # xmllint provides a better format option for the output file - xmllint = find_executable("xmllint") + xmllint = which("xmllint") if xmllint: if isinstance(outfile, str): @@ -690,7 +692,7 @@ def validate_xml_file(self, filename, schema): """ expect(os.path.isfile(filename), "xml file not found {}".format(filename)) expect(os.path.isfile(schema), "schema file not found {}".format(schema)) - xmllint = find_executable("xmllint") + xmllint = which("xmllint") expect( xmllint and os.path.isfile(xmllint), diff --git a/CIME/XML/machines.py b/CIME/XML/machines.py index 55d55423e8a..a933a72acb7 100644 --- a/CIME/XML/machines.py +++ b/CIME/XML/machines.py @@ -44,8 +44,6 @@ def __init__( files = Files() if infile is None: infile = files.get_value("MACHINES_SPEC_FILE") - schema = files.get_schema("MACHINES_SPEC_FILE") - logger.debug("Verifying using schema {}".format(schema)) self.machines_dir = os.path.dirname(infile) if os.path.exists(infile): @@ -53,9 +51,16 @@ def __init__( else: expect(False, f"file not found {infile}") - GenericXML.__init__(self, infile, schema, read_only=read_only) + schema = { + "3.0": files.get_schema( + "MACHINES_SPEC_FILE", attributes={"version": "3.0"} + ), + "2.0": files.get_schema( + "MACHINES_SPEC_FILE", attributes={"version": "2.0"} + ), + } - self.version = self.get_version() + GenericXML.__init__(self, infile, schema, read_only=read_only) # Append the contents of $HOME/.cime/config_machines.xml if it exists. # @@ -137,7 +142,7 @@ def list_available_machines(self): Return a list of machines defined for a given CIME_MODEL """ machines = [] - if self.version < 3: + if self.get_version() < 3: nodes = self.get_children("machine") for node in nodes: mach = self.get(node, "MACH") @@ -189,7 +194,7 @@ def _probe_machine_name_one_guess(self, nametomatch): Find a matching regular expression for nametomatch in the NODENAME_REGEX field in the file. First match wins. Returns None if no match is found. """ - if self.version < 3: + if self.get_version() < 3: return self._probe_machine_name_one_guess_v2(nametomatch) else: return self._probe_machine_name_one_guess_v3(nametomatch) @@ -299,7 +304,7 @@ def set_machine(self, machine, schema=None): """ if machine == "Query": return machine - elif self.version == 3: + elif self.get_version() == 3: machines_file = os.path.join( self.machines_dir, machine, "config_machines.xml" ) diff --git a/CIME/data/config/xml_schemas/config_machines_version3.xsd b/CIME/data/config/xml_schemas/config_machines_version3.xsd new file mode 100644 index 00000000000..92b55839fb2 --- /dev/null +++ b/CIME/data/config/xml_schemas/config_machines_version3.xsd @@ -0,0 +1,330 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From b88cdb1e19ec1d8caecfdf033d19319b1da7780b Mon Sep 17 00:00:00 2001 From: James Edwards Date: Tue, 12 Dec 2023 13:44:33 -0700 Subject: [PATCH 288/390] remove bad debug print --- CIME/XML/files.py | 1 - 1 file changed, 1 deletion(-) diff --git a/CIME/XML/files.py b/CIME/XML/files.py index e616e47c413..c0149f9601a 100644 --- a/CIME/XML/files.py +++ b/CIME/XML/files.py @@ -138,7 +138,6 @@ def get_schema(self, nodename, attributes=None): node = self.get_optional_child("entry", {"id": nodename}) schemanode = self.get_optional_child("schema", root=node, attributes=attributes) - logger.debug("Found schema for {} {}".format(nodename, self.text(schemanode))) if schemanode is not None: logger.debug("Found schema for {}".format(nodename)) From 320ff28ed492efd62b66c4ff7be8a86cb6f936e3 Mon Sep 17 00:00:00 2001 From: James Edwards Date: Tue, 12 Dec 2023 13:47:20 -0700 Subject: [PATCH 289/390] another debug print statement --- CIME/XML/machines.py | 1 + CIME/non_py/cprnc | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CIME/XML/machines.py b/CIME/XML/machines.py index a933a72acb7..12c26b4f7f9 100644 --- a/CIME/XML/machines.py +++ b/CIME/XML/machines.py @@ -59,6 +59,7 @@ def __init__( "MACHINES_SPEC_FILE", attributes={"version": "2.0"} ), } + logger.debug("Verifying using schema {}".format(schema)) GenericXML.__init__(self, infile, schema, read_only=read_only) diff --git a/CIME/non_py/cprnc b/CIME/non_py/cprnc index d6971c90d09..d17494561ac 160000 --- a/CIME/non_py/cprnc +++ b/CIME/non_py/cprnc @@ -1 +1 @@ -Subproject commit d6971c90d09d1bed5f3797eef383299b4fec1c0a +Subproject commit d17494561ace91fc86387c83591c0382a2c79566 From 561f19cef5e13f0c632fd41b60346c5997e7c930 Mon Sep 17 00:00:00 2001 From: James Edwards Date: Tue, 12 Dec 2023 15:37:30 -0700 Subject: [PATCH 290/390] better error check --- CIME/XML/generic_xml.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/CIME/XML/generic_xml.py b/CIME/XML/generic_xml.py index 387e8b8c4d4..d24101a8b26 100644 --- a/CIME/XML/generic_xml.py +++ b/CIME/XML/generic_xml.py @@ -690,8 +690,13 @@ def validate_xml_file(self, filename, schema): """ validate an XML file against a provided schema file using pylint """ - expect(os.path.isfile(filename), "xml file not found {}".format(filename)) - expect(os.path.isfile(schema), "schema file not found {}".format(schema)) + expect( + filename and os.path.isfile(filename), + "xml file not found {}".format(filename), + ) + expect( + schema and os.path.isfile(schema), "schema file not found {}".format(schema) + ) xmllint = which("xmllint") expect( From a4496111c61d6b7637ee8099347b2964d7b756e9 Mon Sep 17 00:00:00 2001 From: James Edwards Date: Tue, 12 Dec 2023 15:45:45 -0700 Subject: [PATCH 291/390] fix schema for v2 ; --- CIME/XML/machines.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CIME/XML/machines.py b/CIME/XML/machines.py index 12c26b4f7f9..41043790d30 100644 --- a/CIME/XML/machines.py +++ b/CIME/XML/machines.py @@ -59,6 +59,10 @@ def __init__( "MACHINES_SPEC_FILE", attributes={"version": "2.0"} ), } + # Before v3 there was but one choice + if not schema: + schema = files.get_schema("MACHINES_SPEC_FILE") + logger.debug("Verifying using schema {}".format(schema)) GenericXML.__init__(self, infile, schema, read_only=read_only) From 463b11e434cdb3e6bece93ae51f1e1a751abeaff Mon Sep 17 00:00:00 2001 From: James Edwards Date: Tue, 12 Dec 2023 16:02:30 -0700 Subject: [PATCH 292/390] fix schema for v2 ; --- CIME/XML/machines.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CIME/XML/machines.py b/CIME/XML/machines.py index 41043790d30..1916a8eee76 100644 --- a/CIME/XML/machines.py +++ b/CIME/XML/machines.py @@ -60,7 +60,7 @@ def __init__( ), } # Before v3 there was but one choice - if not schema: + if not schema["3.0"]: schema = files.get_schema("MACHINES_SPEC_FILE") logger.debug("Verifying using schema {}".format(schema)) From d32b0d585d66c9b2ba9724b1d09f727518455adb Mon Sep 17 00:00:00 2001 From: James Edwards Date: Wed, 13 Dec 2023 06:54:32 -0700 Subject: [PATCH 293/390] add documentation of schema variable --- CIME/XML/generic_xml.py | 3 ++- CIME/XML/machines.py | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CIME/XML/generic_xml.py b/CIME/XML/generic_xml.py index d24101a8b26..3b303047ad3 100644 --- a/CIME/XML/generic_xml.py +++ b/CIME/XML/generic_xml.py @@ -105,7 +105,8 @@ def __init__( def read(self, infile, schema=None): """ - Read and parse an xml file into the object + Read and parse an xml file into the object. The schema variable can either be a path to an xsd schema file or + a dictionary of paths to files by version. """ cached_read = False if not self.DISABLE_CACHING and infile in self._FILEMAP: diff --git a/CIME/XML/machines.py b/CIME/XML/machines.py index 1916a8eee76..a9536d74572 100644 --- a/CIME/XML/machines.py +++ b/CIME/XML/machines.py @@ -30,6 +30,9 @@ def __init__( additional directory that will be searched for a config_machines.xml file; if found, the contents of this file will be appended to the standard config_machines.xml. An empty string is treated the same as None. + + The schema variable can be passed as a path to an xsd schema file or a dictionary of paths + with version number as keys. """ self.machine_node = None From 4e1bd766b1fa2333bd32a01d59e5ab81778b66f2 Mon Sep 17 00:00:00 2001 From: James Foucar Date: Fri, 15 Dec 2023 10:22:39 -0700 Subject: [PATCH 294/390] cprnc subm --- CIME/non_py/cprnc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CIME/non_py/cprnc b/CIME/non_py/cprnc index d17494561ac..d6971c90d09 160000 --- a/CIME/non_py/cprnc +++ b/CIME/non_py/cprnc @@ -1 +1 @@ -Subproject commit d17494561ace91fc86387c83591c0382a2c79566 +Subproject commit d6971c90d09d1bed5f3797eef383299b4fec1c0a From 1c3b5c570d60593cef98ff30552389543eefdbfd Mon Sep 17 00:00:00 2001 From: James Edwards Date: Fri, 15 Dec 2023 14:42:56 -0700 Subject: [PATCH 295/390] update externals for github testing --- .github/workflows/testing.yml | 2 +- Externals.cfg | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index 5744552646c..ffa48a3b802 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -72,7 +72,7 @@ jobs: env: INIT: "false" CIME_MODEL: "cesm" - CIME_DRIVER: "mct" + CIME_DRIVER: "nuopc" UPDATE_CIME: "true" GIT_SHALLOW: "true" CIME_TEST_PLATFORM: ubuntu-latest diff --git a/Externals.cfg b/Externals.cfg index 01537b54197..f9fa6698dce 100644 --- a/Externals.cfg +++ b/Externals.cfg @@ -1,19 +1,19 @@ [ccs_config] -tag = ccs_config_cesm0.0.76 +tag = ccs_config_cesm0.0.88 protocol = git repo_url = https://github.com/ESMCI/ccs_config_cesm local_path = ccs_config required = True [cmeps] -tag = cmeps0.14.38 +tag = cmeps0.14.47 protocol = git repo_url = https://github.com/ESCOMP/CMEPS.git local_path = components/cmeps required = True [cdeps] -tag = cdeps1.0.19 +tag = cdeps1.0.26 protocol = git repo_url = https://github.com/ESCOMP/CDEPS.git local_path = components/cdeps @@ -21,7 +21,7 @@ externals = Externals_CDEPS.cfg required = True [cpl7] -tag = cpl77.0.6 +tag = cpl77.0.8 protocol = git repo_url = https://github.com/ESCOMP/CESM_CPL7andDataComps local_path = components/cpl7 @@ -42,7 +42,7 @@ local_path = libraries/mct required = True [parallelio] -tag = pio2_6_0 +tag = pio2_6_2 protocol = git repo_url = https://github.com/NCAR/ParallelIO local_path = libraries/parallelio From cf3140d20f460fdbabe390af96f09b6cbd5063e8 Mon Sep 17 00:00:00 2001 From: James Edwards Date: Fri, 15 Dec 2023 15:35:09 -0700 Subject: [PATCH 296/390] need to merge to master --- CIME/non_py/cprnc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CIME/non_py/cprnc b/CIME/non_py/cprnc index d6971c90d09..5e2ca5bb5a2 160000 --- a/CIME/non_py/cprnc +++ b/CIME/non_py/cprnc @@ -1 +1 @@ -Subproject commit d6971c90d09d1bed5f3797eef383299b4fec1c0a +Subproject commit 5e2ca5bb5a2956339b60f270df868593e954420b From 8b6381ed4fdc90d18c6ce1e57ffc0b84ee62b6a3 Mon Sep 17 00:00:00 2001 From: James Edwards Date: Fri, 15 Dec 2023 15:41:45 -0700 Subject: [PATCH 297/390] quit testing cesm with mct --- .github/workflows/testing.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index ffa48a3b802..261f40d443a 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -108,6 +108,9 @@ jobs: # exclude nuopc driver when running e3sm tests - model: "e3sm" driver: "nuopc" + # exclude mct driver when running cesm tests + - model: "cesm" + driver: "mct" steps: - name: Checkout code uses: actions/checkout@v2 From 826ef6f82475eeedb971a41729ddc5b6984f346c Mon Sep 17 00:00:00 2001 From: James Edwards Date: Fri, 15 Dec 2023 16:12:09 -0700 Subject: [PATCH 298/390] try this --- docker/config_machines.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/config_machines.xml b/docker/config_machines.xml index ea60a2cfbb3..242150d750c 100644 --- a/docker/config_machines.xml +++ b/docker/config_machines.xml @@ -3,7 +3,7 @@ Docker - docker + LINUX gnu,gnuX From 40d776e2f89ee43a8ef59cd05efdf480488db9b1 Mon Sep 17 00:00:00 2001 From: James Edwards Date: Tue, 19 Dec 2023 16:02:51 -0700 Subject: [PATCH 299/390] userdefined_laptop_template is not a real machine --- CIME/XML/machines.py | 1 + 1 file changed, 1 insertion(+) diff --git a/CIME/XML/machines.py b/CIME/XML/machines.py index a9536d74572..4d11cefe378 100644 --- a/CIME/XML/machines.py +++ b/CIME/XML/machines.py @@ -162,6 +162,7 @@ def list_available_machines(self): if f.is_dir() ] machines.remove("cmake_macros") + machines.remove("userdefined_laptop_template") machines.sort() return machines From 1621a03905d2b15335f2d8547e9e005281516455 Mon Sep 17 00:00:00 2001 From: James Edwards Date: Tue, 19 Dec 2023 17:00:34 -0700 Subject: [PATCH 300/390] remove NODENAME_REGEX from test --- CIME/data/config/xml_schemas/config_machines_template.xml | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/CIME/data/config/xml_schemas/config_machines_template.xml b/CIME/data/config/xml_schemas/config_machines_template.xml index 99c2bc72581..de361c16265 100644 --- a/CIME/data/config/xml_schemas/config_machines_template.xml +++ b/CIME/data/config/xml_schemas/config_machines_template.xml @@ -1,17 +1,12 @@ - + SITE VENDOR platform, os is ---, xx pes/node, batch system is --- - - .*.cheyenne.ucar.edu - LINUX From bcb77f9fa0cf9e1f1ad04268a091f0966af242bb Mon Sep 17 00:00:00 2001 From: James Edwards Date: Wed, 20 Dec 2023 06:55:54 -0700 Subject: [PATCH 301/390] fix some tests --- CIME/XML/machines.py | 21 ++++++++++++--------- CIME/case/case_clone.py | 4 +--- CIME/tests/test_sys_create_newcase.py | 10 ++++++---- 3 files changed, 19 insertions(+), 16 deletions(-) diff --git a/CIME/XML/machines.py b/CIME/XML/machines.py index 4d11cefe378..ec26438f1f0 100644 --- a/CIME/XML/machines.py +++ b/CIME/XML/machines.py @@ -150,19 +150,22 @@ def list_available_machines(self): Return a list of machines defined for a given CIME_MODEL """ machines = [] - if self.get_version() < 3: - nodes = self.get_children("machine") - for node in nodes: - mach = self.get(node, "MACH") - machines.append(mach) - else: - machines = [ + nodes = self.get_children("machine") + for node in nodes: + mach = self.get(node, "MACH") + machines.append(mach) + if self.get_version() == 3.0: + machdirs = [ os.path.basename(f.path) for f in os.scandir(self.machines_dir) if f.is_dir() ] - machines.remove("cmake_macros") - machines.remove("userdefined_laptop_template") + machdirs.remove("cmake_macros") + machdirs.remove("userdefined_laptop_template") + for mach in machdirs: + if mach not in machines: + machines.append(mach) + machines.sort() return machines diff --git a/CIME/case/case_clone.py b/CIME/case/case_clone.py index 737d26564b3..7b81e0e91b5 100644 --- a/CIME/case/case_clone.py +++ b/CIME/case/case_clone.py @@ -107,7 +107,7 @@ def create_clone( if exeroot is not None: expect( not keepexe, - "create_case_clone: if keepexe is True, " "then exeroot cannot be set", + "create_case_clone: if keepexe is True, then exeroot cannot be set", ) newcase.set_value("EXEROOT", exeroot) if rundir is not None: @@ -219,8 +219,6 @@ def create_clone( ) ) - newcase.case_setup() - return newcase diff --git a/CIME/tests/test_sys_create_newcase.py b/CIME/tests/test_sys_create_newcase.py index 1d240c9ee1f..a0f07a001b6 100644 --- a/CIME/tests/test_sys_create_newcase.py +++ b/CIME/tests/test_sys_create_newcase.py @@ -109,10 +109,12 @@ def test_a_createnewcase(self): new_batch_command = case.get_value( "BATCH_COMMAND_FLAGS", subgroup="case.run" ) - self.assertTrue( - "fred" in new_batch_command, - msg="Failed to update JOB_QUEUE in BATCH_COMMAND_FLAGS", - ) + self.assertTrue( + "fred" in new_batch_command, + msg="Failed to update JOB_QUEUE in BATCH_COMMAND_FLAGS {}".format( + new_batch_command + ), + ) # Trying to set values outside of context manager should fail case = Case(testdir, read_only=False) From 1f38577e37215b720ee488975475d35901981ff9 Mon Sep 17 00:00:00 2001 From: James Edwards Date: Wed, 20 Dec 2023 07:36:04 -0700 Subject: [PATCH 302/390] just update externals --- Externals.cfg | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Externals.cfg b/Externals.cfg index 01537b54197..f9fa6698dce 100644 --- a/Externals.cfg +++ b/Externals.cfg @@ -1,19 +1,19 @@ [ccs_config] -tag = ccs_config_cesm0.0.76 +tag = ccs_config_cesm0.0.88 protocol = git repo_url = https://github.com/ESMCI/ccs_config_cesm local_path = ccs_config required = True [cmeps] -tag = cmeps0.14.38 +tag = cmeps0.14.47 protocol = git repo_url = https://github.com/ESCOMP/CMEPS.git local_path = components/cmeps required = True [cdeps] -tag = cdeps1.0.19 +tag = cdeps1.0.26 protocol = git repo_url = https://github.com/ESCOMP/CDEPS.git local_path = components/cdeps @@ -21,7 +21,7 @@ externals = Externals_CDEPS.cfg required = True [cpl7] -tag = cpl77.0.6 +tag = cpl77.0.8 protocol = git repo_url = https://github.com/ESCOMP/CESM_CPL7andDataComps local_path = components/cpl7 @@ -42,7 +42,7 @@ local_path = libraries/mct required = True [parallelio] -tag = pio2_6_0 +tag = pio2_6_2 protocol = git repo_url = https://github.com/NCAR/ParallelIO local_path = libraries/parallelio From d7ff4a496a3673ecca6679f4ae8396cc55709996 Mon Sep 17 00:00:00 2001 From: James Edwards Date: Wed, 20 Dec 2023 14:44:23 -0700 Subject: [PATCH 303/390] need to update share tag --- Externals.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Externals.cfg b/Externals.cfg index f9fa6698dce..1941bd3d53a 100644 --- a/Externals.cfg +++ b/Externals.cfg @@ -28,7 +28,7 @@ local_path = components/cpl7 required = True [share] -tag = share1.0.17 +tag = share1.0.18 protocol = git repo_url = https://github.com/ESCOMP/CESM_share local_path = share From ba270678367cf570bf82619a21865cf257900667 Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Wed, 20 Dec 2023 14:33:28 -0800 Subject: [PATCH 304/390] Fixes CIME Externals.cfg for all CESM testing --- docker/entrypoint.sh | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index e57ed141718..86df76f2276 100755 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -27,7 +27,7 @@ function clone_repo() { extras="${extras} --depth 1" fi - echo "Cloning branch ${branch} of ${repo} into ${path} using ${flags}" + echo "Cloning branch ${branch} of ${repo} into ${path} with flags: ${flags}" git clone -b "${branch}" ${extras} "${repo}" "${path}" || true } @@ -156,17 +156,21 @@ function init_cesm() { clone_repo "${CESM_REPO}" "${install_path}" "${CESM_BRANCH:-master}" fi - cd "${install_path}" + pushd "${install_path}" + + pushd "${install_path}/cime" - echo "Checking out externals" + echo "Checking out externals from `pwd`" - "${install_path}/manage_externals/checkout_externals" + "${install_path}/manage_externals/checkout_externals" -v + + popd fixup_mct "${install_path}/libraries/mct" update_cime "${install_path}/cime/" - cd "${install_path}/cime" + pushd "${install_path}/cime" # Need to run manage_externals again incase branch changes externals instructions # "${install_path}/manage_externals/checkout_externals -e cime/Externals_cime.cfg" @@ -203,7 +207,7 @@ function init_cime() { cd "${install_path}" - "/src/CESM/manage_externals/checkout_externals" + "/src/CESM/manage_externals/checkout_externals" -v fixup_mct "${install_path}/libraries/mct" From db2597c20cb68e5bad98618863c3686f77963e7f Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Wed, 20 Dec 2023 14:37:42 -0800 Subject: [PATCH 305/390] Fixes cancelling system tests when a model/driver fails --- .github/workflows/testing.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index 261f40d443a..725f8a91832 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -101,6 +101,8 @@ jobs: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} strategy: + # allow all jobs to finish + fail-fast: false matrix: model: ["e3sm", "cesm"] driver: ["mct", "nuopc"] From 02fa6026a6ae0d5ea1b374a9cc04590a5a2cde7e Mon Sep 17 00:00:00 2001 From: James Foucar Date: Wed, 3 Jan 2024 12:28:27 -0700 Subject: [PATCH 306/390] Re-add newcase setup to create_clone It's removal in #4546 seems to have broken a number of less-common test types (ERR, MCC, IRT, PRE). --- CIME/case/case_clone.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CIME/case/case_clone.py b/CIME/case/case_clone.py index 7b81e0e91b5..6c7392026b3 100644 --- a/CIME/case/case_clone.py +++ b/CIME/case/case_clone.py @@ -219,6 +219,8 @@ def create_clone( ) ) + newcase.case_setup() + return newcase From 58b7cab30d17b30ccb7fb5d85c319839420f453c Mon Sep 17 00:00:00 2001 From: James Foucar Date: Wed, 3 Jan 2024 13:53:15 -0700 Subject: [PATCH 307/390] Call case2 setup in system_tests_compare_two .. so all the subclasses don't have to remember to do it. --- CIME/SystemTests/erp.py | 4 ---- CIME/SystemTests/nck.py | 1 - CIME/SystemTests/pea.py | 1 - CIME/SystemTests/pem.py | 1 - CIME/SystemTests/pet.py | 5 ----- CIME/SystemTests/seq.py | 1 - CIME/SystemTests/system_tests_compare_two.py | 4 ++++ CIME/case/case_clone.py | 2 -- 8 files changed, 4 insertions(+), 15 deletions(-) diff --git a/CIME/SystemTests/erp.py b/CIME/SystemTests/erp.py index f549f9e116e..6d58248c138 100644 --- a/CIME/SystemTests/erp.py +++ b/CIME/SystemTests/erp.py @@ -42,10 +42,6 @@ def _case_two_setup(self): self._case.set_value("ROOTPE_{}".format(comp), int(rootpe / 2)) RestartTest._case_two_setup(self) - self._case.case_setup(test_mode=True, reset=True) - # Note, some components, like CESM-CICE, have - # decomposition information in env_build.xml that - # needs to be regenerated for the above new tasks and thread counts def _case_one_custom_postrun_action(self): self.copy_case1_restarts_to_case2() diff --git a/CIME/SystemTests/nck.py b/CIME/SystemTests/nck.py index 5a391b5ecf7..f75a2914215 100644 --- a/CIME/SystemTests/nck.py +++ b/CIME/SystemTests/nck.py @@ -61,4 +61,3 @@ def _case_two_setup(self): if rootpe > 1: self._case.set_value("ROOTPE_{}".format(comp), int(rootpe - ntasks)) self._case.set_value("NTASKS_{}".format(comp), ntasks * 2) - self._case.case_setup(test_mode=True, reset=True) diff --git a/CIME/SystemTests/pea.py b/CIME/SystemTests/pea.py index cc9509e4e5b..4fb3a4569ca 100644 --- a/CIME/SystemTests/pea.py +++ b/CIME/SystemTests/pea.py @@ -48,4 +48,3 @@ def _case_two_setup(self): if os.path.isfile("Macros"): os.remove("Macros") - self._case.case_setup(test_mode=True, reset=True) diff --git a/CIME/SystemTests/pem.py b/CIME/SystemTests/pem.py index fc8317f432f..d98f9e4a2c7 100644 --- a/CIME/SystemTests/pem.py +++ b/CIME/SystemTests/pem.py @@ -42,4 +42,3 @@ def _case_two_setup(self): if ntasks > 1: self._case.set_value("NTASKS_{}".format(comp), int(ntasks / 2)) self._case.set_value("ROOTPE_{}".format(comp), int(rootpe / 2)) - self._case.case_setup(test_mode=True, reset=True) diff --git a/CIME/SystemTests/pet.py b/CIME/SystemTests/pet.py index 7dbaa9af79c..432d7c99303 100644 --- a/CIME/SystemTests/pet.py +++ b/CIME/SystemTests/pet.py @@ -34,12 +34,7 @@ def _case_one_setup(self): if self._case.get_value("NTHRDS_{}".format(comp)) <= 1: self._case.set_value("NTHRDS_{}".format(comp), 2) - # Need to redo case_setup because we may have changed the number of threads - def _case_two_setup(self): # Do a run with all threads set to 1 for comp in self._case.get_values("COMP_CLASSES"): self._case.set_value("NTHRDS_{}".format(comp), 1) - - # Need to redo case_setup because we may have changed the number of threads - self._case.case_setup(reset=True, test_mode=True) diff --git a/CIME/SystemTests/seq.py b/CIME/SystemTests/seq.py index 304932d7d14..7413f900899 100644 --- a/CIME/SystemTests/seq.py +++ b/CIME/SystemTests/seq.py @@ -49,4 +49,3 @@ def _case_two_setup(self): rootpe += newntasks self._case.flush() - self._case.case_setup(test_mode=True, reset=True) diff --git a/CIME/SystemTests/system_tests_compare_two.py b/CIME/SystemTests/system_tests_compare_two.py index c58cfa372ba..5eaac4948e1 100644 --- a/CIME/SystemTests/system_tests_compare_two.py +++ b/CIME/SystemTests/system_tests_compare_two.py @@ -24,6 +24,9 @@ (2) _case_two_setup This method will be called to set up case 2, the "test" case +Note that the base class will always call case_setup(reset=True) on +both case1 and case2 during setup. + In addition, they MAY require the following methods: (1) _common_setup @@ -559,6 +562,7 @@ def _setup_cases(self): self._activate_case2() self._common_setup() self._case_two_setup() + self._case2.case_setup(test_mode=True, reset=True) fix_single_exe_case(self._case2) diff --git a/CIME/case/case_clone.py b/CIME/case/case_clone.py index 6c7392026b3..7b81e0e91b5 100644 --- a/CIME/case/case_clone.py +++ b/CIME/case/case_clone.py @@ -219,8 +219,6 @@ def create_clone( ) ) - newcase.case_setup() - return newcase From 3a4dcdceadde042050158e9f995d28fa628736e2 Mon Sep 17 00:00:00 2001 From: James Foucar Date: Thu, 4 Jan 2024 10:59:46 -0700 Subject: [PATCH 308/390] Fix .env_mach_specific.sh when unsetting env When an env var value is None, this tells CIME to unset the environment. This use case was not being handled in the creation of .env_mach_specific.sh. --- CIME/XML/env_mach_specific.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/CIME/XML/env_mach_specific.py b/CIME/XML/env_mach_specific.py index 4652f2a7d0a..0592921ab9a 100644 --- a/CIME/XML/env_mach_specific.py +++ b/CIME/XML/env_mach_specific.py @@ -277,19 +277,25 @@ def make_env_mach_specific_file(self, shell, case, output_dir=""): if env_value.startswith("sh"): lines.append("{}".format(env_name)) else: - lines.append("export {}={}".format(env_name, env_value)) + if env_value is None: + lines.append("unset {}".format(env_name)) + else: + lines.append("export {}={}".format(env_name, env_value)) elif shell == "csh": if env_name == "source": if env_value.startswith("csh"): lines.append("{}".format(env_name)) else: - lines.append("setenv {} {}".format(env_name, env_value)) + if env_value is None: + lines.append("unsetenv {}".format(env_name)) + else: + lines.append("setenv {} {}".format(env_name, env_value)) else: expect(False, "Unknown shell type: '{}'".format(shell)) with open(os.path.join(output_dir, filename), "w") as fd: - fd.write("\n".join(lines)) + fd.write("\n".join(lines) + "\n") # Private API From 9b688a4790ec6f8b5f18ed4ae67aa5dd1e8253a9 Mon Sep 17 00:00:00 2001 From: James Foucar Date: Wed, 10 Jan 2024 09:32:19 -0700 Subject: [PATCH 309/390] Update cprnc submodule --- CIME/non_py/cprnc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CIME/non_py/cprnc b/CIME/non_py/cprnc index 5e2ca5bb5a2..321789409de 160000 --- a/CIME/non_py/cprnc +++ b/CIME/non_py/cprnc @@ -1 +1 @@ -Subproject commit 5e2ca5bb5a2956339b60f270df868593e954420b +Subproject commit 321789409deca7c572a4db533bff80359c5dbd81 From 9456121d107f740470cce261e8631d3908d9c049 Mon Sep 17 00:00:00 2001 From: James Foucar Date: Fri, 12 Jan 2024 16:04:19 -0700 Subject: [PATCH 310/390] If chksum is off, don't try to download_checksum_file --- CIME/case/check_input_data.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CIME/case/check_input_data.py b/CIME/case/check_input_data.py index d099a6da046..5bff3823d7c 100644 --- a/CIME/case/check_input_data.py +++ b/CIME/case/check_input_data.py @@ -212,7 +212,7 @@ def _check_all_input_data_impl( chksum=chksum and chksum_found, ) if download and not success: - if not chksum: + if chksum: chksum_found = _download_checksum_file(self.get_value("RUNDIR")) success = _downloadfromserver(self, input_data_root, data_list_dir) From aedb8b9983b3c83d3454df6b1d34c40e0c62ac7d Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Tue, 16 Jan 2024 14:11:15 -0800 Subject: [PATCH 311/390] Test using dynamic container --- .github/workflows/testing.yml | 46 +++++++++++++++++++++++++++++++++-- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index 725f8a91832..9c54121cf72 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -9,6 +9,7 @@ on: - 'scripts/**' - 'tools/**' - 'utils/**' + - 'docker/**' pull_request: branches: @@ -18,6 +19,7 @@ on: - 'scripts/**' - 'tools/**' - 'utils/**' + - 'docker/**' workflow_dispatch: @@ -30,6 +32,45 @@ permissions: packages: read jobs: + # Only build container if there has been a change. + build-containers: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + steps: + - name: Checkout code + uses: actions/checkout@v3 + - name: Set up QEMU + uses: docker/setup-qemu-action@v2 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + - name: Login to DockerHub + uses: docker/login-action@v2 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Docker meta + id: meta + uses: docker/metadata-action@v4 + with: + images: ghcr.io/ESMCI/cime + flavor: | + latest=auto + tags: | + type=sha + - name: Build and push + uses: docker/build-push-action@v3 + with: + target: base + context: docker/ + push: ${{ github.event_name == 'push' }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max + pre-commit: runs-on: ubuntu-latest timeout-minutes: 2 @@ -57,7 +98,7 @@ jobs: runs-on: ubuntu-latest if: ${{ always() && ! cancelled() }} container: - image: ghcr.io/esmci/cime:latest + image: ghcr.io/esmci/cime:${{ github.sha }} credentials: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} @@ -94,7 +135,8 @@ jobs: # Run system tests system-testing: runs-on: ubuntu-latest - if: ${{ always() && ! cancelled() }} + if: ${{ never() }} + # if: ${{ always() && ! cancelled() }} container: image: ghcr.io/esmci/cime:latest credentials: From b09701ee20881139f17c652c410548fc1105cc2f Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Tue, 16 Jan 2024 14:14:21 -0800 Subject: [PATCH 312/390] Fixes workflow syntax --- .github/workflows/testing.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index 9c54121cf72..0480707ca4a 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -135,7 +135,8 @@ jobs: # Run system tests system-testing: runs-on: ubuntu-latest - if: ${{ never() }} + # temporary disable + if: ${{ github.sha == "" }} # if: ${{ always() && ! cancelled() }} container: image: ghcr.io/esmci/cime:latest From 936cd188584ffde37256772f9c549286a38c95f6 Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Tue, 16 Jan 2024 14:15:16 -0800 Subject: [PATCH 313/390] Fixes workflow syntax --- .github/workflows/testing.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index 0480707ca4a..01234d86ecd 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -136,7 +136,7 @@ jobs: system-testing: runs-on: ubuntu-latest # temporary disable - if: ${{ github.sha == "" }} + if: ${{ github.sha == '' }} # if: ${{ always() && ! cancelled() }} container: image: ghcr.io/esmci/cime:latest From 199602bb214b25c9134ce3644fdee2d8df6b54c3 Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Tue, 16 Jan 2024 14:51:27 -0800 Subject: [PATCH 314/390] Fixes pushing container --- .github/workflows/testing.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index 01234d86ecd..2c7763d9986 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -65,7 +65,8 @@ jobs: with: target: base context: docker/ - push: ${{ github.event_name == 'push' }} + # push: ${{ github.event_name == 'push' }} + push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} cache-from: type=gha From 4757ac76ba399b3fc80d5c642932c215ca983ca8 Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Tue, 16 Jan 2024 15:14:13 -0800 Subject: [PATCH 315/390] Fixes container tag format --- .github/workflows/testing.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index 2c7763d9986..adf792fb7e4 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -59,7 +59,7 @@ jobs: flavor: | latest=auto tags: | - type=sha + type=sha,format=long - name: Build and push uses: docker/build-push-action@v3 with: @@ -99,7 +99,7 @@ jobs: runs-on: ubuntu-latest if: ${{ always() && ! cancelled() }} container: - image: ghcr.io/esmci/cime:${{ github.sha }} + image: ghcr.io/esmci/cime:sha-${{ github.sha }} credentials: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} From 520d83b978eb3d1003f3e8e277416a57226e53d4 Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Tue, 16 Jan 2024 17:00:16 -0800 Subject: [PATCH 316/390] Adds temp file to trigger build --- TO_DELETE | 1 + 1 file changed, 1 insertion(+) create mode 100644 TO_DELETE diff --git a/TO_DELETE b/TO_DELETE new file mode 100644 index 00000000000..980916ec6d8 --- /dev/null +++ b/TO_DELETE @@ -0,0 +1 @@ +Tue Jan 16 05:00:02 PM PST 2024 From 8a31c9e3f2d2d80eea92f42014bab48ba83f6186 Mon Sep 17 00:00:00 2001 From: James Foucar Date: Wed, 17 Jan 2024 10:58:06 -0700 Subject: [PATCH 317/390] jenkins_generic_job: Should probe branch of main src repo, not cime repo --- CIME/Tools/jenkins_generic_job | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CIME/Tools/jenkins_generic_job b/CIME/Tools/jenkins_generic_job index 9787aedab13..ec93bfca238 100755 --- a/CIME/Tools/jenkins_generic_job +++ b/CIME/Tools/jenkins_generic_job @@ -38,7 +38,7 @@ OR CIME.utils.setup_standard_logging_options(parser) - default_baseline = CIME.utils.get_current_branch(repo=CIME.utils.get_cime_root()) + default_baseline = CIME.utils.get_current_branch(repo=CIME.utils.get_src_root()) if default_baseline is not None: default_baseline = default_baseline.replace(".", "_").replace( "/", "_" From f0e6136681b9bd68e98aa11fadd34ebbdbcb79f2 Mon Sep 17 00:00:00 2001 From: James Edwards Date: Fri, 19 Jan 2024 07:20:20 -0700 Subject: [PATCH 318/390] cprnc needs to have a tag for cesm --- CIME/non_py/cprnc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CIME/non_py/cprnc b/CIME/non_py/cprnc index 321789409de..845bb11f142 160000 --- a/CIME/non_py/cprnc +++ b/CIME/non_py/cprnc @@ -1 +1 @@ -Subproject commit 321789409deca7c572a4db533bff80359c5dbd81 +Subproject commit 845bb11f14260006899117b3b56f5fa0c9b6171b From a127d2d76616f329eb9ee34dd993517c04dab513 Mon Sep 17 00:00:00 2001 From: James Edwards Date: Mon, 22 Jan 2024 15:01:06 -0700 Subject: [PATCH 319/390] check .cime for a machine directory first --- CIME/XML/machines.py | 12 +++++++++++- CIME/non_py/cprnc | 2 +- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/CIME/XML/machines.py b/CIME/XML/machines.py index ec26438f1f0..0fab6dc14e3 100644 --- a/CIME/XML/machines.py +++ b/CIME/XML/machines.py @@ -318,7 +318,7 @@ def set_machine(self, machine, schema=None): return machine elif self.get_version() == 3: machines_file = os.path.join( - self.machines_dir, machine, "config_machines.xml" + os.environ.get("HOME"), ".cime", machine, "config_machines.xml" ) if os.path.isfile(machines_file): GenericXML.read( @@ -326,6 +326,16 @@ def set_machine(self, machine, schema=None): machines_file, schema=schema, ) + else: + machines_file = os.path.join( + self.machines_dir, machine, "config_machines.xml" + ) + if os.path.isfile(machines_file): + GenericXML.read( + self, + machines_file, + schema=schema, + ) self.machine_node = super(Machines, self).get_child( "machine", {"MACH": machine}, diff --git a/CIME/non_py/cprnc b/CIME/non_py/cprnc index 845bb11f142..d17494561ac 160000 --- a/CIME/non_py/cprnc +++ b/CIME/non_py/cprnc @@ -1 +1 @@ -Subproject commit 845bb11f14260006899117b3b56f5fa0c9b6171b +Subproject commit d17494561ace91fc86387c83591c0382a2c79566 From fbcfe8b11e935acba5225b39c35f06cd2e8d1b55 Mon Sep 17 00:00:00 2001 From: Chris Fischer Date: Mon, 22 Jan 2024 15:23:09 -0700 Subject: [PATCH 320/390] Switch from drv.log to med.log for nuopc COMP tests. --- CIME/SystemTests/nodefail.py | 2 +- CIME/SystemTests/system_tests_common.py | 2 +- CIME/baselines/performance.py | 2 +- CIME/case/case_run.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CIME/SystemTests/nodefail.py b/CIME/SystemTests/nodefail.py index 35f0ca3c8a6..d975cfc5bfd 100644 --- a/CIME/SystemTests/nodefail.py +++ b/CIME/SystemTests/nodefail.py @@ -24,7 +24,7 @@ def _restart_fake_phase(self): exeroot = self._case.get_value("EXEROOT") driver = self._case.get_value("COMP_INTERFACE") if driver == "nuopc": - logname = "drv" + logname = "med" else: logname = "cpl" fake_exe = """#!/bin/bash diff --git a/CIME/SystemTests/system_tests_common.py b/CIME/SystemTests/system_tests_common.py index 568f3b3647d..cbdf661e49c 100644 --- a/CIME/SystemTests/system_tests_common.py +++ b/CIME/SystemTests/system_tests_common.py @@ -110,7 +110,7 @@ def __init__( self._init_locked_files(caseroot, expected) self._skip_pnl = False self._cpllog = ( - "drv" if self._case.get_value("COMP_INTERFACE") == "nuopc" else "cpl" + "med" if self._case.get_value("COMP_INTERFACE") == "nuopc" else "cpl" ) self._ninja = False self._dry_run = False diff --git a/CIME/baselines/performance.py b/CIME/baselines/performance.py index ad7895f3eff..55092f397e2 100644 --- a/CIME/baselines/performance.py +++ b/CIME/baselines/performance.py @@ -329,7 +329,7 @@ def get_latest_cpl_logs(case): """ coupler_log_path = case.get_value("RUNDIR") - cpllog_name = "drv" if case.get_value("COMP_INTERFACE") == "nuopc" else "cpl" + cpllog_name = "med" if case.get_value("COMP_INTERFACE") == "nuopc" else "cpl" cpllogs = glob.glob(os.path.join(coupler_log_path, "{}*.log.*".format(cpllog_name))) diff --git a/CIME/case/case_run.py b/CIME/case/case_run.py index b7518090504..6dc2c941132 100644 --- a/CIME/case/case_run.py +++ b/CIME/case/case_run.py @@ -304,7 +304,7 @@ def _post_run_check(case, lid): if fv3_standalone: file_prefix = model else: - file_prefix = "drv" + file_prefix = "med" else: file_prefix = "cpl" From 4e0c71348fb8c97103cbf32ea150eb520fbb401b Mon Sep 17 00:00:00 2001 From: Chris Fischer Date: Mon, 22 Jan 2024 16:34:30 -0700 Subject: [PATCH 321/390] Update Externals.cfg --- Externals.cfg | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Externals.cfg b/Externals.cfg index 1941bd3d53a..ab13820bfc3 100644 --- a/Externals.cfg +++ b/Externals.cfg @@ -1,12 +1,12 @@ [ccs_config] -tag = ccs_config_cesm0.0.88 +tag = ccs_config_cesm0.0.89 protocol = git repo_url = https://github.com/ESMCI/ccs_config_cesm local_path = ccs_config required = True [cmeps] -tag = cmeps0.14.47 +tag = cmeps0.14.49 protocol = git repo_url = https://github.com/ESCOMP/CMEPS.git local_path = components/cmeps From aadeaa2421739b31684c890243bb244585e20314 Mon Sep 17 00:00:00 2001 From: Chris Fischer Date: Tue, 23 Jan 2024 16:21:13 -0700 Subject: [PATCH 322/390] Update to ccs_config_cesm0.0.91 --- Externals.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Externals.cfg b/Externals.cfg index ab13820bfc3..5efcc075503 100644 --- a/Externals.cfg +++ b/Externals.cfg @@ -1,5 +1,5 @@ [ccs_config] -tag = ccs_config_cesm0.0.89 +tag = ccs_config_cesm0.0.91 protocol = git repo_url = https://github.com/ESMCI/ccs_config_cesm local_path = ccs_config From 5f675340fff98c7a006f6664e05c62a6c7677020 Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Tue, 23 Jan 2024 17:40:24 -0800 Subject: [PATCH 323/390] Converts to pathlib --- CIME/XML/machines.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/CIME/XML/machines.py b/CIME/XML/machines.py index 0fab6dc14e3..dc56b290ccf 100644 --- a/CIME/XML/machines.py +++ b/CIME/XML/machines.py @@ -7,6 +7,7 @@ from CIME.utils import convert_to_unknown_type, get_cime_config import socket +from pathlib import Path logger = logging.getLogger(__name__) @@ -317,20 +318,20 @@ def set_machine(self, machine, schema=None): if machine == "Query": return machine elif self.get_version() == 3: - machines_file = os.path.join( - os.environ.get("HOME"), ".cime", machine, "config_machines.xml" - ) - if os.path.isfile(machines_file): + machines_file = Path.home() / ".cime" / machine / "config_machines.xml" + + if machines_file.exists(): GenericXML.read( self, machines_file, schema=schema, ) else: - machines_file = os.path.join( - self.machines_dir, machine, "config_machines.xml" + machines_file = ( + Path(self.machines_dir) / machine / "config_machines.xml" ) - if os.path.isfile(machines_file): + + if machines_file.exists(): GenericXML.read( self, machines_file, From 060b8e62b403301fff4c8e5f0436d825d8e092aa Mon Sep 17 00:00:00 2001 From: James Edwards Date: Thu, 25 Jan 2024 15:42:04 -0700 Subject: [PATCH 324/390] fix issue getting correct parameters for job subgroups --- CIME/XML/env_batch.py | 45 ++++++++++++++++++++-------------------- CIME/XML/env_workflow.py | 8 ++++++- CIME/XML/generic_xml.py | 7 +++++-- CIME/case/case.py | 9 ++++++-- CIME/case/case_submit.py | 26 ++++++++++++----------- 5 files changed, 55 insertions(+), 40 deletions(-) diff --git a/CIME/XML/env_batch.py b/CIME/XML/env_batch.py index 7682ab6e1ee..558ff2b0ab4 100644 --- a/CIME/XML/env_batch.py +++ b/CIME/XML/env_batch.py @@ -813,6 +813,7 @@ def submit_jobs( dry_run=dry_run, workflow=workflow, ) + batch_job_id = str(alljobs.index(job)) if dry_run else result depid[job] = batch_job_id jobcmds.append((job, result)) @@ -921,35 +922,33 @@ def _submit_single_job( logger.info("Starting job script {}".format(job)) function_name = job.replace(".", "_") job_name = "." + job - if not dry_run: - args = self._build_run_args( - job, - True, - skip_pnl=skip_pnl, - set_continue_run=resubmit_immediate, - submit_resubmits=workflow and not resubmit_immediate, - ) - try: - if hasattr(case, function_name): - getattr(case, function_name)( - **{k: v for k, (v, _) in args.items()} - ) + args = self._build_run_args( + job, + True, + skip_pnl=skip_pnl, + set_continue_run=resubmit_immediate, + submit_resubmits=workflow and not resubmit_immediate, + ) + try: + if hasattr(case, function_name): + getattr(case, function_name)(**{k: v for k, (v, _) in args.items()}) + else: + expect( + os.path.isfile(job_name), + "Could not find file {}".format(job_name), + ) + if dry_run: + return os.path.join(self._caseroot, job_name) else: - expect( - os.path.isfile(job_name), - "Could not find file {}".format(job_name), - ) run_cmd_no_fail( os.path.join(self._caseroot, job_name), combine_output=True, verbose=True, from_dir=self._caseroot, ) - except Exception as e: - # We don't want exception from the run phases getting into submit phase - logger.warning( - "Exception from {}: {}".format(function_name, str(e)) - ) + except Exception as e: + # We don't want exception from the run phases getting into submit phase + logger.warning("Exception from {}: {}".format(function_name, str(e))) return @@ -1088,10 +1087,10 @@ def _submit_single_job( # add ` before cd $CASEROOT and at end of command submitcmd = submitcmd.replace("cd $CASEROOT", "'cd $CASEROOT") + "'" + submitcmd = case.get_resolved_value(submitcmd, subgroup=job) if dry_run: return submitcmd else: - submitcmd = case.get_resolved_value(submitcmd) logger.info("Submitting job script {}".format(submitcmd)) output = run_cmd_no_fail(submitcmd, combine_output=True) jobid = self.get_job_id(output) diff --git a/CIME/XML/env_workflow.py b/CIME/XML/env_workflow.py index 3c976693639..c59ff23aba4 100644 --- a/CIME/XML/env_workflow.py +++ b/CIME/XML/env_workflow.py @@ -112,7 +112,13 @@ def get_job_specs(self, case, job): if ngpus_per_node > max_gpus_per_node: ngpus_per_node = max_gpus_per_node - return task_count, num_nodes, tasks_per_node, thread_count, ngpus_per_node + return ( + task_count, + num_nodes, + tasks_per_node, + thread_count, + ngpus_per_node, + ) # pylint: disable=arguments-differ def get_value(self, item, attribute=None, resolved=True, subgroup="PRIMARY"): diff --git a/CIME/XML/generic_xml.py b/CIME/XML/generic_xml.py index 3b303047ad3..083743695b3 100644 --- a/CIME/XML/generic_xml.py +++ b/CIME/XML/generic_xml.py @@ -612,7 +612,9 @@ def set_value( return value if valnodes else None - def get_resolved_value(self, raw_value, allow_unresolved_envvars=False): + def get_resolved_value( + self, raw_value, allow_unresolved_envvars=False, subgroup=None + ): """ A value in the xml file may contain references to other xml variables or to environment variables. These are refered to in @@ -662,7 +664,8 @@ def get_resolved_value(self, raw_value, allow_unresolved_envvars=False): logger.debug("find: {}".format(var)) # The overridden versions of this method do not simply return None # so the pylint should not be flagging this - ref = self.get_value(var) # pylint: disable=assignment-from-none + # pylint: disable=assignment-from-none + ref = self.get_value(var, subgroup=subgroup) if ref is not None: logger.debug("resolve: " + str(ref)) diff --git a/CIME/case/case.py b/CIME/case/case.py index 2bf14540205..8b14d33d924 100644 --- a/CIME/case/case.py +++ b/CIME/case/case.py @@ -548,13 +548,17 @@ def get_type_info(self, item): return result - def get_resolved_value(self, item, recurse=0, allow_unresolved_envvars=False): + def get_resolved_value( + self, item, recurse=0, allow_unresolved_envvars=False, subgroup=None + ): num_unresolved = item.count("$") if item else 0 recurse_limit = 10 if num_unresolved > 0 and recurse < recurse_limit: for env_file in self._env_entryid_files: item = env_file.get_resolved_value( - item, allow_unresolved_envvars=allow_unresolved_envvars + item, + allow_unresolved_envvars=allow_unresolved_envvars, + subgroup=subgroup, ) if "$" not in item: return item @@ -563,6 +567,7 @@ def get_resolved_value(self, item, recurse=0, allow_unresolved_envvars=False): item, recurse=recurse + 1, allow_unresolved_envvars=allow_unresolved_envvars, + subgroup=subgroup, ) return item diff --git a/CIME/case/case_submit.py b/CIME/case/case_submit.py index 4ddb26e3056..7893d2d3aae 100644 --- a/CIME/case/case_submit.py +++ b/CIME/case/case_submit.py @@ -12,8 +12,6 @@ from CIME.locked_files import unlock_file, lock_file from CIME.test_status import * -import socket - logger = logging.getLogger(__name__) @@ -39,6 +37,7 @@ def _submit( batch_args=None, workflow=True, chksum=False, + dryrun=False, ): if job is None: job = case.get_first_job() @@ -164,9 +163,6 @@ def _submit( case.check_case(skip_pnl=skip_pnl, chksum=chksum) if job == case.get_primary_job(): case.check_DA_settings() - if case.get_value("MACH") == "mira": - with open(".original_host", "w") as fd: - fd.write(socket.gethostname()) # Load Modules case.load_env() @@ -185,16 +181,20 @@ def _submit( mail_type=mail_type, batch_args=batch_args, workflow=workflow, + dry_run=dryrun, ) - xml_jobids = [] - for jobname, jobid in job_ids.items(): - logger.info("Submitted job {} with id {}".format(jobname, jobid)) - if jobid: - xml_jobids.append("{}:{}".format(jobname, jobid)) + if dryrun: + for job in job_ids: + xml_jobids.append("{}:{}".format(job[0], job[1])) + else: + for jobname, jobid in job_ids.items(): + logger.info("Submitted job {} with id {}".format(jobname, jobid)) + if jobid: + xml_jobids.append("{}:{}".format(jobname, jobid)) xml_jobid_text = ", ".join(xml_jobids) - if xml_jobid_text: + if xml_jobid_text and not dryrun: case.set_value("JOB_IDS", xml_jobid_text) return xml_jobid_text @@ -214,6 +214,7 @@ def submit( batch_args=None, workflow=True, chksum=False, + dryrun=False, ): if resubmit_immediate and self.get_value("MACH") in ["mira", "cetus"]: logger.warning( @@ -266,6 +267,7 @@ def submit( batch_args=batch_args, workflow=workflow, chksum=chksum, + dryrun=dryrun, ) run_and_log_case_status( functor, @@ -353,7 +355,7 @@ def check_case(self, skip_pnl=False, chksum=False): expect( self.get_value("BUILD_COMPLETE"), - "Build complete is " "not True please rebuild the model by calling case.build", + "Build complete is not True please rebuild the model by calling case.build", ) logger.info("Check case OK") From 9480100bba935e38099b5e4bd564ba8d772f533d Mon Sep 17 00:00:00 2001 From: James Edwards Date: Thu, 25 Jan 2024 16:31:18 -0700 Subject: [PATCH 325/390] fix tests --- CIME/case/case.py | 2 +- CIME/tests/test_unit_case.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CIME/case/case.py b/CIME/case/case.py index 8b14d33d924..e08b5ffe2c4 100644 --- a/CIME/case/case.py +++ b/CIME/case/case.py @@ -481,7 +481,7 @@ def get_value(self, item, attribute=None, resolved=True, subgroup=None): if result is not None: if resolved and isinstance(result, str): - result = self.get_resolved_value(result) + result = self.get_resolved_value(result, subgroup=subgroup) vtype = env_file.get_type_info(item) if vtype is not None and vtype != "char": result = convert_to_type(result, vtype, item) diff --git a/CIME/tests/test_unit_case.py b/CIME/tests/test_unit_case.py index 820bd9ac91c..e7f8c9a2ead 100755 --- a/CIME/tests/test_unit_case.py +++ b/CIME/tests/test_unit_case.py @@ -86,6 +86,7 @@ def test_submit( batch_args=None, workflow=True, chksum=True, + dryrun=False, ) From da717c3fb285b7ec5025deb0901bd3244d02d6a9 Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Tue, 30 Jan 2024 12:07:19 -0800 Subject: [PATCH 326/390] Fixes missing directory --- docker/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 6bcee0d1e0f..ed30bb9739b 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -84,7 +84,7 @@ RUN curl -L -k -o "${PWD}/pnetcdf.tar.gz" \ make install && \ rm -rf "${PWD}/pnetcdf" -RUN mkdir /root/.cime +RUN mkdir -p /root/.cime /storage/timings COPY config_machines.xml /root/.cime/ COPY docker.cmake /root/.cime/ From b99db95b63650cf2f490d94ed9eef12ba8778df3 Mon Sep 17 00:00:00 2001 From: James Edwards Date: Fri, 2 Feb 2024 09:29:35 -0700 Subject: [PATCH 327/390] black reformat --- CIME/case/case_setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CIME/case/case_setup.py b/CIME/case/case_setup.py index 6bb16e46e4a..c9017db915b 100644 --- a/CIME/case/case_setup.py +++ b/CIME/case/case_setup.py @@ -427,8 +427,8 @@ def _case_setup_impl( logger.debug("Running cam.case_setup.py") run_cmd_no_fail( "python {cam}/cime_config/cam.case_setup.py {cam} {case}".format( - cam=camroot, case=caseroot - ) + cam=camroot, case=caseroot + ) ) _build_usernl_files(case, "drv", "cpl") From 7f529eab7f06512f7aac2837c790f39dd8cdbd2c Mon Sep 17 00:00:00 2001 From: James Foucar Date: Fri, 2 Feb 2024 14:24:38 -0700 Subject: [PATCH 328/390] case.submit: Fix no-batch mode No output fron the batch env is provided to get_job_id when no_batch is on. Treat that case the same as if batchtype is none. --- CIME/XML/env_batch.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CIME/XML/env_batch.py b/CIME/XML/env_batch.py index 558ff2b0ab4..ecaf1764803 100644 --- a/CIME/XML/env_batch.py +++ b/CIME/XML/env_batch.py @@ -1124,8 +1124,14 @@ def get_job_id(self, output): jobid_pattern is not None, "Could not find jobid_pattern in env_batch.xml", ) + + # If no output was provided, skip the search. This could + # be because --no-batch was provided. + if not output: + return output else: return output + search_match = re.search(jobid_pattern, output) expect( search_match is not None, From 635f78609bda0be698066e03d7b78da77cc25ec2 Mon Sep 17 00:00:00 2001 From: James Foucar Date: Fri, 2 Feb 2024 14:26:47 -0700 Subject: [PATCH 329/390] Update cprnc to 1.05 --- CIME/non_py/cprnc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CIME/non_py/cprnc b/CIME/non_py/cprnc index d17494561ac..e3eaa5346ec 160000 --- a/CIME/non_py/cprnc +++ b/CIME/non_py/cprnc @@ -1 +1 @@ -Subproject commit d17494561ace91fc86387c83591c0382a2c79566 +Subproject commit e3eaa5346ecd50aef8b60fdf54b98cb1c13a24b1 From 5810ea94d899ee9ef17ea982ac28612c37e22678 Mon Sep 17 00:00:00 2001 From: James Foucar Date: Fri, 2 Feb 2024 14:49:51 -0700 Subject: [PATCH 330/390] We never want file_util.copy_file to be verbose --- CIME/utils.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CIME/utils.py b/CIME/utils.py index 1a32319c8d1..18956cb4374 100644 --- a/CIME/utils.py +++ b/CIME/utils.py @@ -1413,6 +1413,7 @@ def safe_copy(src_path, tgt_path, preserve_meta=True): tgt_path, preserve_mode=preserve_meta, preserve_times=preserve_meta, + verbose=0 ) else: # I am not the owner, just copy file contents @@ -1426,6 +1427,7 @@ def safe_copy(src_path, tgt_path, preserve_meta=True): tgt_path, preserve_mode=preserve_meta, preserve_times=preserve_meta, + verbose=0 ) # If src file was executable, then the tgt file should be too From fabc29746e57a2406f4820cb8fc7fb9f5b08ff54 Mon Sep 17 00:00:00 2001 From: James Foucar Date: Fri, 2 Feb 2024 15:13:18 -0700 Subject: [PATCH 331/390] black --- CIME/utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CIME/utils.py b/CIME/utils.py index 18956cb4374..5b186cf7085 100644 --- a/CIME/utils.py +++ b/CIME/utils.py @@ -1413,7 +1413,7 @@ def safe_copy(src_path, tgt_path, preserve_meta=True): tgt_path, preserve_mode=preserve_meta, preserve_times=preserve_meta, - verbose=0 + verbose=0, ) else: # I am not the owner, just copy file contents @@ -1427,7 +1427,7 @@ def safe_copy(src_path, tgt_path, preserve_meta=True): tgt_path, preserve_mode=preserve_meta, preserve_times=preserve_meta, - verbose=0 + verbose=0, ) # If src file was executable, then the tgt file should be too From 03dfa2cc1741c6652f1a19b60d42f507770acd0b Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Fri, 2 Feb 2024 18:09:05 -0800 Subject: [PATCH 332/390] Updates container to support v2/v3 config_machines --- .gitignore | 1 + .../config_machines.v2.xml} | 0 docker/.cime/config_machines.v3.xml | 7 + docker/{ => .cime}/docker.cmake | 0 docker/.cime/docker/config_machines.xml | 39 +++ docker/Dockerfile | 77 +++--- docker/entrypoint.sh | 222 ++---------------- 7 files changed, 121 insertions(+), 225 deletions(-) rename docker/{config_machines.xml => .cime/config_machines.v2.xml} (100%) create mode 100644 docker/.cime/config_machines.v3.xml rename docker/{ => .cime}/docker.cmake (100%) create mode 100644 docker/.cime/docker/config_machines.xml diff --git a/.gitignore b/.gitignore index f6351cf8996..0f54f714f75 100644 --- a/.gitignore +++ b/.gitignore @@ -27,3 +27,4 @@ components libraries share test_coverage/** +*.bak diff --git a/docker/config_machines.xml b/docker/.cime/config_machines.v2.xml similarity index 100% rename from docker/config_machines.xml rename to docker/.cime/config_machines.v2.xml diff --git a/docker/.cime/config_machines.v3.xml b/docker/.cime/config_machines.v3.xml new file mode 100644 index 00000000000..98a0cba3f66 --- /dev/null +++ b/docker/.cime/config_machines.v3.xml @@ -0,0 +1,7 @@ + + + + + docker + + diff --git a/docker/docker.cmake b/docker/.cime/docker.cmake similarity index 100% rename from docker/docker.cmake rename to docker/.cime/docker.cmake diff --git a/docker/.cime/docker/config_machines.xml b/docker/.cime/docker/config_machines.xml new file mode 100644 index 00000000000..e15fd7eaa49 --- /dev/null +++ b/docker/.cime/docker/config_machines.xml @@ -0,0 +1,39 @@ + + Docker + LINUX + + gnu,gnuX + openmpi + CIME + /storage/timings + CIME + /storage/cases + /storage/inputdata + /storage/inputdata-clmforc + /storage/archive/$CASE + /storage/baselines/$COMPILER + /storage/tools/cprnc + make + 4 + e3sm_developer + none + boutte3@llnl.gov + 8 + 8 + + mpiexec + + -n {{ total_tasks }} + --oversubscribe + + + + $CASEROOT/run + $CASEROOT/bld + + 1 + 1 + /opt/conda + /opt/conda + + diff --git a/docker/Dockerfile b/docker/Dockerfile index ed30bb9739b..a148d921d4c 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,31 +1,13 @@ ARG MAMBAFORGE_VERSION=4.14.0-0 FROM condaforge/mambaforge:${MAMBAFORGE_VERSION} AS base -ARG PNETCDF_VERSION=1.12.3 -ENV PNETCDF_VERSION=${PNETCDF_VERSION} - -ARG LIBNETCDF_VERSION=4.9.1 -ENV LIBNETCDF_VERSION=${LIBNETCDF_VERSION} - -ARG NETCDF_FORTRAN_VERSION=* -ENV NETCDF_FORTRAN_VERSION=${NETCDF_FORTRAN_VERSION} - -ARG ESMF_VERSION=* -ENV ESMF_VERSION=${ESMF_VERSION} - -ARG GCC_VERSION=10.* -ENV GCC_VERSION=${GCC_VERSION} - -ENV USER=root -ENV LOGNAME=root - SHELL ["/bin/bash", "-c"] # First layer as they never change, required for E3SM testing, TODO: fix in unittesting as well -RUN mkdir -p /cache/cpl/gridmaps/oQU240 /cache/share/domains && \ - wget -O /cache/cpl/gridmaps/oQU240/map_oQU240_to_ne4np4_aave.160614.nc https://portal.nersc.gov/project/e3sm/inputdata/cpl/gridmaps/oQU240/map_oQU240_to_ne4np4_aave.160614.nc && \ - wget -O /cache/share/domains/domain.ocn.ne4np4_oQU240.160614.nc https://portal.nersc.gov/project/e3sm/inputdata/share/domains/domain.ocn.ne4np4_oQU240.160614.nc && \ - wget -O /cache/share/domains/domain.lnd.ne4np4_oQU240.160614.nc https://portal.nersc.gov/project/e3sm/inputdata/share/domains/domain.lnd.ne4np4_oQU240.160614.nc +RUN mkdir -p /storage/inputdata/cpl/gridmaps/oQU240 /storage/inputdata/share/domains && \ + wget -O /storage/inputdata/cpl/gridmaps/oQU240/map_oQU240_to_ne4np4_aave.160614.nc https://portal.nersc.gov/project/e3sm/inputdata/cpl/gridmaps/oQU240/map_oQU240_to_ne4np4_aave.160614.nc && \ + wget -O /storage/inputdata/share/domains/domain.ocn.ne4np4_oQU240.160614.nc https://portal.nersc.gov/project/e3sm/inputdata/share/domains/domain.ocn.ne4np4_oQU240.160614.nc && \ + wget -O /storage/inputdata/share/domains/domain.lnd.ne4np4_oQU240.160614.nc https://portal.nersc.gov/project/e3sm/inputdata/share/domains/domain.lnd.ne4np4_oQU240.160614.nc # Install common packages RUN mamba install --yes -c conda-forge \ @@ -43,6 +25,16 @@ RUN mamba install --yes -c conda-forge \ openssh && \ rm -rf /opt/conda/pkgs/* +# Compilers and libraries +ARG LIBNETCDF_VERSION=4.9.1 +ENV LIBNETCDF_VERSION=${LIBNETCDF_VERSION} +ARG NETCDF_FORTRAN_VERSION=* +ENV NETCDF_FORTRAN_VERSION=${NETCDF_FORTRAN_VERSION} +ARG ESMF_VERSION=* +ENV ESMF_VERSION=${ESMF_VERSION} +ARG GCC_VERSION=10.* +ENV GCC_VERSION=${GCC_VERSION} + # Install version locked packages # gcc, gxx, gfortran provide symlinks for x86_64-conda-linux-gnu-* # ar and ranlib are not symlinked @@ -61,10 +53,11 @@ RUN mamba install --yes -c conda-forge \ gfortran && \ rm -rf /opt/conda/pkgs/* && \ ln -sf /opt/conda/bin/x86_64-conda-linux-gnu-ar /opt/conda/bin/ar && \ - ln -sf /opt/conda/bin/x86_64-conda-linux-gnu-ranlib /opt/conda/bin/ranlib + ln -sf /opt/conda/bin/x86_64-conda-linux-gnu-ranlib /opt/conda/bin/ranlib && \ + cpan install XML::LibXML Switch -# Install cpan packages -RUN cpan install XML::LibXML Switch +ARG PNETCDF_VERSION=1.12.3 +ENV PNETCDF_VERSION=${PNETCDF_VERSION} # Build pnetcdf RUN curl -L -k -o "${PWD}/pnetcdf.tar.gz" \ @@ -84,10 +77,38 @@ RUN curl -L -k -o "${PWD}/pnetcdf.tar.gz" \ make install && \ rm -rf "${PWD}/pnetcdf" -RUN mkdir -p /root/.cime /storage/timings +# CESM dependencies +ENV CCS_CONFIG_TAG=ccs_config_cesm0.0.88 +ENV CMEPS_TAG=cmeps0.14.47 +ENV CDEPS_TAG=cdeps1.0.26 +ENV CPL7_TAG=cpl77.0.8 +ENV SHARE_TAG=share1.0.18 +ENV MCT_TAG=MCT_2.11.0 +ENV PARALLELIO_TAG=pio2_6_2 + +RUN git clone -b ${CCS_CONFIG_TAG} https://github.com/ESMCI/ccs_config_cesm /src/ccs_config && \ + git clone -b ${CMEPS_TAG} https://github.com/ESCOMP/CMEPS.git /src/components/cmeps && \ + git clone -b ${CDEPS_TAG} https://github.com/ESCOMP/CDEPS.git /src/components/cdeps && \ + git clone -b ${CPL7_TAG} https://github.com/ESCOMP/CESM_CPL7andDataComps /src/components/cpl7 && \ + git clone -b ${SHARE_TAG} https://github.com/ESCOMP/CESM_share /src/share && \ + git clone -b ${MCT_TAG} https://github.com/MCSclimate/MCT /src/libraries/mct && \ + git clone -b ${PARALLELIO_TAG} https://github.com/NCAR/ParallelIO /src/libraries/parallelio && \ + mkdir -p /storage/timings + +ARG CIME_BRANCH=master +ARG CIME_REPO=https://github.com/esmci/cime + +# Separate layer, it's most likely to change +RUN git clone -b ${CIME_BRANCH} ${CIME_REPO} /src/cime + +# General variables +ENV USER=root +ENV LOGNAME=container +ENV ESMFMKFILE=/opt/conda/lib/esmf.mk + +WORKDIR /src/cime -COPY config_machines.xml /root/.cime/ -COPY docker.cmake /root/.cime/ +COPY .cime /root/.cime COPY entrypoint.sh /entrypoint.sh ENTRYPOINT [ "/entrypoint.sh" ] diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index 86df76f2276..f115160324f 100755 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -5,39 +5,15 @@ then set -x fi -readonly INIT=${INIT:-"true"} -readonly UPDATE_CIME=${UPDATE_CIME:-"false"} -readonly GIT_SHALLOW=${GIT_SHALLOW:-"false"} - -declare -xr CIME_REPO=${CIME_REPO:-https://github.com/ESMCI/cime} -declare -xr E3SM_REPO=${E3SM_REPO:-https://github.com/E3SM-Project/E3SM} -declare -xr CESM_REPO=${CESM_REPO:-https://github.com/ESCOMP/CESM} - -####################################### -# Clones git repository -####################################### -function clone_repo() { - local repo="${1}" - local path="${2}" - local branch="${3}" - local extras="" - - if [[ "${GIT_SHALLOW}" == "true" ]] - then - extras="${extras} --depth 1" - fi - - echo "Cloning branch ${branch} of ${repo} into ${path} with flags: ${flags}" - - git clone -b "${branch}" ${extras} "${repo}" "${path}" || true -} +GIT_FLAGS="--filter=tree:0" +GIT_SUBMODULE_FLAGS="--recommend-shallow" ####################################### # Fixes mct/mpeu to use ARFLAGS environment variable # # TODO need to make an offical PR this is temporary. ####################################### -function fixup_mct { +function fix_mct_arflags { local mct_path="${1}" # TODO make PR to fix @@ -57,182 +33,47 @@ function fixup_mct { } ####################################### +# Fixes gitmodules to use https rather than ssh ####################################### -function update_cime() { - local path="${1}" - - if [[ "${UPDATE_CIME}" == "true" ]] - then - echo "Updating CIME using repository ${CIME_REPO} and branch ${CIME_BRANCH}" - - pushd "${path}" - - git remote set-url origin "${CIME_REPO}" - - if [[ "${GIT_SHALLOW}" == "true" ]] - then - git remote set-branches origin "*" - fi - - git fetch origin - - git checkout "${CIME_BRANCH:-master}" - - popd - fi +function fix_gitmodules() { + sed -i".bak" "s/git@github.com:/https:\/\/github.com\//g" "${1}/.gitmodules" } -####################################### -# Creates an environment with E3SM source. -####################################### -function init_e3sm() { +if [[ "${CIME_MODEL}" == "e3sm" ]] +then echo "Setting up E3SM" - export CIME_MODEL="e3sm" + git clone -b ${E3SM_BRANCH:-master} ${GIT_FLAGS} ${E3SM_REPO:-https://github.com/E3SM-Project/E3SM} /src/E3SM - local extras="" - local install_path="${INSTALL_PATH:-/src/E3SM}" - local cache_path="${cache_path:-/storage/inputdata}" + pushd /src/E3SM - if [[ ! -e "${install_path}" ]] - then - clone_repo "${E3SM_REPO}" "${install_path}" "${E3SM_BRANCH:-master}" - - cd "${install_path}" - - if [[ ! -e "${PWD}/.gitmodules.bak" ]] - then - echo "Convering git@github.com to https://github.com urls in ${PWD}/.gitmodules" + fix_gitmodules "${PWD}" - sed -i".bak" "s/git@github.com:/https:\/\/github.com\//g" "${PWD}/.gitmodules" - fi + git submodule update --init "${GIT_SUBMODULE_FLAGS}" - if [[ "${GIT_SHALLOW}" == "true" ]] - then - extras=" --depth 1" - fi - - echo "Initializing submodules in ${PWD}" - - git submodule update --init ${extras} - fi + fix_mct_arflags /src/E3SM/externals/mct - fixup_mct "${install_path}/externals/mct" + pushd cime - update_cime "${install_path}/cime" + fix_gitmodules "${PWD}" - mkdir -p /storage/inputdata - - echo "Copying cached inputdata from /cache to /storage/inputdata" - - rsync -vr /cache/ /storage/inputdata/ - - cd "${install_path}/cime" - - if [[ ! -e "${PWD}/.gitmodules.bak" ]] - then - echo "Convering git@github.com to https://github.com urls in ${PWD}/.gitmodules" + git submodule update --init "${GIT_SUBMODULE_FLAGS}" - sed -i".bak" "s/git@github.com:/https:\/\/github.com\//g" "${PWD}/.gitmodules" - fi - - echo "Initializing submodules in ${PWD}" - - git submodule update --init ${extras} -} - -####################################### -# Creates an environment with CESM source. -####################################### -function init_cesm() { + ln -sf /root/.cime/config_machines.v2.xml /root/.cime/config_machines.xml +elif [[ "${CIME_MODEL}" == "cesm" ]] +then echo "Setting up CESM" - export CIME_MODEL="cesm" - - local install_path="${INSTALL_PATH:-/src/CESM}" - - if [[ ! -e "${install_path}" ]] - then - clone_repo "${CESM_REPO}" "${install_path}" "${CESM_BRANCH:-master}" - fi - - pushd "${install_path}" - - pushd "${install_path}/cime" - - echo "Checking out externals from `pwd`" - - "${install_path}/manage_externals/checkout_externals" -v - - popd - - fixup_mct "${install_path}/libraries/mct" - - update_cime "${install_path}/cime/" - - pushd "${install_path}/cime" - - # Need to run manage_externals again incase branch changes externals instructions - # "${install_path}/manage_externals/checkout_externals -e cime/Externals_cime.cfg" + fix_gitmodules /src/cime - if [[ ! -e "${PWD}/.gitmodules.bak" ]] - then - echo "Convering git@github.com to https://github.com urls in ${PWD}/.gitmodules" - - sed -i".bak" "s/git@github.com:/https:\/\/github.com\//g" "${PWD}/.gitmodules" - fi + git submodule update --init "${GIT_SUBMODULE_FLAGS}" - git submodule update --init -} + fix_mct_arflags /src/libraries/mct -####################################### -# Creates an environment with minimal model requirements. -# Similar to old github actions environment. -####################################### -function init_cime() { - echo "Settig up CIME" - - export CIME_MODEL="cesm" - export ESMFMKFILE="/opt/conda/lib/esmf.mk" - - local install_path="${INSTALL_PATH:-/src/cime}" - - if [[ ! -e "${install_path}" ]] - then - clone_repo "${CIME_REPO}" "${install_path}" "${CIME_BRANCH:-master}" - fi - - # required to using checkout_externals script - clone_repo "${CESM_REPO}" "/src/CESM" "${CESM_BRANCH:-master}" - - cd "${install_path}" - - "/src/CESM/manage_externals/checkout_externals" -v - - fixup_mct "${install_path}/libraries/mct" - - update_cime "${install_path}" - - cd "${install_path}" - - # Need to run manage_externals again incase branch changes externals instructions - # "${install_path}/manage_externals/checkout_externals -e cime/Externals_cime.cfg" - - if [[ ! -e "${PWD}/.gitmodules.bak" ]] - then - echo "Convering git@github.com to https://github.com urls in ${PWD}/.gitmodules" - - sed -i".bak" "s/git@github.com:/https:\/\/github.com\//g" "${PWD}/.gitmodules" - fi - - git submodule update --init -} - -if [[ ! -e "${HOME}/.cime" ]] -then - ln -sf "/root/.cime" "${HOME}/.cime" + ln -sf /root/.cime/config_machines.v3.xml /root/.cime/config_machines.xml fi +# load batch specific entrypoint if [[ -e "/entrypoint_batch.sh" ]] then echo "Sourcing batch entrypoint" @@ -240,17 +81,4 @@ then . "/entrypoint_batch.sh" fi -if [[ "${INIT}" == "true" ]] -then - if [[ "${CIME_MODEL}" == "e3sm" ]] - then - init_e3sm - elif [[ "${CIME_MODEL}" == "cesm" ]] - then - init_cesm - else - init_cime - fi - - exec "${@}" -fi +exec "${@}" From 304af17aad79aab626cb7b15a477984ee3973ec3 Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Fri, 2 Feb 2024 18:12:46 -0800 Subject: [PATCH 333/390] Fixes processing user v3 config_machines --- CIME/XML/machines.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/CIME/XML/machines.py b/CIME/XML/machines.py index dc56b290ccf..e3a047d25de 100644 --- a/CIME/XML/machines.py +++ b/CIME/XML/machines.py @@ -259,9 +259,11 @@ def _probe_machine_name_one_guess_v2(self, nametomatch): def _probe_machine_name_one_guess_v3(self, nametomatch): - node = self.get_child("NODENAME_REGEX", root=self.root) + nodes = self.get_children("NODENAME_REGEX", root=self.root) - for child in self.get_children(root=node): + children = [y for x in nodes for y in self.get_children(root=x)] + + for child in children: machtocheck = self.get(child, "MACH") regex_str = self.text(child) logger.debug( From da41ed27b1354ad3a3f544c79dbbb68a2be189a0 Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Fri, 2 Feb 2024 19:53:37 -0800 Subject: [PATCH 334/390] Testing workflow --- .github/workflows/testing.yml | 31 ++++++++++--------------------- 1 file changed, 10 insertions(+), 21 deletions(-) diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index adf792fb7e4..8d8c8bd79f5 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -32,11 +32,9 @@ permissions: packages: read jobs: - # Only build container if there has been a change. build-containers: runs-on: ubuntu-latest permissions: - contents: read packages: write steps: - name: Checkout code @@ -65,8 +63,7 @@ jobs: with: target: base context: docker/ - # push: ${{ github.event_name == 'push' }} - push: true + push: ${{ github.event_name == 'push' }} tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} cache-from: type=gha @@ -82,12 +79,6 @@ jobs: uses: actions/setup-python@v2 with: python-version: 3.9 - # Offical action is deprecated in favor of pre-commit.ci - # Should evaulate switching or just running manually. - # - name: Runs pre-commit action - # # Do not run if using act tooling (https://github.com/nektos/act) - # if: ${{ !env.ACT }} - # uses: pre-commit/action@v2.0.3 - name: Runs pre-commit run: | pip install pre-commit @@ -112,26 +103,24 @@ jobs: - name: Run tests shell: bash env: - INIT: "false" CIME_MODEL: "cesm" CIME_DRIVER: "nuopc" - UPDATE_CIME: "true" - GIT_SHALLOW: "true" CIME_TEST_PLATFORM: ubuntu-latest run: | - export INSTALL_PATH="${PWD}" - export CIME_REPO=https://github.com/${{ github.event.pull_request.head.repo.full_name || github.repository }} - export CIME_BRANCH=${GITHUB_HEAD_REF:-${GITHUB_REF##*/}} + echo $GITHUB_WORKSPACE + # export INSTALL_PATH="${PWD}" + # export CIME_REPO=https://github.com/${{ github.event.pull_request.head.repo.full_name || github.repository }} + # export CIME_BRANCH=${GITHUB_HEAD_REF:-${GITHUB_REF##*/}} - mamba install -y python=${{ matrix.python-version }} + # mamba install -y python=${{ matrix.python-version }} - source /entrypoint.sh + # source /entrypoint.sh - git config --global --add safe.directory /__w/cime/cime + # git config --global --add safe.directory /__w/cime/cime - init_cime + # init_cime - pytest -vvv --cov=CIME --machine docker --no-fortran-run CIME/tests/test_unit* + # pytest -vvv --cov=CIME --machine docker --no-fortran-run CIME/tests/test_unit* # Run system tests system-testing: From 2b90917c3cd1e510e34453207c83cc4caa4d55fb Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Fri, 2 Feb 2024 20:14:10 -0800 Subject: [PATCH 335/390] Fixes job dependency --- .github/workflows/testing.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index 8d8c8bd79f5..73d5b69b3df 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -89,6 +89,7 @@ jobs: unit-testing: runs-on: ubuntu-latest if: ${{ always() && ! cancelled() }} + needs: build-containers container: image: ghcr.io/esmci/cime:sha-${{ github.sha }} credentials: From 8d8d71861e4059afb33f94e508dc83c351db29dc Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Mon, 5 Feb 2024 09:34:11 -0800 Subject: [PATCH 336/390] Fixes pushing build --- .github/workflows/testing.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index 73d5b69b3df..d2094c985ad 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -54,8 +54,6 @@ jobs: uses: docker/metadata-action@v4 with: images: ghcr.io/ESMCI/cime - flavor: | - latest=auto tags: | type=sha,format=long - name: Build and push @@ -63,7 +61,8 @@ jobs: with: target: base context: docker/ - push: ${{ github.event_name == 'push' }} + # Only push on pull request + push: ${{ github.event_name == 'pull_request' }} tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} cache-from: type=gha From e281c436bebc6713f7d367337d85e0f0e989bbd0 Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Mon, 5 Feb 2024 10:45:25 -0800 Subject: [PATCH 337/390] Fixes unit testing step --- .github/workflows/testing.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index d2094c985ad..e3cb6635e47 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -107,6 +107,14 @@ jobs: CIME_DRIVER: "nuopc" CIME_TEST_PLATFORM: ubuntu-latest run: | + git config --global --add safe.directory $GITHUB_WORKSPACE + + git status + + mamba install -y python=${{ matrix.python-version }} + + source /entrypoint.sh + echo $GITHUB_WORKSPACE # export INSTALL_PATH="${PWD}" # export CIME_REPO=https://github.com/${{ github.event.pull_request.head.repo.full_name || github.repository }} From c9eea88f8abd9a66cd675d67203320f50c146bab Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Mon, 5 Feb 2024 13:29:10 -0800 Subject: [PATCH 338/390] Fixes testing working directory --- .github/workflows/testing.yml | 14 +++----------- docker/entrypoint.sh | 9 +++++---- 2 files changed, 8 insertions(+), 15 deletions(-) diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index e3cb6635e47..860d842b25f 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -97,6 +97,9 @@ jobs: strategy: matrix: python-version: ['3.8', '3.9', '3.10'] + defaults: + run: + working-directory: /src steps: - name: Checkout code uses: actions/checkout@v2 @@ -116,17 +119,6 @@ jobs: source /entrypoint.sh echo $GITHUB_WORKSPACE - # export INSTALL_PATH="${PWD}" - # export CIME_REPO=https://github.com/${{ github.event.pull_request.head.repo.full_name || github.repository }} - # export CIME_BRANCH=${GITHUB_HEAD_REF:-${GITHUB_REF##*/}} - - # mamba install -y python=${{ matrix.python-version }} - - # source /entrypoint.sh - - # git config --global --add safe.directory /__w/cime/cime - - # init_cime # pytest -vvv --cov=CIME --machine docker --no-fortran-run CIME/tests/test_unit* diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index f115160324f..b19abbac0f4 100755 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -5,6 +5,7 @@ then set -x fi +SRC_PATH="`pwd`" GIT_FLAGS="--filter=tree:0" GIT_SUBMODULE_FLAGS="--recommend-shallow" @@ -43,15 +44,15 @@ if [[ "${CIME_MODEL}" == "e3sm" ]] then echo "Setting up E3SM" - git clone -b ${E3SM_BRANCH:-master} ${GIT_FLAGS} ${E3SM_REPO:-https://github.com/E3SM-Project/E3SM} /src/E3SM + git clone -b ${E3SM_BRANCH:-master} ${GIT_FLAGS} ${E3SM_REPO:-https://github.com/E3SM-Project/E3SM} "${SRC_PATH}/E3SM" - pushd /src/E3SM + pushd "${SRC_PATH}/E3SM" fix_gitmodules "${PWD}" git submodule update --init "${GIT_SUBMODULE_FLAGS}" - fix_mct_arflags /src/E3SM/externals/mct + fix_mct_arflags "${SRC_PATH}/E3SM/externals/mct" pushd cime @@ -64,7 +65,7 @@ elif [[ "${CIME_MODEL}" == "cesm" ]] then echo "Setting up CESM" - fix_gitmodules /src/cime + fix_gitmodules "${PWD}" git submodule update --init "${GIT_SUBMODULE_FLAGS}" From 9fee7bcdafcb8095a35d79c4147d1a0f0203e834 Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Mon, 5 Feb 2024 13:34:13 -0800 Subject: [PATCH 339/390] Adds debug statements --- .github/workflows/testing.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index 860d842b25f..cc9827d14b3 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -110,6 +110,10 @@ jobs: CIME_DRIVER: "nuopc" CIME_TEST_PLATFORM: ubuntu-latest run: | + echo $PWD + + ls -la . + git config --global --add safe.directory $GITHUB_WORKSPACE git status From 399c60d561b3981f78b7d3de0fbe03f370f7f601 Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Mon, 5 Feb 2024 13:41:34 -0800 Subject: [PATCH 340/390] Fixes checkout directory --- .github/workflows/testing.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index cc9827d14b3..27e2daa5aa8 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -101,8 +101,12 @@ jobs: run: working-directory: /src steps: + - name: Remove existing /src/cime + run: rm -rf /src/cime - name: Checkout code uses: actions/checkout@v2 + with: + path: /src/cime - name: Run tests shell: bash env: From 9b49848552828e3bd2bb37de1862259f04fdac4f Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Mon, 5 Feb 2024 13:55:09 -0800 Subject: [PATCH 341/390] Fixes setting up working directory --- .github/workflows/testing.yml | 15 --------------- docker/entrypoint.sh | 5 +++++ 2 files changed, 5 insertions(+), 15 deletions(-) diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index 27e2daa5aa8..c5d7262ae2f 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -97,16 +97,9 @@ jobs: strategy: matrix: python-version: ['3.8', '3.9', '3.10'] - defaults: - run: - working-directory: /src steps: - - name: Remove existing /src/cime - run: rm -rf /src/cime - name: Checkout code uses: actions/checkout@v2 - with: - path: /src/cime - name: Run tests shell: bash env: @@ -114,20 +107,12 @@ jobs: CIME_DRIVER: "nuopc" CIME_TEST_PLATFORM: ubuntu-latest run: | - echo $PWD - - ls -la . - - git config --global --add safe.directory $GITHUB_WORKSPACE - git status mamba install -y python=${{ matrix.python-version }} source /entrypoint.sh - echo $GITHUB_WORKSPACE - # pytest -vvv --cov=CIME --machine docker --no-fortran-run CIME/tests/test_unit* # Run system tests diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index b19abbac0f4..11a30cb1a4e 100755 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -65,6 +65,11 @@ elif [[ "${CIME_MODEL}" == "cesm" ]] then echo "Setting up CESM" + if [[ "${SRC_PATH}" != "/src" ]] + then + cp -rf /src/ccs_config /src/components /src/libraries /src/share ../ + fi + fix_gitmodules "${PWD}" git submodule update --init "${GIT_SUBMODULE_FLAGS}" From 89e012fddbe4d740590c647d1c7563515bb6154e Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Mon, 5 Feb 2024 14:00:55 -0800 Subject: [PATCH 342/390] Debugging --- .github/workflows/testing.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index c5d7262ae2f..6cb2a4cbe93 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -107,6 +107,10 @@ jobs: CIME_DRIVER: "nuopc" CIME_TEST_PLATFORM: ubuntu-latest run: | + export SRC_PATH="${GITHUB_WORKSPACE}" + + echo $GITHUB_WORKSPACE + git status mamba install -y python=${{ matrix.python-version }} From 1c6e138b2e95c70d668e7352815556d3938d9e2e Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Mon, 5 Feb 2024 14:06:29 -0800 Subject: [PATCH 343/390] Fixes setting working directory and configuring safe.directory --- .github/workflows/testing.yml | 2 -- docker/entrypoint.sh | 4 ++++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index 6cb2a4cbe93..55b8d941ae6 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -111,8 +111,6 @@ jobs: echo $GITHUB_WORKSPACE - git status - mamba install -y python=${{ matrix.python-version }} source /entrypoint.sh diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index 11a30cb1a4e..d5dd8c83281 100755 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -70,10 +70,14 @@ then cp -rf /src/ccs_config /src/components /src/libraries /src/share ../ fi + git config --global --add safe.directory "${PWD}" + fix_gitmodules "${PWD}" git submodule update --init "${GIT_SUBMODULE_FLAGS}" + git config --global --add safe.directory "${PWD}/cime" + fix_mct_arflags /src/libraries/mct ln -sf /root/.cime/config_machines.v3.xml /root/.cime/config_machines.xml From 41a2c35581d05f0c48c382797f143fcef201a779 Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Mon, 5 Feb 2024 14:17:53 -0800 Subject: [PATCH 344/390] Enables running tests --- .github/workflows/testing.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index 55b8d941ae6..b03551cf2d4 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -115,7 +115,7 @@ jobs: source /entrypoint.sh - # pytest -vvv --cov=CIME --machine docker --no-fortran-run CIME/tests/test_unit* + pytest -vvv --cov=CIME --machine docker --no-fortran-run CIME/tests/test_unit* # Run system tests system-testing: From 019bb49015e61c48df399eb588e9b1aa86c912d7 Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Mon, 5 Feb 2024 14:20:09 -0800 Subject: [PATCH 345/390] Debugs home directory --- .github/workflows/testing.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index b03551cf2d4..bf4e882078a 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -115,6 +115,10 @@ jobs: source /entrypoint.sh + echo $HOME + + id + pytest -vvv --cov=CIME --machine docker --no-fortran-run CIME/tests/test_unit* # Run system tests From bcbae3dd6613107191d691bbaf0617905e589533 Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Mon, 5 Feb 2024 14:23:49 -0800 Subject: [PATCH 346/390] Fixes copying .cime to /home/titters --- .github/workflows/testing.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index bf4e882078a..fd7ccd021ba 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -117,7 +117,7 @@ jobs: echo $HOME - id + cp -rf /root/.cime /github/home/ pytest -vvv --cov=CIME --machine docker --no-fortran-run CIME/tests/test_unit* From 55fa75daca87f999906f74390f38b85c5272921c Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Mon, 5 Feb 2024 14:49:23 -0800 Subject: [PATCH 347/390] Fixes setting up testing directory --- docker/entrypoint.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index d5dd8c83281..1c4000f7bdd 100755 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -65,7 +65,7 @@ elif [[ "${CIME_MODEL}" == "cesm" ]] then echo "Setting up CESM" - if [[ "${SRC_PATH}" != "/src" ]] + if [[ "${SRC_PATH}" != "/src/cime" ]] then cp -rf /src/ccs_config /src/components /src/libraries /src/share ../ fi From 203c3571b5def6cbf575a1870c1e58bf7daf2d88 Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Mon, 5 Feb 2024 15:07:58 -0800 Subject: [PATCH 348/390] Fixes safe.directory --- .github/workflows/testing.yml | 7 +++---- docker/entrypoint.sh | 4 ++-- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index fd7ccd021ba..0eda324aa32 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -109,16 +109,15 @@ jobs: run: | export SRC_PATH="${GITHUB_WORKSPACE}" - echo $GITHUB_WORKSPACE - mamba install -y python=${{ matrix.python-version }} source /entrypoint.sh - echo $HOME - + # GitHub runner home is different than container cp -rf /root/.cime /github/home/ + git status + pytest -vvv --cov=CIME --machine docker --no-fortran-run CIME/tests/test_unit* # Run system tests diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index 1c4000f7bdd..b71331c5d89 100755 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -72,12 +72,12 @@ then git config --global --add safe.directory "${PWD}" + git config --global --add safe.directory "${PWD}/CIME/non_py/cprnc" + fix_gitmodules "${PWD}" git submodule update --init "${GIT_SUBMODULE_FLAGS}" - git config --global --add safe.directory "${PWD}/cime" - fix_mct_arflags /src/libraries/mct ln -sf /root/.cime/config_machines.v3.xml /root/.cime/config_machines.xml From fb5ab4ee3cef03b235b4e68046b07c9f6ec05bf3 Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Mon, 5 Feb 2024 16:40:47 -0800 Subject: [PATCH 349/390] Fixes broken test with shallow git clone --- CIME/tests/test_unit_bless_test_results.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/CIME/tests/test_unit_bless_test_results.py b/CIME/tests/test_unit_bless_test_results.py index 620f07f9603..1daee4fb896 100644 --- a/CIME/tests/test_unit_bless_test_results.py +++ b/CIME/tests/test_unit_bless_test_results.py @@ -798,13 +798,21 @@ def test_baseline_root_none(self, get_test_status_files, TestStatus, Case): assert not success + @mock.patch("CIME.utils.get_current_branch") @mock.patch("CIME.bless_test_results.bless_namelists") @mock.patch("CIME.bless_test_results.Case") @mock.patch("CIME.bless_test_results.TestStatus") @mock.patch("CIME.bless_test_results.get_test_status_files") def test_baseline_name_none( - self, get_test_status_files, TestStatus, Case, bless_namelists + self, + get_test_status_files, + TestStatus, + Case, + bless_namelists, + get_current_branch, ): + get_current_branch.return_value = "master" + bless_namelists.return_value = (True, "") get_test_status_files.return_value = [ @@ -819,6 +827,7 @@ def test_baseline_name_none( case = Case.return_value.__enter__.return_value case.get_value.side_effect = [None, None] + breakpoint() success = bless_test_results( None, "/tmp/baselines", From 24ff828d87834191aeab20b950f2a34eb366aa0e Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Mon, 5 Feb 2024 16:41:33 -0800 Subject: [PATCH 350/390] Revert "Fixes broken test with shallow git clone" This reverts commit fb5ab4ee3cef03b235b4e68046b07c9f6ec05bf3. --- CIME/tests/test_unit_bless_test_results.py | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/CIME/tests/test_unit_bless_test_results.py b/CIME/tests/test_unit_bless_test_results.py index 1daee4fb896..620f07f9603 100644 --- a/CIME/tests/test_unit_bless_test_results.py +++ b/CIME/tests/test_unit_bless_test_results.py @@ -798,21 +798,13 @@ def test_baseline_root_none(self, get_test_status_files, TestStatus, Case): assert not success - @mock.patch("CIME.utils.get_current_branch") @mock.patch("CIME.bless_test_results.bless_namelists") @mock.patch("CIME.bless_test_results.Case") @mock.patch("CIME.bless_test_results.TestStatus") @mock.patch("CIME.bless_test_results.get_test_status_files") def test_baseline_name_none( - self, - get_test_status_files, - TestStatus, - Case, - bless_namelists, - get_current_branch, + self, get_test_status_files, TestStatus, Case, bless_namelists ): - get_current_branch.return_value = "master" - bless_namelists.return_value = (True, "") get_test_status_files.return_value = [ @@ -827,7 +819,6 @@ def test_baseline_name_none( case = Case.return_value.__enter__.return_value case.get_value.side_effect = [None, None] - breakpoint() success = bless_test_results( None, "/tmp/baselines", From fde292105352fefd3f589f2cfb8cb6bb9ea29278 Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Mon, 5 Feb 2024 16:43:57 -0800 Subject: [PATCH 351/390] Fixes failing test due to shallow git clone --- CIME/tests/test_unit_bless_test_results.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/CIME/tests/test_unit_bless_test_results.py b/CIME/tests/test_unit_bless_test_results.py index 620f07f9603..91315b30f8d 100644 --- a/CIME/tests/test_unit_bless_test_results.py +++ b/CIME/tests/test_unit_bless_test_results.py @@ -798,13 +798,21 @@ def test_baseline_root_none(self, get_test_status_files, TestStatus, Case): assert not success + @mock.patch("CIME.utils.get_current_branch") @mock.patch("CIME.bless_test_results.bless_namelists") @mock.patch("CIME.bless_test_results.Case") @mock.patch("CIME.bless_test_results.TestStatus") @mock.patch("CIME.bless_test_results.get_test_status_files") def test_baseline_name_none( - self, get_test_status_files, TestStatus, Case, bless_namelists + self, + get_test_status_files, + TestStatus, + Case, + bless_namelists, + get_current_branch, ): + get_current_branch.return_value = "master" + bless_namelists.return_value = (True, "") get_test_status_files.return_value = [ From 5a80a9be83114f70593a3c045a32a853ca6e5cc1 Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Mon, 5 Feb 2024 16:51:54 -0800 Subject: [PATCH 352/390] Enable system testing --- .github/workflows/testing.yml | 30 +++++++----------------------- 1 file changed, 7 insertions(+), 23 deletions(-) diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index 0eda324aa32..b495e95459a 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -87,7 +87,8 @@ jobs: # Runs unit testing under different python versions. unit-testing: runs-on: ubuntu-latest - if: ${{ always() && ! cancelled() }} + if: ${{ github.sha == '' }} + # if: ${{ always() && ! cancelled() }} needs: build-containers container: image: ghcr.io/esmci/cime:sha-${{ github.sha }} @@ -123,9 +124,7 @@ jobs: # Run system tests system-testing: runs-on: ubuntu-latest - # temporary disable - if: ${{ github.sha == '' }} - # if: ${{ always() && ! cancelled() }} + if: ${{ always() && ! cancelled() }} container: image: ghcr.io/esmci/cime:latest credentials: @@ -155,33 +154,18 @@ jobs: - name: Run tests shell: bash env: - INIT: "false" CIME_MODEL: ${{ matrix.model }} CIME_DRIVER: ${{ matrix.driver }} - UPDATE_CIME: "true" - GIT_SHALLOW: "true" CIME_TEST_PLATFORM: ubuntu-latest run: | - export INSTALL_PATH="${PWD}/cime" - export CIME_REPO=https://github.com/${{ github.event.pull_request.head.repo.full_name || github.repository }} - export CIME_BRANCH=${GITHUB_HEAD_REF:-${GITHUB_REF##*/}} + export SRC_PATH="${GITHUB_WORKSPACE}" source /entrypoint.sh - git config --global --add safe.directory /__w/cime/cime - - if [[ "${CIME_MODEL}" == "e3sm" ]] - then - init_e3sm - else - init_cime - fi - - # Get cprnc - # git clone https://github.com/ESMCI/cprnc $INSTALL_PATH/CIME/non_py/cprnc - source /opt/conda/etc/profile.d/conda.sh + # GitHub runner home is different than container + cp -rf /root/.cime /github/home/ - conda activate base + git status pytest -vvv --cov=CIME --machine docker --no-fortran-run --no-teardown CIME/tests/test_sys* - name: Create testing log archive From 321d9dd71d2ea733af23b89910730401006b3f8b Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Mon, 5 Feb 2024 16:58:43 -0800 Subject: [PATCH 353/390] Fixes system testing image --- .github/workflows/testing.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index b495e95459a..b13b0175a47 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -126,7 +126,7 @@ jobs: runs-on: ubuntu-latest if: ${{ always() && ! cancelled() }} container: - image: ghcr.io/esmci/cime:latest + image: ghcr.io/esmci/cime:sha-${{ github.sha }} credentials: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} From f333f47b568a0c06a949a18a09265690bae7c840 Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Mon, 5 Feb 2024 18:37:34 -0800 Subject: [PATCH 354/390] Fixes E3SM setup --- .github/workflows/testing.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index b13b0175a47..ad598b61318 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -162,6 +162,12 @@ jobs: source /entrypoint.sh + rm -rf "${GITHUB_WORKSPACE}/E3SM/cime" + + cp -rf "${GITHUB_WORKSPACE}" "${GITHUB_WORKSPACE}/E3SM/cime" + + ls -la "${GITHUB_WORKSPACE}/E3SM/cime" + # GitHub runner home is different than container cp -rf /root/.cime /github/home/ From a5c1e54e209b10d715030445fe3de6200558040b Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Mon, 5 Feb 2024 19:22:21 -0800 Subject: [PATCH 355/390] Removes old container workflow --- .github/workflows/container.yml | 60 --------------------------------- 1 file changed, 60 deletions(-) delete mode 100644 .github/workflows/container.yml diff --git a/.github/workflows/container.yml b/.github/workflows/container.yml deleted file mode 100644 index d3b0d1002dc..00000000000 --- a/.github/workflows/container.yml +++ /dev/null @@ -1,60 +0,0 @@ -name: container build/publish - -on: - push: - branches: - - master - paths: - - 'docker/**' - - pull_request: - branches: - - master - paths: - - 'docker/**' - - workflow_dispatch: - -concurrency: - group: ${{ github.ref }} - cancel-in-progress: true - -jobs: - # Only build container if there has been a change. - build-containers: - runs-on: ubuntu-latest - permissions: - contents: read - packages: write - steps: - - name: Checkout code - uses: actions/checkout@v3 - - name: Set up QEMU - uses: docker/setup-qemu-action@v2 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 - - name: Login to DockerHub - uses: docker/login-action@v2 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - name: Docker meta - id: meta - uses: docker/metadata-action@v4 - with: - images: ghcr.io/ESMCI/cime - flavor: | - latest=auto - tags: | - type=sha - - name: Build and push - uses: docker/build-push-action@v3 - with: - target: base - context: docker/ - push: ${{ github.event_name == 'push' }} - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - cache-from: type=gha - cache-to: type=gha,mode=max From 4bf28bed3ab42a5010804163b34fe9afde3ff2c9 Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Mon, 5 Feb 2024 19:26:09 -0800 Subject: [PATCH 356/390] Fix workflow --- .github/workflows/testing.yml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index ad598b61318..b13b0175a47 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -162,12 +162,6 @@ jobs: source /entrypoint.sh - rm -rf "${GITHUB_WORKSPACE}/E3SM/cime" - - cp -rf "${GITHUB_WORKSPACE}" "${GITHUB_WORKSPACE}/E3SM/cime" - - ls -la "${GITHUB_WORKSPACE}/E3SM/cime" - # GitHub runner home is different than container cp -rf /root/.cime /github/home/ From e146de649a551180349f9e89509077fe7652c96f Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Mon, 5 Feb 2024 19:31:10 -0800 Subject: [PATCH 357/390] Removes old cime dir --- .github/workflows/testing.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index b13b0175a47..7c56ea79088 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -162,6 +162,8 @@ jobs: source /entrypoint.sh + rm -rf "${GITHUB_WORKSPACE}/E3SM/cime" + # GitHub runner home is different than container cp -rf /root/.cime /github/home/ From 3fde6db9e95104bbae99e73c03b1a582ef84704c Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Tue, 6 Feb 2024 08:53:21 -0800 Subject: [PATCH 358/390] Fixes directory for E3SM system tests --- .github/workflows/testing.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index 7c56ea79088..729380f9dc7 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -162,11 +162,14 @@ jobs: source /entrypoint.sh - rm -rf "${GITHUB_WORKSPACE}/E3SM/cime" - # GitHub runner home is different than container cp -rf /root/.cime /github/home/ + if [[ "${CIME_MODEL}" == "e3sm" ]]; then + # For E3SM model, need to be in correct CIME directory + pushd E3SM/cime + fi + git status pytest -vvv --cov=CIME --machine docker --no-fortran-run --no-teardown CIME/tests/test_sys* From 32fc9ebe8e525c1dc3c6efb30ee91ab108139696 Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Tue, 6 Feb 2024 11:05:31 -0800 Subject: [PATCH 359/390] Fixes setting up E3SM testing --- .github/workflows/testing.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index 729380f9dc7..16eea8a91c0 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -166,8 +166,10 @@ jobs: cp -rf /root/.cime /github/home/ if [[ "${CIME_MODEL}" == "e3sm" ]]; then - # For E3SM model, need to be in correct CIME directory - pushd E3SM/cime + git remote set-url origin https://github.com/${{ github.event.pull_request.head.repo.full_name || github.repository }} + git remote set-branches origin "*" + git fetch origin + git checkout ${GITHUB_HEAD_REF:-${GITHUB_REF##*/}} fi git status From 72b945ab8edb589353a8141973698a82d1926616 Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Tue, 6 Feb 2024 11:15:13 -0800 Subject: [PATCH 360/390] Adds delay to prevent issues pulling image and fixes syncing cime submodules --- .github/workflows/testing.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index 16eea8a91c0..142179eabf5 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -67,6 +67,7 @@ jobs: labels: ${{ steps.meta.outputs.labels }} cache-from: type=gha cache-to: type=gha,mode=max + - run: sleep 2 pre-commit: runs-on: ubuntu-latest @@ -170,6 +171,9 @@ jobs: git remote set-branches origin "*" git fetch origin git checkout ${GITHUB_HEAD_REF:-${GITHUB_REF##*/}} + + # sync correct submodules + git submodule update fi git status From a0a3fd3438513b070e3504a4d2b59ad7665ee52a Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Tue, 6 Feb 2024 14:34:21 -0800 Subject: [PATCH 361/390] Fixes creating artifacts for all system test runs --- .github/workflows/testing.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index 142179eabf5..de273e43302 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -182,13 +182,13 @@ jobs: - name: Create testing log archive if: ${{ failure() }} shell: bash - run: tar -czvf /testing-logs-${GITHUB_RUN_NUMBER}.tar.gz /storage/cases/ + run: tar -czvf /testing-logs-${GITHUB_RUN_NUMBER}-${{ matrix.model }}-${{ matrix.driver }}.tar.gz /storage/cases/ # How to download artifacts: # https://docs.github.com/en/actions/managing-workflow-runs/downloading-workflow-artifacts - name: Upload testing logs if: ${{ failure() }} uses: actions/upload-artifact@v3 with: - name: testing-logs-${{ github.run_number }} - path: /testing-logs-${{ github.run_number}}.tar.gz + name: testing-logs-${{ github.run_number }}-${{ matrix.model }}-${{ matrix.driver }} + path: /testing-logs-${{ github.run_number}}-${{ matrix.model }}-${{ matrix.driver }}.tar.gz retention-days: 4 From ad163a8defaa02a1027b85c4849f8572906ae535 Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Tue, 6 Feb 2024 14:55:02 -0800 Subject: [PATCH 362/390] Fixes recursive submodule update for e3sm --- docker/entrypoint.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index b71331c5d89..03d11607fd1 100755 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -50,7 +50,9 @@ then fix_gitmodules "${PWD}" - git submodule update --init "${GIT_SUBMODULE_FLAGS}" + git status + + git submodule update --init --recursive "${GIT_SUBMODULE_FLAGS}" fix_mct_arflags "${SRC_PATH}/E3SM/externals/mct" From 01cc5f9c13409193ae6dff8b2ab3356b25cfe0ee Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Tue, 6 Feb 2024 15:12:32 -0800 Subject: [PATCH 363/390] Upates workflow and inceases timeout --- .github/workflows/testing.yml | 2 +- docker/entrypoint.sh | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index de273e43302..87480c17819 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -67,7 +67,7 @@ jobs: labels: ${{ steps.meta.outputs.labels }} cache-from: type=gha cache-to: type=gha,mode=max - - run: sleep 2 + - run: sleep 5 pre-commit: runs-on: ubuntu-latest diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index 03d11607fd1..2c84b4256b1 100755 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -48,11 +48,13 @@ then pushd "${SRC_PATH}/E3SM" + git config --global --add safe.directory "${PWD}" + fix_gitmodules "${PWD}" git status - git submodule update --init --recursive "${GIT_SUBMODULE_FLAGS}" + git submodule update --init "${GIT_SUBMODULE_FLAGS}" fix_mct_arflags "${SRC_PATH}/E3SM/externals/mct" @@ -60,6 +62,8 @@ then fix_gitmodules "${PWD}" + git config --global --add safe.directory "${PWD}/CIME/non_py/cprnc" + git submodule update --init "${GIT_SUBMODULE_FLAGS}" ln -sf /root/.cime/config_machines.v2.xml /root/.cime/config_machines.xml From d40a5df9d609ffeb24a0e43bad07a1d05f2538c0 Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Tue, 6 Feb 2024 15:13:42 -0800 Subject: [PATCH 364/390] Fixes missing job dependency --- .github/workflows/testing.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index 87480c17819..902e30a28c7 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -67,7 +67,6 @@ jobs: labels: ${{ steps.meta.outputs.labels }} cache-from: type=gha cache-to: type=gha,mode=max - - run: sleep 5 pre-commit: runs-on: ubuntu-latest @@ -126,6 +125,7 @@ jobs: system-testing: runs-on: ubuntu-latest if: ${{ always() && ! cancelled() }} + needs: build-containers container: image: ghcr.io/esmci/cime:sha-${{ github.sha }} credentials: From 8533ab5cb0a590f1bf51f2e7cbedfdb55283f98d Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Tue, 6 Feb 2024 18:18:12 -0800 Subject: [PATCH 365/390] Adds debug statement --- .github/workflows/testing.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index 902e30a28c7..2e1e169af32 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -178,7 +178,11 @@ jobs: git status - pytest -vvv --cov=CIME --machine docker --no-fortran-run --no-teardown CIME/tests/test_sys* + which ar + + export + + # pytest -vvv --cov=CIME --machine docker --no-fortran-run --no-teardown CIME/tests/test_sys* - name: Create testing log archive if: ${{ failure() }} shell: bash From 523d611e41b3f15a8bb97887e584e70680a6de69 Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Tue, 6 Feb 2024 18:41:20 -0800 Subject: [PATCH 366/390] Fixes hostname --- .github/workflows/testing.yml | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index 2e1e169af32..16d96634533 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -131,6 +131,7 @@ jobs: credentials: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} + options: --hostname docker strategy: # allow all jobs to finish fail-fast: false @@ -178,11 +179,9 @@ jobs: git status - which ar - - export - - # pytest -vvv --cov=CIME --machine docker --no-fortran-run --no-teardown CIME/tests/test_sys* + if [[ "${CIME_MODEL}" == "e3sm" ]]; then + pytest -vvv --cov=CIME --machine docker --no-fortran-run --no-teardown CIME/tests/test_sys* + fi - name: Create testing log archive if: ${{ failure() }} shell: bash From 206eece2c55f55dfbd0e1188945cd343d9434c4d Mon Sep 17 00:00:00 2001 From: James Foucar Date: Tue, 6 Feb 2024 16:42:47 -0700 Subject: [PATCH 367/390] Fix a string that should be raw --- CIME/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CIME/utils.py b/CIME/utils.py index 5b186cf7085..3f7cfb9f79e 100644 --- a/CIME/utils.py +++ b/CIME/utils.py @@ -201,7 +201,7 @@ def check_name(fullname, additional_chars=None, fullpath=False): False """ - chars = "+*?<>/{}[\]~`@:" # pylint: disable=anomalous-backslash-in-string + chars = r"+*?<>/{}[\]~`@:" if additional_chars is not None: chars += additional_chars if fullname.endswith("/"): From bb3c9a6c902343baf10a19e3407d5c30a6db905a Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Wed, 7 Feb 2024 08:47:24 -0800 Subject: [PATCH 368/390] Adds debug shell --- .github/workflows/testing.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index 16d96634533..e4b8d71fe9b 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -179,9 +179,10 @@ jobs: git status - if [[ "${CIME_MODEL}" == "e3sm" ]]; then - pytest -vvv --cov=CIME --machine docker --no-fortran-run --no-teardown CIME/tests/test_sys* - fi + pytest -vvv --cov=CIME --machine docker --no-fortran-run --no-teardown CIME/tests/test_sys* + - uses: mxschmitt/action-tmate@v3 + with: + limit-access-to-actor: true - name: Create testing log archive if: ${{ failure() }} shell: bash From a22dc16b6ffc3fe2c6188c599a935769238c3b9f Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Wed, 7 Feb 2024 09:36:06 -0800 Subject: [PATCH 369/390] Enable debug shell on failure --- .github/workflows/testing.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index e4b8d71fe9b..242daf60d67 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -181,6 +181,7 @@ jobs: pytest -vvv --cov=CIME --machine docker --no-fortran-run --no-teardown CIME/tests/test_sys* - uses: mxschmitt/action-tmate@v3 + if: ${{ failure() }} with: limit-access-to-actor: true - name: Create testing log archive From 953f2a2a7c8f12efdc3ff95150fa004ed592f6e0 Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Wed, 7 Feb 2024 14:00:37 -0800 Subject: [PATCH 370/390] Just to trigger workflow --- trigger | 1 + 1 file changed, 1 insertion(+) create mode 100644 trigger diff --git a/trigger b/trigger new file mode 100644 index 00000000000..52d143a9c89 --- /dev/null +++ b/trigger @@ -0,0 +1 @@ +Wed Feb 7 02:00:24 PM PST 2024 From fcc113af3d999d0f2dc37fbabae8380c299c5a4c Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Wed, 7 Feb 2024 14:53:12 -0800 Subject: [PATCH 371/390] Fixes activating conda environemnt --- .github/workflows/testing.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index 242daf60d67..c00d417b827 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -175,6 +175,10 @@ jobs: # sync correct submodules git submodule update + + source /opt/conda/etc/profile.d/conda.sh + + conda activate base fi git status From 391ec1065a22898c7a6165f60e84c6021c7417ff Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Wed, 7 Feb 2024 16:07:18 -0800 Subject: [PATCH 372/390] Trigger workflow --- trigger | 1 - 1 file changed, 1 deletion(-) delete mode 100644 trigger diff --git a/trigger b/trigger deleted file mode 100644 index 52d143a9c89..00000000000 --- a/trigger +++ /dev/null @@ -1 +0,0 @@ -Wed Feb 7 02:00:24 PM PST 2024 From 9a5f1581cca217f2dcc4db771c30c3dbbb71e5eb Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Wed, 7 Feb 2024 16:10:00 -0800 Subject: [PATCH 373/390] Disables testing for debugging --- .github/workflows/testing.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index c00d417b827..e3e19648ef8 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -183,7 +183,7 @@ jobs: git status - pytest -vvv --cov=CIME --machine docker --no-fortran-run --no-teardown CIME/tests/test_sys* + # pytest -vvv --cov=CIME --machine docker --no-fortran-run --no-teardown CIME/tests/test_sys* - uses: mxschmitt/action-tmate@v3 if: ${{ failure() }} with: From d90f5f273fd8ddcafc6f8c8f6d71478708c9c185 Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Wed, 7 Feb 2024 16:13:37 -0800 Subject: [PATCH 374/390] Always enable shell --- .github/workflows/testing.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index e3e19648ef8..beeb39e6cd4 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -185,7 +185,7 @@ jobs: # pytest -vvv --cov=CIME --machine docker --no-fortran-run --no-teardown CIME/tests/test_sys* - uses: mxschmitt/action-tmate@v3 - if: ${{ failure() }} + if: ${{ always() }} with: limit-access-to-actor: true - name: Create testing log archive From 42e1d7766595b3cb723d0449f374cfb9d9f2d45a Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Wed, 7 Feb 2024 17:10:08 -0800 Subject: [PATCH 375/390] Fixes dry_run causing recursion --- .github/workflows/testing.yml | 4 ++-- CIME/XML/env_batch.py | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index beeb39e6cd4..b72e6bbfb94 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -183,9 +183,9 @@ jobs: git status - # pytest -vvv --cov=CIME --machine docker --no-fortran-run --no-teardown CIME/tests/test_sys* + pytest -vvv --cov=CIME --machine docker --no-fortran-run --no-teardown CIME/tests/test_sys* - uses: mxschmitt/action-tmate@v3 - if: ${{ always() }} + if: ${{ !always() }} with: limit-access-to-actor: true - name: Create testing log archive diff --git a/CIME/XML/env_batch.py b/CIME/XML/env_batch.py index 558ff2b0ab4..58d1a05658b 100644 --- a/CIME/XML/env_batch.py +++ b/CIME/XML/env_batch.py @@ -929,8 +929,12 @@ def _submit_single_job( set_continue_run=resubmit_immediate, submit_resubmits=workflow and not resubmit_immediate, ) + try: if hasattr(case, function_name): + if dry_run: + return + getattr(case, function_name)(**{k: v for k, (v, _) in args.items()}) else: expect( From 3f00354c34bf29d49a0a83d513bd1a55692affa8 Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Wed, 7 Feb 2024 18:09:22 -0800 Subject: [PATCH 376/390] Re-enables unit tests --- .github/workflows/testing.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index b72e6bbfb94..7f3bd6176cf 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -87,8 +87,7 @@ jobs: # Runs unit testing under different python versions. unit-testing: runs-on: ubuntu-latest - if: ${{ github.sha == '' }} - # if: ${{ always() && ! cancelled() }} + if: ${{ always() && ! cancelled() }} needs: build-containers container: image: ghcr.io/esmci/cime:sha-${{ github.sha }} From d2104d7b72015df915d5fc5980b715004e309935 Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Wed, 7 Feb 2024 23:27:40 -0800 Subject: [PATCH 377/390] Fixes pushing latest tag and limiting testing to pull requests --- .github/workflows/testing.yml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index 7f3bd6176cf..530c00db89e 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -55,14 +55,14 @@ jobs: with: images: ghcr.io/ESMCI/cime tags: | + type=raw,value=latest,enable=${{ github.event_name == 'push' }} type=sha,format=long - name: Build and push uses: docker/build-push-action@v3 with: target: base context: docker/ - # Only push on pull request - push: ${{ github.event_name == 'pull_request' }} + push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} cache-from: type=gha @@ -70,6 +70,7 @@ jobs: pre-commit: runs-on: ubuntu-latest + if: ${{ github.event_name == 'pull_request' && ! cancelled() }} timeout-minutes: 2 steps: - name: Checkout code @@ -87,7 +88,7 @@ jobs: # Runs unit testing under different python versions. unit-testing: runs-on: ubuntu-latest - if: ${{ always() && ! cancelled() }} + if: ${{ github.event_name == 'pull_request' && always() && ! cancelled() }} needs: build-containers container: image: ghcr.io/esmci/cime:sha-${{ github.sha }} @@ -123,7 +124,7 @@ jobs: # Run system tests system-testing: runs-on: ubuntu-latest - if: ${{ always() && ! cancelled() }} + if: ${{ github.event_name == 'pull_request' && always() && ! cancelled() }} needs: build-containers container: image: ghcr.io/esmci/cime:sha-${{ github.sha }} From 7fc53df2b6cf760ae79d7fb46c6404592babca62 Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Wed, 14 Feb 2024 15:02:50 -0800 Subject: [PATCH 378/390] Fixes cloning e3sm if it exists, adds comments --- docker/entrypoint.sh | 35 +++++++++++++++++++++++++++-------- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index 2c84b4256b1..dd0ac467233 100755 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -1,14 +1,22 @@ #!/bin/bash -if [[ -n "${DEBUG}" ]] +DEBUG="${DEBUG:-false}" +SRC_PATH="${SRC_PATH:-`pwd`}" +# Treeless clone +GIT_FLAGS="${GIT_FLAGS:---filter=tree:0}" +# Shallow submodule checkout +GIT_SUBMODULE_FLAGS="${GIT_SUBMODULE_FLAGS:---recommend-shallow}" + +echo "DEBUG = ${DEBUG}" +echo "SRC_PATH = ${SRC_PATH}" +echo "GIT_FLAGS = ${GIT_FLAGS}" +echo "GIT_SUBMODULE_FLAGS = ${GIT_SUBMODULE_FLAGS}" + +if [[ "$(echo ${DEBUG} | tr -s '[:upper:]' '[:lower:]')" == "true" ]] then set -x fi -SRC_PATH="`pwd`" -GIT_FLAGS="--filter=tree:0" -GIT_SUBMODULE_FLAGS="--recommend-shallow" - ####################################### # Fixes mct/mpeu to use ARFLAGS environment variable # @@ -44,48 +52,59 @@ if [[ "${CIME_MODEL}" == "e3sm" ]] then echo "Setting up E3SM" - git clone -b ${E3SM_BRANCH:-master} ${GIT_FLAGS} ${E3SM_REPO:-https://github.com/E3SM-Project/E3SM} "${SRC_PATH}/E3SM" + [[ ! -e "${SRC_PATH}/E3SM" ]] && git clone -b ${E3SM_BRANCH:-master} ${GIT_FLAGS} ${E3SM_REPO:-https://github.com/E3SM-Project/E3SM} "${SRC_PATH}/E3SM" pushd "${SRC_PATH}/E3SM" git config --global --add safe.directory "${PWD}" + # fix E3SM gitmodules fix_gitmodules "${PWD}" git status + # checkout submodules git submodule update --init "${GIT_SUBMODULE_FLAGS}" + # fix mct arflags flags fix_mct_arflags "${SRC_PATH}/E3SM/externals/mct" pushd cime + # fix CIME gitmodules fix_gitmodules "${PWD}" + git config --global --add safe.directory "${PWD}" git config --global --add safe.directory "${PWD}/CIME/non_py/cprnc" + # checkout submodules git submodule update --init "${GIT_SUBMODULE_FLAGS}" + # link v2 config_machines ln -sf /root/.cime/config_machines.v2.xml /root/.cime/config_machines.xml elif [[ "${CIME_MODEL}" == "cesm" ]] then echo "Setting up CESM" + # copy pre cloned repos to new source path if [[ "${SRC_PATH}" != "/src/cime" ]] then - cp -rf /src/ccs_config /src/components /src/libraries /src/share ../ + cp -rf /src/ccs_config /src/components /src/libraries /src/share "${SRC_PATH}/../" fi git config --global --add safe.directory "${PWD}" - git config --global --add safe.directory "${PWD}/CIME/non_py/cprnc" + # fix CIME gitmodules fix_gitmodules "${PWD}" + # update CIME submodules git submodule update --init "${GIT_SUBMODULE_FLAGS}" + # fix mct argflags fix_mct_arflags /src/libraries/mct + # link v3 config_machines ln -sf /root/.cime/config_machines.v3.xml /root/.cime/config_machines.xml fi From 0d50e27777790db253cc032712f51bd03203be63 Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Wed, 14 Feb 2024 15:03:16 -0800 Subject: [PATCH 379/390] Removes temporary trigger file --- TO_DELETE | 1 - 1 file changed, 1 deletion(-) delete mode 100644 TO_DELETE diff --git a/TO_DELETE b/TO_DELETE deleted file mode 100644 index 980916ec6d8..00000000000 --- a/TO_DELETE +++ /dev/null @@ -1 +0,0 @@ -Tue Jan 16 05:00:02 PM PST 2024 From 76d786bb403ceaf84aac4888459f8e06a079c1a2 Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Thu, 15 Feb 2024 11:33:33 -0800 Subject: [PATCH 380/390] Adds script and workflow to prune ghcr.io containers generated from testing --- .github/scripts/ghcr-prune.py | 171 +++++++++++++++++++++++++++++++ .github/workflows/ghcr-prune.yml | 24 +++++ 2 files changed, 195 insertions(+) create mode 100644 .github/scripts/ghcr-prune.py create mode 100644 .github/workflows/ghcr-prune.yml diff --git a/.github/scripts/ghcr-prune.py b/.github/scripts/ghcr-prune.py new file mode 100644 index 00000000000..414474479d9 --- /dev/null +++ b/.github/scripts/ghcr-prune.py @@ -0,0 +1,171 @@ +import argparse +import logging +import requests +import re +import json +from datetime import datetime +from datetime import timedelta + +parser = argparse.ArgumentParser() + +parser.add_argument("--token", required=True, help='GitHub token with "repo" scope') +parser.add_argument("--org", required=True, help="Organization name") +parser.add_argument("--name", required=True, help="Package name") +parser.add_argument( + "--age", type=int, help="Filter versions by age, removing anything older than" +) +parser.add_argument( + "--filter", help="Filter which versions are consider for pruning", default=".*" +) +parser.add_argument("--untagged", action="store_true", help="Prune untagged versions") +parser.add_argument( + "--dry-run", action="store_true", help="Does not actually delete anything" +) + +logging_group = parser.add_argument_group("logging") +logging_group.add_argument( + "--log-level", choices=("DEBUG", "INFO", "WARNING", "ERROR"), default="INFO" +) + +kwargs = vars(parser.parse_args()) + +logging.basicConfig(level=kwargs["log_level"]) + +logger = logging.getLogger("ghcr-prune") + + +class GitHubPaginate: + def __init__(self, token, org, name, age, filter, untagged, **_): + self.token = token + self.session = None + self.url = ( + f"https://api.github.com/orgs/{org}/packages/container/{name}/versions" + ) + self.expired = datetime.now() - timedelta(days=age) + self.filter = re.compile(filter) + self.page = None + self.untagged = untagged + + def create_session(self): + self.session = requests.Session() + self.session.headers.update( + { + "Accept": "application/vnd.github+json", + "Authorization": f"Bearer {self.token}", + "X-GitHub-Api-Version": "2022-11-28", + } + ) + + def grab_page(self): + if self.session is None: + raise Exception("Must create session first") + + if self.url is None: + raise Exception("No more pages") + + response = self.session.get(self.url) + + response.raise_for_status() + + remaining = int(response.headers["X-RateLimit-Remaining"]) + + logger.debug(f"Remaining api limit {remaining}") + + if remaining <= 0: + reset = response.headers["X-RateLimit-Reset"] + + raise Exception(f"Hit ratelimit will reset at {reset}") + + try: + self.url = self.get_next_url(response.headers["Link"]) + except Exception as e: + logger.debug(f"No Link header found {e}") + + self.url = None + + return self.filter_results(response.json()) + + def get_next_url(self, link): + match = re.match("<([^>]*)>.*", link) + + if match is None: + raise Exception("Could not determine next link") + + return match.group(1) + + def filter_results(self, data): + results = [] + + logger.info(f"Processing {len(data)} containers") + + for x in data: + url = x["url"] + updated_at = datetime.strptime(x["updated_at"], "%Y-%m-%dT%H:%M:%SZ") + + logger.debug(f"Processing\n{json.dumps(x, indent=2)}") + + try: + tag = x["metadata"]["container"]["tags"][0] + except IndexError: + logger.info(f'Found untagged version {x["id"]}') + + if self.untagged: + results.append(url) + + continue + + if not self.filter.match(tag): + logger.info(f"Skipping {tag}, did not match filter") + + continue + + if updated_at < self.expired: + logger.info( + f"Pruning {tag}, updated at {updated_at}, expiration {self.expired}" + ) + + results.append(url) + else: + logger.info(f"Skipping {tag}, more recent than {self.expired}") + + return results + + def __iter__(self): + self.create_session() + + return self + + def __next__(self): + if self.page is None or len(self.page) == 0: + try: + self.page = self.grab_page() + except Exception as e: + logger.debug(f"StopIteration condition {e!r}") + + raise StopIteration from None + + try: + item = self.page.pop(0) + except IndexError: + raise StopIteration from None + + return item + + def remove_container(self, url): + if self.session is None: + raise Exception("Must create session first") + + response = self.session.delete(url) + + response.raise_for_status() + + logger.debug(f"{response.headers}") + + +pager = GitHubPaginate(**kwargs) + +for url in pager: + if kwargs["dry_run"]: + logger.info(f"Pruning {url}") + else: + pager.remove_container(url) diff --git a/.github/workflows/ghcr-prune.yml b/.github/workflows/ghcr-prune.yml new file mode 100644 index 00000000000..b3d90d428dd --- /dev/null +++ b/.github/workflows/ghcr-prune.yml @@ -0,0 +1,24 @@ +name: Prune ghcr.io container images +on: + schedule: + # run once a day + - cron: '0 2 * * *' + + # Temporary to test + pull_request: + +permissions: {} + +jobs: + prune: + permissions: + packages: write + runs-on: ubuntu + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + - run: | + pip install requests + + # remove containers older than 14 days and only generated by testing workflow + python .github/scripts/ghcr-prune.py --token ${{ secrets.GITHUB_TOKEN }} --org esmci --name cime --age 14 --filter sha- --untagged From 1b8213f525549245a5846c17f57b63a9c503cee4 Mon Sep 17 00:00:00 2001 From: James Edwards Date: Thu, 15 Feb 2024 14:04:12 -0700 Subject: [PATCH 381/390] test if a component is running standalone --- CIME/SystemTests/system_tests_common.py | 11 +++++++---- CIME/case/case_run.py | 25 ++++++++++++++----------- CIME/utils.py | 19 +++++++++++++++++++ 3 files changed, 40 insertions(+), 15 deletions(-) diff --git a/CIME/SystemTests/system_tests_common.py b/CIME/SystemTests/system_tests_common.py index cbdf661e49c..47897695a28 100644 --- a/CIME/SystemTests/system_tests_common.py +++ b/CIME/SystemTests/system_tests_common.py @@ -13,6 +13,7 @@ expect, get_current_commit, SharedArea, + is_comp_standalone, ) from CIME.test_status import * from CIME.hist_utils import ( @@ -289,10 +290,12 @@ def run(self, skip_pnl=False): if self._case.get_value("COMPARE_BASELINE"): if do_baseline_ops: self._phase_modifying_call(BASELINE_PHASE, self._compare_baseline) - self._phase_modifying_call(MEMCOMP_PHASE, self._compare_memory) - self._phase_modifying_call( - THROUGHPUT_PHASE, self._compare_throughput - ) + comp_standalone, _ = is_comp_standalone(self._case) + if not comp_standalone: + self._phase_modifying_call(MEMCOMP_PHASE, self._compare_memory) + self._phase_modifying_call( + THROUGHPUT_PHASE, self._compare_throughput + ) else: with self._test_status: self._test_status.set_status(BASELINE_PHASE, TEST_PEND_STATUS) diff --git a/CIME/case/case_run.py b/CIME/case/case_run.py index 6dc2c941132..12c5c34c3c5 100644 --- a/CIME/case/case_run.py +++ b/CIME/case/case_run.py @@ -5,7 +5,7 @@ from CIME.config import Config from CIME.utils import gzip_existing_file, new_lid, run_and_log_case_status from CIME.utils import run_sub_or_cmd, append_status, safe_copy, model_log, CIMEError -from CIME.utils import get_model, batch_jobid +from CIME.utils import batch_jobid, is_comp_standalone from CIME.get_timing import get_timing import shutil, time, sys, os, glob @@ -292,16 +292,12 @@ def _post_run_check(case, lid): ############################################################################### rundir = case.get_value("RUNDIR") - model = case.get_value("MODEL") driver = case.get_value("COMP_INTERFACE") - model = get_model() - fv3_standalone = False + comp_standalone, model = is_comp_standalone(case) - if "CPL" not in case.get_values("COMP_CLASSES"): - fv3_standalone = True if driver == "nuopc": - if fv3_standalone: + if comp_standalone: file_prefix = model else: file_prefix = "med" @@ -322,9 +318,11 @@ def _post_run_check(case, lid): cpl_logs = [os.path.join(rundir, file_prefix + ".log." + lid)] cpl_logfile = cpl_logs[0] - # find the last model.log and cpl.log model_logfile = os.path.join(rundir, model + ".log." + lid) + print( + f"driver {driver} cpl_logfile {cpl_logfile} model_logfile {model_logfile} comp_standalone {comp_standalone}" + ) if not os.path.isfile(model_logfile): expect(False, "Model did not complete, no {} log file ".format(model_logfile)) elif os.stat(model_logfile).st_size == 0: @@ -336,11 +334,16 @@ def _post_run_check(case, lid): if not os.path.isfile(cpl_logfile): break with open(cpl_logfile, "r") as fd: - if fv3_standalone and "HAS ENDED" in fd.read(): + logfile = fd.read() + if ( + comp_standalone + and "HAS ENDED" in logfile + or "END OF MODEL RUN" in logfile + ): count_ok += 1 - elif not fv3_standalone and "SUCCESSFUL TERMINATION" in fd.read(): + elif not comp_standalone and "SUCCESSFUL TERMINATION" in logfile: count_ok += 1 - if count_ok != cpl_ninst: + if count_ok < cpl_ninst: expect(False, "Model did not complete - see {} \n ".format(cpl_logfile)) diff --git a/CIME/utils.py b/CIME/utils.py index 3f7cfb9f79e..7471c2e4f4c 100644 --- a/CIME/utils.py +++ b/CIME/utils.py @@ -11,6 +11,7 @@ from argparse import Action from contextlib import contextmanager +# pylint: disable=deprecated-module from distutils import file_util # Return this error code if the scripts worked but tests failed @@ -2736,3 +2737,21 @@ def add_flag_to_cmd(flag, val): separator = "" if no_space else " " return "{}{}{}".format(flag, separator, str(val).strip()) + + +def is_comp_standalone(case): + """ + Test if the case is a single component standalone + such as FKESSLER + """ + stubcnt = 0 + classes = case.get_values("COMP_CLASSES") + for comp in classes: + if case.get_value("COMP_{}".format(comp)) == "s{}".format(comp.lower()): + stubcnt = stubcnt + 1 + else: + model = comp.lower() + numclasses = len(classes) + if stubcnt >= numclasses - 2: + return True, model + return False, get_model() From 62a98d3d3d470c949f1b80b26c37d89f772c3f9e Mon Sep 17 00:00:00 2001 From: James Foucar Date: Thu, 15 Feb 2024 14:20:28 -0700 Subject: [PATCH 382/390] Update cprnc subm again to 1.0.6 --- CIME/non_py/cprnc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CIME/non_py/cprnc b/CIME/non_py/cprnc index e3eaa5346ec..9276b219750 160000 --- a/CIME/non_py/cprnc +++ b/CIME/non_py/cprnc @@ -1 +1 @@ -Subproject commit e3eaa5346ecd50aef8b60fdf54b98cb1c13a24b1 +Subproject commit 9276b219750881633d8673c72ec80ac821f96d82 From fae887ff1a1c599c46107b6a7fe516fac69fb800 Mon Sep 17 00:00:00 2001 From: James Edwards Date: Thu, 15 Feb 2024 14:45:15 -0700 Subject: [PATCH 383/390] make that cam setup backward compatible --- CIME/case/case_setup.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/CIME/case/case_setup.py b/CIME/case/case_setup.py index 27d294dbf7a..a170d1bfddd 100644 --- a/CIME/case/case_setup.py +++ b/CIME/case/case_setup.py @@ -424,12 +424,13 @@ def _case_setup_impl( ) if comp == "cam": camroot = case.get_value("COMP_ROOT_DIR_ATM") - logger.debug("Running cam.case_setup.py") - run_cmd_no_fail( - "python {cam}/cime_config/cam.case_setup.py {cam} {case}".format( - cam=camroot, case=caseroot + if os.path.exists(os.path.join(camroot, "cam.case_setup.py")): + logger.debug("Running cam.case_setup.py") + run_cmd_no_fail( + "python {cam}/cime_config/cam.case_setup.py {cam} {case}".format( + cam=camroot, case=caseroot + ) ) - ) _build_usernl_files(case, "drv", "cpl") From b0318c8205e2be1228a1e6ed533ec21e4fdce563 Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Thu, 15 Feb 2024 16:36:10 -0800 Subject: [PATCH 384/390] Fixes runs-on --- .github/workflows/ghcr-prune.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ghcr-prune.yml b/.github/workflows/ghcr-prune.yml index b3d90d428dd..5e26f83e3ce 100644 --- a/.github/workflows/ghcr-prune.yml +++ b/.github/workflows/ghcr-prune.yml @@ -13,7 +13,7 @@ jobs: prune: permissions: packages: write - runs-on: ubuntu + runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 From 8df8357c5373ac5636055ea67e1f3b8057866dbf Mon Sep 17 00:00:00 2001 From: James Edwards Date: Fri, 16 Feb 2024 11:44:40 -0700 Subject: [PATCH 385/390] remove debug print statements --- CIME/case/case_run.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/CIME/case/case_run.py b/CIME/case/case_run.py index 12c5c34c3c5..2e86e594a5d 100644 --- a/CIME/case/case_run.py +++ b/CIME/case/case_run.py @@ -320,9 +320,6 @@ def _post_run_check(case, lid): cpl_logfile = cpl_logs[0] # find the last model.log and cpl.log model_logfile = os.path.join(rundir, model + ".log." + lid) - print( - f"driver {driver} cpl_logfile {cpl_logfile} model_logfile {model_logfile} comp_standalone {comp_standalone}" - ) if not os.path.isfile(model_logfile): expect(False, "Model did not complete, no {} log file ".format(model_logfile)) elif os.stat(model_logfile).st_size == 0: @@ -330,7 +327,6 @@ def _post_run_check(case, lid): else: count_ok = 0 for cpl_logfile in cpl_logs: - print(f"cpl_logfile {cpl_logfile}") if not os.path.isfile(cpl_logfile): break with open(cpl_logfile, "r") as fd: From 7a6069c69e6674d59726cf1d689c48dbea41c4e5 Mon Sep 17 00:00:00 2001 From: James Foucar Date: Fri, 16 Feb 2024 13:36:51 -0700 Subject: [PATCH 386/390] Fix spelling error in err message --- CIME/SystemTests/system_tests_common.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CIME/SystemTests/system_tests_common.py b/CIME/SystemTests/system_tests_common.py index cbdf661e49c..42abe3c5278 100644 --- a/CIME/SystemTests/system_tests_common.py +++ b/CIME/SystemTests/system_tests_common.py @@ -456,7 +456,7 @@ def run_indv( raise CIMEError( "Could not find all inputdata on any server, try " "manually running `./check_input_data --download " - f"--versbose` from {caseroot!r}." + f"--verbose` from {caseroot!r}." ) from None if submit_resubmits is None: do_resub = self._case.get_value("BATCH_SYSTEM") != "none" From 613db46f1a5be2ba552b829a5474caf9302b0122 Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Fri, 16 Feb 2024 12:54:08 -0800 Subject: [PATCH 387/390] Adds description to ghcr-prune.py --- .github/scripts/ghcr-prune.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/.github/scripts/ghcr-prune.py b/.github/scripts/ghcr-prune.py index 414474479d9..57d08cddbc6 100644 --- a/.github/scripts/ghcr-prune.py +++ b/.github/scripts/ghcr-prune.py @@ -6,7 +6,17 @@ from datetime import datetime from datetime import timedelta -parser = argparse.ArgumentParser() +description = """ +This script can be used to prune container images hosted on ghcr.io.\n + +Our testing workflow will build and push container images to ghcr.io +that are only used for testing. This script is used to cleanup these +temporary images. + +You can filter containers by any combination of name, age, and untagged. +""" + +parser = argparse.ArgumentParser(description=description, formatter_class=argparse.RawTextHelpFormatter) parser.add_argument("--token", required=True, help='GitHub token with "repo" scope') parser.add_argument("--org", required=True, help="Organization name") From 0c184747d32f28f3911c3361405efb38da5719b1 Mon Sep 17 00:00:00 2001 From: Jason Boutte Date: Fri, 16 Feb 2024 12:57:50 -0800 Subject: [PATCH 388/390] Adds class docstring for GitHubPaginate --- .github/scripts/ghcr-prune.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/scripts/ghcr-prune.py b/.github/scripts/ghcr-prune.py index 57d08cddbc6..87b5db7e112 100644 --- a/.github/scripts/ghcr-prune.py +++ b/.github/scripts/ghcr-prune.py @@ -45,6 +45,12 @@ class GitHubPaginate: + """Iterator for GitHub API. + + Provides small wrapper for GitHub API to utilize paging in API calls. + + https://docs.github.com/en/rest/using-the-rest-api/using-pagination-in-the-rest-api?apiVersion=2022-11-28 + """ def __init__(self, token, org, name, age, filter, untagged, **_): self.token = token self.session = None From 34c2b12b3913b96a5f17f7e0b48c7fde0dc1e9b7 Mon Sep 17 00:00:00 2001 From: Robert Jacob Date: Fri, 16 Feb 2024 16:03:31 -0600 Subject: [PATCH 389/390] black formatting for pgn.py tsc.py --- CIME/SystemTests/pgn.py | 4 +++- CIME/SystemTests/tsc.py | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CIME/SystemTests/pgn.py b/CIME/SystemTests/pgn.py index afbdd236e8e..ac68773aeb4 100644 --- a/CIME/SystemTests/pgn.py +++ b/CIME/SystemTests/pgn.py @@ -45,7 +45,9 @@ ] ) FCLD_NC = "cam.h0.cloud.nc" -INIT_COND_FILE_TEMPLATE = "20231105.v3b01.F2010.ne4_oQU240.chrysalis.{}.{}.0002-{:02d}-01-00000.nc" +INIT_COND_FILE_TEMPLATE = ( + "20231105.v3b01.F2010.ne4_oQU240.chrysalis.{}.{}.0002-{:02d}-01-00000.nc" +) INSTANCE_FILE_TEMPLATE = "{}{}_{:04d}.h0.0001-01-01-00000{}.nc" diff --git a/CIME/SystemTests/tsc.py b/CIME/SystemTests/tsc.py index 3a122af893d..1a37ecaac5d 100644 --- a/CIME/SystemTests/tsc.py +++ b/CIME/SystemTests/tsc.py @@ -32,7 +32,9 @@ SIM_LENGTH = 600 # seconds OUT_FREQ = 10 # seconds INSPECT_AT = [300, 450, 600] # seconds -INIT_COND_FILE_TEMPLATE = "20231105.v3b01.F2010.ne4_oQU240.chrysalis.{}.{}.0002-{:02d}-01-00000.nc" +INIT_COND_FILE_TEMPLATE = ( + "20231105.v3b01.F2010.ne4_oQU240.chrysalis.{}.{}.0002-{:02d}-01-00000.nc" +) VAR_LIST = [ "T", "Q", From efee4eb3c960ecd528cdcb5a070e529cc2117ead Mon Sep 17 00:00:00 2001 From: Mariana Vertenstein Date: Mon, 1 Apr 2024 11:53:27 +0200 Subject: [PATCH 390/390] made the cime cprnc submodule use https rather than ssh --- .gitmodules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitmodules b/.gitmodules index 13f9ecb952f..819202b6622 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ [submodule "CIME/non_py/cprnc"] path = CIME/non_py/cprnc - url = git@github.com:ESMCI/cprnc + url = https://github.com/ESMCI/cprnc