From 14e83a121d59e2db1f1deb84a17413a05b816459 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-No=C3=ABl=20Grad?= Date: Thu, 4 Dec 2025 20:55:05 +0100 Subject: [PATCH 1/2] GitHub CI/CD workflows with EESSI --- .../posts/2025/12/gh-ci-workflow-for-EESSI.md | 428 ++++++++++++++++++ 1 file changed, 428 insertions(+) create mode 100644 docs/blog/posts/2025/12/gh-ci-workflow-for-EESSI.md diff --git a/docs/blog/posts/2025/12/gh-ci-workflow-for-EESSI.md b/docs/blog/posts/2025/12/gh-ci-workflow-for-EESSI.md new file mode 100644 index 0000000000..7387197b8a --- /dev/null +++ b/docs/blog/posts/2025/12/gh-ci-workflow-for-EESSI.md @@ -0,0 +1,428 @@ +--- +authors: [jngrad] +date: 2025-12-04 +slug: gh-ci-workflow-for-EESSI +render_macros: false +--- + +# GitHub CI/CD workflows with EESSI + +In a previous blog post, [“An example CI workflow that leverages EESSI CI tools”](https://www.eessi.io/docs/blog/2024/10/11/ci-workflow-for-EESSI/), +Pedro Santos Neves explained how to set up a GitLab CI workflow. +This post will focus on GitHub CI workflows and show how to access the development repository of EESSI. + +## Using the CI component in GitHub + +We will use the [pyMBE](https://github.com/pyMBE-dev/pyMBE)[^Beyer2024] and +[SwarmRL](https://github.com/SwarmRL/SwarmRL)[^Tovey2025] projects as examples. +They both rely on the molecular dynamics simulation package +[ESPResSo](https://github.com/espressomd/espresso)[^Weik2019] available in EESSI. +SwarmRL requires features only available in the development version of ESPResSo, +while pyMBE supports both the last stable release of ESPResSo and the development branch of ESPResSo. +EESSI can satisfy both communities: [`software.eessi.io`](https://software.eessi.io) +provides stable releases of scientific software identified by their version number, +while [`dev.eessi.io`](https://dev.eessi.io) provides development snapshots identified by a commit hash. + +Historically, both SwarmRL and pyMBE had to build ESPResSo from sources in every CI job. +This added 15 min of build time and required extra steps to properly install build dependencies (pyMBE) +or configure a custom Docker image (SwarmRL). +Both projects migrated to the [EESSI GitHub Action](https://github.com/marketplace/actions/eessi) +to reduce the complexity and execution times of their CI/CD workflows. + +### Quickstart + +The SwarmRL project uses a compact CI/CD workflow[^swarmrl-commit-181edfa-cicd-workflow] +that loads project dependencies from EESSI, installs extra Python dependencies +in a virtual environment, runs the testsuite, and uploads a code coverage report. +It is reproduced here, simplified for clarity, with annotations: + +```yaml linenums="1" title="CI/CD workflow definition" +name: ESPResSo tests +on: + pull_request: + push: + branches: +jobs: + testsuite: + runs-on: ubuntu-latest # (1) + steps: + - uses: actions/checkout@v4 # (2) + - name: Setup EESSI + uses: eessi/github-action-eessi@v3 # (3) + with: + eessi_stack_version: "2023.06" + - name: Install dependencies # (4) + run: | + test "${RUNNER_ARCH}" = "X64" && module use /cvmfs/dev.eessi.io/espresso/versions/2023.06/software/linux/x86_64/amd/zen2/modules/all + module load ESPResSo/dc87ede3f6c218bb71624460752bc8c26a271c33-foss-2023b + module save espresso + python -m venv --system-site-packages venv + source venv/bin/activate + python -m pip install . + - name: Run test suite # (5) + run: | + module restore espresso + source venv/bin/activate + export NUM_PROC=$(nproc) + export OMP_PROC_BIND=false OMP_NUM_THREADS=1 + COVERAGE=1 sh CI/run_espresso_test_suite.sh -j ${NUM_PROC} + python -m coverage combine . CI/espresso_tests + python -m coverage report + python -m coverage xml + - name: Upload coverage to Codecov # (6) + if: ${{ github.repository == 'SwarmRL/SwarmRL' }} + uses: codecov/codecov-action@v5 + with: + files: "./coverage.xml" + disable_search: true + env_vars: OS,PYTHON + token: ${{ secrets.CODECOV_TOKEN }} +``` + +1. Run the testsuite in a virtual machine with the latest Ubuntu operating system, using an AMD Zen3 host machine. +2. Locally clone the repository and checkout the branch that triggered the workflow. +3. Set up the EESSI software stack version 2023.06 for the automatically-detected microarchitecture of the host machine. +4. Set up project dependencies: + * activate the `dev.eessi.io` development repository using version 2023.06 and microarchitecture AMD Zen2 (line 17 not required when only `software.eessi.io` is needed) + * load the ESPResSo package from commit hash [`dc87ede`](https://github.com/espressomd/espresso/commit/dc87ede3f6c218bb71624460752bc8c26a271c33) + * create a Python virtual environment and install project dependencies not available in EESSI +5. Reload the software environment and run the testsuite with code coverage enabled. +6. Publish the code coverage report. + +This workflow is transferable to other software projects with minimal changes. +For a Python project, it is only a matter of substituting lines 18 and 29 by the appropriate commands. +For projects in other programming languages, Python-specific commands can be safely removed. + +When a workflow grows in complexity, it might be desirable to decouple +the dependencies installation step from the testsuite step. +One can leverage the modular architecture of GitHub Action workflows +and delegate dependencies management to a self-contained action +that can be called from the CI/CD workflow. +We will explore this strategy in the next section. + +### A custom action to manage dependencies + +Actions are reusable building blocks that are called from a workflow. +We already saw the EESSI action and the Codecov action in the previous section. +[Custom actions](https://docs.github.com/en/actions/concepts/workflows-and-actions/custom-actions) +use the same syntax and structure as workflows, but are stored in a different folder: +actions go to `.github/actions/my_action/action.yml`, +while workflows go to `.github/workflows/my_workflow.yml`. + +Here is how pyMBE defines a custom action to manage dependencies with EESSI and pip[^pymbe-tag-100-deps-action]: + +```yaml linenums="1" title="Custom action to manage dependencies" +name: 'dependencies' +description: 'Install project dependencies' +inputs: + modules: + description: Newline-separated list of arguments for module load. + required: true + extra-python-packages: + description: Newline-separated list of arguments for pip install. + required: false +runs: + using: "composite" + steps: + - run: | + test "${RUNNER_ARCH}" = "X64" && module use /cvmfs/dev.eessi.io/espresso/versions/2023.06/software/linux/x86_64/amd/zen2/modules/all + module load ${{ inputs.modules }} + module save espresso + python3 -m venv --system-site-packages venv + source venv/bin/activate + echo -e "\n" >> requirements.txt + echo "${{ inputs.extra-python-packages }}" >> requirements.txt + python3 -m pip install -r requirements.txt + git restore requirements.txt + deactivate + module purge + shell: bash +``` + +A workflow that calls this action will load software from an EESSI stack (argument `modules`) +and install Python packages from Pypi (argument `extra-python-packages`). +Packages are managed by a Lmod [module collection](https://lmod.readthedocs.io/en/latest/010_user.html#rules-for-loading-modules-from-a-collection) +and by a Python [virtual environment](https://docs.python.org/3/library/venv.html). + +The steps can be broken down as follows: + +* source the `dev.eessi.io` development repository, which extends the `software.eessi.io` production repository already sourced by the EESSI GitHub Action + * line 14 can be removed when only the `software.eessi.io` repository is needed + * microarchitecture `x86_64/amd/zen2` is selected, which is forward-compatible with the Zen3 microarchitecture + available on [standard GitHub-hosted runners](https://docs.github.com/en/actions/reference/runners/github-hosted-runners#standard-github-hosted-runners-for-public-repositories) + * Ubuntu ARM64 runners have microarchitecture `aarch64/nvidia/grace` +* load the list of EESSI modules provided by the workflow and stash them to a collection called `espresso` +* create a Python virtual environment that gives priority to Python packages available in EESSI + (`venv` option `--system-site-packages`, other package managers have a different syntax) +* source the Python virtual environment +* paste the list of Python package requirements from the workflow into the project's + `requirements.txt`, pip install them, and restore `requirements.txt` +* clean up the session + +This action allows us to “hide” the logic for package installation, +and should only be edited by project maintainers. +Project developers can ignore this file and focus on the workflow, described in the next section. + +### A modular CI/CD workflow + +We will define a CI/CD workflow that sets up project dependencies, runs the testsuite, +uploads a code coverage report, and builds the software documentation for deployment +to [GitHub Pages](https://docs.github.com/en/pages/configuring-a-custom-domain-for-your-github-pages-site/about-custom-domains-and-github-pages). +Since the file is rather long, we will break it down into reusable code fragments and go through them with the help +of the [workflow syntax for GitHub Actions](https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax) reference page. + +We start by defining a workflow called `testsuite` that is automatically triggered +when pushing a commit to a branch (`push` event) or updating a pull request (`pull_request` event): + +```yaml linenums="1" title="Workflow general properties" +name: testsuite +on: + push: + pull_request: +permissions: + contents: read # to fetch code (actions/checkout) +``` + +We then define a job to run in an Ubuntu 24.04 virtual machine: + +```yaml linenums="7" title="Virtual machine settings" +jobs: + ubuntu: + runs-on: ubuntu-24.04 + env: + FI_PROVIDER: "^psm3,psm3;ofi_rxd" + OMPI_MCA_mtl_ofi_provider_exclude: psm3 +``` + +Since details about the host machine hardware can be fuzzy in a virtual machine, +we need to help OpenMPI select a suitable communication mechanism with environment variables (`env` field). +Other MPI implementations and virtual machines may require their own workarounds. + +Next, we define a [matrix strategy](https://docs.github.com/en/actions/how-tos/write-workflows/choose-what-workflows-do/run-job-variations) +to automatically create jobs with different input parameters: + +```yaml linenums="13" title="Job matrix configuration" + strategy: + matrix: + espresso: + - version: "4.2.2" + eessi: ESPResSo/4.2.2-foss-2023a + - version: "5.0-dev" + eessi: ESPResSo/8aa60cecd56cdd10ab62042c567552f347374f36-foss-2023b + name: ubuntu - ESPResSo ${{ matrix.espresso.version }} +``` + +In this case, we simply define a flat list with two elements, one for each ESPResSo version, +and give the job a name that reflects the input parameters combination. +The syntax also accepts lists of values, in which case the *n*-fold Cartesian product would be evaluated. + +We can now define the steps that will be executed by every job of the job matrix: + +```yaml linenums="21" title="Cloning the repository and setting up project dependencies" + steps: + - name: Checkout repository + uses: actions/checkout@main + - name: Setup EESSI + uses: eessi/github-action-eessi@v3 + with: + eessi_stack_version: "2023.06" + - name: Install dependencies + uses: ./.github/actions/dependencies + with: + modules: |- + ${{ matrix.espresso.eessi }} + extra-python-packages: |- + pylint==3.0.3 + coverage==7.4.4 + pdoc==15.0.3 +``` + +We start by cloning the git repository and checking out the branch that triggered the workflow. +We then set up the EESSI 2023.06 stack version (it needs to match the one defined in our custom action +in the [previous section](#a-custom-action-to-manage-dependencies)). +Finally, we use our custom action to install dependencies, +using the job matrix parameters to select a specific ESPResSo version +and providing a list of extra packages for code linting, code coverage and documentation generation. + +We now have a complete environment to run the actual testsuite: + +```yaml linenums="37" title="Testing the code" + - name: Run testsuite + run: | + module restore espresso + source venv/bin/activate + make pylint + make unit_tests -j4 COVERAGE=1 + make docs + make coverage_xml + deactivate + module purge + shell: bash +``` + +This step: + +* reloads the module collection called `espresso` (which contains ESPResSo and its dependencies, such as Python and NumPy) + and sources the Python virtual environment (which contains tools for code linting and coverage) +* runs static code analysis (`make pylint`) to detect anti-patterns +* runs the testsuite (`make unit_tests`) + * `-j4` lets the test driver run the tests concurrently on all 4 CPU cores + * `COVERAGE=1` is a pyMBE-specific Makefile variable to enable code coverage collection (injects `-m coverage run --parallel-mode` in the Python invocation of ESPResSo) +* generates the software documentation in HTML format (`make docs`) +* generates the code coverage report in XML format (`make coverage_xml`) + +We now turn our attention to the continuous delivery part of the workflow: + +```yaml linenums="48" title="Saving the software documentation as an artifact" + - name: Upload artifact + if: ${{ matrix.espresso.upload_artifact }} + uses: actions/upload-artifact@v4 + with: + path: "./documentation" + name: documentation + retention-days: 2 + if-no-files-found: error +``` + +The software documentation is uploaded to +[workflow artifacts](https://docs.github.com/en/actions/concepts/workflows-and-actions/workflow-artifacts) +with a 48h retention policy, but only with the stable release of ESPResSo. +When the CI/CD workflow runs on the main branch of the repository and is successful, +a separate deployment workflow[^pymbe-tag-100-deploy-workflow] is automatically triggered +to download the artifact and publish it to [pymbe-dev.github.io/pyMBE](https://pymbe-dev.github.io/pyMBE). + +The code coverage report is submitted to [Codecov](https://about.codecov.io/) using an upload token: + +```yaml linenums="56" title="Publishing the code coverage report" + - name: Upload coverage to Codecov + if: ${{ github.repository == 'pyMBE-dev/pyMBE' }} + uses: codecov/codecov-action@v4 + with: + file: "./coverage.xml" + disable_search: true + env_vars: OS,PYTHON + fail_ci_if_error: false + flags: unittests + token: ${{ secrets.CODECOV_TOKEN }} + verbose: true +``` + +Coverage reports are published to [app.codecov.io/gh/pyMBE-dev/pyMBE](https://app.codecov.io/gh/pyMBE-dev/pyMBE). +The readme file of the pyMBE project also displays a code coverage badge that +dynamically refreshes every time the main branch is updated. +This step is skipped on forked projects, although fork owners can manually activate it +in a new branch by removing the `if` field and setting up their own token. + +Here is the complete workflow[^pymbe-tag-100-cicd-workflow]: + +```yaml linenums="1" title="CI/CD workflow definition" +name: testsuite +on: + push: + pull_request: +permissions: + contents: read # to fetch code (actions/checkout) +jobs: + ubuntu: + runs-on: ubuntu-24.04 + env: + FI_PROVIDER: "^psm3,psm3;ofi_rxd" + OMPI_MCA_mtl_ofi_provider_exclude: psm3 + strategy: + matrix: + espresso: + - version: "4.2.2" + eessi: ESPResSo/4.2.2-foss-2023a + - version: "5.0-dev" + eessi: ESPResSo/8aa60cecd56cdd10ab62042c567552f347374f36-foss-2023b + name: ubuntu - ESPResSo ${{ matrix.espresso.version }} + steps: + - name: Checkout repository + uses: actions/checkout@main + - name: Setup EESSI + uses: eessi/github-action-eessi@v3 + with: + eessi_stack_version: "2023.06" + - name: Install dependencies + uses: ./.github/actions/dependencies + with: + modules: |- + ${{ matrix.espresso.eessi }} + extra-python-packages: |- + pylint==3.0.3 + coverage==7.4.4 + pdoc==15.0.3 + - name: Run testsuite + run: | + module restore espresso + source venv/bin/activate + make pylint + make unit_tests -j4 COVERAGE=1 + make docs + make coverage_xml + deactivate + module purge + shell: bash + - name: Upload artifact + if: ${{ matrix.espresso.upload_artifact }} + uses: actions/upload-artifact@v4 + with: + path: "./documentation" + name: documentation + retention-days: 2 + if-no-files-found: error + - name: Upload coverage to Codecov + if: ${{ github.repository == 'pyMBE-dev/pyMBE' }} + uses: codecov/codecov-action@v4 + with: + file: "./coverage.xml" + disable_search: true + env_vars: OS,PYTHON + fail_ci_if_error: false + flags: unittests + token: ${{ secrets.CODECOV_TOKEN }} + verbose: true +``` + +## Conclusion + +We have just learned how to configure CI/CD workflows to install dependencies, +run a testsuite, upload coverage reports, and generate software documentation. +The pyMBE and SwarmRL workflows are modular and can be easily adapted to fit the needs of your own project. + +If you are new to the GitHub workflow syntax, don't worry: +it is easy to learn and the GitHub documentation is extremely well-written. +To troubleshoot any issue with your first workflow, consider using the GitHub Action +[tmate](https://github.com/marketplace/actions/debugging-with-tmate) to remotely +log into the virtual machine via SSH or web shell. +To do so, add the following step after the checkout step: + +```yaml linenums="1" title="Remotely logging into a virtual machine" + - name: Setup tmate session + uses: mxschmitt/action-tmate@v3 + with: + detached: true + limit-access-to-actor: true +``` + +The EESSI GitHub Action streamlines the installation of scientific software +in the cloud, reduces the complexity of CI/CD workflows, tightens the CI feedback +loop for developers and saves on billable hours for cloud resources. + +Another use case for the EESSI GitHub Action is executable research papers[^Lasser2020]. +There is more than one way to design them; in the case of pyMBE, all simulation scripts +from the pyMBE paper[^Beyer2024] were added to the code repository as code samples +that run every two weeks in a samples workflow[^pymbe-tag-100-samples-workflow] +to detect regressions in the development branch of the software. +The workflow can be triggered manually, for example before merging a large pull request, +and takes 1h of runtime, compared to the 10min runtime of the CI/CD workflow. + +[^Beyer2024]: David Beyer, Paola B. Torres, Sebastian P. Pineda, Claudio F. Narambuena, Jean-Noël Grad, Peter Košovan, and Pablo M. Blanco. “pyMBE: the Python-based molecule builder for ESPResSo”. In: The Journal of Chemical Physics 161.2 (Modular and Interoperable Software for Chemical Physics, July 2024), pp. 022502, doi:[10.1063/5.0216389](https://doi.org/10.1063/5.0216389). +[^Tovey2025]: Samuel Tovey, Christoph Lohrmann, Tobias Merkt, David Zimmer, Konstantin Nikolaou, Simon Koppenhöfer, Anna Bushmakina, Jonas Scheunemann, and Christian Holm. “SwarmRL: building the future of smart active systems”. In: The European Physical Journal E 48.4–5 (April 2025), pp. 16, doi:[10.1140/epje/s10189-025-00477-4](https://doi.org/10.1140/epje/s10189-025-00477-4). +[^Weik2019]: Florian Weik, Rudolf Weeber, Kai Szuttor, Konrad Breitsprecher, Joost de Graaf, Michael Kuron, Jonas Landsgesell, Henri Menke, David Sean, and Christian Holm. “ESPResSo 4.0 – an extensible software package for simulating soft matter systems”. In: The European Physical Journal Special Topics, 227.14 (March 2019), pp. 1789–1816, doi:[10.1140/epjst/e2019-800186-9](https://doi.org/10.1140/epjst/e2019-800186-9). +[^Lasser2020]: Jana Lasser. “Creating an executable paper is a journey through Open Science”. In: Communications Physics 3.1 (August 2020), pp.143, doi:[10.1038/s42005-020-00403-4](https://doi.org/10.1038/s42005-020-00403-4). +[^swarmrl-commit-181edfa-cicd-workflow]: SwarmRL `181edfa` CI/CD workflow: [SwarmRL/SwarmRL@`181edfa`:`.github/workflows/espresso.yml`](https://github.com/SwarmRL/SwarmRL/blob/181edfa/.github/workflows/espresso.yml) +[^pymbe-tag-100-deps-action]: pyMBE v1.0.0 custom action: [pyMBE-dev/pyMBE@1.0.0:`.github/actions/dependencies/action.yml`](https://github.com/pyMBE-dev/pyMBE/blob/1.0.0/.github/actions/dependencies/action.yml) +[^pymbe-tag-100-cicd-workflow]: pyMBE v1.0.0 CI/CD workflow: [pyMBE-dev/pyMBE@1.0.0:`.github/workflows/testsuite.yml`](https://github.com/pyMBE-dev/pyMBE/blob/1.0.0/.github/workflows/testsuite.yml) +[^pymbe-tag-100-deploy-workflow]: pyMBE v1.0.0 GitHub Pages workflow: [pyMBE-dev/pyMBE@1.0.0:`.github/workflows/deploy.yml`](https://github.com/pyMBE-dev/pyMBE/blob/1.0.0/.github/workflows/deploy.yml) +[^pymbe-tag-100-samples-workflow]: pyMBE v1.0.0 samples workflow: [pyMBE-dev/pyMBE@1.0.0:`.github/workflows/samples.yml`](https://github.com/pyMBE-dev/pyMBE/blob/1.0.0/.github/workflows/samples.yml) From 713b6da1670df455e0c30529f48303096b3e81db Mon Sep 17 00:00:00 2001 From: ocaisa Date: Wed, 14 Jan 2026 14:03:14 +0100 Subject: [PATCH 2/2] Apply suggestions from code review --- docs/blog/posts/2025/12/gh-ci-workflow-for-EESSI.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/blog/posts/2025/12/gh-ci-workflow-for-EESSI.md b/docs/blog/posts/2025/12/gh-ci-workflow-for-EESSI.md index 7387197b8a..7bee8861c5 100644 --- a/docs/blog/posts/2025/12/gh-ci-workflow-for-EESSI.md +++ b/docs/blog/posts/2025/12/gh-ci-workflow-for-EESSI.md @@ -140,7 +140,7 @@ runs: ``` A workflow that calls this action will load software from an EESSI stack (argument `modules`) -and install Python packages from Pypi (argument `extra-python-packages`). +and install Python packages from PyPI (argument `extra-python-packages`). Packages are managed by a Lmod [module collection](https://lmod.readthedocs.io/en/latest/010_user.html#rules-for-loading-modules-from-a-collection) and by a Python [virtual environment](https://docs.python.org/3/library/venv.html). @@ -150,7 +150,7 @@ The steps can be broken down as follows: * line 14 can be removed when only the `software.eessi.io` repository is needed * microarchitecture `x86_64/amd/zen2` is selected, which is forward-compatible with the Zen3 microarchitecture available on [standard GitHub-hosted runners](https://docs.github.com/en/actions/reference/runners/github-hosted-runners#standard-github-hosted-runners-for-public-repositories) - * Ubuntu ARM64 runners have microarchitecture `aarch64/nvidia/grace` + * Ubuntu ARM64 runners have microarchitecture compatible with the `aarch64/nvidia/grace` software stack that EESSI ships * load the list of EESSI modules provided by the workflow and stash them to a collection called `espresso` * create a Python virtual environment that gives priority to Python packages available in EESSI (`venv` option `--system-site-packages`, other package managers have a different syntax) @@ -159,7 +159,7 @@ The steps can be broken down as follows: `requirements.txt`, pip install them, and restore `requirements.txt` * clean up the session -This action allows us to “hide” the logic for package installation, +This action allows us to "hide" the logic for package installation, and should only be edited by project maintainers. Project developers can ignore this file and focus on the workflow, described in the next section.