Skip to content

Commit 68c421b

Browse files
Merge branch 'develop' into calibrate-impact-functions
# Conflicts: # script/jenkins/branches/Jenkinsfile # tests_runner.py
2 parents e1fe68a + d6897ae commit 68c421b

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

63 files changed

+1498
-627
lines changed

.coveragerc

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# .coveragerc to control coverage.py
2+
3+
[run]
4+
# Also report branch coverage
5+
branch = True
6+
# Set concurrency type for correct coverage of multi-processing code
7+
concurrency = multiprocessing
8+
9+
[paths]
10+
source = climada/
11+
12+
[report]
13+
# Regexes for lines to exclude from consideration
14+
exclude_also =
15+
# Main code is not run
16+
if __name__ == .__main__.:
17+
18+
# Abtract methods are not run
19+
@(abc\.)?abstractmethod
20+
21+
# Never fail when reporting
22+
ignore_errors = True
23+
24+
[html]
25+
directory = coverage

.github/scripts/make_release.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
#!/usr/bin/env python3
2+
"""This script is part of the GitHub CI make-release pipeline
3+
4+
It reads the version number from climada/_version.py and then uses the `gh` cli
5+
to create the new release.
6+
7+
"""
8+
import glob
9+
import re
10+
import subprocess
11+
12+
13+
def get_version() -> str:
14+
"""Return the current version number, based on the _version.py file."""
15+
[version_file] = glob.glob("climada*/_version.py")
16+
with open(version_file, 'r', encoding="UTF-8") as vfp:
17+
content = vfp.read()
18+
regex = r'^__version__\s*=\s*[\'\"](.*)[\'\"]\s*$'
19+
mtch = re.match(regex, content)
20+
return mtch.group(1)
21+
22+
23+
def make_release():
24+
"""run `gh release create vX.Y.Z"""
25+
version_number = get_version()
26+
subprocess.run(
27+
["gh", "release", "create", "--generate-notes", f"v{version_number}"],
28+
check=True,
29+
)
30+
31+
32+
if __name__ == "__main__":
33+
make_release()

.github/scripts/prepare_release.py

Lines changed: 232 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
1+
#!/usr/bin/env python3
2+
"""This script is part of the GitHub CI make-release pipeline
3+
4+
The following preparation steps are executed:
5+
6+
- update version numbers in _version.py and setup.py
7+
- purge the "Unreleased" section of CHANGELOG.md and rename it to the new version number
8+
- copy the README.md file to doc/misc/README.md,
9+
but without the badges as they interfere with the sphinx doc builder
10+
11+
All changes are immediately commited to the repository.
12+
"""
13+
14+
import glob
15+
import json
16+
import re
17+
import subprocess
18+
import time
19+
20+
21+
def get_last_version() -> str:
22+
"""Return the version number of the last release."""
23+
json_string = (
24+
subprocess.run(
25+
["gh", "release", "view", "--json", "tagName"],
26+
check=True,
27+
stdout=subprocess.PIPE,
28+
stderr=subprocess.PIPE,
29+
)
30+
.stdout.decode("utf8")
31+
.strip()
32+
)
33+
34+
return json.loads(json_string)["tagName"]
35+
36+
37+
def bump_version_number(version_number: str, level: str) -> str:
38+
"""Return a copy of `version_number` with one level number incremented."""
39+
major, minor, patch = version_number.split(".")
40+
if level == "major":
41+
major = str(int(major)+1)
42+
elif level == "minor":
43+
minor = str(int(minor)+1)
44+
elif level == "patch":
45+
patch = str(int(patch)+1)
46+
else:
47+
raise ValueError(f"level should be 'major', 'minor' or 'patch', not {level}")
48+
return ".".join([major, minor, patch])
49+
50+
51+
def update_readme(_nvn):
52+
"""align doc/misc/README.md with ./README.md but remove the non-markdown header lines from """
53+
with open("README.md", 'r', encoding="UTF-8") as rmin:
54+
lines = [line for line in rmin.readlines() if not line.startswith('[![')]
55+
while not lines[0].strip():
56+
lines = lines[1:]
57+
with open("doc/misc/README.md", 'w', encoding="UTF-8") as rmout:
58+
rmout.writelines(lines)
59+
return GitFile('doc/misc/README.md')
60+
61+
62+
def update_changelog(nvn):
63+
"""Rename the "Unreleased" section, remove unused subsections and the code-freeze date,
64+
set the release date to today"""
65+
releases = []
66+
release_name = None
67+
release = []
68+
section_name = None
69+
section = []
70+
with open("CHANGELOG.md", 'r', encoding="UTF-8") as changelog:
71+
for line in changelog.readlines():
72+
if line.startswith('#'):
73+
if line.startswith('### '):
74+
if section:
75+
release.append((section_name, section))
76+
section_name = line[4:].strip()
77+
section = []
78+
#print("tag:", section_name)
79+
elif line.startswith('## '):
80+
if section:
81+
release.append((section_name, section))
82+
if release:
83+
releases.append((release_name, release))
84+
release_name = line[3:].strip()
85+
release = []
86+
section_name = None
87+
section = []
88+
#print("release:", release_name)
89+
else:
90+
section.append(line)
91+
if section:
92+
release.append((section_name, section))
93+
if release:
94+
releases.append((release_name, release))
95+
96+
with open("CHANGELOG.md", 'w', encoding="UTF-8") as changelog:
97+
changelog.write("# Changelog\n\n")
98+
for release_name, release in releases:
99+
if release_name:
100+
if release_name.lower() == "unreleased":
101+
release_name = nvn
102+
changelog.write(f"## {release_name}\n")
103+
for section_name, section in release:
104+
if any(ln.strip() for ln in section):
105+
if section_name:
106+
changelog.write(f"### {section_name}\n")
107+
lines = [ln.strip() for ln in section if "code freeze date: " not in ln.lower()]
108+
if not section_name and release_name.lower() == nvn:
109+
print("setting date")
110+
for i, line in enumerate(lines):
111+
if "release date: " in line.lower():
112+
today = time.strftime("%Y-%m-%d")
113+
lines[i] = f"Release date: {today}"
114+
changelog.write("\n".join(lines).replace("\n\n", "\n"))
115+
changelog.write("\n")
116+
return GitFile('CHANGELOG.md')
117+
118+
119+
def update_version(nvn):
120+
"""Update the _version.py file"""
121+
[file_with_version] = glob.glob("climada*/_version.py")
122+
regex = r'(^__version__\s*=\s*[\'\"]).*([\'\"]\s*$)'
123+
return update_file(file_with_version, regex, nvn)
124+
125+
126+
def update_setup(new_version_number):
127+
"""Update the setup.py file"""
128+
file_with_version = "setup.py"
129+
regex = r'(^\s+version\s*=\s*[\'\"]).*([\'\"]\s*,\s*$)'
130+
return update_file(file_with_version, regex, new_version_number)
131+
132+
133+
def update_file(file_with_version, regex, new_version_number):
134+
"""Replace the version number(s) in a file, based on a rgular expression."""
135+
with open(file_with_version, 'r', encoding="UTF-8") as curf:
136+
lines = curf.readlines()
137+
successfully_updated = False
138+
for i, line in enumerate(lines):
139+
mtch = re.match(regex, line)
140+
if mtch:
141+
lines[i] = f"{mtch.group(1)}{new_version_number}{mtch.group(2)}"
142+
successfully_updated = True
143+
if not successfully_updated:
144+
raise RuntimeError(f"cannot determine version of {file_with_version}")
145+
with open(file_with_version, 'w', encoding="UTF-8") as newf:
146+
for line in lines:
147+
newf.write(line)
148+
return GitFile(file_with_version)
149+
150+
151+
class GitFile():
152+
"""Helper class for `git add`."""
153+
def __init__(self, path):
154+
self.path = path
155+
156+
def gitadd(self):
157+
"""run `git add`"""
158+
_gitadd = subprocess.run(
159+
["git", "add", self.path],
160+
check=True,
161+
stdout=subprocess.PIPE,
162+
stderr=subprocess.PIPE,
163+
).stdout.decode("utf8")
164+
165+
166+
class Git():
167+
"""Helper class for `git commit`."""
168+
def __init__(self):
169+
_gitname = subprocess.run(
170+
["git", "config", "--global", "user.name", "'climada'"],
171+
check=True,
172+
stdout=subprocess.PIPE,
173+
stderr=subprocess.PIPE,
174+
).stdout.decode("utf8")
175+
_gitemail = subprocess.run(
176+
["git", "config", "--global", "user.email", "'[email protected]'"],
177+
check=True,
178+
stdout=subprocess.PIPE,
179+
stderr=subprocess.PIPE,
180+
).stdout.decode("utf8")
181+
182+
def commit(self, new_version):
183+
"""run `git commit`."""
184+
try:
185+
_gitcommit = subprocess.run(
186+
["git", "commit", "-m", f"'Automated update v{new_version}'"],
187+
check=True,
188+
stdout=subprocess.PIPE,
189+
stderr=subprocess.PIPE,
190+
).stdout.decode("utf8")
191+
_gitpush = subprocess.run(
192+
["git", "push"],
193+
check=True,
194+
stdout=subprocess.PIPE,
195+
stderr=subprocess.PIPE,
196+
).stdout.decode("utf8")
197+
except subprocess.CalledProcessError as err:
198+
message = err.stdout.decode("utf8")
199+
print("message:", message)
200+
if "nothing to commit" in message:
201+
print("repo already up to date with new version number")
202+
else:
203+
raise RuntimeError(f"failed to run: {message}") from err
204+
205+
206+
def prepare_new_release(level):
207+
"""Prepare files for a new release on GitHub."""
208+
try:
209+
last_version_number = get_last_version().strip("v")
210+
except subprocess.CalledProcessError as err:
211+
if "release not found" in err.stderr.decode("utf8"):
212+
# The project doesn't have any releases yet.
213+
last_version_number = "0.0.0"
214+
else:
215+
raise
216+
new_version_number = bump_version_number(last_version_number, level)
217+
218+
update_setup(new_version_number).gitadd()
219+
update_version(new_version_number).gitadd()
220+
update_changelog(new_version_number).gitadd()
221+
update_readme(new_version_number).gitadd()
222+
223+
Git().commit(new_version_number)
224+
225+
226+
if __name__ == "__main__":
227+
from sys import argv
228+
try:
229+
LEVEL = argv[1]
230+
except IndexError:
231+
LEVEL = "patch"
232+
prepare_new_release(LEVEL)

0 commit comments

Comments
 (0)