diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 22fee12e..ff2385c4 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -4,11 +4,31 @@ name: Deploy API Docs # environment everything else uses — no conda) and publish to gh-pages. # autodoc imports shapepipe, so it needs the full runtime; the dev image's # `doc` extra provides sphinx + myst-parser + the theme on top of that. +# +# The published site is versioned. Each ref builds at its own time, in its own +# image, into its own slice of the gh-pages branch: +# master / main -> site root (stable; keeps existing URLs working) +# develop -> /latest/ +# tags v* -> // (e.g. /v1.1.0/) +# A `switcher.json` at the site root drives the version dropdown. Pull requests +# build the docs and upload them as an artifact, but do not deploy — so a broken +# docs build is caught before merge, and reviewers can preview the rendered HTML. on: push: branches: - master - main + - develop + tags: + - "v*" + # On PRs, build + upload the docs (no deploy) only when something that affects + # them changes, so a broken docs build is caught before merge. + pull_request: + paths: + - "docs/**" + - "src/**" + - "pyproject.toml" + - ".github/workflows/cd.yml" workflow_dispatch: jobs: @@ -20,6 +40,20 @@ jobs: - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - name: Resolve version slug + id: slug + # Map the pushed ref to a version label (DOCS_VERSION, used by conf.py to + # highlight the switcher) and a gh-pages destination directory ("." = root). + run: | + case "${GITHUB_REF}" in + refs/tags/*) slug="${GITHUB_REF#refs/tags/}"; dir="${slug}" ;; + refs/heads/develop) slug="latest"; dir="latest" ;; + refs/heads/master|refs/heads/main) slug="stable"; dir="." ;; + *) slug="latest"; dir="latest" ;; + esac + echo "slug=$slug" >> "$GITHUB_OUTPUT" + echo "dir=$dir" >> "$GITHUB_OUTPUT" + - name: Set up Docker Buildx uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 with: @@ -37,16 +71,40 @@ jobs: # Generate API stubs and build the HTML inside the image. docs/ is # bind-mounted so sphinx-apidoc's generated rst and the _build output - # land back on the runner for the deploy step. + # land back on the runner for the deploy step. DOCS_VERSION tells conf.py + # which version this build represents (for the switcher highlight). - name: Build API documentation run: | - docker run --rm -v "${GITHUB_WORKSPACE}/docs:/app/docs" shapepipe-dev:docs bash -c " - sphinx-apidoc -t docs/_templates -feTMo docs/source shapepipe shapepipe/modules/*_runner.py && - sphinx-build -E docs/source docs/_build - " + docker run --rm -e DOCS_VERSION="${{ steps.slug.outputs.slug }}" \ + -v "${GITHUB_WORKSPACE}/docs:/app/docs" shapepipe-dev:docs bash -c " + sphinx-apidoc -t docs/_templates -feTMo docs/source src/shapepipe src/shapepipe/modules/*_runner.py && + sphinx-build -E docs/source docs/_build + " + + - name: Upload built HTML as artifact + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: docs-html-${{ steps.slug.outputs.slug }} + path: docs/_build + + # Stage the gh-pages payload: the build under its version directory, plus a + # fresh switcher.json at the root so the dropdown stays current whenever any + # version is published. + - name: Stage versioned site + if: github.event_name != 'pull_request' + run: | + target="site" + [ "${{ steps.slug.outputs.dir }}" != "." ] && target="site/${{ steps.slug.outputs.dir }}" + mkdir -p "$target" + cp -r docs/_build/. "$target"/ + cp docs/switcher.json site/switcher.json + # keep_files: true so each deploy only writes its own version directory (and + # the root switcher.json) and never wipes the other versions on the branch. - name: Deploy API documentation + if: github.event_name != 'pull_request' uses: peaceiris/actions-gh-pages@84c30a85c19949d7eee79c4ff27748b70285e453 # v4.1.0 with: github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: docs/_build + publish_dir: site + keep_files: true diff --git a/docs/source/conf.py b/docs/source/conf.py index ab410f0d..eea9b8f4 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -14,6 +14,8 @@ # import os import sys +from importlib.metadata import PackageNotFoundError +from importlib.metadata import version as _get_version sys.path.insert(0, os.path.abspath("../..")) @@ -23,10 +25,15 @@ copyright = "2022, CosmoStat" author = "CosmoStat" -# The short X.Y version -version = "1.0" -# The full version, including alpha/beta/rc tags -release = "1.0.1" +# The version is read from the installed package metadata, so the docs never +# carry a hand-maintained (and inevitably stale) version string. +try: + # The full version, including any alpha/beta/rc tags. + release = _get_version("shapepipe") +except PackageNotFoundError: + release = "0.0.0" +# The short X.Y version. +version = ".".join(release.split(".")[:2]) # -- General configuration --------------------------------------------------- @@ -117,6 +124,19 @@ "use_edit_page_button": True, "path_to_docs": "docs/source", "extra_navbar": "

", + # -- Version switcher ---------------------------------------------------- + # The published docs are versioned: stable (master) lives at the site root, + # `latest` (develop) and tagged releases live in sub-directories, and this + # dropdown moves between them. `switcher.json` (deployed to the site root) + # lists the versions; DOCS_VERSION tells the build which one it is, so the + # dropdown highlights the right entry. The CI deploy sets it to the slug it + # publishes ("stable", "latest", or a tag like "v1.1.0"); local builds + # default to "latest". + "switcher": { + "json_url": "https://cosmostat.github.io/shapepipe/switcher.json", + "version_match": os.environ.get("DOCS_VERSION", "latest"), + }, + "navbar_end": ["version-switcher", "navbar-icon-links"], } html_collapsible_definitions = True html_awesome_headerlinks = True diff --git a/docs/switcher.json b/docs/switcher.json new file mode 100644 index 00000000..cb9f3e74 --- /dev/null +++ b/docs/switcher.json @@ -0,0 +1,13 @@ +[ + { + "name": "stable", + "version": "stable", + "url": "https://cosmostat.github.io/shapepipe/", + "preferred": true + }, + { + "name": "latest (develop)", + "version": "latest", + "url": "https://cosmostat.github.io/shapepipe/latest/" + } +]