Distributing MODFLOW 6
This document describes release procedures for MODFLOW 6.
Overview
MODFLOW 6 releases come in two flavors:
nightly development builds
full/approved distributions
Development builds are created nightly from the tip of the develop branch and released from the MODFLOW-ORG/modflow6-nightly-build repository. Development distributions contain only MODFLOW 6 input/output documentation, release notes, code.json metadata, and core executables and libraries:
mf6: MODFLOW 6 executablezbud6: Zonebudget executablemf5to6: MODFLOW 5 to 6 converter executablelibmf6: MODFLOW 6 dynamic library
Full distributions contain the items listed above, as well as:
Meson build files
Fortran source code
MODFLOW 6 example models
MODFLOW 6 makefiles
MODFLOW 6 Visual Studio files
more extensive documentation, including:
MODFLOW 6 input/output docs
MODFLOW 6 example model docs
MODFLOW 6 release notes
MODFLOW 6 supplementary technical information
docs for various MODFLOW 6 features and packages
docs for
mf5to6andzbud6
Official releases can be classified further into two types: patch and minor releases. Patch releases should typically branch from master and cherry-pick relevant commits, since develop may contain broader changes not yet ready for release. Minor releases typically branch from develop. MODFLOW 6 does not currently increment the major version number.
Both nightly builds and official distributions are created automatically with GitHub Actions.
Requirements
This document assumes a MODFLOW 6 development environment has been configured as per the developer documentation, including a Fortran compiler (either ifort or gfortran) as well as a Pixi environment as specified in pixi.toml. Official distributions are currently prepared with Intel Fortran (ifort).
Steps
Broadly, steps to prepare an official release for distribution include:
update release notes for the release (and reset them after)
update version information with
update_version.py(re)build makefiles with
build_makefiles.pybenchmark example models with
benchmark.pybuild the MF6IO documentation with
build_docs.pybuild the distribution archive with
build_dist.pyverify the distribution archive with
check_dist.py
These should occur roughly in the order presented above. The procedure is automated in the .github/workflows/release.yml and release_dispatch.yml workflows.
Note: git- and/or GitHub-related steps are omitted from this section. See the Procedure section below for a step-by-step recipe for creating and distributing release with the help of GitHub Actions.
Update release notes
The release notes PDF document is constructed from the doc/ReleaseNotes/ReleaseNotes.tex LaTeX file. During development, release notes should be maintained in doc/ReleaseNotes/develop.toml — this file is turned into a LaTeX file by doc/ReleaseNotes/mk_releasenotes.py. A pixi task is available for this, e.g. pixi run make-release-notes. The resulting LaTex file is referenced from doc/ReleaseNotes/ReleaseNotes.tex.
Before making a release, add a line to the Release History section of ReleaseNotes.tex providing the version number, date and DOI of the current release, e.g. 6.4.4 & February 13, 2024 & \url{https://doi.org/10.5066/P9FL1JCC}.
Note: Deprecated and removed MF6IO options are included in the release notes. See the developer docs for more info on deprecation policy, searching for deprecations among DFNs, and generating a deprecations/removals table for insertion into the release notes.
Update version info
MODFLOW 6 version numbers follow the semantic versioning convention major.minor.patch. Release tags do not include an initial v, as is common in many other projects.
Version information is stored primarily in version.txt in the project root, as well as in several other files in the repository.
The update_version.py script updates files containing version information. First a file lock is acquired, then repository files are updated, then the lock is released.
The version can be specified with the --version (short -v) option. For instance, to set version to 6.4.1, run from the scripts/ folder:
python update_version.py -v 6.4.1
If no --version is provided, the version is not changed, only the build timestamp.
An optional label may be appended to the release number, e.g.
python update_version.py -v 6.4.2rc
The label must start immediately following the patch version number, with no space in between. The label may contain numeric characters or symbols, but must not start with a number (otherwise there is no way to distinguish it from the patch version number).
The update_version.py script has a few other flags:
--approved(short-a): approve an official release. If the--approvedflag is provided, disclaimer language is altered to reflect approval. If the flag is not provided, the language reflects preliminary/provisional status and(preliminary)is appended to version numbers.--releasemode(short-r): toggle whether binaries are built in development or release mode by editing the contents ofsrc/Utilities/version.f90. If the--releasemodeflag is provided,IDEVELOPMODEis set to 0. If--releasemodeis not provided,IDEVELOPMODEis set to 1.--get(short-g): print the current version number tostdoutwithout making any updates.--citation(short-c): generate a citation from the contents ofCITATION.cffand print it tostdout, again without making any updates.
Build makefiles
The build_makefiles.py script is used to rewrite makefiles after Fortran source files have been added, removed, or renamed. Up-to-date makefiles must be generated for inclusion in a distribution. A pixi task build-makefiles is also available.
Build example models
MODFLOW 6 example models are bundled with official releases. Example models must be built and run to generate plots and tables before documentation can be generated. The release.yml workflow attempts to download the latest release from the examples repository, only re-building and re-running example models if no such release is available. See the examples repository for more information on preparing example models.
Benchmark example models
MODFLOW 6 documentation includes a performance evaluation comparing the current version against the last official release. Benchmarks must run before a release can be prepared. Benchmarks run as a component of the docs.yml CI workflow — release.yml attempts to download benchmark results if available, only re-running them if necessary.
The benchmark.py script benchmarks the current development version of MODFLOW 6 against the latest release rebuilt in development mode, using the models from the MODFLOW-ORG/modflow6-examples repository. Paths to pre-built binaries for both versions can be provided via the --current-bin-path (short -c) and --previous-bin-path (short -p) command line options. If bin paths are not provided, executables are rebuilt in the default locations:
<project root>/bin: current development version
<project root>/bin/rebuilt: previous version
The examples repository must first be installed and prepared as described above. Its path may be explicitly provided with the --examples-repo-path (short -e) option. If no path is provided, the repository is assumed to be named modflow6-examples and live side-by-side with the modflow6 repository on the filesystem.
The directory to write benchmark results can be specified with --output-path (short -o). If no such option is provided, results are written to the current working directory.
python benchmark.py -e ../modflow6-examples -o .benchmarks
The above will write results to a markdown file .benchmarks/run-time-comparison.md relative to the project root.
Build documentation
Extensive documentation is bundled with official MODFLOW 6 releases. MODFLOW 6 documentation is written in LaTeX. Some LaTeX files (in particular for MODFLOW 6 input/output documentation) are automatically generated from DFN files. The release.yml workflow first runs update_version.py to update version strings to be substituted into the docs, then runs build_docs.py to regenerate LaTeX files where necessary, download benchmark results (and convert the Markdown results file to LaTeX), download publications hosted on the USGS website, and finally convert LaTeX to PDFs.
Manually building MODFLOW 6 documentation requires additional Python dependencies specified in build_rtd_docs/requirements.rtd.txt. Styles defined in the MODFLOW-ORG/usgslatex are also required. (See that repository’s README for installation instructions or this repo’s `../.github/workflows/docs.yml CI workflow for an example.)
Build the distribution archive
After each step above is complete, the build_dist.py script can be used to construct the MODFLOW 6 distribution. The build_dist.py script can be used to create both minimal and full distributions. By default, a minimal distribution is created. To create a full distribution, run the script with the --full flag.
The build_dist.py script is lazy — benchmarks, example models and documentation artifacts are downloaded via the GitHub API if available, and only re-created if none exist or the --force (-f) flag is provided. This allows the release workflow to consume artifacts previously created by other workflow runs, reducing the time needed to create and publish a release.
The script has several other arguments:
--build-path: path to the build workspace, defaults to<project root>/builddir--output-path (-o): path to create a distribution zipfile, defaults to<project root>/distribution/--examples-repo-path (-e): path to theMODFLOW-ORG/modflow6-examplesrepository, defaults tomodflow6-examplesside-by-side with project root--force (-f): whether to recreate and overwrite preexisting components of the distribution, if they already exist
Default paths are resolved relative to the script’s location on the filesystem, not the current working directory, so the script can be run from distribution/, from the project root, or from anywhere else. This is true of all scripts in the distribution/ directory.
See the release.yml workflow for a complete example of how to build a distribution archive.
Verify the distribution archive
The check_dist.py script can be used to check the release distribution folder. The --path argument is the path to the dist folder. The --approved flag can be used to signal that the release is approved/official. By default the release is assumed preliminary. The script checks the version string emitted by mf6 -v for the presence or absence of “preliminary” depending on this flag.
Procedure
The steps above are automated in the .github/workflows/release.yml and release_dispatch.yml workflows. The .github/workflows/release.yml workflow is used for both nightly builds and official releases. It should not be necessary to prepare a release manually.
The release.yml workflow has no triggers of its own, and must be dispatched by .github/workflows/release_dispatch.yml, in one of two ways:
Push a release branch to the
MODFLOW-ORG/modflow6repository. This method should be used for proper releases.Manually trigger the workflow via GitHub CLI or web UI. Useful for testing release candidates or verifying the release automation before a final release is made — see the Testing section below for more detail.
To release an official version of MODFLOW 6 via the release branch method:
Create a release candidate branch from the tip of
developormaster. The branch’s name must begin withvfollowed by the version number. For an officially approved release, include only the version number. For a preliminary release candidate, appendrcafter the version number, e.g.v6.4.0rc. If the branch name does not end inrc, the release is assumed to be approved.Push the branch to the
MODFLOW-ORG/modflow6repository. This triggers the release workflow. If the release is still an unapproved candidate (i.e. the branch name ends withrc) binaries are built withIDEVELOPMODEset to 1, and the workflow ends after uploading binaries and documentation artifacts for inspection. If the release is approved/official, the workflow drafts a pull request against themasterbranch.To continue with the release, merge (do not squash) the PR into
master. This triggers another job to tag the new tip ofmasterwith the release number, draft a release, and upload binaries and documentation as release assets.If the release assets pass inspection, publish the release. The following format convention is used for the GitHub release post:
This is the approved USGS MODFLOW <semver> release. <authors>, <release year>, MODFLOW 6 Modular Hydrologic Model version <semver>: U.S. Geological Survey Software Release, <release date>, <doi link> Visit the USGS "MODFLOW and Related Programs" site for information on MODFLOW 6 and related software: https://doi.org/10.5066/F76Q1VQV
Create a branch from
master, naming it something likepost-release-x.y.z-reset. Rundistribution/update_version.py -v x.y.z.devN, substitutingx,y,zandNas appropriate for the next development cycle’s version number. This will substitute the version number into all necessary files and will also setIDEVELOPMODEback to 1. Reset the release notes bycopying the
develop.texfile generated fromdevelop.tomltodoc/ReleaseNotes/previous/vx.y.z.tex(wherex.y.zis the semantic version just released), theninserting a new line
\input{./previous/vx.y.z.tex}at the top ofdoc/ReleaseNotes/appendixA.tex, thenclearing
develop.toml.
Open a pull request into master from the reset branch. Merge (do not squash) the PR.
Note: Squashing the release PR into master or the post-release reset PR into develop causes develop and master to diverge, leading to conflicts at the next release time. Both pull requests should be merged with a merge commit, not squashed.
Testing
Each script used in the release procedure can be tested separately. The procedure can also be tested end-to-end by manually dispatching the release workflow.
Testing release scripts
Each script in distribution/ contains its own tests. To run them, run pytest from the distribution/ folder. The tests will not be discovered if pytest is run from a different location, as the scripts in this folder are not named test_*.py and are only discoverable by virtue of the patterns provided in distribution/pytest.ini. The tests use temporary directories where possible and revert modifications to tracked files on teardown.
Note: the tests clean up by reverting changes to files in the following locations:
doc/makeutils/**/make/
Make sure you don’t have any uncommitted changes in these locations before running the tests.
Note: to avoid contested file access, the tests will refuse to run in parallel with pytest-xdist.
There is a small additional suite of tests that can be used to validate a release distribution folder after it is built: check_dist.py. These tests are run as part of the release workflow — see below for more detail.
Testing the release workflow
The workflow_dispatch event is GitHub’s mechanism for manually triggering workflows. This can be accomplished from the Actions tab in the GitHub UI. This is a convenient way to test the release procedure and evaluate release candidate distributions.
To dispatch the release workflow, navigate to the Actions tab of this repository. Select the release dispatch workflow. A Run workflow button should be visible in an alert at the top of the list of workflow runs. Click the Run workflow button, selecting values for the various inputs:
approve: whether the release is officially approved, or just a release candidatebranch: the branch to release fromdevelopment: whether to build a minimal development distribution or a full distributionrun_tests: whether to run autotests after building binariesversion: the version number of the release