Skip to content

Commit

Permalink
Merge branch 'master' into amend_jules_code_access_form
Browse files Browse the repository at this point in the history
  • Loading branch information
maggiehendry authored Nov 6, 2024
2 parents ee4dfba + e49b79a commit 05b953c
Show file tree
Hide file tree
Showing 17 changed files with 595 additions and 66 deletions.
241 changes: 241 additions & 0 deletions .github/scripts/release.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,241 @@
#!/usr/bin/env python3

"""
Automate the steps required to deploy the jules documentation.
"""

# pylint: disable=useless-return


import os
import re
import logging
from argparse import ArgumentParser
from datetime import datetime
from fileinput import FileInput
from subprocess import run, Popen, STDOUT, PIPE, CalledProcessError


class JulesDocsRelease:
"""Class which deploys the jules docs."""

def __init__(self, vnumber):
self.vnumber = str(vnumber)
self.changed = False
return

def update_config(self, target):
"""Update the sphinx configuration file."""

year = str(datetime.now().year)

cr_pattern = re.compile(r"\s*copyright\s*=\s*.(\d+).")
vn_pattern = re.compile(r"\s*(release|version)\s*=\s*.([\d\.]+).")

with FileInput(target, inplace=True) as fd:
# Update the copyright and version information in the
# sphinx config file

for line in fd:
if match := cr_pattern.match(line):
if match.group(1) != year:
line = f"copyright = '{year}'\n"
self.changed = True
logging.info("Updated sphinx copyright year")
elif match := vn_pattern.match(line):
if match.group(2) != self.vnumber:
line = f"{match.group(1)} = '{self.vnumber}'\n"
self.changed = True
logging.info(
"Updated sphinx %s number to %s",
match.group(1),
self.vnumber,
)
print(line, end="")

return

def update_index(self, target):
"""Update the index page with the new release."""

# Check whether an update is required
with FileInput(target) as fd:
match = f'a href="vn{self.vnumber}"'
found = False

for line in fd:
if match in line:
found = True
break

if found:
# Stop if version is already present
return

# Add the missing version link
with FileInput(target, inplace=True) as fd:
for line in fd:
if 'href="latest/index.html"' in line:
line += " " * 16
line += (
f'<li><a href="vn{self.vnumber}">vn{self.vnumber}</a></li>\n'
)
self.changed = True
logging.info("Added reference to vn%s to index", self.vnumber)
print(line, end="")

return

def mkdocs(self):
"""Run sphinx to build the HTML documentation."""

dest = f"vn{self.vnumber}"

if not os.path.exists(dest):
# Build the sphinx documentation
cmd = [
"sphinx-build",
"-qb",
"html",
"user_guide/doc/source",
f"vn{self.vnumber}",
]
run(cmd, check=True)
logging.info("Successfully built the sphinx documentation")

if os.path.exists(dest):
# Check directory exists and create a symlink
if os.path.islink("latest") and os.readlink("latest") != dest:
os.unlink("latest")
if not os.path.exists("latest"):
os.symlink(dest, "latest")
logging.info("Set latest link to %s", dest)

return


def last_log_message(branch):
"""Get the log message of the last commit to trunk."""

cmd = ["git", "log", "--pretty=format:message: %s", "--branches", branch, "-1"]

with Popen(cmd, stdout=PIPE, stderr=STDOUT, encoding="utf-8") as proc:
message = "unknown update"

for line in proc.stdout:
if line.startswith("message: "):
message = line.split(": ", 1)[-1]
break

return message


def commit_changes(args):
"""Commit any new change to the trunk."""

cmd = ["git", "add", "-A"]
run(cmd, check=False)

cmd = ["git", "diff", "--cached", "--quiet"]
if run(cmd, check=False) == 0:
logging.info("no changes need to be committed")

message = last_log_message(args.trunk_name)
message = f"Docs build for {message}"

if args.no_commit:
# Skip the commit and push commands
logging.info("Need to commit change for %r", message)
return

cmd = ["git", "commit", "--no-gpg-sign", "-a", "--quiet", "-m", message]

run(cmd, check=True)
logging.info("Committed change %r", message)

# Push the update to the upstream repo and branch
cmd = ["git", "push", "--force", "origin"]
if args.gh_pages:
cmd.append("gh-pages")
run(cmd, check=True)
logging.info("Pushed automatic commit")

return


def setup_pages_branch(args, branch):
"""Setup pages branch for documentation build."""

# Check out the docs branch or create it if it doesn't exist
cmd = ["git", "checkout", branch]
result = run(cmd, check=False)
if result.returncode == 0:
logging.info("Checking out %s branch", branch)
else:
# Add the branch option and retry the checkout
cmd.insert(-1, "-b")
run(cmd, check=True)
logging.info("Created branch %s", branch)

# Merge changes from the trunk into the docs branch but defer
# committing until the docs have been built. If there is a
# conflict, resolve by overwriting with the copy from the trunk
cmd = ["git", "merge", "-v", "-X", "theirs", "--no-commit", args.trunk_name]
run(cmd, check=True)
logging.info("Merging changes from %s to %s branch", args.trunk_name, branch)

return


def main():
"""Main function."""

parser = ArgumentParser()
parser.add_argument("--verbose", action="store_true", help="enable verbose output")
parser.add_argument(
"--trunk-name", type=str, default="master", help="name of trunk"
)
parser.add_argument(
"--no-commit", action="store_true", help="do not commit the changes"
)
parser.add_argument(
"--gh-pages", action="store_true", help="deploy to a gh-pages branch"
)
parser.add_argument("release", type=str, help="github release reference")
args = parser.parse_args()

match = re.match(r".*vn?([\.\d]+)-*\S*$", args.release)
if match:
vnumber = match.group(1)
else:
parser.error(f"unable to identify version number '{args.release}'")

if args.verbose:
logging.basicConfig(level=logging.INFO, format="%(message)s")

release = JulesDocsRelease(vnumber)

try:
if args.gh_pages:
setup_pages_branch(args, "gh-pages")

release.update_config("user_guide/doc/source/conf.py")
release.mkdocs()
release.update_index("index.html")
except (FileNotFoundError, CalledProcessError) as error:
logging.error(str(error))
raise SystemExit(1) from error

if release.changed:
# There are at least some updates to apply
commit_changes(args)

else:
logging.info("No changes made")
raise SystemExit(2)

return


if __name__ == "__main__":
main()
86 changes: 86 additions & 0 deletions .github/workflows/build_docs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
---
# This workflow builds the JULES Sphinx documenation.
#
# Where the triggering event does not match the master branch on the
# main jules docs repository, the action limits itself to a test
# build.
#
# Where the event is a push to master on the main repository, the
# workflow checks for documentation that matches the most recent
# release tag and adds it to the gh-pages branch as necessary. The
# documentation can be published by configuring the repository to
# use the gh-pages branch.

name: build_docs

# Controls when the action will run
on:
push:
pull_request:

# Enables manual running from the Actions tab
workflow_dispatch:

jobs:
build-and-deploy:
runs-on: ubuntu-latest

steps:
- name: checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
fetch-tags: true

- name: Setup conda
run: |
conda update conda
conda env create -f environment.yml
echo ":heavy_check_mark: Set up conda environment" >> $GITHUB_STEP_SUMMARY
- name: Test documentation
run: |
eval "$(conda shell.bash hook)"
conda activate jules-user-guide
pushd user_guide/doc
# Add status of tests to the summary
if make html; then
echo ":heavy_check_mark: Documentation tests passed" >> $GITHUB_STEP_SUMMARY
else
echo ":x: Documentation tests failed" >> $GITHUB_STEP_SUMMARY
exit 1
fi
popd
if: ${{ github.repository != 'jules-lsm/jules-lsm.github.io'
|| github.ref != 'refs/heads/master' }}

- name: Deploy documentation
run: |
eval "$(conda shell.bash hook)"
conda activate jules-user-guide
# Setup some git things
git config --global user.email "umsysteam@metoffice.gov.uk"
git config --global user.name "Jules Bot"
TAG=$( git describe --tags | sed "s/-.*//" )
# Capture the output status of the command, no matter what
# it is, without causing the workflow to fail if it is
# non-zero
python3 .github/scripts/release.py --verbose --gh-pages $TAG \
&& STATUS=$? || STATUS=$?
# Add a message to the summary
if [[ $STATUS = 0 ]]; then
echo ":heavy_check_mark: Documentation for $TAG deployed" >> $GITHUB_STEP_SUMMARY
elif [[ $STATUS = 1 ]]; then
echo ":x: Documentation for $TAG failed" >> $GITHUB_STEP_SUMMARY
exit 1
elif [[ $STATUS = 2 ]]; then
echo ":speech_balloon: Documentation for $TAG is up to date" >> $GITHUB_STEP_SUMMARY
fi
exit 0
if: ${{ github.repository == 'jules-lsm/jules-lsm.github.io'
&& github.ref == 'refs/heads/master'
&& (github.event_name == 'push'
|| github.event_name == 'workflow_dispatch') }}
Loading

0 comments on commit 05b953c

Please sign in to comment.