Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
120 commits
Select commit Hold shift + click to select a range
08f06f2
Add set_version python and shell scripts.
hsorby Apr 28, 2026
56842db
Add initial prepare release GitHub action script.
hsorby Apr 28, 2026
b567a51
Merge branch 'update-version-number' into create-release-staging-br
hsorby Apr 28, 2026
817a4ad
Update setting version numbers script parameters.
hsorby Apr 28, 2026
f04eaba
Use actions/checkout@v6 in release-prepare.yml.
hsorby Apr 28, 2026
7d1d71e
Set set_version.sh as executable.
hsorby Apr 28, 2026
c00457a
Set a defulat value of 'main' for the source branch.
hsorby Apr 28, 2026
33cb8e0
Add debug printing to set_version.sh.
hsorby Apr 28, 2026
a41d407
Add more debug printing to set_version.sh.
hsorby Apr 28, 2026
984f6ef
Update set_version.sh to run on GNU bash, improve version match regexp.
hsorby Apr 28, 2026
029b20b
Update peter-evans/create-pull-request in release-prepare.yml.
hsorby Apr 28, 2026
4904518
Add debug echo statements to release-prepare.yml.
hsorby Apr 28, 2026
013cd6a
Change find regexp in release-prepare.yml.
hsorby Apr 28, 2026
e1decd6
Add debug messages in release-prepare.yml.
hsorby Apr 28, 2026
eaed2c9
Ignore Act resource files.
hsorby Apr 29, 2026
c68bef2
Debug 2.
hsorby Apr 29, 2026
b3b5ac5
Debug 2.
hsorby Apr 29, 2026
230f22e
Debug 3.
hsorby Apr 29, 2026
f523fd3
Debug 4.
hsorby Apr 29, 2026
6d8dc63
Debug 5.
hsorby Apr 29, 2026
f613c09
Debug 6.
hsorby Apr 29, 2026
a26dad6
Re-instate creation of PR when a release process is started.
hsorby Apr 29, 2026
5c184b2
Add PR write permissions to release-prepare workflow.
hsorby Apr 29, 2026
dd2b7f8
Try basing changes off the release branch.
hsorby Apr 29, 2026
bd68aef
Try and fix creation of PR against release branch.
hsorby Apr 29, 2026
222ab5a
Fully qualify environment BRANCH_PREFIX in release-prepare.yml.
hsorby Apr 29, 2026
63ed75f
Use rsync to copy changes to release staging branch.
hsorby Apr 29, 2026
d3f9e2b
Improve commit message for release changes.
hsorby Apr 29, 2026
eaa7ad4
Set release repo path for peter-evans/create-pull-request.
hsorby Apr 29, 2026
fb77b56
Use gh instead of peter-evans/create-pull-request in release-prepare.…
hsorby Apr 30, 2026
36bbb34
Add Github token to Create PR step in env:
hsorby Apr 30, 2026
d56fa3d
Change into release repo. before creating PR.
hsorby Apr 30, 2026
d811484
Try using here document to set PR body text in release-prepare.yml.
hsorby Apr 30, 2026
bc70c87
Try indenting the here document body differently.
hsorby Apr 30, 2026
174a64f
Use a text file to store body of PR text.
hsorby Apr 30, 2026
7221d2b
Improve the title of the created PR.
hsorby Apr 30, 2026
c45d167
Add initial generate changelog GitHub Actions scripts/workflow.
hsorby Apr 30, 2026
45dc79e
Remove whitespace before grepped output of staging branch.
hsorby Apr 30, 2026
a79d0a5
Use version 3.12 in release-changelog.
hsorby Apr 30, 2026
cf52090
Try an not install python if running workflow using act.
hsorby Apr 30, 2026
9d03548
Use proper if statement syntax for Github actions.
hsorby Apr 30, 2026
1290080
Look at error with rsync copying files in release-prepare.yml.
hsorby Apr 30, 2026
b983c84
Improve creation of code in release staging branch.
hsorby Apr 30, 2026
34a7d7a
Fix up generate_changelog.py and release-changelog.yml to a working s…
hsorby Apr 30, 2026
269a355
Add clean and reindexing script to release-changelog.yml.
hsorby Apr 30, 2026
fb146aa
Add utilities Python file for unwanted_versions function.
hsorby Apr 30, 2026
07dad53
Don't do relative import from non-package in clean_and_reindex_change…
hsorby Apr 30, 2026
e46850d
Move generated changelog into changelogs directory so it can be indexed.
hsorby Apr 30, 2026
74cbc60
Install requests package for generating changelogs.
hsorby Apr 30, 2026
8b884e6
Also install packaging package for generating changelogs.
hsorby Apr 30, 2026
532edbd
Add set_version python and shell scripts.
hsorby Apr 28, 2026
c7415df
Update setting version numbers script parameters.
hsorby Apr 28, 2026
94f9347
Use actions/checkout@v6 in release-prepare.yml.
hsorby Apr 28, 2026
4b63196
Set set_version.sh as executable.
hsorby Apr 28, 2026
1abcbaa
Set a defulat value of 'main' for the source branch.
hsorby Apr 28, 2026
51d08f7
Add debug printing to set_version.sh.
hsorby Apr 28, 2026
969a769
Add more debug printing to set_version.sh.
hsorby Apr 28, 2026
e3f182f
Update set_version.sh to run on GNU bash, improve version match regexp.
hsorby Apr 28, 2026
871400b
Update peter-evans/create-pull-request in release-prepare.yml.
hsorby Apr 28, 2026
4f02bce
Add debug echo statements to release-prepare.yml.
hsorby Apr 28, 2026
0c3a2d0
Change find regexp in release-prepare.yml.
hsorby Apr 28, 2026
0e66623
Add debug messages in release-prepare.yml.
hsorby Apr 28, 2026
06f4647
Ignore Act resource files.
hsorby Apr 29, 2026
f6a7542
Re-instate creation of PR when a release process is started.
hsorby Apr 29, 2026
e6ef119
Add PR write permissions to release-prepare workflow.
hsorby Apr 29, 2026
407ada9
Try basing changes off the release branch.
hsorby Apr 29, 2026
cd30250
Try and fix creation of PR against release branch.
hsorby Apr 29, 2026
953a184
Fully qualify environment BRANCH_PREFIX in release-prepare.yml.
hsorby Apr 29, 2026
7dfbe3e
Use rsync to copy changes to release staging branch.
hsorby Apr 29, 2026
7647a74
Improve commit message for release changes.
hsorby Apr 29, 2026
3f7c36e
Set release repo path for peter-evans/create-pull-request.
hsorby Apr 29, 2026
0e29e6f
Use gh instead of peter-evans/create-pull-request in release-prepare.…
hsorby Apr 30, 2026
f1770bb
Add Github token to Create PR step in env:
hsorby Apr 30, 2026
2e6af9e
Change into release repo. before creating PR.
hsorby Apr 30, 2026
38870a8
Try using here document to set PR body text in release-prepare.yml.
hsorby Apr 30, 2026
fb1fc64
Try indenting the here document body differently.
hsorby Apr 30, 2026
ee9b587
Use a text file to store body of PR text.
hsorby Apr 30, 2026
27672e4
Improve the title of the created PR.
hsorby Apr 30, 2026
4c846bd
Add initial generate changelog GitHub Actions scripts/workflow.
hsorby Apr 30, 2026
3427b6d
Remove whitespace before grepped output of staging branch.
hsorby Apr 30, 2026
a3bd936
Use version 3.12 in release-changelog.
hsorby Apr 30, 2026
2120a56
Try an not install python if running workflow using act.
hsorby Apr 30, 2026
b953618
Use proper if statement syntax for Github actions.
hsorby Apr 30, 2026
8da4621
Look at error with rsync copying files in release-prepare.yml.
hsorby Apr 30, 2026
350fb16
Improve creation of code in release staging branch.
hsorby Apr 30, 2026
70dad82
Fix up generate_changelog.py and release-changelog.yml to a working s…
hsorby Apr 30, 2026
6648a10
Add clean and reindexing script to release-changelog.yml.
hsorby Apr 30, 2026
071edc2
Add utilities Python file for unwanted_versions function.
hsorby Apr 30, 2026
1839f87
Don't do relative import from non-package in clean_and_reindex_change…
hsorby Apr 30, 2026
f59ccb6
Move generated changelog into changelogs directory so it can be indexed.
hsorby Apr 30, 2026
aa0ce06
Install requests package for generating changelogs.
hsorby Apr 30, 2026
a24722d
Also install packaging package for generating changelogs.
hsorby Apr 30, 2026
dcad344
Separate out release codebase changes from version change in prepare …
hsorby Apr 30, 2026
f2847a5
Work on creating three distinct commits when preparing for a new rele…
hsorby May 4, 2026
c121096
Merge in changes from origin/main fixing conflicts.
hsorby May 4, 2026
9accb4b
Connect up set version and add changelog workflows.
hsorby May 4, 2026
1b0f253
Remove unexpected value secrets.
hsorby May 4, 2026
04dc6d3
Correct paths to workflow files in release-prepare.yml.
hsorby May 4, 2026
f4c0ff3
Add some debug output.
hsorby May 4, 2026
6e2cbd2
Use qualified path to reusable workflows.
hsorby May 4, 2026
7ca9df7
Remove debug output.
hsorby May 4, 2026
a537518
Try splitting out calls to other workflows into separate jobs.
hsorby May 4, 2026
be9800b
Add workflow_call triggers to called workflows.
hsorby May 4, 2026
1bc4d59
Add type to workflow call inputs.
hsorby May 4, 2026
d55ccc0
Try and fix errors in release-changelog workflow.
hsorby May 4, 2026
0739187
Add github token back in through secrets.
hsorby May 4, 2026
a969c3b
Add github tokenn secret into calling environment.
hsorby May 4, 2026
6ceca28
Check GitHub token in release-changelog.
hsorby May 5, 2026
d180eaa
Delete release branch if the workflow failed.
hsorby May 5, 2026
99a87f4
Use secrets.GITHUB_TOKEN to pass through to sub workflows.
hsorby May 5, 2026
7a44a9e
Use secrets.GITHUB_TOKEN to pass through to sub workflows.
hsorby May 5, 2026
87e676c
Make GH_TOKEN an input for workflow call in set-version workflow.
hsorby May 5, 2026
9330e60
Correct indentation of secrets in workflow_call for set-version workf…
hsorby May 5, 2026
fca8698
Fix reference to PR body text in release-prepare.
hsorby May 5, 2026
1ada891
Set up repository for gh command in release-prepare workflow.
hsorby May 5, 2026
7c96f4e
Tidy release process workflows a little.
hsorby May 5, 2026
c2b71a7
Update release process documentation.
hsorby May 5, 2026
9d55bc0
Update release process documentation for clarity and completeness.
hsorby May 5, 2026
581a695
Fix numeric version regex.
hsorby May 5, 2026
4569abb
Merge branch 'main' into create-release-staging-br
agarny May 7, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
126 changes: 126 additions & 0 deletions .github/scripts/clean_and_reindex_changelogs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import argparse
import os
import sys

from packaging.version import Version

from utilities import unwanted_versions

CHANGELOG_PREFIX = "changelog_v"
CHANGELOG_TOC_START_MARKER = " # CHANGELOG_TOC_START_MARKER\n"
CHANGELOG_TOC_END_MARKER = " # CHANGELOG_TOC_END_MARKER\n"


def _get_changelog_file_names(location):
current_dir = os.path.abspath(os.path.curdir)
working_dir = os.path.join(current_dir, location)
os.chdir(working_dir)

changelogs = []
for root, dirs, files in os.walk(".", topdown=False):
for name in files:
parts = os.path.splitext(name)
if len(parts) == 2 and parts[0].startswith(CHANGELOG_PREFIX) and parts[1] == ".rst":
# changelogs.append(os.path.join(root, name))
changelogs.append(parts[0].replace(CHANGELOG_PREFIX, ''))

os.chdir(current_dir)

return changelogs


def _remove_changelogs(location, versions):
for version_ in versions:
os.remove(os.path.join(location, f"{CHANGELOG_PREFIX}{version_}.rst"))


def _create_text(versions, with_directory=False):
if with_directory:
changelog_dir = "changelogs/"
else:
changelog_dir = ""

text = [
"\n",
"Changelogs\n",
"==========\n",
"\n",
".. toctree::\n",
"\n",
]
for v in versions:
text.append(f" {changelog_dir}{CHANGELOG_PREFIX}{v}\n")
text.append("\n")

return text


def _write_changelog_index_file(file_name, versions):
text = _create_text(versions)

with open(file_name, 'w') as f:
for line in text:
f.write(line)


def _write_main_index_file(file_name, versions):
with open(file_name) as f:
lines = f.readlines()

start_index = lines.index(CHANGELOG_TOC_START_MARKER)
# We subtract one line to include the restructured text comment marker.
end_index = lines.index(CHANGELOG_TOC_END_MARKER) - 1
target_lines = [j for i, j in enumerate(lines)
if not start_index < i < end_index]

text = _create_text(versions, with_directory=True)

# Insert our generated changelog toc into current file content.
insert_index = start_index + 1
target_lines[insert_index:insert_index] = text

with open(file_name, 'w') as f:
for line in target_lines:
f.write(line)


def process_arguments():
parser = argparse.ArgumentParser(description="Update the table of contents to match existing changelogs.")
parser.add_argument("local_repo",
help="The location of the project repository. Absolute path or relative path relative to the "
"current working directory.")

return parser


def main():
parser = process_arguments()
args = parser.parse_args()
repo_relative_path = args.local_repo

cur_dir = os.path.abspath(os.curdir)

repo_path = os.path.join(cur_dir, repo_relative_path)
if not os.path.isfile(os.path.join(repo_path, 'CMakeLists.txt')):
sys.exit(2)

if not os.path.isdir(os.path.join(repo_path, 'docs', 'changelogs')):
sys.exit(3)

changelogs = _get_changelog_file_names(os.path.join(repo_path, 'docs', 'changelogs'))
sorted_changelogs = sorted(changelogs, key=Version)
sorted_changelogs.reverse()

remove_versions = unwanted_versions(sorted_changelogs, depth=3)
wanted_changelogs = list(filter(lambda x: x not in remove_versions, sorted_changelogs))
_remove_changelogs(os.path.join(repo_path, 'docs', 'changelogs'), remove_versions)

main_index_file = os.path.join(repo_path, 'docs', 'index.rst')
_write_main_index_file(main_index_file, wanted_changelogs)

changelog_index_file = os.path.join(repo_path, 'docs', 'changelogs', 'index.rst')
_write_changelog_index_file(changelog_index_file, wanted_changelogs)


if __name__ == "__main__":
main()
199 changes: 199 additions & 0 deletions .github/scripts/generate_changelog.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
import argparse
import json
import os
import re
import subprocess
import requests
from packaging import version as semver

GITHUB_API = "https://api.github.com"
IGNORED_CONTRIBUTORS = ['abi-git-user', 'github-actions[bot]']

HEADERS = {
"Accept": "application/vnd.github+json",
"Authorization": f"Bearer {os.environ['GH_TOKEN']}"
}

TAG_PATTERN = re.compile(r"^source-v(\d+\.\d+\.\d+)$")
LABEL_PRIORITY = []


class Contributor:

def __init__(self, avatar_url, user_url):
self._avatar_url = avatar_url
self._user_url = user_url

@property
def avatar_url(self):
return self._avatar_url

@property
def user_url(self):
return self._user_url

def __eq__(self, other):
return isinstance(other, Contributor) and \
self.user_url == other.user_url and \
self.avatar_url == other.avatar_url

def __hash__(self):
return hash((self.user_url, self.avatar_url))


def run(cmd):
return subprocess.check_output(cmd, text=True).strip()


def find_previous_source_tag(end_tag):
tags = run(["git", "tag"]).splitlines()
valid = []

m = None if end_tag == "HEAD" else TAG_PATTERN.match(end_tag)
end_version = semver.parse(m.group(1)) if m else None

for t in tags:
m = TAG_PATTERN.match(t)
if not m:
continue

v = semver.parse(m.group(1))
if end_version and v >= semver.parse(end_version):
continue

valid.append((v, t))

if not valid:
raise RuntimeError("No valid source-vX.Y.Z tags found")

valid.sort(reverse=True)
return valid[0][1]


def get_merge_commits(start, end="HEAD"):
log = run([
"git", "log",
f"{start}..{end}",
"--merges",
"--first-parent",
"--pretty=%s"
])
return log.splitlines()


def extract_pr_numbers(messages):
prs = []
for msg in messages:
m = re.search(r"#(\d+)", msg)
if m:
prs.append(int(m.group(1)))
return prs


def fetch_pr(org_repo, pr_number):
url = f"{GITHUB_API}/repos/{org_repo}/pulls/{pr_number}"
r = requests.get(url, headers=HEADERS)
r.raise_for_status()
return r.json()


def choose_primary_label(labels):
names = [l["name"] for l in labels]
for p in LABEL_PRIORITY:
if p in names:
return p
return names[0] if names else "No category"


def extract_summary(pr):
primary_label = choose_primary_label(pr["labels"])
secondary_labels = [l["name"] for l in pr["labels"] if l["name"] != primary_label]
return {
"title": pr["title"],
"label": primary_label,
"secondary_labels": secondary_labels,
"number": pr["number"],
"url": pr["html_url"],
"user": pr["user"]["login"],
"user_url": pr["user"]["html_url"],
"avatar_url": pr["user"]["avatar_url"],
}


def write_out_to_changelog_file(sorted_summaries, tag_end):
current_label = ''
file_name = f'changelog_{tag_end}.rst'
with open(file_name, 'w') as f:

changelog_title = f'libCellML {tag_end} Changelog'
f.write(f'{changelog_title}\n')
f.write('=' * len(changelog_title))
f.write('\n')

contributors = []
for summary in sorted_summaries:
if current_label != summary['label']:
current_label = summary['label']
f.write(f'\n{current_label}\n')
f.write('-' * len(current_label))
f.write('\n\n')

title = summary['title'][:-1] if summary['title'].endswith('.') else summary['title']
user_link = f'`@{summary["user"]} <{summary["user_url"]}>`_'
pr_link = f'`#{summary["number"]} <{summary["url"]}>`_'
suffix = ""
if summary.get("secondary_labels"):
suffix = f" (also: {', '.join(summary['secondary_labels'])})"
f.write(f'* {title}{suffix} by {user_link} [{pr_link}].\n')
contributors.append(Contributor(summary['avatar_url'], summary['user_url']))

contributors = list(set(contributors))
if contributors:
section_title = 'Contributors'
f.write(f'\n{section_title}\n')
f.write('-' * len(section_title))
f.write('\n\n')
for contributor in contributors:
f.write(f'.. image:: {contributor.avatar_url}\n :target: {contributor.user_url}'
f'\n :height: 32\n :width: 32\n')

print(f'Changelog written to: {file_name}.')


def process_arguments():
parser = argparse.ArgumentParser(description="Create a simple change log from merged pull requests from a GitHub "
"project.")
parser.add_argument("-p", "--project",
help="GitHub project to work with, default 'cellml/libcellml'.", default="cellml/libcellml")
# parser.add_argument("-r", "--local-repo",
# help="The location of the project repository. Absolute path or relative path relative to the "
# "current working directory.", default=None)
parser.add_argument("-t", "--tag-end-display-name",
help="Override the tag end label display name.", default=None)
parser.add_argument("tag_start", nargs='?', default="PREV",)
parser.add_argument("tag_end", nargs='?', default="HEAD")

return parser.parse_args()


if __name__ == "__main__":
args = process_arguments()
previous_source_tag = find_previous_source_tag(args.tag_end) if args.tag_start == "PREV" else args.tag_start
messages = get_merge_commits(previous_source_tag)
pr_numbers = extract_pr_numbers(messages)

summaries = []
for pr_number in pr_numbers:
pr = fetch_pr(args.project, pr_number)

if pr["user"]["login"] in IGNORED_CONTRIBUTORS:
continue

if pr["merged"]:
summaries.append(extract_summary(pr))

sorted_summaries = sorted(summaries, key=lambda x: x["label"])

tag_end_label = "latest" if args.tag_end == "HEAD" else f"v{args.tag_end}"
tag_end_label = tag_end_label if args.tag_end_display_name is None else args.tag_end_display_name
write_out_to_changelog_file(sorted_summaries, tag_end=tag_end_label)
39 changes: 39 additions & 0 deletions .github/scripts/set_version.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import argparse
import os
import subprocess

here = os.path.abspath(os.path.dirname(__file__))


def run_set_version(location, core, developer):
current_dir = os.path.abspath(os.path.curdir)
working_dir = os.path.join(current_dir, location)
os.chdir(working_dir)

subprocess.call(f"{os.path.join(here, 'set_version.sh')} {core} {developer}", shell=True)

os.chdir(current_dir)


def _process_arguments():
parser = argparse.ArgumentParser(description="Update all version numbers in the libCellML codebase.",
prefix_chars='/')
parser.add_argument("directory",
help="Relative directory from current working directory"
" to directory of libCellML source code.")
parser.add_argument("core",
help="Core version number in the form X.Y.Z where X, Y, and Z are whole numbers.")
parser.add_argument("developer",
nargs="?", default="",
help="Developer version number in the form -{dev|rc}.W where W is a whole number.")

return parser.parse_args()


def main():
args = _process_arguments()
run_set_version(args.directory, args.core, args.developer)


if __name__ == "__main__":
main()
31 changes: 31 additions & 0 deletions .github/scripts/set_version.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#!/bin/bash

version=$1
developer_version=$2

IFS='.' read -ra version_array <<< "$version"
numeric_version=$(printf %02d "${version_array[@]}")
version_regex='[[:digit:]]*\.[[:digit:]]*\.[[:digit:]]*'

sed -i "s@ EXPECT_EQ(\"${version_regex}\", versionString);@ EXPECT_EQ(\"${version}\", versionString);@" tests/version/version.cpp
sed -i 's@ EXPECT_EQ(0x[[:digit:]]*U, version);@ EXPECT_EQ(0x'${numeric_version}'U, version);@' tests/version/version.cpp

sed -i 's/^set(_PROJECT_VERSION[^)]*)$/set(_PROJECT_VERSION '${version}')/' CMakeLists.txt
sed -i 's/^set(PROJECT_DEVELOPER_VERSION[^)]*)$/set(PROJECT_DEVELOPER_VERSION '${developer_version}')/' CMakeLists.txt

files=$(grep -rl "^LIBCELLML_VERSION = \"${version_regex}\"" .)
sed -i "s@LIBCELLML_VERSION = \"${version_regex}\"@LIBCELLML_VERSION = \"${version}\"@" $files

files=$(grep -rl "/\* The content of this file was generated using \(the\|a modified\) C profile of libCellML ${version_regex}\. \*/" *)
sed -i -E "s@/\* The content of this file was generated using (the|a modified) C profile of libCellML ${version_regex}\. \*/@/\* The content of this file was generated using \1 C profile of libCellML ${version}. \*/@" $files

files=$(grep -rl "# The content of this file was generated using \(the\|a modified\) Python profile of libCellML ${version_regex}\." *)
sed -i -E "s@# The content of this file was generated using (the|a modified) Python profile of libCellML ${version_regex}\.@# The content of this file was generated using \1 Python profile of libCellML ${version}.@" $files

files=$(grep -rl "const char LIBCELLML_VERSION\[\] = \"${version_regex}\";" *)
sed -i "s@const char LIBCELLML_VERSION\[\] = \"${version_regex}\";@const char LIBCELLML_VERSION\[\] = \"${version}\";@" $files

files=$(grep -rl " expect(libcellml\.versionString()).toBe('${version_regex}');" *)
sed -i "s@ expect(libcellml\.versionString()).toBe('${version_regex}');@ expect(libcellml\.versionString()).toBe('${version}');@" $files

exit 0
Loading
Loading