Skip to content

Commit 6754ba7

Browse files
insert release pipline files
1 parent 4d8fa43 commit 6754ba7

File tree

8 files changed

+513
-0
lines changed

8 files changed

+513
-0
lines changed

.github/scripts/make_release.py

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

.github/scripts/prepare_release.py

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

.github/scripts/setup_devbranch.py

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
#!/usr/bin/env python3
2+
import glob
3+
import json
4+
import re
5+
import subprocess
6+
7+
8+
def get_last_version() -> str:
9+
"""Return the version number of the last release."""
10+
json_string = (
11+
subprocess.run(
12+
["gh", "release", "view", "--json", "tagName"],
13+
check=True,
14+
stdout=subprocess.PIPE,
15+
stderr=subprocess.PIPE,
16+
)
17+
.stdout.decode("utf8")
18+
.strip()
19+
)
20+
21+
return json.loads(json_string)["tagName"]
22+
23+
24+
def update_changelog():
25+
"""Insert a vanilla "Unreleased" section on top."""
26+
with open("CHANGELOG.md", 'r') as cl:
27+
lines = cl.readlines()
28+
29+
if "## Unreleased" in lines:
30+
return
31+
32+
with open("CHANGELOG.md", 'w') as cl:
33+
cl.write("""# Changelog
34+
35+
## Unreleased
36+
37+
Release date: YYYY-MM-DD
38+
39+
Code freeze date: YYYY-MM-DD
40+
41+
### Description
42+
43+
### Dependency Changes
44+
45+
### Added
46+
47+
### Changed
48+
49+
### Fixed
50+
51+
### Deprecated
52+
53+
### Removed
54+
55+
""")
56+
cl.writelines(lines[2:])
57+
58+
59+
def update_version(nvn):
60+
"""Update the _version.py file"""
61+
[file_with_version] = glob.glob("climada*/_version.py")
62+
regex = r'(^__version__\s*=\s*[\'\"]).*([\'\"]\s*$)'
63+
return update_file(file_with_version, regex, nvn)
64+
65+
66+
def update_setup(new_version_number):
67+
"""Update the setup.py file"""
68+
file_with_version = "setup.py"
69+
regex = r'(^\s+version\s*=\s*[\'\"]).*([\'\"]\s*,\s*$)'
70+
return update_file(file_with_version, regex, new_version_number)
71+
72+
73+
def update_file(file_with_version, regex, new_version_number):
74+
"""Replace the version number(s) in a file, based on a rgular expression."""
75+
with open(file_with_version, 'r') as curf:
76+
lines = curf.readlines()
77+
successfully_updated = False
78+
for i, line in enumerate(lines):
79+
m = re.match(regex, line)
80+
if m:
81+
lines[i] = f"{m.group(1)}{new_version_number}{m.group(2)}"
82+
successfully_updated = True
83+
if not successfully_updated:
84+
raise Exception(f"cannot determine version of {file_with_version}")
85+
with open(file_with_version, 'w') as newf:
86+
for line in lines:
87+
newf.write(line)
88+
89+
90+
def setup_devbranch():
91+
"""Adjust files after a release was published, i.e.,
92+
apply the canonical deviations from main in develop.
93+
94+
Just changes files, all `git` commands are in the setup_devbranch.sh file.
95+
"""
96+
main_version = get_last_version().strip('v')
97+
98+
dev_version = f"{main_version}-dev"
99+
100+
update_setup(dev_version)
101+
update_version(dev_version)
102+
update_changelog()
103+
104+
print(f"v{dev_version}")
105+
106+
107+
if __name__ == "__main__":
108+
setup_devbranch()
109+

.github/scripts/setup_devbranch.sh

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
#!/usr/bin/bash
2+
3+
set -e
4+
5+
git config --global user.name 'climada'
6+
git config --global user.email '[email protected]'
7+
8+
git fetch --unshallow || echo cannot \"git fetch --unshallow \"
9+
git checkout develop
10+
git pull
11+
12+
git checkout origin/main \
13+
setup.py \
14+
doc/misc/README.md \
15+
CHANGELOG.md \
16+
*/_version.py
17+
18+
release=`python .github/scripts/setup_devbranch.py`
19+
20+
git add \
21+
setup.py \
22+
doc/misc/README.md \
23+
CHANGELOG.md \
24+
*/_version.py
25+
26+
git commit -m "setup develop branch for $release"
27+
28+
git push || echo cannot \"git push\"
29+
git checkout main

0 commit comments

Comments
 (0)