This document describes release procedures for MODFLOW 6. This folder contains scripts to automate MODFLOW 6 distribution tasks.
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-USGS/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
mf5to6
andzbud6
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.
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
).
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.py
- benchmark example models with
benchmark.py
- build the MF6IO documentation with
build_docs.py
- build the distribution archive with
build_dist.py
- verify 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.
The release notes document is constructed from the doc/ReleaseNotes/ReleaseNotes.tex
LaTeX file. During each development cycle, release notes should be maintained in doc/ReleaseNotes/develop.tex
— this 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}
.
After each release is made, several steps are required to reset the release notes for the next development cycle:
- copy
develop.tex
into a new filedoc/ReleaseNotes/previous/vx.y.z.tex
(wherex.y.z
is the semantic version just released) - add a new entry like
\input{./previous/vx.y.z.tex}
to line 3 ofdoc/ReleaseNotes/appendixA.tex
- overwrite
develop.tex
with the contents ofdoc/ReleaseNotes/vx.y.z-template.tex
Now new changes can be added to develop.tex
as development proceeds.
Note: Newly deprecated MF6IO options are included in the release notes. See the developer docs for more info on MF6's deprecation policy, searching for deprecations among DFNs, and generating a deprecations table for insertion into the release notes.
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--approved
flag 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--releasemode
flag is provided,IDEVELOPMODE
is set to 0. If--releasemode
is not provided,IDEVELOPMODE
is set to 1. -
--get
(short-g
): print the current version number tostdout
without making any updates. -
--citation
(short-c
): generate a citation from the contents ofCITATION.cff
and print it tostdout
, again without making any updates.
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.
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.
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-USGS/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.
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-USGS/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.)
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-USGS/modflow6-examples
repository, defaults tomodflow6-examples
side-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.
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.
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-USGS/modflow6
repository. 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
develop
ormaster
. The branch's name must begin withv
followed by the version number. For an officially approved release, include only the version number. For a preliminary release candidate, appendrc
after 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-USGS/modflow6
repository. This triggers the release workflow. If the release is still an unapproved candidate (i.e. the branch name ends withrc
) binaries are built withIDEVELOPMODE
set 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 themaster
branch. -
To continue with the release, merge (do not squash) the PR into
master
. This triggers another job to tag the new tip ofmaster
with 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
,z
andN
as appropriate for the next development cycle's version number. This will substitute the version number into all necessary files and will also setIDEVELOPMODE
back to 1. Reset release notes as described above. Open a pull request intomaster
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.
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.
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/
make
utils/**/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.
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