Skip to content

Commit

Permalink
Merge branch 'main' into develop
Browse files Browse the repository at this point in the history
# Conflicts:
#	requirements/env_climada.yml
  • Loading branch information
emanuel-schmid committed Jun 21, 2023
2 parents 5d54dba + 47b80ad commit 09a9ecd
Show file tree
Hide file tree
Showing 7 changed files with 512 additions and 0 deletions.
33 changes: 33 additions & 0 deletions .github/scripts/make_release.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#!/usr/bin/env python3
"""This script is part of the GitHub CI make-release pipeline
It reads the version number from climada/_version.py and then uses the `gh` cli
to create the new release.
"""
import glob
import re
import subprocess


def get_version() -> str:
"""Return the current version number, based on the _version.py file."""
[version_file] = glob.glob("climada*/_version.py")
with open(version_file, 'r', encoding="UTF-8") as vfp:
content = vfp.read()
regex = r'^__version__\s*=\s*[\'\"](.*)[\'\"]\s*$'
mtch = re.match(regex, content)
return mtch.group(1)


def make_release():
"""run `gh release create vX.Y.Z"""
version_number = get_version()
subprocess.run(
["gh", "release", "create", "--generate-notes", f"v{version_number}"],
check=True,
)


if __name__ == "__main__":
make_release()
232 changes: 232 additions & 0 deletions .github/scripts/prepare_release.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
#!/usr/bin/env python3
"""This script is part of the GitHub CI make-release pipeline
The following preparation steps are executed:
- update version numbers in _version.py and setup.py
- purge the "Unreleased" section of CHANGELOG.md and rename it to the new version number
- copy the README.md file to doc/misc/README.md,
but without the badges as they interfere with the sphinx doc builder
All changes are immediately commited to the repository.
"""

import glob
import json
import re
import subprocess
import time


def get_last_version() -> str:
"""Return the version number of the last release."""
json_string = (
subprocess.run(
["gh", "release", "view", "--json", "tagName"],
check=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
.stdout.decode("utf8")
.strip()
)

return json.loads(json_string)["tagName"]


def bump_version_number(version_number: str, level: str) -> str:
"""Return a copy of `version_number` with one level number incremented."""
major, minor, patch = version_number.split(".")
if level == "major":
major = str(int(major)+1)
elif level == "minor":
minor = str(int(minor)+1)
elif level == "patch":
patch = str(int(patch)+1)
else:
raise ValueError(f"level should be 'major', 'minor' or 'patch', not {level}")
return ".".join([major, minor, patch])


def update_readme(_nvn):
"""align doc/misc/README.md with ./README.md but remove the non-markdown header lines from """
with open("README.md", 'r', encoding="UTF-8") as rmin:
lines = [line for line in rmin.readlines() if not line.startswith('[![')]
while not lines[0].strip():
lines = lines[1:]
with open("doc/misc/README.md", 'w', encoding="UTF-8") as rmout:
rmout.writelines(lines)
return GitFile('doc/misc/README.md')


def update_changelog(nvn):
"""Rename the "Unreleased" section, remove unused subsections and the code-freeze date,
set the release date to today"""
releases = []
release_name = None
release = []
section_name = None
section = []
with open("CHANGELOG.md", 'r', encoding="UTF-8") as changelog:
for line in changelog.readlines():
if line.startswith('#'):
if line.startswith('### '):
if section:
release.append((section_name, section))
section_name = line[4:].strip()
section = []
#print("tag:", section_name)
elif line.startswith('## '):
if section:
release.append((section_name, section))
if release:
releases.append((release_name, release))
release_name = line[3:].strip()
release = []
section_name = None
section = []
#print("release:", release_name)
else:
section.append(line)
if section:
release.append((section_name, section))
if release:
releases.append((release_name, release))

with open("CHANGELOG.md", 'w', encoding="UTF-8") as changelog:
changelog.write("# Changelog\n\n")
for release_name, release in releases:
if release_name:
if release_name.lower() == "unreleased":
release_name = nvn
changelog.write(f"## {release_name}\n")
for section_name, section in release:
if any(ln.strip() for ln in section):
if section_name:
changelog.write(f"### {section_name}\n")
lines = [ln.strip() for ln in section if "code freeze date: " not in ln.lower()]
if not section_name and release_name.lower() == nvn:
print("setting date")
for i, line in enumerate(lines):
if "release date: " in line.lower():
today = time.strftime("%Y-%m-%d")
lines[i] = f"Release date: {today}"
changelog.write("\n".join(lines).replace("\n\n", "\n"))
changelog.write("\n")
return GitFile('CHANGELOG.md')


def update_version(nvn):
"""Update the _version.py file"""
[file_with_version] = glob.glob("climada*/_version.py")
regex = r'(^__version__\s*=\s*[\'\"]).*([\'\"]\s*$)'
return update_file(file_with_version, regex, nvn)


def update_setup(new_version_number):
"""Update the setup.py file"""
file_with_version = "setup.py"
regex = r'(^\s+version\s*=\s*[\'\"]).*([\'\"]\s*,\s*$)'
return update_file(file_with_version, regex, new_version_number)


def update_file(file_with_version, regex, new_version_number):
"""Replace the version number(s) in a file, based on a rgular expression."""
with open(file_with_version, 'r', encoding="UTF-8") as curf:
lines = curf.readlines()
successfully_updated = False
for i, line in enumerate(lines):
mtch = re.match(regex, line)
if mtch:
lines[i] = f"{mtch.group(1)}{new_version_number}{mtch.group(2)}"
successfully_updated = True
if not successfully_updated:
raise RuntimeError(f"cannot determine version of {file_with_version}")
with open(file_with_version, 'w', encoding="UTF-8") as newf:
for line in lines:
newf.write(line)
return GitFile(file_with_version)


class GitFile():
"""Helper class for `git add`."""
def __init__(self, path):
self.path = path

def gitadd(self):
"""run `git add`"""
_gitadd = subprocess.run(
["git", "add", self.path],
check=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
).stdout.decode("utf8")


class Git():
"""Helper class for `git commit`."""
def __init__(self):
_gitname = subprocess.run(
["git", "config", "--global", "user.name", "'climada'"],
check=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
).stdout.decode("utf8")
_gitemail = subprocess.run(
["git", "config", "--global", "user.email", "'test.climada@gmail.com'"],
check=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
).stdout.decode("utf8")

def commit(self, new_version):
"""run `git commit`."""
try:
_gitcommit = subprocess.run(
["git", "commit", "-m", f"'Automated update v{new_version}'"],
check=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
).stdout.decode("utf8")
_gitpush = subprocess.run(
["git", "push"],
check=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
).stdout.decode("utf8")
except subprocess.CalledProcessError as err:
message = err.stdout.decode("utf8")
print("message:", message)
if "nothing to commit" in message:
print("repo already up to date with new version number")
else:
raise RuntimeError(f"failed to run: {message}") from err


def prepare_new_release(level):
"""Prepare files for a new release on GitHub."""
try:
last_version_number = get_last_version().strip("v")
except subprocess.CalledProcessError as err:
if "release not found" in err.stderr.decode("utf8"):
# The project doesn't have any releases yet.
last_version_number = "0.0.0"
else:
raise
new_version_number = bump_version_number(last_version_number, level)

update_setup(new_version_number).gitadd()
update_version(new_version_number).gitadd()
update_changelog(new_version_number).gitadd()
update_readme(new_version_number).gitadd()

Git().commit(new_version_number)


if __name__ == "__main__":
from sys import argv
try:
LEVEL = argv[1]
except IndexError:
LEVEL = "patch"
prepare_new_release(LEVEL)
118 changes: 118 additions & 0 deletions .github/scripts/setup_devbranch.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
#!/usr/bin/env python3
"""This script is part of the GitHub CI postrelease-setup-devbranch pipeline
The following preparation steps are executed:
- update version numbers in _version.py and setup.py: append a -dev suffix
- insert a vanilla "unreleased" section on top of CHANGELOG.md
The changes are not commited to the repository. This is dealt with in the bash script
`setup_devbranch.sh` (which is also the caller of this script).
"""
import glob
import json
import re
import subprocess


def get_last_version() -> str:
"""Return the version number of the last release."""
json_string = (
subprocess.run(
["gh", "release", "view", "--json", "tagName"],
check=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
.stdout.decode("utf8")
.strip()
)

return json.loads(json_string)["tagName"]


def update_changelog():
"""Insert a vanilla "Unreleased" section on top."""
with open("CHANGELOG.md", 'r', encoding="UTF-8") as changelog:
lines = changelog.readlines()

if "## Unreleased" in lines:
return

with open("CHANGELOG.md", 'w', encoding="UTF-8") as changelog:
changelog.write("""# Changelog
## Unreleased
Release date: YYYY-MM-DD
Code freeze date: YYYY-MM-DD
### Description
### Dependency Changes
### Added
### Changed
### Fixed
### Deprecated
### Removed
""")
changelog.writelines(lines[2:])


def update_version(nvn):
"""Update the _version.py file"""
[file_with_version] = glob.glob("climada*/_version.py")
regex = r'(^__version__\s*=\s*[\'\"]).*([\'\"]\s*$)'
return update_file(file_with_version, regex, nvn)


def update_setup(new_version_number):
"""Update the setup.py file"""
file_with_version = "setup.py"
regex = r'(^\s+version\s*=\s*[\'\"]).*([\'\"]\s*,\s*$)'
return update_file(file_with_version, regex, new_version_number)


def update_file(file_with_version, regex, new_version_number):
"""Replace the version number(s) in a file, based on a rgular expression."""
with open(file_with_version, 'r', encoding="UTF-8") as curf:
lines = curf.readlines()
successfully_updated = False
for i, line in enumerate(lines):
mtch = re.match(regex, line)
if mtch:
lines[i] = f"{mtch.group(1)}{new_version_number}{mtch.group(2)}"
successfully_updated = True
if not successfully_updated:
raise RuntimeError(f"cannot determine version of {file_with_version}")
with open(file_with_version, 'w', encoding="UTF-8") as newf:
for line in lines:
newf.write(line)


def setup_devbranch():
"""Adjust files after a release was published, i.e.,
apply the canonical deviations from main in develop.
Just changes files, all `git` commands are in the setup_devbranch.sh file.
"""
main_version = get_last_version().strip('v')

dev_version = f"{main_version}-dev"

update_setup(dev_version)
update_version(dev_version)
update_changelog()

print(f"v{dev_version}")


if __name__ == "__main__":
setup_devbranch()
Loading

0 comments on commit 09a9ecd

Please sign in to comment.