Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Program repos pin the release actions (actions/) at a devtools release, so
# updates to the actions they use land here once instead of in every program
# repo.
version: 2
updates:
- package-ecosystem: github-actions
directories:
- /
- /actions/*
schedule:
interval: monthly
groups:
actions:
patterns:
- "*"
171 changes: 171 additions & 0 deletions .github/workflows/actions.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
# Tests for the release actions (actions/) and modules
# (modflow_devtools/release/): the modules, including the release workflow's
# stages against a local git remote and a fake gh, the workflow syntax, and
# the composite actions against a small Fortran program (autotest/release) on
# each released platform.
name: Release actions
on:
push:
paths:
- 'actions/**'
- 'modflow_devtools/release/**'
- 'autotest/release/**'
- 'autotest/test_release.py'
- 'docs/examples/**'
- '.github/workflows/actions.yml'
pull_request:
branches:
- main
- develop
paths:
- 'actions/**'
- 'modflow_devtools/release/**'
- 'autotest/release/**'
- 'autotest/test_release.py'
- 'docs/examples/**'
- '.github/workflows/actions.yml'
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
PIXI_BETA_WARNING_OFF: true
jobs:
scripts:
name: Scripts
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v7

- name: Setup uv
uses: astral-sh/setup-uv@v7

# the changelog tests run git-cliff with pipx
- name: Install
run: uv sync --group test

- name: Test
working-directory: autotest
run: uv run pytest -v test_release.py

lint:
name: Lint workflows
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v7

- name: Run actionlint
run: |
bash <(curl -sSfL https://raw.githubusercontent.com/rhysd/actionlint/v1.7.7/scripts/download-actionlint.bash) 1.7.7
./actionlint -color .github/workflows/actions.yml

# the examples call each other, so lint them as a repo's workflows
- name: Lint examples
run: |
mkdir -p "$RUNNER_TEMP/examples/.github/workflows"
cp docs/examples/*.yml "$RUNNER_TEMP/examples/.github/workflows/"
git init -q "$RUNNER_TEMP/examples"
cd "$RUNNER_TEMP/examples" && "$GITHUB_WORKSPACE/actionlint" -color

release-start:
name: Release start (no-op)
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v7

# not a release event, so there should be nothing to do
- name: Start release
id: start
uses: ./actions/release-start
with:
path: autotest/release
long-name: Hello

- name: Check stage
env:
STAGE: ${{ steps.start.outputs.stage }}
run: '[[ -z "$STAGE" ]]'

actions:
name: Actions ${{ matrix.os }} ${{ matrix.compiler }} ${{ matrix.version }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- {os: ubuntu-22.04, compiler: intel, version: "2025.0"}
- {os: macos-15, compiler: gcc, xcode-version: "16.4"}
- {os: windows-2022, compiler: intel, version: "2025.0"}
- {os: ubuntu-22.04, compiler: gcc}
- {os: windows-2022, compiler: gcc}
steps:
- name: Checkout
uses: actions/checkout@v7

- name: Set version
id: version
uses: ./actions/version
with:
path: autotest/release
set: 1.2.3

- name: Check version
shell: bash
env:
VERSION: ${{ steps.version.outputs.version }}
RELEASE: ${{ steps.version.outputs.release }}
run: |
[[ "$VERSION" == "1.2.3" && "$RELEASE" == "true" ]]
grep -q "VERSION = '1.2.3 " autotest/release/src/hello.f90

- name: Setup
uses: ./actions/setup
with:
path: autotest/release
compiler: ${{ matrix.compiler }}
compiler-version: ${{ matrix.version }}
xcode-version: ${{ matrix.xcode-version }}

- name: Build and test
shell: bash
working-directory: autotest/release
run: |
pixi run setup builddir
pixi run build builddir
pixi run setup builddir-dble -Ddouble=true
pixi run build builddir-dble
pixi run test
pixi run meson test --no-rebuild -C builddir-dble

- name: Check linkage
uses: ./actions/check-linkage
with:
path: autotest/release/bin

- name: Package
id: package
uses: ./actions/package
with:
path: autotest/release
name: hello

- name: Check archives
shell: bash
env:
DIST: ${{ steps.package.outputs.dist }}
OSTAG: ${{ steps.package.outputs.ostag }}
run: |
ls -l "$DIST"
python3 - "$DIST/$OSTAG.zip" <<'EOF'
import sys, zipfile
names = sorted(zipfile.ZipFile(sys.argv[1]).namelist())
print(names)
stems = sorted(n.removesuffix(".exe") for n in names)
assert stems == ["hello", "hellodbl"], names
EOF
if [[ "$RUNNER_OS" == "Linux" ]]; then
unzip -l "$DIST/hello_source.zip" | grep -q src/hello.f90
fi
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ Python development tools for MODFLOW 6 and related projects.
- [`MODFLOW-ORG/modflow6-examples`](https://github.com/MODFLOW-ORG/modflow6-examples)
- [`MODFLOW-ORG/modflow6-testmodels`](https://github.com/MODFLOW-ORG/modflow6-testmodels)
- [`MODFLOW-ORG/modflow6-largetestmodels`](https://github.com/MODFLOW-ORG/modflow6-largetestmodels)
* GitHub Actions for building, packaging and releasing MODFLOW programs, see [release actions](docs/md/release.md)

## Requirements

Expand Down
63 changes: 63 additions & 0 deletions actions/changelog/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
name: Changelog
description: >
Prepend a release's features, fixes and performance changes to
CHANGELOG.md with git-cliff and the shared template (command `update`;
needs full git history), or write a release's section of CHANGELOG.md to a
notes file (command `notes`).
inputs:
path:
description: Directory of the program repo checkout
default: .
command:
description: update or notes
required: true
version:
description: Release version, X.Y.Z
required: true
long-name:
description: Program long name, e.g. MODFLOW-USG; used in the changelog header and the notes title
required: true
outputs:
notes-file:
description: Path of the notes file (command `notes`)
value: ${{ steps.notes.outputs.file }}
runs:
using: composite
steps:
- name: Setup Python
id: python
uses: actions/setup-python@v5
with:
python-version: "3.12"
update-environment: false

- name: Update CHANGELOG.md
if: inputs.command == 'update'
shell: bash
working-directory: ${{ inputs.path }}
env:
PYTHON: ${{ steps.python.outputs.python-path }}
PYTHONPATH: ${{ github.action_path }}/../..
VERSION: ${{ inputs.version }}
LONG_NAME: ${{ inputs.long-name }}
REPOSITORY: ${{ github.repository }}
run: |
"$PYTHON" -P -m modflow_devtools.release.changelog update --version "$VERSION" \
--repository "$REPOSITORY" --long-name "$LONG_NAME"

- name: Write release notes
id: notes
if: inputs.command == 'notes'
shell: bash
working-directory: ${{ inputs.path }}
env:
PYTHON: ${{ steps.python.outputs.python-path }}
PYTHONPATH: ${{ github.action_path }}/../..
VERSION: ${{ inputs.version }}
LONG_NAME: ${{ inputs.long-name }}
run: |
file="$RUNNER_TEMP/notes.md"
"$PYTHON" -P -m modflow_devtools.release.changelog notes --version "$VERSION" \
--long-name "$LONG_NAME" > "$file"
cat "$file"
echo "file=$file" >> "$GITHUB_OUTPUT"
58 changes: 58 additions & 0 deletions actions/check-linkage/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
name: Check linkage
description: >
List the shared libraries each executable and shared library in a
directory links, and fail if any links a compiler runtime library that
users might not have: the Intel, GNU (gfortran, quadmath, OpenMP, and on
macOS and Windows the gcc and C++ runtimes), or Microsoft Visual C++
runtime. Linux's libgcc_s and libstdc++ are system libraries, so they're
allowed there. On macOS, also require arm64.
inputs:
path:
description: Directory holding the executables
default: bin
runs:
using: composite
steps:
- name: Check linkage
shell: bash
working-directory: ${{ inputs.path }}
run: |
intel='libifcore|libifport|libimf|libsvml|libintlc|libirng|libiomp5|libmmd|svml_disp'
gnu='libgfortran|libquadmath|libgomp'
status=0
check() {
local exe="$1" deps="$2" forbidden="$3"
echo "::group::$exe"; echo "$deps"; echo "::endgroup::"
if bad=$(grep -iE "$forbidden" <<< "$deps"); then
echo "::error::$exe dynamically links a compiler runtime library: $(tr '\n' ' ' <<< "$bad")"
status=1
fi
}
case "$RUNNER_OS" in
Linux)
for exe in $(find . -maxdepth 1 -type f \( -perm -u+x -o -name '*.so*' \) | sort); do
check "$exe" "$(ldd "$exe" 2>&1 || true)" "$intel|$gnu"
done
;;
macOS)
for exe in $(find . -maxdepth 1 -type f \( -perm -u+x -o -name '*.dylib' \) | sort); do
check "$exe" "$(otool -L "$exe")" "$intel|$gnu|libgcc_s|/libstdc\+\+"
if ! lipo -archs "$exe" | grep -qw arm64; then
echo "::error::$exe is not built for arm64"
status=1
fi
done
;;
Windows)
if ! command -v objdump >/dev/null; then
echo "::error::objdump not found, can't check linkage"
exit 1
fi
shopt -s nullglob
for exe in *.exe *.dll; do
check "$exe" "$(objdump -p "$exe" | grep -i 'DLL Name' || true)" \
"$intel|$gnu|libgcc_s|libstdc\+\+|libwinpthread|vcruntime|msvcp"
done
;;
esac
exit $status
72 changes: 72 additions & 0 deletions actions/package/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
name: Package a MODFLOW program
description: >
Zip everything in bin/ as <ostag>.zip (linux, macarm or win64), with the
files at the archive root, and on Linux also zip the source as
<name>_source.zip. The archives are made the same way on every platform,
with modflow_devtools/release/package.py. Outputs the directory holding
the archives, and optionally uploads them as artifact dist-<ostag> for the
release-finish action.
inputs:
path:
description: Directory of the program repo checkout
default: .
name:
description: Program name, used for the source archive
required: true
source-paths:
description: >
Space-separated paths to put in the source archive, or `none` to skip
it; by default meson.build, meson.options (if present) and src
default: ""
upload:
description: >
Upload the archives as artifact dist-<ostag>; do this from one job per
platform in a release build
default: "false"
outputs:
ostag:
description: The platform tag
value: ${{ steps.package.outputs.ostag }}
dist:
description: Directory holding the archives
value: ${{ steps.package.outputs.dist }}
runs:
using: composite
steps:
- name: Setup Python
id: python
uses: actions/setup-python@v5
with:
python-version: "3.12"
update-environment: false

- name: Zip archives
id: package
shell: bash
working-directory: ${{ inputs.path }}
env:
PYTHON: ${{ steps.python.outputs.python-path }}
PYTHONPATH: ${{ github.action_path }}/../..
NAME: ${{ inputs.name }}
SOURCE_PATHS: ${{ inputs.source-paths }}
run: |
dist="$RUNNER_TEMP/dist"
args=(--name "$NAME" --dist "$dist")
if [[ "$SOURCE_PATHS" == "none" ]]; then
args+=(--source-paths)
elif [[ -n "$SOURCE_PATHS" ]]; then
read -ra paths <<< "$SOURCE_PATHS"
args+=(--source-paths "${paths[@]}")
fi
ostag=$("$PYTHON" -P -m modflow_devtools.release.package "${args[@]}")
ls -l "$dist"
echo "ostag=$ostag" >> "$GITHUB_OUTPUT"
echo "dist=$dist" >> "$GITHUB_OUTPUT"

- name: Upload archives
if: inputs.upload == 'true'
uses: actions/upload-artifact@v7
with:
name: dist-${{ steps.package.outputs.ostag }}
path: ${{ steps.package.outputs.dist }}/*.zip
if-no-files-found: error
Loading
Loading