diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index bd6e288cae2..86d4180232f 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -38,6 +38,16 @@ jobs: permissions: packages: write steps: + - name: Free Disk Space (Ubuntu) + uses: jlumbroso/free-disk-space@main + with: + tool-cache: true + android: true + dotnet: true + haskell: true + large-package: true + docker-images: true + swap-storage: true - name: Checkout code uses: actions/checkout@v4 - name: Set up QEMU @@ -62,8 +72,8 @@ jobs: - name: Build and push uses: docker/build-push-action@v6 with: - target: base context: docker/ + file: docker/base/Dockerfile push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} @@ -123,6 +133,10 @@ jobs: with: path: cesm/cime submodules: "true" + - name: Install uv + uses: astral-sh/setup-uv@v6 + with: + python-version: ${{ matrix.python-version }} - name: Run tests shell: bash working-directory: cesm/cime @@ -132,16 +146,13 @@ jobs: CIME_TEST_PLATFORM: "ubuntu-latest" SKIP_ENTRYPOINT: "true" run: | - source /opt/conda/etc/profile.d/conda.sh - - conda activate cesm - - mamba install -y 'python=${{ matrix.python-version }}' - - pip install -r test-requirements.txt + uv venv + source .venv/bin/activate + uv pip install -r test-requirements.txt - # GitHub runner home is different than container - cp -rf /home/cime/.cime /github/home/ + set -x + unset SKIP_SETUP + USER_ID=`id -u` GROUP_ID=`id -g` SKIP_COMMAND="true" source /entrypoint.sh pytest -vvv --cov=CIME --cov-branch --cov-report=xml --machine docker CIME/tests/test_unit* - name: Upload coverage reports to Codecov @@ -214,6 +225,10 @@ jobs: with: path: /home/cime/inputdata key: inputdata-2 + - name: Install uv + uses: astral-sh/setup-uv@v6 + with: + python-version: 3.12 - name: Run tests shell: bash working-directory: ${{ matrix.model.name }}/cime @@ -221,32 +236,22 @@ jobs: CIME_MODEL: ${{ matrix.model.name }} CIME_DRIVER: ${{ matrix.driver }} CIME_TEST_PLATFORM: ubuntu-latest - SKIP_ENTRYPOINT: "true" + SKIP_SETUP: "true" run: | - source /opt/conda/etc/profile.d/conda.sh - - conda activate ${{ matrix.model.name }} - - pip install -r test-requirements.txt - - source /entrypoint.sh - - if [[ "${{ matrix.model.name }}" == "e3sm" ]]; then - fix_mct_makefiles ../externals/mct - fi - - # GitHub runner home is different than container - cp -rf /home/cime/.cime /github/home/ - - if [[ "${CIME_MODEL}" == "e3sm" ]]; then - ln -sf /github/home/.cime/config_machines.v2.xml /github/home/.cime/config_machines.xml - else - ln -sf /github/home/.cime/config_machines.v3.xml /github/home/.cime/config_machines.xml - fi + set -x + unset SKIP_SETUP + USER_ID=`id -u` GROUP_ID=`id -g` SKIP_COMMAND="true" source /entrypoint.sh + # Required for testing that uses case git feature git config --global user.name "${{ github.actor }}" git config --global user.email "${{ github.actor }}@users.noreply.github.com" + source /opt/spack-environment/activate.sh + + uv venv + source .venv/bin/activate + uv pip install -r test-requirements.txt + pytest -vvv --cov=CIME --cov-branch --cov-report=xml --machine docker --no-fortran-run --no-teardown CIME/tests/test_sys* - name: Upload coverage reports to Codecov uses: codecov/codecov-action@v4 diff --git a/.gitignore b/.gitignore index a1c5112c573..fe217809213 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,11 @@ _build/ dist/ .coverage +# Ignore env directories +.venv +.env +.spack-env + # Ignore emacs backup files *~ diff --git a/doc/source/contributing-guide.rst b/doc/source/contributing-guide.rst index c479ed864ca..d697f86d8ba 100644 --- a/doc/source/contributing-guide.rst +++ b/doc/source/contributing-guide.rst @@ -9,13 +9,17 @@ Contributing Guide Introduction ------------ -The `Case` class is the core of the CIME Case Control system. All interactions with a case are performed through this class. The variables used to create and manipulate a case are defined in XML files, and for each XML file, there is a corresponding Python class to interact with it. +The Community Earth System Model (CIME) is a powerful, community-driven framework designed to support the development, configuration, and execution of Earth system models. As an open-source project, CIME thrives on the contributions of researchers, developers, and users worldwide. Whether you’re fixing a bug, adding new features, improving documentation, or sharing your expertise, your involvement helps advance the scientific community’s ability to understand and simulate Earth’s complex systems. This guide provides everything you need to get started—from setting up your development environment to submitting your first pull request—so you can contribute with confidence and join a vibrant community dedicated to scientific collaboration and innovation. -XML files that are part of the CIME distribution and are intended to be read-only with respect to a case are typically named `config_something.xml`. The corresponding Python class is named `Something` and can be found in the file `CIME.XML.something.py`. These are referred to as the CIME config classes. +To get started, you can contribute by fixing issues or answering questions in the community forums. If you’re ready to dive deeper and contribute code, consider exploring these key areas: -XML files that are part of a case and thus are read/write to a case are typically named `env_whatever.xml`. The corresponding Python modules are `CIME.XML.env_whatever.py`, and the classes are named `EnvWhatever`. These are referred to as the Case env classes. +- The Case class, which is central to the CIME Configuration System (CCS); +- XML classes, which form the backbone of CCS configuration; +- SystemsTest, used to test models under specific conditions. -The `Case` class includes an array of the Case env classes. In the `configure` function and its supporting functions, the case object creates and manipulates the Case env classes by reading and interpreting the CIME config classes. +**As you add features or bug fixes, remember the importance of adding unit tests.** + +Unit tests help ensure that each new change works as intended and doesn’t break existing functionality. By isolating and verifying small units of code, you catch bugs early, reduce the risk of regressions, and make your codebase more maintainable. Unit tests also act as living documentation, clarifying expected behavior for future developers and enabling safer refactoring. Investing in unit tests saves time, improves code quality, and builds confidence in your software’s reliability. .. _contributing-guide-running-tests: @@ -23,94 +27,53 @@ Testing ------- CIME splits it's testing into two categories `unit` and `sys`. -The `unit` category covers both doctests and unit tests. While the `sys` category covers regression tests. The tests are named accordingly e.g. `unit` tests are found as `CIME/tests/test_unit*`. - -How to run the tests -``````````````````````` -There are two possible methods to run these tests. - -.. warning:: - - scripts_regression_tests.py is deprecated and will be removed in the future. +- The `unit` category covers both doctests and unit tests. +- The `sys` category covers regression tests. -pytest -:::::: -CIME supports running tests using `pytest`. By using `pytest` coverage reports are automatically generated. `pytest` supports all the same arguments as `scripts_regression_tests.py`, see `--help` for details. +The tests are named accordingly e.g. `unit` tests are found as `CIME/tests/test_unit*`. -To get started install `pytest` and `pytest-cov`. - -.. code-block:: bash - - pip install pytest pytest-cov - # or - # conda install -c conda-forge pytest pytest-cov +Running the tests +````````````````` +There are two possible methods to run these tests; ``pytest`` and ``scripts_regression_tests``. -Examples -........ -Runing all the ``sys`` and ``unit`` tests. +To run the tests with ``pytest`` you'll need to install some packages; ``pytest`` and ``pytest-cov``. -.. code-block:: bash +.. warning:: - pytest + The scripts_regression_tests.py will be deprecated and the preferred method of running tests it ``pytest``. -Running only ``sys`` tests, ``sys`` can be replaced with ``unit`` to run only unit testing. +Unit Testing +:::::::::::: +Here's an example for running the unit tests using both methods. .. code-block:: bash - pytest CIME/tests/test_sys.* - -Runnig a specific test case. + pytest CIME/tests/test_unit* + .. code-block:: bash - pytest CIME/tests/test_unit_case.py + python CIME/tests/scripts_regression_tests.py CIME/tests/test_unit* -A specific test can be ran with the followin. +System Testing +:::::::::::::: .. code-block:: bash - pytest CIME/tests/test_unit_case.py::TestCaseSubmit::test_check_case - - -scripts_regression_tests.py -::::::::::::::::::::::::::: -The `scripts_regression_tests.py` script is located under `CIME/tests`. - -You can pass either the module name or the file path of a test. - -Examples -........ -Runing all the ``sys`` and ``unit`` tests. - -.. code-block:: bash - - python CIME/tests/scripts_regression_tests.py - -Running only ``sys`` tests, ``sys`` can be replaced with ``unit`` to run only unit testing. + pytest CIME/tests/test_sys* .. code-block:: bash python CIME/tests/scripts_regression_tests.py CIME/tests/test_sys* -Runnig a specific test case. - -.. code-block:: bash - - python CIME/tests/scripts_regression_tests.py CIME.tests.test_unit_case - -A specific test can be ran with the followin. - -.. code-block:: bash - - python CIME/tests/scripts_regression_tests.py CIME.tests.test_unit_case.TestCaseSubmit.test_check_case - Code Quality ------------ -To ensure code quality we require all code to be linted by `pylint` and formatted using `black`. We run a few other tools to check XML formatting, ending files with newlines and trailing white spaces. +**To maintain high code quality and consistency, all code must be linted with Pylint and formatted using Black.** We also use additional tools to enforce proper XML formatting, ensure files end with newlines, and eliminate trailing whitespace. + +**To streamline these checks, we require the use of pre-commit.** While our GitHub Actions will automatically lint and check the format of every pull request, they will not fix issues for you—developers are responsible for resolving any linting or formatting errors. -To ensure consistency when running these checks we require the use of [`pre-commit`](https://pre-commit.com/). +**We strongly recommend installing pre-commit’s Git hooks** to run these checks automatically before each commit, helping you catch and fix issues early. -Our GitHub actions will lint and check the format of each PR but will not automatically fix any issues. It's up to the developer to resolve linting and formatting issues. We encourage installing `pre-commit`'s [Git hooks](#installing-git-hook-scripts) that will run these checks before code can be committed. Installing pre-commit ````````````````````` @@ -138,92 +101,50 @@ If you install these scripts then `pre-commit` will automatically run on `git co Docker container ---------------- -GitHub actions runs all CIME's tests in containers. The dockerfile can be found under the `docker/` directory. +CIME provides a container that is utilized by CI testing and provides developers with a consistent environment they can test in. -You can skip building the container and use the same container from the GitHub actions using the following commands. This will pull the latest [image](https://hub.docker.com/r/jasonb87/cime/tags), see the available [run modifiers](#running-the-container) to customize the container. - -The current container supports the ``GNU`` compiler and ``OpenMPI`` library. - -Running -``````` -The default environment is similar to the one used by GitHub Actions. It will clone CIME into `/src/cime`, set `CIME_MODEL=cesm` and run CESM's `checkout_externals`. This will create a minimum base environment to run both unit and system tests. +This container provides a ``GNU`` compiler, ``OpenMPI`` library and all confguration required to run CIME's testing and build simple test cases. The compiler and libraries in the container is managed using ``Anaconda``. There are currently two preloaded environments; ``e3sm`` and ``cesm``. -The `CIME_MODEL` environment vairable will change the environment that is created. - -Setting it to `E3SM` will clone E3SM into `/src/E3SM`, checkout the submodules and update the CIME repository using `CIME_REPO` and `CIME_BRANCH`. - -Setting it to `CESM` will clone CESM into `/src/CESM`, run `checkout_externals` and update the CIME repository using `CIME_REPO` and `CIME_BRANCH`. - -The container can further be modified using the environment variables defined below. - -.. code-block:: bash - - docker run -it --name cime --hostname docker cime:latest bash - - -.. code-block:: bash - - docker run -it --name cime --hostname docker -e CIME_MODEL=e3sm cime:latest bash - -.. note:: - - It's recommended when running the container to pass `--hostname docker` as it will match the custom machine defined in `config_machines.xml`. If this is omitted, `--machine docker` must be passed to CIME commands in order to use the correct machine definition. - -Environment variables -::::::::::::::::::::: - -Environment variables to modify the container environment. - -| Name | Description | Default | -| ---- | ----------- | ------- | -| INIT | Set to false to skip init | true | -| GIT_SHALLOW | Performs shallow checkouts, to save time | false | -| UPDATE_CIME | Setting this will cause the CIME repository to be updated using `CIME_REPO` and `CIME_BRANCH` | "false" | -| CIME_MODEL | Setting this will change which environment is loaded | | -| CIME_REPO | CIME repository URL | https://github.com/ESMCI/cime | -| CIME_BRANCH | CIME branch that will be cloned | master | -| E3SM_REPO | E3SM repository URL | https://github.com/E3SM-Project/E3SM | -| E3SM_BRANCH | E3SM branch that will be cloned | master | -| CESM_REPO | CESM repository URL | https://github.com/ESCOMP/CESM | -| CESM_BRANCH | CESM branch that will be cloned | master | - -Examples -:::::::: -.. code-block:: bash - - docker run -it -e INIT=false cime:latest bash +.. warning:: -.. code-block:: bash - - docker run -it -e CIME_REPO=https://github.com/user/cime -e CIME_BRANCH=updates_xyz cime:latest bash + Since CIME is generally provided as a sub-module in a projects repostiory the CIME containers do not provide either the project or CIME code. You'll need to pass these through using a volume. -Persisting data -::::::::::::::: +The provide machine configuration requires you to either set the containers hostname with ``--hostname docker`` or pass ``--machine docker`` to all CIME tooling. -The `config_machines.xml` definition as been setup to provided persistance for inputdata, cases, archives and tools. The following paths can be mounted as volumes to provide persistance. +It's usually recommended to use the ``latest`` tag but if an older version is required you can find these at https://github.com/ESMCI/cime/pkgs/container/cime. -* /storage/inputdata -* /storage/cases -* /storage/archives -* /storage/tools +Running +``````` +The following assumes you've checkouted out your projects code to ``$HOME/model`` and the name of your model is stored in ``$TARGET_MODEL`` environment variable. .. code-block:: bash - docker run -it -v ${PWD}/data-cache:/storage/inputdata cime:latest bash - -It's also possible to persist the source git repositories. + docker run -it --rm --hostname docker -v "$HOME/model":/home/cime/model -e USER_ID=`id -u` -e GROUP_ID=`id -g` -e CIME_MODEL="$TARGET_MODEL" ghcr.io/esmci/cime:latest bash -.. code-block:: bash +.. warning:: - docker run -it -v ${PWD}/src:/src cime:latest bash + The ``-e USER_ID=`id -u` -e GROUP_ID=`id- g``` is required so the containers uid/gid will match your host machines. This allows the containers user to have the correct permissions to view/edit files and if any new files are created you can access them outside the container. -Local git respositories can be mounted as well. +While running CIME within the continer there may be a need to persist either the inputdata retrieved by CIME or the cases that are created. This can be accomplished using volumes. The following example assumes inputdata will be stored at ``$HOME/CIME/inputdata`` and cases at ``$HOME/CIME/cases``. .. code-block:: bash - docker run -v ${PWD}:/src/cime cime:latest bash + docker run -it --rm --hostname docker -v "$HOME/model":/home/cime/model -v "$HOME/CIME/inputdata":/home/cime/inputdata -v "$HOME/CIME/cases":/home/cime/cases -e USER_ID=`id -u` -e GROUP_ID=`id -g` -e CIME_MODEL="$TARGET_MODEL" ghcr.io/esmci/cime:latest bash - docker run -v ${PWD}:/src/E3SM cime:latest bash +Environment variables +::::::::::::::::::::: +The following environment variables control some aspects of the cotnainers behavior. + ++------------------+--------------------------------------------+---------+ +| Name | Description | Default | ++==================+============================================+=========+ +| USER_ID | Sets the container's user id. | 1000 | ++------------------+--------------------------------------------+---------+ +| GROUP_ID | Sets the container's group id. | 1000 | ++------------------+--------------------------------------------+---------+ +| SKIP_ENTRYPOINT | Skips entering a new shell as the | false | +| | container's user. | | ++------------------+--------------------------------------------+---------+ Building ```````` @@ -236,29 +157,3 @@ The container provides 3 targets. .. code-block:: bash docker build -t ghcr.io/ESMCI/cime:latest --target docker/ - -Customizing -::::::::::: -When building the container some features can be customized. Multiple `--build-arg` arguments can be passed. - -.. code-block:: bash - - docker build -t ghcr.io/ESMCI/cime:latest --build-arg {name}={value} docker/ - -+------------------------+-----------------------------------------------+---------+ -| Argument | Description | Default | -+========================+===============================================+=========+ -| MAMBAFORGE_VERSION | Version of the condaforge/mambaforge image | 4.11.0-0| -| | used as a base | | -+------------------------+-----------------------------------------------+---------+ -| PNETCDF_VERSION | Parallel NetCDF version to build | 1.12.1 | -+------------------------+-----------------------------------------------+---------+ -| LIBNETCDF_VERSION | Version of libnetcdf, the default will | 4.8.1 | -| | install the latest | | -+------------------------+-----------------------------------------------+---------+ -| NETCDF_FORTRAN_VERSION | Version of netcdf-fortran, the default will | 4.5.4 | -| | install the latest | | -+------------------------+-----------------------------------------------+---------+ -| ESMF_VERSION | Version of ESMF, the default will install the | 8.2.0 | -| | latest | | -+------------------------+-----------------------------------------------+---------+ diff --git a/docker/.cime/config_machines.v2.xml b/docker/.cime/config_machines.v2.xml index 73b7ea56fb2..ccf08502c42 100644 --- a/docker/.cime/config_machines.v2.xml +++ b/docker/.cime/config_machines.v2.xml @@ -19,7 +19,7 @@ /home/cime/tools/cprnc make 4 - e3sm_developer + cime_developer none boutte3@llnl.gov 16 @@ -35,13 +35,15 @@ $CASEROOT/run $CASEROOT/bld + FALSE + /opt/view + /opt/view + /opt/view + + 1 1 - /opt/conda/envs/e3sm - FALSE - /opt/conda/envs/e3sm - /opt/conda/envs/e3sm - /opt/conda/envs/e3sm + /opt/view diff --git a/docker/.cime/docker.cmake b/docker/.cime/docker.cmake index f8439e5e9a2..f5cd14eb8e8 100644 --- a/docker/.cime/docker.cmake +++ b/docker/.cime/docker.cmake @@ -1,4 +1,3 @@ - if (COMP_NAME STREQUAL gptl) string(APPEND CPPDEFS " -DHAVE_NANOTIME -DBIT64 -DHAVE_SLASHPROC -DHAVE_GETTIMEOFDAY") endif() @@ -13,8 +12,8 @@ string(APPEND CMAKE_CXX_FLAGS_DEBUG " -O0") # required for grid generation tests that use make if (CMAKE_SOURCE_DIR MATCHES "^.*TestGridGeneration.*$") - string(APPEND FFLAGS " -I/opt/conda/envs/$ENV{CIME_MODEL}/include") - string(APPEND SLIBS " -L/opt/conda/envs/$ENV{CIME_MODEL} -lnetcdf -lnetcdff") + string(APPEND FFLAGS " -I/opt/views/view/include") + string(APPEND SLIBS " -L/opt/views/view/lib -lnetcdf -lnetcdff") endif() # DEBUGGING variables diff --git a/docker/.cime/docker/config_machines.xml b/docker/.cime/docker/config_machines.xml index 79fb6f4898d..f9bb2e3cd71 100644 --- a/docker/.cime/docker/config_machines.xml +++ b/docker/.cime/docker/config_machines.xml @@ -18,7 +18,7 @@ /home/cime/tools/cprnc make 4 - e3sm_developer + cime_developer none boutte3@llnl.gov 16 @@ -34,13 +34,15 @@ $CASEROOT/run $CASEROOT/bld + FALSE + /opt/view + /opt/view + /opt/view + + 1 1 - /opt/conda/envs/cesm - FALSE - /opt/conda/envs/cesm - /opt/conda/envs/cesm - /opt/conda/envs/cesm + /opt/view diff --git a/docker/Dockerfile b/docker/Dockerfile deleted file mode 100644 index b9362775bcf..00000000000 --- a/docker/Dockerfile +++ /dev/null @@ -1,96 +0,0 @@ -ARG BASE_TAG=latest -FROM condaforge/miniforge3:${BASE_TAG} AS base - -WORKDIR /home/cime - -RUN mkdir -p \ - /home/cime/inputdata/cpl/gridmaps/oQU240 \ - /home/cime/inputdata/share/domains \ - /home/cime/timings \ - /home/cime/cases \ - /home/cime/archive \ - /home/cime/baselines \ - /home/cime/tools \ - && wget -O /home/cime/inputdata/cpl/gridmaps/oQU240/map_oQU240_to_ne4np4_aave.160614.nc \ - https://portal.nersc.gov/project/e3sm/inputdata/cpl/gridmaps/oQU240/map_oQU240_to_ne4np4_aave.160614.nc \ - && wget -O /home/cime/inputdata/share/domains/domain.ocn.ne4np4_oQU240.160614.nc \ - https://portal.nersc.gov/project/e3sm/inputdata/share/domains/domain.ocn.ne4np4_oQU240.160614.nc \ - && wget -O /home/cime/inputdata/share/domains/domain.lnd.ne4np4_oQU240.160614.nc \ - https://portal.nersc.gov/project/e3sm/inputdata/share/domains/domain.lnd.ne4np4_oQU240.160614.nc - -COPY cime.yaml cime.yaml - -RUN apt-get update && \ - DEBIAN_FRONTEND=noninteractive \ - apt-get install -y --no-install-recommends \ - perl libxml-libxml-perl && \ - rm -rf /var/lib/apt/lists/* - -RUN mamba install -y gosu tree - -RUN mamba create -n e3sm -y --file cime.yaml python=3.10 'libnetcdf=4.9.1=*openmpi*' \ - && conda remove -n e3sm --force perl \ - && conda clean -afy \ - && rm -rf /opt/conda/pkgs/* - -RUN mamba create -n cesm -y --file cime.yaml python=3.10 'libnetcdf>=4.9.2=*openmpi*' \ - && conda remove -n cesm --force perl \ - && conda clean -afy \ - && rm -rf /opt/conda/pkgs/* - -COPY .cime .cime -COPY entrypoint.sh /entrypoint.sh - -ENTRYPOINT [ "/entrypoint.sh" ] - -FROM base AS slurm - -RUN apt-get update && \ - DEBIAN_FRONTEND=noninteractive \ - apt-get install -y --no-install-recommends \ - munge slurmd slurm-client slurmctld && \ - rm -rf /var/lib/apt/lists/* && \ - sed -i"" "s/\(.*\)[^<]*\(<\/BATCH_SYSTEM>\)/\1slurm\2/g" ~/.cime/config_machines.xml - -COPY slurm/slurm.conf /etc/slurm-llnl/ -COPY slurm/config_batch.xml /root/.cime/ -COPY slurm/entrypoint_batch.sh /entrypoint_batch.sh - -FROM base AS pbs - -RUN apt-get update && \ - DEBIAN_FRONTEND=noninteractive \ - apt-get install -y --no-install-recommends \ - curl ca-certificates software-properties-common \ - gcc make libtool libhwloc-dev libx11-dev libxt-dev libedit-dev \ - libical-dev ncurses-dev python-dev tcl-dev tk-dev swig libexpat-dev libssl-dev \ - libxext-dev libxft-dev autoconf automake \ - postgresql-12 postgresql-server-dev-all postgresql-contrib \ - expat libedit2 python3 sendmail-bin sudo tcl tk && \ - add-apt-repository ppa:deadsnakes/ppa && \ - apt-get update && \ - DEBIAN_FRONTEND=noninteractive \ - apt-get install -y python3.7 python3.7-dev && \ - rm -rf /var/lib/apt/lists/* - -RUN mkdir /src && pushd /src && \ - curl -LO https://github.com/openpbs/openpbs/archive/refs/tags/v20.0.1.tar.gz && \ - tar -xvf v20.0.1.tar.gz && \ - cd openpbs-20.0.1 && \ - sed -i"" 's/\(#include "list_link.h"\)/\1\n#include /' /src/openpbs-20.0.1/src/lib/Libifl/list_link.c && \ - export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin && \ - ./autogen.sh && \ - PYTHON=/usr/bin/python3.7 \ - CFLAGS="`/usr/bin/python3.7m-config --cflags`" \ - LDFLAGS="`/usr/bin/python3.7m-config --ldflags`" \ - LIBS="-lpthread -lm -lpython3.7m" \ - ./configure --prefix=/opt/pbs && \ - make -j8 && \ - make install && \ - popd && \ - rm -rf /src && \ - sed -i"" "s/\(.*\)[^<]*\(<\/BATCH_SYSTEM>\)/\1pbs\2/g" ~/.cime/config_machines.xml - -COPY pbs/pbs.conf /etc/ -COPY pbs/config_batch.xml /root/.cime/ -COPY pbs/entrypoint_batch.sh /entrypoint_batch.sh diff --git a/docker/base/Dockerfile b/docker/base/Dockerfile new file mode 100644 index 00000000000..45d82984be1 --- /dev/null +++ b/docker/base/Dockerfile @@ -0,0 +1,84 @@ +# Build stage with Spack pre-installed and ready to be used +FROM spack/ubuntu-jammy:develop AS builder + +# Install OS packages needed to build the software +RUN apt-get -yqq update && apt-get -yqq upgrade \ + && apt-get -yqq install gcc-11 g++-11 gfortran-11 \ + && rm -rf /var/lib/apt/lists/* + +# What we want to install and how we want to install it +# is specified in a manifest file (spack.yaml) +RUN mkdir -p /opt/spack-environment && \ +set -o noclobber \ +&& (echo spack: \ +&& echo ' specs:' \ +&& echo ' - hdf5@1.10.7+cxx+fortran+hl' \ +&& echo ' - netcdf-c@4.7.4+parallel-netcdf' \ +&& echo ' - netcdf-fortran@4.5.3' \ +&& echo ' - parallel-netcdf@1.11.0' \ +&& echo ' - intel-oneapi-mkl@2022.1.0+gfortran' \ +&& echo ' - openmpi@4.1.6' \ +&& echo ' - esmf' \ +&& echo ' config:' \ +&& echo ' template_dirs:' \ +&& echo ' - ./../templates' \ +&& echo '' \ +&& echo ' install_tree:' \ +&& echo ' root: /opt/software' \ +&& echo ' concretizer:' \ +&& echo ' unify: true' \ +&& echo ' view: /opt/views/view') > /opt/spack-environment/spack.yaml + +# Install the software, remove unnecessary deps +RUN cd /opt/spack-environment && spack env activate . && spack install --fail-fast && spack gc -y + +# Strip all the binaries +RUN find -L /opt/views/view/* -type f -exec readlink -f '{}' \; | \ + xargs file -i | \ + grep 'charset=binary' | \ + grep 'x-executable\|x-archive\|x-sharedlib' | \ + awk -F: '{print $1}' | xargs strip + +# Modifications to the environment that are necessary to run +RUN cd /opt/spack-environment && \ + spack env activate --sh -d . > activate.sh + + +# Bare OS image to run the installed executables +FROM ubuntu:22.04 + +COPY --from=builder /opt/spack-environment /opt/spack-environment +COPY --from=builder /opt/software /opt/software +COPY --from=builder /opt/views /opt/views + +RUN { \ + echo '#!/bin/sh' \ + && echo '.' /opt/spack-environment/activate.sh \ + && echo 'exec "$@"'; \ + } > /entrypoint.sh \ +&& chmod a+x /entrypoint.sh \ +&& ln -s /opt/views/view /opt/view + + +RUN apt-get -yqq update && apt-get -yqq upgrade \ + && apt-get -yqq install vim python3 python3-pip cmake make gosu wget curl subversion git libxml2-utils gcc-11 g++-11 gfortran-11 \ + && rm -rf /var/lib/apt/lists/* + + +RUN groupadd -g 1000 cime \ + && useradd -g 1000 -m -u 1000 cime \ + && update-alternatives --install /usr/bin/gfortran gfortran /usr/bin/gfortran-11 100 \ + && update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-11 100 + +COPY .cime /home/cime/.cime +COPY entrypoint.sh /entrypoint.sh + +RUN SKIP_SETUP=true bash -c "source /entrypoint.sh && download_inputdata" \ + && pip install uv \ + && uv python install 3.10 + +WORKDIR /home/cime + +ENTRYPOINT [ "/entrypoint.sh" ] +CMD [ "/bin/bash" ] + diff --git a/docker/base/spack.yaml b/docker/base/spack.yaml new file mode 100644 index 00000000000..e75fcd028b4 --- /dev/null +++ b/docker/base/spack.yaml @@ -0,0 +1,42 @@ +spack: + specs: + - hdf5@1.10.7+cxx+fortran+hl + - netcdf-c@4.7.4+parallel-netcdf + - netcdf-fortran@4.5.3 + - parallel-netcdf@1.11.0 + - intel-oneapi-mkl@2022.1.0+gfortran + - openmpi@4.1.6 + - esmf + + config: + template_dirs: + - ./../templates + + container: + images: + build: spack/ubuntu-jammy:develop + final: ubuntu:22.04 + + os_packages: + command: apt + build: + - gcc-11 + - g++-11 + - gfortran-11 + final: + - vim + - python3 + - python3-pip + - cmake + - make + - gosu + - wget + - curl + - subversion + - git + - libxml2-utils + - gcc-11 + - g++-11 + - gfortran-11 + + template: container/default diff --git a/docker/cime.yaml b/docker/cime.yaml deleted file mode 100644 index 52d58039fc1..00000000000 --- a/docker/cime.yaml +++ /dev/null @@ -1,40 +0,0 @@ -name: base -channels: - - conda-forge -dependencies: -# development tools - - cmake<4.0 # some libraries still require 3.5.x - - make - - wget - - curl - - subversion - - m4 - - pkg-config - - vim - - rsync - - openssh - - tree -# python packages - - pytest - - pytest-cov - - pyyaml -# libraries - - lapack - - blas - # Testing hdf5 is out of scope for CIME - # - hdf5<1.14.0=*openmpi* - - netcdf-fortran=*=*openmpi* - - libpnetcdf=*=*openmpi* - - esmf=*=*openmpi* - - openmpi - - gcc_linux-64 - - gxx_linux-64 - - gfortran_linux-64 - - gcc=12.* - - gxx=12.* - - gfortran=12.* - - compilers - - c-compiler - - cxx-compiler - - fortran-compiler -prefix: /opt/conda diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index 5e78a7c5ccf..f2c6c65c27c 100755 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -1,12 +1,33 @@ #!/bin/bash -export USER=root -export LOGNAME=root export USER_ID=${USER_ID:-1000} export GROUP_ID=${GROUP_ID:-1000} -SKIP_ENTRYPOINT="${SKIP_ENTRYPOINT:-false}" +HOME_DIR="$(getent passwd ${USER_ID} | cut -d':' -f6)" +SKIP_SETUP="${SKIP_SETUP:-false}" +SKIP_COMMAND="${SKIP_COMMAND:-false}" +echo "Container configuration" +echo "USER_ID: ${USER_ID}" +echo "GROUP_ID: ${GROUP_ID}" +echo "HOME_DIR: ${HOME_DIR}" +echo "SKIP_SETUP: ${SKIP_SETUP}" +echo "SKIP_COMMAND: ${SKIP_COMMAND}" + +function download_inputdata() { + mkdir -p /home/cime/inputdata/cpl/gridmaps/oQU240 \ + /home/cime/inputdata/cpl/gridmaps/gx1v6 \ + /home/cime/inputdata/share/domains + + wget -O /home/cime/inputdata/cpl/gridmaps/oQU240/map_oQU240_to_ne4np4_aave.160614.nc \ + https://portal.nersc.gov/project/e3sm/inputdata/cpl/gridmaps/oQU240/map_oQU240_to_ne4np4_aave.160614.n + wget -O /home/cime/inputdata/share/domains/domain.ocn.ne4np4_oQU240.160614.nc \ + https://portal.nersc.gov/project/e3sm/inputdata/share/domains/domain.ocn.ne4np4_oQU240.160614.nc + wget -O /home/cime/inputdata/share/domains/domain.lnd.ne4np4_oQU240.160614.nc \ + https://portal.nersc.gov/project/e3sm/inputdata/share/domains/domain.lnd.ne4np4_oQU240.160614.nc +} + +# Only required by E3SM for mct function fix_mct_makefiles() { fix_arflags "${1}/mct/Makefile" fix_arflags "${1}/mpeu/Makefile" @@ -17,42 +38,79 @@ function fix_arflags() { if [[ ! -e "${1}.bak" ]]; then echo "Fixing AR variable in ${1}" - sed -i".bak" "s/\$(AR)/\$(AR) \$(ARFLAGS)/g" "${1}" + sed -i".bak" "s/\$(AR)/\$(AR) cq/g" "${1}" fi } -if [[ "${SKIP_ENTRYPOINT}" == "false" ]]; then +function link_config_machines() { + local src_path="/home/cime/.cime" + local dst_path="${1}" + if [[ "${CIME_MODEL}" == "e3sm" ]]; then - ln -sf /home/cime/.cime/config_machines.v2.xml /home/cime/.cime/config_machines.xml + echo "Linking E3SM ${src_path}/config_machines.v2.xml -> ${dst_path}/.cime/config_machines.xml" + + ln -sf "${src_path}/config_machines.v2.xml" "${dst_path}/.cime/config_machines.xml" elif [[ "${CIME_MODEL}" == "cesm" ]]; then - export ESMFMKFILE=/opt/conda/envs/cesm/lib/esmf.mk + echo "Link CESM ${src_path}/config_machines.v3.xml -> ${dst_path}/.cime/config_machines.xml" - ln -sf /home/cime/.cime/config_machines.v3.xml /home/cime/.cime/config_machines.xml + ln -sf "${src_path}/config_machines.v3.xml" "${dst_path}/.cime/config_machines.xml" fi +} + +function fix_permissions() { + echo "Changing permissions to ${USER_ID}:${GROUP_ID} for ${1}" + + chown -R "${USER_ID}":"${GROUP_ID}" "${1}" +} + +if [[ "${SKIP_SETUP}" == "false" ]]; then + # will always be /home/cime due to config_machines.xml + mkdir -p /home/cime/{timings,cases,archive,baselines,tools} if [[ "${USER_ID}" == "0" ]]; then - cp -rf /home/cime/.cime /root/ + export USER=root + export LOGNAME=root - git config --global --add safe.directory "*" + echo "Copying /home/.cime -> ${HOME_DIR}/" - exec "${@}" + if [[ "${GITHUB_ACTIONS:-false}" == "true" ]]; then + cp -rf /home/cime/.cime "${HOME}/" + else + cp -rf /home/cime/.cime "${HOME_DIR}/" + fi else - groupmod -g "${GROUP_ID}" -n cime ubuntu - usermod -d /home/cime -u "${USER_ID}" -g "${GROUP_ID}" -l cime ubuntu + export USER=cime + export LOGNAME=cime - chown -R cime:cime /home/cime + echo "Updating cime uid/gid to ${USER_ID}:${GROUP_ID}" - if [[ -n "${SRC_PATH}" ]] && [[ -e "${SRC_PATH}" ]]; then - chown -R cime:cime "${SRC_PATH}" + # update the uid/gid for cime + groupmod -g "${GROUP_ID}" cime + usermod -u "${USER_ID}" cime + fi - git config --global --add safe.directory "*" - fi + if [[ "${GITHUB_ACTIONS:-false}" == "true" ]]; then + link_config_machines ${HOME} + else + link_config_machines ${HOME_DIR} + fi + + git config --global --add safe.directory "*" - { - echo "source /opt/conda/etc/profile.d/conda.sh" - echo "conda activate base" - } > /home/cime/.bashrc + cat << EOF > "${HOME_DIR}/.bashrc" +source /opt/spack-environment/activate.sh +EOF + + if [[ "${USER_ID}" == "0" ]]; then + [[ "${SKIP_COMMAND}" == "false" ]] && exec "${@}" || true + else + fix_permissions /opt + fix_permissions /home/cime + + if [[ -n "${SRC_PATH}" && -e "${SRC_PATH}" ]]; then + fix_permissions "${SRC_PATH}" + fi - gosu "${USER_ID}" "${@}" + [[ "${SKIP_COMMAND}" ]] && gosu "${USER_ID}":"${GROUP_ID}" "${@}" || true fi fi diff --git a/docker/templates/container/default b/docker/templates/container/default new file mode 100644 index 00000000000..4fba44fe1df --- /dev/null +++ b/docker/templates/container/default @@ -0,0 +1,19 @@ +{% extends "container/Dockerfile" %} +{% block final_stage %} +{{ super() }} + +RUN groupadd -g 1000 cime \ + && useradd -g 1000 -m -u 1000 cime \ + && update-alternatives --install /usr/bin/gfortran gfortran /usr/bin/gfortran-11 100 \ + && update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-11 100 + +COPY .cime /home/cime/.cime +COPY entrypoint.sh /entrypoint.sh + +RUN SKIP_SETUP=true bash -c "source /entrypoint.sh && download_inputdata" \ + && pip install uv \ + && uv python install 3.10 + +WORKDIR /home/cime + +{% endblock %} diff --git a/test-requirements.txt b/test-requirements.txt index d4abdec4d0f..aa4c0b1701a 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -1 +1,3 @@ +pytest +pytest-cov evv4esm