-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'master' into amend_jules_code_access_form
- Loading branch information
Showing
17 changed files
with
595 additions
and
66 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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') }} |
Oops, something went wrong.