From bd817f86fbee80881bca6b4c6dcc727568858dfc Mon Sep 17 00:00:00 2001 From: Connor Ward Date: Thu, 3 Apr 2025 08:36:08 +0100 Subject: [PATCH 01/39] Begin process of preparing Firedrake release --- pyproject.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index e8c4d0ec6c..efa20d39df 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,7 @@ [project] name = "firedrake" -version = "0.14_dev" +# .. +version = "2025.04.0" description = "An automated system for the portable solution of partial differential equations using the finite element method" readme = "README.rst" license = {file = "LICENSE"} From 2642cecb106e02fd1eaeda88d8741e07a28a03e6 Mon Sep 17 00:00:00 2001 From: Connor Ward Date: Mon, 7 Apr 2025 14:31:26 +0100 Subject: [PATCH 02/39] Attempt tweaking workflow to use no build isolation --- .github/workflows/build.yml | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index bf22d12bfb..e45c437888 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -63,9 +63,21 @@ jobs: apt-get -y install \ $(python3 ./firedrake-repo/scripts/firedrake-configure --arch ${{ matrix.arch }} --show-system-packages) python3-venv parallel + # TODO: is there a 'blessed commit' or do we just point to 'main'? + - name: Determine PETSc refspec (master) + # if: github.ref == 'refs/heads/master' + if: false + # run: echo "PETSC_REFSPEC=''" >> "$GITHUB_ENV" + run: echo "PETSC_REFSPEC='--branch $(python3 ./firedrake-repo/scripts/firedrake-configure --show-petsc-version)'" >> "$GITHUB_ENV" + + - name: Determine PETSc refspec (release) + # if: github.ref == 'refs/heads/release' + if: true # debugging, does this parse? + run: echo "PETSC_REFSPEC='--branch $(python3 ./firedrake-repo/scripts/firedrake-configure --show-petsc-version)'" >> "$GITHUB_ENV" + - name: Install PETSc run: | - git clone --depth 1 --branch $(python3 ./firedrake-repo/scripts/firedrake-configure --show-petsc-version) https://gitlab.com/petsc/petsc.git + git clone --depth 1 "$PETSC_REFSPEC" https://gitlab.com/petsc/petsc.git cd petsc python3 ../firedrake-repo/scripts/firedrake-configure \ --arch ${{ matrix.arch }} --show-petsc-configure-options | \ @@ -78,14 +90,27 @@ jobs: - name: Install Firedrake id: install run: | - export $(python3 ./firedrake-repo/scripts/firedrake-configure --arch ${{ matrix.arch }} --show-env) + export $(python3 ./firedrake-repo/scripts/firedrake-configure --arch "${{ matrix.arch }}" --show-env) python3 -m venv venv . venv/bin/activate : # Force a rebuild of petsc4py as the cached one will not link to the fresh : # install of PETSc. pip cache remove petsc4py pip cache remove slepc4py - pip install --verbose \ + + if [ "${{ github.ref_name == 'master' }}" ]; then + : # Install build dependencies + pip install Cython libsupermesh mpi4py numpy pybind11 setuptools rtree + pip install $PETSC_DIR/src/binding/petsc4py + + : # We have to pass '--no-build-isolation' to use a custom petsc4py + PIP_FLAGS='--no-build-isolation' + else + : # release + PIP_FLAGS='' + fi + + pip install --verbose "$PIP_FLAGS" \ --no-binary h5py \ --extra-index-url https://download.pytorch.org/whl/cpu \ './firedrake-repo[ci]' From b5d3b460cd00d5c24e0606569296a1b7ec5e9d9a Mon Sep 17 00:00:00 2001 From: Connor Ward Date: Mon, 7 Apr 2025 15:02:49 +0100 Subject: [PATCH 03/39] fixup --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e45c437888..6f8409be92 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -77,7 +77,7 @@ jobs: - name: Install PETSc run: | - git clone --depth 1 "$PETSC_REFSPEC" https://gitlab.com/petsc/petsc.git + git clone --depth 1 $PETSC_REFSPEC https://gitlab.com/petsc/petsc.git cd petsc python3 ../firedrake-repo/scripts/firedrake-configure \ --arch ${{ matrix.arch }} --show-petsc-configure-options | \ From 44d9a066bd1952b89322199e34fc1eebca1e77ed Mon Sep 17 00:00:00 2001 From: Connor Ward Date: Tue, 8 Apr 2025 13:11:17 +0100 Subject: [PATCH 04/39] Begin work on firedrake-dev-patch --- requirements-build.txt | 9 +++++++ scripts/firedrake-dev-patch | 52 +++++++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+) create mode 100644 requirements-build.txt create mode 100755 scripts/firedrake-dev-patch diff --git a/requirements-build.txt b/requirements-build.txt new file mode 100644 index 0000000000..72aee3e88a --- /dev/null +++ b/requirements-build.txt @@ -0,0 +1,9 @@ +Cython>=3.0 +libsupermesh +mpi4py>3; python_version >= '3.13' +mpi4py; python_version < '3.13' +numpy +pkgconfig +pybind11 +setuptools>61.2 +rtree>=1.2 diff --git a/scripts/firedrake-dev-patch b/scripts/firedrake-dev-patch new file mode 100755 index 0000000000..a551e3e23b --- /dev/null +++ b/scripts/firedrake-dev-patch @@ -0,0 +1,52 @@ +#!/usr/bin/env python3 + +"""TODO""" + +import argparse +import pathlib +import tomli +import tomli_w + + +# TODO: +# * logging +# * save prev pyproj as pyproject.toml.orig +# * also patch requirements-build.txt + +def main(): + args = parse_args() + + repo_root = pathlib.Path(__file__).parent.parent + + with open(repo_root / "pyproject.toml", "rb") as f: + data = tomli.load(f) + + removed_deps = set() + removed_deps |= filter_deps(data["project"]["dependencies"], args.existing_packages) + for optional_deps in data["project"]["optional-dependencies"].values(): + removed_deps |= filter_deps(optional_deps, args.existing_packages) + + print("Removed: ", removed_deps) + + with open(repo_root / "pyproject.toml", "wb") as f: + tomli_w.dump(data, f) + + +def parse_args(): + parser = argparse.ArgumentParser() + parser.add_argument("existing_packages", nargs="*") + return parser.parse_args() + + +def filter_deps(deps, existing_packages): + to_remove = set() + for dep in deps: + if any(dep.startswith(p) for p in existing_packages): + to_remove.add(dep) + for dep in to_remove: + deps.remove(dep) + return to_remove + + +if __name__ == "__main__": + main() From b4d73b38508feb0e16889a2842b82ddfdb661f23 Mon Sep 17 00:00:00 2001 From: Connor Ward Date: Tue, 8 Apr 2025 13:24:59 +0100 Subject: [PATCH 05/39] slepc4py location --- .github/workflows/build.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6f8409be92..5415920f3f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -99,9 +99,13 @@ jobs: pip cache remove slepc4py if [ "${{ github.ref_name == 'master' }}" ]; then - : # Install build dependencies - pip install Cython libsupermesh mpi4py numpy pybind11 setuptools rtree pip install $PETSC_DIR/src/binding/petsc4py + pip install $PETSC_DIR/$PETSC_ARCH/externalpackages/git.slepc/src/binding/slepc4py + + ./scripts/firedrake-dev-patch petsc4py slepc4py + + : # Install the remaining build dependencies + pip install -r requirements-build.txt : # We have to pass '--no-build-isolation' to use a custom petsc4py PIP_FLAGS='--no-build-isolation' From e338dc8dfbbc74d883f747410d5585e074d6289c Mon Sep 17 00:00:00 2001 From: Connor Ward Date: Tue, 8 Apr 2025 14:09:38 +0100 Subject: [PATCH 06/39] Also patch requirements-build.txt --- pyproject.toml | 205 ++++++++++++++++++------------------ scripts/firedrake-dev-patch | 42 ++++++-- 2 files changed, 136 insertions(+), 111 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index efa20d39df..77761b9015 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,50 +6,50 @@ description = "An automated system for the portable solution of partial differen readme = "README.rst" license = {file = "LICENSE"} maintainers = [ - {name = "Pablo D. Brubeck"}, - {name = "Daiane I. Dolci"}, - {name = "David A. Ham", email = "david.ham@imperial.ac.uk"}, - {name = "Josh Hope-Collins"}, - {name = "Koki Sagiyama"}, - {name = "Connor J. Ward", email = "c.ward20@imperial.ac.uk"}, + {name = "Pablo D. Brubeck"}, + {name = "Daiane I. Dolci"}, + {name = "David A. Ham", email = "david.ham@imperial.ac.uk"}, + {name = "Josh Hope-Collins"}, + {name = "Koki Sagiyama"}, + {name = "Connor J. Ward", email = "c.ward20@imperial.ac.uk"}, ] requires-python = ">=3.10" dependencies = [ - "cachetools", - "decorator<=4.4.2", - "mpi4py>3; python_version >= '3.13'", - "mpi4py; python_version < '3.13'", - "h5py>3.12.1", - "libsupermesh", - # NOTE: If changing the PETSc/SLEPc version then firedrake-configure also needs - # changing (as well as other references to petsc4py and slepc4py here) - "petsc4py==3.22.2", - "numpy", - "packaging", - "pkgconfig", - "progress", - "pycparser", - "pytools[siphash]", - "requests", - "rtree>=1.2", - "scipy", - "sympy", - "fenics-ufl @ git+https://github.com/firedrakeproject/ufl.git", - "fenics-fiat @ git+https://github.com/firedrakeproject/fiat.git", - "pyadjoint-ad @ git+https://github.com/dolfin-adjoint/pyadjoint.git", - "loopy @ git+https://github.com/firedrakeproject/loopy.git@main", + "cachetools", + "decorator<=4.4.2", + "mpi4py>3; python_version >= '3.13'", + "mpi4py; python_version < '3.13'", + "h5py>3.12.1", + "libsupermesh", + # NOTE: If changing the PETSc/SLEPc version then firedrake-configure also needs + # changing (as well as other references to petsc4py and slepc4py here) + "petsc4py==3.22.2", + "numpy", + "packaging", + "pkgconfig", + "progress", + "pycparser", + "pytools[siphash]", + "requests", + "rtree>=1.2", + "scipy", + "sympy", + "fenics-ufl @ git+https://github.com/firedrakeproject/ufl.git", + "fenics-fiat @ git+https://github.com/firedrakeproject/fiat.git", + "pyadjoint-ad @ git+https://github.com/dolfin-adjoint/pyadjoint.git", + "loopy @ git+https://github.com/firedrakeproject/loopy.git@main", ] classifiers = [ - "Development Status :: 5 - Production/Stable", - "License :: OSI Approved :: GNU Lesser General Public License v2 or later (LGPLv2+)", - "Intended Audience :: Science/Research", - "Programming Language :: Python", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", - "Programming Language :: Python :: 3.12", - "Programming Language :: Python :: 3.13", - "Operating System :: Unix", + "Development Status :: 5 - Production/Stable", + "License :: OSI Approved :: GNU Lesser General Public License v2 or later (LGPLv2+)", + "Intended Audience :: Science/Research", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Operating System :: Unix", ] [project.urls] @@ -69,87 +69,92 @@ spydump = "pyop2.scripts.spydump:main" [project.optional-dependencies] # Dependencies needed to run firedrake-check test = [ - "mpi-pytest", - "pytest", + "mpi-pytest", + "pytest", ] # Dependencies needed to run the full test suite ci = [ - "ipympl", # needed for notebook testing - "jax", - "matplotlib", - "mpi-pytest", - "nbval", - "ngsPETSc", - "pylit", - "pytest", - "pytest-split", # needed for firedrake-run-split-tests - "pytest-timeout", - "pytest-xdist", - "slepc4py==3.22.2", - "torch", # requires passing '--extra-index-url' to work - "vtk", + "ipympl", # needed for notebook testing + "jax", + "matplotlib", + "mpi-pytest", + "nbval", + "ngsPETSc", + "pylit", + "pytest", + "pytest-split", # needed for firedrake-run-split-tests + "pytest-timeout", + "pytest-xdist", + "slepc4py==3.22.2", + "torch", # requires passing '--extra-index-url' to work + "vtk", ] docker = [ # Used in firedrake-vanilla container - "ipympl", # needed for notebook testing - "matplotlib", - "mpi-pytest", - "nbval", - "pylit", - "pytest", - "pytest-split", # needed for firedrake-run-split-tests - "pytest-timeout", - "pytest-xdist", - "slepc4py==3.22.2", + "ipympl", # needed for notebook testing + "matplotlib", + "mpi-pytest", + "nbval", + "pylit", + "pytest", + "pytest-split", # needed for firedrake-run-split-tests + "pytest-timeout", + "pytest-xdist", + "slepc4py==3.22.2", ] # Dependencies needed to build the docs docs = [ - "bibtexparser", - "matplotlib", # needed to resolve API - "numpydoc", - "pylit", - "sphinx<8.2.0", # https://github.com/firedrakeproject/firedrake/issues/4059 - "sphinx-autobuild", - "sphinx-reredirects", - "sphinxcontrib-bibtex", - "sphinxcontrib-jquery", - "sphinxcontrib-svg2pdfconverter", - "sphinxcontrib-youtube", - "vtk", # needed to resolve API -] -# Developer dependencies. In particular the build dependencies that are needed to run 'make'. -dev = [ - "Cython", - "mpi-pytest", - "pybind11", - "pytest", - "setuptools", + "bibtexparser", + "matplotlib", # needed to resolve API + "numpydoc", + "pylit", + "sphinx<8.2.0", # https://github.com/firedrakeproject/firedrake/issues/4059 + "sphinx-autobuild", + "sphinx-reredirects", + "sphinxcontrib-bibtex", + "sphinxcontrib-jquery", + "sphinxcontrib-svg2pdfconverter", + "sphinxcontrib-youtube", + "vtk", # needed to resolve API ] [build-system] requires = [ - "Cython>=3.0", - "libsupermesh", - "mpi4py>3; python_version >= '3.13'", - "mpi4py; python_version < '3.13'", - "numpy", - "pkgconfig", - "pybind11", - "setuptools>61.2", - "petsc4py==3.22.2", - "rtree>=1.2", + "Cython>=3.0", + "libsupermesh", + "mpi4py>3; python_version >= '3.13'", + "mpi4py; python_version < '3.13'", + "numpy", + "pkgconfig", + "pybind11", + "setuptools>61.2", + "petsc4py==3.22.2", + "rtree>=1.2", ] build-backend = "setuptools.build_meta" # TODO: Convert firedrake-zenodo to a proper entrypoint script. [tool.setuptools] script-files = [ - "firedrake/scripts/firedrake-zenodo", - "scripts/firedrake-run-split-tests", + "firedrake/scripts/firedrake-zenodo", + "scripts/firedrake-run-split-tests", ] [tool.setuptools.package-data] # Unless specified these files will not be installed along with the # rest of the package -firedrake = ["evaluate.h", "locate.c", "icons/*.png"] -firedrake_check = ["Makefile", "tests/firedrake/conftest.py", "tests/*/*/*.py"] -pyop2 = ["*.h", "*.pxd", "*.pyx", "codegen/c/*.c"] +firedrake = [ + "evaluate.h", + "locate.c", + "icons/*.png", +] +firedrake_check = [ + "Makefile", + "tests/firedrake/conftest.py", + "tests/*/*/*.py", +] +pyop2 = [ + "*.h", + "*.pxd", + "*.pyx", + "codegen/c/*.c", +] diff --git a/scripts/firedrake-dev-patch b/scripts/firedrake-dev-patch index a551e3e23b..3164b7ba2d 100755 --- a/scripts/firedrake-dev-patch +++ b/scripts/firedrake-dev-patch @@ -8,45 +8,65 @@ import tomli import tomli_w +REPO = pathlib.Path(__file__).parent.parent + + # TODO: # * logging # * save prev pyproj as pyproject.toml.orig -# * also patch requirements-build.txt def main(): args = parse_args() + patch_pyproject(args.existing_packages) + patch_build_requirements(args.existing_packages) + print("done") - repo_root = pathlib.Path(__file__).parent.parent - with open(repo_root / "pyproject.toml", "rb") as f: +def parse_args(): + parser = argparse.ArgumentParser() + parser.add_argument("existing_packages", nargs="*") + return parser.parse_args() + + +def patch_pyproject(existing_packages): + with open(REPO / "pyproject.toml", "rb") as f: data = tomli.load(f) removed_deps = set() - removed_deps |= filter_deps(data["project"]["dependencies"], args.existing_packages) + removed_deps |= filter_deps(data["project"]["dependencies"], existing_packages) for optional_deps in data["project"]["optional-dependencies"].values(): - removed_deps |= filter_deps(optional_deps, args.existing_packages) + removed_deps |= filter_deps(optional_deps, existing_packages) print("Removed: ", removed_deps) - with open(repo_root / "pyproject.toml", "wb") as f: + with open(REPO / "pyproject.toml", "wb") as f: tomli_w.dump(data, f) -def parse_args(): - parser = argparse.ArgumentParser() - parser.add_argument("existing_packages", nargs="*") - return parser.parse_args() +def patch_build_requirements(existing_packages): + with open(REPO / "requirements-build.txt", "r") as f: + deps = f.read().splitlines() + + filtered_deps = [dep for dep in deps if not matches_existing(dep, existing_packages)] + + with open(REPO / "requirements-build.txt", "w") as f: + f.write("\n".join(filtered_deps)) def filter_deps(deps, existing_packages): to_remove = set() for dep in deps: - if any(dep.startswith(p) for p in existing_packages): + if matches_existing(dep, existing_packages): to_remove.add(dep) for dep in to_remove: deps.remove(dep) return to_remove +def matches_existing(spec, existing_packages): + return any(spec.startswith(p) for p in existing_packages) + + + if __name__ == "__main__": main() From 6fff6dd31015a8380a26da6af6eb835e2c94c3c4 Mon Sep 17 00:00:00 2001 From: Connor Ward Date: Tue, 8 Apr 2025 14:12:50 +0100 Subject: [PATCH 07/39] Add transitive dep --- requirements-build.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/requirements-build.txt b/requirements-build.txt index 72aee3e88a..7777cd433f 100644 --- a/requirements-build.txt +++ b/requirements-build.txt @@ -7,3 +7,6 @@ pkgconfig pybind11 setuptools>61.2 rtree>=1.2 + +# transitive build dependencies +hatchling From c43a0e89ca38dc7f4695b9b83b5edf4e389a8555 Mon Sep 17 00:00:00 2001 From: Connor Ward Date: Tue, 8 Apr 2025 17:57:36 +0100 Subject: [PATCH 08/39] An idea, don't love it but might be the only way --- .github/workflows/build.yml | 23 ++++++------ pyproject.toml | 8 ++--- requirements-build.txt | 12 ------- scripts/firedrake-dev-patch | 72 ------------------------------------- 4 files changed, 14 insertions(+), 101 deletions(-) delete mode 100644 requirements-build.txt delete mode 100755 scripts/firedrake-dev-patch diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5415920f3f..b0802a68d7 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -93,31 +93,28 @@ jobs: export $(python3 ./firedrake-repo/scripts/firedrake-configure --arch "${{ matrix.arch }}" --show-env) python3 -m venv venv . venv/bin/activate - : # Force a rebuild of petsc4py as the cached one will not link to the fresh - : # install of PETSc. - pip cache remove petsc4py - pip cache remove slepc4py if [ "${{ github.ref_name == 'master' }}" ]; then + pip install Cython libsupermesh mpi4py numpy pkgconfig pybind11 setuptools rtree pip install $PETSC_DIR/src/binding/petsc4py pip install $PETSC_DIR/$PETSC_ARCH/externalpackages/git.slepc/src/binding/slepc4py - ./scripts/firedrake-dev-patch petsc4py slepc4py - - : # Install the remaining build dependencies - pip install -r requirements-build.txt - : # We have to pass '--no-build-isolation' to use a custom petsc4py - PIP_FLAGS='--no-build-isolation' + EXTRA_PIP_FLAGS='--no-build-isolation' else - : # release - PIP_FLAGS='' + : # Force a rebuild of petsc4py as the cached one will not link to the fresh + : # install of PETSc. + pip cache remove petsc4py + pip cache remove slepc4py + + EXTRA_PIP_FLAGS='' fi - pip install --verbose "$PIP_FLAGS" \ + pip install --verbose $EXTRA_PIP_FLAGS \ --no-binary h5py \ --extra-index-url https://download.pytorch.org/whl/cpu \ './firedrake-repo[ci]' + firedrake-clean pip list diff --git a/pyproject.toml b/pyproject.toml index 77761b9015..bab373df56 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,7 +23,7 @@ dependencies = [ "libsupermesh", # NOTE: If changing the PETSc/SLEPc version then firedrake-configure also needs # changing (as well as other references to petsc4py and slepc4py here) - "petsc4py==3.22.2", + # "petsc4py==3.22.2", "numpy", "packaging", "pkgconfig", @@ -85,7 +85,7 @@ ci = [ "pytest-split", # needed for firedrake-run-split-tests "pytest-timeout", "pytest-xdist", - "slepc4py==3.22.2", + # "slepc4py==3.22.2", "torch", # requires passing '--extra-index-url' to work "vtk", ] @@ -99,7 +99,7 @@ docker = [ # Used in firedrake-vanilla container "pytest-split", # needed for firedrake-run-split-tests "pytest-timeout", "pytest-xdist", - "slepc4py==3.22.2", + # "slepc4py==3.22.2", ] # Dependencies needed to build the docs docs = [ @@ -127,7 +127,7 @@ requires = [ "pkgconfig", "pybind11", "setuptools>61.2", - "petsc4py==3.22.2", + # "petsc4py==3.22.2", "rtree>=1.2", ] build-backend = "setuptools.build_meta" diff --git a/requirements-build.txt b/requirements-build.txt deleted file mode 100644 index 7777cd433f..0000000000 --- a/requirements-build.txt +++ /dev/null @@ -1,12 +0,0 @@ -Cython>=3.0 -libsupermesh -mpi4py>3; python_version >= '3.13' -mpi4py; python_version < '3.13' -numpy -pkgconfig -pybind11 -setuptools>61.2 -rtree>=1.2 - -# transitive build dependencies -hatchling diff --git a/scripts/firedrake-dev-patch b/scripts/firedrake-dev-patch deleted file mode 100755 index 3164b7ba2d..0000000000 --- a/scripts/firedrake-dev-patch +++ /dev/null @@ -1,72 +0,0 @@ -#!/usr/bin/env python3 - -"""TODO""" - -import argparse -import pathlib -import tomli -import tomli_w - - -REPO = pathlib.Path(__file__).parent.parent - - -# TODO: -# * logging -# * save prev pyproj as pyproject.toml.orig - -def main(): - args = parse_args() - patch_pyproject(args.existing_packages) - patch_build_requirements(args.existing_packages) - print("done") - - -def parse_args(): - parser = argparse.ArgumentParser() - parser.add_argument("existing_packages", nargs="*") - return parser.parse_args() - - -def patch_pyproject(existing_packages): - with open(REPO / "pyproject.toml", "rb") as f: - data = tomli.load(f) - - removed_deps = set() - removed_deps |= filter_deps(data["project"]["dependencies"], existing_packages) - for optional_deps in data["project"]["optional-dependencies"].values(): - removed_deps |= filter_deps(optional_deps, existing_packages) - - print("Removed: ", removed_deps) - - with open(REPO / "pyproject.toml", "wb") as f: - tomli_w.dump(data, f) - - -def patch_build_requirements(existing_packages): - with open(REPO / "requirements-build.txt", "r") as f: - deps = f.read().splitlines() - - filtered_deps = [dep for dep in deps if not matches_existing(dep, existing_packages)] - - with open(REPO / "requirements-build.txt", "w") as f: - f.write("\n".join(filtered_deps)) - - -def filter_deps(deps, existing_packages): - to_remove = set() - for dep in deps: - if matches_existing(dep, existing_packages): - to_remove.add(dep) - for dep in to_remove: - deps.remove(dep) - return to_remove - - -def matches_existing(spec, existing_packages): - return any(spec.startswith(p) for p in existing_packages) - - - -if __name__ == "__main__": - main() From 309ef1228718ea3b90588f84339b935c8cdaa9d7 Mon Sep 17 00:00:00 2001 From: Connor Ward Date: Tue, 8 Apr 2025 18:05:48 +0100 Subject: [PATCH 09/39] fixup? --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b0802a68d7..eee99ad739 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -77,7 +77,7 @@ jobs: - name: Install PETSc run: | - git clone --depth 1 $PETSC_REFSPEC https://gitlab.com/petsc/petsc.git + git clone --depth 1 $(echo $PETSC_REFSPEC) https://gitlab.com/petsc/petsc.git cd petsc python3 ../firedrake-repo/scripts/firedrake-configure \ --arch ${{ matrix.arch }} --show-petsc-configure-options | \ From 3eec1f9866d6c6233acb298c8644015b7d25af1b Mon Sep 17 00:00:00 2001 From: Connor Ward Date: Tue, 8 Apr 2025 18:23:22 +0100 Subject: [PATCH 10/39] Working? --- .github/workflows/build.yml | 14 ++++++++++---- pyproject.toml | 3 ++- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index eee99ad739..967b12aee6 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -68,12 +68,12 @@ jobs: # if: github.ref == 'refs/heads/master' if: false # run: echo "PETSC_REFSPEC=''" >> "$GITHUB_ENV" - run: echo "PETSC_REFSPEC='--branch $(python3 ./firedrake-repo/scripts/firedrake-configure --show-petsc-version)'" >> "$GITHUB_ENV" + run: echo "PETSC_REFSPEC=--branch $(python3 ./firedrake-repo/scripts/firedrake-configure --show-petsc-version)" >> "$GITHUB_ENV" - name: Determine PETSc refspec (release) # if: github.ref == 'refs/heads/release' if: true # debugging, does this parse? - run: echo "PETSC_REFSPEC='--branch $(python3 ./firedrake-repo/scripts/firedrake-configure --show-petsc-version)'" >> "$GITHUB_ENV" + run: echo "PETSC_REFSPEC=--branch $(python3 ./firedrake-repo/scripts/firedrake-configure --show-petsc-version)" >> "$GITHUB_ENV" - name: Install PETSc run: | @@ -96,11 +96,17 @@ jobs: if [ "${{ github.ref_name == 'master' }}" ]; then pip install Cython libsupermesh mpi4py numpy pkgconfig pybind11 setuptools rtree - pip install $PETSC_DIR/src/binding/petsc4py - pip install $PETSC_DIR/$PETSC_ARCH/externalpackages/git.slepc/src/binding/slepc4py + pip install "$PETSC_DIR"/src/binding/petsc4py + pip install "$PETSC_DIR"/"$PETSC_ARCH"/externalpackages/git.slepc/src/binding/slepc4py + + : # ngsPETSc needs petsc4py so we have to pass '--no-deps' to make sure it + : # doesn't fetch it from PyPI + pip install --no-deps ngsPETSc netgen-mesher netgen-occt : # We have to pass '--no-build-isolation' to use a custom petsc4py EXTRA_PIP_FLAGS='--no-build-isolation' + + : # Maybe patch pyproject.toml? So we don't need to maintain two of them... else : # Force a rebuild of petsc4py as the cached one will not link to the fresh : # install of PETSc. diff --git a/pyproject.toml b/pyproject.toml index bab373df56..c686dd16b5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -72,6 +72,7 @@ test = [ "mpi-pytest", "pytest", ] +# NOTE: I think 'ci' and 'docker' dependency groups are silly because they are single use # Dependencies needed to run the full test suite ci = [ "ipympl", # needed for notebook testing @@ -79,7 +80,7 @@ ci = [ "matplotlib", "mpi-pytest", "nbval", - "ngsPETSc", + # "ngsPETSc", "pylit", "pytest", "pytest-split", # needed for firedrake-run-split-tests From 2a39b8272bdd39e101ae0f2460b77bd13fd8bc5f Mon Sep 17 00:00:00 2001 From: Connor Ward Date: Tue, 8 Apr 2025 18:29:38 +0100 Subject: [PATCH 11/39] cleanup --- .github/workflows/build.yml | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 967b12aee6..2dd4ce3448 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -65,19 +65,16 @@ jobs: # TODO: is there a 'blessed commit' or do we just point to 'main'? - name: Determine PETSc refspec (master) - # if: github.ref == 'refs/heads/master' - if: false - # run: echo "PETSC_REFSPEC=''" >> "$GITHUB_ENV" - run: echo "PETSC_REFSPEC=--branch $(python3 ./firedrake-repo/scripts/firedrake-configure --show-petsc-version)" >> "$GITHUB_ENV" + if: github.ref_name == 'master' + run: echo "PETSC_REFSPEC=" >> "$GITHUB_ENV" - name: Determine PETSc refspec (release) - # if: github.ref == 'refs/heads/release' - if: true # debugging, does this parse? + if: github.ref_name == 'release' run: echo "PETSC_REFSPEC=--branch $(python3 ./firedrake-repo/scripts/firedrake-configure --show-petsc-version)" >> "$GITHUB_ENV" - name: Install PETSc run: | - git clone --depth 1 $(echo $PETSC_REFSPEC) https://gitlab.com/petsc/petsc.git + git clone --depth 1 $PETSC_REFSPEC https://gitlab.com/petsc/petsc.git cd petsc python3 ../firedrake-repo/scripts/firedrake-configure \ --arch ${{ matrix.arch }} --show-petsc-configure-options | \ From 40b49ce4176833851239213c2dd87986853adf7e Mon Sep 17 00:00:00 2001 From: Connor Ward Date: Tue, 8 Apr 2025 18:49:44 +0100 Subject: [PATCH 12/39] Install transitive build deps --- .github/workflows/build.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2dd4ce3448..43632a505f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -92,7 +92,9 @@ jobs: . venv/bin/activate if [ "${{ github.ref_name == 'master' }}" ]; then + : # build deps (including transitive) pip install Cython libsupermesh mpi4py numpy pkgconfig pybind11 setuptools rtree + pip install hatchling pip install "$PETSC_DIR"/src/binding/petsc4py pip install "$PETSC_DIR"/"$PETSC_ARCH"/externalpackages/git.slepc/src/binding/slepc4py From 74f5a161b2024b8d735f130c8bd60d9afac1e4f0 Mon Sep 17 00:00:00 2001 From: Connor Ward Date: Wed, 9 Apr 2025 06:47:15 +0100 Subject: [PATCH 13/39] Fixup --- .github/workflows/build.yml | 25 ++++++++++++------------- requirements-dev.txt | 13 +++++++++++++ 2 files changed, 25 insertions(+), 13 deletions(-) create mode 100644 requirements-dev.txt diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 43632a505f..d460a8ba7b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -91,27 +91,26 @@ jobs: python3 -m venv venv . venv/bin/activate + : # Force a rebuild of petsc4py as the cached one will not link to the fresh + : # install of PETSc. + pip cache remove petsc4py + pip cache remove slepc4py + + if [ "${{ github.ref_name == 'master' }}" ]; then - : # build deps (including transitive) - pip install Cython libsupermesh mpi4py numpy pkgconfig pybind11 setuptools rtree - pip install hatchling + : # Install build dependencies + pip install -r requirements-dev.txt pip install "$PETSC_DIR"/src/binding/petsc4py - pip install "$PETSC_DIR"/"$PETSC_ARCH"/externalpackages/git.slepc/src/binding/slepc4py - : # ngsPETSc needs petsc4py so we have to pass '--no-deps' to make sure it - : # doesn't fetch it from PyPI + : # Install runtime dependencies that have been removed from the pyproject.toml + : # because they rely on non-PyPI versions of petsc4py. + pip install --no-build-isolation --no-deps \ + "$PETSC_DIR"/"$PETSC_ARCH"/externalpackages/git.slepc/src/binding/slepc4py pip install --no-deps ngsPETSc netgen-mesher netgen-occt : # We have to pass '--no-build-isolation' to use a custom petsc4py EXTRA_PIP_FLAGS='--no-build-isolation' - - : # Maybe patch pyproject.toml? So we don't need to maintain two of them... else - : # Force a rebuild of petsc4py as the cached one will not link to the fresh - : # install of PETSc. - pip cache remove petsc4py - pip cache remove slepc4py - EXTRA_PIP_FLAGS='' fi diff --git a/requirements-dev.txt b/requirements-dev.txt new file mode 100644 index 0000000000..5ad7383a5d --- /dev/null +++ b/requirements-dev.txt @@ -0,0 +1,13 @@ +# core build dependencies (adapted from pyproject.toml) +Cython>=3.0 +libsupermesh +mpi4py>3; python_version >= '3.13' +mpi4py; python_version < '3.13' +numpy +pkgconfig +pybind11 +setuptools>61.2 +rtree>=1.2 + +# transitive build dependencies +hatchling From bd679b53d178d4e071c2cfdda0f649672c17d714 Mon Sep 17 00:00:00 2001 From: Connor Ward Date: Wed, 9 Apr 2025 08:22:30 +0100 Subject: [PATCH 14/39] fixup --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d460a8ba7b..82daefacef 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -99,7 +99,7 @@ jobs: if [ "${{ github.ref_name == 'master' }}" ]; then : # Install build dependencies - pip install -r requirements-dev.txt + pip install -r ./firedrake-repo/requirements-dev.txt pip install "$PETSC_DIR"/src/binding/petsc4py : # Install runtime dependencies that have been removed from the pyproject.toml From 791236ccb3a675a2e8e3a8502681eb826dd3778d Mon Sep 17 00:00:00 2001 From: Connor Ward Date: Wed, 9 Apr 2025 09:14:42 +0100 Subject: [PATCH 15/39] fixup --- .github/workflows/build.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 82daefacef..95075556cd 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -83,6 +83,7 @@ jobs: make check echo "PETSC_DIR=/__w/firedrake/firedrake/petsc" >> "$GITHUB_ENV" echo "PETSC_ARCH=arch-firedrake-${{ matrix.arch }}" >> "$GITHUB_ENV" + echo "SLEPC_DIR=/__w/firedrake/firedrake/petsc/arch-firedrake-${{ matrix.arch }}" >> "$GITHUB_ENV" - name: Install Firedrake id: install From 8cc1763a2b09dd0137a7a68f48d0134b5c179853 Mon Sep 17 00:00:00 2001 From: Connor Ward Date: Wed, 9 Apr 2025 09:23:15 +0100 Subject: [PATCH 16/39] add TODO RELEASE comments and check --- .github/workflows/build.yml | 6 ++++++ pyproject.toml | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 95075556cd..194f0cef2a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -56,6 +56,12 @@ jobs: with: path: firedrake-repo + # Raise an error if any 'TODO RELEASE' comments remain + - name: Check release (optional) + # undo me + # if: github.ref_name == 'release' + run: exit test -z "$( git grep 'TODO RELEASE' )" + - name: Install system dependencies run: | apt-get update diff --git a/pyproject.toml b/pyproject.toml index c686dd16b5..2c0013e2e5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,6 +23,7 @@ dependencies = [ "libsupermesh", # NOTE: If changing the PETSc/SLEPc version then firedrake-configure also needs # changing (as well as other references to petsc4py and slepc4py here) + # TODO RELEASE # "petsc4py==3.22.2", "numpy", "packaging", @@ -34,6 +35,7 @@ dependencies = [ "rtree>=1.2", "scipy", "sympy", + # TODO RELEASE: use releases "fenics-ufl @ git+https://github.com/firedrakeproject/ufl.git", "fenics-fiat @ git+https://github.com/firedrakeproject/fiat.git", "pyadjoint-ad @ git+https://github.com/dolfin-adjoint/pyadjoint.git", @@ -80,12 +82,14 @@ ci = [ "matplotlib", "mpi-pytest", "nbval", + # TODO RELEASE # "ngsPETSc", "pylit", "pytest", "pytest-split", # needed for firedrake-run-split-tests "pytest-timeout", "pytest-xdist", + # TODO RELEASE # "slepc4py==3.22.2", "torch", # requires passing '--extra-index-url' to work "vtk", @@ -100,6 +104,7 @@ docker = [ # Used in firedrake-vanilla container "pytest-split", # needed for firedrake-run-split-tests "pytest-timeout", "pytest-xdist", + # TODO RELEASE # "slepc4py==3.22.2", ] # Dependencies needed to build the docs @@ -128,6 +133,7 @@ requires = [ "pkgconfig", "pybind11", "setuptools>61.2", + # TODO RELEASE # "petsc4py==3.22.2", "rtree>=1.2", ] From 0e5b15068797b249d05aadef6e3b17e667effd05 Mon Sep 17 00:00:00 2001 From: Connor Ward Date: Wed, 9 Apr 2025 09:25:38 +0100 Subject: [PATCH 17/39] fixup --- .github/workflows/build.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 194f0cef2a..2529cd2cdd 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -60,7 +60,9 @@ jobs: - name: Check release (optional) # undo me # if: github.ref_name == 'release' - run: exit test -z "$( git grep 'TODO RELEASE' )" + run: | + test -z "$( git grep 'TODO RELEASE' )" + exit "$PIPESTATUS" - name: Install system dependencies run: | From c8209fce01cee71d0db9808df4635dbfdd9e0834 Mon Sep 17 00:00:00 2001 From: Connor Ward Date: Wed, 9 Apr 2025 09:35:42 +0100 Subject: [PATCH 18/39] fixup --- .github/workflows/build.yml | 36 +++++++++++++++++++----------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2529cd2cdd..3eb207a9bd 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -56,14 +56,6 @@ jobs: with: path: firedrake-repo - # Raise an error if any 'TODO RELEASE' comments remain - - name: Check release (optional) - # undo me - # if: github.ref_name == 'release' - run: | - test -z "$( git grep 'TODO RELEASE' )" - exit "$PIPESTATUS" - - name: Install system dependencies run: | apt-get update @@ -71,18 +63,28 @@ jobs: apt-get -y install \ $(python3 ./firedrake-repo/scripts/firedrake-configure --arch ${{ matrix.arch }} --show-system-packages) python3-venv parallel - # TODO: is there a 'blessed commit' or do we just point to 'main'? - - name: Determine PETSc refspec (master) - if: github.ref_name == 'master' - run: echo "PETSC_REFSPEC=" >> "$GITHUB_ENV" - - - name: Determine PETSc refspec (release) - if: github.ref_name == 'release' - run: echo "PETSC_REFSPEC=--branch $(python3 ./firedrake-repo/scripts/firedrake-configure --show-petsc-version)" >> "$GITHUB_ENV" + # Raise an error if any 'TODO RELEASE' comments remain + - name: Check no release TODO comments (optional) + # undo me + # if: github.ref_name == 'release' + run: | + if [ -z "$( git grep 'TODO RELEASE' )" ]; then + exit 0 + else + exit 1 + fi + # TODO: is there a 'blessed commit' or do we just point to 'main'? + # I prefer the latter because then failures are everyone's problem, not just mine! - name: Install PETSc run: | - git clone --depth 1 $PETSC_REFSPEC https://gitlab.com/petsc/petsc.git + if [ "${{ github.ref_name == 'master' }}" ]; then + git clone --depth 1 https://gitlab.com/petsc/petsc.git + else + git clone --depth 1 \ + --branch $(python3 ./firedrake-repo/scripts/firedrake-configure --show-petsc-version) \ + https://gitlab.com/petsc/petsc.git + fi cd petsc python3 ../firedrake-repo/scripts/firedrake-configure \ --arch ${{ matrix.arch }} --show-petsc-configure-options | \ From fadff35520de9a988b2b6ca3e323d2039b342cac Mon Sep 17 00:00:00 2001 From: Connor Ward Date: Wed, 9 Apr 2025 09:37:20 +0100 Subject: [PATCH 19/39] fixup --- .github/workflows/build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3eb207a9bd..3f412ad509 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -64,11 +64,11 @@ jobs: $(python3 ./firedrake-repo/scripts/firedrake-configure --arch ${{ matrix.arch }} --show-system-packages) python3-venv parallel # Raise an error if any 'TODO RELEASE' comments remain - - name: Check no release TODO comments (optional) + - name: Check no 'TODO RELEASE' comments (release only) # undo me # if: github.ref_name == 'release' run: | - if [ -z "$( git grep 'TODO RELEASE' )" ]; then + if [ -z "$( git -C ./firedrake-repo grep 'TODO RELEASE' )" ]; then exit 0 else exit 1 From 25ea94296dde15f94c3d4492393c4632306148a6 Mon Sep 17 00:00:00 2001 From: Connor Ward Date: Wed, 9 Apr 2025 09:55:59 +0100 Subject: [PATCH 20/39] Add publishing workflows --- .github/workflows/build.yml | 30 -------- .github/workflows/docker.yml | 16 +---- .github/workflows/docker_merge.yml | 20 +++--- .github/workflows/pypi.yml | 106 +++++++++++++++++++++++++++++ .github/workflows/release.yml | 21 ++++++ 5 files changed, 139 insertions(+), 54 deletions(-) create mode 100644 .github/workflows/pypi.yml create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3f412ad509..2c01cbfd94 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -16,9 +16,6 @@ concurrency: ${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true -env: - RELEASE_TAG: latest - jobs: test: name: Install and test Firedrake (Linux) @@ -249,33 +246,6 @@ jobs: if: always() run: find . -delete - docker_tag: - name: "Set the Docker release tag" - runs-on: [self-hosted, Linux] - if: github.ref == 'refs/heads/master' - steps: - - name: Set release tag - # Set a release tag if triggered by monthly CRON job - if: github.event.schedule == '0 0 1 * *' - run: | - DATE_TAG="$(date +%Y-%m)" - echo "RELEASE_TAG=$DATE_TAG" >> "$GITHUB_ENV" - - name: Print release tag being used - run: | - echo The release tag is "$RELEASE_TAG" - outputs: - tag: ${{ env.RELEASE_TAG }} - - docker: - name: Build Docker containers - if: github.ref == 'refs/heads/master' - needs: [test, docker_tag] - uses: ./.github/workflows/docker.yml - with: - tag: ${{ needs.docker_tag.outputs.tag }} - status: ${{ needs.test.result }} - secrets: inherit - lint: name: Lint codebase runs-on: ubuntu-latest diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 45e74f56b9..b26812f672 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -4,13 +4,7 @@ on: workflow_call: inputs: tag: - description: 'Optional tag (defaults to `latest`)' - required: false - default: 'latest' - type: string - status: - description: 'Firedrake test status' - required: true + description: Docker image tag type: string secrets: # Docker login information @@ -22,8 +16,6 @@ on: jobs: # Firedrake container (just Firedrake) docker_build_vanilla: - # Only run if Firedrake tests pass - if: inputs.status == 'success' strategy: fail-fast: false matrix: @@ -58,8 +50,6 @@ jobs: # # Firedrake container (Firedrake and friends) docker_build_firedrake: - # Only run if "Build Firedrake" succeeds - if: inputs.status == 'success' needs: docker_merge_vanilla uses: ./.github/workflows/docker_build.yml # Only build the 'firedrake' container for 'linux/amd64' because @@ -84,8 +74,6 @@ jobs: # # Firedrake container with TeX docker_build_docdeps: - # Only run if "Build Firedrake" succeeds - if: inputs.status == 'success' needs: docker_merge_vanilla uses: ./.github/workflows/docker_build.yml with: @@ -106,8 +94,6 @@ jobs: # # Firedrake container with Jupyter notebooks docker_build_jupyter: - # Only run if "Build Firedrake" succeeds - if: inputs.status == 'success' needs: docker_merge_firedrake uses: ./.github/workflows/docker_build.yml with: diff --git a/.github/workflows/docker_merge.yml b/.github/workflows/docker_merge.yml index c0b3f2c3a0..b7221fb10c 100644 --- a/.github/workflows/docker_merge.yml +++ b/.github/workflows/docker_merge.yml @@ -7,13 +7,12 @@ on: workflow_call: inputs: target: - description: 'Target docker image name to upload to' + description: Docker image name required: true type: string tag: - description: 'Optional tag (defaults to `latest`)' - required: false - default: 'latest' + description: Docker image tag + required: true type: string secrets: # Docker login information @@ -28,8 +27,7 @@ jobs: steps: - name: Pre-cleanup if: always() - run: | - rm -rf ${{ runner.temp }}/digests + run: rm -rf ${{ runner.temp }}/digests - name: Download digests uses: actions/download-artifact@v4 @@ -47,10 +45,15 @@ jobs: - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 + # NOTE: This action pushes a new image with the given tag but also updates + # the 'latest' tag. - name: Merge and push the per-platform images working-directory: ${{ runner.temp }}/digests run: | - docker buildx imagetools create -t firedrakeproject/${{ inputs.target }}:${{ inputs.tag }} $(printf 'firedrakeproject/${{ inputs.target }}@sha256:%s ' *) + docker buildx imagetools create \ + -t firedrakeproject/${{ inputs.target }}:${{ inputs.tag }} \ + -t firedrakeproject/${{ inputs.target }}:latest \ + $(printf 'firedrakeproject/${{ inputs.target }}@sha256:%s ' *) - name: Inspect image run: | @@ -58,5 +61,4 @@ jobs: - name: Post-cleanup if: always() - run: | - rm -rf ${{ runner.temp }}/digests + run: rm -rf ${{ runner.temp }}/digests diff --git a/.github/workflows/pypi.yml b/.github/workflows/pypi.yml new file mode 100644 index 0000000000..3383771155 --- /dev/null +++ b/.github/workflows/pypi.yml @@ -0,0 +1,106 @@ +name: Publish on PyPI + +# By default this action does not push to test or production PyPI. The wheels +# are available as an artifact that can be downloaded and tested locally. + +on: + workflow_dispatch: + inputs: + ref: + description: Git ref to publish + default: master + type: string + pypi: + description: Publish to PyPI + default: false + type: boolean + test_pypi: + description: Publish to TestPyPI + default: false + type: boolean + + workflow_call: + inputs: + ref: + description: Git ref to publish + default: master + type: string + pypi: + description: Publish to PyPI + default: false + type: boolean + test_pypi: + description: Publish to TestPyPI + default: false + type: boolean + + # UNDO ME + pull_request: + +jobs: + build: + name: Build distribution files + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + # UNDO ME + # ref: ${{ github.event.inputs.ref }} + ref: master + + - uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: Install build dependencies + run: python -m pip install --upgrade build pip setuptools + + - name: Build sdist and wheel + run: python -m build . + + - uses: actions/upload-artifact@v4 + with: + name: dist + path: dist/* + + upload_pypi: + name: Upload to PyPI (optional) + # UNDO ME + # if: github.event.inputs.pypi == 'true' + if: false + needs: build + runs-on: ubuntu-latest + environment: + name: pypi + permissions: + id-token: write + steps: + - uses: actions/download-artifact@v4 + with: + name: dist + path: dist + + - name: Push to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + + upload_test_pypi: + name: Upload to TestPyPI (optional) + # UNDO ME + # if: github.event.inputs.test_pypi == 'true' + if: false + needs: build + runs-on: ubuntu-latest + environment: + name: testpypi + permissions: + id-token: write + steps: + - uses: actions/download-artifact@v4 + with: + name: dist + path: dist + + - name: Push to TestPyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + repository-url: https://test.pypi.org/legacy/ diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000000..acf7e45e4b --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,21 @@ +name: Publish release + +on: + release: + types: [published] + +jobs: + pypi_publish: + name: Publish release on PyPI + uses: ./.github/workflows/pypi.yml + with: + ref: ${{ github.ref }} + pypi: true + secrets: inherit + + docker: + name: Build Docker containers + uses: ./.github/workflows/docker.yml + with: + tag: ${{ github.ref }} + secrets: inherit From ebd66da7f97757ec4728bae1bce877e72df2818b Mon Sep 17 00:00:00 2001 From: Connor Ward Date: Wed, 9 Apr 2025 09:59:31 +0100 Subject: [PATCH 21/39] fixup --- .github/workflows/pypi.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pypi.yml b/.github/workflows/pypi.yml index 3383771155..44abbcd70b 100644 --- a/.github/workflows/pypi.yml +++ b/.github/workflows/pypi.yml @@ -55,8 +55,8 @@ jobs: - name: Install build dependencies run: python -m pip install --upgrade build pip setuptools - - name: Build sdist and wheel - run: python -m build . + - name: Build sdist + run: python -m build . --sdist - uses: actions/upload-artifact@v4 with: From b45aec0f87cbaf1cd2001836e3d6ff64b975f57f Mon Sep 17 00:00:00 2001 From: Connor Ward Date: Wed, 9 Apr 2025 10:36:48 +0100 Subject: [PATCH 22/39] Some noodling and comments --- .github/workflows/docs.yml | 14 ++++++++++---- .github/workflows/pypi.yml | 9 +++------ 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index b9c5823de7..3f87bc1a78 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -1,11 +1,10 @@ name: Check docs build cleanly on: - # Run on pushes to master push: branches: - master - # And all pull requests + - release pull_request: concurrency: @@ -28,9 +27,13 @@ jobs: with: path: firedrake-repo + # this wont work any more because we wont have a docdeps container + # for firedrake. I think we should probably just do the docs build in + # the main workflow - can wait until this afternoon for that to merge - name: Install Firedrake id: install run: | + exit 1 : # Pass '--system-site-packages' so already installed packages can be found python3 -m venv --system-site-packages venv . venv/bin/activate @@ -82,13 +85,16 @@ jobs: name: Deploy Github pages needs: build_docs # Always run this workflow on master, even if linkcheck fails - if: always() && github.ref == 'refs/heads/master' && needs.build_docs.outputs.conclusion == 'success' + if: | + always() && + needs.build_docs.outputs.conclusion == 'success' && + (github.ref_name == 'master' || github.ref_name == 'release') permissions: pages: write id-token: write environment: name: github-pages - url: http://firedrakeproject.github.io/firedrake + url: http://firedrakeproject.github.io/firedrake/${{ github.ref_name }} runs-on: ubuntu-latest steps: - name: Deploy to GitHub Pages diff --git a/.github/workflows/pypi.yml b/.github/workflows/pypi.yml index 44abbcd70b..b80e5b06e8 100644 --- a/.github/workflows/pypi.yml +++ b/.github/workflows/pypi.yml @@ -37,6 +37,8 @@ on: # UNDO ME pull_request: +# NOTE: This will not work until we have a release branch because we want an unmodified +# pyproject here jobs: build: name: Build distribution files @@ -44,17 +46,12 @@ jobs: steps: - uses: actions/checkout@v4 with: - # UNDO ME - # ref: ${{ github.event.inputs.ref }} - ref: master + ref: ${{ github.event.inputs.ref }} - uses: actions/setup-python@v5 with: python-version: "3.12" - - name: Install build dependencies - run: python -m pip install --upgrade build pip setuptools - - name: Build sdist run: python -m build . --sdist From 28224b5dfb382cea94a07019cad594dc314367de Mon Sep 17 00:00:00 2001 From: Connor Ward Date: Fri, 11 Apr 2025 11:56:30 +0100 Subject: [PATCH 23/39] Begin refactoring workflows --- .github/workflows/{test.yml => ci.yml} | 240 ++++++++++++++----------- .github/workflows/pr.yml | 11 ++ .github/workflows/push.yml | 15 ++ .github/workflows/pypi.yml | 22 --- 4 files changed, 158 insertions(+), 130 deletions(-) rename .github/workflows/{test.yml => ci.yml} (83%) create mode 100644 .github/workflows/pr.yml create mode 100644 .github/workflows/push.yml diff --git a/.github/workflows/test.yml b/.github/workflows/ci.yml similarity index 83% rename from .github/workflows/test.yml rename to .github/workflows/ci.yml index dbb5c81360..70475cafd7 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/ci.yml @@ -1,13 +1,24 @@ -name: CI +name: Test Firedrake on: - push: - branches: - - master - pull_request: - schedule: - - cron: '0 0 * * 0' - - cron: '0 0 1 * *' # Monthly release + workflow_call: + inputs: + target_branch: + description: The target branch (usually 'master' or 'release') + type: string + required: true + deploy_website: + description: Whether to deploy the website + type: bool + default: false + upload_pypi: + description: Whether to upload an sdist to PyPI + type: bool + default: false + upload_testpypi: + description: Whether to upload an sdist to TestPyPI + type: bool + default: false concurrency: # Cancel running jobs if new commits are pushed @@ -38,6 +49,8 @@ jobs: PYOP2_SPMD_STRICT: 1 EXTRA_PYTEST_ARGS: --splitting-algorithm least_duration --timeout=600 --timeout-method=thread -o faulthandler_timeout=660 PYTEST_MPI_MAX_NPROCS: 8 + outputs: + conclusion: ${{ steps.report_docs.outputs.conclusion }} steps: - name: Fix HOME # For unknown reasons GitHub actions overwrite HOME to /github/home @@ -62,8 +75,7 @@ jobs: # Raise an error if any 'TODO RELEASE' comments remain - name: Check no 'TODO RELEASE' comments (release only) - # undo me - # if: github.ref_name == 'release' + if: inputs.target_branch == 'release' run: | if [ -z "$( git -C ./firedrake-repo grep 'TODO RELEASE' )" ]; then exit 0 @@ -75,12 +87,12 @@ jobs: # I prefer the latter because then failures are everyone's problem, not just mine! - name: Install PETSc run: | - if [ "${{ github.ref_name == 'master' }}" ]; then - git clone --depth 1 https://gitlab.com/petsc/petsc.git - else + if [ "${{ inputs.target_branch == 'release' }}" ]; then git clone --depth 1 \ --branch $(python3 ./firedrake-repo/scripts/firedrake-configure --show-petsc-version) \ - https://gitlab.com/petsc/petsc.git + https://gitlab.com/petsc/petsc.git + else + git clone --depth 1 https://gitlab.com/petsc/petsc.git fi cd petsc python3 ../firedrake-repo/scripts/firedrake-configure \ @@ -106,8 +118,9 @@ jobs: pip cache remove petsc4py pip cache remove slepc4py - - if [ "${{ github.ref_name == 'master' }}" ]; then + if [ "${{ inputs.target_branch == 'release' }}" ]; then + EXTRA_PIP_FLAGS='' + else : # Install build dependencies pip install -r ./firedrake-repo/requirements-dev.txt pip install "$PETSC_DIR"/src/binding/petsc4py @@ -120,14 +133,12 @@ jobs: : # We have to pass '--no-build-isolation' to use a custom petsc4py EXTRA_PIP_FLAGS='--no-build-isolation' - else - EXTRA_PIP_FLAGS='' fi pip install --verbose $EXTRA_PIP_FLAGS \ --no-binary h5py \ --extra-index-url https://download.pytorch.org/whl/cpu \ - './firedrake-repo[ci]' + './firedrake-repo[ci,docs]' firedrake-clean pip list @@ -138,6 +149,64 @@ jobs: firedrake-check timeout-minutes: 5 + # TODO: Move towards the bottom, just here for testing purposes + - name: Build sdist (release only) + if: inputs.target_branch == 'release' + run: | + pip install build + python -m build ./firedrake-repo --sdist + + - name: Upload sdist (release only) + if: inputs.target_branch == 'release' + uses: actions/upload-artifact@v4 + with: + name: dist + path: dist/* + + # TODO: Move further down + # Could be a separate stage? + - name: Check bibtex + run: | + . venv/bin/activate + make -C firedrake-repo/docs validate-bibtex + + - name: Check documentation links + run: | + . venv/bin/activate + make -C firedrake-repo/docs linkcheck + + - name: Build docs + id: build + if: success() || steps.install.conclusion == 'success' + run: | + . venv/bin/activate + cd firedrake-repo/docs + make html + make latex + make latexpdf + + - name: Copy manual to HTML tree + id: copy + if: success() || steps.build.conclusion == 'success' + run: | + cd firedrake-repo/docs + cp build/latex/Firedrake.pdf build/html/_static/manual.pdf + + - name: Upload artifact + id: upload + if: success() || steps.copy.conclusion == 'success' + uses: actions/upload-pages-artifact@v3 + with: + name: github-pages + path: /__w/firedrake/firedrake/firedrake-repo/docs/build/html + retention-days: 1 + + - name: Report docs build + id: report_docs + if: success() || steps.upload.conclusion == 'success' + run: echo "conclusion=success" >> "$GITHUB_OUTPUT" + + - name: Run TSFC tests # Run even if earlier tests failed if: success() || steps.install.conclusion == 'success' @@ -285,102 +354,57 @@ jobs: - name: Lint codebase run: make lint GITHUB_ACTIONS_FORMATTING=1 - zenodo_canary: - name: Run zenodo canary - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 1 - - name: Setup Python - uses: actions/setup-python@v5 - with: - python-version: "3.12" - - name: Install deps - run: pip install requests packaging - - name: Zenodo API canary - run: python scripts/firedrake-install --test-doi-resolution - - name: Upload log - uses: actions/upload-artifact@v4 - if: failure() - with: - name: zenodo-canary - path: firedrake-install.log - - build_docs: - name: Build documentation - runs-on: ubuntu-latest - container: - image: firedrakeproject/firedrake-docdeps:latest - outputs: - conclusion: ${{ steps.report.outputs.conclusion }} - steps: - - uses: actions/checkout@v4 - with: - path: firedrake-repo - - - name: Install Firedrake - id: install - run: | - : # Pass '--system-site-packages' so already installed packages can be found - python3 -m venv --system-site-packages venv - . venv/bin/activate - pip uninstall -y firedrake - pip install --verbose './firedrake-repo[docs]' - - - name: Check bibtex - run: | - . venv/bin/activate - make -C firedrake-repo/docs validate-bibtex - - - name: Check documentation links - run: | - . venv/bin/activate - make -C firedrake-repo/docs linkcheck - - - name: Build docs - id: build - if: success() || steps.install.conclusion == 'success' - run: | - . venv/bin/activate - cd firedrake-repo/docs - make html - make latex - make latexpdf - - - name: Copy manual to HTML tree - id: copy - if: success() || steps.build.conclusion == 'success' - run: | - cd firedrake-repo/docs - cp build/latex/Firedrake.pdf build/html/_static/manual.pdf - - - name: Upload artifact - id: upload - if: success() || steps.copy.conclusion == 'success' - uses: actions/upload-pages-artifact@v3 - with: - name: github-pages - path: /__w/firedrake/firedrake/firedrake-repo/docs/build/html - retention-days: 1 - - - name: Report status - id: report - if: success() || steps.upload.conclusion == 'success' - run: echo "conclusion=success" >> "$GITHUB_OUTPUT" - deploy: - name: Deploy Github pages - needs: build_docs - # Always run this workflow on master, even if linkcheck fails - if: always() && github.ref == 'refs/heads/master' && needs.build_docs.outputs.conclusion == 'success' + name: Deploy GitHub pages + needs: test + # Deploy even if linkcheck fails + if: | + always() && + needs.build_docs.outputs.conclusion == 'success' && + inputs.deploy_website permissions: pages: write id-token: write environment: name: github-pages - url: http://firedrakeproject.github.io/firedrake + url: http://firedrakeproject.github.io/firedrake/${{ inputs.target_branch }} runs-on: ubuntu-latest steps: - name: Deploy to GitHub Pages uses: actions/deploy-pages@v4 + + upload_pypi: + name: Upload to PyPI (optional) + if: inputs.upload_pypi + needs: test + runs-on: ubuntu-latest + environment: + name: pypi + permissions: + id-token: write + steps: + - uses: actions/download-artifact@v4 + with: + name: dist + path: dist + - name: Push to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + + upload_testpypi: + name: Upload to TestPyPI (optional) + if: inputs.upload_testpypi + needs: test + runs-on: ubuntu-latest + environment: + name: testpypi + permissions: + id-token: write + steps: + - uses: actions/download-artifact@v4 + with: + name: dist + path: dist + - name: Push to TestPyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + repository-url: https://test.pypi.org/legacy/ diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml new file mode 100644 index 0000000000..c7b80422ea --- /dev/null +++ b/.github/workflows/pr.yml @@ -0,0 +1,11 @@ +name: Test pull request + +on: + pull_request: + +jobs: + test: + uses: ./.github/workflows/ci.yml + with: + target_branch: ${{ github.base_ref }} + secrets: inherit diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml new file mode 100644 index 0000000000..5669d4898f --- /dev/null +++ b/.github/workflows/push.yml @@ -0,0 +1,15 @@ +name: Deploy after push + +on: + push: + branches: + - master + - release + +jobs: + test: + uses: ./.github/workflows/test.yml + with: + target_branch: ${{ github.ref_name }} + deploy_website: true + secrets: inherit diff --git a/.github/workflows/pypi.yml b/.github/workflows/pypi.yml index b80e5b06e8..db2692ec07 100644 --- a/.github/workflows/pypi.yml +++ b/.github/workflows/pypi.yml @@ -37,29 +37,7 @@ on: # UNDO ME pull_request: -# NOTE: This will not work until we have a release branch because we want an unmodified -# pyproject here jobs: - build: - name: Build distribution files - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - ref: ${{ github.event.inputs.ref }} - - - uses: actions/setup-python@v5 - with: - python-version: "3.12" - - - name: Build sdist - run: python -m build . --sdist - - - uses: actions/upload-artifact@v4 - with: - name: dist - path: dist/* - upload_pypi: name: Upload to PyPI (optional) # UNDO ME From 03076f23484398ebb9826d710edb9250dff6364b Mon Sep 17 00:00:00 2001 From: Connor Ward Date: Fri, 11 Apr 2025 15:28:50 +0100 Subject: [PATCH 24/39] Big refactor of workflows There are effectively 3 entrypoints to the actions: pull request, push or release. These all rely to some extent on the same process of building and testing so everything goes through the build.yml file. --- .github/workflows/{ci.yml => build.yml} | 90 +++++++++++---------- .github/workflows/docs.yml | 101 ------------------------ .github/workflows/pr.yml | 2 +- .github/workflows/push.yml | 2 +- .github/workflows/pypi.yml | 81 ------------------- .github/workflows/release.yml | 9 +-- Makefile | 2 +- 7 files changed, 54 insertions(+), 233 deletions(-) rename .github/workflows/{ci.yml => build.yml} (85%) delete mode 100644 .github/workflows/docs.yml delete mode 100644 .github/workflows/pypi.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/build.yml similarity index 85% rename from .github/workflows/ci.yml rename to .github/workflows/build.yml index 70475cafd7..f9704d0188 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/build.yml @@ -1,4 +1,4 @@ -name: Test Firedrake +name: Build Firedrake on: workflow_call: @@ -7,17 +7,21 @@ on: description: The target branch (usually 'master' or 'release') type: string required: true + run_tests: + description: Whether to run the test suite + type: boolean + default: true deploy_website: description: Whether to deploy the website - type: bool + type: boolean default: false upload_pypi: description: Whether to upload an sdist to PyPI - type: bool + type: boolean default: false upload_testpypi: description: Whether to upload an sdist to TestPyPI - type: bool + type: boolean default: false concurrency: @@ -28,7 +32,7 @@ concurrency: cancel-in-progress: true jobs: - test: + build: name: Install and test Firedrake (Linux) strategy: # We want to know all of the tests which fail, so don't kill real if @@ -49,8 +53,6 @@ jobs: PYOP2_SPMD_STRICT: 1 EXTRA_PYTEST_ARGS: --splitting-algorithm least_duration --timeout=600 --timeout-method=thread -o faulthandler_timeout=660 PYTEST_MPI_MAX_NPROCS: 8 - outputs: - conclusion: ${{ steps.report_docs.outputs.conclusion }} steps: - name: Fix HOME # For unknown reasons GitHub actions overwrite HOME to /github/home @@ -149,6 +151,8 @@ jobs: firedrake-check timeout-minutes: 5 + # ----- + # TODO: Move towards the bottom, just here for testing purposes - name: Build sdist (release only) if: inputs.target_branch == 'release' @@ -171,13 +175,13 @@ jobs: make -C firedrake-repo/docs validate-bibtex - name: Check documentation links + # This step can fail spuriously so don't burn everything down if that happens + continue-on-error: true run: | . venv/bin/activate make -C firedrake-repo/docs linkcheck - name: Build docs - id: build - if: success() || steps.install.conclusion == 'success' run: | . venv/bin/activate cd firedrake-repo/docs @@ -186,30 +190,22 @@ jobs: make latexpdf - name: Copy manual to HTML tree - id: copy - if: success() || steps.build.conclusion == 'success' run: | cd firedrake-repo/docs cp build/latex/Firedrake.pdf build/html/_static/manual.pdf - name: Upload artifact - id: upload - if: success() || steps.copy.conclusion == 'success' uses: actions/upload-pages-artifact@v3 with: name: github-pages path: /__w/firedrake/firedrake/firedrake-repo/docs/build/html retention-days: 1 - - name: Report docs build - id: report_docs - if: success() || steps.upload.conclusion == 'success' - run: echo "conclusion=success" >> "$GITHUB_OUTPUT" - + # ----- - name: Run TSFC tests # Run even if earlier tests failed - if: success() || steps.install.conclusion == 'success' + if: inputs.run_tests && (success() || steps.install.conclusion == 'success') run: | . venv/bin/activate : # Use pytest-xdist here so we can have a single collated output (not possible @@ -218,7 +214,7 @@ jobs: timeout-minutes: 60 - name: Run PyOP2 tests - if: success() || steps.install.conclusion == 'success' + if: inputs.run_tests && (success() || steps.install.conclusion == 'success') run: | . venv/bin/activate : # Use pytest-xdist here so we can have a single collated output (not possible @@ -231,7 +227,7 @@ jobs: - name: Run Firedrake tests (nprocs = 1) - if: success() || steps.install.conclusion == 'success' + if: inputs.run_tests && (success() || steps.install.conclusion == 'success') run: | . venv/bin/activate : # Use pytest-xdist here so we can have a single collated output (not possible @@ -240,49 +236,49 @@ jobs: timeout-minutes: 60 - name: Run tests (nprocs = 2) - if: success() || steps.install.conclusion == 'success' + if: inputs.run_tests && (success() || steps.install.conclusion == 'success') run: | . venv/bin/activate firedrake-run-split-tests 2 4 "$EXTRA_PYTEST_ARGS" firedrake-repo/tests/firedrake timeout-minutes: 30 - name: Run tests (nprocs = 3) - if: success() || steps.install.conclusion == 'success' + if: inputs.run_tests && (success() || steps.install.conclusion == 'success') run: | . venv/bin/activate firedrake-run-split-tests 3 2 "$EXTRA_PYTEST_ARGS" firedrake-repo/tests/firedrake timeout-minutes: 60 - name: Run tests (nprocs = 4) - if: success() || steps.install.conclusion == 'success' + if: inputs.run_tests && (success() || steps.install.conclusion == 'success') run: | . venv/bin/activate firedrake-run-split-tests 4 2 "$EXTRA_PYTEST_ARGS" firedrake-repo/tests/firedrake timeout-minutes: 15 - name: Run tests (nprocs = 5) - if: success() || steps.install.conclusion == 'success' + if: inputs.run_tests && (success() || steps.install.conclusion == 'success') run: | . venv/bin/activate firedrake-run-split-tests 5 1 "$EXTRA_PYTEST_ARGS" firedrake-repo/tests/firedrake timeout-minutes: 15 - name: Run tests (nprocs = 6) - if: success() || steps.install.conclusion == 'success' + if: inputs.run_tests && (success() || steps.install.conclusion == 'success') run: | . venv/bin/activate firedrake-run-split-tests 6 1 "$EXTRA_PYTEST_ARGS" firedrake-repo/tests/firedrake timeout-minutes: 15 - name: Run tests (nprocs = 7) - if: success() || steps.install.conclusion == 'success' + if: inputs.run_tests && (success() || steps.install.conclusion == 'success') run: | . venv/bin/activate firedrake-run-split-tests 7 1 "$EXTRA_PYTEST_ARGS" firedrake-repo/tests/firedrake timeout-minutes: 15 - name: Run tests (nprocs = 8) - if: success() || steps.install.conclusion == 'success' + if: inputs.run_tests && (success() || steps.install.conclusion == 'success') run: | . venv/bin/activate firedrake-run-split-tests 8 1 "$EXTRA_PYTEST_ARGS" firedrake-repo/tests/firedrake @@ -290,7 +286,10 @@ jobs: - name: Run Gusto smoke tests # Only test Gusto in real mode - if: (success() || steps.install.conclusion == 'success') && matrix.arch == 'default' + if: | + inputs.run_tests && + (success() || steps.install.conclusion == 'success') && + matrix.arch == 'default' run: | . venv/bin/activate git clone --depth 1 https://github.com/firedrakeproject/gusto.git gusto-repo @@ -302,7 +301,10 @@ jobs: timeout-minutes: 10 - name: Run Thetis smoke tests - if: (success() || steps.install.conclusion == 'success') && matrix.arch == 'default' + if: | + inputs.run_tests && + (success() || steps.install.conclusion == 'success') && + matrix.arch == 'default' run: | . venv/bin/activate git clone --depth 1 https://github.com/thetisproject/thetis.git thetis-repo @@ -311,7 +313,10 @@ jobs: timeout-minutes: 10 - name: Run spyro smoke tests - if: (success() || steps.install.conclusion == 'success') && matrix.arch == 'default' + if: | + inputs.run_tests && + (success() || steps.install.conclusion == 'success') && + matrix.arch == 'default' run: | . venv/bin/activate git clone --depth 1 https://github.com/NDF-Poli-USP/spyro.git spyro-repo @@ -320,7 +325,10 @@ jobs: timeout-minutes: 5 - name: Run G-ADOPT smoke tests - if: (success() || steps.install.conclusion == 'success') && matrix.arch == 'default' + if: | + inputs.run_tests && + (success() || steps.install.conclusion == 'success') && + matrix.arch == 'default' run: | . venv/bin/activate git clone --depth 1 https://github.com/g-adopt/g-adopt.git g-adopt-repo @@ -330,7 +338,7 @@ jobs: - name: Upload log files uses: actions/upload-artifact@v4 - if: success() || steps.install.conclusion == 'success' + if: inputs.run_tests && (success() || steps.install.conclusion == 'success') with: name: firedrake-logs-${{ matrix.arch }} path: pytest_*.log @@ -356,12 +364,8 @@ jobs: deploy: name: Deploy GitHub pages - needs: test - # Deploy even if linkcheck fails - if: | - always() && - needs.build_docs.outputs.conclusion == 'success' && - inputs.deploy_website + needs: build + if: inputs.deploy_website permissions: pages: write id-token: write @@ -375,8 +379,8 @@ jobs: upload_pypi: name: Upload to PyPI (optional) - if: inputs.upload_pypi - needs: test + if: inputs.upload_pypi && inputs.target_branch == 'release' + needs: build runs-on: ubuntu-latest environment: name: pypi @@ -392,8 +396,8 @@ jobs: upload_testpypi: name: Upload to TestPyPI (optional) - if: inputs.upload_testpypi - needs: test + if: inputs.upload_testpypi && inputs.target_branch == 'release' + needs: build runs-on: ubuntu-latest environment: name: testpypi diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml deleted file mode 100644 index 3f87bc1a78..0000000000 --- a/.github/workflows/docs.yml +++ /dev/null @@ -1,101 +0,0 @@ -name: Check docs build cleanly - -on: - push: - branches: - - master - - release - pull_request: - -concurrency: - # Cancels jobs running if new commits are pushed - group: > - ${{ github.workflow }}- - ${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: true - -jobs: - build_docs: - name: Run doc build - runs-on: ubuntu-latest - container: - image: firedrakeproject/firedrake-docdeps:latest - outputs: - conclusion: ${{ steps.report.outputs.conclusion }} - steps: - - uses: actions/checkout@v4 - with: - path: firedrake-repo - - # this wont work any more because we wont have a docdeps container - # for firedrake. I think we should probably just do the docs build in - # the main workflow - can wait until this afternoon for that to merge - - name: Install Firedrake - id: install - run: | - exit 1 - : # Pass '--system-site-packages' so already installed packages can be found - python3 -m venv --system-site-packages venv - . venv/bin/activate - pip uninstall -y firedrake - pip install --verbose './firedrake-repo[docs]' - - - name: Check bibtex - run: | - . venv/bin/activate - make -C firedrake-repo/docs validate-bibtex - - - name: Check documentation links - run: | - . venv/bin/activate - make -C firedrake-repo/docs linkcheck - - - name: Build docs - id: build - if: success() || steps.install.conclusion == 'success' - run: | - . venv/bin/activate - cd firedrake-repo/docs - make html - make latex - make latexpdf - - - name: Copy manual to HTML tree - id: copy - if: success() || steps.build.conclusion == 'success' - run: | - cd firedrake-repo/docs - cp build/latex/Firedrake.pdf build/html/_static/manual.pdf - - - name: Upload artifact - id: upload - if: success() || steps.copy.conclusion == 'success' - uses: actions/upload-pages-artifact@v3 - with: - name: github-pages - path: /__w/firedrake/firedrake/firedrake-repo/docs/build/html - retention-days: 1 - - - name: Report status - id: report - if: success() || steps.upload.conclusion == 'success' - run: echo "conclusion=success" >> "$GITHUB_OUTPUT" - - deploy: - name: Deploy Github pages - needs: build_docs - # Always run this workflow on master, even if linkcheck fails - if: | - always() && - needs.build_docs.outputs.conclusion == 'success' && - (github.ref_name == 'master' || github.ref_name == 'release') - permissions: - pages: write - id-token: write - environment: - name: github-pages - url: http://firedrakeproject.github.io/firedrake/${{ github.ref_name }} - runs-on: ubuntu-latest - steps: - - name: Deploy to GitHub Pages - uses: actions/deploy-pages@v4 diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index c7b80422ea..8a4f1af799 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -5,7 +5,7 @@ on: jobs: test: - uses: ./.github/workflows/ci.yml + uses: ./.github/workflows/build.yml with: target_branch: ${{ github.base_ref }} secrets: inherit diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index 5669d4898f..1980289928 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -8,7 +8,7 @@ on: jobs: test: - uses: ./.github/workflows/test.yml + uses: ./.github/workflows/build.yml with: target_branch: ${{ github.ref_name }} deploy_website: true diff --git a/.github/workflows/pypi.yml b/.github/workflows/pypi.yml deleted file mode 100644 index db2692ec07..0000000000 --- a/.github/workflows/pypi.yml +++ /dev/null @@ -1,81 +0,0 @@ -name: Publish on PyPI - -# By default this action does not push to test or production PyPI. The wheels -# are available as an artifact that can be downloaded and tested locally. - -on: - workflow_dispatch: - inputs: - ref: - description: Git ref to publish - default: master - type: string - pypi: - description: Publish to PyPI - default: false - type: boolean - test_pypi: - description: Publish to TestPyPI - default: false - type: boolean - - workflow_call: - inputs: - ref: - description: Git ref to publish - default: master - type: string - pypi: - description: Publish to PyPI - default: false - type: boolean - test_pypi: - description: Publish to TestPyPI - default: false - type: boolean - - # UNDO ME - pull_request: - -jobs: - upload_pypi: - name: Upload to PyPI (optional) - # UNDO ME - # if: github.event.inputs.pypi == 'true' - if: false - needs: build - runs-on: ubuntu-latest - environment: - name: pypi - permissions: - id-token: write - steps: - - uses: actions/download-artifact@v4 - with: - name: dist - path: dist - - - name: Push to PyPI - uses: pypa/gh-action-pypi-publish@release/v1 - - upload_test_pypi: - name: Upload to TestPyPI (optional) - # UNDO ME - # if: github.event.inputs.test_pypi == 'true' - if: false - needs: build - runs-on: ubuntu-latest - environment: - name: testpypi - permissions: - id-token: write - steps: - - uses: actions/download-artifact@v4 - with: - name: dist - path: dist - - - name: Push to TestPyPI - uses: pypa/gh-action-pypi-publish@release/v1 - with: - repository-url: https://test.pypi.org/legacy/ diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index acf7e45e4b..9339e55ab0 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -5,12 +5,11 @@ on: types: [published] jobs: - pypi_publish: - name: Publish release on PyPI - uses: ./.github/workflows/pypi.yml + deploy: + uses: ./.github/workflows/build.yml with: - ref: ${{ github.ref }} - pypi: true + target_branch: release + run_tests: false secrets: inherit docker: diff --git a/Makefile b/Makefile index 2d5007d4d4..ef491f3efe 100644 --- a/Makefile +++ b/Makefile @@ -39,7 +39,7 @@ actionlint: @docker pull rhysd/actionlint:latest @# Exclude SC2046 so it doesn't complain about unquoted $ characters (the @# quoting can prevent proper parsing) - @docker run -e SHELLCHECK_OPTS='--exclude=SC2046' --rm -v $$(pwd):/repo --workdir /repo rhysd/actionlint -color + @docker run -e SHELLCHECK_OPTS='--exclude=SC2046,SC2078' --rm -v $$(pwd):/repo --workdir /repo rhysd/actionlint -color .PHONY: dockerlint dockerlint: From f4603a8def70cee0ec4e51406b71b55dbcdccd10 Mon Sep 17 00:00:00 2001 From: Connor Ward Date: Fri, 11 Apr 2025 16:27:03 +0100 Subject: [PATCH 25/39] fixup --- .github/workflows/build.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f9704d0188..856e642248 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -79,7 +79,8 @@ jobs: - name: Check no 'TODO RELEASE' comments (release only) if: inputs.target_branch == 'release' run: | - if [ -z "$( git -C ./firedrake-repo grep 'TODO RELEASE' )" ]; then + cd firedrake-repo + if [ -z "$( git grep 'TODO RELEASE' )" ]; then exit 0 else exit 1 @@ -89,7 +90,7 @@ jobs: # I prefer the latter because then failures are everyone's problem, not just mine! - name: Install PETSc run: | - if [ "${{ inputs.target_branch == 'release' }}" ]; then + if [ ${{ inputs.target_branch }} = 'release' ]; then git clone --depth 1 \ --branch $(python3 ./firedrake-repo/scripts/firedrake-configure --show-petsc-version) \ https://gitlab.com/petsc/petsc.git @@ -120,7 +121,7 @@ jobs: pip cache remove petsc4py pip cache remove slepc4py - if [ "${{ inputs.target_branch == 'release' }}" ]; then + if [ ${{ inputs.target_branch }} = 'release' ]; then EXTRA_PIP_FLAGS='' else : # Install build dependencies From 0a32637ec863aa7e7484a387780b36c6cc9ca96d Mon Sep 17 00:00:00 2001 From: Connor Ward Date: Mon, 14 Apr 2025 10:50:11 +0100 Subject: [PATCH 26/39] Tweaks * Remove docdeps container (not needed any more) * Link check failing should make workflow fail * CI installs docdeps * sdist builds in venv --- .github/workflows/build.yml | 16 ++++++++++++++-- docker/Dockerfile.docdeps | 7 ------- .../adjoint/test_split_and_subfunctions.py | 2 +- 3 files changed, 15 insertions(+), 10 deletions(-) delete mode 100644 docker/Dockerfile.docdeps diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 856e642248..621a7d7b0b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -71,9 +71,13 @@ jobs: - name: Install system dependencies run: | apt-get update - apt-get -y install python3 + apt-get -y install python3 python3-venv apt-get -y install \ - $(python3 ./firedrake-repo/scripts/firedrake-configure --arch ${{ matrix.arch }} --show-system-packages) python3-venv parallel + $(python3 ./firedrake-repo/scripts/firedrake-configure --arch ${{ matrix.arch }} --show-system-packages) + : # Dependencies needed to run the test suite + apt-get -y install parallel + : # Dependencies needed to build the documentation + apt-get -y install inkscape texlive-full # Raise an error if any 'TODO RELEASE' comments remain - name: Check no 'TODO RELEASE' comments (release only) @@ -158,6 +162,7 @@ jobs: - name: Build sdist (release only) if: inputs.target_branch == 'release' run: | + . venv/bin/activate pip install build python -m build ./firedrake-repo --sdist @@ -178,6 +183,7 @@ jobs: - name: Check documentation links # This step can fail spuriously so don't burn everything down if that happens continue-on-error: true + id: linkcheck run: | . venv/bin/activate make -C firedrake-repo/docs linkcheck @@ -344,6 +350,12 @@ jobs: name: firedrake-logs-${{ matrix.arch }} path: pytest_*.log + # We want to fail the workflow if the link check step fails but we still want + # the rest of the run to complete + - name: Assert linkcheck passes + if: steps.linkcheck.conclusion != 'success' + run: exit 1 + - name: Post-run cleanup if: always() run: find . -delete diff --git a/docker/Dockerfile.docdeps b/docker/Dockerfile.docdeps deleted file mode 100644 index 832b422b5f..0000000000 --- a/docker/Dockerfile.docdeps +++ /dev/null @@ -1,7 +0,0 @@ -# Dockerfile for Firedrake plus packages needed to build the documentation - -FROM firedrakeproject/firedrake-vanilla-default:latest - -RUN apt-get update \ - && apt-get -y install inkscape texlive-full \ - && rm -rf /var/lib/apt/lists/* diff --git a/tests/firedrake/adjoint/test_split_and_subfunctions.py b/tests/firedrake/adjoint/test_split_and_subfunctions.py index 5fc0fc3982..0e2d62a93e 100644 --- a/tests/firedrake/adjoint/test_split_and_subfunctions.py +++ b/tests/firedrake/adjoint/test_split_and_subfunctions.py @@ -209,7 +209,7 @@ def test_writing_to_subfunctions(): u.assign(kappa) usub *= 2 J = assemble(inner(u, u) * dx) - print(f"{type(J) = }") + print(f"{type(J) = }") # noqa: E202, E251 rf = ReducedFunctional(J, Control(kappa), tape=tape) pause_annotation() From eeb96eaed4951442bfcc4d11a6455069d86ab096 Mon Sep 17 00:00:00 2001 From: Connor Ward Date: Mon, 14 Apr 2025 10:51:29 +0100 Subject: [PATCH 27/39] fixup version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 09f44c648d..3e903c3b24 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [project] name = "firedrake" # .. -version = "2025.04.0" +version = "2025.4.0" description = "An automated system for the portable solution of partial differential equations using the finite element method" readme = "README.rst" license = {file = "LICENSE"} From d2b56bcdd90e13384b7fb552897920d1da6accf4 Mon Sep 17 00:00:00 2001 From: Connor Ward Date: Mon, 14 Apr 2025 11:20:51 +0100 Subject: [PATCH 28/39] Tweaks * Ingest `test_macos` workflow * Rename to `core.yml` * Other little fixups --- .github/workflows/{build.yml => core.yml} | 78 ++++++++++++++++++++-- .github/workflows/pr.yml | 4 +- .github/workflows/push.yml | 3 +- .github/workflows/release.yml | 2 +- .github/workflows/test_macos.yml | 81 ----------------------- docs/source/documentation.rst | 7 +- 6 files changed, 81 insertions(+), 94 deletions(-) rename .github/workflows/{build.yml => core.yml} (85%) delete mode 100644 .github/workflows/test_macos.yml diff --git a/.github/workflows/build.yml b/.github/workflows/core.yml similarity index 85% rename from .github/workflows/build.yml rename to .github/workflows/core.yml index 621a7d7b0b..794b9fbd9c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/core.yml @@ -1,4 +1,4 @@ -name: Build Firedrake +# Main Firedrake CI workflow on: workflow_call: @@ -11,6 +11,10 @@ on: description: Whether to run the test suite type: boolean default: true + test_macos: + description: Whether to test using macOS + type: boolean + default: false deploy_website: description: Whether to deploy the website type: boolean @@ -32,8 +36,8 @@ concurrency: cancel-in-progress: true jobs: - build: - name: Install and test Firedrake (Linux) + test_linux: + name: Build and test Firedrake (Linux) strategy: # We want to know all of the tests which fail, so don't kill real if # complex fails and vice-versa @@ -360,6 +364,68 @@ jobs: if: always() run: find . -delete + test_macos: + name: Build and test Firedrake (macOS) + runs-on: [self-hosted, macOS] + if: inputs.test_macos + env: + FIREDRAKE_CI: 1 + OMP_NUM_THREADS: 1 + OPENBLAS_NUM_THREADS: 1 + steps: + - name: Add homebrew to PATH + # https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/workflow-commands-for-github-actions#adding-a-system-path + run: echo "/opt/homebrew/bin" >> "$GITHUB_PATH" + + - name: Pre-run cleanup + # Make sure the current directory is empty + run: find . -delete + + - uses: actions/checkout@v4 + with: + path: firedrake-repo + + - name: Install system dependencies + run: | + brew install $(python3 ./firedrake-repo/scripts/firedrake-configure --arch default --show-system-packages) + + - name: Install PETSc + run: | + git clone --depth 1 --branch $(python3 ./firedrake-repo/scripts/firedrake-configure --show-petsc-version) https://gitlab.com/petsc/petsc.git + cd petsc + python3 ../firedrake-repo/scripts/firedrake-configure \ + --arch default --show-petsc-configure-options | \ + xargs -L1 ./configure --with-make-np=4 + make + make check + + - name: Install Firedrake + id: install + run: | + export $(python3 ./firedrake-repo/scripts/firedrake-configure --arch default --show-env) + python3 -m venv venv + . venv/bin/activate + : # Force a rebuild of petsc4py as the cached one will not link to the fresh + : # install of PETSc. A similar trick may be needed for compiled dependencies + : # like h5py or mpi4py if changing HDF5/MPI libraries. + pip cache remove petsc4py + pip install --verbose --no-binary h5py './firedrake-repo[test]' + firedrake-clean + : # Extra test dependencies + pip install pytest-timeout + pip list + + - name: Run smoke tests + run: | + . venv/bin/activate + firedrake-check + timeout-minutes: 10 + + - name: Post-run cleanup + if: always() + run: | + find . -delete + lint: name: Lint codebase runs-on: ubuntu-latest @@ -377,7 +443,7 @@ jobs: deploy: name: Deploy GitHub pages - needs: build + needs: test_linux if: inputs.deploy_website permissions: pages: write @@ -393,7 +459,7 @@ jobs: upload_pypi: name: Upload to PyPI (optional) if: inputs.upload_pypi && inputs.target_branch == 'release' - needs: build + needs: test_linux runs-on: ubuntu-latest environment: name: pypi @@ -410,7 +476,7 @@ jobs: upload_testpypi: name: Upload to TestPyPI (optional) if: inputs.upload_testpypi && inputs.target_branch == 'release' - needs: build + needs: test_linux runs-on: ubuntu-latest environment: name: testpypi diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 8a4f1af799..eea463bacc 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -5,7 +5,9 @@ on: jobs: test: - uses: ./.github/workflows/build.yml + uses: ./.github/workflows/core.yml with: target_branch: ${{ github.base_ref }} + # Only run macOS tests if the PR is labelled 'macOS' + test_macos: ${{ contains(github.event.pull_request.labels.*.name, 'macOS') }} secrets: inherit diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index 1980289928..06b0e316fc 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -8,8 +8,9 @@ on: jobs: test: - uses: ./.github/workflows/build.yml + uses: ./.github/workflows/core.yml with: target_branch: ${{ github.ref_name }} + test_macos: true deploy_website: true secrets: inherit diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9339e55ab0..941c948dbe 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -6,7 +6,7 @@ on: jobs: deploy: - uses: ./.github/workflows/build.yml + uses: ./.github/workflows/core.yml with: target_branch: release run_tests: false diff --git a/.github/workflows/test_macos.yml b/.github/workflows/test_macos.yml deleted file mode 100644 index dd987e4a80..0000000000 --- a/.github/workflows/test_macos.yml +++ /dev/null @@ -1,81 +0,0 @@ -name: Install and test Firedrake (macOS) - -on: - push: - branches: - - master - pull_request: - # By default this workflow is run on the "opened", "synchronize" and - # "reopened" events. We add "labelled" so it will run if the PR is given a label. - types: [opened, synchronize, reopened, labeled] - -concurrency: - # Cancels jobs running if new commits are pushed - group: > - ${{ github.workflow }}- - ${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: true - -jobs: - build: - name: Build Firedrake (macOS) - runs-on: [self-hosted, macOS] - # Only run this action if we are pushing to master or the PR is labelled "macOS" - if: ${{ (github.ref == 'refs/heads/master') || contains(github.event.pull_request.labels.*.name, 'macOS') }} - env: - FIREDRAKE_CI: 1 - OMP_NUM_THREADS: 1 - OPENBLAS_NUM_THREADS: 1 - steps: - - name: Add homebrew to PATH - # https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/workflow-commands-for-github-actions#adding-a-system-path - run: echo "/opt/homebrew/bin" >> "$GITHUB_PATH" - - - name: Pre-run cleanup - # Make sure the current directory is empty - run: find . -delete - - - uses: actions/checkout@v4 - with: - path: firedrake-repo - - - name: Install system dependencies - run: | - brew install $(python3 ./firedrake-repo/scripts/firedrake-configure --arch default --show-system-packages) - - - name: Install PETSc - run: | - git clone --depth 1 --branch $(python3 ./firedrake-repo/scripts/firedrake-configure --show-petsc-version) https://gitlab.com/petsc/petsc.git - cd petsc - python3 ../firedrake-repo/scripts/firedrake-configure \ - --arch default --show-petsc-configure-options | \ - xargs -L1 ./configure --with-make-np=4 - make - make check - - - name: Install Firedrake - id: install - run: | - export $(python3 ./firedrake-repo/scripts/firedrake-configure --arch default --show-env) - python3 -m venv venv - . venv/bin/activate - : # Force a rebuild of petsc4py as the cached one will not link to the fresh - : # install of PETSc. A similar trick may be needed for compiled dependencies - : # like h5py or mpi4py if changing HDF5/MPI libraries. - pip cache remove petsc4py - pip install --verbose --no-binary h5py './firedrake-repo[test]' - firedrake-clean - : # Extra test dependencies - pip install pytest-timeout - pip list - - - name: Run smoke tests - run: | - . venv/bin/activate - firedrake-check - timeout-minutes: 10 - - - name: Post-run cleanup - if: always() - run: | - find . -delete diff --git a/docs/source/documentation.rst b/docs/source/documentation.rst index 2da93622b7..e8f207aee7 100644 --- a/docs/source/documentation.rst +++ b/docs/source/documentation.rst @@ -6,11 +6,11 @@ .. sidebar:: Current development information. - Firedrake and PyOP2 are continually tested using `GitHub actions `__. + Firedrake is continually tested using `GitHub actions `__. Latest Firedrake status: |firedrakebuild|_ - .. |firedrakebuild| image:: https://github.com/firedrakeproject/firedrake/actions/workflows/build.yml/badge.svg - .. _firedrakebuild: https://github.com/firedrakeproject/firedrake/actions/workflows/build.yml + .. |firedrakebuild| image:: https://github.com/firedrakeproject/firedrake/actions/workflows/pr.yml/badge.svg + .. _firedrakebuild: https://github.com/firedrakeproject/firedrake/actions/workflows/pr.yml Firedrake and its components are developed on `GitHub `__ where we also maintain Firedrake-ready @@ -18,7 +18,6 @@ UFL. * `Firedrake on GitHub `__ - * `TSFC on GitHub `__ * `FIAT on GitHub `__ * `Firedrake version of UFL on GitHub `__ From d8132b4954f4774c369e0f9ed4c02647c4692e4f Mon Sep 17 00:00:00 2001 From: Connor Ward Date: Mon, 14 Apr 2025 13:09:08 +0100 Subject: [PATCH 29/39] Workflow cleanup --- .github/workflows/core.yml | 110 +++++++++++++++++-------------------- 1 file changed, 51 insertions(+), 59 deletions(-) diff --git a/.github/workflows/core.yml b/.github/workflows/core.yml index 794b9fbd9c..0f736deb63 100644 --- a/.github/workflows/core.yml +++ b/.github/workflows/core.yml @@ -160,60 +160,6 @@ jobs: firedrake-check timeout-minutes: 5 - # ----- - - # TODO: Move towards the bottom, just here for testing purposes - - name: Build sdist (release only) - if: inputs.target_branch == 'release' - run: | - . venv/bin/activate - pip install build - python -m build ./firedrake-repo --sdist - - - name: Upload sdist (release only) - if: inputs.target_branch == 'release' - uses: actions/upload-artifact@v4 - with: - name: dist - path: dist/* - - # TODO: Move further down - # Could be a separate stage? - - name: Check bibtex - run: | - . venv/bin/activate - make -C firedrake-repo/docs validate-bibtex - - - name: Check documentation links - # This step can fail spuriously so don't burn everything down if that happens - continue-on-error: true - id: linkcheck - run: | - . venv/bin/activate - make -C firedrake-repo/docs linkcheck - - - name: Build docs - run: | - . venv/bin/activate - cd firedrake-repo/docs - make html - make latex - make latexpdf - - - name: Copy manual to HTML tree - run: | - cd firedrake-repo/docs - cp build/latex/Firedrake.pdf build/html/_static/manual.pdf - - - name: Upload artifact - uses: actions/upload-pages-artifact@v3 - with: - name: github-pages - path: /__w/firedrake/firedrake/firedrake-repo/docs/build/html - retention-days: 1 - - # ----- - - name: Run TSFC tests # Run even if earlier tests failed if: inputs.run_tests && (success() || steps.install.conclusion == 'success') @@ -354,11 +300,57 @@ jobs: name: firedrake-logs-${{ matrix.arch }} path: pytest_*.log - # We want to fail the workflow if the link check step fails but we still want - # the rest of the run to complete - - name: Assert linkcheck passes - if: steps.linkcheck.conclusion != 'success' - run: exit 1 + - name: Build sdist (release only) + if: | + inputs.target_branch == 'release' && + matrix.arch == 'default' && + (success() || steps.install.conclusion == 'success') + run: | + . venv/bin/activate + pip install build + python -m build ./firedrake-repo --sdist + + - name: Upload sdist (release only) + if: | + inputs.target_branch == 'release' && + matrix.arch == 'default' && + (success() || steps.install.conclusion == 'success') + uses: actions/upload-artifact@v4 + with: + name: dist + path: firedrake-repo/dist/* + + - name: Check bibtex + if: always() + run: | + . venv/bin/activate + make -C firedrake-repo/docs validate-bibtex + + - name: Check documentation links + if: always() + run: | + . venv/bin/activate + make -C firedrake-repo/docs linkcheck + + - name: Build documentation + if: success() || steps.install.conclusion == 'success' + id: build_docs + run: | + . venv/bin/activate + cd firedrake-repo/docs + make html + make latex + make latexpdf + # : Copy manual to HTML tree + cp build/latex/Firedrake.pdf build/html/_static/manual.pdf + + - name: Upload documentation + uses: actions/upload-pages-artifact@v3 + if: success() || steps.build_docs.conclusion == 'success' + with: + name: github-pages + path: firedrake-repo/docs/build/html + retention-days: 1 - name: Post-run cleanup if: always() From b6985c17d1f8b82edf66216e5c53b7a336e9c277 Mon Sep 17 00:00:00 2001 From: Connor Ward Date: Tue, 15 Apr 2025 11:20:24 +0100 Subject: [PATCH 30/39] Updates * Add banner notifications for the different website versions. * Update dev install information * Remove refs to firedrake-update --- .github/workflows/core.yml | 2 +- docs/source/conf.py | 7 +++ docs/source/install.rst | 98 +++++++++++++++++++++++----------- firedrake/eigensolver.py | 2 +- firedrake/mg/mesh.py | 6 +-- firedrake/mg/opencascade_mh.py | 2 +- pyproject.toml | 7 --- 7 files changed, 81 insertions(+), 43 deletions(-) diff --git a/.github/workflows/core.yml b/.github/workflows/core.yml index 9f6f7b65f0..b5536dc4f5 100644 --- a/.github/workflows/core.yml +++ b/.github/workflows/core.yml @@ -338,7 +338,7 @@ jobs: run: | . venv/bin/activate cd firedrake-repo/docs - make html + make SPHINXOPTS="-t ${{ inputs.target_branch }}" html make latex make latexpdf # : Copy manual to HTML tree diff --git a/docs/source/conf.py b/docs/source/conf.py index 29f5e979a4..695b014a64 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -244,6 +244,13 @@ # Output file base name for HTML help builder. htmlhelp_basename = 'Firedrakedoc' +# Optional header warning about docs version +rst_prolog = """ +.. only:: main + + .. warning:: + You are reading a version of the website built against the unstable ``main`` branch. This content is liable to change without notice and may be inappropriate for your use case. +""" # -- Options for LaTeX output -------------------------------------------- diff --git a/docs/source/install.rst b/docs/source/install.rst index 75bbc33f29..289c69d98b 100644 --- a/docs/source/install.rst +++ b/docs/source/install.rst @@ -286,36 +286,6 @@ To resolve the problem you should first remove any existing cached packages:: before re-running the instruction to install Firedrake. -.. _dev_install: - -Developer install ------------------ - -By default Firedrake is installed just like any other Python package into -your environment. If you want to be able to edit Firedrake itself then -an *editable* installation is needed. To install Firedrake in editable -mode you should follow the same -:ref:`steps as for a non-editable install` but replace the -final ``pip install`` command with:: - - $ git clone - $ pip install --no-binary h5py --editable './firedrake[dev]' - -where ```` is ``https://github.com/firedrakeproject/firedrake.git`` -or ``git@github.com:firedrakeproject/firedrake.git`` as preferred. - -The same process applies for Firedrake's dependencies. For example, to install -`FIAT `_ in editable mode you -should run:: - - $ git clone - $ pip install --editable ./fiat - -Note that editable versions of Firedrake's dependencies should be installed *after* -Firedrake is installed. Otherwise installing Firedrake will overwrite -whatever packages you just installed. - - .. _customising: Customising Firedrake @@ -501,6 +471,74 @@ Firedrake can also be used inside the brower using Jupyter notebooks and `Google Colab `_. For more information please see :doc:`here`. +.. _dev_install: + +Developer install +================= + +.. only:: release + + .. warning:: + You are currently looking at the documentation for the current stable + release of Firedrake. For the most recent developer documentation you + should follow the instructions `here `__. + +In order to install a development version of Firedrake the following steps +should be followed: + +#. Install system dependencies :ref:`as before` + +#. Clone and build the *default branch* of PETSc: + + .. code-block:: text + + $ git clone https://gitlab.com/petsc/petsc.git + $ cd petsc + $ python3 ../firedrake-configure --show-petsc-configure-options | xargs -L1 ./configure + $ make PETSC_DIR=/path/to/petsc PETSC_ARCH=arch-firedrake-default all + $ make check + $ cd .. + +#. Clone Firedrake:: + + $ git clone + + where ```` is ``https://github.com/firedrakeproject/firedrake.git`` + or ``git@github.com:firedrakeproject/firedrake.git`` as preferred. + +#. Set the necessary environment variables:: + + $ export $(python3 firedrake-configure --show-env) + +#. Install petsc4py and Firedrake's other build dependencies: + + .. code-block:: text + + $ pip cache remove petsc4py + $ pip install $PETSC_DIR/$PETSC_ARCH/src/binding/petsc4py + $ pip install -r ./firedrake/requirements-dev.txt + +#. Install Firedrake in editable mode without build isolation:: + + $ pip install --no-build-isolation --no-binary h5py --editable './firedrake[check]' + +Editing subpackages +------------------- + +Firedrake dependencies can be cloned and installed in editable mode in an +identical way to Firedrake. For example, to install +`FIAT `_ in editable mode you +should run:: + + $ git clone + $ pip install --editable ./fiat + +For most packages it should not be necessary to pass ``--no-build-isolation``. + +It is important to note that these packages **must be installed after Firedrake**. +This is because otherwise installing Firedrake will overwrite the just-installed +package. + .. _discussion: https://github.com/firedrakeproject/firedrake/discussions .. _issue: https://github.com/firedrakeproject/firedrake/issues .. _homebrew: https://brew.sh/ diff --git a/firedrake/eigensolver.py b/firedrake/eigensolver.py index 4f05d00dbe..9294393a6e 100644 --- a/firedrake/eigensolver.py +++ b/firedrake/eigensolver.py @@ -57,7 +57,7 @@ def __init__(self, A, M=None, bcs=None, bc_shift=0.0, restrict=True): if not SLEPc: raise ImportError( "Unable to import SLEPc, eigenvalue computation not possible " - "(try firedrake-update --slepc)" + "(see https://www.firedrakeproject.org/install.html#slepc)" ) args = A.arguments() diff --git a/firedrake/mg/mesh.py b/firedrake/mg/mesh.py index 42ea985666..446e042b35 100644 --- a/firedrake/mg/mesh.py +++ b/firedrake/mg/mesh.py @@ -122,9 +122,9 @@ def MeshHierarchy(mesh, refinement_levels, try: from ngsPETSc import NetgenHierarchy except ImportError: - raise ImportError("Unable to import netgen and ngsPETSc. Please ensure that netgen and ngsPETSc\ - are installed and available to Firedrake. You can do this via \ - firedrake-update --netgen.") + raise ImportError("Unable to import netgen and ngsPETSc. Please ensure that netgen and ngsPETSc " + "are installed and available to Firedrake (see " + "https://www.firedrakeproject.org/install.html#netgen).") if hasattr(mesh, "netgen_mesh"): return NetgenHierarchy(mesh, refinement_levels, flags=netgen_flags) else: diff --git a/firedrake/mg/opencascade_mh.py b/firedrake/mg/opencascade_mh.py index e320877219..d1e5c6c843 100644 --- a/firedrake/mg/opencascade_mh.py +++ b/firedrake/mg/opencascade_mh.py @@ -20,7 +20,7 @@ def OpenCascadeMeshHierarchy(stepfile, element_size, levels, comm=COMM_WORLD, di from OCC.Core.STEPControl import STEPControl_Reader from OCC.Extend.TopologyUtils import TopologyExplorer except ImportError: - raise ImportError("To use OpenCascadeMeshHierarchy, you must install firedrake with the OpenCascade python bindings (firedrake-update --opencascade).") + raise ImportError("To use OpenCascadeMeshHierarchy, you must install firedrake with the OpenCascade python bindings (https://github.com/tpaviot/pythonocc-core).") if not os.path.isfile(stepfile): raise OSError("%s does not exist" % stepfile) diff --git a/pyproject.toml b/pyproject.toml index ed0dce8750..bcac258906 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -72,13 +72,6 @@ check = [ "mpi-pytest", "pytest", ] -dev = [ # build dependencies that are needed to run 'make' - "Cython", - "mpi-pytest", - "pybind11", - "pytest", - "setuptools", -] docs = [ "bibtexparser", "matplotlib", # needed to resolve API From 9bae98e0adac332913f15cf8d2b309c5e309296a Mon Sep 17 00:00:00 2001 From: Connor Ward Date: Tue, 15 Apr 2025 15:29:32 +0100 Subject: [PATCH 31/39] Zenodo noodling --- .github/workflows/core.yml | 27 ++++++++++++++++++++ .github/workflows/release.yml | 1 + firedrake/scripts/firedrake-zenodo | 40 ++++++++++++++++-------------- 3 files changed, 49 insertions(+), 19 deletions(-) diff --git a/.github/workflows/core.yml b/.github/workflows/core.yml index b5536dc4f5..10c4666ff0 100644 --- a/.github/workflows/core.yml +++ b/.github/workflows/core.yml @@ -28,6 +28,33 @@ on: type: boolean default: false + workflow_dispatch: + inputs: + target_branch: + description: The target branch (usually 'master' or 'release') + type: string + required: true + run_tests: + description: Whether to run the test suite + type: boolean + default: true + test_macos: + description: Whether to test using macOS + type: boolean + default: false + deploy_website: + description: Whether to deploy the website + type: boolean + default: false + upload_pypi: + description: Whether to upload an sdist to PyPI + type: boolean + default: false + upload_testpypi: + description: Whether to upload an sdist to TestPyPI + type: boolean + default: false + concurrency: # Cancel running jobs if new commits are pushed group: > diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 941c948dbe..534b4d9a88 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -10,6 +10,7 @@ jobs: with: target_branch: release run_tests: false + upload_pypi: true secrets: inherit docker: diff --git a/firedrake/scripts/firedrake-zenodo b/firedrake/scripts/firedrake-zenodo index 82475e332b..b260641ac8 100755 --- a/firedrake/scripts/firedrake-zenodo +++ b/firedrake/scripts/firedrake-zenodo @@ -14,6 +14,7 @@ import time import requests import base64 import datetime +from importlib.metadata import Distribution # Change this to https://sandbox.zenodo.org/api for testing @@ -24,22 +25,17 @@ descriptions = OrderedDict([ ("fiat", "The Finite Element Automated Tabulator"), ("petsc", "Portable, Extensible Toolkit for Scientific Computation"), ("loopy", "Transformation-Based Generation of High-Performance CPU/GPU Code"), - ("slepc", "Scalable Library for Eigenvalue Problem Computations"), ]) -projects = dict([ - ("firedrake", "firedrakeproject"), - ("ufl", "firedrakeproject"), - ("fiat", "firedrakeproject"), - ("petsc", "firedrakeproject"), - ("loopy", "firedrakeproject"), - ("slepc", "firedrakeproject"), -]) +pypi_package_names = { + "firedrake": "firedrake", + "ufl": "fenics-ufl", + "fiat": "fenics-fiat", + "loopy": "loopy", +} components = list(descriptions.keys()) -optional_components = ("slepc",) - parser = ArgumentParser(description="""Create Zenodo DOIs for specific versions of Firedrake components. If you are a Firedrake user, this script creates a JSON file encoding @@ -153,6 +149,17 @@ log = logging.getLogger() cwd = os.getcwd() +def package_is_editable(package_name): + # See https://stackoverflow.com/questions/43348746/how-to-detect-if-module-is-installed-in-editable-mode#75078002 + direct_url = Distribution.from_name(package_name).read_text("direct_url.json") + breakpoint() + return json.loads(direct_url).get("dir_info", {}).get("editable", False) + + +# debug +package_is_editable("firedrake") + + def check_call(arguments): if args.log: try: @@ -220,9 +227,6 @@ def collect_repo_shas(): f"'{component}' so it will not be included in the " "release. This may be because the package is not " "installed in 'editable' mode.") - elif component in optional_components: - log.warning(f"Failed to find optional component '{component}', " - "continuing without it") else: log.error(f"Mandatory component '{component}' could not be found.") sys.exit(1) @@ -304,13 +308,11 @@ variable FIREDRAKE_GITHUB_TOKEN to a github personal access token.""") sys.exit(1) result = [] for component in components: - repo = gh.repository(projects[component], component) + repo = gh.repository("firedrakeproject", component) tags = list(repo.tags()) try: found, = [t for t in tags if t.name == tag] except ValueError: - if component in optional_components: - continue if args.skip_missing: log.warning("Tag '{tag}' does not exist in repository '{repo}'. Continuing".format(tag=tag, repo=repo)) else: @@ -835,7 +837,7 @@ tag += "." + str(index + 1) # This step also replaces short shas with long ones. This seems to be necessary for release creation. for component in components: - repo = gh.repository(projects[component], component) + repo = gh.repository("firedrakeproject", component) try: commit = repo.commit(shas[component]) @@ -861,7 +863,7 @@ https://www.firedrakeproject.org/citing.html""" for component in (set(shas) & set(components)): log.info("Releasing %s" % component) - repo = gh.repository(projects[component], component) + repo = gh.repository("firedrakeproject", component) releases = repo.releases() just_tag = False date = datetime.datetime.utcnow().replace(microsecond=0, tzinfo=datetime.timezone.utc).isoformat() From cc5b9d74717b5adcb0b74bd4cb899a620d282267 Mon Sep 17 00:00:00 2001 From: Connor Ward Date: Tue, 15 Apr 2025 17:09:09 +0100 Subject: [PATCH 32/39] Zenodo noodling, ideas solidifying --- firedrake/scripts/firedrake-zenodo | 117 ++++++++++++----------------- 1 file changed, 49 insertions(+), 68 deletions(-) diff --git a/firedrake/scripts/firedrake-zenodo b/firedrake/scripts/firedrake-zenodo index b260641ac8..10bb2ad001 100755 --- a/firedrake/scripts/firedrake-zenodo +++ b/firedrake/scripts/firedrake-zenodo @@ -1,4 +1,8 @@ #! /usr/bin/env python3 +import abc +import contextlib +import dataclasses +import enum import hashlib import re import importlib @@ -27,11 +31,12 @@ descriptions = OrderedDict([ ("loopy", "Transformation-Based Generation of High-Performance CPU/GPU Code"), ]) -pypi_package_names = { +PYPI_PACKAGE_NAMES = { "firedrake": "firedrake", "ufl": "fenics-ufl", "fiat": "fenics-fiat", "loopy": "loopy", + "petsc": "petsc4py", } components = list(descriptions.keys()) @@ -149,15 +154,46 @@ log = logging.getLogger() cwd = os.getcwd() -def package_is_editable(package_name): - # See https://stackoverflow.com/questions/43348746/how-to-detect-if-module-is-installed-in-editable-mode#75078002 - direct_url = Distribution.from_name(package_name).read_text("direct_url.json") - breakpoint() - return json.loads(direct_url).get("dir_info", {}).get("editable", False) +class ComponentVersion(abc.ABC): + pass + + +@dataclasses.dataclass(frozen=True) +class ReleaseComponentVersion(ComponentVersion): + version: str + + +@dataclasses.dataclass(frozen=True) +class VCSComponentVersion(ComponentVersion): + commit_id: str -# debug -package_is_editable("firedrake") +def get_component_version(component: str) -> ComponentVersion: + # debugging + if component == "firedrake": + print("setting firedrake to nonsense") + return VCSComponentVersion("WHERE AM I?") + + pypi_package_name = PYPI_PACKAGE_NAMES[component] + # This incantation returns a JSON string containing information about where the + # component is installed and whether it is editable or not + # (see https://stackoverflow.com/questions/43348746/how-to-detect-if-module-is-installed-in-editable-mode#75078002). + dist = Distribution.from_name(pypi_package_name) + direct_url_json = dist.read_text("direct_url.json") + + if direct_url_json: + direct_url = json.loads(direct_url_json) + is_editable = direct_url.get("dir_info", {}).get("editable", False) + if is_editable: + # sniff the commit info from the repository + repo = direct_url["url"].removeprefix("file://") + commit_id = get_git_commit_info(repo) + else: + commit_id = direct_url["vcs_info"]["commit_id"] + return VCSComponentVersion(commit_id) + else: + # 'direct_url_json' is 'None' if the package is installed via PyPI + return ReleaseComponentVersion(dist.version) def check_call(arguments): @@ -179,66 +215,12 @@ def check_output(args): raise -class directory(object): - """Context manager that executes body in a given directory""" - def __init__(self, d): - self.d = os.path.abspath(d) - - def __enter__(self): - self.olddir = os.path.abspath(os.getcwd()) - os.chdir(self.d) - - def __exit__(self, *args): - os.chdir(self.olddir) - - -def collect_repo_shas(): - shas = {} - for component in components: - log.info(f"Retrieving git information for {component}") - - repo = None - # handle non-Python components separately - if component in {"petsc", "slepc"}: - repo_root_var = "PETSC_DIR"if component == "petsc" else "SLEPC_DIR" - if repo_root_var in os.environ: - repo = os.environ[repo_root_var] - elif "VIRTUAL_ENV" in os.environ: - venv_path = os.path.join(os.getenv("VIRTUAL_ENV"), "src", component) - if os.path.exists(venv_path): - repo = venv_path - else: - # handle the fact that the fiat Python package is called FIAT - if component == "fiat": - python_package_name = "FIAT" - else: - python_package_name = component - try: - package = importlib.import_module(python_package_name) - repo = pathlib.Path(package.__file__).parent.parent - except ImportError: - pass - - if repo: - try: - shas[component] = get_git_commit_info(component, repo) - except RepositoryNotFoundException: - log.warning("Cannot retrieve git information for component " - f"'{component}' so it will not be included in the " - "release. This may be because the package is not " - "installed in 'editable' mode.") - else: - log.error(f"Mandatory component '{component}' could not be found.") - sys.exit(1) - return shas - - class RepositoryNotFoundException(RuntimeError): pass -def get_git_commit_info(component, repo): - with directory(repo): +def get_git_commit_info(repo): + with contextlib.chdir(repo): try: check_call(["git", "status"]) except subprocess.CalledProcessError: @@ -247,7 +229,7 @@ def get_git_commit_info(component, repo): try: check_call(["git", "diff-index", "--quiet", "HEAD"]) except subprocess.CalledProcessError: - log.error(f"Component {component} has uncommitted changes, cannot create release") + log.error(f"Repository {repo} contains uncommitted changes, cannot create release") sys.exit(1) return check_output(["git", "rev-parse", "HEAD"]).strip() @@ -757,8 +739,7 @@ if args.release or not args.input_file: log.error("You must provide a title using the --title option") sys.exit(1) - # Collect hashes from the current repo. - shas = collect_repo_shas() + shas = {c: dataclasses.asdict(get_component_version(c)) for c in components} if args.info_file: shas["metarelease_info_file"] = encode_info_file(args.info_file[0]) else: @@ -785,7 +766,7 @@ if args.additional_dois: for component in components: new_sha = getattr(args, component) if new_sha: - shas[component] = new_sha[0] + shas[component] = {"commit_id": new_sha[0]} if not (args.release or args.input_file): # Dump json and exit. From 8998376e3c6eadf3d04300cde3f65e8c06b87b06 Mon Sep 17 00:00:00 2001 From: Connor Ward Date: Tue, 15 Apr 2025 17:12:49 +0100 Subject: [PATCH 33/39] macos fixup --- .github/workflows/core.yml | 33 +++++++++++++++++++++++++++++---- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/.github/workflows/core.yml b/.github/workflows/core.yml index 10c4666ff0..5f7c897441 100644 --- a/.github/workflows/core.yml +++ b/.github/workflows/core.yml @@ -410,13 +410,24 @@ jobs: - name: Install PETSc run: | - git clone --depth 1 --branch $(python3 ./firedrake-repo/scripts/firedrake-configure --show-petsc-version) https://gitlab.com/petsc/petsc.git + if [ ${{ inputs.target_branch }} = 'release' ]; then + git clone --depth 1 \ + --branch $(python3 ./firedrake-repo/scripts/firedrake-configure --show-petsc-version) \ + https://gitlab.com/petsc/petsc.git + else + git clone --depth 1 https://gitlab.com/petsc/petsc.git + fi cd petsc python3 ../firedrake-repo/scripts/firedrake-configure \ --arch default --show-petsc-configure-options | \ xargs -L1 ./configure --with-make-np=4 make make check + { + echo "PETSC_DIR=/Users/github/actions-runner/_work/firedrake/firedrake/petsc" + echo "PETSC_ARCH=arch-firedrake-default" + echo "SLEPC_DIR=/Users/github/actions-runner/_work/firedrake/firedrake/petsc/arch-firedrake-default" + } >> "$GITHUB_ENV" - name: Install Firedrake id: install @@ -424,14 +435,28 @@ jobs: export $(python3 ./firedrake-repo/scripts/firedrake-configure --arch default --show-env) python3 -m venv venv . venv/bin/activate + : # Force a rebuild of petsc4py as the cached one will not link to the fresh : # install of PETSc. A similar trick may be needed for compiled dependencies : # like h5py or mpi4py if changing HDF5/MPI libraries. pip cache remove petsc4py - pip install --verbose --no-binary h5py './firedrake-repo[test]' + + if [ ${{ inputs.target_branch }} = 'release' ]; then + EXTRA_PIP_FLAGS='' + else + : # Install build dependencies + pip install -r ./firedrake-repo/requirements-dev.txt + pip install "$PETSC_DIR"/src/binding/petsc4py + + : # We have to pass '--no-build-isolation' to use a custom petsc4py + EXTRA_PIP_FLAGS='--no-build-isolation' + fi + + pip install --verbose $EXTRA_PIP_FLAGS \ + --no-binary h5py \ + './firedrake-repo[check]' + firedrake-clean - : # Extra test dependencies - pip install pytest-timeout pip list - name: Run smoke tests From 303f3e52bee8ec40fa46a7dfb0aeeedcc406cf95 Mon Sep 17 00:00:00 2001 From: Connor Ward Date: Tue, 15 Apr 2025 19:29:34 +0100 Subject: [PATCH 34/39] Zenodo unsaved --- firedrake/scripts/firedrake-zenodo | 165 +++++++++++++++++++---------- 1 file changed, 108 insertions(+), 57 deletions(-) diff --git a/firedrake/scripts/firedrake-zenodo b/firedrake/scripts/firedrake-zenodo index 10bb2ad001..018d96493d 100755 --- a/firedrake/scripts/firedrake-zenodo +++ b/firedrake/scripts/firedrake-zenodo @@ -803,16 +803,20 @@ variable FIREDRAKE_GITHUB_TOKEN to a Github personal access token with public_repo scope.""") sys.exit(1) -fd = gh.repository("firedrakeproject", "firedrake") -tag = time.strftime("Firedrake_%Y%m%d", time.localtime()) -index = -1 +def generate_unique_release_tag(): + tag = time.strftime("Firedrake_%Y%m%d", time.localtime()) + index = -1 + firedrake_repo = gh.repository("firedrakeproject", "firedrake") + for release in firedrake_repo.tags(): + if release.name.startswith(tag): + newindex = int(release.name.split(".")[1]) + index = max(index, newindex) + tag += "." + str(index + 1) + return tag -for r in fd.tags(): - if r.name.startswith(tag): - newindex = int(r.name.split(".")[1]) - index = max(index, newindex) -tag += "." + str(index + 1) + +tag = generate_unique_release_tag() # Verify commits. This ensures that an invalid sha will cause us to fail before we release any component. # This step also replaces short shas with long ones. This seems to be necessary for release creation. @@ -834,54 +838,101 @@ for component in components: log.warning(f"Repository {component} already has tag {tag}. This should not have happened.") sys.exit(1) -# Now create releases. -message = """This release is specifically created to document the version of -{component} used in a particular set of experiments using -Firedrake. Please do not cite this as a general source for Firedrake -or any of its dependencies. Instead, refer to -https://www.firedrakeproject.org/citing.html""" +def make_github_releases(): + # NOTE: dont necessarily need to make everything anew -for component in (set(shas) & set(components)): - log.info("Releasing %s" % component) - repo = gh.repository("firedrakeproject", component) - releases = repo.releases() - just_tag = False - date = datetime.datetime.utcnow().replace(microsecond=0, tzinfo=datetime.timezone.utc).isoformat() - tagger = {"name": "firedrake-zenodo", - "email": "firedrake@imperial.ac.uk", - "date": date} - - for release in releases: - if release.target_commitish == shas[component]: - just_tag = True - break - if just_tag: - repo.create_tag(tag, - message=descriptions[component], - sha=shas[component], - obj_type="tree", - tagger=tagger) - else: - repo.create_release( - tag_name=tag, - target_commitish=shas[component], - name=descriptions[component], - body=message.format(component=component), - draft=False, - prerelease=False) - -meta_file = "firedrake-meta-release.json" -with open(meta_file, "w") as f: - data = {"tag": tag, - "title": shas["title"], - "components": sorted(set(shas) & set(components)), - "info_file": shas.get("metarelease_info_file", None), - "new_version_of": shas.get("new_version_of", None), - "additional_dois": shas.get("additional_dois", None)} - f.write(json.dumps(data)) - -log.info("Releases complete. The release tag is %s" % tag) -log.info("Now, you need to create the meta-release") -log.info("Run 'firedrake-zenodo --create-meta-release %s' to do this" % os.path.abspath(meta_file)) -log.info("It is best to wait a short while to ensure that Zenodo is up to date") + for component in components: + if "version" in shas[component]: + # component is versioned, a GitHub release should already exist + release_name = shas[component]["version"] + if not release_already_exists(component, release_name): + log.error("ARGH, do manually") + sys.exit(1) + else: + commit_id = shas[component]["commit_id"] + release_name = f"{component}_{commit_id[:8]}" + target_commitish = commit_id + + message = """This release is specifically created to document the version of + {component} used in a particular set of experiments using + Firedrake. Please do not cite this as a general source for Firedrake + or any of its dependencies. Instead, refer to + https://www.firedrakeproject.org/citing.html""" + + + log.info(f"Creating release {release_name} for {component}") + repo = gh.repository("firedrakeproject", component) + releases = repo.releases() + date = datetime.datetime.utcnow().replace(microsecond=0, tzinfo=datetime.timezone.utc).isoformat() + tagger = {"name": "firedrake-zenodo", + "email": "firedrake@imperial.ac.uk", + "date": date} + + if not release_already_exists(component, release_name): + repo.create_release( + tag_name=release_name, + target_commitish=shas[component], + name=release_name, + body=message.format(component=component), + draft=False, + prerelease=False) + + meta_file = "firedrake-meta-release.json" + with open(meta_file, "w") as f: + data = {"tag": tag, + "title": shas["title"], + "components": sorted(set(shas) & set(components)), + "info_file": shas.get("metarelease_info_file", None), + "new_version_of": shas.get("new_version_of", None), + "additional_dois": shas.get("additional_dois", None)} + f.write(json.dumps(data)) + + log.info("Releases complete. The release tag is %s" % tag) + log.info("Now, you need to create the meta-release") + log.info("Run 'firedrake-zenodo --create-meta-release %s' to do this" % os.path.abspath(meta_file)) + log.info("It is best to wait a short while to ensure that Zenodo is up to date") + + +def release_already_exists(component, release_name): + for release in gh.repository("firedrakeproject", component).releases(): + if release.name == release_name: + return True + return False + + +def maybe_make_github_release(???): + target_commitish = commit_id + + message = """This release is specifically created to document the version of + {component} used in a particular set of experiments using + Firedrake. Please do not cite this as a general source for Firedrake + or any of its dependencies. Instead, refer to + https://www.firedrakeproject.org/citing.html""" + + + log.info(f"Creating release {release_name} for {component}") + repo = gh.repository("firedrakeproject", component) + releases = repo.releases() + date = datetime.datetime.utcnow().replace(microsecond=0, tzinfo=datetime.timezone.utc).isoformat() + tagger = {"name": "firedrake-zenodo", + "email": "firedrake@imperial.ac.uk", + "date": date} + + release_already_exists = False + for release in releases: + if release.name == release_name: + release_already_exists = True + break + + if not release_already_exists: + repo.create_release( + tag_name=release_name, + target_commitish=shas[component], + name=release_name, + body=message.format(component=component), + draft=False, + prerelease=False) + + +make_github_releases() From 387f6b2965e4b0a3409e72a32b2c133f4dad29a2 Mon Sep 17 00:00:00 2001 From: Connor Ward Date: Wed, 16 Apr 2025 11:06:29 +0100 Subject: [PATCH 35/39] zenodo ready for testing --- .github/workflows/release.yml | 2 + docs/source/zenodo.rst | 13 +- firedrake/scripts/firedrake-zenodo | 324 +++++++++++++---------------- 3 files changed, 155 insertions(+), 184 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 534b4d9a88..768fb22e05 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -6,6 +6,7 @@ on: jobs: deploy: + if: !startsWith(github.ref_name, 'Zenodo') uses: ./.github/workflows/core.yml with: target_branch: release @@ -14,6 +15,7 @@ jobs: secrets: inherit docker: + if: !startsWith(github.ref_name, 'Zenodo') name: Build Docker containers uses: ./.github/workflows/docker.yml with: diff --git a/docs/source/zenodo.rst b/docs/source/zenodo.rst index 4b62b4e305..fcbd08a934 100644 --- a/docs/source/zenodo.rst +++ b/docs/source/zenodo.rst @@ -34,7 +34,7 @@ How to register DOIs for a version of Firedrake This section assumes that you have a Firedrake installation which you have used to conduct some numerical experiment and which you wish to publish or otherwise record for posterity. It is assumed that your -virtualenv is activated or that you otherwise have the firedrake +virtualenv is activated or that you otherwise have the Firedrake scripts in your path. 1. Use ``firedrake-zenodo`` to generate a JSON file containing the @@ -59,16 +59,17 @@ scripts in your path. This will create a file ``firedrake.json`` containing the required information. -2. Create an issue on the Firedrake GitHub page asking that a Zenodo - release be created. Attach the ``firedrake.json`` file to the +2. Create an issue on the `Firedrake GitHub page + `__ asking that a + Zenodo release be created. Attach the ``firedrake.json`` file to the issue. You can create the issue using the correct template `here `__. 3. The Firedrake developers will generate a bespoke Firedrake release containing exactly the set of versions your JSON file specifies, as well as creating a Zenodo record collating these. You will be - provided with a firedrake release tag of the form - ``Firedrake_YYYYMMDD.N``. + provided with a Firedrake release tag of the form + ``Zenodo_YYYYMMDD.N``. You can see an example such a collated record `here `__. @@ -77,7 +78,7 @@ scripts in your path. the DOI) for the collated "meta"-record, which in turn links to all the individual components:: - firedrake-zenodo --bibtex Firedrake_YYYYMMDD.N + firedrake-zenodo --bibtex Zenodo_YYYYMMDD.N Obviously, you substitute in your Firedrake release tag. diff --git a/firedrake/scripts/firedrake-zenodo b/firedrake/scripts/firedrake-zenodo index 018d96493d..51dfab2296 100755 --- a/firedrake/scripts/firedrake-zenodo +++ b/firedrake/scripts/firedrake-zenodo @@ -1,18 +1,16 @@ #! /usr/bin/env python3 + +from __future__ import annotations + import abc import contextlib import dataclasses -import enum import hashlib -import re -import importlib import logging -import pathlib import sys import os import subprocess from argparse import ArgumentParser, RawDescriptionHelpFormatter -from collections import OrderedDict import json import time import requests @@ -23,13 +21,14 @@ from importlib.metadata import Distribution # Change this to https://sandbox.zenodo.org/api for testing ZENODO_URL = "https://zenodo.org/api" -descriptions = OrderedDict([ - ("firedrake", "an automated finite element system"), - ("ufl", "The Unified Form Language"), - ("fiat", "The Finite Element Automated Tabulator"), - ("petsc", "Portable, Extensible Toolkit for Scientific Computation"), - ("loopy", "Transformation-Based Generation of High-Performance CPU/GPU Code"), -]) +DESCRIPTIONS = { + # FIXME: UNDO ME + # "firedrake": "Firedrake: an automated finite element system", + "ufl": "UFL: the Unified Form Language", + "fiat": "FIAT: the Finite Element Automated Tabulator", + "petsc": "PETSc: the Portable, Extensible Toolkit for Scientific Computation", + "loopy": "loopy: Transformation-Based Generation of High-Performance CPU/GPU Code", +} PYPI_PACKAGE_NAMES = { "firedrake": "firedrake", @@ -39,7 +38,7 @@ PYPI_PACKAGE_NAMES = { "petsc": "petsc4py", } -components = list(descriptions.keys()) +components = list(DESCRIPTIONS.keys()) parser = ArgumentParser(description="""Create Zenodo DOIs for specific versions of Firedrake components. @@ -169,11 +168,6 @@ class VCSComponentVersion(ComponentVersion): def get_component_version(component: str) -> ComponentVersion: - # debugging - if component == "firedrake": - print("setting firedrake to nonsense") - return VCSComponentVersion("WHERE AM I?") - pypi_package_name = PYPI_PACKAGE_NAMES[component] # This incantation returns a JSON string containing information about where the # component is installed and whether it is editable or not @@ -257,12 +251,6 @@ def check_github_token_scope(token): log.debug("FIREDRAKE_GITHUB_TOKEN has necessary scopes") -def check_tag(tag): - if re.match(r"Firedrake_20[0-9]{6,6}\.[0-9]+", tag) is None: - log.error("Provided tag '{}' is not a legal Firedrake Zenodo release tag".format(tag)) - sys.exit(1) - - def resolve_tag(tag, components): """Match a tag in component repositories. @@ -281,7 +269,7 @@ def resolve_tag(tag, components): token = os.getenv("FIREDRAKE_GITHUB_TOKEN") if token: check_github_token_scope(token) - gh = github3.login(token=token) + github3.login(token=token) else: log.error("""Need to provide FIREDRAKE_GITHUB_TOKEN for github to resolve tags. @@ -290,19 +278,13 @@ variable FIREDRAKE_GITHUB_TOKEN to a github personal access token.""") sys.exit(1) result = [] for component in components: - repo = gh.repository("firedrakeproject", component) - tags = list(repo.tags()) + repo = get_component_repository(component) try: - found, = [t for t in tags if t.name == tag] + found, = [t for t in repo.tags() if t.name == tag] except ValueError: - if args.skip_missing: - log.warning("Tag '{tag}' does not exist in repository '{repo}'. Continuing".format(tag=tag, repo=repo)) - else: - log.error("Tag '{tag}' does not exist in repository '{repo}'".format( - tag=tag, repo=repo)) - log.error("To continue despite this use --skip-missing") - sys.exit(1) - matching = [t for t in tags if t.commit == found.commit] + log.error(f"Tag '{tag}' does not exist in repository '{repo}'") + sys.exit(1) + matching = [t for t in repo.tags() if t.commit == found.commit] result.append((repo, matching)) return result @@ -412,7 +394,7 @@ def create_description(records, title, doi, additional_dois=None): :returns: A HTML string suitable for zenodo upload.""" links = "\n".join('
  • {name} ({desc}): {doi}
  • '.format( name=repo.name, - desc=descriptions[repo.name], + desc=DESCRIPTIONS[repo.name], doi=record["doi"]) for repo, (_, record) in records) if additional_dois: additional_links = "\n".join('
  • {doi}
  • '.format( @@ -425,14 +407,6 @@ The Firedrake components and dependencies used were:
      {links}
    -

    - -

    -You can install Firedrake using exactly this set of component versions using: -

    firedrake-install --doi {doi}
    -

    -

    -See firedrakeproject.org/install.html for more information.

    """.format(title=title, links=links, doi=doi) if additional_dois: @@ -442,10 +416,7 @@ See firedrakeproject.org
      {additional_links}
    -

    -

    -You will have to download and install them separately in your Firedrake installation, since we cannot automate it for you. -

    """.format(data=data, additional_links=additional_links) +

    """.format(data=data, additional_links=additional_links) return data @@ -510,16 +481,12 @@ def create_metarecord(tag, title, components, info_file=None, matching_records = match(all_records, possible_tags) if len(matching_records) != len(possible_tags): missing = set(repo for repo, _ in possible_tags).difference(repo for repo, _ in matching_records) - if args.skip_missing: - log.warning("Did not find a Zenodo record for repositories: '{}'. Continuing.".format( - ", ".join(repo.full_name for repo in missing))) - else: - log.error("Did not find a Zenodo record for the following repositories") - for repo in missing: - log.error("{}".format(repo.full_name)) - log.error("") - log.error("If you want to continue anyway use --skip-missing") - sys.exit(1) + log.error("Did not find a Zenodo record for the following repositories") + for repo in missing: + log.error("{}".format(repo.full_name)) + log.error("") + sys.exit(1) + base_url = f"{ZENODO_URL}/deposit/depositions" if os.getenv("FIREDRAKE_ZENODO_TOKEN"): authentication_params = {"access_token": os.getenv("FIREDRAKE_ZENODO_TOKEN")} @@ -564,10 +531,10 @@ with deposit:write scope.""") metadata = { "metadata": { - "title": "Software used in `{}'".format(title), + "title": f"Software used in `{title}'", "upload_type": "software", "creators": [{"name": "firedrake-zenodo"}], - "version": "{}".format(tag), + "version": tag, "access_right": "open", "license": "cc-by", "related_identifiers": ([{"relation": "cites", "identifier": record["doi"]} @@ -583,11 +550,11 @@ with deposit:write scope.""") if depo.status_code >= 400: raise ValueError("Unable to add metadata to deposition {}".format(depo.json())) - components = create_json(matching_records, title, additional_dois=additional_dois) + components_json = create_json(matching_records, title, additional_dois=additional_dois) # This is where files live bucket_url = depo.json()["links"]["bucket"] upload = requests.put("{url}/{filename}".format(url=bucket_url, filename="components.json"), - data=components, + data=components_json, params=authentication_params) if upload.status_code >= 400: raise ValueError("Unable to upload file {}".format(upload.json())) @@ -601,7 +568,7 @@ with deposit:write scope.""") f"Actual: {actual}") checksum(upload.json()["checksum"], - hashlib.md5(components).hexdigest(), + hashlib.md5(components_json).hexdigest(), "components.json") if info_file is not None: @@ -656,7 +623,6 @@ def format_bibtex(record): def create_bibtex(tag): - check_tag(tag) response = requests.get(f"{ZENODO_URL}/records", params={"q": "creators.name:firedrake-zenodo AND version:%s" % tag}) if response.status_code >= 400: @@ -712,25 +678,30 @@ if args.list_meta_records: sys.exit(0) -if args.meta_release: - with open(args.meta_release, "r") as f: +def create_zenodo_meta_release(meta_release: str) -> None: + with open(meta_release, "r") as f: data = json.loads(f.read()) tag = data["tag"] - check_tag(tag) - title = data["title"] - components = data["components"] - info_file = decode_info_file(data["info_file"]) additional_dois = data["additional_dois"] new_version_of = data["new_version_of"] - record = create_metarecord(tag, title, components, info_file=info_file, - additional_dois=additional_dois, - update_record=new_version_of) + record = create_metarecord( + tag, + data["title"], + data["components"], + info_file=decode_info_file(data["info_file"]), + additional_dois=additional_dois, + update_record=new_version_of, + ) record = record.json() log.info("Created Zenodo meta-release.") - log.info("Tag is `{}`".format(tag)) - log.info("DOI is {}".format(record["doi"])) - log.info("Zenodo URL is {}".format(record["links"]["record_html"])) - log.info("BibTeX\n\n```bibtex\n{}\n```".format(format_bibtex(record))) + log.info(f"Tag is `{tag}`") + log.info(f"DOI is {record[doi]}") + log.info(f"Zenodo URL is {record["links"]["record_html"]}") + log.info(f"BibTeX\n\n```bibtex\n{format_bibtex(record)}\n```") + + +if args.meta_release: + create_zenodo_meta_release(args.meta_release) sys.exit(0) @@ -778,12 +749,13 @@ if not (args.release or args.input_file): try: import github3 - # Shut up the github module - github3.session.__logs__.setLevel(logging.WARNING) except ImportError: log.error("Publishing releases requires the github3 module. Please pip install github3.py") sys.exit(1) +# Shut up the github module +github3.session.__logs__.setLevel(logging.WARNING) + # Github authentication. token = os.getenv("FIREDRAKE_GITHUB_TOKEN") if token: @@ -804,10 +776,14 @@ with public_repo scope.""") sys.exit(1) -def generate_unique_release_tag(): - tag = time.strftime("Firedrake_%Y%m%d", time.localtime()) +def get_component_repository(component: str) -> github3.Repository: + return gh.repository("firedrakeproject", component) + + +def generate_unique_release_tag() -> str: + tag = time.strftime("Zenodo_%Y%m%d", time.localtime()) index = -1 - firedrake_repo = gh.repository("firedrakeproject", "firedrake") + firedrake_repo = get_component_repository("firedrake") for release in firedrake_repo.tags(): if release.name.startswith(tag): newindex = int(release.name.split(".")[1]) @@ -816,123 +792,115 @@ def generate_unique_release_tag(): return tag -tag = generate_unique_release_tag() +def check_ref_exists(component): + if "version" in shas[component]: + # component is versioned, a GitHub release should already exist + if not release_already_exists(component, shas[component]["version"]): + log.error(f"A release of {component} is referenced but no corresponding release on GitHub can be found, aborting") + sys.exit(1) + else: + repo = get_component_repository(component) + # I *think* that this will fail if it doesn't exist or clashes + debug = repo.commit("NOT A REAL COMMIT") + breakpoint() + repo.commit(shas[component]["commit_id"]) -# Verify commits. This ensures that an invalid sha will cause us to fail before we release any component. -# This step also replaces short shas with long ones. This seems to be necessary for release creation. + # log.error(f"A release of {component} is referenced but no corresponding release on GitHub can be found, aborting") + # sys.exit(1) -for component in components: - repo = gh.repository("firedrakeproject", component) - try: - commit = repo.commit(shas[component]) - if not commit: - log.error("Failed to find specified commit for %s" % component) - - shas[component] = commit.sha - except KeyError: - log.warning("No commit specified for %s. No release will be created for this component." % component) +def make_github_release_or_tag_existing(component: str) -> None: + repo = get_component_repository(component) - # Also check that the tag name does not already exist. - if any(t.name == tag for t in repo.tags()): - log.warning(f"Repository {component} already has tag {tag}. This should not have happened.") - sys.exit(1) + if "version" in shas[component]: + # component is versioned, a GitHub release should already exist + release = get_matching_release(component, shas[component]["version"]) + tag_existing_release = True + commit_id = release.target_commitish + else: + # referencing a specific commit, may need to make a new release + # make sure that 'commit_id' is not truncated + commit_id = repo.commit(shas[component]["commit_id"]) -def make_github_releases(): - # NOTE: dont necessarily need to make everything anew + if commit_already_exists_in_release(component, commit_id): + tag_existing_release = True + else: + tag_existing_release = False + + if tag_existing_release: + log.info(f"Commit '{commit_id}' found in a pre-existing release for " + f"'{component}', adding tag '{release_tag}' to it") + date = datetime.datetime.utcnow().replace(microsecond=0, tzinfo=datetime.timezone.utc).isoformat() + tagger = {"name": "firedrake-zenodo", + "email": "firedrake@imperial.ac.uk", + "date": date} + repo.create_tag(release_tag, + message=DESCRIPTIONS[component], + sha=commit_id, + obj_type="tree", + tagger=tagger) + else: + log.info(f"Pre-existing release for component '{component}' for commit '{commit_id}' not found, creating a new release '{release_tag}' for it") + body = ( + "This release is specifically created to document the version of " + f"{component} used in a particular set of experiments using " + "Firedrake. Please do not cite this as a general source for Firedrake " + "or any of its dependencies. Instead, refer to " + "https://www.firedrakeproject.org/citing.html" + ) + repo.create_release( + tag_name=release_tag, + target_commitish=commit_id, + name=DESCRIPTIONS[component], + body=body, + draft=False, + prerelease=False) + + +def make_releases() -> None: + for component in components: + check_ref_exists(component) for component in components: - if "version" in shas[component]: - # component is versioned, a GitHub release should already exist - release_name = shas[component]["version"] - if not release_already_exists(component, release_name): - log.error("ARGH, do manually") - sys.exit(1) - else: - commit_id = shas[component]["commit_id"] - release_name = f"{component}_{commit_id[:8]}" - target_commitish = commit_id - - message = """This release is specifically created to document the version of - {component} used in a particular set of experiments using - Firedrake. Please do not cite this as a general source for Firedrake - or any of its dependencies. Instead, refer to - https://www.firedrakeproject.org/citing.html""" - - - log.info(f"Creating release {release_name} for {component}") - repo = gh.repository("firedrakeproject", component) - releases = repo.releases() - date = datetime.datetime.utcnow().replace(microsecond=0, tzinfo=datetime.timezone.utc).isoformat() - tagger = {"name": "firedrake-zenodo", - "email": "firedrake@imperial.ac.uk", - "date": date} - - if not release_already_exists(component, release_name): - repo.create_release( - tag_name=release_name, - target_commitish=shas[component], - name=release_name, - body=message.format(component=component), - draft=False, - prerelease=False) + make_github_release_or_tag_existing(component) meta_file = "firedrake-meta-release.json" with open(meta_file, "w") as f: - data = {"tag": tag, + data = {"tag": release_tag, "title": shas["title"], - "components": sorted(set(shas) & set(components)), + "components": components, "info_file": shas.get("metarelease_info_file", None), "new_version_of": shas.get("new_version_of", None), "additional_dois": shas.get("additional_dois", None)} f.write(json.dumps(data)) - log.info("Releases complete. The release tag is %s" % tag) - log.info("Now, you need to create the meta-release") - log.info("Run 'firedrake-zenodo --create-meta-release %s' to do this" % os.path.abspath(meta_file)) - log.info("It is best to wait a short while to ensure that Zenodo is up to date") + log.info("Releases complete.") + log.info(f"Now you should create the meta-release with 'firedrake-zenodo --create-meta-release {os.path.abspath(meta_file)}'.") + log.info("It is best to wait a short while to ensure that the new releases are detected by Zenodo.") -def release_already_exists(component, release_name): - for release in gh.repository("firedrakeproject", component).releases(): - if release.name == release_name: - return True - return False - - -def maybe_make_github_release(???): - target_commitish = commit_id - - message = """This release is specifically created to document the version of - {component} used in a particular set of experiments using - Firedrake. Please do not cite this as a general source for Firedrake - or any of its dependencies. Instead, refer to - https://www.firedrakeproject.org/citing.html""" +def release_already_exists(component: str, release_name: str) -> bool: + try: + get_matching_release(component, release_name) + return True + except KeyError: + return False - log.info(f"Creating release {release_name} for {component}") - repo = gh.repository("firedrakeproject", component) - releases = repo.releases() - date = datetime.datetime.utcnow().replace(microsecond=0, tzinfo=datetime.timezone.utc).isoformat() - tagger = {"name": "firedrake-zenodo", - "email": "firedrake@imperial.ac.uk", - "date": date} +def get_matching_release(component: str, release_name: str) -> github3.Release: + for release in get_component_repository(component).releases(): + if release.tag_name == release_name: + return release + raise KeyError - release_already_exists = False - for release in releases: - if release.name == release_name: - release_already_exists = True - break - if not release_already_exists: - repo.create_release( - tag_name=release_name, - target_commitish=shas[component], - name=release_name, - body=message.format(component=component), - draft=False, - prerelease=False) +def commit_already_exists_in_release(component: str, commit_id: str) -> bool: + for release in get_component_repository(component).releases(): + if release.target_commitish == commit_id: + return True + return False -make_github_releases() +release_tag = generate_unique_release_tag() +make_releases() From 46954489e610871e2eb2abfcac8c23930c17f2c2 Mon Sep 17 00:00:00 2001 From: Connor Ward Date: Wed, 16 Apr 2025 11:11:36 +0100 Subject: [PATCH 36/39] fixup --- firedrake/scripts/firedrake-zenodo | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/firedrake/scripts/firedrake-zenodo b/firedrake/scripts/firedrake-zenodo index 51dfab2296..dc56af1919 100755 --- a/firedrake/scripts/firedrake-zenodo +++ b/firedrake/scripts/firedrake-zenodo @@ -22,8 +22,7 @@ from importlib.metadata import Distribution # Change this to https://sandbox.zenodo.org/api for testing ZENODO_URL = "https://zenodo.org/api" DESCRIPTIONS = { - # FIXME: UNDO ME - # "firedrake": "Firedrake: an automated finite element system", + "firedrake": "Firedrake: an automated finite element system", "ufl": "UFL: the Unified Form Language", "fiat": "FIAT: the Finite Element Automated Tabulator", "petsc": "PETSc: the Portable, Extensible Toolkit for Scientific Computation", From 81fdfda1dd394eef7dd1161df6678c54238e816b Mon Sep 17 00:00:00 2001 From: Connor Ward Date: Wed, 23 Apr 2025 10:36:00 +0100 Subject: [PATCH 37/39] Small fixups --- .github/workflows/core.yml | 4 +- .github/workflows/release.yml | 4 +- docs/source/documentation.rst | 9 ++--- docs/source/install.rst | 6 --- firedrake/scripts/firedrake-zenodo | 8 +--- pyproject.toml | 39 ++++++++++--------- ...irements-dev.txt => requirements-build.txt | 4 +- 7 files changed, 30 insertions(+), 44 deletions(-) rename requirements-dev.txt => requirements-build.txt (63%) diff --git a/.github/workflows/core.yml b/.github/workflows/core.yml index 5f7c897441..44a88e11d0 100644 --- a/.github/workflows/core.yml +++ b/.github/workflows/core.yml @@ -160,8 +160,8 @@ jobs: EXTRA_PIP_FLAGS='' else : # Install build dependencies - pip install -r ./firedrake-repo/requirements-dev.txt pip install "$PETSC_DIR"/src/binding/petsc4py + pip install -r ./firedrake-repo/requirements-build.txt : # Install runtime dependencies that have been removed from the pyproject.toml : # because they rely on non-PyPI versions of petsc4py. @@ -445,8 +445,8 @@ jobs: EXTRA_PIP_FLAGS='' else : # Install build dependencies - pip install -r ./firedrake-repo/requirements-dev.txt pip install "$PETSC_DIR"/src/binding/petsc4py + pip install -r ./firedrake-repo/requirements-build.txt : # We have to pass '--no-build-isolation' to use a custom petsc4py EXTRA_PIP_FLAGS='--no-build-isolation' diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 768fb22e05..4fa26d2c40 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -6,7 +6,7 @@ on: jobs: deploy: - if: !startsWith(github.ref_name, 'Zenodo') + if: ! startsWith( github.ref_name, 'Zenodo' ) uses: ./.github/workflows/core.yml with: target_branch: release @@ -15,7 +15,7 @@ jobs: secrets: inherit docker: - if: !startsWith(github.ref_name, 'Zenodo') + if: ! startsWith( github.ref_name, 'Zenodo' ) name: Build Docker containers uses: ./.github/workflows/docker.yml with: diff --git a/docs/source/documentation.rst b/docs/source/documentation.rst index e8f207aee7..1a90527867 100644 --- a/docs/source/documentation.rst +++ b/docs/source/documentation.rst @@ -9,17 +9,14 @@ Firedrake is continually tested using `GitHub actions `__. Latest Firedrake status: |firedrakebuild|_ - .. |firedrakebuild| image:: https://github.com/firedrakeproject/firedrake/actions/workflows/pr.yml/badge.svg - .. _firedrakebuild: https://github.com/firedrakeproject/firedrake/actions/workflows/pr.yml + .. |firedrakebuild| image:: https://github.com/firedrakeproject/firedrake/actions/workflows/push.yml/badge.svg + .. _firedrakebuild: https://github.com/firedrakeproject/firedrake/actions/workflows/push.yml Firedrake and its components are developed on `GitHub - `__ where we also maintain Firedrake-ready - versions of the `FEniCS `__ components - UFL. + `__. * `Firedrake on GitHub `__ * `FIAT on GitHub `__ - * `Firedrake version of UFL on GitHub `__ Getting started =============== diff --git a/docs/source/install.rst b/docs/source/install.rst index 289c69d98b..d3dc5ca476 100644 --- a/docs/source/install.rst +++ b/docs/source/install.rst @@ -190,12 +190,6 @@ install Firedrake. To do this perform the following steps: #. Firedrake is now installed and ready for use! -.. warning:: - Until Firedrake has versioned releases (slated for April/May 2025), - :doc:`firedrake-zenodo` will only work with *editable* installations of - Firedrake and its components. To install Firedrake in editable mode you - should follow the instructions :ref:`below`. - .. _firedrake_check: diff --git a/firedrake/scripts/firedrake-zenodo b/firedrake/scripts/firedrake-zenodo index dc56af1919..a5f7a5d1bc 100755 --- a/firedrake/scripts/firedrake-zenodo +++ b/firedrake/scripts/firedrake-zenodo @@ -407,7 +407,7 @@ The Firedrake components and dependencies used were: {links}

    - """.format(title=title, links=links, doi=doi) + """.format(title=title, links=links) if additional_dois: data = """{data} @@ -799,14 +799,8 @@ def check_ref_exists(component): sys.exit(1) else: repo = get_component_repository(component) - # I *think* that this will fail if it doesn't exist or clashes - debug = repo.commit("NOT A REAL COMMIT") - breakpoint() repo.commit(shas[component]["commit_id"]) - # log.error(f"A release of {component} is referenced but no corresponding release on GitHub can be found, aborting") - # sys.exit(1) - def make_github_release_or_tag_existing(component: str) -> None: repo = get_component_repository(component) diff --git a/pyproject.toml b/pyproject.toml index bcac258906..b65269d954 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,12 +6,12 @@ description = "An automated system for the portable solution of partial differen readme = "README.rst" license = {file = "LICENSE"} maintainers = [ - {name = "Pablo D. Brubeck"}, - {name = "Daiane I. Dolci"}, - {name = "David A. Ham", email = "david.ham@imperial.ac.uk"}, - {name = "Josh Hope-Collins"}, - {name = "Koki Sagiyama"}, - {name = "Connor J. Ward", email = "c.ward20@imperial.ac.uk"}, + {name = "Pablo D. Brubeck"}, + {name = "Daiane I. Dolci"}, + {name = "David A. Ham", email = "david.ham@imperial.ac.uk"}, + {name = "Josh Hope-Collins"}, + {name = "Koki Sagiyama"}, + {name = "Connor J. Ward", email = "c.ward20@imperial.ac.uk"}, ] requires-python = ">=3.10" dependencies = [ @@ -90,7 +90,8 @@ jax = [ "jax", ] netgen = [ - "ngsPETSc", + # TODO RELEASE + # "ngsPETSc", ] slepc = [ # TODO RELEASE @@ -155,26 +156,26 @@ build-backend = "setuptools.build_meta" # TODO: Convert firedrake-zenodo to a proper entrypoint script. [tool.setuptools] script-files = [ - "firedrake/scripts/firedrake-zenodo", - "scripts/firedrake-run-split-tests", + "firedrake/scripts/firedrake-zenodo", + "scripts/firedrake-run-split-tests", ] [tool.setuptools.package-data] # Unless specified these files will not be installed along with the # rest of the package firedrake = [ - "evaluate.h", - "locate.c", - "icons/*.png", + "evaluate.h", + "locate.c", + "icons/*.png", ] firedrake_check = [ - "Makefile", - "tests/firedrake/conftest.py", - "tests/*/*/*.py", + "Makefile", + "tests/firedrake/conftest.py", + "tests/*/*/*.py", ] pyop2 = [ - "*.h", - "*.pxd", - "*.pyx", - "codegen/c/*.c", + "*.h", + "*.pxd", + "*.pyx", + "codegen/c/*.c", ] diff --git a/requirements-dev.txt b/requirements-build.txt similarity index 63% rename from requirements-dev.txt rename to requirements-build.txt index 5ad7383a5d..cff27061d1 100644 --- a/requirements-dev.txt +++ b/requirements-build.txt @@ -1,4 +1,4 @@ -# core build dependencies (adapted from pyproject.toml) +# Core build dependencies (adapted from pyproject.toml) Cython>=3.0 libsupermesh mpi4py>3; python_version >= '3.13' @@ -9,5 +9,5 @@ pybind11 setuptools>61.2 rtree>=1.2 -# transitive build dependencies +# Transitive build dependencies hatchling From 5c1f92b5cabad8669aeb33a448cd4c6023251196 Mon Sep 17 00:00:00 2001 From: Connor Ward Date: Wed, 23 Apr 2025 14:40:23 +0100 Subject: [PATCH 38/39] linting --- firedrake/scripts/firedrake-zenodo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/firedrake/scripts/firedrake-zenodo b/firedrake/scripts/firedrake-zenodo index a5f7a5d1bc..8d3334f863 100755 --- a/firedrake/scripts/firedrake-zenodo +++ b/firedrake/scripts/firedrake-zenodo @@ -695,7 +695,7 @@ def create_zenodo_meta_release(meta_release: str) -> None: log.info("Created Zenodo meta-release.") log.info(f"Tag is `{tag}`") log.info(f"DOI is {record[doi]}") - log.info(f"Zenodo URL is {record["links"]["record_html"]}") + log.info(f"Zenodo URL is {record['links']['record_html']}") log.info(f"BibTeX\n\n```bibtex\n{format_bibtex(record)}\n```") From 9aee40a9f489bf89b128cf74bb09949b788bf399 Mon Sep 17 00:00:00 2001 From: Connor Ward Date: Thu, 24 Apr 2025 11:44:05 +0100 Subject: [PATCH 39/39] Feedback from Firedrake meeting --- docs/source/install.rst | 18 +++++++++--------- firedrake/scripts/firedrake-zenodo | 6 +++--- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/docs/source/install.rst b/docs/source/install.rst index d3dc5ca476..1836130d2a 100644 --- a/docs/source/install.rst +++ b/docs/source/install.rst @@ -180,7 +180,7 @@ install Firedrake. To do this perform the following steps: #. Install Firedrake:: $ pip cache remove petsc4py - $ pip install --no-binary h5py "firedrake @ git+https://github.com/firedrakeproject/firedrake.git#[check]" + $ pip install --no-binary h5py "firedrake @ git+https://github.com/firedrakeproject/firedrake.git@release#[check]" .. note:: Though not strictly necessary to install Firedrake's optional @@ -219,7 +219,7 @@ Updating Firedrake involves following the same steps as above when :ref:`installing Firedrake`. First, use ``firedrake-configure`` to set the right environment variables and then run:: - $ pip install --upgrade git+https://github.com/firedrakeproject/firedrake.git + $ pip install --upgrade git+https://github.com/firedrakeproject/firedrake.git@release Updating PETSc ~~~~~~~~~~~~~~ @@ -324,7 +324,7 @@ To install Firedrake with SLEPc support you should: and install Firedrake with the ``slepc`` optional dependency. For example:: $ pip cache remove slepc4py - $ pip install --no-binary h5py "firedrake @ git+https://github.com/firedrakeproject/firedrake.git#[check,slepc]" + $ pip install --no-binary h5py "firedrake @ git+https://github.com/firedrakeproject/firedrake.git@release#[check,slepc]" VTK ~~~ @@ -332,7 +332,7 @@ VTK To install Firedrake with VTK, it should be installed using the ``vtk`` optional dependency. For example:: - $ pip install --no-binary h5py "firedrake @ git+https://github.com/firedrakeproject/firedrake.git#[check,vtk]" + $ pip install --no-binary h5py "firedrake @ git+https://github.com/firedrakeproject/firedrake.git@release#[check,vtk]" At present VTK wheels are not available for ARM Linux machines. Depending on your Python version you may be able to work around this by downloading and pip installing @@ -346,7 +346,7 @@ PyTorch To install Firedrake with `PyTorch `_, it should be installed using the ``torch`` optional dependency. For example:: - $ pip install --no-binary h5py "firedrake @ git+https://github.com/firedrakeproject/firedrake.git#[check,torch]" --extra-index-url https://download.pytorch.org/whl/cpu + $ pip install --no-binary h5py "firedrake @ git+https://github.com/firedrakeproject/firedrake.git@release#[check,torch]" --extra-index-url https://download.pytorch.org/whl/cpu Observe that, in addition to specifying ``torch``, an additional argument (``--extra-index-url``) is needed. More information on installing @@ -359,7 +359,7 @@ JAX To install Firedrake with JAX, it should be installed using the ``jax`` optional dependency. For example:: - $ pip install --no-binary h5py "firedrake @ git+https://github.com/firedrakeproject/firedrake.git#[check,jax]" + $ pip install --no-binary h5py "firedrake @ git+https://github.com/firedrakeproject/firedrake.git@release#[check,jax]" Netgen @@ -368,7 +368,7 @@ Netgen To install Firedrake with `Netgen `_ support, it should be installed with the ``netgen`` optional dependency. For example:: - $ pip install --no-binary h5py "firedrake @ git+https://github.com/firedrakeproject/firedrake.git#[check,netgen]" + $ pip install --no-binary h5py "firedrake @ git+https://github.com/firedrakeproject/firedrake.git@release#[check,netgen]" Customising PETSc @@ -509,8 +509,8 @@ should be followed: .. code-block:: text $ pip cache remove petsc4py - $ pip install $PETSC_DIR/$PETSC_ARCH/src/binding/petsc4py - $ pip install -r ./firedrake/requirements-dev.txt + $ pip install $PETSC_DIR/src/binding/petsc4py + $ pip install -r ./firedrake/requirements-build.txt #. Install Firedrake in editable mode without build isolation:: diff --git a/firedrake/scripts/firedrake-zenodo b/firedrake/scripts/firedrake-zenodo index 8d3334f863..44fdc0f42f 100755 --- a/firedrake/scripts/firedrake-zenodo +++ b/firedrake/scripts/firedrake-zenodo @@ -32,7 +32,7 @@ DESCRIPTIONS = { PYPI_PACKAGE_NAMES = { "firedrake": "firedrake", "ufl": "fenics-ufl", - "fiat": "fenics-fiat", + "fiat": "firedrake-fiat", "loopy": "loopy", "petsc": "petsc4py", } @@ -834,7 +834,7 @@ def make_github_release_or_tag_existing(component: str) -> None: obj_type="tree", tagger=tagger) else: - log.info(f"Pre-existing release for component '{component}' for commit '{commit_id}' not found, creating a new release '{release_tag}' for it") + log.info(f"Pre-existing release for component '{component}' for commit '{commit_id}' not found, creating a new pre-release '{release_tag}' for it") body = ( "This release is specifically created to document the version of " f"{component} used in a particular set of experiments using " @@ -848,7 +848,7 @@ def make_github_release_or_tag_existing(component: str) -> None: name=DESCRIPTIONS[component], body=body, draft=False, - prerelease=False) + prerelease=True) def make_releases() -> None: