From 74c5932f8c43b21ecd5863991b763e013c6dd7fb Mon Sep 17 00:00:00 2001 From: Xylar Asay-Davis Date: Sat, 21 Mar 2026 12:43:01 +0100 Subject: [PATCH 01/22] Update to v2.0.0-alpha.1 --- compass/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compass/version.py b/compass/version.py index 83290e3ddf..6e3b223f6e 100644 --- a/compass/version.py +++ b/compass/version.py @@ -1 +1 @@ -__version__ = '1.9.0-alpha.2' +__version__ = '2.0.0-alpha.1' From 4d8795d690312161cb56e9306e9bb3aed511edc6 Mon Sep 17 00:00:00 2001 From: Xylar Asay-Davis Date: Sat, 21 Mar 2026 12:24:59 +0100 Subject: [PATCH 02/22] Switch to pyproject.toml --- .flake8.cfg | 16 +++++++ .pre-commit-config.yaml | 2 +- pyproject.toml | 102 ++++++++++++++++++++++++++++++++++++++++ setup.cfg | 42 ----------------- setup.py | 87 ---------------------------------- 5 files changed, 119 insertions(+), 130 deletions(-) create mode 100644 .flake8.cfg create mode 100644 pyproject.toml delete mode 100644 setup.cfg delete mode 100644 setup.py diff --git a/.flake8.cfg b/.flake8.cfg new file mode 100644 index 0000000000..f7ad654c10 --- /dev/null +++ b/.flake8.cfg @@ -0,0 +1,16 @@ +[flake8] +# https://pep8.readthedocs.io/en/latest/intro.html#error-codes +ignore = + # line break after operator + W504 +# Max width of GitHub code review is 79 characters +max-line-length = 79 +max-complexity = 18 +per-file-ignores = + */__init__.py: F401 +exclude = + .git, + docs, + .idea, + .mypy_cache, + .pytest_cache, diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a4305a176a..9516db5851 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -22,7 +22,7 @@ repos: rev: 7.3.0 hooks: - id: flake8 - args: ["--config=setup.cfg"] + args: ["--config=.flake8.cfg"] additional_dependencies: [flake8-isort] # https://pre-commit.ci/#configuration diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000000..74a796ebb9 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,102 @@ +[project] +name = "compass" +dynamic = ["version"] +authors = [ + { name = "COMPASS Developers", email = "mpas-developers@googlegroups.com" }, +] +description = "Configuration Of Model for Prediction Across Scales Setups (COMPASS) is an automated system to set up test cases that match the MPAS-Model repository. All namelists and streams files begin with the default generated from the Registry.xml file, and only the changes relevant to the particular test case are altered in those files." +readme = "README.md" +requires-python = ">=3.10" +license = "BSD-3-Clause" +classifiers = [ + "Development Status :: 4 - Beta", + "Intended Audience :: Science/Research", + "Operating System :: OS Independent", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Topic :: Scientific/Engineering", +] +dependencies = [ + "cartopy", + "cmocean", + "gsw", + "h5py", + "ipython", + "jupyter", + "lxml", + "matplotlib", + "netcdf4", + "numpy", + "progressbar2", + "pyamg", + "pyproj", + "requests", + "ruamel.yaml", + "scipy>=1.8.0", + "shapely", + "xarray", +] + +[project.scripts] +compass = "compass.__main__:main" + +[project.urls] +Homepage = "https://github.com/MPAS-Dev/MPAS-Tools" + +[build-system] +requires = ["setuptools>=61"] +build-backend = "setuptools.build_meta" + +[tool.setuptools.packages.find] +include = ["compass", "compass.*"] + +[tool.setuptools.package-data] +compass = [ + "*.cfg", + "*.csv", + "*.geojson", + "*.json", + "*.mat", + "*.nml", + "*.template", + "*.txt", + "*.yaml", + "*.README", + "README", + "namelist*", + "streams*", + "**/*.cfg", + "**/*.csv", + "**/*.geojson", + "**/*.json", + "**/*.mat", + "**/*.nml", + "**/*.template", + "**/*.txt", + "**/*.yaml", + "**/README", + "**/namelist*", + "**/streams*", +] + +[tool.setuptools.dynamic] +version = { attr = "compass.version.__version__" } + +[tool.mypy] +python_version = "3.14" +check_untyped_defs = true +ignore_missing_imports = true +warn_unused_ignores = true +warn_redundant_casts = true +warn_unused_configs = true + +[tool.isort] +multi_line_output = 3 +include_trailing_comma = true +force_grid_wrap = 0 +use_parentheses = true +line_length = 79 diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 7c7f54af0e..0000000000 --- a/setup.cfg +++ /dev/null @@ -1,42 +0,0 @@ -[flake8] -# https://pep8.readthedocs.io/en/latest/intro.html#error-codes -ignore = - # line break after operator - W504 -# Max width of Github code review is 79 characters -max-line-length = 79 -max-complexity = 18 -per-file-ignores = - */__init__.py: F401 -exclude = - .git, - docs, - .idea, - .mypy_cache, - .pytest_cache, - -[isort] -multi_line_output=3 -include_trailing_comma=True -force_grid_wrap=0 -use_parentheses=True -line_length=79 -skip= - e3sm_diags/e3sm_diags_driver.py - -[pycodestyle] -max-line-length = 79 -exclude = - .git - docs - .idea - .mypy_cache - .pytest_cache - -[mypy] -python_version = 3.13 -check_untyped_defs = True -ignore_missing_imports = True -warn_unused_ignores = True -warn_redundant_casts = True -warn_unused_configs = True diff --git a/setup.py b/setup.py deleted file mode 100644 index 4f7de6bf42..0000000000 --- a/setup.py +++ /dev/null @@ -1,87 +0,0 @@ -#!/usr/bin/env python - -import os - -from setuptools import find_packages, setup - - -def package_files(directory, prefixes, extensions): - """ based on https://stackoverflow.com/a/36693250/7728169""" - paths = [] - for (path, directories, filenames) in os.walk(directory): - for filename in filenames: - parts = filename.split('.') - prefix = parts[0] - extension = parts[-1] - if prefix in prefixes or extension in extensions: - paths.append(os.path.join('..', path, filename)) - return paths - - -install_requires = \ - ['cartopy', - 'cmocean', - 'gsw', - 'h5py', - 'ipython', - 'jupyter', - 'lxml', - 'matplotlib', - 'netcdf4', - 'numpy', - 'progressbar2', - 'pyamg', - 'pyproj', - 'requests', - 'ruamel.yaml', - 'scipy>=1.8.0', - 'shapely', - 'xarray'] - -here = os.path.abspath(os.path.dirname(__file__)) -version_path = os.path.join(here, 'compass', 'version.py') -with open(version_path) as f: - main_ns = {} - exec(f.read(), main_ns) - version = main_ns['__version__'] - -os.chdir(here) - -data_files = package_files('compass', - prefixes=['namelist', 'streams', 'README'], - extensions=['cfg', 'csv', 'template', 'json', - 'txt', 'geojson', 'mat', 'nml', - 'yaml']) - -setup(name='compass', - version=version, - description='Configuration Of Model for Prediction Across Scales ' - 'Setups (COMPASS) is an automated system to set up test ' - 'cases that match the MPAS-Model repository. All ' - 'namelists and streams files begin with the default ' - 'generated from the Registry.xml file, and only the ' - 'changes relevant to the particular test case are altered ' - 'in those files.', - url='https://github.com/MPAS-Dev/MPAS-Tools', - author='COMPASS Developers', - author_email='mpas-developers@googlegroups.com', - license='BSD', - classifiers=[ - 'Development Status :: 4 - Beta', - 'License :: OSI Approved :: BSD License', - 'Operating System :: OS Independent', - 'Intended Audience :: Science/Research', - 'Programming Language :: Python', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.10', - 'Programming Language :: Python :: 3.11', - 'Programming Language :: Python :: 3.12', - 'Programming Language :: Python :: 3.13', - 'Topic :: Scientific/Engineering', - ], - packages=find_packages(include=['compass', 'compass.*']), - package_data={'': data_files}, - install_requires=install_requires, - entry_points={'console_scripts': - ['compass = compass.__main__:main', - 'create_compass_load_script=compass.load:main']}) From 54ebdd1d5582b14d5cdc7d1fcdb520203c78b71b Mon Sep 17 00:00:00 2001 From: Xylar Asay-Davis Date: Sat, 21 Mar 2026 11:25:36 +0100 Subject: [PATCH 03/22] Move deployment to `deploy.py` using `mache.deploy` --- compass/machines/conda-osx.cfg | 9 - .../{conda-linux.cfg => default-linux-64.cfg} | 3 +- compass/machines/default-osx-64.cfg | 8 + conda/.gitignore | 1 - conda/bootstrap.py | 1185 ----------------- conda/compass_env/load_compass.template | 37 - conda/compass_env/spec-file.template | 78 -- conda/configure_compass_env.py | 138 -- conda/default.cfg | 40 - conda/packages/build_packages.py | 330 ----- .../compass/recipe/conda_build_config.yaml | 4 - conda/packages/compass/recipe/recipe.yaml | 115 -- .../packages/compass/variants/linux_64_.yaml | 16 - conda/packages/compass/variants/osx_64_.yaml | 20 - conda/packages/otps/recipe/build.sh | 13 - conda/packages/otps/recipe/recipe.yaml | 37 - conda/packages/otps/variants/linux_64_.yaml | 16 - conda/packages/otps/variants/osx_64_.yaml | 20 - conda/packages/rattler-build-config.toml | 5 - conda/petsc_supported.txt | 11 - conda/shared.py | 281 ---- conda/spack/eligos_gnu_openmpi.yaml | 38 - conda/spack/morpheus_gnu_openmpi.yaml | 38 - deploy.py | 494 +++++++ {conda => deploy}/albany_supported.txt | 1 - deploy/cli_spec.json | 106 ++ deploy/config.yaml.j2 | 179 +++ deploy/custom_cli_spec.json | 11 + deploy/hooks.py | 197 +++ deploy/load.sh | 62 + deploy/pins.cfg | 29 + deploy/pixi.toml.j2 | 93 ++ deploy/spack.yaml.j2 | 41 + {conda => deploy}/unsupported.txt | 9 - 34 files changed, 1221 insertions(+), 2444 deletions(-) delete mode 100644 compass/machines/conda-osx.cfg rename compass/machines/{conda-linux.cfg => default-linux-64.cfg} (64%) create mode 100644 compass/machines/default-osx-64.cfg delete mode 100644 conda/.gitignore delete mode 100755 conda/bootstrap.py delete mode 100644 conda/compass_env/load_compass.template delete mode 100644 conda/compass_env/spec-file.template delete mode 100755 conda/configure_compass_env.py delete mode 100644 conda/default.cfg delete mode 100755 conda/packages/build_packages.py delete mode 100644 conda/packages/compass/recipe/conda_build_config.yaml delete mode 100644 conda/packages/compass/recipe/recipe.yaml delete mode 100644 conda/packages/compass/variants/linux_64_.yaml delete mode 100644 conda/packages/compass/variants/osx_64_.yaml delete mode 100644 conda/packages/otps/recipe/build.sh delete mode 100644 conda/packages/otps/recipe/recipe.yaml delete mode 100644 conda/packages/otps/variants/linux_64_.yaml delete mode 100644 conda/packages/otps/variants/osx_64_.yaml delete mode 100644 conda/packages/rattler-build-config.toml delete mode 100644 conda/petsc_supported.txt delete mode 100644 conda/shared.py delete mode 100644 conda/spack/eligos_gnu_openmpi.yaml delete mode 100644 conda/spack/morpheus_gnu_openmpi.yaml create mode 100755 deploy.py rename {conda => deploy}/albany_supported.txt (87%) create mode 100644 deploy/cli_spec.json create mode 100644 deploy/config.yaml.j2 create mode 100644 deploy/custom_cli_spec.json create mode 100644 deploy/hooks.py create mode 100644 deploy/load.sh create mode 100644 deploy/pins.cfg create mode 100644 deploy/pixi.toml.j2 create mode 100644 deploy/spack.yaml.j2 rename {conda => deploy}/unsupported.txt (71%) diff --git a/compass/machines/conda-osx.cfg b/compass/machines/conda-osx.cfg deleted file mode 100644 index f6a859c719..0000000000 --- a/compass/machines/conda-osx.cfg +++ /dev/null @@ -1,9 +0,0 @@ -# Options related to deploying a compass conda environment on supported -# machines -[deploy] - -# the compiler set to use for system libraries and MPAS builds -compiler = clang - -# the system MPI library to use for gnu compiler -mpi_clang = mpich diff --git a/compass/machines/conda-linux.cfg b/compass/machines/default-linux-64.cfg similarity index 64% rename from compass/machines/conda-linux.cfg rename to compass/machines/default-linux-64.cfg index b86800d427..ea05fc8065 100644 --- a/compass/machines/conda-linux.cfg +++ b/compass/machines/default-linux-64.cfg @@ -1,5 +1,4 @@ -# Options related to deploying a compass conda environment on supported -# machines +# Options related to deploying compass environments on supported machines [deploy] # the compiler set to use for system libraries and MPAS builds diff --git a/compass/machines/default-osx-64.cfg b/compass/machines/default-osx-64.cfg new file mode 100644 index 0000000000..17b03785b3 --- /dev/null +++ b/compass/machines/default-osx-64.cfg @@ -0,0 +1,8 @@ +# Options related to deploying compass environments on supported machines +[deploy] + +# the compiler set to use for system libraries and MPAS builds +compiler = clang + +# the system MPI library to use for clang compiler +mpi_clang = mpich diff --git a/conda/.gitignore b/conda/.gitignore deleted file mode 100644 index 53762ee938..0000000000 --- a/conda/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/build_*/ diff --git a/conda/bootstrap.py b/conda/bootstrap.py deleted file mode 100755 index 6a0736601d..0000000000 --- a/conda/bootstrap.py +++ /dev/null @@ -1,1185 +0,0 @@ -#!/usr/bin/env python3 - -import glob -import grp -import importlib.resources -import os -import platform -import shutil -import socket -import stat -import subprocess -import time -from configparser import ConfigParser - -import progressbar -from jinja2 import Template -from mache import MachineInfo -from mache import discover_machine as mache_discover_machine -from mache.spack import get_spack_script, make_spack_env -from packaging import version -from shared import ( - check_call, - get_conda_base, - get_logger, - get_spack_base, - install_miniforge, - log_message, - parse_args, -) - - -def get_config(config_file, machine): - # we can't load compass so we find the config files - here = os.path.abspath(os.path.dirname(__file__)) - default_config = os.path.join(here, 'default.cfg') - config = ConfigParser() - config.read(default_config) - - if machine is not None: - if not machine.startswith('conda'): - machine_config = \ - importlib.resources.files('mache.machines') / f'{machine}.cfg' - config.read(str(machine_config)) - - machine_config = os.path.join(here, '..', 'compass', 'machines', - f'{machine}.cfg') - config.read(machine_config) - - if config_file is not None: - config.read(config_file) - - return config - - -def get_version(): - # we can't import compass because we probably don't have the necessary - # dependencies, so we get the version by parsing (same approach used in - # the root setup.py) - here = os.path.abspath(os.path.dirname(__file__)) - version_path = os.path.join(here, '..', 'compass', 'version.py') - with open(version_path) as f: - main_ns = {} - exec(f.read(), main_ns) - version = main_ns['__version__'] - - return version - - -def get_compilers_mpis(config, machine, compilers, mpis, # noqa: C901 - source_path): - - unsupported = parse_unsupported(machine, source_path) - if machine is None: - all_compilers = None - all_mpis = None - elif machine == 'conda-linux': - all_compilers = ['gfortran'] - all_mpis = ['mpich', 'openmpi'] - elif machine == 'conda-osx': - all_compilers = ['clang'] - all_mpis = ['mpich', 'openmpi'] - else: - machine_info = MachineInfo(machine) - all_compilers = machine_info.compilers - all_mpis = machine_info.mpilibs - - if config.has_option('deploy', 'compiler'): - default_compiler = config.get('deploy', 'compiler') - else: - default_compiler = None - - error_on_unsupported = True - - if compilers is not None and compilers[0] == 'all': - error_on_unsupported = False - if mpis is not None and mpis[0] == 'all': - # make a matrix of compilers and mpis - compilers = list() - mpis = list() - for compiler in all_compilers: - for mpi in all_mpis: - compilers.append(compiler) - mpis.append(mpi) - else: - compilers = all_compilers - if mpis is not None: - if len(mpis) > 1: - raise ValueError(f'"--compiler all" can only be combined ' - f'with "--mpi all" or a single MPI ' - f'library, \n' - f'but got: {mpis}') - mpi = mpis[0] - mpis = [mpi for _ in compilers] - - elif mpis is not None and mpis[0] == 'all': - error_on_unsupported = False - mpis = all_mpis - if compilers is None: - compiler = default_compiler - else: - if len(compilers) > 1: - raise ValueError(f'"--mpis all" can only be combined with ' - f'"--compiler all" or a single compiler, \n' - f'but got: {compilers}') - compiler = compilers[0] - # The compiler is all the same - compilers = [compiler for _ in mpis] - - if compilers is None: - if config.has_option('deploy', 'compiler'): - compilers = [config.get('deploy', 'compiler')] - else: - compilers = [None] - - if mpis is None: - mpis = list() - for compiler in compilers: - if compiler is None: - mpi = 'nompi' - else: - mpi = config.get('deploy', 'mpi_{}'.format(compiler)) - mpis.append(mpi) - - supported_compilers = list() - supported_mpis = list() - for compiler, mpi in zip(compilers, mpis): - if (compiler, mpi) in unsupported: - if error_on_unsupported: - raise ValueError(f'{compiler} with {mpi} is not supported on ' - f'{machine}') - else: - supported_compilers.append(compiler) - supported_mpis.append(mpi) - - return supported_compilers, supported_mpis - - -def get_env_setup(args, config, machine, compiler, mpi, env_type, source_path, - conda_base, env_name, compass_version, logger): - - if args.python is not None: - python = args.python - else: - python = config.get('deploy', 'python') - - if args.recreate is not None: - recreate = args.recreate - else: - recreate = config.getboolean('deploy', 'recreate') - - if machine is None: - conda_mpi = 'nompi' - activ_suffix = '' - env_suffix = '' - elif not machine.startswith('conda'): - conda_mpi = 'nompi' - activ_suffix = '_{}_{}_{}'.format(machine, compiler, mpi) - env_suffix = '' - else: - activ_suffix = '_{}'.format(mpi) - env_suffix = activ_suffix - conda_mpi = mpi - - lib_suffix = '' - if args.with_albany: - lib_suffix = f'{lib_suffix}_albany' - else: - config.set('deploy', 'albany', 'None') - - if args.with_petsc: - lib_suffix = f'{lib_suffix}_petsc' - log_message( - logger, - 'Turning off OpenMP because it doesn\'t work well with PETSc') - args.without_openmp = True - else: - config.set('deploy', 'petsc', 'None') - config.set('deploy', 'lapack', 'None') - - activ_suffix = f'{activ_suffix}{lib_suffix}' - - if env_type == 'dev': - activ_path = source_path - else: - activ_path = os.path.abspath(os.path.join(conda_base, '..')) - - if args.with_albany: - check_supported('albany', machine, compiler, mpi, source_path) - - if args.with_petsc: - check_supported('petsc', machine, compiler, mpi, source_path) - - if env_type == 'dev': - ver = version.parse(compass_version) - release_version = '.'.join(str(vr) for vr in ver.release) - spack_env = f'dev_compass_{release_version}{env_suffix}' - compass_env = f'dev_compass_{compass_version}{env_suffix}' - elif env_type == 'test_release': - spack_env = f'test_compass_{compass_version}{env_suffix}' - compass_env = spack_env - else: - spack_env = f'compass_{compass_version}{env_suffix}' - compass_env = spack_env - - if env_name is None or env_type != 'dev': - env_name = compass_env - - # add the compiler and MPI library to the spack env name - spack_env = f'{spack_env}_{compiler}_{mpi}{lib_suffix}' - # spack doesn't like dots - spack_env = spack_env.replace('.', '_') - - env_path = os.path.join(conda_base, 'envs', env_name) - - source_activation_scripts = \ - f'source {conda_base}/etc/profile.d/conda.sh' - - activate_env = f'{source_activation_scripts} && conda activate {env_name}' - - return python, recreate, conda_mpi, activ_suffix, env_suffix, \ - activ_path, env_path, env_name, activate_env, spack_env - - -def build_conda_env(env_type, recreate, mpi, conda_mpi, version, - python, source_path, conda_template_path, conda_base, - env_name, env_path, activate_base, use_local, - local_conda_build, logger, local_mache, update_jigsaw, - with_alphabetalab): - - if env_type != 'dev': - install_miniforge(conda_base, activate_base, logger) - - if conda_mpi == 'nompi': - mpi_prefix = 'nompi' - else: - mpi_prefix = f'mpi_{mpi}' - - channels = ['-c conda-forge', '-c defaults'] - if use_local: - channels = ['--use-local'] + channels - if local_conda_build is not None: - channels = ['-c', local_conda_build] + channels - if env_type == 'test_release': - # for a test release, we will be the compass package from the dev label - channels = channels + ['-c e3sm/label/compass_dev'] - channels = channels + ['-c e3sm/label/compass'] - - channels = f'--override-channels {" ".join(channels)}' - packages = f'python={python}' - - conda_base = os.path.abspath(conda_base) - - activate_env = \ - f'source {conda_base}/etc/profile.d/conda.sh &&' \ - f'conda activate {env_name}' - - with open(f'{conda_template_path}/spec-file.template', 'r') as f: - template = Template(f.read()) - - if env_type == 'dev': - supports_otps = platform.system() == 'Linux' - if platform.system() == 'Linux': - conda_openmp = 'libgomp' - elif platform.system() == 'Darwin': - conda_openmp = 'llvm-openmp' - else: - conda_openmp = '' - spec_file = template.render(supports_otps=supports_otps, - mpi=conda_mpi, openmp=conda_openmp, - mpi_prefix=mpi_prefix, - include_mache=not local_mache) - - spec_filename = f'spec-file-{conda_mpi}.txt' - with open(spec_filename, 'w') as handle: - handle.write(spec_file) - else: - spec_filename = None - - if not os.path.exists(env_path): - recreate = True - - if recreate: - print(f'creating {env_name}') - if env_type == 'dev': - # install dev dependencies and compass itself - commands = \ - f'{activate_base} && ' \ - f'conda create -y -n {env_name} {channels} ' \ - f'--file {spec_filename} {packages}' - check_call(commands, logger=logger) - else: - # conda packages don't like dashes - version_conda = version.replace('-', '') - packages = f'{packages} "compass={version_conda}={mpi_prefix}_*"' - commands = \ - f'{activate_base} && ' \ - f'conda create -y -n {env_name} {channels} {packages}' - check_call(commands, logger=logger) - else: - if env_type == 'dev': - print(f'Updating {env_name}\n') - # install dev dependencies and compass itself - commands = \ - f'{activate_base} && ' \ - f'conda install -y -n {env_name} {channels} ' \ - f'--file {spec_filename} {packages}' - check_call(commands, logger=logger) - else: - print(f'{env_name} already exists') - - if env_type == 'dev': - if with_alphabetalab: - print('Installing AlphaBetaLab\n') - commands = \ - f'{activate_env} && ' \ - f'cd {source_path} && ' \ - f'git submodule update --init alphaBetaLab &&' \ - f'cd {source_path}/alphaBetaLab&& ' \ - f'conda install -y --file dev-spec.txt && ' \ - f'python -m pip install --no-deps --no-build-isolation -e .' - check_call(commands, logger=logger) - - if recreate or update_jigsaw: - build_jigsaw(activate_env, source_path, env_path, logger) - - # install (or reinstall) compass in edit mode - print('Installing compass\n') - commands = \ - f'{activate_env} && ' \ - f'cd {source_path} && ' \ - f'rm -rf compass.egg-info && ' \ - f'python -m pip install --no-deps --no-build-isolation -e .' - check_call(commands, logger=logger) - - print('Installing pre-commit\n') - commands = \ - f'{activate_env} && ' \ - f'cd {source_path} && ' \ - f'pre-commit install' - check_call(commands, logger=logger) - - -def build_jigsaw(activate_env, source_path, env_path, logger): - # remove conda jigsaw and jigsaw-python - t0 = time.time() - commands = \ - f'{activate_env} && ' \ - f'conda remove -y --force-remove jigsaw jigsawpy' - try: - check_call(commands, logger=logger) - except subprocess.CalledProcessError: - # it's fine if these aren't installed, we just want to make sure - pass - - commands = \ - f'{activate_env} && ' \ - f'cd {source_path} && ' \ - f'git submodule update --init jigsaw-python' - check_call(commands, logger=logger) - - print('Building JIGSAW\n') - # add build tools to deployment env, not compass env - jigsaw_build_deps = 'cxx-compiler cmake' - if platform.system() == 'Linux': - jigsaw_build_deps = f'{jigsaw_build_deps} sysroot_linux-64=2.17' - elif platform.system() == 'Darwin': - jigsaw_build_deps = \ - f'{jigsaw_build_deps} macosx_deployment_target=10.13' - netcdf_lib = f'{env_path}/lib/libnetcdf.so' - cmake_args = f'-DCMAKE_BUILD_TYPE=Release -DNETCDF_LIBRARY={netcdf_lib}' - - commands = \ - f'conda install -y {jigsaw_build_deps} && ' \ - f'cd {source_path}/jigsaw-python/external/jigsaw && ' \ - f'rm -rf tmp && ' \ - f'mkdir tmp && ' \ - f'cd tmp && ' \ - f'cmake .. {cmake_args} && ' \ - f'cmake --build . --config Release --target install --parallel 4 && ' \ - f'cd {source_path}/jigsaw-python && ' \ - f'rm -rf jigsawpy/_bin jigsawpy/_lib && ' \ - f'cp -r external/jigsaw/bin/ jigsawpy/_bin && ' \ - f'cp -r external/jigsaw/lib/ jigsawpy/_lib' - check_call(commands, logger=logger) - - print('Installing JIGSAW and JIGSAW-Python\n') - commands = \ - f'{activate_env} && ' \ - f'cd {source_path}/jigsaw-python && ' \ - f'python -m pip install --no-deps --no-build-isolation -e . && ' \ - f'cp jigsawpy/_bin/* ${{CONDA_PREFIX}}/bin' - check_call(commands, logger=logger) - - t1 = time.time() - total = int(t1 - t0 + 0.5) - message = f'JIGSAW install took {total:.1f} s.' # noqa: 231 - if logger is None: - print(message) - else: - logger.info(message) - - -def get_env_vars(machine, compiler, mpilib): - - if machine is None: - machine = 'None' - - # convert env vars from mache to a list - env_vars = 'export MPAS_EXTERNAL_LIBS=""\n' - - if 'intel' in compiler and machine == 'anvil': - env_vars = \ - f'{env_vars}' \ - f'export I_MPI_CC=icc\n' \ - f'export I_MPI_CXX=icpc\n' \ - f'export I_MPI_F77=ifort\n' \ - f'export I_MPI_F90=ifort\n' - - if machine.startswith('conda'): - # we're using parallelio so we don't have ADIOS support - env_vars = \ - f'{env_vars}' \ - f'export HAVE_ADIOS=false\n' - - if platform.system() == 'Linux' and machine.startswith('conda'): - env_vars = \ - f'{env_vars}' \ - f'export MPAS_EXTERNAL_LIBS="${{MPAS_EXTERNAL_LIBS}} -lgomp"\n' - - if mpilib == 'mvapich': - env_vars = \ - f'{env_vars}' \ - f'export MV2_ENABLE_AFFINITY=0\n' \ - f'export MV2_SHOW_CPU_BINDING=1\n' - - if machine.startswith('chicoma') or machine.startswith('pm'): - env_vars = \ - f'{env_vars}' \ - f'export NETCDF=${{CRAY_NETCDF_HDF5PARALLEL_PREFIX}}\n' \ - f'export NETCDFF=${{CRAY_NETCDF_HDF5PARALLEL_PREFIX}}\n' \ - f'export PNETCDF=${{CRAY_PARALLEL_NETCDF_PREFIX}}\n' - else: - env_vars = \ - f'{env_vars}' \ - f'export NETCDF=$(dirname $(dirname $(which nc-config)))\n' \ - f'export NETCDFF=$(dirname $(dirname $(which nf-config)))\n' \ - f'export PNETCDF=$(dirname $(dirname $(which pnetcdf-config)))\n' - - return env_vars - - -def build_spack_env(config, update_spack, machine, compiler, mpi, # noqa: C901 - spack_env, spack_base, spack_template_path, env_vars, - tmpdir, logger): - - albany = config.get('deploy', 'albany') - albany_variants = config.get('deploy', 'albany_variants') - trilinos_variants = config.get('deploy', 'trilinos_variants') - cmake = config.get('deploy', 'cmake') - esmf = config.get('deploy', 'esmf') - lapack = config.get('deploy', 'lapack') - metis = config.get('deploy', 'metis') - moab = config.get('deploy', 'moab') - petsc = config.get('deploy', 'petsc') - scorpio = config.get('deploy', 'scorpio') - parallelio = config.get('deploy', 'parallelio') - - if config.has_option('deploy', 'spack_mirror'): - spack_mirror = config.get('deploy', 'spack_mirror') - else: - spack_mirror = None - - spack_branch_base = f'{spack_base}/{spack_env}' - - specs = list() - - if cmake != 'None': - specs.append(f'cmake@{cmake}') - - e3sm_hdf5_netcdf = config.getboolean('deploy', 'use_e3sm_hdf5_netcdf') - if not e3sm_hdf5_netcdf: - hdf5 = config.get('deploy', 'hdf5') - netcdf_c = config.get('deploy', 'netcdf_c') - netcdf_fortran = config.get('deploy', 'netcdf_fortran') - pnetcdf = config.get('deploy', 'pnetcdf') - specs.extend([ - f'hdf5@{hdf5}+cxx+fortran+hl+mpi+shared', - f'netcdf-c@{netcdf_c}+mpi~parallel-netcdf', - f'netcdf-fortran@{netcdf_fortran}', - f'parallel-netcdf@{pnetcdf}+cxx+fortran']) - - if esmf != 'None': - specs.append(f'esmf@{esmf}+mpi+netcdf~pnetcdf~external-parallelio') - if lapack != 'None': - specs.append(f'netlib-lapack@{lapack}') - include_e3sm_lapack = False - else: - include_e3sm_lapack = True - if metis != 'None': - specs.append( - f'metis@{metis}+int64+real64') - if moab != 'None': - specs.append( - f'moab@{moab}+mpi+hdf5+netcdf+pnetcdf+metis+parmetis+tempest') - if petsc != 'None': - specs.append(f'petsc@{petsc}+mpi+batch') - - custom_spack = '' - if scorpio != 'None': - specs.append( - f'e3sm-scorpio' - f'@{scorpio}+mpi~timing~internal-timing~tools+malloc') - # make sure scorpio, not esmf, libraries are linked - lib_path = \ - f'{spack_branch_base}/var/spack/environments/' \ - f'{spack_env}/.spack-env/view/lib' - scorpio_lib_path = '$(spack find --format "{prefix}" e3sm-scorpio)' - if scorpio_lib_path == '': - raise ValueError('Could not find e3sm-scorpio in Spack. ' - 'Did something go wrong with the build?') - custom_spack = \ - f'{custom_spack}' \ - f'ln -sfn {scorpio_lib_path}/lib/libpioc.a {lib_path}\n' \ - f'ln -sfn {scorpio_lib_path}/lib/libpiof.a {lib_path}\n' - - if parallelio != 'None': - specs.append( - f'parallelio' - f'@{parallelio}+pnetcdf~timing') - - if albany != 'None': - specs.append(f'trilinos-for-albany@{albany}{trilinos_variants}') - specs.append(f'albany@{albany}{albany_variants}') - - yaml_template = f'{spack_template_path}/{machine}_{compiler}_{mpi}.yaml' - if not os.path.exists(yaml_template): - yaml_template = None - - if machine is not None: - here = os.path.abspath(os.path.dirname(__file__)) - machine_config = os.path.join(here, '..', 'compass', 'machines', - f'{machine}.cfg') - else: - machine_config = None - - if update_spack: - home_dir = os.path.expanduser('~') - log_message(logger, 'Removing ~/.spack for safety') - safe_rmtree(os.path.join(home_dir, '.spack')) - make_spack_env(spack_path=spack_branch_base, env_name=spack_env, - spack_specs=specs, compiler=compiler, mpi=mpi, - machine=machine, config_file=machine_config, - include_e3sm_lapack=include_e3sm_lapack, - include_e3sm_hdf5_netcdf=e3sm_hdf5_netcdf, - yaml_template=yaml_template, tmpdir=tmpdir, - custom_spack=custom_spack, spack_mirror=spack_mirror) - - # remove ESMC/ESMF include files that interfere with MPAS time keeping - include_path = \ - f'{spack_branch_base}/var/spack/environments/' \ - f'{spack_env}/.spack-env/view/include' - for prefix in ['ESMC', 'esmf']: - files = glob.glob(os.path.join(include_path, f'{prefix}*')) - for filename in files: - os.remove(filename) - set_ld_library_path(spack_branch_base, spack_env, logger) - - spack_script = get_spack_script( - spack_path=spack_branch_base, env_name=spack_env, compiler=compiler, - mpi=mpi, shell='sh', machine=machine, config_file=machine_config, - include_e3sm_lapack=include_e3sm_lapack, - include_e3sm_hdf5_netcdf=e3sm_hdf5_netcdf, - yaml_template=yaml_template) - - spack_view = \ - f'{spack_branch_base}/var/spack/environments/' \ - f'{spack_env}/.spack-env/view' - env_vars = \ - f'{env_vars}' \ - f'export PIO={spack_view}\n' - if parallelio != 'None': - env_vars = \ - f'{env_vars}' \ - f'export HAVE_ADIOS=false\n' - if albany != 'None': - albany_flag_filename = f'{spack_view}/export_albany.in' - if not os.path.exists(albany_flag_filename): - raise ValueError(f'Missing Albany linking flags in ' - f'{albany_flag_filename}.\n Maybe your Spack ' - f'environment may need to be rebuilt with ' - f'Albany?') - with open(albany_flag_filename, 'r') as f: - albany_flags = f.read() - if platform.system() == 'Darwin': - stdcxx = '-lc++' - else: - stdcxx = '-lstdc++' - if mpi == 'openmpi' and machine in ['anvil', 'chrysalis']: - mpicxx = '-lmpi_cxx' - else: - mpicxx = '' - env_vars = \ - f'{env_vars}' \ - f'export {albany_flags}\n' \ - f'export MPAS_EXTERNAL_LIBS="${{MPAS_EXTERNAL_LIBS}} ' \ - f'${{ALBANY_LINK_LIBS}} {stdcxx} {mpicxx}"\n' - - if lapack != 'None': - env_vars = \ - f'{env_vars}' \ - f'export LAPACK={spack_view}\n' \ - f'export USE_LAPACK=true\n' - - if petsc != 'None': - env_vars = \ - f'{env_vars}' \ - f'export PETSC={spack_view}\n' \ - f'export USE_PETSC=true\n' - - return spack_branch_base, spack_script, env_vars - - -def set_ld_library_path(spack_branch_base, spack_env, logger): - commands = \ - f'source {spack_branch_base}/share/spack/setup-env.sh && ' \ - f'spack env activate {spack_env} && ' \ - f'spack config add modules:prefix_inspections:lib:[LD_LIBRARY_PATH] && ' \ - f'spack config add modules:prefix_inspections:lib64:[LD_LIBRARY_PATH]' # noqa: E501,E231 - check_call(commands, logger=logger) - - -def write_load_compass(template_path, activ_path, conda_base, env_type, - activ_suffix, prefix, env_name, spack_script, machine, - env_vars, env_only, source_path, without_openmp, - compass_version): - - try: - os.makedirs(activ_path) - except FileExistsError: - pass - - if prefix.endswith(activ_suffix): - # avoid a redundant activation script name if the suffix is already - # part of the environment name - script_filename = '{}/{}.sh'.format(activ_path, prefix) - else: - script_filename = '{}/{}{}.sh'.format(activ_path, prefix, activ_suffix) - - if not env_only: - env_vars = \ - f'{env_vars}\n' \ - f'export USE_PIO2=true' - if without_openmp: - env_vars = \ - f'{env_vars}\n' \ - f'export OPENMP=false' - else: - env_vars = \ - f'{env_vars}\n' \ - f'export OPENMP=true' - - env_vars = \ - f'{env_vars}\n' \ - f'export HDF5_USE_FILE_LOCKING=FALSE\n' \ - f'export LOAD_COMPASS_ENV={script_filename}' - if machine is not None and not machine.startswith('conda'): - env_vars = \ - f'{env_vars}\n' \ - f'export COMPASS_MACHINE={machine}' - - filename = f'{template_path}/load_compass.template' - with open(filename, 'r') as f: - template = Template(f.read()) - - if env_type == 'dev': - update_compass = \ - """ - if [[ -z "${NO_COMPASS_REINSTALL}" && -f "./setup.py" && \\ - -d "compass" ]]; then - # safe to assume we're in the compass repo - # update the compass installation to point here - mkdir -p conda/logs - echo Reinstalling compass package in edit mode... - rm -rf compass.egg-info - python -m pip install --no-deps --no-build-isolation -e . \\ - &> conda/logs/install_compass.log - echo Done. - echo - fi - """ # noqa: E501 - else: - update_compass = '' - - script = template.render(conda_base=conda_base, compass_env=env_name, - env_vars=env_vars, - spack=spack_script, - update_compass=update_compass, - env_type=env_type, - compass_source_path=source_path, - compass_version=compass_version) - - # strip out redundant blank lines - lines = list() - prev_line = '' - for line in script.split('\n'): - line = line.strip() - if line != '' or prev_line != '': - lines.append(line) - prev_line = line - - lines.append('') - - script = '\n'.join(lines) - - print('Writing:\n {}\n'.format(script_filename)) - with open(script_filename, 'w') as handle: - handle.write(script) - - return script_filename - - -def check_env(script_filename, env_name, logger): - print("Checking the environment {}".format(env_name)) - - activate = 'source {}'.format(script_filename) - - imports = ['geometric_features', 'mpas_tools', 'jigsawpy', 'compass'] - commands = [['gpmetis', '--help'], - ['ffmpeg', '--help'], - ['compass', 'list'], - ['compass', 'setup', '--help'], - ['compass', 'suite', '--help'], - ['compass', 'clean', '--help']] - - for import_name in imports: - command = '{} && python -c "import {}"'.format(activate, import_name) - test_command(command, os.environ, import_name, logger) - - for command in commands: - package = command[0] - command = '{} && {}'.format(activate, ' '.join(command)) - test_command(command, os.environ, package, logger) - - -def test_command(command, env, package, logger): - try: - check_call(command, env=env, logger=logger) - except subprocess.CalledProcessError as e: - print(' {} failed'.format(package)) - raise e - print(' {} passes'.format(package)) - - -def update_permissions(config, env_type, activ_path, # noqa: C901 - directories): - - if not config.has_option('e3sm_unified', 'group'): - return - - group = config.get('e3sm_unified', 'group') - - new_uid = os.getuid() - new_gid = grp.getgrnam(group).gr_gid - - print('changing permissions on activation scripts') - - read_perm = (stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | - stat.S_IWGRP | stat.S_IROTH) - exec_perm = (stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR | - stat.S_IRGRP | stat.S_IWGRP | stat.S_IXGRP | - stat.S_IROTH | stat.S_IXOTH) - - mask = stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO - - if env_type != 'dev': - - activation_files = glob.glob('{}/*_compass*.sh'.format( - activ_path)) - for file_name in activation_files: - os.chmod(file_name, read_perm) - os.chown(file_name, new_uid, new_gid) - - print('changing permissions on environments') - - # first the base directories that don't seem to be included in - # os.walk() - for directory in directories: - try: - dir_stat = os.stat(directory) - except OSError: - continue - - perm = dir_stat.st_mode & mask - - if dir_stat.st_uid != new_uid: - # current user doesn't own this dir so let's move on - continue - - if perm == exec_perm and dir_stat.st_gid == new_gid: - continue - - try: - os.chown(directory, new_uid, new_gid) - os.chmod(directory, exec_perm) - except OSError: - continue - - files_and_dirs = [] - for base in directories: - for root, dirs, files in os.walk(base): - files_and_dirs.extend(dirs) - files_and_dirs.extend(files) - - widgets = [progressbar.Percentage(), ' ', progressbar.Bar(), - ' ', progressbar.ETA()] - bar = progressbar.ProgressBar(widgets=widgets, - maxval=len(files_and_dirs)).start() - progress = 0 - for base in directories: - for root, dirs, files in os.walk(base): - for directory in dirs: - progress += 1 - try: - bar.update(progress) - except ValueError: - pass - - directory = os.path.join(root, directory) - - try: - dir_stat = os.stat(directory) - except OSError: - continue - - if dir_stat.st_uid != new_uid: - # current user doesn't own this dir so let's move on - continue - - perm = dir_stat.st_mode & mask - - if perm == exec_perm and dir_stat.st_gid == new_gid: - continue - - try: - os.chown(directory, new_uid, new_gid) - os.chmod(directory, exec_perm) - except OSError: - continue - - for file_name in files: - progress += 1 - try: - bar.update(progress) - except ValueError: - pass - file_name = os.path.join(root, file_name) - try: - file_stat = os.stat(file_name) - except OSError: - continue - - if file_stat.st_uid != new_uid: - # current user doesn't own this file so let's move on - continue - - perm = file_stat.st_mode & mask - - if perm & stat.S_IXUSR: - # executable, so make sure others can execute it - new_perm = exec_perm - else: - new_perm = read_perm - - if perm == new_perm and file_stat.st_gid == new_gid: - continue - - try: - os.chown(file_name, new_uid, new_gid) - os.chmod(file_name, new_perm) - except OSError: - continue - - bar.finish() - print(' done.') - - -def parse_unsupported(machine, source_path): - with open(os.path.join(source_path, 'conda', 'unsupported.txt'), 'r') as f: - content = f.readlines() - content = [line.strip() for line in content if not - line.strip().startswith('#')] - unsupported = list() - for line in content: - if line.strip() == '': - continue - parts = [part.strip() for part in line.split(',')] - if len(parts) != 3: - raise ValueError(f'Bad line in "unsupported.txt" {line}') - if parts[0] != machine: - continue - compiler = parts[1] - mpi = parts[2] - unsupported.append((compiler, mpi)) - - return unsupported - - -def check_supported(library, machine, compiler, mpi, source_path): - filename = os.path.join(source_path, 'conda', f'{library}_supported.txt') - with open(filename, 'r') as f: - content = f.readlines() - content = [line.strip() for line in content if not - line.strip().startswith('#')] - for line in content: - if line.strip() == '': - continue - supported = [part.strip() for part in line.split(',')] - if len(supported) != 3: - raise ValueError(f'Bad line in "{library}_supported.txt" {line}') - if machine == supported[0] and compiler == supported[1] and \ - mpi == supported[2]: - return - - raise ValueError(f'{compiler} with {mpi} is not supported with {library} ' - f'on {machine}') - - -def safe_rmtree(path): - try: - shutil.rmtree(path) - except OSError: - pass - - -def discover_machine(quiet=False): - """ - Figure out the machine from the host name - - Parameters - ---------- - quiet : bool, optional - Whether to print warnings if the machine name is ambiguous - - Returns - ------- - machine : str - The name of the current machine - """ - machine = mache_discover_machine(quiet=quiet) - if machine is None: - possible_hosts = get_possible_hosts() - hostname = socket.gethostname() - for possible_machine, hostname_contains in possible_hosts.items(): - if hostname_contains in hostname: - machine = possible_machine - break - return machine - - -def get_possible_hosts(): - here = os.path.abspath(os.path.dirname(__file__)) - files = sorted(glob.glob(os.path.join( - here, '..', 'compass', 'machines', '*.cfg'))) - - possible_hosts = dict() - for filename in files: - machine = os.path.splitext(os.path.split(filename)[1])[0] - config = ConfigParser() - config.read(filename) - if config.has_section('discovery') and \ - config.has_option('discovery', 'hostname_contains'): - hostname_contains = config.get('discovery', - 'hostname_contains') - possible_hosts[machine] = hostname_contains - - return possible_hosts - - -def main(): # noqa: C901 - args = parse_args(bootstrap=True) - - if args.verbose: - logger = None - else: - logger = get_logger(log_filename='conda/logs/bootstrap.log', - name=__name__) - - source_path = os.getcwd() - conda_template_path = f'{source_path}/conda/compass_env' - spack_template_path = f'{source_path}/conda/spack' - - compass_version = get_version() - - local_mache = args.mache_fork is not None and args.mache_branch is not None - - machine = None - if not args.env_only: - if args.machine is None: - machine = discover_machine() - else: - machine = args.machine - - known_machine = machine is not None - - if machine is None and not args.env_only: - if platform.system() == 'Linux': - machine = 'conda-linux' - elif platform.system() == 'Darwin': - machine = 'conda-osx' - - config = get_config(args.config_file, machine) - - env_type = config.get('deploy', 'env_type') - if env_type not in ['dev', 'test_release', 'release']: - raise ValueError(f'Unexpected env_type: {env_type}') - shared = (env_type != 'dev') - conda_base = get_conda_base(args.conda_base, config, shared=shared, - warn=False) - conda_base = os.path.abspath(conda_base) - - source_activation_scripts = \ - f'source {conda_base}/etc/profile.d/conda.sh' - - activate_base = f'{source_activation_scripts} && conda activate' - - compilers, mpis = get_compilers_mpis(config, machine, args.compilers, - args.mpis, source_path) - - if machine is not None: - # write out a log file for use by matrix builds - with open('conda/logs/matrix.log', 'w') as f: - f.write(f'{machine}\n') - for compiler, mpi in zip(compilers, mpis): - f.write(f'{compiler}, {mpi}\n') - - print('Configuring environment(s) for the following compilers and MPI ' - 'libraries:') - for compiler, mpi in zip(compilers, mpis): - print(f' {compiler}, {mpi}') - print('') - - previous_conda_env = None - - permissions_dirs = [] - activ_path = None - - for compiler, mpi in zip(compilers, mpis): - - python, recreate, conda_mpi, activ_suffix, env_suffix, \ - activ_path, conda_env_path, conda_env_name, activate_env, \ - spack_env = get_env_setup(args, config, machine, compiler, mpi, - env_type, source_path, conda_base, - args.env_name, compass_version, logger) - - build_dir = f'conda/build{activ_suffix}' - - safe_rmtree(build_dir) - try: - os.makedirs(build_dir) - except FileExistsError: - pass - - os.chdir(build_dir) - - if args.spack_base is not None: - spack_base = args.spack_base - elif known_machine and compiler is not None: - spack_base = get_spack_base(args.spack_base, config) - else: - spack_base = None - - if spack_base is not None and args.update_spack: - # even if this is not a release, we need to update permissions on - # shared system libraries - permissions_dirs.append(spack_base) - - if previous_conda_env != conda_env_name: - build_conda_env( - env_type, recreate, mpi, conda_mpi, compass_version, - python, source_path, conda_template_path, conda_base, - conda_env_name, conda_env_path, activate_base, args.use_local, - args.local_conda_build, logger, local_mache, - args.update_jigsaw, args.with_alphabetalab) - - if local_mache: - print('Install local mache\n') - commands = \ - f'source {conda_base}/etc/profile.d/conda.sh && ' \ - f'conda activate {conda_env_name} && ' \ - f'cd ../build_mache/mache && ' \ - f'conda install -y --file spec-file.txt && ' \ - f'python -m pip install --no-deps --no-build-isolation .' - check_call(commands, logger=logger) - - previous_conda_env = conda_env_name - - if env_type != 'dev': - permissions_dirs.append(conda_base) - - spack_script = '' - if compiler is not None: - env_vars = get_env_vars(machine, compiler, mpi) - if spack_base is not None: - spack_branch_base, spack_script, env_vars = build_spack_env( - config, args.update_spack, machine, compiler, mpi, - spack_env, spack_base, spack_template_path, env_vars, - args.tmpdir, logger) - spack_script = \ - f'echo Loading Spack environment...\n' \ - f'{spack_script}\n' \ - f'echo Done.\n' \ - f'echo\n' - else: - env_vars = \ - f'{env_vars}' \ - f'export PIO={conda_env_path}\n' \ - f'export OPENMP_INCLUDE=-I"{conda_env_path}/include"\n' - else: - env_vars = '' - - if env_type == 'dev': - if args.env_name is not None: - prefix = 'load_{}'.format(args.env_name) - else: - prefix = 'load_dev_compass_{}'.format(compass_version) - elif env_type == 'test_release': - prefix = 'test_compass_{}'.format(compass_version) - else: - prefix = 'load_compass_{}'.format(compass_version) - - script_filename = write_load_compass( - conda_template_path, activ_path, conda_base, env_type, - activ_suffix, prefix, conda_env_name, spack_script, machine, - env_vars, args.env_only, source_path, args.without_openmp, - compass_version) - - if args.check: - check_env(script_filename, conda_env_name, logger) - - if env_type == 'release' and not (args.with_albany or - args.with_petsc): - # make a symlink to the activation script - link = os.path.join(activ_path, - f'load_latest_compass_{compiler}_{mpi}.sh') - check_call(f'ln -sfn {script_filename} {link}') - - default_compiler = config.get('deploy', 'compiler') - default_mpi = config.get('deploy', - 'mpi_{}'.format(default_compiler)) - if compiler == default_compiler and mpi == default_mpi: - # make a default symlink to the activation script - link = os.path.join(activ_path, 'load_latest_compass.sh') - check_call(f'ln -sfn {script_filename} {link}') - os.chdir(source_path) - - commands = '{} && conda clean -y -p -t'.format(activate_base) - check_call(commands, logger=logger) - - if args.update_spack or env_type != 'dev': - # we need to update permissions on shared stuff - update_permissions(config, env_type, activ_path, permissions_dirs) - - -if __name__ == '__main__': - main() diff --git a/conda/compass_env/load_compass.template b/conda/compass_env/load_compass.template deleted file mode 100644 index 62ff25f355..0000000000 --- a/conda/compass_env/load_compass.template +++ /dev/null @@ -1,37 +0,0 @@ -{% if env_type == 'dev' -%} -export COMPASS_BRANCH="{{ compass_source_path }}" -export COMPASS_VERSION="{{ compass_version }}" - -version_file="${COMPASS_BRANCH}/compass/version.py" -code_version=$(cat $version_file) -if [[ "$code_version" != *"$COMPASS_VERSION"* ]]; then - -echo "This load script is for a different version of compass:" -echo "__version__ = '$COMPASS_VERSION'" -echo "" -echo "Your code is version:" -echo "$code_version" -echo "" -echo "You need to run ./conda/configure_compass_env.py to update your conda " -echo "environment and load script." - -else -# the right compass version -{%- endif %} - -echo Loading conda environment -source {{ conda_base }}/etc/profile.d/conda.sh -conda activate {{ compass_env }} -echo Done. -echo - -{{ update_compass }} - -{{ spack }} - -{{ env_vars }} - -{% if env_type == 'dev' -%} -# the right compass version -fi -{%- endif %} diff --git a/conda/compass_env/spec-file.template b/conda/compass_env/spec-file.template deleted file mode 100644 index 76c2e0cf61..0000000000 --- a/conda/compass_env/spec-file.template +++ /dev/null @@ -1,78 +0,0 @@ -# This file may be used to create an environment using: -# $ conda create --name --file - -# Base -python>=3.10 -cartopy -cartopy_offlinedata -cmocean -esmf=8.9.0={{ mpi_prefix }}_* -ffmpeg -geometric_features=1.6.1 -git -gsw -h5py -ipython -jupyter -lxml -{% if include_mache %} -mache=1.32.0 -{% endif %} -matplotlib-base >=3.9.1 -metis -moab >=5.5.1 -moab=*={{ mpi_prefix }}_tempest_* -mpas_tools=1.3.2 -nco -netcdf4=*=nompi_* -numpy >=2.0,<3.0 -{% if supports_otps %} -otps=2021.10 -{% endif %} -progressbar2 -pyamg >=4.2.2 -pyproj -pyremap>=2.0.0,<3.0.0 -requests -ruamel.yaml -# having pip check problems with this version -scikit-image !=0.20.0 -scipy>=1.8.0 -shapely>=2.0,<3.0 -xarray - -# Development -pip -{% if mpi != "nompi" %} -c-compiler -cmake -cxx-compiler -fortran-compiler -libnetcdf=4.9.2={{ mpi_prefix }}_* -libpnetcdf=1.14.0={{ mpi_prefix }}_* -parallelio=2.6.6={{ mpi_prefix }}_* -m4 -make -{{ mpi }} -{{ openmp }} -netcdf-fortran -{% endif %} - -# Linting and testing -pytest -isort -flake8 -pre-commit - -# CF-compliance -cfchecker -udunits2 - -# Documentation -m2r -mock -sphinx -sphinx_rtd_theme - -# Visualization -ncview diff --git a/conda/configure_compass_env.py b/conda/configure_compass_env.py deleted file mode 100755 index 6d358ba8b6..0000000000 --- a/conda/configure_compass_env.py +++ /dev/null @@ -1,138 +0,0 @@ -#!/usr/bin/env python3 - -import os -import sys -from configparser import ConfigParser - -from shared import ( - check_call, - get_conda_base, - get_logger, - install_miniforge, - parse_args, -) - - -def get_config(config_file): - # we can't load compass so we find the config files - here = os.path.abspath(os.path.dirname(__file__)) - default_config = os.path.join(here, 'default.cfg') - config = ConfigParser() - config.read(default_config) - - if config_file is not None: - config.read(config_file) - - return config - - -def bootstrap(activate_install_env, source_path, local_conda_build): - - print('Creating the compass conda environment\n') - bootstrap_command = f'{source_path}/conda/bootstrap.py' - command = \ - f'{activate_install_env} && ' \ - f'{bootstrap_command} {" ".join(sys.argv[1:])}' - if local_conda_build is not None: - command = f'{command} --local_conda_build {local_conda_build}' - check_call(command) - - -def setup_install_env(env_name, activate_base, use_local, logger, recreate, - conda_base, mache): - env_path = os.path.join(conda_base, 'envs', env_name) - if use_local: - channels = '--use-local' - else: - channels = '' - packages = f'jinja2 {mache} packaging progressbar2' - if recreate or not os.path.exists(env_path): - print('Setting up a conda environment for installing compass\n') - commands = f'{activate_base} && ' \ - f'conda create -y -n {env_name} {channels} {packages}' - else: - print('Updating conda environment for installing compass\n') - commands = \ - f'{activate_base} && ' \ - f'conda install -y -n {env_name} {channels} {packages}' - - check_call(commands, logger=logger) - - -def main(): - args = parse_args(bootstrap=False) - source_path = os.getcwd() - - if args.tmpdir is not None: - try: - os.makedirs(args.tmpdir) - except FileExistsError: - pass - - config = get_config(args.config_file) - - conda_base = get_conda_base(args.conda_base, config, warn=True) - conda_base = os.path.abspath(conda_base) - - env_name = 'compass_bootstrap' - - source_activation_scripts = \ - f'source {conda_base}/etc/profile.d/conda.sh' - - activate_base = f'{source_activation_scripts} && conda activate' - - activate_install_env = \ - f'{source_activation_scripts} && ' \ - f'conda activate {env_name}' - try: - os.makedirs('conda/logs') - except OSError: - pass - - if args.verbose: - logger = None - else: - logger = get_logger(log_filename='conda/logs/prebootstrap.log', - name=__name__) - - # install miniforge if needed - install_miniforge(conda_base, activate_base, logger) - - local_mache = args.mache_fork is not None and args.mache_branch is not None - if local_mache: - mache = '' - else: - mache = '"mache=1.32.0"' - - setup_install_env(env_name, activate_base, args.use_local, logger, - args.recreate, conda_base, mache) - - if local_mache: - print('Clone and install local mache\n') - commands = \ - f'{activate_install_env} && ' \ - f'rm -rf conda/build_mache && ' \ - f'mkdir -p conda/build_mache && ' \ - f'cd conda/build_mache && ' \ - f'git clone -b {args.mache_branch} ' \ - f'git@github.com:{args.mache_fork}.git mache && ' \ - f'cd mache && ' \ - f'conda install -y --file spec-file.txt && ' \ - f'python -m pip install --no-deps --no-build-isolation .' # noqa: E231,E501 - - check_call(commands, logger=logger) - - env_type = config.get('deploy', 'env_type') - if env_type not in ['dev', 'test_release', 'release']: - raise ValueError(f'Unexpected env_type: {env_type}') - - if env_type == 'test_release' and args.use_local: - local_conda_build = os.path.abspath(f'{conda_base}/conda-bld') - else: - local_conda_build = None - - bootstrap(activate_install_env, source_path, local_conda_build) - - -if __name__ == '__main__': - main() diff --git a/conda/default.cfg b/conda/default.cfg deleted file mode 100644 index dbfb9463d7..0000000000 --- a/conda/default.cfg +++ /dev/null @@ -1,40 +0,0 @@ -# Options related to deploying a compass conda environment on supported -# machines -[deploy] - -# The type of environment to deploy: dev, test_release or release -# This should nearly always be left as "dev". Only experienced developers -# should deploy a shared environment -env_type = dev - -# Recreate the environment if it already exists? -recreate = False - -# a suffix on the environment name -suffix = - -# the python version -python = 3.13 - -# the MPI version (nompi, mpich or openmpi) -mpi = nompi - -# the version of various packages to include if using spack -albany = compass-2024-03-13 -# spack variants for Albany and Trilinos -albany_variants = +mpas~py+unit_tests -trilinos_variants = -# cmake newer than 3.23.0 needed for Trilinos -cmake = 3.23.0: -esmf = 8.9.0 -hdf5 = 1.14.6 -lapack = 3.9.1 -metis = 5.1.0 -moab = master -netcdf_c = 4.9.2 -netcdf_fortran = 4.6.2 -petsc = 3.19.1 -pnetcdf = 1.14.0 -scorpio = 1.8.2 -# parallelio = 2.6.3 -parallelio = None diff --git a/conda/packages/build_packages.py b/conda/packages/build_packages.py deleted file mode 100755 index dd4fad1759..0000000000 --- a/conda/packages/build_packages.py +++ /dev/null @@ -1,330 +0,0 @@ -#!/usr/bin/env python3 -import argparse -import os -import platform -import shlex -import subprocess -import tempfile -from pathlib import Path - -import yaml - - -def get_variant_configs(variants_dir, platform_tag): - config_files = sorted(Path(variants_dir).glob("*.yaml")) - filtered = [] - for config in config_files: - name = config.name - if platform_tag and not name.startswith(platform_tag): - continue - filtered.append(str(config)) - return filtered - - -def apply_channel_overrides(variant_config_path, channel_sources, outputs_dir, - overrides=None): - with open(variant_config_path) as handle: - variant_config = yaml.safe_load(handle) or {} - - variant_config["channel_sources"] = [",".join(channel_sources)] - if overrides: - variant_config.update(overrides) - - override_dir = outputs_dir / "variant_overrides" - override_dir.mkdir(parents=True, exist_ok=True) - override_path = ( - override_dir / f"{Path(variant_config_path).stem}_labels.yaml" - ) - with open(override_path, "w") as handle: - yaml.safe_dump(variant_config, handle, sort_keys=False) - return str(override_path) - - -def get_platform_tag(): - system = platform.system().lower() - machine = platform.machine().lower() - if system == "linux": - if machine in {"x86_64", "amd64"}: - return "linux_64" - if machine in {"aarch64", "arm64"}: - return "linux_aarch64" - if system == "darwin": - if machine in {"x86_64", "amd64"}: - return "osx_64" - if machine in {"aarch64", "arm64"}: - return "osx_arm64" - return None - - -def get_variant_values(variant_file): - with open(variant_file) as handle: - data = yaml.safe_load(handle) or {} - python_spec = None - mpi = None - if "python_min" in data and data["python_min"]: - python_spec = f"{data['python_min'][0]}" - elif "python" in data and data["python"]: - python_spec = str(data["python"][0]).split()[0] - if "mpi" in data and data["mpi"]: - mpi = str(data["mpi"][0]) - return python_spec, mpi - - -def normalize_spec(spec, mpi_prefix): - rendered = spec.replace("${{ mpi_prefix }}", mpi_prefix) - tokens = rendered.split() - if len(tokens) <= 2: - return rendered - return " ".join(tokens[:2]) - - -def parse_run_requirements(recipe_path, mpi_prefix, platform_tag): - with open(recipe_path) as handle: - recipe = yaml.safe_load(handle) or {} - requirements = recipe.get("requirements", {}) - run_reqs = requirements.get("run", []) - specs = [] - for entry in run_reqs: - if isinstance(entry, str): - specs.append(normalize_spec(entry, mpi_prefix)) - elif isinstance(entry, dict) and "if" in entry and "then" in entry: - condition = str(entry["if"]).lower() - if condition == "linux" and platform_tag.startswith("linux"): - for then_entry in entry.get("then", []): - specs.append(normalize_spec(then_entry, mpi_prefix)) - return specs - - -def get_conda_sh(): - conda_sh = os.environ.get("CONDA_SH") - if conda_sh: - return Path(conda_sh).expanduser() - return Path.home() / "miniforge3" / "etc" / "profile.d" / "conda.sh" - - -def run_conda_command(args, extra_env=None): - conda_sh = get_conda_sh() - if not conda_sh.exists(): - raise FileNotFoundError( - f"Conda activation script not found: {conda_sh}" - ) - env = os.environ.copy() - if extra_env: - env.update(extra_env) - cmd = " ".join(shlex.quote(arg) for arg in args) - subprocess.run( - [ - "bash", - "-lc", - f"source '{conda_sh}' && conda {cmd}", - ], - check=True, - env=env, - ) - - -def prefetch_with_conda(variant_file, recipe_dir, outputs_dir, platform_tag, - channels, mpi_override=None): - python_spec, mpi = get_variant_values(variant_file) - if python_spec is None: - python_spec = ">=3.10" - if mpi_override is not None: - mpi = mpi_override - elif mpi is None: - mpi = "nompi" - mpi_prefix = "nompi" if mpi == "nompi" else f"mpi_{mpi}" - recipe_path = Path(recipe_dir) / "recipe.yaml" - run_specs = parse_run_requirements(recipe_path, mpi_prefix, platform_tag) - specs = [f"python {python_spec}", "pip", "setuptools"] + run_specs - prefetch_root = outputs_dir / "prefetch" - env_dir = prefetch_root / f"conda_env_{Path(variant_file).stem}" - pkgs_dir = prefetch_root / "conda_pkgs" - args = [ - "create", - "--download-only", - "--yes", - "--override-channels", - "--repodata-fn", - "repodata.json", - "--prefix", - str(env_dir), - ] - for channel in channels: - args.extend(["-c", channel]) - args.extend(specs) - print("Prefetching packages with conda...") - try: - run_conda_command( - args, - extra_env={ - "CONDA_PKGS_DIRS": str(pkgs_dir), - }, - ) - except subprocess.CalledProcessError: - print("Conda prefetch failed; continuing with remote downloads.") - return None - return pkgs_dir - - -def run_build(variant_file, recipe_dir, outputs_dir, config_file, cache_dir): - cmd = [ - "rattler-build", - "build", - "--config-file", - str(config_file), - "--io-concurrency-limit", - "1", - "-m", - variant_file, - "-r", - str(recipe_dir), - "--output-dir", - str(outputs_dir), - ] - env = os.environ.copy() - env.setdefault("REQWEST_DISABLE_HTTP2", "1") - env.setdefault("RATTLER_IO_CONCURRENCY_LIMIT", "1") - env.setdefault("RATTLER_CACHE_DIR", str(cache_dir)) - print("Running:", " ".join(cmd)) - subprocess.run(cmd, check=True, env=env) - - -def main(): - parser = argparse.ArgumentParser( - description="Build compass and OTPS conda packages with rattler-build." - ) - parser.add_argument( - "--otps", - action="store_true", - help="Only build OTPS variants." - ) - parser.add_argument( - "--compass", - action="store_true", - help="Only build COMPASS variants." - ) - parser.add_argument( - "--prefetch", - action="store_true", - help="Prefetch dependencies using conda and reuse its package cache." - ) - parser.add_argument( - "--mpi", - nargs="+", - help="MPI variant(s) to build for (overrides default matrix)." - ) - args = parser.parse_args() - - recipe_root = Path(__file__).parent - config_file = recipe_root / "rattler-build-config.toml" - outputs_dir = recipe_root / "outputs" - outputs_dir.mkdir(parents=True, exist_ok=True) - - build_otps = args.otps or not args.compass - build_compass = args.compass or not args.otps - - platform_tag = get_platform_tag() - if platform_tag is None: - raise ValueError( - "Unsupported platform for automatic variant selection." - ) - - default_cache_dir = Path( - os.environ.get("TMPDIR", tempfile.gettempdir()) - ) / "rattler_cache" - - if build_otps: - otps_dir = recipe_root / "otps" - otps_variants_dir = otps_dir / "variants" - otps_recipe_dir = otps_dir / "recipe" - otps_variants = get_variant_configs( - variants_dir=otps_variants_dir, - platform_tag=platform_tag, - ) - if not otps_variants: - raise ValueError( - "No OTPS variant config files matched the requested filters." - ) - otps_channel_sources = ["https://conda.anaconda.org/conda-forge"] - otps_prefetch_sources = otps_channel_sources - for variant_file in otps_variants: - effective_sources = otps_channel_sources - cache_dir = default_cache_dir - if args.prefetch: - prefetch_cache_dir = prefetch_with_conda( - variant_file=variant_file, - recipe_dir=otps_recipe_dir, - outputs_dir=outputs_dir, - platform_tag=platform_tag, - channels=otps_prefetch_sources, - ) - if prefetch_cache_dir: - cache_dir = Path(prefetch_cache_dir) - override_file = apply_channel_overrides( - variant_config_path=variant_file, - channel_sources=effective_sources, - outputs_dir=outputs_dir, - ) - run_build( - override_file, - otps_recipe_dir, - outputs_dir, - config_file, - cache_dir=cache_dir, - ) - - if build_compass: - compass_dir = recipe_root / "compass" - compass_variants_dir = compass_dir / "variants" - compass_recipe_dir = compass_dir / "recipe" - compass_build_config = compass_recipe_dir / "conda_build_config.yaml" - with open(compass_build_config) as handle: - compass_build_data = yaml.safe_load(handle) or {} - default_mpis = compass_build_data.get("mpi", ["nompi"]) - mpi_variants = args.mpi or default_mpis - compass_variants = get_variant_configs( - variants_dir=compass_variants_dir, - platform_tag=platform_tag, - ) - if not compass_variants: - raise ValueError( - "No COMPASS variant config files matched the requested " - "filters." - ) - channel_sources = [ - "https://conda.anaconda.org/conda-forge", - "e3sm/label/compass", - ] - prefetch_sources = channel_sources - for variant_file in compass_variants: - for mpi_variant in mpi_variants: - effective_sources = channel_sources - cache_dir = default_cache_dir - if args.prefetch: - prefetch_cache_dir = prefetch_with_conda( - variant_file=variant_file, - recipe_dir=compass_recipe_dir, - outputs_dir=outputs_dir, - platform_tag=platform_tag, - channels=prefetch_sources, - mpi_override=mpi_variant, - ) - if prefetch_cache_dir: - cache_dir = Path(prefetch_cache_dir) - override_file = apply_channel_overrides( - variant_config_path=variant_file, - channel_sources=effective_sources, - outputs_dir=outputs_dir, - overrides={"mpi": [str(mpi_variant)]}, - ) - run_build( - override_file, - compass_recipe_dir, - outputs_dir, - config_file, - cache_dir=cache_dir, - ) - - -if __name__ == "__main__": - main() diff --git a/conda/packages/compass/recipe/conda_build_config.yaml b/conda/packages/compass/recipe/conda_build_config.yaml deleted file mode 100644 index 94e30fbaff..0000000000 --- a/conda/packages/compass/recipe/conda_build_config.yaml +++ /dev/null @@ -1,4 +0,0 @@ -mpi: - - nompi - - openmpi - - mpich diff --git a/conda/packages/compass/recipe/recipe.yaml b/conda/packages/compass/recipe/recipe.yaml deleted file mode 100644 index 82bc04c4bb..0000000000 --- a/conda/packages/compass/recipe/recipe.yaml +++ /dev/null @@ -1,115 +0,0 @@ -context: - name: compass - version: "1.9.0a2" - build: 0 - mpi_prefix: ${{ "nompi" if mpi == "nompi" else "mpi_" ~ mpi }} - build_number: ${{ 100 + build if mpi == "nompi" else build }} - -package: - name: ${{ name|lower }} - version: ${{ version }} - -source: - path: ../../../.. - -build: - number: ${{ build_number }} - noarch: python - # add build string so packages can depend on mpi or nompi variants - string: ${{ mpi_prefix }}_h${{ hash }}_${{ build_number }} - script: python -m pip install . --no-deps -vv - python: - entry_points: - - compass = compass.__main__:main - - create_compass_load_script = compass.load:main - -requirements: - host: - - python ${{ python_min }}.* - - pip - - setuptools - run: - - python >=${{ python_min }},<3.14 - - cartopy - - cartopy_offlinedata - - cmocean - - esmf ==8.9.0 ${{ mpi_prefix }}_* - - ffmpeg - - geometric_features ==1.6.1 - - git - - gsw - - h5py - - ipython - - jupyter - - lxml - - mache ==1.32.0 - - matplotlib-base >=3.9.1 - - metis - - moab >=5.5.1 - - moab * ${{ mpi_prefix }}_tempest_* - - mpas_tools ==1.3.2 - - nco - - netcdf4 * nompi_* - - numpy >=2.0,<3.0 - - if: linux - then: - - otps ==2021.10 - - progressbar2 - - pyamg >=4.2.2 - - pyproj - - pyremap >=2.0.0,<3.0.0 - - requests - - ruamel.yaml - - scikit-image !=0.20.0 - - scipy >=1.8.0 - - shapely >=2.0,<3.0 - - xarray - # Keep the package noarch while constraining platform - - if: linux - then: __linux - - if: osx - then: __osx - - if: win - then: __win - -tests: - - python: - imports: - - compass - pip_check: true - python_version: ${{ python_min }}.* - - script: - - export CONDA_PREFIX="${PREFIX}" - - export CONDA_EXE="$(command -v conda)" - - build_jigsaw --clone --subdir jigsaw-python - - compass list - - compass list --machines - - compass list --suites - - compass list --help - - compass setup --help - - compass suite --help - - compass clean --help - - create_compass_load_script --help - requirements: - run: - - conda - - git - - pip - -about: - homepage: https://github.com/MPAS-Dev/compass - license: BSD-3-Clause - license_file: LICENSE - summary: Test cases for the Model for Prediction Across Scales (MPAS) - description: | - Configuration Of Model for Prediction Across Scales Setups (COMPASS) is an - automated system to set up test cases that match the MPAS-Model repository. - All namelists and streams files begin with the default generated from the - Registry.xml file, and only the changes relevant to the particular test - case are altered in those files. - documentation: https://mpas-dev.github.io/compass/latest/ - repository: https://github.com/MPAS-Dev/compass - -extra: - recipe-maintainers: - - xylar diff --git a/conda/packages/compass/variants/linux_64_.yaml b/conda/packages/compass/variants/linux_64_.yaml deleted file mode 100644 index b76221d02c..0000000000 --- a/conda/packages/compass/variants/linux_64_.yaml +++ /dev/null @@ -1,16 +0,0 @@ -channel_sources: -- conda-forge -channel_targets: -- conda-forge main -docker_image: -- quay.io/condaforge/linux-anvil-x86_64:alma9 -mpi: -- mpich -mpich: -- '4' -openmpi: -- '4' -python_min: -- '3.10' -target_platform: -- linux-64 diff --git a/conda/packages/compass/variants/osx_64_.yaml b/conda/packages/compass/variants/osx_64_.yaml deleted file mode 100644 index 56348d3ef3..0000000000 --- a/conda/packages/compass/variants/osx_64_.yaml +++ /dev/null @@ -1,20 +0,0 @@ -MACOSX_DEPLOYMENT_TARGET: -- '10.13' -MACOSX_SDK_VERSION: -- '10.13' -channel_sources: -- conda-forge -channel_targets: -- conda-forge main -macos_machine: -- x86_64-apple-darwin13.4.0 -mpi: -- mpich -mpich: -- '4' -openmpi: -- '4' -python_min: -- '3.10' -target_platform: -- osx-64 diff --git a/conda/packages/otps/recipe/build.sh b/conda/packages/otps/recipe/build.sh deleted file mode 100644 index 107befe3b2..0000000000 --- a/conda/packages/otps/recipe/build.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash -set -e - -gdown 1FBlS_Xmf6_dnCg1T0t5GSTRTwMjLuA8N -tar xvf OTPS.tar.Z - -cd OTPS - -for exec in extract_HC extract_local_model predict_tide -do - ${FC} ${FCFLAGS} -o ${exec} -fconvert=swap -frecord-marker=4 ${exec}.f90 subs.f90 - cp ${exec} ${PREFIX}/bin/ -done diff --git a/conda/packages/otps/recipe/recipe.yaml b/conda/packages/otps/recipe/recipe.yaml deleted file mode 100644 index 46c85c1b09..0000000000 --- a/conda/packages/otps/recipe/recipe.yaml +++ /dev/null @@ -1,37 +0,0 @@ -context: - version: "2021.10" - -package: - name: otps - version: ${{ version }} - -build: - number: 1 - skip: - - win - -requirements: - build: - - make - - ${{ compiler('fortran') }} - - ${{ stdlib('c') }} - host: - - gdown - - tar - -tests: - - script: - - test -f ${PREFIX}/bin/extract_HC - - test -f ${PREFIX}/bin/extract_local_model - - test -f ${PREFIX}/bin/predict_tide - -about: - homepage: https://www.tpxo.net/otps - license: LicenseRef-custom - license_file: OTPS/COPYRIGHT - summary: OSU TIDAL PREDICTION Software (OTPS) - -extra: - recipe-maintainers: - - xylar - - sbrus89 diff --git a/conda/packages/otps/variants/linux_64_.yaml b/conda/packages/otps/variants/linux_64_.yaml deleted file mode 100644 index 43cb2912de..0000000000 --- a/conda/packages/otps/variants/linux_64_.yaml +++ /dev/null @@ -1,16 +0,0 @@ -c_stdlib: -- sysroot -c_stdlib_version: -- '2.17' -channel_sources: -- conda-forge -channel_targets: -- conda-forge main -docker_image: -- quay.io/condaforge/linux-anvil-x86_64:alma9 -fortran_compiler: -- gfortran -fortran_compiler_version: -- '14' -target_platform: -- linux-64 diff --git a/conda/packages/otps/variants/osx_64_.yaml b/conda/packages/otps/variants/osx_64_.yaml deleted file mode 100644 index a7c9926d6f..0000000000 --- a/conda/packages/otps/variants/osx_64_.yaml +++ /dev/null @@ -1,20 +0,0 @@ -MACOSX_DEPLOYMENT_TARGET: -- '10.13' -MACOSX_SDK_VERSION: -- '10.13' -c_stdlib: -- macosx_deployment_target -c_stdlib_version: -- '10.13' -channel_sources: -- conda-forge -channel_targets: -- conda-forge main -fortran_compiler: -- gfortran -fortran_compiler_version: -- '14' -macos_machine: -- x86_64-apple-darwin13.4.0 -target_platform: -- osx-64 diff --git a/conda/packages/rattler-build-config.toml b/conda/packages/rattler-build-config.toml deleted file mode 100644 index 9a4db8b29e..0000000000 --- a/conda/packages/rattler-build-config.toml +++ /dev/null @@ -1,5 +0,0 @@ -# Rattler-build configuration used by build_packages.py -# https://rattler-build.prefix.dev/latest/config/ - -[mirrors] -"https://conda.anaconda.org/conda-forge" = ["https://prefix.dev/conda-forge"] diff --git a/conda/petsc_supported.txt b/conda/petsc_supported.txt deleted file mode 100644 index 8aff04a7d0..0000000000 --- a/conda/petsc_supported.txt +++ /dev/null @@ -1,11 +0,0 @@ -# a list of supported machine, compiler and mpi combinations for Netlib LAPACK -# and PETSc - -anvil, intel, impi -anvil, gnu, openmpi -chicoma-cpu, gnu, mpich -chrysalis, intel, openmpi -chrysalis, gnu, openmpi -compy, intel, impi -morpheus, gnu, openmpi -pm-cpu, gnu, mpich diff --git a/conda/shared.py b/conda/shared.py deleted file mode 100644 index 27de88ce82..0000000000 --- a/conda/shared.py +++ /dev/null @@ -1,281 +0,0 @@ -import argparse -import logging -import os -import platform -import shutil -import subprocess -import sys -from urllib.request import Request, urlopen - - -def parse_args(bootstrap): - parser = argparse.ArgumentParser( - description='Deploy a compass conda environment') - parser.add_argument("-m", "--machine", dest="machine", - help="The name of the machine for loading machine-" - "related config options") - parser.add_argument("--conda", dest="conda_base", - help="Path to the conda base") - parser.add_argument("--spack", dest="spack_base", - help="Path to the spack base") - parser.add_argument("--env_name", dest="env_name", - help="The conda environment name and activation script" - " prefix") - parser.add_argument("-p", "--python", dest="python", type=str, - help="The python version to deploy") - parser.add_argument("-c", "--compiler", dest="compilers", type=str, - nargs="*", help="The name of the compiler(s)") - parser.add_argument("-i", "--mpi", dest="mpis", type=str, nargs="*", - help="The MPI library (or libraries) to deploy, see " - "the docs for details") - parser.add_argument("--env_only", dest="env_only", action='store_true', - help="Create only the compass environment, don't " - "install compilers or build SCORPIO") - parser.add_argument("--recreate", dest="recreate", action='store_true', - help="Recreate the environment if it exists") - parser.add_argument("--update_jigsaw", dest="update_jigsaw", - action='store_true', - help="Reinstall JIGSAW even if not recreating conda " - "environment.") - parser.add_argument("-f", "--config_file", dest="config_file", - help="Config file to override deployment config " - "options") - parser.add_argument("--check", dest="check", action='store_true', - help="Check the resulting environment for expected " - "packages") - parser.add_argument("--use_local", dest="use_local", action='store_true', - help="Use locally built conda packages (for testing).") - parser.add_argument("--mache_fork", dest="mache_fork", - help="Point to a mache fork (and branch) for testing") - parser.add_argument("--mache_branch", dest="mache_branch", - help="Point to a mache branch (and fork) for testing") - parser.add_argument("--update_spack", dest="update_spack", - action='store_true', - help="If the shared spack environment should be " - "created or recreated.") - parser.add_argument("--tmpdir", dest="tmpdir", - help="A temporary directory for building spack " - "packages") - parser.add_argument("--with_albany", dest="with_albany", - action='store_true', - help="Whether to include albany in the spack " - "environment") - parser.add_argument("--with_petsc", dest="with_petsc", - action='store_true', - help="Whether to include PETSc and Netlib-LAPACK in " - "the spack environment") - parser.add_argument("--without_openmp", dest="without_openmp", - action='store_true', - help="If this flag is included, OPENMP=false will " - "be added to the load script. By default, MPAS " - "builds will be with OpenMP (OPENMP=true).") - parser.add_argument("--with_alphabetalab", dest="with_alphabetalab", - action='store_true', - help="Whether to install alphaBetaLab from its " - "submodule.") - parser.add_argument("--verbose", dest="verbose", - action='store_true', - help="Print all output to the terminal, rather than " - "log files (usually for debugging).") - if bootstrap: - parser.add_argument("--local_conda_build", dest="local_conda_build", - type=str, - help="A path for conda packages (for testing).") - - args = parser.parse_args(sys.argv[1:]) - - if (args.mache_fork is None) != (args.mache_branch is None): - raise ValueError('You must supply both or neither of ' - '--mache_fork and --mache_branch') - - return args - - -def get_conda_base(conda_base, config, shared=False, warn=False): - if shared: - conda_base = config.get('paths', 'compass_envs') - elif conda_base is None: - try: - conda_base = subprocess.check_output( - ['conda', 'info', '--base'], text=True - ).strip() - if warn: - print(f'\nWarning: --conda path not supplied. Using conda ' - f'installed at:\n' - f' {conda_base}\n') - except subprocess.CalledProcessError as e: - raise ValueError( - 'No conda base provided with --conda and ' - 'none could be inferred.' - ) from e - # handle "~" in the path - conda_base = os.path.abspath(os.path.expanduser(conda_base)) - return conda_base - - -def get_spack_base(spack_base, config): - if spack_base is None: - if config.has_option('deploy', 'spack'): - spack_base = config.get('deploy', 'spack') - else: - raise ValueError('No spack base provided with --spack and none is ' - 'provided in a config file.') - # handle "~" in the path - spack_base = os.path.abspath(os.path.expanduser(spack_base)) - return spack_base - - -def check_call(commands, env=None, logger=None): - print_command = '\n '.join(commands.split(' && ')) - if logger is None: - print(f'\n Running:\n {print_command}\n') - else: - logger.info(f'\nrunning:\n {print_command}\n') - - if logger is None: - process = subprocess.Popen(commands, env=env, executable='/bin/bash', - shell=True) - process.wait() - else: - process = subprocess.Popen(commands, stdout=subprocess.PIPE, - stderr=subprocess.PIPE, env=env, - executable='/bin/bash', shell=True) - stdout, stderr = process.communicate() - - if stdout: - stdout = stdout.decode('utf-8') - for line in stdout.split('\n'): - logger.info(line) - if stderr: - stderr = stderr.decode('utf-8') - for line in stderr.split('\n'): - logger.error(line) - - if process.returncode != 0: - raise subprocess.CalledProcessError(process.returncode, commands) - - -def install_miniforge(conda_base, activate_base, logger): - if not os.path.exists(conda_base): - print('Installing Miniforge3') - if platform.system() == 'Linux': - system = 'Linux' - elif platform.system() == 'Darwin': - system = 'MacOSX' - else: - system = 'Linux' - miniforge = f'Miniforge3-{system}-x86_64.sh' - url = f'https://github.com/conda-forge/miniforge/releases/latest/download/{miniforge}' # noqa: E501 - print(url) - req = Request(url, headers={'User-Agent': 'Mozilla/5.0'}) - with urlopen(req) as f: - html = f.read() - with open(miniforge, 'wb') as outfile: - outfile.write(html) - - command = f'/bin/bash {miniforge} -b -p {conda_base}' - check_call(command, logger=logger) - os.remove(miniforge) - - backup_bashrc() - - print('Doing initial setup\n') - - commands = f'{activate_base} && ' \ - f'conda config --add channels conda-forge && ' \ - f'conda config --set channel_priority strict' - - check_call(commands, logger=logger) - - commands = f'{activate_base} && ' \ - f'conda update -y --all && ' \ - f'conda init' - - check_call(commands, logger=logger) - - restore_bashrc() - - -def backup_bashrc(): - home_dir = os.path.expanduser('~') - files = ['.bashrc', '.bash_profile'] - for filename in files: - src = os.path.join(home_dir, filename) - dst = os.path.join(home_dir, f'{filename}.conda_bak') - if os.path.exists(src): - shutil.copyfile(src, dst) - - -def restore_bashrc(): - home_dir = os.path.expanduser('~') - files = ['.bashrc', '.bash_profile'] - for filename in files: - src = os.path.join(home_dir, f'{filename}.conda_bak') - dst = os.path.join(home_dir, filename) - if os.path.exists(src): - shutil.move(src, dst) - - -def log_message(logger, message): - if logger is None: - print(message) - else: - logger.info(message) - - -def get_logger(name, log_filename): - print(f'Logging to: {log_filename}\n') - try: - os.remove(log_filename) - except OSError: - pass - logger = logging.getLogger(name) - handler = logging.FileHandler(log_filename) - formatter = CompassFormatter() - handler.setFormatter(formatter) - logger.addHandler(handler) - logger.setLevel(logging.INFO) - logger.propagate = False - return logger - - -class CompassFormatter(logging.Formatter): - """ - A custom formatter for logging - Modified from: - https://stackoverflow.com/a/8349076/7728169 - https://stackoverflow.com/a/14859558/7728169 - """ - - # printing error messages without a prefix because they are sometimes - # errors and sometimes only warnings sent to stderr - dbg_fmt = "DEBUG: %(module)s: %(lineno)d: %(msg)s" - info_fmt = "%(msg)s" - err_fmt = info_fmt - - def __init__(self, fmt=info_fmt): - logging.Formatter.__init__(self, fmt) - - def format(self, record): - - # Save the original format configured by the user - # when the logger formatter was instantiated - format_orig = self._fmt - - # Replace the original format with one customized by logging level - if record.levelno == logging.DEBUG: - self._fmt = CompassFormatter.dbg_fmt - - elif record.levelno == logging.INFO: - self._fmt = CompassFormatter.info_fmt - - elif record.levelno == logging.ERROR: - self._fmt = CompassFormatter.err_fmt - - # Call the original formatter class to do the grunt work - result = logging.Formatter.format(self, record) - - # Restore the original format configured by the user - self._fmt = format_orig - - return result diff --git a/conda/spack/eligos_gnu_openmpi.yaml b/conda/spack/eligos_gnu_openmpi.yaml deleted file mode 100644 index 355346e0ba..0000000000 --- a/conda/spack/eligos_gnu_openmpi.yaml +++ /dev/null @@ -1,38 +0,0 @@ -spack: - specs: - - gcc - - openmpi -{{ specs }} - concretizer: - unify: true - packages: - all: - compiler: [gcc@9.4.0] - providers: - mpi: [openmpi] - curl: - externals: - - spec: curl@7.68.0 - prefix: /usr - buildable: false - gcc: - externals: - - spec: gcc@9.4.0 - prefix: /usr - buildable: false - config: - install_missing_compilers: false - compilers: - - compiler: - spec: gcc@9.4.0 - paths: - cc: /usr/bin/gcc - cxx: /usr/bin/g++ - f77: /usr/bin/gfortran - fc: /usr/bin/gfortran - flags: {} - operating_system: ubuntu20.04 - target: x86_64 - modules: [] - environment: {} - extra_rpaths: [] diff --git a/conda/spack/morpheus_gnu_openmpi.yaml b/conda/spack/morpheus_gnu_openmpi.yaml deleted file mode 100644 index 6e5a4aa8d7..0000000000 --- a/conda/spack/morpheus_gnu_openmpi.yaml +++ /dev/null @@ -1,38 +0,0 @@ -spack: - specs: - - gcc - - openmpi -{{ specs }} - concretizer: - unify: true - packages: - all: - compiler: [gcc@11.3.0] - providers: - mpi: [openmpi] - curl: - externals: - - spec: curl@7.81.0 - prefix: /usr - buildable: false - gcc: - externals: - - spec: gcc@11.3.0 - prefix: /usr - buildable: false - config: - install_missing_compilers: false - compilers: - - compiler: - spec: gcc@11.3.0 - paths: - cc: /usr/bin/gcc - cxx: /usr/bin/g++ - f77: /usr/bin/gfortran - fc: /usr/bin/gfortran - flags: {} - operating_system: ubuntu22.04 - target: x86_64 - modules: [] - environment: {} - extra_rpaths: [] diff --git a/deploy.py b/deploy.py new file mode 100755 index 0000000000..b16c7503b7 --- /dev/null +++ b/deploy.py @@ -0,0 +1,494 @@ +#!/usr/bin/env python3 +""" +Target software deployment entrypoint. + +- Reads pinned mache version from deploy/pins.cfg +- Reads CLI spec from deploy/cli_spec.json and builds argparse CLI +- Downloads mache/deploy/bootstrap.py for either: + * a given mache fork/branch, or + * the pinned mache version +- Calls bootstrap.py with routed args (bootstrap|both) and stops +""" + +import argparse +import configparser +import json +import os +import shlex +import shutil +import stat +import subprocess +import sys +import time +from urllib.error import HTTPError, URLError +from urllib.request import Request, urlopen + +PINS_CFG = os.path.join('deploy', 'pins.cfg') +CLI_SPEC_JSON = os.path.join('deploy', 'cli_spec.json') +DEPLOY_TMP_DIR = 'deploy_tmp' +BOOTSTRAP_PATH = os.path.join(DEPLOY_TMP_DIR, 'bootstrap.py') + +# Default upstream repo for release/tag downloads +DEFAULT_MACHE_REPO = 'E3SM-Project/mache' + +# Where bootstrap.py lives inside the mache repo +BOOTSTRAP_RELPATH = 'mache/deploy/bootstrap.py' + + +def main(): + _check_location() + + pinned_mache_version, pinned_python_version = _read_pins(PINS_CFG) + cli_spec = _read_cli_spec(CLI_SPEC_JSON) + + parser = _build_parser_from_cli_spec(cli_spec) + args = parser.parse_args(sys.argv[1:]) + + if args.python: + python_version = args.python + else: + python_version = pinned_python_version + + _validate_fork_branch_pair(args) + + using_fork = getattr(args, 'mache_fork', None) is not None + requested_mache_version = str( + getattr(args, 'mache_version', '') or '' + ).strip() + + if not using_fork: + _validate_cli_spec_matches_pins(cli_spec, pinned_mache_version) + + bootstrap_mache_version = pinned_mache_version + if not using_fork and requested_mache_version: + bootstrap_mache_version = requested_mache_version + + # remove tmp dir + if os.path.exists(DEPLOY_TMP_DIR): + shutil.rmtree(DEPLOY_TMP_DIR) + + os.makedirs(DEPLOY_TMP_DIR) + + bootstrap_url = _bootstrap_url( + mache_version=bootstrap_mache_version, + mache_fork=getattr(args, 'mache_fork', None), + mache_branch=getattr(args, 'mache_branch', None), + ) + + _download_file(bootstrap_url, BOOTSTRAP_PATH) + + # Make sure it's executable (nice-to-have). We'll still run with + # sys.executable. + _make_executable(BOOTSTRAP_PATH) + + bootstrap_argv = _build_routed_argv(cli_spec, args, route_key='bootstrap') + + software = str(cli_spec.get('meta', {}).get('software', '')).strip() + if not software: + raise SystemExit( + 'ERROR: deploy/cli_spec.json meta.software must be set to the ' + 'target software name.' + ) + # Always include target software name (not user-facing). + bootstrap_argv = [ + '--software', + software, + '--python', + python_version, + ] + bootstrap_argv + + # Only pass a mache version when using a tagged release. If a fork/branch + # is requested, bootstrap must take dependencies from the branch's + # pixi.toml (not from a pinned release). + if not using_fork: + if '--mache-version' not in bootstrap_argv: + bootstrap_argv += ['--mache-version', pinned_mache_version] + + cmd = [sys.executable, BOOTSTRAP_PATH] + bootstrap_argv + try: + subprocess.check_call(cmd) + except subprocess.CalledProcessError as e: + raise SystemExit( + f'\nERROR: Bootstrap step failed (exit code {e.returncode}). ' + f'See the error output above.' + ) from None + + if args.bootstrap_only: + pixi_exe = _get_pixi_executable(getattr(args, 'pixi', None)) + bootstrap_dir = os.path.join(DEPLOY_TMP_DIR, 'bootstrap_pixi') + update_cmd = f'mache deploy update --software {software}' + if requested_mache_version: + update_cmd = ( + f'{update_cmd} --mache-version ' + f'{shlex.quote(requested_mache_version)}' + ) + print( + '\nBootstrap environment is ready. To use it interactively:\n' + f' pixi shell -m {bootstrap_dir}/pixi.toml\n\n' + 'Then, you can run:\n' + f' {update_cmd}\n' + 'After update, edit deploy/pins.cfg to set [pixi] mache to the ' + 'new version.\n' + f' exit\n' + ) + + # Now that the bootstrap env exists and has mache installed, run + # deployment. Forward args routed to "mache". + mache_run_argv = _build_routed_argv(cli_spec, args, route_key='run') + + if not args.bootstrap_only: + pixi_exe = _get_pixi_executable(getattr(args, 'pixi', None)) + if '--pixi' not in mache_run_argv: + mache_run_argv = ['--pixi', pixi_exe] + mache_run_argv + _run_mache_deploy_run( + pixi_exe=pixi_exe, + repo_root='.', + mache_run_argv=mache_run_argv, + ) + + +def _check_location(): + """Fail fast if not run from repo root.""" + expected = [ + 'deploy.py', + PINS_CFG, + CLI_SPEC_JSON, + ] + missing = [p for p in expected if not os.path.exists(p)] + if missing: + missing_str = '\n - ' + '\n - '.join(missing) + raise SystemExit( + f'ERROR: deploy.py must be run from the root of the target ' + f'software repository.\n' + f'Current location: {os.getcwd()}\n' + f'Missing expected files:{missing_str}' + ) + + +def _read_pins(pins_path): + if not os.path.exists(pins_path): + raise SystemExit(f'ERROR: Required pins file not found: {pins_path}') + + cfg = configparser.ConfigParser(interpolation=None) + try: + with open(pins_path, 'r', encoding='utf-8') as f: + cfg.read_file(f) + except OSError as e: + raise SystemExit(f'ERROR: Failed to read {pins_path}: {e!r}') from e + + section = None + if cfg.has_section('pixi') and cfg.has_option('pixi', 'mache'): + section = 'pixi' + + if section is None: + raise SystemExit(f'ERROR: {pins_path} must contain [pixi] mache') + + mache_version = cfg.get(section, 'mache').strip() + if not mache_version: + raise SystemExit( + f'ERROR: {pins_path} option [{section}] mache is empty' + ) + + python_version = cfg.get(section, 'python').strip() + if not python_version: + raise SystemExit( + f'ERROR: {pins_path} option [{section}] python is empty' + ) + + return mache_version, python_version + + +def _read_cli_spec(spec_path): + if not os.path.exists(spec_path): + raise SystemExit(f'ERROR: Required CLI spec not found: {spec_path}') + + try: + with open(spec_path, 'r', encoding='utf-8') as f: + spec = json.load(f) + except (OSError, json.JSONDecodeError) as e: + raise SystemExit(f'ERROR: Failed to parse {spec_path}: {e!r}') from e + + if 'meta' not in spec or 'arguments' not in spec: + raise SystemExit( + f"ERROR: {spec_path} must contain top-level keys 'meta' and " + f"'arguments'" + ) + + if 'mache_version' not in spec['meta']: + raise SystemExit( + f"ERROR: {spec_path} meta must include 'mache_version'" + ) + + if not isinstance(spec['arguments'], list): + raise SystemExit(f"ERROR: {spec_path} 'arguments' must be a list") + + return spec + + +def _build_parser_from_cli_spec(cli_spec): + description = cli_spec.get('meta', {}).get( + 'description', 'Deploy E3SM software environment' + ) + parser = argparse.ArgumentParser(description=description) + + for entry, flags in _iter_routed_cli_spec_entries( + cli_spec, route_key='deploy' + ): + # Build kwargs for argparse. Only allow a small, safe subset. + kwargs = {} + for key in ( + 'dest', + 'help', + 'action', + 'default', + 'required', + 'choices', + 'nargs', + ): + if key in entry: + kwargs[key] = entry[key] + + # NOTE: intentionally not supporting arbitrary 'type' here to keep it + # simple/stdlib-only. If you need types later, you can support a + # limited string->callable mapping. + + try: + parser.add_argument(*flags, **kwargs) + except TypeError as e: + raise SystemExit( + f'ERROR: Bad argparse spec for flags {flags}: {e}' + ) from e + + return parser + + +def _iter_routed_cli_spec_entries(cli_spec, route_key): + """Yield (entry, flags) for entries whose route contains route_key. + + This function centralizes CLI-spec validation shared between parser + construction and argv forwarding. + """ + for entry in cli_spec['arguments']: + flags = entry.get('flags') + route = entry.get('route') + + if not isinstance(route, list): + raise SystemExit( + f'ERROR: cli_spec.json argument {entry.get("flags")} has ' + f"invalid 'route'; must be a list" + ) + + if route_key not in route: + continue + + if not flags or not isinstance(flags, list): + raise SystemExit("ERROR: cli_spec.json entry missing 'flags' list") + + yield entry, flags + + +def _validate_fork_branch_pair(args): + fork = getattr(args, 'mache_fork', None) + branch = getattr(args, 'mache_branch', None) + if (fork is None) != (branch is None): + raise SystemExit( + 'ERROR: You must supply both --mache-fork and --mache-branch, or ' + 'neither.' + ) + + +def _validate_cli_spec_matches_pins(cli_spec, pinned_mache_version): + meta_version = str(cli_spec['meta'].get('mache_version', '')).strip() + if not meta_version: + raise SystemExit('ERROR: cli_spec.json meta.mache_version is empty') + + if meta_version != pinned_mache_version: + raise SystemExit( + f'ERROR: Mache version mismatch.\n' + f' deploy/pins.cfg pins mache = {pinned_mache_version}\n' + f' deploy/cli_spec.json meta.mache_version = {meta_version}\n\n' + f'Fix: copy deploy/cli_spec.json from the matching mache version ' + f'into this repo (or update both together).' + ) + + +def _bootstrap_url( + mache_version, + mache_fork=None, + mache_branch=None, +): + override_url = str(os.environ.get('MACHE_BOOTSTRAP_URL', '')).strip() + if override_url: + return override_url + + if mache_fork is not None and mache_branch is not None: + # Raw file from a fork/branch + return f'https://raw.githubusercontent.com/{mache_fork}/{mache_branch}/{BOOTSTRAP_RELPATH}' # noqa: E501 + + # Raw file from a version tag. Convention: tags are "X.Y.Z". + return f'https://raw.githubusercontent.com/{DEFAULT_MACHE_REPO}/{mache_version}/{BOOTSTRAP_RELPATH}' # noqa: E501 + + +def _download_file(url, dest_path): + # Avoid stale/cached responses from proxies/CDNs (common on HPC networks). + # GitHub raw content supports query strings; adding a cache-buster forces a + # fresh fetch even if an intermediate cache is misbehaving. + effective_url = url + if 'raw.githubusercontent.com' in url: + sep = '&' if '?' in url else '?' + effective_url = f'{url}{sep}_cb={int(time.time())}' + + req = Request( + effective_url, + headers={ + 'User-Agent': 'Mozilla/5.0', + 'Cache-Control': 'no-cache, no-store, max-age=0', + 'Pragma': 'no-cache', + }, + ) + try: + with urlopen(req, timeout=60) as resp: + data = resp.read() + except HTTPError as e: + raise SystemExit( + f'ERROR: Failed to download bootstrap.py (HTTP {e.code}) from ' + f'{effective_url}' + ) from e + except URLError as e: + raise SystemExit( + f'ERROR: Failed to download bootstrap.py from {effective_url}: ' + f'{e.reason}' + ) from e + except Exception as e: + raise SystemExit( + f'ERROR: Unexpected error downloading bootstrap.py from ' + f'{effective_url}: ' + f'{e!r}' + ) from e + + # Basic sanity check: should look like a python script. + first_line = data.splitlines()[0].strip() if data else b'' + if b'python' not in first_line and b'#!/' not in first_line: + raise SystemExit( + f'ERROR: Downloaded bootstrap.py does not look like a python ' + f'script.\n' + f'URL: {effective_url}\n' + f'This may indicate a proxy/redirect issue.' + ) + + try: + with open(dest_path, 'wb') as f: + f.write(data) + except OSError as e: + raise SystemExit(f'ERROR: Failed to write {dest_path}: {e!r}') from e + + +def _make_executable(path): + try: + st = os.stat(path) + os.chmod(path, st.st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH) + except OSError: + # Not fatal; we run via sys.executable anyway. + pass + + +def _get_pixi_executable(pixi): + if pixi: + pixi = os.path.abspath(os.path.expanduser(pixi)) + if not os.path.exists(pixi): + raise SystemExit(f'ERROR: pixi executable not found: {pixi}') + return pixi + + which = shutil.which('pixi') + if which is not None: + return which + + default_pixi = os.path.join( + os.path.expanduser('~'), '.pixi', 'bin', 'pixi' + ) + if os.path.isfile(default_pixi) and os.access(default_pixi, os.X_OK): + return default_pixi + + raise SystemExit( + 'ERROR: pixi executable not found on PATH or default install ' + 'location (~/.pixi/bin). Install pixi or pass --pixi.' + ) + + +def _build_routed_argv(cli_spec, args, route_key): + """Build forwarded argv from args for entries routed to route_key.""" + argv = [] + for entry, flags in _iter_routed_cli_spec_entries( + cli_spec, route_key=route_key + ): + dest = entry.get('dest') + if not dest: + raise SystemExit( + f"ERROR: cli_spec.json argument {flags} missing 'dest'" + ) + + value = getattr(args, dest, None) + action = entry.get('action') + + # Use the first flag as the canonical one when forwarding. + flag0 = flags[0] + + if action == 'store_true': + if value: + argv.append(flag0) + else: + if value is None: + continue + + # If the argparse entry used `nargs` (or otherwise produced a + # list), expand into repeated tokens: `--flag a b c`. + if isinstance(value, (list, tuple)): + if len(value) == 0: + continue + argv.append(flag0) + argv.extend(str(v) for v in value) + else: + argv.extend([flag0, str(value)]) + + return argv + + +def _run_mache_deploy_run(pixi_exe, repo_root, mache_run_argv): + """ + Run `mache deploy run ...` inside the bootstrap pixi environment. + """ + repo_root = os.path.abspath(repo_root) + + bootstrap_dir = os.path.abspath( + os.path.join(DEPLOY_TMP_DIR, 'bootstrap_pixi') + ) + pixi_toml = os.path.join(bootstrap_dir, 'pixi.toml') + if not os.path.exists(pixi_toml): + raise SystemExit( + f'ERROR: bootstrap pixi project not found. Expected: {pixi_toml}' + ) + + # Build a bash command that runs mache inside pixi, then cd's to repo. + mache_cmd = 'mache deploy run' + if mache_run_argv: + mache_cmd = f'{mache_cmd} ' + ' '.join( + shlex.quote(a) for a in mache_run_argv + ) + + cmd = ( + f'env -u PIXI_PROJECT_MANIFEST -u PIXI_PROJECT_ROOT ' + f'-u PIXI_ENVIRONMENT_NAME -u PIXI_IN_SHELL ' + f'{shlex.quote(pixi_exe)} run -m {shlex.quote(pixi_toml)} bash -lc ' + f'{shlex.quote("cd " + repo_root + " && " + mache_cmd)}' + ) + try: + subprocess.check_call(['/bin/bash', '-lc', cmd]) + except subprocess.CalledProcessError as e: + raise SystemExit( + f'\nERROR: Deployment step failed (exit code {e.returncode}). ' + f'See the error output above.' + ) from None + + +if __name__ == '__main__': + main() diff --git a/conda/albany_supported.txt b/deploy/albany_supported.txt similarity index 87% rename from conda/albany_supported.txt rename to deploy/albany_supported.txt index d969e7a20a..ac79e86a14 100644 --- a/conda/albany_supported.txt +++ b/deploy/albany_supported.txt @@ -4,4 +4,3 @@ chicoma-cpu, gnu, mpich chrysalis, gnu, openmpi pm-cpu, gnu, mpich pm-gpu, gnugpu, mpich -morpheus, gnu, openmpi diff --git a/deploy/cli_spec.json b/deploy/cli_spec.json new file mode 100644 index 0000000000..5ad521d7a7 --- /dev/null +++ b/deploy/cli_spec.json @@ -0,0 +1,106 @@ +{ + "meta": { + "software": "compass", + "mache_version": "3.0.4", + "description": "Deploy compass environment" + }, + "arguments": [ + { + "flags": ["--machine"], + "dest": "machine", + "help": "Name of the machine to deploy for (must be known to mache). If not provided, mache will attempt to detect the machine from the host.", + "route": ["deploy", "run"] + }, + { + "flags": ["--pixi"], + "dest": "pixi", + "help": "Path to the pixi executable. If not provided, pixi is found on PATH.", + "route": ["deploy", "bootstrap", "run"] + }, + { + "flags": ["--prefix"], + "dest": "prefix", + "help": "Install the environment into this prefix (directory). Overrides deploy/config.yaml.j2.", + "route": ["deploy", "bootstrap", "run"] + }, + { + "flags": ["--compiler"], + "dest": "compiler", + "nargs": "+", + "help": "Name of the compiler toolchain (primarily for Spack). If not provided, defaults may come from merged machine config [deploy] compiler.", + "route": ["deploy", "run"] + }, + { + "flags": ["--mpi"], + "dest": "mpi", + "nargs": "+", + "help": "Name of the MPI library (primarily for Spack). If not provided, defaults may come from merged machine config [deploy] mpi_ (or mpi).", + "route": ["deploy", "run"] + }, + { + "flags": ["--deploy-spack"], + "dest": "deploy_spack", + "action": "store_true", + "help": "Deploy all supported Spack environments (overrides spack.deploy in deploy/config.yaml.j2).", + "route": ["deploy", "run"] + }, + { + "flags": ["--no-spack"], + "dest": "no_spack", + "action": "store_true", + "help": "Disable all Spack use for this run, including reuse of existing Spack environments.", + "route": ["deploy", "run"] + }, + { + "flags": ["--spack-path"], + "dest": "spack_path", + "help": "Path to the Spack checkout to use (overrides spack.spack_path in deploy/config.yaml.j2).", + "route": ["deploy", "run"] + }, + { + "flags": ["--recreate"], + "dest": "recreate", + "action": "store_true", + "help": "Recreate the environment if it exists.", + "route": ["deploy", "bootstrap", "run"] + }, + { + "flags": ["--mache-version"], + "dest": "mache_version", + "help": "The mache version to use if not using an org/fork/branch.", + "route": ["deploy", "bootstrap", "run"] + }, + { + "flags": ["--python"], + "dest": "python", + "help": "The python major and minor version to use. Overrides deploy/pins.cfg.j2.", + "route": ["deploy", "bootstrap", "run"] + }, + { + "flags": ["--mache-fork"], + "dest": "mache_fork", + "help": "Point to a mache org/fork (and branch) for testing. Example: E3SM-Project/mache", + "route": ["deploy", "bootstrap", "run"] + }, + { + "flags": ["--mache-branch"], + "dest": "mache_branch", + "help": "Point to a mache branch (and fork) for testing.", + "route": ["deploy", "bootstrap", "run"] + }, + { + "flags": ["--quiet"], + "dest": "quiet", + "action": "store_true", + "help": "Only print output to log files, not to the terminal.", + "route": ["deploy", "bootstrap", "run"] + }, + { + "flags": ["--bootstrap-only"], + "dest": "bootstrap_only", + "action": "store_true", + "help": "Only create or update the bootstrap pixi environment.", + "route": ["deploy"] + } + ] +} diff --git a/deploy/config.yaml.j2 b/deploy/config.yaml.j2 new file mode 100644 index 0000000000..1d96a590ce --- /dev/null +++ b/deploy/config.yaml.j2 @@ -0,0 +1,179 @@ +project: + software: "compass" + + # A specific version string or "dynamic" if provided by the "pre_pixi" hook. + version: "dynamic" + + # Machine name selection. + # Priority order in `mache deploy run`: + # 1. CLI --machine + # 2. this value (project.machine) + # 3. automatic detection (if this is "dynamic") + machine: "dynamic" + + runtime_version_cmd: "python -c 'from compass.version import __version__; print(__version__)'" + +machines: + # Optional path containing machine config files in ini format. + # + # This MUST be a filesystem path (not a Python package) because we need to + # read machine configs before the target software (and its dependencies) + # have been installed into the pixi environment. + # + # Should be a relative path, relative to the target software repo root. + # + # Files should be named like ".cfg" (e.g. "chrysalis.cfg"). + # + # Machine config is loaded in this order: + # 1. mache.machines/default.cfg + # 2. mache.machines/.cfg (if a known machine is selected) + # 3. /default.cfg (if machines.path is set) + # 4. /.cfg (if present) + path: compass/machines + +pixi: + # Whether to deploy the pixi environment + deploy: true + + # Where to install the pixi project (and its .pixi directory). + # Absolute path is recommended for shared deployments. + # Environment variables will be expanded at runtime (e.g. $SCRATCH). + prefix: pixi-env + + # Channels used by pixi for this environment. + channels: + - conda-forge + - e3sm/label/compass + + # MPI provider for conda packages. + # Supported values in `mache deploy run`: + # - nompi + # - mpich + # - openmpi + # - hpc (E3SM-Unified only) + # - dynamic (determine by the "pre_pixi" hook) + mpi: "dynamic" + + # Whether to install the target software in editable/development mode. + install_dev_software: true + +# System toolchain selection (primarily for Spack-based dependencies). +# +# These values are resolved in `mache deploy run` with the following priority: +# 1. CLI flags: --compiler / --mpi +# 2. Values here +# 3. Machine config defaults from [deploy] in merged machine config: +# compiler = +# mpi_ = +# +# Use "dynamic" to request defaults from machine config. +toolchain: + # One or more compiler names. Examples: + # compiler: [gnu] + # compiler: [gnu, intel] + # + # If empty (or "dynamic"), defaults come from merged machine config: + # [deploy] compiler + compiler: [] + + # One or more MPI libraries. Examples: + # mpi: [openmpi] + # mpi: [mpich, openmpi] + # + # Pairing rules in `mache deploy run`: + # - If both lists have the same length, they are zipped. + # - If one list has length 1, it is broadcast across the other. + # - If mpi is empty (or "dynamic"), defaults come from machine config: + # [deploy] mpi_ (preferred) + # [deploy] mpi (fallback) + mpi: [] + +env_vars: + # Placeholder: env vars to export in the "load" script + set: {} + +spack: + # Whether to deploy Spack environments at all. + # + # Behavior: + # - If true, deploy ALL supported Spack environments. + # - If false, deploy none. + # + # This can be forced on at runtime with the `mache deploy run` CLI flag: + # --deploy-spack + deploy: false + + # Whether this target repository supports a Spack *library* environment. + # + # If true, mache will deploy one library env per toolchain pair. + supported: true + + # Optional: deploy an additional "software" spack environment. + # + # This environment is built once (not per toolchain) with a single compiler + # and MPI from the merged machine config: + # [deploy] software_compiler + # [deploy] mpi_ + # + # Load scripts do NOT activate this environment; they add its view's `bin` + # directory to PATH. + software: + # Whether this target repository supports a Spack *software* environment. + supported: false + + # Optional override for the environment name. + # Default: "_software" + env_name: null + + # Base path for the spack checkout used for deployment. + # If it does not exist, mache will clone the E3SM spack repo. + # + # In practice, most target repositories should set this dynamically in the + # `pre_spack()` hook (e.g., based on machine config) by writing: + # ctx.runtime['spack']['spack_path'] = + # This config value is a fallback. + # + # Required (either via hook/runtime or here) when spack.deploy (or + # --deploy-spack) is used and at least one supported spack environment is + # enabled. + # Can also be overridden temporarily via: --spack-path + spack_path: null + + # Prefix for spack environment names. + # Final env name is computed as: "__". + env_name_prefix: "compass" + + # Jinja2-templated YAML file in the target repo containing a YAML list of + # spack specs. This is inserted into the appropriate mache spack env + # template for the selected machine/compiler/mpi. + # + # Expected format: a YAML mapping with keys "library" and/or "software", + # each containing a list of spec strings. + specs_template: "deploy/spack.yaml.j2" + + # Optional spack build settings + tmpdir: null + mirror: null + custom_spack: "" + +jigsaw: + # If true, build/install JIGSAW + JIGSAW-Python into the pixi env + enabled: true + + # Relative path in the target repo where JIGSAW-Python lives. + # If this is a submodule, `mache deploy run` will initialize it. + # If it is not, `mache deploy run` will clone it from the main JIGSAW-Python + # repo if missing. + jigsaw_python_path: jigsaw-python + +# Optional deployment hooks. +# +# Hooks run ONLY during `mache deploy run` and execute arbitrary Python code +# from the target repository. Use only with trusted repositories. +# +# hooks: +hooks: + file: "deploy/hooks.py" + entrypoints: + pre_pixi: "pre_pixi" + pre_spack: "pre_spack" diff --git a/deploy/custom_cli_spec.json b/deploy/custom_cli_spec.json new file mode 100644 index 0000000000..46fae3daa9 --- /dev/null +++ b/deploy/custom_cli_spec.json @@ -0,0 +1,11 @@ +{ + "arguments": [ + { + "flags": ["--with-albany"], + "dest": "with_albany", + "action": "store_true", + "help": "Enable Albany support for this deployment.", + "route": ["deploy", "run"] + } + ] +} diff --git a/deploy/hooks.py b/deploy/hooks.py new file mode 100644 index 0000000000..8277665dc3 --- /dev/null +++ b/deploy/hooks.py @@ -0,0 +1,197 @@ +"""Compass-specific hooks for ``mache deploy run``.""" + +from __future__ import annotations + +import os +from pathlib import Path +from typing import TYPE_CHECKING, Any + +from packaging.version import Version + +if TYPE_CHECKING: + from mache.deploy.hooks import DeployContext + + +def pre_pixi(ctx: DeployContext) -> dict[str, Any] | None: + compass_version = _get_version() + mpi = _get_pixi_mpi(ctx.machine, ctx.machine_config) + return { + 'project': {'version': compass_version}, + 'pixi': {'mpi': mpi}, + } + + +def pre_spack(ctx: DeployContext) -> dict[str, Any] | None: + toolchain_pairs = _get_toolchain_pairs(ctx) + _check_unsupported(ctx.machine, toolchain_pairs) + + updates: dict[str, Any] = {} + spack_path = _get_spack_path(ctx.config, ctx.machine, ctx.machine_config) + if spack_path is not None: + updates['spack'] = {'spack_path': spack_path} + + if _with_albany(ctx): + _check_albany_support(ctx.machine, toolchain_pairs) + _set_albany_config(ctx) + + return updates + + +def _get_version() -> str: + here = Path(__file__).resolve().parent + version_path = here.parent / 'compass' / 'version.py' + namespace: dict[str, str] = {} + exec(version_path.read_text(encoding='utf-8'), namespace) + return namespace['__version__'] + + +def _get_pixi_mpi(machine: str | None, machine_config) -> str: + if machine is not None and not machine.startswith('conda'): + return 'nompi' + + if not machine_config.has_section('deploy'): + raise ValueError("Missing 'deploy' section in machine config") + + compiler = machine_config.get('deploy', 'compiler', fallback='').strip() + if not compiler: + raise ValueError("Missing 'compiler' option in 'deploy' section") + + mpi_option = f'mpi_{compiler.replace("-", "_")}' + mpi = machine_config.get('deploy', mpi_option, fallback='').strip() + if not mpi: + raise ValueError( + f"Missing '{mpi_option}' option in 'deploy' section" + ) + return mpi + + +def _get_spack_path(config, machine: str | None, machine_config) -> str | None: + spack_cfg = config.get('spack', {}) + if isinstance(spack_cfg, dict): + spack_path = spack_cfg.get('spack_path') + if spack_path not in (None, '', 'null', 'None'): + return None + + if machine is None or machine.startswith('conda'): + return None + + if not machine_config.has_section('deploy'): + raise ValueError("Missing 'deploy' section in machine config") + + spack_base = machine_config.get('deploy', 'spack', fallback='').strip() + if not spack_base: + raise ValueError("Missing 'spack' option in 'deploy' section") + + release_version = Version(_get_version()).base_version + return os.path.join(spack_base, f'dev_compass_{release_version}') + + +def _get_toolchain_pairs(ctx: DeployContext) -> list[tuple[str, str]]: + runtime_toolchain = ctx.runtime.get('toolchain', {}) + if not isinstance(runtime_toolchain, dict): + return [] + pairs = runtime_toolchain.get('pairs', []) + if not isinstance(pairs, list): + return [] + + toolchain_pairs: list[tuple[str, str]] = [] + for pair in pairs: + if not isinstance(pair, dict): + continue + compiler = str(pair.get('compiler', '')).strip() + mpi = str(pair.get('mpi', '')).strip() + if compiler and mpi: + toolchain_pairs.append((compiler, mpi)) + return toolchain_pairs + + +def _check_unsupported( + machine: str | None, toolchain_pairs: list[tuple[str, str]] +) -> None: + if machine is None or not toolchain_pairs: + return + + unsupported = _read_triplets(Path('deploy') / 'unsupported.txt', machine) + for compiler, mpi in toolchain_pairs: + if (compiler, mpi) in unsupported: + raise ValueError( + f'{compiler} with {mpi} is not supported on {machine}' + ) + + +def _check_albany_support( + machine: str | None, toolchain_pairs: list[tuple[str, str]] +) -> None: + if machine is None: + raise ValueError('Albany deployment requires a known machine') + if not toolchain_pairs: + raise ValueError('Albany deployment requires a compiler and MPI pair') + + supported = _read_triplets( + Path('deploy') / 'albany_supported.txt', machine + ) + for compiler, mpi in toolchain_pairs: + if (compiler, mpi) not in supported: + raise ValueError( + f'{compiler} with {mpi} is not supported with albany on ' + f'{machine}' + ) + + +def _set_albany_config(ctx: DeployContext) -> None: + spack_cfg = ctx.config.setdefault('spack', {}) + spack_cfg['env_name_prefix'] = 'compass_albany' + + spack_pins = ctx.pins.setdefault('spack', {}) + spack_pins['albany_enabled'] = 'true' + spack_pins['albany_variants'] = _get_machine_override( + ctx.machine_config, + option='albany_variants', + fallback=spack_pins.get('albany_variants', '+mpas~py+unit_tests'), + ) + spack_pins['trilinos_variants'] = _get_machine_override( + ctx.machine_config, + option='trilinos_variants', + fallback=spack_pins.get('trilinos_variants', ''), + ) + + +def _get_machine_override(machine_config, option: str, fallback: str) -> str: + if not machine_config.has_section('deploy'): + return fallback + value = machine_config.get('deploy', option, fallback=fallback) + return str(value).strip() + + +def _with_albany(ctx: DeployContext) -> bool: + # The custom CLI flag takes precedence; the env var remains a fallback for + # compatibility with older workflows. + return ( + bool(getattr(ctx.args, 'with_albany', False)) or + _with_albany_from_env() + ) + + +def _with_albany_from_env() -> bool: + return os.environ.get('COMPASS_DEPLOY_WITH_ALBANY', '').lower() in ( + '1', + 'true', + 'yes', + 'on', + ) + + +def _read_triplets( + filename: Path, machine: str +) -> set[tuple[str, str]]: + triples: set[tuple[str, str]] = set() + for line in filename.read_text(encoding='utf-8').splitlines(): + line = line.strip() + if not line or line.startswith('#'): + continue + parts = [part.strip() for part in line.split(',')] + if len(parts) != 3: + raise ValueError(f'Bad line in "{filename.name}": {line}') + if parts[0] == machine: + triples.add((parts[1], parts[2])) + return triples diff --git a/deploy/load.sh b/deploy/load.sh new file mode 100644 index 0000000000..795a647fb1 --- /dev/null +++ b/deploy/load.sh @@ -0,0 +1,62 @@ +# bash snippet for adding Compass-specific environment variables + +if [ "${COMPASS_MACHINE:-}" = "chicoma-cpu" ] || \ + [ "${COMPASS_MACHINE:-}" = "pm-cpu" ] || \ + [ "${COMPASS_MACHINE:-}" = "pm-gpu" ]; then + export NETCDF="${CRAY_NETCDF_HDF5PARALLEL_PREFIX}" + export NETCDFF="${CRAY_NETCDF_HDF5PARALLEL_PREFIX}" + export PNETCDF="${CRAY_PARALLEL_NETCDF_PREFIX}" +else + export NETCDF="$(dirname "$(dirname "$(which nc-config)")")" + export NETCDFF="$(dirname "$(dirname "$(which nf-config)")")" + export PNETCDF="$(dirname "$(dirname "$(which pnetcdf-config)")")" +fi + +if [ "${COMPASS_MACHINE:-}" = "anvil" ] && \ + [[ "${COMPASS_COMPILER:-}" == *intel* ]]; then + export I_MPI_CC=icc + export I_MPI_CXX=icpc + export I_MPI_F77=ifort + export I_MPI_F90=ifort +fi + +if [ "${COMPASS_MPI:-}" = "mvapich" ]; then + export MV2_ENABLE_AFFINITY=0 + export MV2_SHOW_CPU_BINDING=1 +fi + +if [ -n "${MACHE_DEPLOY_SPACK_LIBRARY_VIEW:-}" ]; then + export PIO="${MACHE_DEPLOY_SPACK_LIBRARY_VIEW}" + export METIS_ROOT="${MACHE_DEPLOY_SPACK_LIBRARY_VIEW}" + export PARMETIS_ROOT="${MACHE_DEPLOY_SPACK_LIBRARY_VIEW}" +else + export PIO="${CONDA_PREFIX}" + export OPENMP_INCLUDE="-I${CONDA_PREFIX}/include" +fi + +export HAVE_ADIOS=false + +albany_flag_file="${MACHE_DEPLOY_SPACK_LIBRARY_VIEW:-}/export_albany.in" +if [ -f "${albany_flag_file}" ]; then + # shellcheck source=/dev/null + source "${albany_flag_file}" + export ALBANY_LINK_LIBS + + stdcxx="-lstdc++" + if [ "$(uname)" = "Darwin" ]; then + stdcxx="-lc++" + fi + + mpicxx="" + if [ "${COMPASS_MPI:-}" = "openmpi" ] && \ + { [ "${COMPASS_MACHINE:-}" = "anvil" ] || \ + [ "${COMPASS_MACHINE:-}" = "chrysalis" ]; }; then + mpicxx="-lmpi_cxx" + fi + + export MPAS_EXTERNAL_LIBS="${MPAS_EXTERNAL_LIBS} ${ALBANY_LINK_LIBS} ${stdcxx} ${mpicxx}" +fi + +export USE_PIO2=true +export OPENMP=true +export HDF5_USE_FILE_LOCKING=FALSE diff --git a/deploy/pins.cfg b/deploy/pins.cfg new file mode 100644 index 0000000000..1406262691 --- /dev/null +++ b/deploy/pins.cfg @@ -0,0 +1,29 @@ +# pins for the pixi environment +[pixi] +bootstrap_python = 3.13 +python = 3.13 +esmf = 8.9.0 +geometric_features = 1.6.1 +mache = 3.0.4 +mpas_tools = 1.3.2 +otps = 2021.10 +parallelio = 2.6.6 + +# pins for the spack environment +[spack] +albany = compass-2024-03-13 +albany_variants = +mpas~py+unit_tests +# cmake newer than 3.23.0 needed for Trilinos +cmake = 3.23.0: +hdf5 = 1.14.6 +metis = 5.1.0 +moab = master +scorpio = 1.8.2 +trilinos_variants = + + +# pins for both pixi and spack environments +[all] +netcdf_c = 4.9.2 +netcdf_fortran = 4.6.2 +pnetcdf = 1.14.0 diff --git a/deploy/pixi.toml.j2 b/deploy/pixi.toml.j2 new file mode 100644 index 0000000000..11cb15f35a --- /dev/null +++ b/deploy/pixi.toml.j2 @@ -0,0 +1,93 @@ +[workspace] +name = "compass-dev" +channels = [ +{%- for channel in pixi_channels %} + "{{ channel }}"{%- if not loop.last %},{% endif %} +{%- endfor %} +] +platforms = ["{{ platform }}"] +channel-priority = "strict" + +[dependencies] +python = "{{ python }}.*" +pip = "*" +setuptools = "*" + +{%- if include_mache %} +mache = "{{ mache }}.*" +{%- endif %} + +{%- if include_jigsaw %} +jigsawpy = "*" +{%- endif %} + +cartopy = "*" +cartopy_offlinedata = "*" +cmocean = "*" +esmf = {version = "{{ esmf }}.*", build = "{{ mpi_prefix }}_*"} +ffmpeg = "*" +geometric_features = "{{ geometric_features }}.*" +git = "*" +gsw = "*" +h5py = "*" +ipython = "*" +jupyter = "*" +lxml = "*" +matplotlib-base = ">=3.9.1" +metis = "*" +moab = {version = ">=5.5.1", build = "{{ mpi_prefix }}_tempest_*"} +mpas_tools = "{{ mpas_tools }}.*" +nco = "*" +netcdf4 = {version = "*", build = "nompi_*"} +numpy = ">=2.0,<3.0" +{%- if platform == 'linux-64' %} +otps = "{{ otps }}.*" +{%- endif %} +progressbar2 = "*" +pyamg = ">=4.2.2" +pyproj = "*" +pyremap = ">=2.0.0,<3.0.0" +requests = "*" +"ruamel.yaml" = "*" +scikit-image = "!=0.20.0" +scipy = ">=1.8.0" +shapely = ">=2.0,<3.0" +xarray = "*" + +# Development +pytest = "*" +isort = "*" +flake8 = "*" +pre-commit = "*" + +# Documentation +m2r = "*" +mock = "*" +sphinx = "*" +sphinx_rtd_theme = "*" + +{%- if mpi != "nompi" %} +c-compiler = "*" +cmake = "*" +cxx-compiler = "*" +fortran-compiler = "*" +libnetcdf = {version = "{{ netcdf_c }}.*", build = "{{ mpi_prefix }}_*"} +libpnetcdf = {version = "{{ pnetcdf }}.*", build = "{{ mpi_prefix }}_*"} +parallelio = {version = "{{ parallelio }}.*", build = "{{ mpi_prefix }}_*"} +m4 = "*" +make = "*" +{{ mpi }} = "*" +netcdf-fortran = {version = "{{ netcdf_fortran }}.*", build = "{{ mpi_prefix }}_*"} +{%- if system == 'linux' %} +libgomp = "*" +{%- elif system == 'osx' %} +llvm-openmp = "*" +{%- endif %} +{%- endif %} + +# CF-compliance +cfchecker = "*" +udunits2 = "*" + +# Visualization +ncview = "*" diff --git a/deploy/spack.yaml.j2 b/deploy/spack.yaml.j2 new file mode 100644 index 0000000000..36bd6269a7 --- /dev/null +++ b/deploy/spack.yaml.j2 @@ -0,0 +1,41 @@ +# Spack specs for deployment. +# +# This file is rendered with Jinja2 during `mache deploy run`. +# Available template variables include: +# - software, machine, compiler, mpi +# - pins (dict of dicts from deploy/pins.cfg) +# - spack / pixi / all (shortcuts for pins sections) +# +# Expected format: a YAML mapping with one or both keys: +# - library: specs for the per-toolchain "library" environment(s) +# - software: specs for the single "software" environment +# +# Backward compatibility: if this renders to a YAML list[str], it will be +# interpreted as the "library" specs. +# +# Example: +# library: +# - "hdf5@{{ spack.hdf5 }} +fortran +hl" +# - "netcdf-c@{{ spack.netcdf_c }} +mpi" +# - "netcdf-fortran@{{ spack.netcdf_fortran }}" +# software: +# - "cmake@{{ spack.cmake }}" +# +library: + - "cmake@{{ spack.cmake }}" +{%- if not e3sm_hdf5_netcdf %} + - "hdf5@{{ spack.hdf5 }}+cxx+fortran+hl+mpi+shared" + - "netcdf-c@{{ all.netcdf_c }}+mpi~parallel-netcdf" + - "netcdf-fortran@{{ all.netcdf_fortran }}" + - "parallel-netcdf@{{ all.pnetcdf }}+cxx+fortran" +{%- endif %} + - "esmf@{{ pixi.esmf }}+mpi+netcdf~pnetcdf~external-parallelio" + - "metis@{{ spack.metis }}+int64+real64" + - "moab@{{ spack.moab }}+mpi+hdf5+netcdf+pnetcdf+metis+parmetis+tempest" + - "e3sm-scorpio@{{ spack.scorpio }}+mpi~timing~internal-timing~tools+malloc" +{%- if spack.albany_enabled | default(false) %} + - "trilinos-for-albany@{{ spack.trilinos }}{{ spack.trilinos_variants }}" + - "albany@{{ spack.albany }}{{ spack.albany_variants }}" +{%- endif %} + +software: [] diff --git a/conda/unsupported.txt b/deploy/unsupported.txt similarity index 71% rename from conda/unsupported.txt rename to deploy/unsupported.txt index cfce62076d..98d65ff2cc 100644 --- a/conda/unsupported.txt +++ b/deploy/unsupported.txt @@ -1,7 +1,6 @@ # a list of unsupported machine, compiler and mpi combinations # no spack available -anvil, gnu, impi chicoma-cpu, nvidia, mpich chicoma-cpu, aocc, mpich chicoma-cpu, amdclang, mpich @@ -18,13 +17,5 @@ pm-cpu, amdclang, mpich pm-gpu, gnu, mpich pm-gpu, nvidia, mpich -# compiles but tests unreliable (errors or hanging), -# see https://github.com/MPAS-Dev/compass/issues/336 -anvil, intel, mvapich - - # can't build ESMF chrysalis, intel, impi - -# can't build MOAB -anvil, gnu, mvapich From 84ebd19f15847fec59164585db3cbea7291915ab Mon Sep 17 00:00:00 2001 From: Xylar Asay-Davis Date: Sat, 21 Mar 2026 11:51:50 +0100 Subject: [PATCH 04/22] Update dependencies --- deploy.py | 74 +++++++++++++++++++++++++++++++++++++++++++- deploy/cli_spec.json | 2 +- deploy/pins.cfg | 21 +++++++------ deploy/pixi.toml.j2 | 4 +-- 4 files changed, 87 insertions(+), 14 deletions(-) diff --git a/deploy.py b/deploy.py index b16c7503b7..369d0d3e7d 100755 --- a/deploy.py +++ b/deploy.py @@ -3,7 +3,8 @@ Target software deployment entrypoint. - Reads pinned mache version from deploy/pins.cfg -- Reads CLI spec from deploy/cli_spec.json and builds argparse CLI +- Reads CLI spec from deploy/cli_spec.json plus optional + deploy/custom_cli_spec.json and builds argparse CLI - Downloads mache/deploy/bootstrap.py for either: * a given mache fork/branch, or * the pinned mache version @@ -25,6 +26,7 @@ PINS_CFG = os.path.join('deploy', 'pins.cfg') CLI_SPEC_JSON = os.path.join('deploy', 'cli_spec.json') +CUSTOM_CLI_SPEC_JSON = os.path.join('deploy', 'custom_cli_spec.json') DEPLOY_TMP_DIR = 'deploy_tmp' BOOTSTRAP_PATH = os.path.join(DEPLOY_TMP_DIR, 'bootstrap.py') @@ -40,6 +42,10 @@ def main(): pinned_mache_version, pinned_python_version = _read_pins(PINS_CFG) cli_spec = _read_cli_spec(CLI_SPEC_JSON) + cli_spec = _merge_optional_cli_spec( + cli_spec, + _read_optional_cli_spec(CUSTOM_CLI_SPEC_JSON), + ) parser = _build_parser_from_cli_spec(cli_spec) args = parser.parse_args(sys.argv[1:]) @@ -225,6 +231,72 @@ def _read_cli_spec(spec_path): return spec +def _read_optional_cli_spec(spec_path): + if not os.path.exists(spec_path): + return None + + try: + with open(spec_path, 'r', encoding='utf-8') as f: + spec = json.load(f) + except (OSError, json.JSONDecodeError) as e: + raise SystemExit(f'ERROR: Failed to parse {spec_path}: {e!r}') from e + + if not isinstance(spec, dict): + raise SystemExit(f'ERROR: {spec_path} must contain a JSON object') + if 'arguments' not in spec: + raise SystemExit( + f"ERROR: {spec_path} must contain top-level key 'arguments'" + ) + if not isinstance(spec['arguments'], list): + raise SystemExit(f"ERROR: {spec_path} 'arguments' must be a list") + meta = spec.get('meta') + if meta is not None and not isinstance(meta, dict): + raise SystemExit(f"ERROR: {spec_path} 'meta' must be an object") + + return spec + + +def _merge_optional_cli_spec(cli_spec, custom_cli_spec): + if custom_cli_spec is None: + return cli_spec + + merged = { + 'meta': dict(cli_spec.get('meta', {})), + 'arguments': list(cli_spec.get('arguments', [])), + } + + seen_dests = set() + seen_flags = set() + for entry in merged['arguments']: + dest = entry.get('dest') + if dest: + seen_dests.add(dest) + for flag in entry.get('flags', []): + seen_flags.add(flag) + + for entry in custom_cli_spec['arguments']: + dest = entry.get('dest') + if dest in seen_dests: + raise SystemExit( + 'ERROR: deploy/custom_cli_spec.json duplicates generated ' + f"dest '{dest}'" + ) + flags = entry.get('flags', []) + duplicate_flags = [flag for flag in flags if flag in seen_flags] + if duplicate_flags: + dup_str = ', '.join(duplicate_flags) + raise SystemExit( + 'ERROR: deploy/custom_cli_spec.json duplicates generated ' + f'flags: {dup_str}' + ) + merged['arguments'].append(entry) + if dest: + seen_dests.add(dest) + seen_flags.update(flags) + + return merged + + def _build_parser_from_cli_spec(cli_spec): description = cli_spec.get('meta', {}).get( 'description', 'Deploy E3SM software environment' diff --git a/deploy/cli_spec.json b/deploy/cli_spec.json index 5ad521d7a7..86bf8fc3bd 100644 --- a/deploy/cli_spec.json +++ b/deploy/cli_spec.json @@ -1,7 +1,7 @@ { "meta": { "software": "compass", - "mache_version": "3.0.4", + "mache_version": "3.1.0", "description": "Deploy compass environment" }, "arguments": [ diff --git a/deploy/pins.cfg b/deploy/pins.cfg index 1406262691..b75871f34b 100644 --- a/deploy/pins.cfg +++ b/deploy/pins.cfg @@ -1,29 +1,30 @@ # pins for the pixi environment [pixi] -bootstrap_python = 3.13 -python = 3.13 -esmf = 8.9.0 +bootstrap_python = 3.14 +python = 3.14 +esmf = 8.9.1 geometric_features = 1.6.1 -mache = 3.0.4 -mpas_tools = 1.3.2 +mache = 3.1.0 +mpas_tools = 1.4.0 otps = 2021.10 -parallelio = 2.6.6 +parallelio = 2.6.9 # pins for the spack environment [spack] -albany = compass-2024-03-13 +albany = compass-2026-03-10 albany_variants = +mpas~py+unit_tests # cmake newer than 3.23.0 needed for Trilinos cmake = 3.23.0: hdf5 = 1.14.6 metis = 5.1.0 -moab = master +moab = 5.6.0 scorpio = 1.8.2 +trilinos = compass-2026-02-06 trilinos_variants = # pins for both pixi and spack environments [all] -netcdf_c = 4.9.2 +netcdf_c = 4.10.0 netcdf_fortran = 4.6.2 -pnetcdf = 1.14.0 +pnetcdf = 1.14.1 diff --git a/deploy/pixi.toml.j2 b/deploy/pixi.toml.j2 index 11cb15f35a..5573454792 100644 --- a/deploy/pixi.toml.j2 +++ b/deploy/pixi.toml.j2 @@ -35,7 +35,7 @@ jupyter = "*" lxml = "*" matplotlib-base = ">=3.9.1" metis = "*" -moab = {version = ">=5.5.1", build = "{{ mpi_prefix }}_tempest_*"} +moab = {version = "{{ moab }}.*", build = "*_tempest_*"} mpas_tools = "{{ mpas_tools }}.*" nco = "*" netcdf4 = {version = "*", build = "nompi_*"} @@ -72,12 +72,12 @@ cmake = "*" cxx-compiler = "*" fortran-compiler = "*" libnetcdf = {version = "{{ netcdf_c }}.*", build = "{{ mpi_prefix }}_*"} +netcdf-fortran = {version = "{{ netcdf_fortran }}.*", build = "{{ mpi_prefix }}_*"} libpnetcdf = {version = "{{ pnetcdf }}.*", build = "{{ mpi_prefix }}_*"} parallelio = {version = "{{ parallelio }}.*", build = "{{ mpi_prefix }}_*"} m4 = "*" make = "*" {{ mpi }} = "*" -netcdf-fortran = {version = "{{ netcdf_fortran }}.*", build = "{{ mpi_prefix }}_*"} {%- if system == 'linux' %} libgomp = "*" {%- elif system == 'osx' %} From 98aebf3992b408ba787f5962d33240feb7c9fdf0 Mon Sep 17 00:00:00 2001 From: Xylar Asay-Davis Date: Sat, 21 Mar 2026 11:35:16 +0100 Subject: [PATCH 05/22] Remove compass.load and use mache.deploy instead --- .../branch_ensemble/branch_run.py | 11 +-- .../ensemble_generator/ensemble_member.py | 11 +-- .../ismip6_ais_proj2300/set_up_experiment.py | 11 +-- compass/load/__init__.py | 72 ------------------- compass/load/load_script.template | 12 ---- compass/load_script.py | 25 +++++++ compass/setup.py | 15 ++-- 7 files changed, 35 insertions(+), 122 deletions(-) delete mode 100644 compass/load/__init__.py delete mode 100644 compass/load/load_script.template create mode 100644 compass/load_script.py diff --git a/compass/landice/tests/ensemble_generator/branch_ensemble/branch_run.py b/compass/landice/tests/ensemble_generator/branch_ensemble/branch_run.py index 864a751ff0..b2ead90a1f 100644 --- a/compass/landice/tests/ensemble_generator/branch_ensemble/branch_run.py +++ b/compass/landice/tests/ensemble_generator/branch_ensemble/branch_run.py @@ -4,8 +4,8 @@ import netCDF4 import compass.namelist -from compass.io import symlink from compass.job import write_job_script +from compass.load_script import symlink_load_script from compass.model import run_model from compass.step import Step @@ -166,14 +166,7 @@ def setup(self): pre_run_commands=pre_run_cmd, post_run_commands=post_run_cmd) - # COMPASS does not create symlinks for the load script in step dirs, - # so use the standard approach for creating that symlink in each - # step dir. - if 'LOAD_COMPASS_ENV' in os.environ: - script_filename = os.environ['LOAD_COMPASS_ENV'] - # make a symlink to the script for loading the compass conda env. - symlink(script_filename, os.path.join(self.work_dir, - 'load_compass_env.sh')) + symlink_load_script(self.work_dir) def run(self): """ diff --git a/compass/landice/tests/ensemble_generator/ensemble_member.py b/compass/landice/tests/ensemble_generator/ensemble_member.py index ca08833cff..52733ffea5 100644 --- a/compass/landice/tests/ensemble_generator/ensemble_member.py +++ b/compass/landice/tests/ensemble_generator/ensemble_member.py @@ -6,9 +6,9 @@ import netCDF4 import yaml -from compass.io import symlink from compass.job import write_job_script from compass.landice.extrapolate import extrapolate_variable +from compass.load_script import symlink_load_script from compass.model import make_graph_file, run_model from compass.step import Step @@ -262,14 +262,7 @@ def setup(self): target_cores=self.ntasks, min_cores=self.min_tasks, work_dir=self.work_dir) - # COMPASS does not create symlinks for the load script in step dirs, - # so use the standard approach for creating that symlink in each - # step dir. - if 'LOAD_COMPASS_ENV' in os.environ: - script_filename = os.environ['LOAD_COMPASS_ENV'] - # make a symlink to the script for loading the compass conda env. - symlink(script_filename, os.path.join(self.work_dir, - 'load_compass_env.sh')) + symlink_load_script(self.work_dir) # save run info for analysis/viz with open(os.path.join(self.work_dir, 'run_info.cfg'), 'w') \ diff --git a/compass/landice/tests/ismip6_run/ismip6_ais_proj2300/set_up_experiment.py b/compass/landice/tests/ismip6_run/ismip6_ais_proj2300/set_up_experiment.py index 94914678e0..b205b0ff1c 100644 --- a/compass/landice/tests/ismip6_run/ismip6_ais_proj2300/set_up_experiment.py +++ b/compass/landice/tests/ismip6_run/ismip6_ais_proj2300/set_up_experiment.py @@ -6,8 +6,8 @@ from jinja2 import Template -from compass.io import symlink from compass.job import write_job_script +from compass.load_script import symlink_load_script from compass.model import make_graph_file, run_model from compass.step import Step @@ -290,14 +290,7 @@ def setup(self): # noqa:C901 graph_filename=os.path.join(self.work_dir, 'graph.info')) - # COMPASS does not create symlinks for the load script in step dirs, - # so use the standard approach for creating that symlink in each - # step dir. - if 'LOAD_COMPASS_ENV' in os.environ: - script_filename = os.environ['LOAD_COMPASS_ENV'] - # make a symlink to the script for loading the compass conda env. - symlink(script_filename, os.path.join(self.work_dir, - 'load_compass_env.sh')) + symlink_load_script(self.work_dir) # customize job script self.config.set('job', 'job_name', self.exp) diff --git a/compass/load/__init__.py b/compass/load/__init__.py deleted file mode 100644 index a26631b70d..0000000000 --- a/compass/load/__init__.py +++ /dev/null @@ -1,72 +0,0 @@ -import argparse -import os -import subprocess -import sys -from importlib import resources - -from jinja2 import Template - -from compass.version import __version__ as compass_version - - -def get_conda_base_and_env(): - if 'CONDA_EXE' in os.environ: - conda_exe = os.environ['CONDA_EXE'] - conda_base = os.path.abspath( - os.path.join(conda_exe, '..', '..')) - else: - raise ValueError('No conda executable detected.') - - if 'CONDA_DEFAULT_ENV' in os.environ: - conda_env = os.environ['CONDA_DEFAULT_ENV'] - else: - raise ValueError('No conda environment detected.') - - return conda_base, conda_env - - -def get_mpi(): - - for mpi in ['mpich', 'openmpi']: - try: - check = subprocess.check_output( - ['conda', 'list', mpi], stderr=subprocess.DEVNULL - ).decode('utf-8') - except subprocess.CalledProcessError: - continue - - if mpi in check: - return mpi - - return None - - -def main(): - parser = argparse.ArgumentParser( - description='Generate a load script for a Linux or OSX machine') - parser.parse_args() - - conda_base, conda_env = get_conda_base_and_env() - - mpi = get_mpi() - - if mpi is None: - suffix = '' - else: - suffix = f'_{mpi}' - - if sys.platform == 'Linux': - env_vars = 'export MPAS_EXTERNAL_LIBS="-lgomp"' - else: - env_vars = '' - - script_filename = f'load_compass_{compass_version}{suffix}.sh' - script_filename = os.path.abspath(script_filename) - - template = Template(resources.read_text( - 'compass.load', 'load_script.template')) - text = template.render(conda_base=conda_base, conda_env=conda_env, - env_vars=env_vars, load_script=script_filename) - - with open(script_filename, 'w') as handle: - handle.write(text) diff --git a/compass/load/load_script.template b/compass/load/load_script.template deleted file mode 100644 index 2b3c271204..0000000000 --- a/compass/load/load_script.template +++ /dev/null @@ -1,12 +0,0 @@ -source {{ conda_base }}/etc/profile.d/conda.sh -conda activate {{ conda_env }} - -export NETCDF=$(dirname $(dirname $(which nc-config))) -export NETCDFF=$(dirname $(dirname $(which nf-config))) -export PNETCDF=$(dirname $(dirname $(which pnetcdf-config))) - -{{ env_vars }} -export PIO={{ conda_base}}/envs/{{ conda_env }} -export USE_PIO2=true -export HDF5_USE_FILE_LOCKING=FALSE -export LOAD_COMPASS_ENV={{ load_script }} diff --git a/compass/load_script.py b/compass/load_script.py new file mode 100644 index 0000000000..29268d2a54 --- /dev/null +++ b/compass/load_script.py @@ -0,0 +1,25 @@ +import os + +from compass.io import symlink + + +def get_active_load_script(): + """ + Get the active COMPASS load script from the environment. + + ``mache.deploy`` exports ``COMPASS_LOAD_SCRIPT`` from generated load + scripts. ``LOAD_COMPASS_ENV`` is retained as a legacy fallback while the + transition is underway. + """ + return os.environ.get( + 'COMPASS_LOAD_SCRIPT', os.environ.get('LOAD_COMPASS_ENV') + ) + + +def symlink_load_script(work_dir): + """ + Symlink the active load script into a work directory if one is available. + """ + script_filename = get_active_load_script() + if script_filename is not None: + symlink(script_filename, os.path.join(work_dir, 'load_compass_env.sh')) diff --git a/compass/setup.py b/compass/setup.py index 70fa193bc1..5f40b1880e 100644 --- a/compass/setup.py +++ b/compass/setup.py @@ -8,6 +8,7 @@ from compass.config import CompassConfigParser from compass.io import symlink from compass.job import write_job_script +from compass.load_script import symlink_load_script from compass.machines import discover_machine from compass.mpas_cores import get_mpas_cores @@ -160,7 +161,7 @@ def setup_cases(tests=None, numbers=None, config_file=None, machine=None, # noq with open(pickle_file, 'wb') as handle: pickle.dump(test_suite, handle, protocol=pickle.HIGHEST_PROTOCOL) - _symlink_load_script(work_dir) + symlink_load_script(work_dir) max_cores, max_of_min_cores = _get_required_cores(test_cases) @@ -307,7 +308,7 @@ def setup_case(path, test_case, config_file, machine, work_dir, baseline_dir, pickle.dump((test_case, step), handle, protocol=pickle.HIGHEST_PROTOCOL) - _symlink_load_script(step.work_dir) + symlink_load_script(step.work_dir) if machine is not None: cores = step.cpus_per_task * step.ntasks @@ -323,7 +324,7 @@ def setup_case(path, test_case, config_file, machine, work_dir, baseline_dir, 'work_dir': test_case.work_dir} pickle.dump(test_suite, handle, protocol=pickle.HIGHEST_PROTOCOL) - _symlink_load_script(test_case_dir) + symlink_load_script(test_case_dir) if machine is not None: max_cores, max_of_min_cores = _get_required_cores({path: test_case}) @@ -458,11 +459,3 @@ def _get_basic_config(config_file, machine, mpas_model_path, mpas_core): config.set('paths', 'mpas_model', mpas_model_path, user=True) return config - - -def _symlink_load_script(work_dir): - """ make a symlink to the script for loading the compass conda env. """ - if 'LOAD_COMPASS_ENV' in os.environ: - script_filename = os.environ['LOAD_COMPASS_ENV'] - symlink(script_filename, - os.path.join(work_dir, 'load_compass_env.sh')) From d197640928ebdf7761d86fc962a58a787a8d54e0 Mon Sep 17 00:00:00 2001 From: Xylar Asay-Davis Date: Sat, 21 Mar 2026 11:37:44 +0100 Subject: [PATCH 06/22] Update provenance to use pixi --- compass/provenance.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/compass/provenance.py b/compass/provenance.py index b5d59c1768..51530891ba 100644 --- a/compass/provenance.py +++ b/compass/provenance.py @@ -1,8 +1,6 @@ import os -import sys import subprocess - -from compass.config import CompassConfigParser +import sys def write(work_dir, test_cases, config=None): @@ -38,11 +36,13 @@ def write(work_dir, test_cases, config=None): else: mpas_git_version = _get_mpas_git_version(config) + package_list_name = 'pixi list' try: - args = ['conda', 'list'] - conda_list = subprocess.check_output(args).decode('utf-8') - except subprocess.CalledProcessError: - conda_list = None + package_list = subprocess.check_output( + ['pixi', 'list'] + ).decode('utf-8') + except (subprocess.CalledProcessError, FileNotFoundError): + package_list = None calling_command = ' '.join(sys.argv) @@ -92,9 +92,9 @@ def write(work_dir, test_cases, config=None): provenance_file.write('{}\n'.format(print_string)) - if conda_list is not None: - provenance_file.write('conda list:\n') - provenance_file.write('{}\n'.format(conda_list)) + if package_list is not None and package_list_name is not None: + provenance_file.write(f'{package_list_name}:\n') + provenance_file.write('{}\n'.format(package_list)) provenance_file.write('**************************************************' '*********************\n') From 0ce83f9e6fbcea6c03fd4e5798d5efdc54553c74 Mon Sep 17 00:00:00 2001 From: Xylar Asay-Davis Date: Sat, 21 Mar 2026 11:49:40 +0100 Subject: [PATCH 07/22] Add ignores related to mache.deploy --- .gitignore | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/.gitignore b/.gitignore index b8107fe4e3..d91cc23028 100644 --- a/.gitignore +++ b/.gitignore @@ -120,3 +120,21 @@ ENV/ # activation scripts /load_*.sh + +# pixi +.pixi/ +pixi.lock +pixi-env/ + +# vs code +.vscode/ + +# mypy +.mypy_cache/ + +# ruff +.ruff_cache/ + +# mache +.mache_cache/ +deploy_tmp From a7186d58d912e40e46d5d85277d12ca8a88a0ccc Mon Sep 17 00:00:00 2001 From: Xylar Asay-Davis Date: Sat, 21 Mar 2026 12:34:57 +0100 Subject: [PATCH 08/22] Update CI for mache.deploy --- .github/workflows/build_workflow.yml | 54 +++++++++++-------- .github/workflows/docs_workflow.yml | 66 ++++++++++++------------ .github/workflows/gis_coarse_meshgen.yml | 50 +++++++++++++----- 3 files changed, 103 insertions(+), 67 deletions(-) diff --git a/.github/workflows/build_workflow.yml b/.github/workflows/build_workflow.yml index 773a898ef1..9ae6eedaab 100644 --- a/.github/workflows/build_workflow.yml +++ b/.github/workflows/build_workflow.yml @@ -12,7 +12,7 @@ on: env: CANCEL_OTHERS: false PATHS_IGNORE: '["**/README.md", "**/docs/**"]' - PYTHON_VERSION: "3.13" + PYTHON_VERSION: "3.14" jobs: pre-commit-hooks: @@ -60,7 +60,7 @@ jobs: shell: bash -l {0} strategy: matrix: - python-version: ["3.10", "3.11", "3.12", "3.13"] + python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"] fail-fast: false steps: - id: skip_check @@ -73,34 +73,45 @@ jobs: uses: actions/checkout@v6 - if: ${{ steps.skip_check.outputs.should_skip != 'true' }} - name: Set up Conda Environment - uses: mamba-org/setup-micromamba@v2 + name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v6 + with: + python-version: ${{ matrix.python-version }} + + - if: ${{ steps.skip_check.outputs.should_skip != 'true' }} + name: Cache pixi packages + uses: actions/cache@v5 + env: + # Increase this value to reset cache if deploy inputs have not changed in the workflow + CACHE_NUMBER: 0 with: - environment-name: compass_test - init-shell: bash - condarc: | - channel_priority: strict - channels: - - conda-forge - - e3sm/label/compass - create-args: >- - python=${{ matrix.python-version }} + path: | + ~/.cache/rattler/cache + ~/.pixi/bin + key: ${{ runner.os }}-${{ matrix.python-version }}-pixi-${{ env.CACHE_NUMBER }}-${{ hashFiles('deploy.py', 'deploy/**', 'pyproject.toml') }} - if: ${{ steps.skip_check.outputs.should_skip != 'true' }} name: Install compass run: | git config --global url."https://github.com/".insteadOf "git@github.com:" - ./conda/configure_compass_env.py \ - --env_name compass_test \ - --python=${{ matrix.python-version }} \ - --mpi=mpich \ - --verbose - source load_compass_test_mpich.sh + ./deploy.py \ + --no-spack \ + --python ${{ matrix.python-version }} \ + --recreate + load_script=$(find . -maxdepth 1 -type f -name 'load_compass*.sh' | sort | tail -n 1) + if [ -z "$load_script" ]; then + echo "ERROR: no generated Compass load script found" >&2 + exit 1 + fi + source "$load_script" + python -c "import compass; import compass.version; print(compass.version.__version__)" + compass --help - if: ${{ steps.skip_check.outputs.should_skip != 'true' }} name: Build Sphinx Docs run: | - source load_compass_test_mpich.sh + load_script=$(find . -maxdepth 1 -type f -name 'load_compass*.sh' | sort | tail -n 1) + source "$load_script" cd docs make html @@ -109,7 +120,8 @@ jobs: env: CHECK_IMAGES: False run: | - source load_compass_test_mpich.sh + load_script=$(find . -maxdepth 1 -type f -name 'load_compass*.sh' | sort | tail -n 1) + source "$load_script" python -c "import compass" compass list compass list --machines diff --git a/.github/workflows/docs_workflow.yml b/.github/workflows/docs_workflow.yml index 5ca7e699d0..c0b1c0ef42 100644 --- a/.github/workflows/docs_workflow.yml +++ b/.github/workflows/docs_workflow.yml @@ -8,7 +8,7 @@ on: types: [published] env: - PYTHON_VERSION: "3.13" + PYTHON_VERSION: "3.14" jobs: publish-docs: @@ -16,8 +16,6 @@ jobs: defaults: run: shell: bash -l {0} - env: - docs_version: "latest" timeout-minutes: 20 steps: - uses: actions/checkout@v6 @@ -25,46 +23,46 @@ jobs: persist-credentials: false fetch-depth: 0 - - if: ${{ steps.skip_check.outputs.should_skip != 'true' }} - name: Set up Conda Environment - uses: mamba-org/setup-micromamba@v2 + - name: Set up Python ${{ env.PYTHON_VERSION }} + uses: actions/setup-python@v6 with: - environment-name: compass_test - init-shell: bash - condarc: | - channel_priority: strict - channels: - - conda-forge - - e3sm/label/compass - create-args: >- - python=${{ env.PYTHON_VERSION }} + python-version: ${{ env.PYTHON_VERSION }} - - if: ${{ steps.skip_check.outputs.should_skip != 'true' }} - name: Install compass + - name: Cache pixi packages + uses: actions/cache@v5 + env: + # Increase this value to reset cache if deploy inputs have not changed in the workflow + CACHE_NUMBER: 0 + with: + path: | + ~/.cache/rattler/cache + ~/.pixi/bin + key: ${{ runner.os }}-${{ env.PYTHON_VERSION }}-pixi-${{ env.CACHE_NUMBER }}-${{ hashFiles('deploy.py', 'deploy/**', 'pyproject.toml') }} + + - name: Install compass run: | git config --global url."https://github.com/".insteadOf "git@github.com:" - ./conda/configure_compass_env.py \ - --env_only \ - --env_name compass_test \ - --python=${{ env.PYTHON_VERSION }} \ - --verbose - source load_compass_test.sh + ./deploy.py \ + --no-spack \ + --python ${{ env.PYTHON_VERSION }} \ + --recreate + load_script=$(find . -maxdepth 1 -type f -name 'load_compass*.sh' | sort | tail -n 1) + if [ -z "$load_script" ]; then + echo "ERROR: no generated Compass load script found" >&2 + exit 1 + fi + source "$load_script" - name: Build Sphinx Docs run: | - source load_compass_test.sh + load_script=$(find . -maxdepth 1 -type f -name 'load_compass*.sh' | sort | tail -n 1) + source "$load_script" cd docs - if [[ $GITHUB_REF_NAME == "main" ]]; then - export DOCS_VERSION="latest" - else - export DOCS_VERSION=${GITHUB_REF_NAME} - fi - echo "Docs version: $DOCS_VERSION" - echo "docs_version=$DOCS_VERSION" >> $GITHUB_ENV make html - name: Copy Docs and Commit run: | - source load_compass_test.sh + load_script=$(find . -maxdepth 1 -type f -name 'load_compass*.sh' | sort | tail -n 1) + source "$load_script" cd docs # gh-pages branch must already exist git clone https://github.com/MPAS-Dev/compass.git --branch gh-pages --single-branch gh-pages @@ -74,8 +72,8 @@ jobs: touch .nojekyll # Add `index.html` to point to the `main` branch automatically. printf '' > index.html - # Only replace `main` docs with latest changes. Docs for releases should be untouched. - export DOCS_VERSION="${{ env.docs_version }}" + # Replace the docs for the current branch or tag with the latest build. + export DOCS_VERSION="${GITHUB_REF_NAME}" rm -rf ${DOCS_VERSION} mkdir ${DOCS_VERSION} cp -r ../_build/html/* ${DOCS_VERSION} diff --git a/.github/workflows/gis_coarse_meshgen.yml b/.github/workflows/gis_coarse_meshgen.yml index 159dbad3e4..328503fa7b 100644 --- a/.github/workflows/gis_coarse_meshgen.yml +++ b/.github/workflows/gis_coarse_meshgen.yml @@ -15,24 +15,51 @@ jobs: gis-coarse-meshgen: name: GIS Coarse Mesh Generation runs-on: ubuntu-latest + defaults: + run: + shell: bash -l {0} steps: - uses: actions/checkout@v6 with: submodules: recursive - path: compass - - name: setup_and_run_step - run: | - # create top level working directory - root=$PWD - pushd compass - # create the compass environment - ./conda/configure_compass_env.py --conda $root/mambaForge --env_name compass_test --mpi mpich + - name: Set up Python 3.14 + uses: actions/setup-python@v6 + with: + python-version: "3.14" + + - name: Cache pixi packages + uses: actions/cache@v5 + env: + # Increase this value to reset cache if deploy inputs have not changed in the workflow + CACHE_NUMBER: 0 + with: + path: | + ~/.cache/rattler/cache + ~/.pixi/bin + key: ${{ runner.os }}-3.14-pixi-${{ env.CACHE_NUMBER }}-${{ hashFiles('deploy.py', 'deploy/**', 'pyproject.toml') }} + + - name: Install compass + run: | + git config --global url."https://github.com/".insteadOf "git@github.com:" + ./deploy.py \ + --no-spack \ + --python 3.14 \ + --recreate + load_script=$(find . -maxdepth 1 -type f -name 'load_compass*.sh' | sort | tail -n 1) + if [ -z "$load_script" ]; then + echo "ERROR: no generated Compass load script found" >&2 + exit 1 + fi + source "$load_script" - # enable the compass environment - source load_compass_test_mpich.sh + - name: Setup and run step + run: | + load_script=$(find . -maxdepth 1 -type f -name 'load_compass*.sh' | sort | tail -n 1) + source "$load_script" - popd + # create top level working directory + root=$PWD # download the input GIS coarse mesh tarball=gis4kmSubSampled_01302025.tgz @@ -71,4 +98,3 @@ jobs: # run the test case cd $testDir compass run - From fcdc966fc75b1330f0b96fbbe7a129a1cbd7608a Mon Sep 17 00:00:00 2001 From: Xylar Asay-Davis Date: Sat, 21 Mar 2026 13:52:10 +0100 Subject: [PATCH 09/22] Update the docs following move to mache.deploy --- docs/developers_guide/building_docs.rst | 13 +- docs/developers_guide/command_line.rst | 14 +- docs/developers_guide/deploying_spack.rst | 584 ++++-------------- docs/developers_guide/machines/anvil.rst | 4 +- docs/developers_guide/machines/chicoma.rst | 2 +- docs/developers_guide/machines/chrysalis.rst | 4 +- docs/developers_guide/machines/compy.rst | 3 +- docs/developers_guide/machines/index.rst | 171 ++--- docs/developers_guide/machines/perlmutter.rst | 2 +- docs/developers_guide/overview.rst | 9 +- docs/developers_guide/quick_start.rst | 431 +++++-------- docs/developers_guide/troubleshooting.rst | 18 +- docs/tutorials/dev_add_rrm.rst | 21 +- docs/tutorials/dev_add_test_group.rst | 24 +- docs/tutorials/dev_porting_legacy.rst | 22 +- 15 files changed, 386 insertions(+), 936 deletions(-) diff --git a/docs/developers_guide/building_docs.rst b/docs/developers_guide/building_docs.rst index a2c78a7f6b..54e637c926 100644 --- a/docs/developers_guide/building_docs.rst +++ b/docs/developers_guide/building_docs.rst @@ -5,8 +5,8 @@ Building the Documentation ************************** As long as you have followed the procedure in :ref:`dev_conda_env` for setting -up your conda environment, you will already have the packages available that -you need to build the documentation. +up your development environment, you will already have the packages available +that you need to build the documentation. Then, run the following script to build the docs: @@ -17,10 +17,11 @@ Then, run the following script to build the docs: rm -rf developers_guide/generated/ developers_guide/*/generated/ _build/ make html -You may need to re-source your compass load script in the root of the compass branch -for the api docs to build successfully if you have added new modules since the load -script was last sourced. The load script will reinstall compass into the conda -environment when it is sourced in the root of the compass branch. +You may need to re-source your compass load script in the root of the compass +branch for the API docs to build successfully if you have added new modules +since the load script was last sourced. The load script will reinstall +``compass`` into the active deployment environment when it is sourced in the +root of the compass branch. You can view the documentation by opening ``_build/html/index.html``. From any machine, you can scp the ``html`` directory to your local computer for diff --git a/docs/developers_guide/command_line.rst b/docs/developers_guide/command_line.rst index 0bdae34c8e..16fb95676e 100644 --- a/docs/developers_guide/command_line.rst +++ b/docs/developers_guide/command_line.rst @@ -8,13 +8,13 @@ scripts: ``compass list``, ``compass setup``, ``compass clean`` and ``compass suite``, and ``compass run``. These are the primary user interface to the package, as described below. -When the ``compass`` package is installed into your conda environment, you can -run these commands as above. If you are developing ``compass`` from a local -branch off of https://github.com/MPAS-Dev/compass, you will need to create a -conda environment appropriate for development (see :ref:`dev_conda_env`). -If you do, ``compass`` will be installed in the environment in "development" -mode, meaning you can make changes to the branch and they will be reflected -when you call the ``compass`` command-line tool. +When the ``compass`` package is installed into your deployment environment, +you can run these commands as above. If you are developing ``compass`` from a +local branch off of https://github.com/MPAS-Dev/compass, you will need to +create a development environment with ``./deploy.py`` (see +:ref:`dev_conda_env`). If you do, ``compass`` will be installed in the +environment in editable mode, meaning you can make changes to the branch and +they will be reflected when you call the ``compass`` command-line tool. .. _dev_compass_list: diff --git a/docs/developers_guide/deploying_spack.rst b/docs/developers_guide/deploying_spack.rst index 95454da3e0..5cc26fdd32 100644 --- a/docs/developers_guide/deploying_spack.rst +++ b/docs/developers_guide/deploying_spack.rst @@ -4,544 +4,200 @@ Deploying a new spack environment ********************************* -Where do we update compass dependencies? -======================================== +Compass now deploys environments through ``./deploy.py``, backed by +``mache.deploy``. Compared with pixi dependency updates, Spack updates are +heavier-weight because the resulting environments are often shared on +supported machines and build times can be substantial. -Mache ------ +Use this page when you need to update or redeploy the shared Spack-backed +Compass stack on supported HPC systems. -If system modules change in E3SM, we try to stay in sync: +What lives where? +================= -* compilers +The main deployment inputs in this repository are: -* MPI libraries +``deploy.py`` + The repository entry point for deployment. -* netcdf-C +``deploy/pins.cfg`` + Version pins for pixi, Spack and shared dependencies. -* netcdf-fortran +``deploy/config.yaml.j2`` + Deployment behavior and runtime settings consumed by ``mache.deploy``. -* pnetcdf +``deploy/spack.yaml.j2`` + The Compass-specific list of Spack specs to deploy. -* mkl (or other linear algebra libs) +``deploy/hooks.py`` + Compass-specific deployment hooks for machine defaults and optional Albany + support. -When we update the mache version in compass, we also need to bump the compass -version (typically either the major or the minor version) and then re-deploy -shared spack environments on each supported machine. +Machine-specific Spack environment templates now live in +`mache `_, not in this repository. -Spack ------ +When do shared Spack environments need to be updated? +===================================================== -Spack is for libraries used by MPAS and tools that need system MPI: +Shared Spack environments typically need to be redeployed when one of the +following changes: -* ESMF +* the pinned versions in ``deploy/pins.cfg`` under ``[spack]`` or ``[all]`` -* SCORPIO +* the requested specs in ``deploy/spack.yaml.j2`` -* Albany +* machine-specific Spack behavior in ``mache`` that affects supported systems -* PETSc +Because these environments are shared, it is usually appropriate to bump the +Compass version before redeploying them. -* Netlib LAPACK +The current automated Compass Spack deployment covers the libraries listed in +``deploy/spack.yaml.j2``. In this branch, that includes packages such as +ESMF, MOAB, SCORPIO and optional Albany/Trilinos support. PETSc and +Netlib-LAPACK are no longer part of the automated ``./deploy.py`` workflow in +this repository. -When we update the versions of any of these libraries in compass, we also need -to bump the compass version (typically either the major or the minor version) -and then re-deploy shared spack environments on each supported machine. +Testing a Spack deployment +========================== -Conda ------ +Before touching any shared location, do a test deployment in a scratch or +user-owned Spack path. -Conda (via conda-forge) is used for python packages and related dependencies -that don’t need system MPI. Conda environments aren’t shared between -developers because the compass you’re developing is part of the conda -environment. - -When we update the constraints on conda dependencies, we also need to bump the -compass alpha, beta or rc version. We do not need to re-deploy spack -environments on share machines because they remain unaffected. - -Mache -===== - -A brief tour of mache. - -Identifying E3SM machines -------------------------- - -Compass and other packages use mache to identify what machine they’re on (when -you’re on a login node). This is used when configuring compass and creating a -conda environment. Because of this, there is a “bootstrapping” process where -a conda environment is created with mache installed in it, then the machine is -identified and the rest of the compass setup can continue. - -Config options describing E3SM machines ---------------------------------------- - -Mache has config files for each E3SM machine that tell us where E3SM-Unified -is, where to find diagnostics, and much more, see -`machines `_. -These config options are shared across packages including: - -* MPAS-Analysis - -* E3SM_Diags - -* zppy - -* compass - -* E3SM-Unified - -Compass uses these config options to know how to make a job script, where to -find locally cached files in its “databases” and much more. - -Modules, env. variables, etc. for E3SM machines ------------------------------------------------- - -Mache keeps its own copy of the E3SM file -`config_machines.xml `_ -in the package `here `_. -We try to keep a close eye on E3SM master and update mache when system modules -for machines that mache knows about get updated. When this happens, we update -mache’s copy of ``config_machines.xml`` and that tells me which modules to -update in spack, see below.dev_quick_start - -Mirroring MOAB on Chicoma -------------------------- - -The firewall on LANL IC's Chicoma blocks access to the MOAB package (at least -at the moment -- Xylar has made a request to allow access). To get around -this, someone testing or deploying spack builds on Chicoma will first need to -update the local spack mirror with the desired version of MOAB (5.5.1 in this -example). - -First, you need to know the versions of the ``mache`` and ``moab`` packages -that are needed (1.20.0 and 5.5.1, respectively, in this example). These are -specified in ``conda/configure_compass_env.py`` and ``conda/default.cfg``, -respectively. On a LANL laptop with either (1) the VPN turned off and the -proxies unset or (2) the VPN turned on and the proxies set, run: - -.. code-block:: bash - - MACHE_VER=1.20.0 - MOAB_VER=5.5.1 - mkdir spack_mirror - cd spack_mirror - git clone git@github.com:E3SM-Project/spack.git -b spack_for_mache_${MACHE_VER} spack_for_mache_${MACHE_VER} - source spack_for_mache_${MACHE_VER}/share/spack/setup-env.sh - - # remove any cache files that might cause trouble - rm -rf ~/.spack - - # this should create spack_mirror with subdirectories moab and _source-cache - spack mirror create -d spack_mirror moab@${MOAB_VER} - - tar cvfj spack_mirror.tar.bz2 spack_mirror +Basic test deployment +--------------------- -Then, if you used option (1) above turn on the LANL VPN (and set the proxies). -You may find it convenient to login on to Chicoma -(e.g. ``ssh -tt wtrw 'ssh ch-fe'``) in a separate terminal if you have -configured your laptop to preserve connections. +On a supported machine, run something like: .. code-block:: bash - rsync -rLpt -e 'ssh wtrw ssh' spack_mirror.tar.bz2 ch-fe:/usr/projects/e3sm/compass/chicoma-cpu/spack/ + SCRATCH_SPACK= + ./deploy.py --deploy-spack --spack-path ${SCRATCH_SPACK} \ + --compiler intel intel gnu --mpi openmpi impi openmpi --recreate +Adjust ``--compiler`` and ``--mpi`` to match the machine you are testing. -Then, on Chicoma: +Useful flags +------------ -.. code-block:: bash +``--deploy-spack`` + Build or update supported Spack environments. - cd /usr/projects/e3sm/compass/chicoma-cpu/spack/ - tar xvf spack_mirror.tar.bz2 - chgrp -R climate spack_mirror/ - chmod -R ug+w spack_mirror/ - chmod -R ugo+rX spack_mirror/ - rm spack_mirror.tar.bz2 +``--spack-path `` + Use a test Spack checkout and install location instead of the shared one. -Creating spack environments ---------------------------- +``--compiler `` and ``--mpi `` + Select the toolchain combinations to test. -Mache has templates for making spack environments on some of the E3SM supported -machines. See `spack `_. -It also has functions for building the spack environments with these templates -using E3SM’s fork of spack (see below). +``--recreate`` + Start from a clean deployment for this run. +``--with-albany`` + Deploy the Albany-enabled Spack environment in addition to the default one. -Updating spack from compass with mache from a remote branch -=========================================================== +If you need a non-default temporary directory for Spack builds, set +``spack.tmpdir`` in ``deploy/config.yaml.j2`` before running deployment. -If you haven’t cloned compass and added my fork, here’s the process: +Testing against a mache branch +------------------------------ -.. code-block:: bash - - mkdir compass - cd compass/ - git clone git@github.com:MPAS-Dev/compass.git main - cd main/ - git remote add xylar/compass git@github.com:xylar/compass.git - -Now, we need to set up compass and build spack packages making use of the -updated mache. This involves changing the mache version in a couple of places -in compass and updating the version of compass itself to a new alpha, beta or -rc. As an example, we will use the branch -`simplify_local_mache `_. - -Often, we will need to test with a ``mache`` branch that has changes needed -by compass. Here, we will use ```` as a stand-in for the fork of mache -to use (e.g. ``E3SM-Project/mache``) and ```` as the stand-in for a branch on -that fork (e.g. ``main``). - -We also need to make sure there is a spack branch for the version of compass. -The spack branch is a branch off of the develop branch on -`E3SM’s spack repo `_ that has any -updates to packages required for this version of mache. The remote branch -is named after the release version of mache (omitting any alpha, beta or rc -suffix because it is intended to be the spack branch we will use once the -``mache`` release happens). In this example, we will work with the branch -`spack_for_mache_1.12.0 `_. -The local clone is instead named after the compass version (again any omitting -alpha, beta or rc) plus the compiler and MPI library because we have discovered -two users cannot make modifications to the same git clone. Giving each clone -of the spack branch a unique name ensures that they are independent. - -Here's how to get a branch of compass we're testing (``simplify_local_mache`` -in this case) as a local worktree: +When Compass changes depend on unreleased ``mache`` updates, test with a fork +and branch explicitly: .. code-block:: bash - # get my branch - git fetch --all -p - # make a worktree for checking out my branch - git worktree add ../simplify_local_mache -b simplify_local_mache \ - --checkout xylar/compass/simplify_local_mache - cd ../simplify_local_mache/ + SCRATCH_SPACK= + ./deploy.py --mache-fork --mache-branch \ + --deploy-spack --spack-path ${SCRATCH_SPACK} \ + --compiler intel intel gnu --mpi openmpi impi openmpi --recreate -You will also need a local installation of -`Miniforge `_. -Compass can do this for you if you haven't already installed it. If you want -to download it manually, use the Linux x86_64 version for all our supported -machines. +This keeps the Compass deployment workflow the same while swapping in the +requested ``mache`` branch during bootstrap and deployment. -.. note:: +Albany deployments +------------------ - We have found that an existing Miniconda3 installation **does not** always - work well for compass, so please start with Miniforge3 instead. - -.. note:: - - You definitely need your own local Miniforge3 installation -- you can’t use - a system version or a shared one like E3SM-Unified. - -Define a location where Miniforge3 is installed or where you want to install -it: +If you are testing MALI workflows that require Albany, add +``--with-albany``: .. code-block:: bash - # change to your conda installation - export CONDA_BASE=${HOME}/miniforge + SCRATCH_SPACK= + ./deploy.py --deploy-spack --spack-path ${SCRATCH_SPACK} \ + --compiler gnu --mpi openmpi --with-albany --recreate -Okay, we're finally ready to do a test spack build for compass. -To do this, we call the ``configure_compass_env.py`` script using -``--mache_fork``, ``--mache_branch``, ``--update_spack``, ``--spack`` and -``--tmpdir``. Here is an example appropriate for Anvil or Chrysalis: +Albany support is restricted to the machine/compiler/MPI combinations allowed +by the deployment hooks and machine configuration. -.. code-block:: bash +Validating the deployment +========================= - export TMPDIR=/lcrc/group/e3sm/${USER}/spack_temp - ./conda/configure_compass_env.py \ - --conda ${CONDA_BASE} \ - --mache_fork \ - --mache_branch \ - --update_spack \ - --spack /lcrc/group/e3sm/${USER}/spack_test \ - --tmpdir ${TMPDIR} \ - --compiler intel intel gnu \ - --mpi openmpi impi openmpi \ - --recreate - -The directory you point to with ``--conda`` either doesn't exist or contains -your existing installation of Miniforge3. - -When you supply ``--mache_fork`` and ``--mache_branch``, compass will clone -a fork of the ``mache`` repo and check out the requested branch, then install -that version of mache into both the compass installation conda environment and -the final compass environment. - -``mache`` gets installed twice because the deployment tools need ``mache`` to -even know how to install compass and build the spack environment on supported -machines. The "prebootstrap" step in deployment is creating the installation -conda environment. The "bootstrap" step is creating the conda environment that -compass will actually use and (in this case with ``--update_spack``) building -spack packages, then creating the "load" or "activation" script that you will -need to build MPAS components and run compass. - -For testing, you want to point to a different location for installing spack -using ``--spack``. - -On many machines, the ``/tmp`` directory is not a safe place to build spack -packages. Use ``--tmpdir`` to point to another place, e.g., your scratch -space. - -The ``--recreate`` flag may not be strictly necessary but it’s a good idea. -This will make sure both the bootstrapping conda environment (the one that -installs mache to identify the machine) and the compass conda environment are -created fresh. - -The ``--compiler`` flag is a list of one or more compilers to build for and the -``--mpi`` flag is the corresponding list of MPI libraries. To see what is -supported on each machine, take a look at :ref:`dev_supported_machines`. - -Be aware that not all compilers and MPI libraries support Albany and PETSc, as -discussed below. - -Testing spack with PETSc (and Netlib LAPACK) --------------------------------------------- - -If you want to build PETSc (and Netlib LAPACK), use the ``--with_petsc`` flag. -Currently, this only works with some -compilers, but that may be more that I was trying to limit the amount of work -for the compass support team. There is a file, -`petsc_supported.txt `_, -that lists supported compilers and MPI libraries on each machine. - -Here is an example: +After deployment completes: -.. code-block:: bash +* source the generated load script for the toolchain you want to validate - export TMPDIR=/lcrc/group/e3sm/${USER}/spack_temp - ./conda/configure_compass_env.py \ - --conda ${CONDA_BASE} \ - --mache_fork \ - --mache_branch \ - --update_spack \ - --spack /lcrc/group/e3sm/${USER}/spack_test \ - --tmpdir ${TMPDIR} \ - --compiler intel gnu \ - --mpi openmpi \ - --with_petsc \ - --recreate \ - --verbose - -Testing spack with Albany -------------------------- - -If you also want to build Albany, use the ``--with_albany`` flag. Currently, -this only works with Gnu compilers. There is a file, -`albany_support.txt `_, -that lists supported compilers and MPI libraries on each machine. - -Here is an example: +* build the appropriate MPAS component with the matching make target -.. code-block:: bash +* set up and run the relevant Compass suites or test cases - export TMPDIR=/lcrc/group/e3sm/${USER}/spack_temp - ./conda/configure_compass_env.py \ - --conda ${CONDA_BASE} \ - --mache_fork \ - --mache_branch \ - --update_spack \ - --spack /lcrc/group/e3sm/${USER}/spack_test \ - --tmpdir ${TMPDIR} \ - --compiler gnu \ - --mpi openmpi \ - --with_albany \ - --recreate - -Troubleshooting spack ---------------------- +* confirm the generated work directories contain ``load_compass_env.sh`` and + that sourcing it activates the intended environment -If you encounter an error like: -.. code-block:: none +The :ref:`dev_quick_start` page and the machine pages under +:ref:`dev_supported_machines` remain the main references for build targets and +day-to-day usage once deployment has succeeded. - ==> spack env activate dev_compass_1_2_0-alpha_6_gnu_mpich - ==> Error: Package 'armpl' not found. - You may need to run 'spack clean -m'. +Troubleshooting Spack deployment +================================ -during the attempt to build spack, you will first need to find the path to -``setup-env.sh`` (see ``compass/build_*/build*.sh``) and source that script to -get the ``spack`` command, e.g.: - -.. code-block:: bash - - source ${PSCRATCH}/spack_test/dev_compass_1_2_0-alpha_6_gnu_mpich/share/spack/setup-env.sh - -Then run the suggested command: +If a Spack deployment fails part way through and suggests clearing caches, the +usual recovery step is: .. code-block:: bash + source /share/spack/setup-env.sh spack clean -m -After that, re-running ``./conda/configure_compass_env.py`` should work correctly. - -This issue seems to be related to switching between spack v0.18 and v0.19 (used by different versions of compass). +Then rerun ``./deploy.py`` with the same arguments. -Testing compass -=============== - -Testing MPAS-Ocean without PETSc --------------------------------- - -Please use the E3SM-Project submodule in compass for testing, rather than -E3SM’s master branch. The submodule is the version we know works with compass -and serves as kind of a baseline for other testing. +If deployment is failing because the wrong architecture or operating system is +being inferred for a new machine template, use: .. code-block:: bash - # source whichever load script is appropriate - source load_dev_compass_1.2.0-alpha.5_chrysalis_intel_openmpi.sh - git submodule update --init --recursive - cd E3SM-Project/components/mpas-ocean - # this will build with PIO and OpenMP - make ifort - compass suite -s -c ocean -t pr -p . \ - -w /lcrc/group/e3sm/ac.xylar/compass/test_20230202/ocean_pr_chrys_intel_openmpi - cd /lcrc/group/e3sm/ac.xylar/compass/test_20230202/ocean_pr_chrys_intel_openmpi - sbatch job_script.pr.bash - -You can make other worktrees of E3SM-Project for testing other compilers if -that’s helpful. It also might be good to open a fresh terminal to source a -new load script. This isn’t required but you’ll get some warnings. + source /share/spack/setup-env.sh + spack arch -o + spack arch -g -.. code-block:: bash +and update the corresponding machine support in ``mache``. - source load_dev_compass_1.2.0-alpha.5_chrysalis_gnu_openmpi.sh - cd E3SM-Project - git worktree add ../e3sm_chrys_gnu_openmpi - cd ../e3sm_chrys_gnu_openmpi - git submodule update --init --recursive - cd components/mpas-ocean - make gfortran - compass suite -s -c ocean -t pr -p . \ - -w /lcrc/group/e3sm/ac.xylar/compass/test_20230202/ocean_pr_chrys_gnu_openmpi - cd /lcrc/group/e3sm/ac.xylar/compass/test_20230202/ocean_pr_chrys_gnu_openmpi - sbatch job_script.pr.bash - -You can also explore the utility in -`utils/matrix `_ to -test on several compilers automatically. - -Testing MALI with Albany ------------------------- - -Please use the MALI-Dev submodule in compass for testing, rather than MALI-Dev -develop branch. The submodule is the version we know works with compass and -serves as kind of a baseline for other testing. +If a machine requires a local mirror or site-specific workaround for one of the +Spack packages, coordinate that change with the machine maintainers and keep +the Compass pins in ``deploy/pins.cfg`` aligned with whatever is available at +the site. -.. code-block:: bash - - # source whichever load script is appropriate - source load_dev_compass_1.2.0-alpha.5_chrysalis_gnu_openmpi_albany.sh - git submodule update --init --recursive - cd MALI-Dev/components/mpas-albany-landice - # you need to tell it to build with Albany - make ALBANY=true gfortran - compass suite -s -c landice -t full_integration -p . \ - -w /lcrc/group/e3sm/ac.xylar/compass/test_20230202/landice_full_chrys_gnu_openmpi - cd /lcrc/group/e3sm/ac.xylar/compass/test_20230202/landice_full_chrys_gnu_openmpi - sbatch job_script.full_integration.bash - -Testing MPAS-Ocean with PETSc ------------------------------ - -The tests for PETSc use nonhydrostatic capabilities not yet integrated into -E3SM. So you can’t use the E3SM-Project submodule. You need to use Sara -Calandrini’s `nonhydro `_ -branch. - -.. code-block:: bash - - # source whichever load script is appropriate - source load_dev_compass_1.2.0-alpha.5_chrysalis_intel_openmpi_petsc.sh - git submodule update --init - cd E3SM-Project - git remote add scalandr/E3SM git@github.com:scalandr/E3SM.git - git worktree add ../nonhydro_chrys_intel_openmpi -b nonhydro_chrys_intel_openmpi \ - --checkout scalandr/E3SM/ocean/nonhydro - cd ../nonhydro_chrys_intel_openmpi - git submodule update --init --recursive - cd components/mpas-ocean - # this will build with PIO, Netlib LAPACK and PETSc - make ifort - compass list | grep nonhydro - # update these numbers for the 2 nonhydro test cases - compass setup -n 245 246 -p . \ - -w /lcrc/group/e3sm/ac.xylar/compass/test_20230202/nonhydro_chrys_intel_openmpi - cd /lcrc/group/e3sm/ac.xylar/compass/test_20230202/nonhydro_chrys_intel_openmpi - sbatch job_script.custom.bash - -As with non-PETSc MPAS-Ocean and MALI, you can have different worktrees with -Sara’s nonhydro branch for building with different compilers or use -`utils/matrix `_ to -build (and run). - -Deploying shared spack environments +Deploying shared Spack environments =================================== .. note:: - Be careful about deploying shared spack environments, as changes you make - can affect other compass users. - -Once compass has been tested with the spack builds in a temporary location, it -is time to deploy the shared spack environments for all developers to use. -A ``mache`` developer will make a ``mache`` release (if needed) before this -step begins. So there is no need to build mache from a remote branch anymore. - -Compass knows where to deploy spack on each machine because of the ``spack`` -config option specified in the ``[deploy]`` section of each machine's config -file, see the `machine configs `_. + Be careful about deploying shared Spack environments, because changes you + make can affect other Compass users on that machine. -It is best to update the remote compass branch in case of changes: +Once test deployments and validation passes are complete, deploy to the shared +location by omitting ``--spack-path`` and using the machine defaults from the +``[deploy]`` section of the relevant machine config: .. code-block:: bash - cd simplify_local_mache - # get any changes - git fetch --all -p - # hard reset if there are changes - git reset –hard xylar/compass/simplify_local_mache + ./deploy.py --deploy-spack \ + --compiler intel intel gnu --mpi openmpi impi openmpi --recreate -Deploy spack for compass without Albany or PETSc ------------------------------------------------- +For an Albany-enabled shared deployment, run: .. code-block:: bash - export TMPDIR=/lcrc/group/e3sm/${USER}/spack_temp - ./conda/configure_compass_env.py \ - --conda ${CONDA_BASE} \ - --update_spack \ - --tmpdir ${TMPDIR} \ - --compiler intel intel gnu \ - --mpi openmpi impi openmpi \ - --recreate - -Deploying spack with Albany ---------------------------- - -.. code-block:: bash - - export TMPDIR=/lcrc/group/e3sm/${USER}/spack_temp - ./conda/configure_compass_env.py \ - --conda ${CONDA_BASE} \ - --update_spack \ - --tmpdir ${TMPDIR} \ - --compiler gnu \ - --mpi openmpi \ - --with_albany \ - --recreate - -Deploying spack with PETSc (and Netlib LAPACK) ----------------------------------------------- - -.. code-block:: bash + ./deploy.py --deploy-spack \ + --compiler gnu --mpi openmpi --with-albany --recreate - export TMPDIR=/lcrc/group/e3sm/${USER}/spack_temp - ./conda/configure_compass_env.py \ - --conda ${CONDA_BASE} \ - --update_spack \ - --tmpdir ${TMPDIR} \ - --compiler intel gnu \ - --mpi openmpi \ - --with_petsc \ - --recreate \ - --verbose +Do this only after the equivalent test deployments have succeeded in a private +Spack path. diff --git a/docs/developers_guide/machines/anvil.rst b/docs/developers_guide/machines/anvil.rst index 9db37cdb76..0665edd848 100644 --- a/docs/developers_guide/machines/anvil.rst +++ b/docs/developers_guide/machines/anvil.rst @@ -11,7 +11,7 @@ been set up properly (see :ref:`dev_conda_env`), you should be able to source: .. code-block:: bash - source load_dev_compass_1.0.0_anvil_intel_impi.sh + source load_compass_anvil_intel_impi.sh Then, you can build the MPAS model with @@ -32,7 +32,7 @@ If you've set things up for this compiler, you should be able to: .. code-block:: bash - source load_dev_compass_1.0.0_anvil_gnu_openmpi.sh + source load_compass_anvil_gnu_openmpi.sh Then, you can build the MPAS model with diff --git a/docs/developers_guide/machines/chicoma.rst b/docs/developers_guide/machines/chicoma.rst index 69d4c7139e..e8e45b1f5c 100644 --- a/docs/developers_guide/machines/chicoma.rst +++ b/docs/developers_guide/machines/chicoma.rst @@ -12,7 +12,7 @@ script similar to: .. code-block:: bash - source load_dev_compass_1.2.0-alpha.4_chicoma-cpu_gnu_mpich.sh + source load_compass_chicoma-cpu_gnu_mpich.sh Then, you can build the MPAS model with diff --git a/docs/developers_guide/machines/chrysalis.rst b/docs/developers_guide/machines/chrysalis.rst index 388a32ebc0..31f76f3788 100644 --- a/docs/developers_guide/machines/chrysalis.rst +++ b/docs/developers_guide/machines/chrysalis.rst @@ -11,7 +11,7 @@ been set up properly (see :ref:`dev_conda_env`), you should be able to source: .. code-block:: bash - source load_dev_compass_1.0.0_chrysalis_intel_openmpi.sh + source load_compass_chrysalis_intel_openmpi.sh Then, you can build the MPAS model with @@ -26,7 +26,7 @@ If you've set things up for this compiler, you should be able to: .. code-block:: bash - source load_dev_compass_1.0.0_chrysalis_gnu_openmpi.sh + source load_compass_chrysalis_gnu_openmpi.sh Then, you can build the MPAS model with diff --git a/docs/developers_guide/machines/compy.rst b/docs/developers_guide/machines/compy.rst index 58f4312332..69758e80fa 100644 --- a/docs/developers_guide/machines/compy.rst +++ b/docs/developers_guide/machines/compy.rst @@ -15,11 +15,10 @@ able to source: .. code-block:: bash - source load_dev_compass_1.0.0_compy_intel_impi.sh + source load_compass_compy_intel_impi.sh Then, you can build the MPAS model with .. code-block:: bash make [DEBUG=true] [OPENMP=true] intel-mpi - diff --git a/docs/developers_guide/machines/index.rst b/docs/developers_guide/machines/index.rst index c84ec310b7..934e928411 100644 --- a/docs/developers_guide/machines/index.rst +++ b/docs/developers_guide/machines/index.rst @@ -16,23 +16,21 @@ cases are configured in a way that is appropriate for your machine. Supported Machines ------------------ -If you follow the procedure in :ref:`dev_conda_env`, you will have an -activation script for activating the development conda environment, setting -loading system modules and setting environment variables so you can build -MPAS and work with ``compass``. Just source the script that should appear in -the base of your compass branch, e.g.: +If you follow the procedure in :ref:`dev_conda_env`, you will have one or more +generated load scripts in the root of your compass branch. Source the one +that matches the machine, compiler and MPI library you want to use, e.g.: .. code-block:: bash - source load_dev_compass_1.0.0_anvil_intel_impi.sh + source load_compass_anvil_intel_impi.sh After loading this environment, you can set up test cases or test suites, and a link ``load_compass_env.sh`` will be included in each suite or test case work directory. This is a link to the activation script that you sourced when you were setting things up. You can can source this file on a compute node -(e.g. in a job script) to get the right compass conda environment, compilers, -MPI libraries and environment variables for running ``compass`` tests and -the MPAS model. +(e.g. in a job script) to get the right compass deployment environment, +compilers, MPI libraries and environment variables for running ``compass`` +tests and the MPAS model. .. note:: @@ -79,28 +77,27 @@ Below are specifics for each supported machine Other Machines -------------- -If you are working on an "unknown" machine, the procedure is pretty similar -to what was described in :ref:`dev_conda_env`. The main difference is that -we will use ``mpich`` or ``openmpi`` and the gnu compilers from conda-forge -rather than system compilers. To create a development conda environment and -an activation script for it, on Linux, run: +If you are working on an unknown machine, the procedure is pretty similar to +what was described in :ref:`dev_conda_env`. In general, use ``./deploy.py`` +to create a local pixi environment and load scripts. For example, on Linux +run: .. code-block:: bash - ./conda/configure_compass_env.py --conda -c gnu -i mpich + ./deploy.py --no-spack --compiler gnu --mpi mpich and on OSX run: .. code-block:: bash - ./conda/configure_compass_env.py --conda -c clang -i mpich + ./deploy.py --no-spack --compiler clang --mpi mpich You may use ``openmpi`` instead of ``mpich`` but we have had better experiences with the latter. -The result should be an activation script ``load_dev_compass_1.0.0_.sh``. -Source this script to get the appropriate conda environment and environment -variables. +The result should be one or more generated load scripts. Source the one that +matches the environment you want to use to get the appropriate deployment +environment and environment variables. Under Linux, you can build the MPAS model with @@ -119,13 +116,13 @@ Under OSX, you can build the MPAS model with Adding a New Supported Machine ------------------------------ -If you want to add a new supported machine, you need to add both a config file -and a yaml file describing your machine, as detailed below. +If you want to add a new supported machine, you need to add a config file in +``compass`` and corresponding machine support in ``mache``. Adding a Machine Config File ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -The first step in adding a new supported machine to add a config file in +The first step in adding a new supported machine is to add a config file in ``compass/machines``. The config file needs to describe the parallel environment and some paths where shared Spack environments will be installed and shared data will be downloaded. The easiest place to start is one of the @@ -166,12 +163,11 @@ will be added soon.) # A shared root directory where MPAS standalone data can be found database_root = /home/xylar/data/mpas/mpas_standalonedata - # the path to the base conda environment where compass environments have - # been created + # the path where deployed compass environments are located compass_envs = /home/xylar/data/mpas/compass_envs - # Options related to deploying a compass conda environment on supported + # Options related to deploying compass environments on supported # machines [deploy] @@ -216,11 +212,11 @@ libraries aren't being found when you try to build an MPAS component. In the ``[paths]`` section, you will first give a path where you would like to store shared data files used in compass test cases in ``database_root``. Compass will create this directory if it doesn't exist. Then, you can specify -``compass_envs`` as a path where shared conda environments will be installed -for compass releases. If developers always create their own conda +``compass_envs`` as a path where shared deployment environments will be +installed for compass releases. If developers always create their own local environments, this path will never be used. -In ``[deploy]``, you will specify config options used in setting up conda +In ``[deploy]``, you will specify config options used in setting up deployment and Spack environments for developers. The ``compiler`` is the default compiler to use for your system. You must supply a corresponding ``mpi_`` for each supported compiler (not just the default compiler) @@ -238,122 +234,44 @@ to automatically identify your machine based on its hostname. If your machine has multiple login nodes with different hostnames, hopefully, a string common to all login nodes can be used here. If your machine has a unique hostname, simply give that. This option saves developers from having to specify -``--machine `` each time they setup compass environments or test -cases. +``--machine `` each time they deploy compass environments or set up +test cases. Describing a Spack Environment ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -The next step is to create a template YAML file that can be used to create -`Spack environments `_ -for your machine. Compass uses Spack environments to build packages that need -MPI support or which should be build for some other reason with system -compilers rather than coming from pre-built conda packages. Using a Spack -environment allows these packages to be built together in a consistent way that -is not guaranteed if you try to install dependencies one-by-one. In Spack -parlance, this is known as -`unified concretization `_. +Compass no longer carries machine-specific Spack environment templates inside +this repository. Those templates now live in ``mache`` and are consumed by +``mache.deploy`` during ``./deploy.py`` runs. -To do this, you will create a file ``conda/spack/__.yaml`` -similar to the following example for an Ubuntu laptop: +When adding support for a new machine, the usual split is: -.. code-block:: +* add the Compass machine config in ``compass/machines`` - spack: - specs: - - gcc - - openmpi - {{ specs }} - concretizer: - unify: true - packages: - all: - compiler: [gcc@11.3.0] - providers: - mpi: [openmpi] - curl: - externals: - - spec: curl@7.81.0 - prefix: /usr - buildable: false - gcc: - externals: - - spec: gcc@11.3.0 - prefix: /usr - buildable: false - config: - install_missing_compilers: false - compilers: - - compiler: - spec: gcc@11.3.0 - paths: - cc: /usr/bin/gcc - cxx: /usr/bin/g++ - f77: /usr/bin/gfortran - fc: /usr/bin/gfortran - flags: {} - operating_system: ubuntu22.04 - target: x86_64 - modules: [] - environment: {} - extra_rpaths: [] - - -Typically your system will already have compilers if nothing else, and this is -what we assume here. Give the appropriate path (replace ``/usr`` with the -appropriate path on your system). We have had better luck with ``gcc`` than -other compilers like Intel so far so for new supported machines so that's our -recommendation. Use ``gcc --version`` to determine the version and replace -``11.3.0`` with this number. - -Finally, you might need to update the ``target`` and ``operating_system``. -This is a bit of a "catch 22" in that you can use Spack to find this out but -compass is designed to clone and set up Spack for you so we assume you don't -have it yet. For now, make your best guess using the info on -`this page `_ -and correct it later if necessary. - -You may need to load a system module to get the compilers and potentially other -libraries such as MPI, HDF5, and NetCDF-C if you prefer to use system modules -rather than having Spack build them. If this is the case, the best way to do -this is to add a file -``conda/spack/__.sh`` along these lines: +* add or update the corresponding machine-specific Spack template in + ``mache`` -.. code-block:: bash +* keep the Compass package list in ``deploy/spack.yaml.j2`` in sync with the + capabilities of that ``mache`` template - module purge - module load perl/5.32.0-bsnc6lt - module load gcc/9.2.0-ugetvbp - module load openmpi/4.1.3-sxfyy4k - module load intel-mkl/2020.4.304-n3b5fye - module load hdf5/1.10.7-j3zxncu - module load netcdf-c/4.4.1-7ohuiwq - module load netcdf-fortran/4.4.4-k2zu3y5 - module load parallel-netcdf/1.11.0-mirrcz7 - -These modules will be loaded either before or after the spack environment, -depending on the ``modules_before`` and ``modules_after`` config options above. -You can also add modules in your YAML file but this shouldn't be necessary. - -For examples from various supported machines, compilers and MPI libraries, see the +For examples from supported machines, compilers and MPI libraries, see the `mache spack directory `_. Building the Spack Environment ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -The next step is to try setting up compass and asking it to build the Spack +The next step is to try setting up Compass and asking it to build the Spack environment with a command something like: .. code-block:: bash - ./conda/configure_compass_env.py --verbose --update_spack --conda -c gnu -i openmpi ... + ./deploy.py --deploy-spack --compiler gnu --mpi openmpi --recreate -The ``--update_spack`` flag tells compass to create (or update) a Spack -environment. You can specify a directory for testing Spack with the -``--spack`` flag. You can specify a temporary directory for building spack -packages with ``--tmpdir`` (this directory must already exist). This is useful -if your ``/tmp`` space is small (Spack will use several GB of temporary space). +The ``--deploy-spack`` flag tells Compass to create or update supported Spack +environments. You can specify a directory for testing Spack with the +``--spack-path`` flag. If you need a custom temporary directory for Spack +builds, set ``spack.tmpdir`` in ``deploy/config.yaml.j2``. Creating the Spack environment may take anywhere from minutes to hours, @@ -383,9 +301,8 @@ You can run: spack arch -o spack arch -g -where ``$SPACKDIR`` is the directory where the Spack repository was cloned -by compass (you should see ``Cloning into <$SPACKDIR>`` in the terminal, which -will hopefully help you find the right directory). This should hopefully give +where ``$SPACKDIR`` is the directory where the Spack repository was cloned by +deployment. This should hopefully give you something close to what Spack wants. If you get something like ``x86_64_v4`` for the target, use ``x86_64`` instead. diff --git a/docs/developers_guide/machines/perlmutter.rst b/docs/developers_guide/machines/perlmutter.rst index a18818bb29..e3cd850a55 100644 --- a/docs/developers_guide/machines/perlmutter.rst +++ b/docs/developers_guide/machines/perlmutter.rst @@ -12,7 +12,7 @@ script similar to: .. code-block:: bash - source load_dev_compass_1.2.0-alpha.2_pm-cpu_gnu_mpich.sh + source load_compass_pm-cpu_gnu_mpich.sh Then, you can build the MPAS model with diff --git a/docs/developers_guide/overview.rst b/docs/developers_guide/overview.rst index d954f49c91..7743b42dd7 100644 --- a/docs/developers_guide/overview.rst +++ b/docs/developers_guide/overview.rst @@ -37,10 +37,11 @@ reformatting your code (e.g. with `autopep8 because this can often produce undesirable and confusing results. The `flake8 `_ utility for linting python -files to the PEP8 standard is included in the COMPASS conda environment. To use -flake8, just run ``flake8`` from any directory and it will return lint results -for all files recursively through all subdirectories. You can also run it for a -single file or using wildcards (e.g., ``flake8 *.py``). There also is a +files to the PEP8 standard is included in the COMPASS development environment. +To use flake8, just run ``flake8`` from any directory and it will return lint +results for all files recursively through all subdirectories. You can also +run it for a single file or using wildcards (e.g., ``flake8 *.py``). There +also is a `vim plugin `_ that runs the flake8 linter from within vim. If you are not using an IDE that lints automatically, it is recommended you run flake8 from the command line or the vim plugin before diff --git a/docs/developers_guide/quick_start.rst b/docs/developers_guide/quick_start.rst index ede95cc8c6..49da211e3f 100644 --- a/docs/developers_guide/quick_start.rst +++ b/docs/developers_guide/quick_start.rst @@ -34,51 +34,26 @@ the compass repository. .. _dev_conda_env: -compass conda environment, compilers and system modules -------------------------------------------------------- - -As a developer, you will need your own environment with the latest dependencies -for compass and a development installation of ``compass`` from the branch -you're working on. - -The ``conda`` directory in the repository has a tool ``configure_compass_env.py`` -that can get you started. - -You will need to run ``./conda/configure_compass_env.py`` each time you check -out a new branch or create a new worktree with ``git``. Typically, you will -*not* need to run this command when you make changes to files within the -``compass`` python package. These will automatically be recognized because -``compass`` is installed into the conda environment in "editable" mode. You -*will* need to run the command if you add new code files or data files to the -package because these don't get added automatically. - -Whether you are on one of the :ref:`dev_supported_machines` or an "unknown" -machine, you will need to specify a path where -`Miniforge3 `_ either has -already been installed or an empty directory where the script can install it. -You must have write permission in the base environment (if it exists). +compass pixi and Spack environments, compilers and system modules +----------------------------------------------------------------- -.. note:: - - We have found that an existing Miniconda3 installation **does not** always - work well for ``compass``, so please start with Miniforge3 instead. +As a developer, you will need your own deployment environment with the latest +dependencies for compass and a development installation of ``compass`` from +the branch you're working on. -.. note:: +Compass now uses ``mache.deploy`` for deployment. In this repository, the +entry point is ``./deploy.py``. - It is *very* important that you not use a shared installation of Miniforge3 - or Miniconda3 such as the base environment for E3SM-Unified for ``compass`` - development. Most developers will not have write access to shared - environments, meaning that you will get write-permission errors when you - try to update the base environment or create the compass development - environment. +You will typically rerun ``./deploy.py`` each time you check out a new branch +or create a new worktree with ``git``. In most cases, you do not need to +rerun deployment while you are editing existing files in the ``compass`` +package because ``compass`` is installed in editable mode. - For anyone who does have write permission to a shared environment, you - would be creating your compass development environment in a shared space, - which could cause confusion. +.. note:: - Please use your own personal installation of Miniforge3 for development, - letting ``configure_compass_env.py`` download and install Miniforge3 for - you if you don't already have it installed. + ``./deploy.py`` expects ``pixi`` to be available either on ``PATH`` or at + ``~/.pixi/bin/pixi``. If your ``pixi`` executable lives somewhere else, + pass it explicitly with ``--pixi ``. Supported machines ~~~~~~~~~~~~~~~~~~ @@ -87,199 +62,115 @@ If you are on one of the :ref:`dev_supported_machines`, run: .. code-block:: bash - ./conda/configure_compass_env.py --conda \ - -c [--mpi ] [-m ] [--with_albany] \ - [--with_petsc] + ./deploy.py [--machine ] [--compiler ...] \ + [--mpi ...] [--deploy-spack] [--no-spack] \ + [--prefix ] [--recreate] [--with-albany] -The ```` is typically ``~/miniforge3``. -This is the location where you would like to install Miniforge3 or where it is -already installed. If you have limited space in your home directory, you may -want to give another path. If you already have it installed, that path will -be used to add (or update) the compass test environment. +If you are on a login node, machine detection typically works automatically. +You can pass ``--machine `` explicitly if needed. -See the machine under :ref:`dev_supported_machines` for a list of available -compilers to pass to ``-c``. If you don't supply a compiler, you will get -the default one for that machine (usually Intel). Typically, you will want the -default MPI flavor that compass has defined for each compiler, so you should -not need to specify which MPI version to use but you may do so with ``--mpi`` -if you need to. - -If you are on a login node, the script should automatically recognize what -machine you are on. You can supply the machine name with ``-m `` if -you run into trouble with the automatic recognition (e.g. if you're setting -up the environment on a compute node, which is not recommended). +By default, Compass will reuse existing machine-specific Spack environments +when the current deployment needs them. Use ``--deploy-spack`` when you want +to build or update those Spack environments. Use ``--no-spack`` for a +pixi-only deployment. Environments with Albany ~~~~~~~~~~~~~~~~~~~~~~~~ -If you are working with MALI, you should specify ``--with_albany``. This will -ensure that the Albany and Trilinos libraries are included among those built -with system compilers and MPI libraries, a requirement for many MAlI test -cases. Currently, only Albany is only supported with ``gnu`` compilers. +If you are working with MALI, use ``--with-albany`` so the Albany and +Trilinos libraries are included in the deployed Spack environment. Albany is +currently only supported for some machine/compiler/MPI combinations, most +commonly ``gnu`` builds on supported machines. -It is safe to add the ``--with_albany`` flag for MPAS-Ocean but it is not -recommended unless a user wants to be able to run both models with the same -conda/spack environment. The main downside is simply that unneeded libraries -will be linked in to MPAS-Ocean. +Unknown machines +~~~~~~~~~~~~~~~~ -Environments with PETSc and Netlib-LAPACK -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +If a machine is not known to ``mache``, add machine support first +(see :ref:`dev_add_supported_machine`). -If you are working with MPAS-Ocean test cases that need PETSc and -Netlib-LAPACK, you should specify ``--with_petsc`` to -point to Spack environments where these libraries are included. Appropriate -environment variables for pointing to these libraries will be build into the -resulting load script (see below). +For workflows that need custom machine config files, see :ref:`config_files`. -Unknown machines -~~~~~~~~~~~~~~~~ +What the script does +~~~~~~~~~~~~~~~~~~~~ -If your are on an "unknown" machine, typically a Mac or Linux laptop or -workstation, you will need to specify which flavor of MPI you want to use -(``mpich`` or ``openmpi``): +``./deploy.py`` can: -.. code-block:: bash +* create or update a local pixi deployment prefix (``pixi-env`` by default) - ./conda/configure_compass_env.py --conda --mpi +* install `Jigsaw `_ and + `Jigsaw-Python `_ from the + ``jigsaw-python`` submodule when needed -Again, the ```` is typically ``~/miniforge3``, and is the location -where you would like to install Miniforge3 or where it is already installed. -If you already have it installed, that path will be used to add (or update) the -compass test environment. +* install the ``compass`` package from the local branch in editable mode so + changes you make to the repo are reflected immediately -We only support one set of compilers for Mac and Linux (``gnu`` for Linux and -``clang`` with ``gfortran`` for Mac), so there is no need to specify them. -See :ref:`dev_other_machines` for more details. +* optionally deploy or reuse Spack environments for selected compiler/MPI + toolchains -In addition, unknown machines require a config file to be specified when setting -up the compass test environment. A config file can be specified using -``-f ``, where ```` is an absolute or relative path to the -file. More information, including example config files, can be found -in :ref:`config_files`. +* generate activation scripts (``load_*.sh``) -.. note:: +Useful flags +~~~~~~~~~~~~ - Currently, there is not a good way to build Albany for an unknown machine as - part of the compass deployment process, meaning MALI will be limited to the - shallow-ice approximation (SIA) solver. +``--machine`` + Set the machine explicitly instead of relying on automatic detection - To get started on HPC systems that aren't supported by Compass, get in touch - with the developers. +``--prefix`` + Choose the deployment prefix for the pixi environment -What the script does -~~~~~~~~~~~~~~~~~~~~ +``--compiler``, ``--mpi`` + Select compiler/MPI combinations, primarily for Spack deployment -In addition to installing Miniforge3 and creating the conda environment for -you, this script will also: +``--deploy-spack`` + Deploy supported Spack environments instead of only reusing existing ones -* install `Jigsaw `_ and - `Jigsaw-Python `_ from source - from the `jigsaw-python` submodule. These tools are used to create many of - the meshes used in Compass. - -* install the ``compass`` package from the local branch in "development" mode - so changes you make to the repo are immediately reflected in the conda - environment. - -* with the ``--update_spack`` flag on supported machines, installs or - reinstalls a spack environment with various system libraries. The - ``--spack`` flag can be used to point to a location for the spack repo to be - checked out. Without this flag, a default location is used. Spack is used to - build several libraries with system compilers and MPI library, including: - `ParallelIO `_ (parallel i/o in NetCDF - format) `ESMF `_ (making mapping files - in parallel), `Trilinos `_, - `Albany `_, - `Netlib-LAPACK `_ and - `PETSc `_. **Please uses these flags with caution, as - they can affect shared environments!** See :ref:`dev_deploying_spack`. - -* with the ``--with_albany`` flag, creates or uses an existing Spack - environment that includes Albany and Trilinos. - -* with the ``--with_petsc`` flag, creates or uses an - existing Spack environment that includes PETSc and Netlib-LAPACK. - -* make an activation script called ``load_*.sh``, where the details of the - name encode the conda environment name, the machine, compilers, MPI - libraries, and optional libraries, e.g. - ``load_dev_compass____.sh`` (```` - is the compass version, ```` is the name of the - machine, ```` is the compiler name, and ``mpi`` is the MPI flavor). - -* optionally (with the ``--check`` flag), run some tests to make sure some of - the expected packages are available. - -Optional flags -~~~~~~~~~~~~~~ - -``--check`` - Check to make sure expected commands are present - -``--python`` - Select a particular python version (the default is currently 3.13) - -``--env_name`` - Set the name of the environment (and the prefix for the activation script) - to something other than the default (``dev_compass_`` or - ``dev_compass__``). - -``--update_jigsaw`` - Used to reinstall Jigsaw and Jigsaw-Python into the conda environment if - you have made changes to the Jigsaw (c++) code in the ``jigsaw-python`` - submodule. You should not need to reinstall Jigsaw-Python if you have made - changes only to the python code in ``jigsaw-python``, as the python package - is installed in - `edit mode `_. +``--no-spack`` + Disable all Spack use for this run and rely on pixi dependencies instead + +``--spack-path`` + Set the Spack checkout path used for deployment + +``--recreate`` + Recreate deployment artifacts if they already exist + +``--bootstrap-only`` + Update only the bootstrap pixi environment used internally by deployment + +``--mache-fork``, ``--mache-branch``, ``--mache-version`` + Test deployment against a specific mache fork, branch or version Activating the environment ~~~~~~~~~~~~~~~~~~~~~~~~~~ -Each time you want to work with compass, you will need to run: +Each time you want to work with compass, source one of the generated load +scripts: .. code-block:: bash - source ./load_dev_compass____.sh - -This will load the appropriate conda environment, load system modules for -compilers, MPI and libraries needed to build and run MPAS components, and -set environment variables needed for MPAS or ``compass``. It will also set an -environment variable ``LOAD_COMPASS_ENV`` that points to the activation script. -``compass`` uses this to make an symlink to the activation script called -``load_compass_env.sh`` in the work directory. When the load script is -executed from the base of the compass repository (i.e., as -``source ./load_dev_compass____.sh``), -it will install the version of the ``compass`` package from that location into the associated -conda environment. When the load script is executed from the work -directory through the symlink, it will activate the associated conda -environment, but does *not* install the ``compass`` package into the conda -environment; it is assumed that is already up to date from when the conda -environment was created or last updated. - -It is generally recommended to activate the ``compass`` environment (from -either the compass repo or via the workdir symlink) from a -clean environment. Unexpected behavior may occur if activating a different -``compass`` environment after having one already activated. - -Once you have sourced the activation script, you can run ``compass`` commands -anywhere, and it always refers to that branch. To find out which branch you -are actually running ``compass`` from, you should run: + source ./load_*.sh -.. code-block:: bash +This activates the deployment environment, loads machine modules when +appropriate, and sets environment variables needed by ``compass`` and MPAS +components. - echo $LOAD_COMPASS_ENV +When you are working inside a suite or test-case work directory, source +``load_compass_env.sh`` instead. This is a symlink to the load script you +used while setting up the work directory. -This will give you the path to the load script, which will also tell you where -the branch is. If you do not use the worktree approach, you will also need to -check what branch you are currently on with ``git log``, ``git branch`` or -a similar command. +When a generated load script is sourced from the root of the compass +repository, it reinstalls the version of ``compass`` from that location into +the active deployment environment. This is what lets one deployment prefix be +shared across several branches or worktrees, as long as you re-source the load +script in the repo you want to work from. -If you wish to work with another compiler, simply rerun the configure script with a new -compiler name and an activation script will be produced. You can then source -either activation script to get the same conda environment but with different -compilers and related modules. Make sure you are careful to set up compass by -pointing to a version of the MPAS model that was compiled with the correct -compiler. +The active load script path is exported in ``COMPASS_LOAD_SCRIPT``. Compass +still accepts ``LOAD_COMPASS_ENV`` as a legacy fallback while the migration is +in progress. + +If you wish to work with another compiler or MPI library, rerun +``./deploy.py`` with the desired options so the corresponding load script is +generated or refreshed. Make sure you build MPAS with the same compiler and +MPI combination as the load script you plan to use. Switching between different compass environments ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -288,50 +179,43 @@ Many developers are switching between different ``compass`` branches. We have 2 main workflows for doing this: checking out different branches in the same directory (with ``git checkout``) or creating new directories for each branch (with ``git worktree``). Either way, you need to be careful that -the version of the ``compass`` package that is installed in the conda -environment you are using is the one you want. But how to handle it +the version of the ``compass`` package that is installed in the environment +you are using is the one you want. But how to handle it differs slightly between these workflows. If you are developing or using multiple ``compass`` branches in the same directory (switching between them using ``git checkout``), you will need -to make sure you update your ``compass`` environment after changing -branches. Often the branches you're developing will make use of the -same conda environment, because they are using the same -``compass`` version (so the dependencies aren't changing). The same -conda environment (e.g. ``dev_compass_``) can safely be used -with multiple branches if you explicitly reinstall the ``compass`` package -you want to use into the conda environment *after* moving to a new branch. -You can do this by simply re-executing -``source ./load_dev_compass____.sh`` -from the *root of the repo* before proceeding. +to make sure you update your environment after changing branches. If +dependencies are unchanged, you can usually just re-source a load script in +the branch root: + +``source ./load_*.sh`` Similarly, if you are developing or using multiple ``compass`` branches but you use a different directory for each (creating the directories with ``git worktree``), you will need to make sure the version of the ``compass`` package -in your conda environment is the one you want. +in your active environment is the one you want. If your branches use the same ``compass`` version (so the dependencies -are the same), you can use the same conda environment -(e.g. ``dev_compass_``) for all of them. But you will only -be able to test one of them at a time. You will tell the conda environment -which branch to use by running -``source ./load_dev_compass____.sh`` +are the same), you can use the same deployment prefix for all of them. You +will tell the environment which branch to use by running +``source ./load_*.sh`` from the *root of the directory (worktree) you want to work with* before proceeding. -In both of these workflows, you can modify the ``compass`` code and the conda +In both of these workflows, you can modify the ``compass`` code and the environment will notice the changes as you make them. However, if you have added or removed any files during your development, you need to source the load script again: -``source ./load_dev_compass____.sh`` +``source ./load_*.sh`` in the root of the repo or worktree so that the added or removed files will be -accounted for in the conda environment. +accounted for in the environment. If you know that ``compass`` has different dependencies in a branch or worktree you are working on compared to a previous branch you have worked with (or if you aren't sure), it is safest to not just reinstall -the ``compass`` package but also to check the dependencies by re-running: -``./conda/configure_compass_env.py`` with the same arguments as above. +the ``compass`` package but also to check the dependencies by re-running +``./deploy.py`` with the same arguments as above. This will also reinstall the ``compass`` package from the current directory. The activation script includes a check to see if the version of compass used to produce the load script is the same as the version of compass in the @@ -347,25 +231,18 @@ and the environment is not activated: Your code is version: __version__ = '1.2.0-alpha.7' - You need to run ./conda/configure_compass_env.py to update your conda - environment and load script. + You need to run ./deploy.py to update your environment and load script. -If you need more than one conda environment (e.g. because you are testing -multiple branches at the same time), you can choose your own name -for the conda environment. Typically, this might be something related to the -name of the branch you are developing. This can be done with the -``--env-name`` argument to ``./conda/configure_compass_env.py``. You -can reuse the same custom-named environment across multiple branches -if that is useful. Just remember to reinstall ``compass`` each time you -switch branches. +If you need more than one environment (e.g. because you are testing multiple +branches at the same time), use different deployment prefixes with +``./deploy.py --prefix ``. .. note:: - If you switch branches and *do not* remember to recreate the conda - environment (``./conda/configure_compass_env.py``) or at least source the - activation script (``load_dev_compass*.sh``), you are likely to end up with - an incorrect and possibly unusable ``compass`` package in your conda - environment. + If you switch branches and *do not* remember to recreate the environment + (``./deploy.py``) or at least source the activation script + (``load_*.sh``), you are likely to end up with an incorrect and possibly + unusable ``compass`` package in your environment. In general, if one wishes to switch between environments created for different compass branches or applications, the best practice is to end @@ -377,18 +254,18 @@ switch branches. .. note:: - With the conda environment activated, you can switch branches and update + With the environment activated, you can switch branches and update just the ``compass`` package with: .. code-block:: bash python -m pip install --no-deps --no-build-isolation -e . - The activation script will do this automatically when you source it in - the root directory of your compass branch. The activation script will also + The activation script will do this automatically when you source it in the + root directory of your compass branch. The activation script will also check if the current compass version matches the one used to create the activation script, thus catching situations where the dependencies are out - of date and the configure script needs to be rerun. + of date and ``./deploy.py`` needs to be rerun. Troubleshooting ~~~~~~~~~~~~~~~ @@ -398,60 +275,65 @@ can run: .. code-block:: bash - ./conda/configure_compass_env.py --conda -c --recreate + ./deploy.py [--machine ] [--compiler ...] \ + [--mpi ...] [--deploy-spack] [--no-spack] --recreate -The ``--recreate`` flag will delete the conda environment and create it from -scratch. This takes just a little extra time. +The ``--recreate`` flag will delete the deployment artifacts and create them +from scratch. This takes just a little extra time. .. _dev_creating_only_env: Creating/updating only the compass environment ---------------------------------------------- -For some workflows (e.g. for MALI development with the Albany library when the -MALI build environment has been created outside of ``compass``, for example, -on an unsupported machine), you may only want to create the conda environment -and not build PIO, ESMF or other packages with system compilers, or -include any system modules or environment variables in your activation script. -In such cases, run with the ``--env_only`` flag: +For some workflows, you may only want to create the pixi environment and not +build or reuse Spack environments. In such cases, run: + +.. code-block:: bash + + ./deploy.py --no-spack + +When ``--no-spack`` is not used, omitting ``--deploy-spack`` still means +Compass will try to reuse any required pre-existing Spack environments. + +To update only the bootstrap environment used internally by deployment, run: .. code-block:: bash - ./conda/configure_compass_env.py --conda --env_only + ./deploy.py --bootstrap-only -Each time you want to work with compass, you will need to run: +Each time you want to work with compass, source the generated load script: .. code-block:: bash - source ./load_dev_compass_.sh + source ./load_*.sh -This will load the appropriate conda environment for ``compass``. It will also -set an environment variable ``LOAD_COMPASS_ENV`` that points to the activation -script. ``compass`` uses this to make a symlink to the activation script -called ``load_compass_env.sh`` in the work directory. +This will load the appropriate deployment environment for ``compass``. It +will also set an environment variable ``COMPASS_LOAD_SCRIPT`` that points to +the activation script. ``compass`` uses this to make a symlink to the +activation script called ``load_compass_env.sh`` in the work directory. If you switch to another branch, you will need to rerun: .. code-block:: bash - ./conda/configure_compass_env.py --conda --env_only + ./deploy.py -to make sure dependencies are up to date and the ``compass`` package points -to the current directory. +to make sure dependencies are up to date and the ``compass`` package points to +the current directory. .. note:: - With the conda environment activated, you can switch branches and update + With the environment activated, you can switch branches and update just the ``compass`` package with: .. code-block:: bash python -m pip install --no-deps --no-build-isolation -e . - This will be substantially faster than rerunning - ``./conda/configure_compass_env.py ...`` but at the risk that dependencies are - not up-to-date. Since dependencies change fairly rarely, this will usually - be safe. + This will be substantially faster than rerunning ``./deploy.py ...`` but + at the risk that dependencies are not up to date. Since dependencies + change fairly rarely, this will usually be safe. .. _dev_build_mpas: @@ -464,7 +346,7 @@ compile MPAS-Ocean: .. code-block:: bash - source ./load_dev_compass____.sh + source ./load_*.sh cd E3SM-Project/components/mpas-ocean/ make @@ -478,7 +360,7 @@ MPAS-Ocean, i.e.: .. code-block:: bash - source ./load_dev_compass____.sh + source ./load_*.sh cd MALI-Dev/components/mpas-albany-landice make @@ -539,10 +421,9 @@ case), log onto a compute node (if on an HPC machine) and run: source load_compass_env.sh compass run -The first command will source the same activation script -(``load_dev_compass____.sh``) that you used to set -up the suite or test case (``load_compass_env.sh`` is just a symlink to that -activation script you sourced before setting up the suite or test case). +The first command will source the same activation script that you used to set +up the suite or test case (``load_compass_env.sh`` is just a symlink to the +load script you sourced before setting up the suite or test case). .. _dev_compass_style: @@ -562,9 +443,9 @@ for some tips on checking code style in PyCharm. Here's the manual way to check for PEP8 compliance. `Flake8 `_ is a PEP8 checker that is -included in the ``compass`` conda environment. For each of the files you have -modified, you can run the Flake8 checker to see a list of all instances of -non-compliance in that file. +included in the ``compass`` development environment. For each of the files you +have modified, you can run the Flake8 checker to see a list of all instances +of non-compliance in that file. .. code-block:: bash diff --git a/docs/developers_guide/troubleshooting.rst b/docs/developers_guide/troubleshooting.rst index c1162a6dc5..bad3ced067 100644 --- a/docs/developers_guide/troubleshooting.rst +++ b/docs/developers_guide/troubleshooting.rst @@ -8,14 +8,14 @@ suggested solutions. .. _dev_troubleshooting_conda_solver: -Solver errors when configuring conda environment ------------------------------------------------- +Solver errors when configuring deployment environment +----------------------------------------------------- When setting up :ref:`dev_conda_env`, by calling: .. code-block:: bash - ./conda/configure_compass_env.sh ... + ./deploy.py ... you may run into an error like: @@ -31,22 +31,22 @@ you may run into an error like: Details of the error may vary but the message indicates in some way that there was a problem solving for the requested combination of packages. This likely indicates that you have an existing compass development environment -(``dev_compass_*``) that can't be updated to be compatible with the new -set of development packages given in: +that can't be updated to be compatible with the new set of development +packages given in: .. code-block:: none - conda/build*/spec-file*.txt + deploy_tmp/build*/spec-file*.txt The solution should be to recreate the environment rather than trying to update it: .. code-block:: bash - ./conda/configure_compass_env.sh --recreate ... + ./deploy.py --recreate ... -The ``--recreate`` flag will first delete the existing ``dev_compass_*`` conda -environment before creating it again with the new set of packages required for +The ``--recreate`` flag will first delete the existing deployment artifacts +before creating them again with the new set of packages required for developing with the requested compiler and MPI type. .. _dev_troubleshooting_proxy: diff --git a/docs/tutorials/dev_add_rrm.rst b/docs/tutorials/dev_add_rrm.rst index 71a415514c..1b05f9f3b0 100644 --- a/docs/tutorials/dev_add_rrm.rst +++ b/docs/tutorials/dev_add_rrm.rst @@ -26,27 +26,24 @@ free to use the ``worktree`` approach instead if you are comfortable with it. cd add-yet-another-mesh git checkout -b add-yet-another-mesh -Now, you will need to create a conda environment for developing compass, as +Now, you will need to create a development environment for compass, as described in :ref:`dev_conda_env`. We will assume a simple situation where -you are working on a "supported" machine and using the default compilers and -MPI libraries, but consult the documentation to make an environment to suit -your needs. +you are working on a supported machine and using the default compilers and MPI +libraries, but consult the documentation to make an environment to suit your +needs. .. code-block:: bash # this one will take a while the first time - ./conda/configure_compass_env.py --conda $HOME/miniforge --env_name compass_yam + ./deploy.py --prefix $HOME/compass_yam -If you don't already have Miniforge3 installed in the directory pointed to by -``--conda``, it will be installed automatically for you. If all goes well, you -will have a file named ``load_compass_yam*.sh``, where the details of the -``*`` depend on your specific machine and compilers. For example, on -Chrysalis, you will have ``load_compass_yam_chrysalis_intel_openmpi.sh``, -which will be the example used here: +If all goes well, you will have one or more generated load scripts called +``load_*.sh`` in the root of the repository. For example, on Chrysalis, you +could use ``load_compass_chrysalis_intel_openmpi.sh``: .. code-block:: bash - source load_compass_yam_chrysalis_intel_openmpi.sh + source load_compass_chrysalis_intel_openmpi.sh Now, we're ready to get the MPAS-Ocean source code from the E3SM repository: diff --git a/docs/tutorials/dev_add_test_group.rst b/docs/tutorials/dev_add_test_group.rst index 3040bca5f4..9b7fb3879c 100644 --- a/docs/tutorials/dev_add_test_group.rst +++ b/docs/tutorials/dev_add_test_group.rst @@ -25,27 +25,25 @@ free to use the ``worktree`` approach instead if you are comfortable with it. git clone git@github.com:MPAS-Dev/compass.git add_baroclinic_channel cd add_baroclinic_channel -Now, you will need to create a conda environment for developing compass, as +Now, you will need to create a development environment for compass, as described in :ref:`dev_conda_env`. We will assume a simple situation where -you are working on a "supported" machine and using the default compilers and -MPI libraries, but consult the documentation to make an environment to suit -your needs. +you are working on a supported machine and using the default compilers and MPI +libraries, but consult the documentation to make an environment to suit your +needs. .. code-block:: bash # this one will take a while the first time - ./conda/configure_compass_env.py --conda $HOME/miniforge + ./deploy.py -If you don't already have Miniforge3 installed in the directory pointed to by -``--conda``, it will be installed automatically for you. If all goes well, you -will have a file named ``load_dev_compass_1.0.0*.sh``, where the details of the -``*`` depend on your specific machine and compilers. For example, on -Chrysalis, you will have ``load_dev_compass_1.0.0_chrysalis_intel_impi.sh``, -which will be the example used here: +If all goes well, you will have one or more generated load scripts called +``load_*.sh`` in the root of the repository. On Chrysalis, one such script +could be ``load_compass_chrysalis_intel_openmpi.sh``, which will be the +example used here: .. code-block:: bash - source load_dev_compass_1.0.0_chrysalis_intel_impi.sh + source load_compass_chrysalis_intel_openmpi.sh Now, we're ready to get the MPAS-Ocean source code from the E3SM repository: @@ -1574,4 +1572,4 @@ groups and test cases were documented well. Add ```` in the appropriate place (in alphabetical order) to the list of test groups in ``docs/developers_guide//test_groups/index.rst``. -At this point, you are ready to make a pull request with the new test group! \ No newline at end of file +At this point, you are ready to make a pull request with the new test group! diff --git a/docs/tutorials/dev_porting_legacy.rst b/docs/tutorials/dev_porting_legacy.rst index b678a65507..8a0ce37faf 100644 --- a/docs/tutorials/dev_porting_legacy.rst +++ b/docs/tutorials/dev_porting_legacy.rst @@ -32,25 +32,25 @@ instead if you are comfortable with it. git clone git@github.com:MPAS-Dev/compass.git add_gotm cd add_gotm -Now, you will need to create a conda environment for developing compass, as +Now, you will need to create a development environment for compass, as described in :ref:`dev_conda_env`. We will assume a simple situation where -you are working on a "supported" machine and using the default compilers and -MPI libraries, but consult the documentation to make an environment to suit -your needs. +you are working on a supported machine and using the default compilers and MPI +libraries, but consult the documentation to make an environment to suit your +needs. .. code-block:: bash # this one will take a while the first time - ./conda/configure_compass_env.py --conda $HOME/miniforge + ./deploy.py -If all goes well, you will have a file named ``load_dev_compass_1.0.0*.sh``, where -the details of the ``*`` depend on your specific machine and compilers. For -example, on Chrysalis, you will have ``load_dev_compass_1.0.0_chrysalis_intel_impi.sh``, -which will be the example used here: +If all goes well, you will have one or more generated load scripts called +``load_*.sh`` in the root of the repository. On Chrysalis, one such script +could be ``load_compass_chrysalis_intel_openmpi.sh``, which will be the +example used here: .. code-block:: bash - source load_dev_compass_1.0.0_chrysalis_intel_impi.sh + source load_compass_chrysalis_intel_openmpi.sh Now, we're ready to get the MPAS-Ocean source code from the E3SM repository and build the MPAS-Ocean executable: @@ -1293,4 +1293,4 @@ newly added test cases are documented well. Add ```` in the appropriate place (in alphabetical order) in the list of test groups in ``docs/developers_guide//test_groups/index.rst``. -At this point, you are ready to make a pull request with the ported test group! \ No newline at end of file +At this point, you are ready to make a pull request with the ported test group! From a4a9dc325b89d54a98a03bf1d194d05380db09cf Mon Sep 17 00:00:00 2001 From: Xylar Asay-Davis Date: Sat, 21 Mar 2026 13:57:48 +0100 Subject: [PATCH 10/22] Fix machine name detection in setup.py --- compass/setup.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/compass/setup.py b/compass/setup.py index 5f40b1880e..37a6a9fd48 100644 --- a/compass/setup.py +++ b/compass/setup.py @@ -71,6 +71,9 @@ def setup_cases(tests=None, numbers=None, config_file=None, machine=None, # noq if machine is None: machine = discover_machine() + if not machine: + machine = None + if config_file is None and machine is None: raise ValueError('At least one of config_file and machine is needed.') @@ -424,6 +427,9 @@ def _get_basic_config(config_file, machine, mpas_model_path, mpas_core): """ config = CompassConfigParser() + if not machine: + machine = None + if config_file is not None: config.add_user_config(config_file) From 6b0f0d6d412b3d128dd8dcbbf7c4a8a426bb2343 Mon Sep 17 00:00:00 2001 From: Xylar Asay-Davis Date: Sat, 21 Mar 2026 14:12:21 +0100 Subject: [PATCH 11/22] Remove configs for unsupported machines --- compass/machines/anvil.cfg | 32 ------------------- compass/machines/eligos.cfg | 60 ----------------------------------- compass/machines/morpheus.cfg | 60 ----------------------------------- 3 files changed, 152 deletions(-) delete mode 100644 compass/machines/anvil.cfg delete mode 100644 compass/machines/eligos.cfg delete mode 100644 compass/machines/morpheus.cfg diff --git a/compass/machines/anvil.cfg b/compass/machines/anvil.cfg deleted file mode 100644 index 7670b9868b..0000000000 --- a/compass/machines/anvil.cfg +++ /dev/null @@ -1,32 +0,0 @@ - -# The paths section describes paths that are used within the ocean core test -# cases. -[paths] - -# A shared root directory where MPAS standalone data can be found -database_root = /lcrc/group/e3sm/public_html/mpas_standalonedata - -# the path to the base conda environment where compass environments have -# been created -compass_envs = /lcrc/soft/climate/compass/anvil/base - - -# Options related to deploying a compass conda environment on supported -# machines -[deploy] - -# the compiler set to use for system libraries and MPAS builds -compiler = intel - -# the system MPI library to use for intel compiler -mpi_intel = impi - -# the system MPI library to use for gnu compiler -mpi_gnu = openmpi - -# the base path for spack environments used by compass -spack = /lcrc/soft/climate/compass/anvil/spack - -# whether to use the same modules for hdf5, netcdf-c, netcdf-fortran and -# pnetcdf as E3SM (spack modules are used otherwise) -use_e3sm_hdf5_netcdf = True diff --git a/compass/machines/eligos.cfg b/compass/machines/eligos.cfg deleted file mode 100644 index aa1a52bdaa..0000000000 --- a/compass/machines/eligos.cfg +++ /dev/null @@ -1,60 +0,0 @@ -# The parallel section describes options related to running jobs in parallel -[parallel] - -# parallel system of execution: slurm, cobalt or single_node -system = single_node - -# whether to use mpirun or srun to run a task -parallel_executable = mpirun - -# cores per node on the machine -cores_per_node = 4 - - -# Config options related to spack environments -[spack] - -# whether to load modules from the spack yaml file before loading the spack -# environment -modules_before = False - -# whether to load modules from the spack yaml file after loading the spack -# environment -modules_after = False - - -# The paths section describes paths that are used within the ocean core test -# cases. -[paths] - -# A shared root directory where MPAS standalone data can be found -database_root = /home/xylar/data/mpas/mpas_standalonedata - -# the path to the base conda environment where compass environments have -# been created -compass_envs = /home/xylar/data/mpas/compass_envs - - -# Options related to deploying a compass conda environment on supported -# machines -[deploy] - -# the compiler set to use for system libraries and MPAS builds -compiler = gnu - -# the system MPI library to use for gnu compiler -mpi_gnu = openmpi - -# the base path for spack environments used by compass -spack = /home/xylar/data/mpas/spack - -# whether to use the same modules for hdf5, netcdf-c, netcdf-fortran and -# pnetcdf as E3SM (spack modules are used otherwise) -use_e3sm_hdf5_netcdf = False - - -# Options related to machine discovery -[discovery] - -# a substring used to identify this machine from its hostname -hostname_contains = eligos diff --git a/compass/machines/morpheus.cfg b/compass/machines/morpheus.cfg deleted file mode 100644 index f44918fffa..0000000000 --- a/compass/machines/morpheus.cfg +++ /dev/null @@ -1,60 +0,0 @@ -# The parallel section describes options related to running jobs in parallel -[parallel] - -# parallel system of execution: slurm, cobalt or single_node -system = single_node - -# whether to use mpirun or srun to run a task -parallel_executable = mpirun - -# cores per node on the machine -cores_per_node = 8 - - -# Config options related to spack environments -[spack] - -# whether to load modules from the spack yaml file before loading the spack -# environment -modules_before = False - -# whether to load modules from the spack yaml file after loading the spack -# environment -modules_after = False - - -# The paths section describes paths that are used within the ocean core test -# cases. -[paths] - -# A shared root directory where MPAS standalone data can be found -database_root = /home/xylar/data/mpas/mpas_standalonedata - -# the path to the base conda environment where compass environments have -# been created -compass_envs = /home/xylar/data/mpas/compass_envs - - -# Options related to deploying a compass conda environment on supported -# machines -[deploy] - -# the compiler set to use for system libraries and MPAS builds -compiler = gnu - -# the system MPI library to use for gnu compiler -mpi_gnu = openmpi - -# the base path for spack environments used by compass -spack = /home/xylar/data/mpas/spack - -# whether to use the same modules for hdf5, netcdf-c, netcdf-fortran and -# pnetcdf as E3SM (spack modules are used otherwise) -use_e3sm_hdf5_netcdf = False - - -# Options related to machine discovery -[discovery] - -# a substring used to identify this machine from its hostname -hostname_contains = morpheus From a6bee70e499647731fda0c1e4dbf203ae0443541 Mon Sep 17 00:00:00 2001 From: Xylar Asay-Davis Date: Sat, 21 Mar 2026 13:48:45 +0100 Subject: [PATCH 12/22] TEMP: use add-target-software-cli-flags mache branch in CI --- .github/workflows/build_workflow.yml | 1 + .github/workflows/gis_coarse_meshgen.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/build_workflow.yml b/.github/workflows/build_workflow.yml index 9ae6eedaab..c627532aa7 100644 --- a/.github/workflows/build_workflow.yml +++ b/.github/workflows/build_workflow.yml @@ -95,6 +95,7 @@ jobs: run: | git config --global url."https://github.com/".insteadOf "git@github.com:" ./deploy.py \ + --mache-fork xylar/mache --mache-branch add-target-software-cli-flags \ --no-spack \ --python ${{ matrix.python-version }} \ --recreate diff --git a/.github/workflows/gis_coarse_meshgen.yml b/.github/workflows/gis_coarse_meshgen.yml index 328503fa7b..93e615445c 100644 --- a/.github/workflows/gis_coarse_meshgen.yml +++ b/.github/workflows/gis_coarse_meshgen.yml @@ -43,6 +43,7 @@ jobs: run: | git config --global url."https://github.com/".insteadOf "git@github.com:" ./deploy.py \ + --mache-fork xylar/mache --mache-branch add-target-software-cli-flags \ --no-spack \ --python 3.14 \ --recreate From ba7c9009b4c96814150c854a94780e010aa7b498 Mon Sep 17 00:00:00 2001 From: Xylar Asay-Davis Date: Sat, 21 Mar 2026 14:40:44 +0100 Subject: [PATCH 13/22] Update MALI-Dev submodule --- MALI-Dev | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MALI-Dev b/MALI-Dev index 9ef2dcae2c..427e397731 160000 --- a/MALI-Dev +++ b/MALI-Dev @@ -1 +1 @@ -Subproject commit 9ef2dcae2cbd0dcd10d4823c0b590b1e820453a8 +Subproject commit 427e39773136657869bcc5b66f9ce85d436a8b2e From ed6e83ce2843bcff29c3b981d40a008ef10c77c0 Mon Sep 17 00:00:00 2001 From: Xylar Asay-Davis Date: Sat, 21 Mar 2026 15:43:18 +0100 Subject: [PATCH 14/22] Update the docs for recent code changes The biggest content changes are: * removing the stale user quick-start conda/load-script workflow that referenced commands no longer in the repo * updating CLI docs to match current `compass list/setup/suite/run` behavior and output fields * fixing framework docs to use the current pickle/log names like `test_case.pickle` and `case_outputs` * bringing landice docs in line with current registered test paths, especially the solver-specific Greenland, Dome, and Thwaites cases * updating the landice suite docs to match current suite contents and clarifying that eigencalving exists in the test group even when it is not in the regression suite --- docs/developers_guide/command_line.rst | 56 ++++----- docs/developers_guide/framework.rst | 22 ++-- docs/developers_guide/landice/index.rst | 20 ++-- .../landice/test_groups/humboldt.rst | 7 +- docs/developers_guide/ocean/index.rst | 14 +-- docs/users_guide/config_files.rst | 16 +-- docs/users_guide/landice/index.rst | 2 - docs/users_guide/landice/suites.rst | 10 +- .../test_groups/calving_dt_convergence.rst | 20 ++-- docs/users_guide/landice/test_groups/dome.rst | 47 +++++--- .../landice/test_groups/greenland.rst | 41 +++---- .../landice/test_groups/humboldt.rst | 10 +- .../landice/test_groups/thwaites.rst | 46 ++++---- docs/users_guide/quick_start.rst | 108 +++++------------- 14 files changed, 184 insertions(+), 235 deletions(-) diff --git a/docs/developers_guide/command_line.rst b/docs/developers_guide/command_line.rst index 16fb95676e..c6feec844b 100644 --- a/docs/developers_guide/command_line.rst +++ b/docs/developers_guide/command_line.rst @@ -10,11 +10,10 @@ to the package, as described below. When the ``compass`` package is installed into your deployment environment, you can run these commands as above. If you are developing ``compass`` from a -local branch off of https://github.com/MPAS-Dev/compass, you will need to -create a development environment with ``./deploy.py`` (see -:ref:`dev_conda_env`). If you do, ``compass`` will be installed in the -environment in editable mode, meaning you can make changes to the branch and -they will be reflected when you call the ``compass`` command-line tool. +local branch off of https://github.com/MPAS-Dev/compass, create or update a +development environment with ``./deploy.py`` (see :ref:`dev_conda_env`). In +that workflow, ``compass`` is installed in editable mode, meaning changes to +the branch are reflected when you call the ``compass`` command-line tool. .. _dev_compass_list: @@ -34,8 +33,9 @@ By default, all test cases are listed: $ compass list Testcases: - 0: examples/example_compact/1km/test1 - 1: examples/example_compact/1km/test2 + 0: landice/antarctica/mesh_gen + 1: landice/calving_dt_convergence/mismip+.specified_calving_velocity.none + 2: landice/calving_dt_convergence/mismip+.von_Mises_stress.none ... The number of each test case is displayed, followed by the relative path that @@ -46,7 +46,7 @@ command-line options. The ``-t`` or ``--test_expr`` flag can be used to supply a substring or regular expression that can be used to list a subset of the tests. Think of this as -as search expression within the default list of test-case relative paths. +a search expression within the default list of test-case relative paths. The flags ``-n`` or ``--number`` are used to list the name (relative path) of a single test case with the given number. @@ -60,21 +60,19 @@ by using the ``--suites`` flag. The result are the flags that would be passed to ``compass suite`` as part of setting up this test suite. The ``-v`` or ``--verbose`` flag lists more detail about each test case, -including its description, short name, core, configuration, subdirectory within -the configuration and the names of its steps: +including its short name, MPAS core, test group, subdirectory and the names of +its steps: .. code-block:: none - $ compass list -n 0 -v - path: examples/example_compact/1km/test1 - description: Tempate 1km test1 - name: test1 - core: examples - configuration: example_compact - subdir: 1km/test1 - steps: - - step1 - - step2 + $ compass list -t '^landice/greenland/sia_smoke_test$' -v + 39: path: landice/greenland/sia_smoke_test + name: smoke_test + MPAS core: landice + test group: greenland + subdir: sia_smoke_test + steps: + - run_model See :ref:`dev_list` for more about the underlying framework. @@ -96,13 +94,14 @@ The command-line options are: compass setup [-h] [-t PATH] [-n NUM [NUM ...]] [-f FILE] [-m MACH] [-w PATH] [-b PATH] [-p PATH] [--suite_name SUITE] + [--cached STEP [STEP ...]] [--copy_executable] The ``-h`` or ``--help`` options will display the help message describing the command-line options. The test cases to set up can be specified either by relative path or by number. The ``-t`` or ``--test`` flag is used to pass the relative path of the test -case within the resulting work directory. The is the path given by +case within the resulting work directory. This is the path given by :ref:`dev_compass_list`. Only one test case at a time can be supplied to ``compass setup`` this way. @@ -114,7 +113,7 @@ given by :ref:`dev_compass_list`. ``compass setup`` requires a few basic pieces of information to be able to set up a test case. These include places to download and cache some data files used in the test cases and the location where you built the MPAS model. There -are a few ways to to supply these. The ``-m`` -r ``--machine`` option is used +are a few ways to supply these. The ``-m`` or ``--machine`` option is used to tell ``compass setup`` which supported machine you're running on (leave this off if you're working on an "unknown" machine). See :ref:`dev_compass_list` above for how to list the supported machines. @@ -150,7 +149,7 @@ leave it with the default name ``custom``. You can run this test suite with Test cases within the custom suite are run in the order they are supplied to ``compass setup``, so keep this in mind when providing the list. Any test -cases that depend on the output of other test cases must run afther their +cases that depend on the output of other test cases must run after their dependencies. See :ref:`dev_setup` for more about the underlying framework. @@ -172,7 +171,7 @@ options are: The ``-h`` or ``--help`` options will display the help message describing the command-line options. -As with :ref:`dev_compass_setup`, the test cases to cleaned up can be specified +As with :ref:`dev_compass_setup`, the test cases to be cleaned up can be specified either by relative path or by number. The meanings of the ``-t`` or ``--test``, ``-n`` or ``--case_number``, and ``-w`` or ``--work_dir`` flags are the same as in :ref:`dev_compass_setup`. @@ -189,13 +188,14 @@ options are: .. code-block:: none - compass suite [-h] -c CORE -t SUITE [-f FILE] [-s] [--clean] [-v] + compass suite [-h] -c CORE -t SUITE [-f FILE] [-s] [--clean] [-m MACH] [-b PATH] [-w PATH] [-p PATH] + [--copy_executable] The ``-h`` or ``--help`` options will display the help message describing the command-line options. -The required argument are ``-c`` or ``--core``, one of the :ref:`dev_cores`, +The required arguments are ``-c`` or ``--core``, one of the :ref:`dev_cores`, where the test suite and its test cases reside; and ``-t`` or ``--test_suite``, the name of the test suite. These are the options listed when you run ``compass list --suites``. @@ -231,8 +231,8 @@ that has been set up in the current directory: .. code-block:: none compass run [-h] [--steps STEPS [STEPS ...]] - [--no-steps NO_STEPS [NO_STEPS ...]] - [suite] + [--no-steps NO_STEPS [NO_STEPS ...]] [-q] + [--step_is_subprocess] [suite] Whereas other ``compass`` commands are typically run in the local clone of the compass repo, ``compass run`` needs to be run in the appropriate work diff --git a/docs/developers_guide/framework.rst b/docs/developers_guide/framework.rst index 482db29ddd..0defd821ec 100644 --- a/docs/developers_guide/framework.rst +++ b/docs/developers_guide/framework.rst @@ -48,7 +48,7 @@ clean module ~~~~~~~~~~~~ The :py:func:`compass.clean.clean_cases()` function is used by -``compass clean`` and ``compass suite`` to delete the constants of a test-case +``compass clean`` and ``compass suite`` to delete the contents of a test-case subdirectory in the work directory. .. _dev_suite: @@ -85,8 +85,8 @@ and CPUs per task for each step, then it calls each step's ``run()`` method. Suites run from the base work directory with a pickle file starting with the suite name, or ``custom.pickle`` if a suite name was not given. Test cases or -steps run from their respective subdirectories with a ``testcase.pickle`` or -``step.pickle`` file in them. Both of these functions reads the local pickle +steps run from their respective subdirectories with a ``test_case.pickle`` or +``step.pickle`` file in them. Both of these functions read the local pickle file to retrieve information about the test suite, test case and/or step that was stored during setup. @@ -94,7 +94,7 @@ If :py:func:`compass.run.serial.run_tests()` is used for a test suite, it will run each test case in the test suite in the order that they are given in the text file defining the suite (``compass//suites/.txt``). Output from test cases and their steps are stored in log files in the -``case_output`` subdirectory of the base work directory. If the function is +``case_outputs`` subdirectory of the base work directory. If the function is used for a single test case, it will run the steps of that test case, writing output for each step to a log file starting with the step's name. In either case (suite or individual test), it displays a ``PASS`` or ``FAIL`` message for @@ -103,7 +103,7 @@ within the test case or suite and validation against a baseline (depending on the implementation of the ``validate()`` method in the test case and whether a baseline was provided during setup). -:py:func:`compass.run.run_single_step()` runs only the selected step from a +:py:func:`compass.run.serial.run_single_step()` runs only the selected step from a given test case, skipping any others, displaying the output in the terminal window rather than a log file. @@ -138,7 +138,7 @@ The :py:meth:`mpas_tools.config.MpasConfigParser.add_from_package()` method can be used to add the contents of a config file within a package to the config options. Examples of this can be found in many test cases as well as :py:func:`compass.setup.setup_case()`. Here is a typical example from -:py:func:`compass.ocean.tests.global_ocean.make_diagnostics_files.MakeDiagnosticsFiles.configure()`: +:py:meth:`compass.ocean.tests.global_ocean.mesh.Mesh.configure()`: .. code-block:: python @@ -147,8 +147,10 @@ options. Examples of this can be found in many test cases as well as Modify the configuration options for this test case """ self.config.add_from_package( - 'compass.ocean.tests.global_ocean.make_diagnostics_files', - 'make_diagnostics_files.cfg', exception=True) + 'compass.mesh', 'mesh.cfg', exception=True) + self.config.add_from_package( + 'compass.ocean.mesh', 'remap_topography.cfg', + exception=True) The first and second arguments are the name of a package containing the config file and the name of the config file itself, respectively. You can see that @@ -160,7 +162,7 @@ default behavior. In some cases, you would like the code to add the config options if the config file exists and do nothing if it does not. This can be useful if a common configure function is being used for all test cases in a configuration, as in this example from -:py:func:`setup.setup_case()`: +:py:func:`compass.setup.setup_case()`: .. code-block:: python @@ -216,7 +218,7 @@ are attributes that belong to the step or test case. If you run a step on its own, no log file is created and logging happens to ``stdout``/``stderr``. If you run a full test case, each step gets logged to its own log file within the test case's work directory. If you run a test suite, each test case and its -steps get logged to a file in the ``case_output`` directory of the suite's work +steps get logged to a file in the ``case_outputs`` directory of the suite's work directory. Although the logger will capture ``print`` statements, anywhere with a diff --git a/docs/developers_guide/landice/index.rst b/docs/developers_guide/landice/index.rst index 3f62ee0769..db1ecfb441 100644 --- a/docs/developers_guide/landice/index.rst +++ b/docs/developers_guide/landice/index.rst @@ -3,7 +3,7 @@ Landice core ============ -The ``landice`` core is defined by the :py:class:`compass.landice.LandIce` +The ``landice`` core is defined by the :py:class:`compass.landice.Landice` class. All test cases in the ``landice`` core share the following set of default config options: @@ -18,27 +18,21 @@ default config options: # has been built mpas_model = MALI-Dev/components/mpas-albany-landice - # The namelists section defines paths to example_compact namelists that will be used - # to generate specific namelists. By default, these point to the forward and - # init namelists in the default_inputs directory after a successful build of - # the landice model. Change these in a custom config file if you need a different - # example_compact. + # The namelists section defines paths to default namelist templates used + # to generate case-specific namelists. [namelists] forward = ${paths:mpas_model}/default_inputs/namelist.landice - # The streams section defines paths to example_compact streams files that will be used - # to generate specific streams files. By default, these point to the forward and - # init streams files in the default_inputs directory after a successful build of - # the landice model. Change these in a custom config file if you need a different - # example_compact. + # The streams section defines paths to default stream templates used to + # generate case-specific streams files. [streams] forward = ${paths:mpas_model}/default_inputs/streams.landice # The executables section defines paths to required executables. These # executables are provided for use by specific test cases. Most tools that - # compass needs should be in the conda environment, so this is only the path - # to the MPAS-Ocean executable by default. + # compass needs should be in the deployment environment, so this is only + # the path to the MALI executable by default. [executables] model = ${paths:mpas_model}/landice_model diff --git a/docs/developers_guide/landice/test_groups/humboldt.rst b/docs/developers_guide/landice/test_groups/humboldt.rst index 1836091e19..9c8b16658a 100644 --- a/docs/developers_guide/landice/test_groups/humboldt.rst +++ b/docs/developers_guide/landice/test_groups/humboldt.rst @@ -37,17 +37,17 @@ the process for setting up and running a MALI simulation for the humboldt configuration. It is called by :py:class:`compass.landice.tests.humboldt.decomposition_test.DecompositionTest` and -:py:class:`compass.landice.tests.humboldt.restartn_test.RestartTest`. +:py:class:`compass.landice.tests.humboldt.restart_test.RestartTest`. decomposition_test ------------------ -The compass.landice.tests.humboldt.decomposition_test.DecompositionTest +:py:class:`compass.landice.tests.humboldt.decomposition_test.DecompositionTest` performs the same simulation on different numbers of cores. It ensures relevant variables are identical or have expected differences. restart_test ------------ -The compass.landice.tests.humboldt.restart_test.RestartTest performs a +:py:class:`compass.landice.tests.humboldt.restart_test.RestartTest` performs a run of a full specified duration followed by a short run plus a restart to equal the same total duration. It checks that relevant variables are bit-for-bit when doing a restart. @@ -58,4 +58,3 @@ suffix. - diff --git a/docs/developers_guide/ocean/index.rst b/docs/developers_guide/ocean/index.rst index c1359aa641..28b55de623 100644 --- a/docs/developers_guide/ocean/index.rst +++ b/docs/developers_guide/ocean/index.rst @@ -18,20 +18,14 @@ default config options: # has been built mpas_model = E3SM-Project/components/mpas-ocean - # The namelists section defines paths to example_compact namelists that will be used - # to generate specific namelists. By default, these point to the forward and - # init namelists in the default_inputs directory after a successful build of - # the ocean model. Change these in a custom config file if you need a different - # example_compact. + # The namelists section defines paths to default namelist templates used + # to generate case-specific namelists. [namelists] forward = ${paths:mpas_model}/default_inputs/namelist.ocean.forward init = ${paths:mpas_model}/default_inputs/namelist.ocean.init - # The streams section defines paths to example_compact streams files that will be used - # to generate specific streams files. By default, these point to the forward and - # init streams files in the default_inputs directory after a successful build of - # the ocean model. Change these in a custom config file if you need a different - # example_compact. + # The streams section defines paths to default stream templates used to + # generate case-specific streams files. [streams] forward = ${paths:mpas_model}/default_inputs/streams.ocean.forward init = ${paths:mpas_model}/default_inputs/streams.ocean.init diff --git a/docs/users_guide/config_files.rst b/docs/users_guide/config_files.rst index a6e173db26..3cd7783c3b 100644 --- a/docs/users_guide/config_files.rst +++ b/docs/users_guide/config_files.rst @@ -32,7 +32,7 @@ Here is an example: # the relative or absolute path to the root of a branch where the MPAS # component has been built - mpas_mode = . + mpas_model = . # A root directory where MPAS standalone data can be found database_root = /home/xylar/data/mpas/mpas_standalonedata/mpas-ocean @@ -231,11 +231,8 @@ looks like: landice_database_root = /home/xylar/data/mpas/mpas_standalonedata/mpas-albany-landice - # The namelists section defines paths to example_compact namelists that will be used - # to generate specific namelists. By default, these point to the forward and - # init namelists in the default_inputs directory after a successful build of - # the ocean model. Change these in a custom config file if you need a different - # example_compact. + # The namelists section defines paths to default namelist templates that + # are used to generate case-specific namelists. [namelists] # source: /home/xylar/code/compass/customize_config_parser/compass/ocean/ocean.cfg @@ -245,11 +242,8 @@ looks like: init = /home/xylar/code/compass/customize_config_parser/E3SM-Project/components/mpas-ocean/default_inputs/namelist.ocean.init - # The streams section defines paths to example_compact streams files that will be used - # to generate specific streams files. By default, these point to the forward and - # init streams files in the default_inputs directory after a successful build of - # the ocean model. Change these in a custom config file if you need a different - # example_compact. + # The streams section defines paths to default stream templates that are + # used to generate case-specific streams files. [streams] # source: /home/xylar/code/compass/customize_config_parser/compass/ocean/ocean.cfg diff --git a/docs/users_guide/landice/index.rst b/docs/users_guide/landice/index.rst index e76ab0e4aa..d5d365d3b5 100644 --- a/docs/users_guide/landice/index.rst +++ b/docs/users_guide/landice/index.rst @@ -9,8 +9,6 @@ model. For more information on MALI, see `Hoffman et al. (2018) `_ and the `MALI User's Guide `_. There are a number of test groups, listed below. -Some additional ``landice`` test cases are available through -:ref:`legacy_compass`. Some helpful external links: diff --git a/docs/users_guide/landice/suites.rst b/docs/users_guide/landice/suites.rst index f9353d3a59..9e99762a4c 100644 --- a/docs/users_guide/landice/suites.rst +++ b/docs/users_guide/landice/suites.rst @@ -38,8 +38,8 @@ it is run on different numbers of cores (``decomposition_test``). landice/eismint2/enthalpy_decomposition_test landice/eismint2/restart_test landice/eismint2/enthalpy_restart_test - landice/greenland/restart_test - landice/greenland/decomposition_test + landice/greenland/sia_restart_test + landice/greenland/sia_decomposition_test landice/hydro_radial/restart_test landice/hydro_radial/decomposition_test landice/humboldt/mesh-3km_decomposition_test/velo-none_calving-none_subglacialhydro @@ -63,8 +63,8 @@ it is run on different numbers of cores (``decomposition_test``). humboldt_calving_tests ---------------------- -The ``humboldt_calving_tests`` test suite provide complete coverage of all -calving laws currently support by MALI applied to a 3 km resolution +The ``humboldt_calving_tests`` test suite provides complete coverage of all +calving laws currently supported by MALI applied to a 3 km resolution Humboldt Glacier domain. For the tests in this suite, the velocity solver is disabled, and the velocity field comes from an input field, allowing for rapid testing and for testing @@ -133,6 +133,8 @@ velocity fields, and collectively take about 15 minutes. The tests with FO velocity solver each take about 100 minutes and one may prefer to run them in individual jobs (which is why they are listed last in the test suite). Each test generates a .png image summarizing the results. +The broader ``landice/calving_dt_convergence`` test group also includes +``eigencalving`` variants that are not part of this regression suite. The suite includes: .. code-block:: none diff --git a/docs/users_guide/landice/test_groups/calving_dt_convergence.rst b/docs/users_guide/landice/test_groups/calving_dt_convergence.rst index 6d659dc421..2b168747d2 100644 --- a/docs/users_guide/landice/test_groups/calving_dt_convergence.rst +++ b/docs/users_guide/landice/test_groups/calving_dt_convergence.rst @@ -7,8 +7,12 @@ The ``landice/calving_dt_convergence`` test group supports tests for assessing the timestep convergence of calving physics in MALI. The tests all use pre-generated meshes. -The test group includes a single test case with many variants for using -different meshes, calving laws, and velocity solver settings. +The test group includes many individual test cases that cover combinations of +three meshes (``mismip+``, ``humboldt`` and ``thwaites``), three calving laws +(``specified_calving_velocity``, ``von_Mises_stress`` and ``eigencalving``), +and two velocity settings (``none`` and ``FO``). The +``specified_calving_velocity`` cases are only available with ``none`` +velocity because the FO combination is not used in the code. config options -------------- @@ -18,21 +22,21 @@ There currently are no config options. dt_convergence_test ------------------- -``landice/calving_dt_convergence/dt_convergence_test`` runs short -integrations repeatedly with different values for the fraction of the -calving CFL limit applied in the adpative timestepper. Time series of the +Cases in this test group have names such as +``landice/calving_dt_convergence/humboldt.von_Mises_stress.none`` or +``landice/calving_dt_convergence/thwaites.eigencalving.FO``. Each one runs +short integrations repeatedly with different values for the fraction of the +calving CFL limit applied in the adaptive timestepper. Time series of the total calving flux and the calving CFL to actual timestep ratio are then plotted, as well as summary of the number of calving warnings for each choice of calving CFL fraction (see below). -The individual tests are named for the mesh, the calving law, and the -velocity solver setting, separated by periods. .. figure:: images/calving_dt_comparison.png :width: 777 px :align: center Example results of calving dt test for the - ``humboldt.specified_calving_velocity.none`` test. The top plot + ``humboldt.specified_calving_velocity.none`` test case. The top plot shows the total calving flux over time for different choices of the calving CFL fraction. Results should be similar for small fraction values. As the fraction is increased, diff --git a/docs/users_guide/landice/test_groups/dome.rst b/docs/users_guide/landice/test_groups/dome.rst index c154794c85..7d9355d8e9 100644 --- a/docs/users_guide/landice/test_groups/dome.rst +++ b/docs/users_guide/landice/test_groups/dome.rst @@ -28,7 +28,8 @@ The test cases in this test group can run with either the SIA or FO velocity solvers. Running with the FO solver requires a build of MALI that includes Albany, but the SIA variant of the test can be run without Albany. -The test group includes 3 test cases. All test cases have 3 steps, +The test group includes three families of test cases: smoke, decomposition and +restart. All of these test cases have three steps, ``setup_mesh``, which defines the mesh and initial conditions for the model; ``run_model`` (given another name in many test cases to distinguish multiple forward runs), which performs time integration of the model; and ``visualize``, @@ -38,7 +39,7 @@ or both). config options -------------- -All 3 test cases share the same set of default config options: +All of these test cases share the same set of default config options: .. code-block:: cfg @@ -83,28 +84,36 @@ All 3 test cases share the same set of default config options: smoke_test ---------- -``landice/dome/2000m/smoke_test`` and ``landice/dome/varres/smoke_test`` are -the default version of the dome test case which is meant as a minimal example test -to set up and run. There is optional validation against the analytic solution. -The SIA version of this test runs for 200 years, while the FO version is configured -to only run for 2 years, given the greater expense of the FO solver. These default -durations can be modified in the namelist.landice file in the ``run_step`` directory. +The smoke-test variants are ``landice/dome/2000m/sia_smoke_test``, +``landice/dome/2000m/fo_smoke_test``, +``landice/dome/variable_resolution/sia_smoke_test`` and +``landice/dome/variable_resolution/fo_smoke_test``. These are the default +minimal example tests to set up and run. There is optional validation +against the analytic solution. The SIA versions run for 200 years, while the +FO versions are configured to run for only 2 years because the FO solver is +more expensive. These default durations can be modified in the +``namelist.landice`` file in the ``run_model`` step directory. decomposition_test ------------------ -``landice/dome/2000m/decomposition_test`` and -``landice/dome/varres/decomposition_test`` run short (1 year) integrations -of the model forward in time on 1 (``1proc_run`` step) and then on 4 processors -(``4proc_run`` step) to make sure the resulting prognostic variables are -bit-for-bit identical between the two runs. +The decomposition-test variants are +``landice/dome/2000m/sia_decomposition_test``, +``landice/dome/2000m/fo_decomposition_test``, +``landice/dome/variable_resolution/sia_decomposition_test`` and +``landice/dome/variable_resolution/fo_decomposition_test``. They run short +(1 year) integrations of the model forward in time on 1 (``1proc_run`` step) +and then on 4 processors (``4proc_run`` step) to compare the resulting +prognostic variables between the two runs. restart_test ------------ -``landice/dome/2000m/restart_test`` and ``landice/dome/varres/restart_test`` -first run a short (2 year) integration of the model forward in time -(``full_run`` step). Then, a second step (``restart_run``) performs 2 -1-year runs, where the second begins from a restart file saved by the first. -Prognostic variables are compared between the "full" and "restart" runs at -year 2 to make sure they are bit-for-bit identical. +The restart-test variants are ``landice/dome/2000m/sia_restart_test``, +``landice/dome/2000m/fo_restart_test``, +``landice/dome/variable_resolution/sia_restart_test`` and +``landice/dome/variable_resolution/fo_restart_test``. Each first runs a +short (2 year) integration of the model forward in time (``full_run`` step). +Then, a second step (``restart_run``) performs two 1-year runs, where the +second begins from a restart file saved by the first. Prognostic variables +are compared between the "full" and "restart" runs at year 2. diff --git a/docs/users_guide/landice/test_groups/greenland.rst b/docs/users_guide/landice/test_groups/greenland.rst index d50f2f2a46..f19aa88b67 100644 --- a/docs/users_guide/landice/test_groups/greenland.rst +++ b/docs/users_guide/landice/test_groups/greenland.rst @@ -13,11 +13,12 @@ and to create customized variable resolution meshes. FO velocity solution visualized in Paraview. -The test group includes 3 test cases, each of which has one or more steps -that are variants on ``run_model`` (given other names in the decomposition and -restart test cases to distinguish multiple model runs), which performs time -integration of the model. There is a fourth test case, ``mesh_gen``, that -creates a variable resolution Greenland Ice Sheet mesh, with the step ``mesh``. +The test group includes paired smoke, decomposition and restart test cases for +the SIA and FO velocity solvers, each of which has one or more steps that are +variants on ``run_model`` (given other names in the decomposition and restart +test cases to distinguish multiple model runs), which performs time +integration of the model. There is also a ``mesh_gen`` test case that creates +a variable resolution Greenland Ice Sheet mesh, with the step ``mesh``. The test cases in this test group can run with either the SIA or FO velocity solvers. Running with the FO solver requires a build of MALI that includes @@ -109,33 +110,33 @@ The other test cases do not use config options. smoke_test ---------- -``landice/greenland/smoke_test`` is the default version of the greenland test -case for a short (5-day) test run. +The smoke-test variants are ``landice/greenland/sia_smoke_test`` and +``landice/greenland/fo_smoke_test``. They perform short (5-day) test runs. decomposition_test ------------------ -``landice/greenland/decomposition_test`` runs short (5-day) integrations of the -model forward in time on 1 (``1proc_run`` step) and then on 4 cores -(``4proc_run`` step) to make sure the resulting prognostic variables are -bit-for-bit identical between the two runs. +The decomposition-test variants are +``landice/greenland/sia_decomposition_test`` and +``landice/greenland/fo_decomposition_test``. The SIA case compares 1-core +and 8-core runs, whereas the FO case compares 16-core and 32-core runs. restart_test ------------ -``landice/greenland/2000m/restart_test`` first run a short (5-day) integration -of the model forward in time (``full_run`` step). Then, a second step -(``restart_run``) performs a 3-day, then a 2-day run, where the second begins -from a restart file saved by the first. Prognostic variables are compared -between the "full" and "restart" runs at year 2 to make sure they are -bit-for-bit identical. +The restart-test variants are ``landice/greenland/sia_restart_test`` and +``landice/greenland/fo_restart_test``. Each first runs a short (5-day) +integration of the model forward in time (``full_run`` step). Then, a second +step (``restart_run``) performs a 3-day run and then a 2-day run, where the +second begins from a restart file saved by the first. Prognostic variables +are compared between the "full" and "restart" runs. mesh_gen ------------- ``landice/greenland/mesh_gen`` creates a variable resolution mesh based -on the the config options listed above. This will not be the same as the -pre-generated 20 km mesh used in the other three test cases because it uses +on the config options listed above. This will not be the same as the +pre-generated 20 km mesh used in the smoke, decomposition and restart cases because it uses a newer version of Jigsaw. Note that the basal friction optimization is performed separately and is not part of this test case. @@ -146,7 +147,7 @@ as well as using conservative remapping directly from the high-resolution BedMachine v5 and MeASUReS 2006-2010 velocity datasets. There is a fairly heavy degree of pre-processing done to get the BedMachine and MeASUReS datasets ready to be used here. The pre-processing includes renaming -variables, setting reasonable _FillValue and missing_value attributes +variables, setting reasonable ``_FillValue`` and ``missing_value`` attributes, extrapolating fields to avoid interpolation ramps at ice margins, updating mask values. diff --git a/docs/users_guide/landice/test_groups/humboldt.rst b/docs/users_guide/landice/test_groups/humboldt.rst index 724ff9cf04..06c28af47d 100644 --- a/docs/users_guide/landice/test_groups/humboldt.rst +++ b/docs/users_guide/landice/test_groups/humboldt.rst @@ -3,7 +3,7 @@ humboldt ======== -The ``landice/humboldt`` test group includes test case for creating a +The ``landice/humboldt`` test group includes a test case for creating a mesh for Humboldt Glacier, Greenland, and a series of tests for running simulations on a Humboldt Glacier mesh. Note that the tests that run MALI do not use the output of the mesh generation step directly, but instead @@ -68,7 +68,7 @@ the mesh generation options are adjusted through the config file. mesh_gen -------- -``landice/humboldt/default`` creates a 1-10km variable resolution mesh. +``landice/humboldt/mesh_gen`` creates a 1-10 km variable resolution mesh. There is no model integration step. decomposition_tests @@ -76,7 +76,7 @@ decomposition_tests There are a number of variants of a decomposition test that runs a 5-year simulation on 16 (16proc_run step) and then on 32 cores (32proc_run step) -to make sure key prognostic variables are either bit-fot-bit (without the +to make sure key prognostic variables are either bit-for-bit (without the FO solver) or have only small differences within a specified tolerance (with the FO solver). The FO solver is not BFB on different decompositions, but the differences are small. There are variants of this test for each calving law @@ -96,12 +96,12 @@ restart_tests There are a number of variants of a restart test that runs a 3-year simulation compared to a 2-year simulation followed by a restart for an additional -1 year. Results should be bit-for-bit identical. +1 year. Results should be bit-for-bit identical. There are variants of this test for each calving law that MALI currently supports, paired with either the FO velocity solver or no velocity solver. The full set of combinations use the 3 km mesh. There is additionally a -decomposition test using the 1 km mesh that has calving disabled. +restart test using the 1 km mesh that has calving disabled. Finally, there is a set of "full physics" tests that use von Mises calving, plus damage threshold calving and marine facemelting. This configuration can be run with either the FO velocity solver or no velocity solver. It is meant diff --git a/docs/users_guide/landice/test_groups/thwaites.rst b/docs/users_guide/landice/test_groups/thwaites.rst index 9a7c4bf0b1..0888042fa9 100644 --- a/docs/users_guide/landice/test_groups/thwaites.rst +++ b/docs/users_guide/landice/test_groups/thwaites.rst @@ -6,7 +6,7 @@ thwaites The ``landice/thwaites`` test group runs tests with a coarse (4-14-km) `Thwaites Glacier mesh `_. The purpose of this test group is to provide a realistic glacier that -includess an ice shelf. +includes an ice shelf. The mesh and initial condition are already generated. In the future, additional test cases may be added for generating a new version of the Thwaites mesh at different resolutions and using different data sources. @@ -18,16 +18,18 @@ Thwaites mesh at different resolutions and using different data sources. FO velocity solution visualized in Paraview. Grounding line position shown with a white line. -The test group includes three test cases, two of which have one or more steps -that are variants on ``run_model`` (given other names in the decomposition and -restart test cases to distinguish multiple model runs), which performs time -integration of the model. There is a not an explicit smoke test, but the -``full_run`` step of the ``restart_test`` can be used as a smoke test. +The test group includes four FO-based model test cases, each with one or more +steps that are variants on ``run_model`` (given other names in the +decomposition and restart test cases to distinguish multiple model runs), +which performs time integration of the model. These are +``fo_decomposition_test``, ``fo-depthInt_decomposition_test``, +``fo_restart_test`` and ``fo-depthInt_restart_test``. There is also a +``mesh_gen`` test case. There is not an explicit smoke test, but the +``full_run`` step of either restart test can be used as a smoke test. -The ``decomposition_test`` and ``restart_test`` test cases in this test group -can only be run with the FO velocity solvers. Running with the FO solver requires -a build of MALI that includes Albany. There is no integration step for the test -case ``mesh_gen``. +The model test cases in this test group can only be run with FO velocity +solvers. Running with FO requires a build of MALI that includes Albany. There +is no integration step for the ``mesh_gen`` test case. config options -------------- @@ -69,26 +71,28 @@ The other test cases do not use config options. decomposition_test ------------------ -``landice/thwaites/decomposition_test`` runs short (2-day) integrations of the -model forward in time on 16 (``16proc_run`` step) and then on 32 cores -(``32proc_run`` step) to make sure the resulting prognostic variables are -bit-for-bit identical between the two runs. +The decomposition-test variants are +``landice/thwaites/fo_decomposition_test`` and +``landice/thwaites/fo-depthInt_decomposition_test``. They run short (2-day) +integrations of the model forward in time on 16 (``16proc_run`` step) and +then on 32 cores (``32proc_run`` step) to make sure the resulting prognostic +variables agree within the tolerances used by the test. restart_test ------------ -``landice/thwaites/restart_test`` first runs a short (5-day) integration -of the model forward in time (``full_run`` step). Then, a second step -(``restart_run``) performs two subsequent 2 and 3 day integrations, where the -second begins from a restart file saved by the first. Prognostic variables -are compared between the "full" and "restart" runs to make sure they are -bit-for-bit identical. +The restart-test variants are ``landice/thwaites/fo_restart_test`` and +``landice/thwaites/fo-depthInt_restart_test``. Each first runs a short +(5-day) integration of the model forward in time (``full_run`` step). Then, +a second step (``restart_run``) performs two subsequent 2- and 3-day +integrations, where the second begins from a restart file saved by the first. +Prognostic variables are compared between the "full" and "restart" runs. mesh_gen ------------- ``landice/thwaites/mesh_gen`` creates a variable resolution mesh based -on the the config options listed above. This will not be the same as the +on the config options listed above. This will not be the same as the pre-generated 4-14km mesh used in ``decomposition_test`` and ``restart_test`` because it uses a newer version of Jigsaw. Note that the basal friction optimization is performed separately and is not part of this test case. diff --git a/docs/users_guide/quick_start.rst b/docs/users_guide/quick_start.rst index d8128f1e15..02174e34c7 100644 --- a/docs/users_guide/quick_start.rst +++ b/docs/users_guide/quick_start.rst @@ -5,74 +5,30 @@ Quick Start for Users .. _conda_env: -compass conda environment -------------------------- +compass deployment environment +------------------------------ E3SM supported machines ~~~~~~~~~~~~~~~~~~~~~~~ E3SM Compass users are basically all developers so we recommend using the -:ref:`dev_quick_start` guide. We used to maintain shared Compass environments -build for each release but this proved to be a maintenance burden with little -benefit. +:ref:`dev_quick_start` guide. That workflow uses ``./deploy.py`` to create or +update a local Compass deployment, generates the load scripts that Compass +work directories link to as ``load_compass_env.sh``, and matches the current +state of the code in this repository. Other machines ~~~~~~~~~~~~~~ -To install your own ``compass`` conda environment on other machines, first, -install `Miniforge3 `_ -(if it is not already installed), then create a new conda environment (called -``compass`` in this example) as follows: - -.. code-block:: bash - - conda create -n compass -c e3sm/label/compass -c conda-forge python=3.13 \ - "compass=*=mpi_mpich*" - -This will install the version of the package with MPI from conda-forge's MPICH -package. If you want OpenMPI, use ``"compass=*=mpi_openmpi*"`` instead. If -you do not want MPI from conda-forge (e.g. because you are working with a -system with its own MPI), use ``"compass=*=nompi*"`` - -To get a specific version of ``compass``, you can instead run: +Compass no longer maintains the old release-conda workflow documented in +earlier versions of this page. In particular, helper commands such as +``create_compass_load_script`` are no longer part of this repository. -.. code-block:: bash - - conda create -n compass -c e3sm/label/compass -c conda-forge python=3.13 \ - "compass=1.8.0=mpi_mpich*" - -That is, you will replace ``compass=*`` with ``compass=1.8.0``. - -Then, you will need to create a load script to activate the conda environment -and set some environment variables. On unsupported machines, you should first -clone and build `Jigsaw `_ and installs -both Jigsaw and `Jigsaw-Python `_. -These tools are used to build MPAS grids and the latest versions are not -available as conda packages. - -.. code-block:: bash - - conda activate compass - build_jigsaw --clone --subdir jigsaw-python - -Then, in a directory where you want to store the load script, run: - -.. code-block:: bash - - create_compass_load_script - -From then on, each time you want to set up test cases or suites with compass -or build MPAS components, you will need to source that load script, for -example: - -.. code-block:: bash - - source load_compass_1.8.0_mpich.sh - -When you set up tests, a link called ``load_compass_env.sh`` will be added to -each test case or suite work directory. To run the tests, you may find it -more convenient to source that link instead of finding the path to the original -load script. +For current workflows on unsupported machines, use the same deployment path as +developers where practical, or create an environment with the dependencies +needed by the cases you plan to run and make sure Jigsaw/Jigsaw-Python are +available for mesh-generation workflows. In either case, the maintained +reference is still :ref:`dev_quick_start`. .. _build_mpas: @@ -159,22 +115,22 @@ and you get output like this: 9: landice/dome/variable_resolution/sia_restart_test ... -The list is long, so it will likely be useful to ``grep`` for particular +The list is long, so it will likely be useful to filter for particular content: .. code-block:: bash - compass list | grep baroclinic_channel + compass list -t '^landice/greenland/' .. code-block:: none - 32: ocean/baroclinic_channel/1km/rpe_test - 33: ocean/baroclinic_channel/4km/rpe_test - 34: ocean/baroclinic_channel/10km/rpe_test - 35: ocean/baroclinic_channel/10km/decomp_test - 36: ocean/baroclinic_channel/10km/default - 37: ocean/baroclinic_channel/10km/restart_test - 38: ocean/baroclinic_channel/10km/threads_test + 39: landice/greenland/sia_smoke_test + 40: landice/greenland/sia_decomposition_test + 41: landice/greenland/sia_restart_test + 42: landice/greenland/fo_smoke_test + 43: landice/greenland/fo_decomposition_test + 44: landice/greenland/fo_restart_test + 45: landice/greenland/mesh_gen See :ref:`dev_compass_list` for more information. @@ -183,7 +139,8 @@ test case: .. code-block:: bash - compass setup -t ocean/global_ocean/QU240/mesh -w -p + compass setup -t landice/greenland/sia_smoke_test \ + -w -p or you can replace the ``-t`` flag with the simple shortcut: ``-n 15``. You can set up several test cases at once by passing test numbers separated by @@ -196,7 +153,7 @@ Chrysalis at LCRC, you might use: -w /lcrc/group/e3sm/$USER/runs/210131_test_new_branch -The placeholder ```` is the relative or absolute path where the MPAS +The placeholder ```` is the relative or absolute path where the MPAS component has been built (the directory, not the executable itself; see :ref:`machines`). You will typically want to provide a path either with ``-p`` or in a config file (see below) because the default paths are only useful for @@ -386,11 +343,9 @@ The output is: Suites: -c landice -t calving_dt_convergence - -c landice -t fo_integration -c landice -t full_integration -c landice -t humboldt_calving_tests -c landice -t humboldt_calving_tests_fo - -c landice -t sia_integration -c ocean -t cosine_bell_cached_init -c ocean -t ec30to60 -c ocean -t ecwisc30to60 @@ -398,14 +353,7 @@ The output is: -c ocean -t kuroshio12to60 -c ocean -t nightly -c ocean -t pr - -c ocean -t qu240_for_e3sm - -c ocean -t quwisc240 - -c ocean -t quwisc240_for_e3sm - -c ocean -t so12to30 - -c ocean -t sowisc12to30 - -c ocean -t wc14 - -c ocean -t wcwisc14 - -c ocean -t wetdry + ... You can set up a suite as follows: @@ -433,4 +381,4 @@ if there are multiple suites in the same ````. You can optionally specify a suite like ``compass run [suitename].pickle``, which is convenient for tab completion on the command line. The load script ``load_compass_env.sh`` is a link to whatever load script you sourced before -setting up the test case (see :ref:`conda_env`). \ No newline at end of file +setting up the test case (see :ref:`conda_env`). From 682022271b064be2d5a8f16a892ce93cac8ca3cb Mon Sep 17 00:00:00 2001 From: Xylar Asay-Davis Date: Sat, 21 Mar 2026 16:03:24 +0100 Subject: [PATCH 15/22] Check the docs with a strict build This treats warnings as errors. --- .github/workflows/build_workflow.yml | 2 +- docs/Makefile | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build_workflow.yml b/.github/workflows/build_workflow.yml index c627532aa7..5b238f465b 100644 --- a/.github/workflows/build_workflow.yml +++ b/.github/workflows/build_workflow.yml @@ -114,7 +114,7 @@ jobs: load_script=$(find . -maxdepth 1 -type f -name 'load_compass*.sh' | sort | tail -n 1) source "$load_script" cd docs - make html + make html-strict - if: ${{ steps.skip_check.outputs.should_skip != 'true' }} name: Run Tests diff --git a/docs/Makefile b/docs/Makefile index 487a471ce2..1125e0f6fa 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -12,7 +12,10 @@ BUILDDIR = _build help: @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) -.PHONY: help Makefile +.PHONY: help html-strict Makefile + +html-strict: + @$(SPHINXBUILD) -M html "$(SOURCEDIR)" "$(BUILDDIR)" -W $(SPHINXOPTS) $(O) # Catch-all target: route all unknown targets to Sphinx using the new # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). From fce6acdc8fecf06813a070453649b638f2f31179 Mon Sep 17 00:00:00 2001 From: Xylar Asay-Davis Date: Sat, 21 Mar 2026 16:44:45 +0100 Subject: [PATCH 16/22] Require cmake >=3.27 for Trilinos This means we need to opt out of the system CMake. --- deploy/config.yaml.j2 | 12 ++++++++++++ deploy/pins.cfg | 4 ++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/deploy/config.yaml.j2 b/deploy/config.yaml.j2 index 1d96a590ce..fd81ea7df0 100644 --- a/deploy/config.yaml.j2 +++ b/deploy/config.yaml.j2 @@ -156,6 +156,18 @@ spack: mirror: null custom_spack: "" + # Optional list of machine-provided Spack packages to suppress so Spack can + # build them instead. + # + # We exclude the system `cmake` so Spack can build the newer version + # requested in `deploy/spack.yaml.j2`. + # + # The HDF5/NetCDF bundle is controlled dynamically in `pre_spack()` based on + # merged machine config, mapping [deploy] use_e3sm_hdf5_netcdf = false to + # excluding `hdf5_netcdf`. + exclude_packages: + - cmake + jigsaw: # If true, build/install JIGSAW + JIGSAW-Python into the pixi env enabled: true diff --git a/deploy/pins.cfg b/deploy/pins.cfg index b75871f34b..f3aa308172 100644 --- a/deploy/pins.cfg +++ b/deploy/pins.cfg @@ -13,8 +13,8 @@ parallelio = 2.6.9 [spack] albany = compass-2026-03-10 albany_variants = +mpas~py+unit_tests -# cmake newer than 3.23.0 needed for Trilinos -cmake = 3.23.0: +# cmake newer than 3.27.0 needed for Trilinos +cmake = 3.27.0: hdf5 = 1.14.6 metis = 5.1.0 moab = 5.6.0 From 23004d060312014b8ee273f8a2ea0fb07ffe8600 Mon Sep 17 00:00:00 2001 From: Xylar Asay-Davis Date: Sat, 21 Mar 2026 16:45:19 +0100 Subject: [PATCH 17/22] Opt out of system hdf5 and netcdf based on config option --- deploy/hooks.py | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/deploy/hooks.py b/deploy/hooks.py index 8277665dc3..eeec6af281 100644 --- a/deploy/hooks.py +++ b/deploy/hooks.py @@ -26,9 +26,16 @@ def pre_spack(ctx: DeployContext) -> dict[str, Any] | None: _check_unsupported(ctx.machine, toolchain_pairs) updates: dict[str, Any] = {} + exclude_packages = _get_spack_exclude_packages(ctx.config) + _maybe_exclude_e3sm_hdf5_netcdf( + exclude_packages=exclude_packages, machine_config=ctx.machine_config + ) + spack_path = _get_spack_path(ctx.config, ctx.machine, ctx.machine_config) if spack_path is not None: updates['spack'] = {'spack_path': spack_path} + if exclude_packages: + updates.setdefault('spack', {})['exclude_packages'] = exclude_packages if _with_albany(ctx): _check_albany_support(ctx.machine, toolchain_pairs) @@ -105,6 +112,35 @@ def _get_toolchain_pairs(ctx: DeployContext) -> list[tuple[str, str]]: return toolchain_pairs +def _get_spack_exclude_packages(config) -> list[str]: + spack_cfg = config.get('spack', {}) + if not isinstance(spack_cfg, dict): + return [] + + exclude_packages = spack_cfg.get('exclude_packages', []) + if exclude_packages is None: + return [] + if isinstance(exclude_packages, str): + return [exclude_packages] + + return [str(package) for package in exclude_packages] + + +def _maybe_exclude_e3sm_hdf5_netcdf( + exclude_packages: list[str], machine_config +) -> None: + use_bundle = False + if machine_config.has_section('deploy') and machine_config.has_option( + 'deploy', 'use_e3sm_hdf5_netcdf' + ): + use_bundle = machine_config.getboolean( + 'deploy', 'use_e3sm_hdf5_netcdf' + ) + + if not use_bundle and 'hdf5_netcdf' not in exclude_packages: + exclude_packages.append('hdf5_netcdf') + + def _check_unsupported( machine: str | None, toolchain_pairs: list[tuple[str, str]] ) -> None: From b466b24881c7b407f56ced40637ae9df26cb1b0d Mon Sep 17 00:00:00 2001 From: Xylar Asay-Davis Date: Sat, 21 Mar 2026 16:54:19 +0100 Subject: [PATCH 18/22] Remove unneed Anvil clause --- deploy/load.sh | 8 -------- 1 file changed, 8 deletions(-) diff --git a/deploy/load.sh b/deploy/load.sh index 795a647fb1..c854c33441 100644 --- a/deploy/load.sh +++ b/deploy/load.sh @@ -12,14 +12,6 @@ else export PNETCDF="$(dirname "$(dirname "$(which pnetcdf-config)")")" fi -if [ "${COMPASS_MACHINE:-}" = "anvil" ] && \ - [[ "${COMPASS_COMPILER:-}" == *intel* ]]; then - export I_MPI_CC=icc - export I_MPI_CXX=icpc - export I_MPI_F77=ifort - export I_MPI_F90=ifort -fi - if [ "${COMPASS_MPI:-}" = "mvapich" ]; then export MV2_ENABLE_AFFINITY=0 export MV2_SHOW_CPU_BINDING=1 From cec99efe96b71060ff1d7edbb0351a6d955f3fcd Mon Sep 17 00:00:00 2001 From: Xylar Asay-Davis Date: Sat, 21 Mar 2026 17:00:06 +0100 Subject: [PATCH 19/22] Exclude system CMake on Chrysalis It is too old. Don't exclude it on Perlmutter, where it is new enough and can't be built with Spack. --- compass/machines/chrysalis.cfg | 3 +++ deploy/config.yaml.j2 | 6 +----- deploy/hooks.py | 18 ++++++++++++++++++ 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/compass/machines/chrysalis.cfg b/compass/machines/chrysalis.cfg index 88cffb29fb..f90b67e5be 100644 --- a/compass/machines/chrysalis.cfg +++ b/compass/machines/chrysalis.cfg @@ -30,3 +30,6 @@ spack = /lcrc/soft/climate/compass/chrysalis/spack # whether to use the same modules for hdf5, netcdf-c, netcdf-fortran and # pnetcdf as E3SM (spack modules are used otherwise) use_e3sm_hdf5_netcdf = True + +# whether to exclude system cmake from spack build +exclude_system_cmake = True diff --git a/deploy/config.yaml.j2 b/deploy/config.yaml.j2 index fd81ea7df0..80358609ad 100644 --- a/deploy/config.yaml.j2 +++ b/deploy/config.yaml.j2 @@ -159,14 +159,10 @@ spack: # Optional list of machine-provided Spack packages to suppress so Spack can # build them instead. # - # We exclude the system `cmake` so Spack can build the newer version - # requested in `deploy/spack.yaml.j2`. - # # The HDF5/NetCDF bundle is controlled dynamically in `pre_spack()` based on # merged machine config, mapping [deploy] use_e3sm_hdf5_netcdf = false to # excluding `hdf5_netcdf`. - exclude_packages: - - cmake + exclude_packages: [] jigsaw: # If true, build/install JIGSAW + JIGSAW-Python into the pixi env diff --git a/deploy/hooks.py b/deploy/hooks.py index eeec6af281..d59762fb55 100644 --- a/deploy/hooks.py +++ b/deploy/hooks.py @@ -30,6 +30,9 @@ def pre_spack(ctx: DeployContext) -> dict[str, Any] | None: _maybe_exclude_e3sm_hdf5_netcdf( exclude_packages=exclude_packages, machine_config=ctx.machine_config ) + _maybe_exclude_cmake( + exclude_packages=exclude_packages, machine_config=ctx.machine_config + ) spack_path = _get_spack_path(ctx.config, ctx.machine, ctx.machine_config) if spack_path is not None: @@ -141,6 +144,21 @@ def _maybe_exclude_e3sm_hdf5_netcdf( exclude_packages.append('hdf5_netcdf') +def _maybe_exclude_cmake( + exclude_packages: list[str], machine_config +) -> None: + exclude_cmake = False + if machine_config.has_section('deploy') and machine_config.has_option( + 'deploy', 'exclude_system_cmake' + ): + exclude_cmake = machine_config.getboolean( + 'deploy', 'exclude_system_cmake' + ) + + if exclude_cmake and 'cmake' not in exclude_packages: + exclude_packages.append('cmake') + + def _check_unsupported( machine: str | None, toolchain_pairs: list[tuple[str, str]] ) -> None: From 701e79a792a7101e08e79b5557e4b3cdfd27b6a2 Mon Sep 17 00:00:00 2001 From: Xylar Asay-Davis Date: Sat, 21 Mar 2026 17:41:47 +0100 Subject: [PATCH 20/22] Add a post_spack() hook that deletes ESMF includes These clash with MPAS framework's equivalent files. --- deploy/config.yaml.j2 | 1 + deploy/hooks.py | 72 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+) diff --git a/deploy/config.yaml.j2 b/deploy/config.yaml.j2 index 80358609ad..8bf3a71709 100644 --- a/deploy/config.yaml.j2 +++ b/deploy/config.yaml.j2 @@ -185,3 +185,4 @@ hooks: entrypoints: pre_pixi: "pre_pixi" pre_spack: "pre_spack" + post_spack: "post_spack" diff --git a/deploy/hooks.py b/deploy/hooks.py index d59762fb55..7be3df4fd7 100644 --- a/deploy/hooks.py +++ b/deploy/hooks.py @@ -47,6 +47,36 @@ def pre_spack(ctx: DeployContext) -> dict[str, Any] | None: return updates +def post_spack(ctx: DeployContext) -> None: + if getattr(ctx.args, 'no_spack', False): + return + + spack_path = _resolve_spack_path(ctx) + if spack_path is None: + return + + env_name_prefix = _get_spack_env_name_prefix(ctx) + for compiler, mpi in _get_toolchain_pairs(ctx): + env_name = f'{env_name_prefix}_{compiler}_{mpi}' + include_path = Path( + spack_path, + 'var', + 'spack', + 'environments', + env_name, + '.spack-env', + 'view', + 'include', + ) + removed = _remove_esmf_include_files(include_path) + if removed: + ctx.logger.info( + 'Removed %s ESMF/ESMC include file(s) from %s', + removed, + include_path, + ) + + def _get_version() -> str: here = Path(__file__).resolve().parent version_path = here.parent / 'compass' / 'version.py' @@ -96,6 +126,38 @@ def _get_spack_path(config, machine: str | None, machine_config) -> str | None: return os.path.join(spack_base, f'dev_compass_{release_version}') +def _resolve_spack_path(ctx: DeployContext) -> str | None: + spack_path = getattr(ctx.args, 'spack_path', None) + if spack_path not in (None, '', 'null', 'None'): + return os.path.abspath(os.path.expanduser(str(spack_path))) + + runtime_spack = ctx.runtime.get('spack', {}) + if isinstance(runtime_spack, dict): + spack_path = runtime_spack.get('spack_path') + if spack_path not in (None, '', 'null', 'None'): + return os.path.abspath(os.path.expanduser(str(spack_path))) + + return _get_spack_path(ctx.config, ctx.machine, ctx.machine_config) + + +def _get_spack_env_name_prefix(ctx: DeployContext) -> str: + env_name_prefix = 'spack_env' + + spack_cfg = ctx.config.get('spack', {}) + if isinstance(spack_cfg, dict): + env_name_prefix = str( + spack_cfg.get('env_name_prefix') or env_name_prefix + ).strip() + + runtime_spack = ctx.runtime.get('spack', {}) + if isinstance(runtime_spack, dict): + env_name_prefix = str( + runtime_spack.get('env_name_prefix') or env_name_prefix + ).strip() + + return env_name_prefix + + def _get_toolchain_pairs(ctx: DeployContext) -> list[tuple[str, str]]: runtime_toolchain = ctx.runtime.get('toolchain', {}) if not isinstance(runtime_toolchain, dict): @@ -115,6 +177,16 @@ def _get_toolchain_pairs(ctx: DeployContext) -> list[tuple[str, str]]: return toolchain_pairs +def _remove_esmf_include_files(include_path: Path) -> int: + removed = 0 + for prefix in ('ESMC', 'esmf'): + for filename in include_path.glob(f'{prefix}*'): + if filename.is_file(): + filename.unlink() + removed += 1 + return removed + + def _get_spack_exclude_packages(config) -> list[str]: spack_cfg = config.get('spack', {}) if not isinstance(spack_cfg, dict): From 54ad7a24281042d6f972b3e00ee0c6fdc42bb59d Mon Sep 17 00:00:00 2001 From: Xylar Asay-Davis Date: Sat, 21 Mar 2026 18:13:42 +0100 Subject: [PATCH 21/22] Add spack libs to LD_LIBRARY_PATH in post_spack() hook --- deploy/hooks.py | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/deploy/hooks.py b/deploy/hooks.py index 7be3df4fd7..c54726b25b 100644 --- a/deploy/hooks.py +++ b/deploy/hooks.py @@ -3,6 +3,7 @@ from __future__ import annotations import os +import shlex from pathlib import Path from typing import TYPE_CHECKING, Any @@ -58,6 +59,11 @@ def post_spack(ctx: DeployContext) -> None: env_name_prefix = _get_spack_env_name_prefix(ctx) for compiler, mpi in _get_toolchain_pairs(ctx): env_name = f'{env_name_prefix}_{compiler}_{mpi}' + _set_ld_library_path_for_spack_env( + ctx=ctx, + spack_path=spack_path, + env_name=env_name, + ) include_path = Path( spack_path, 'var', @@ -187,6 +193,38 @@ def _remove_esmf_include_files(include_path: Path) -> int: return removed +def _set_ld_library_path_for_spack_env( + ctx: DeployContext, spack_path: str, env_name: str +) -> None: + from mache.deploy.bootstrap import check_call + + setup_env = Path(spack_path) / 'share' / 'spack' / 'setup-env.sh' + commands = ' && '.join( + [ + f'source {shlex.quote(str(setup_env))}', + f'spack env activate {shlex.quote(env_name)}', + 'spack config add ' + 'modules:prefix_inspections:lib:[LD_LIBRARY_PATH]', + 'spack config add ' + 'modules:prefix_inspections:lib64:[LD_LIBRARY_PATH]', + ] + ) + check_call( + commands, + log_filename=_get_log_filename(ctx), + quiet=bool(getattr(ctx.args, 'quiet', False)), + ) + + +def _get_log_filename(ctx: DeployContext) -> str: + for handler in ctx.logger.handlers: + base_filename = getattr(handler, 'baseFilename', None) + if base_filename: + return str(base_filename) + + return str(Path(ctx.work_dir) / 'logs' / 'mache_deploy_run.log') + + def _get_spack_exclude_packages(config) -> list[str]: spack_cfg = config.get('spack', {}) if not isinstance(spack_cfg, dict): From 96be87408381011ecf7b68f7352c8f6f2b4ddb9e Mon Sep 17 00:00:00 2001 From: Xylar Asay-Davis Date: Sat, 21 Mar 2026 22:06:51 +0100 Subject: [PATCH 22/22] Update to albany tag compass-2026-03-21 --- deploy/pins.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deploy/pins.cfg b/deploy/pins.cfg index f3aa308172..3a2dddb7a3 100644 --- a/deploy/pins.cfg +++ b/deploy/pins.cfg @@ -11,7 +11,7 @@ parallelio = 2.6.9 # pins for the spack environment [spack] -albany = compass-2026-03-10 +albany = compass-2026-03-21 albany_variants = +mpas~py+unit_tests # cmake newer than 3.27.0 needed for Trilinos cmake = 3.27.0: