Skip to content

Commit ab73ed6

Browse files
authored
ci(release): add automated release workflow and utility scripts (MODFLOW-ORG#18)
1 parent afc08df commit ab73ed6

File tree

5 files changed

+494
-0
lines changed

5 files changed

+494
-0
lines changed

.github/workflows/release.yml

Lines changed: 273 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,273 @@
1+
name: Release
2+
on:
3+
push:
4+
branches:
5+
- main
6+
- v[0-9]+.[0-9]+.[0-9]+*
7+
release:
8+
types:
9+
- published
10+
jobs:
11+
prep:
12+
name: Prepare release
13+
runs-on: ubuntu-latest
14+
if: ${{ github.event_name == 'push' && github.ref_name != 'main' }}
15+
permissions:
16+
contents: write
17+
pull-requests: write
18+
defaults:
19+
run:
20+
shell: bash
21+
steps:
22+
23+
- name: Checkout release branch
24+
uses: actions/checkout@v3
25+
with:
26+
fetch-depth: 0
27+
28+
- name: Setup Python
29+
uses: actions/setup-python@v4
30+
with:
31+
python-version: 3.8
32+
cache: 'pip'
33+
cache-dependency-path: pyproject.toml
34+
35+
- name: Install Python dependencies
36+
run: |
37+
pip install --upgrade pip
38+
pip install build twine
39+
pip install .
40+
pip install ".[lint, test]"
41+
42+
- name: Update version
43+
id: version
44+
run: |
45+
ref="${{ github.ref_name }}"
46+
version="${ref#"v"}"
47+
python scripts/update_version.py -v "$version"
48+
python -c "import modflowapi; print('Version: ', modflowapi.__version__)"
49+
echo "version=$version" >> $GITHUB_OUTPUT
50+
51+
- name: Touch changelog
52+
run: touch HISTORY.md
53+
54+
- name: Generate changelog
55+
id: cliff
56+
uses: orhun/git-cliff-action@v1
57+
with:
58+
config: cliff.toml
59+
args: --verbose --unreleased --tag ${{ steps.version.outputs.version }}
60+
env:
61+
OUTPUT: CHANGELOG.md
62+
63+
- name: Update changelog
64+
id: update-changelog
65+
run: |
66+
# move changelog
67+
clog="CHANGELOG_${{ steps.version.outputs.version }}.md"
68+
echo "changelog=$clog" >> $GITHUB_OUTPUT
69+
sudo cp "${{ steps.cliff.outputs.changelog }}" "$clog"
70+
71+
# show current release changelog
72+
cat "$clog"
73+
74+
# substitute full group names
75+
sed -i 's/#### Ci/#### Continuous integration/' "$clog"
76+
sed -i 's/#### Feat/#### New features/' "$clog"
77+
sed -i 's/#### Fix/#### Bug fixes/' "$clog"
78+
sed -i 's/#### Refactor/#### Refactoring/' "$clog"
79+
sed -i 's/#### Test/#### Testing/' "$clog"
80+
81+
cat "$clog" HISTORY.md > temp_history.md
82+
sudo mv temp_history.md HISTORY.md
83+
84+
# show full changelog
85+
cat HISTORY.md
86+
87+
- name: Upload changelog
88+
uses: actions/upload-artifact@v3
89+
with:
90+
name: changelog
91+
path: ${{ steps.update-changelog.outputs.changelog }}
92+
93+
- name: Format Python files
94+
run: python scripts/pull_request_prepare.py
95+
96+
- name: Push release branch
97+
env:
98+
GITHUB_TOKEN: ${{ github.token }}
99+
run: |
100+
ver="${{ steps.version.outputs.version }}"
101+
changelog=$(cat ${{ steps.update-changelog.outputs.changelog }} | grep -v "### Version $ver")
102+
103+
# remove this release's changelog so we don't commit it
104+
# the changes have already been prepended to HISTORY.md
105+
rm ${{ steps.update-changelog.outputs.changelog }}
106+
rm -f CHANGELOG.md
107+
108+
# commit and push changes
109+
git config core.sharedRepository true
110+
git config user.name "github-actions[bot]"
111+
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
112+
git add -A
113+
git commit -m "ci(release): set version to ${{ steps.version.outputs.version }}, update changelog"
114+
git push origin "${{ github.ref_name }}"
115+
116+
title="Release $ver"
117+
body='
118+
# Release '$ver'
119+
120+
The release can be approved by merging this pull request into `main`. This will trigger jobs to publish the release to PyPI and reset `develop` from `main`, incrementing the minor version number.
121+
122+
## Changelog
123+
124+
'$changelog'
125+
'
126+
gh pr create -B "main" -H "${{ github.ref_name }}" --title "$title" --draft --body "$body"
127+
128+
release:
129+
name: Draft release
130+
# runs only when changes are merged to main
131+
if: ${{ github.event_name == 'push' && github.ref_name == 'main' }}
132+
runs-on: ubuntu-latest
133+
permissions:
134+
contents: write
135+
pull-requests: write
136+
steps:
137+
138+
- name: Checkout repo
139+
uses: actions/checkout@v3
140+
with:
141+
ref: main
142+
143+
- name: Setup Python
144+
uses: actions/setup-python@v4
145+
with:
146+
python-version: 3.8
147+
148+
- name: Install Python dependencies
149+
run: |
150+
pip install --upgrade pip
151+
pip install .
152+
pip install ".[test]"
153+
154+
# actions/download-artifact won't look at previous workflow runs but we need to in order to get changelog
155+
- name: Download artifacts
156+
uses: dawidd6/action-download-artifact@v2
157+
158+
- name: Draft release
159+
env:
160+
GITHUB_TOKEN: ${{ github.token }}
161+
run: |
162+
version=$(python scripts/update_version.py -g)
163+
title="modflowapi $version"
164+
notes=$(cat "changelog/CHANGELOG_$version.md" | grep -v "### Version $version")
165+
gh release create "$version" \
166+
--target main \
167+
--title "$title" \
168+
--notes "$notes" \
169+
--draft \
170+
--latest
171+
172+
publish:
173+
name: Publish package
174+
# runs only after release is published (manually promoted from draft)
175+
if: github.event_name == 'release' && github.repository_owner == 'MODFLOW-USGS'
176+
runs-on: ubuntu-22.04
177+
permissions:
178+
contents: write
179+
pull-requests: write
180+
steps:
181+
182+
- name: Checkout main branch
183+
uses: actions/checkout@v3
184+
with:
185+
ref: main
186+
187+
- name: Setup Python
188+
uses: actions/setup-python@v4
189+
with:
190+
python-version: 3.8
191+
192+
- name: Install Python dependencies
193+
run: |
194+
pip install --upgrade pip
195+
pip install build twine
196+
pip install .
197+
198+
- name: Build package
199+
run: python -m build
200+
201+
- name: Check package
202+
run: twine check --strict dist/*
203+
204+
- name: Publish package
205+
run: twine upload dist/*
206+
207+
reset:
208+
name: Draft reset PR
209+
if: ${{ github.event_name == 'release' }}
210+
runs-on: ubuntu-22.04
211+
permissions:
212+
contents: write
213+
pull-requests: write
214+
steps:
215+
216+
- name: Checkout main branch
217+
uses: actions/checkout@v3
218+
with:
219+
ref: main
220+
221+
- name: Setup Python
222+
uses: actions/setup-python@v4
223+
with:
224+
python-version: 3.8
225+
cache: 'pip'
226+
cache-dependency-path: pyproject.toml
227+
228+
- name: Install Python dependencies
229+
run: |
230+
pip install --upgrade pip
231+
pip install .
232+
pip install ".[lint, test]"
233+
234+
- name: Get release tag
235+
uses: oprypin/find-latest-tag@v1
236+
id: latest_tag
237+
with:
238+
repository: ${{ github.repository }}
239+
releases-only: true
240+
241+
- name: Draft pull request
242+
env:
243+
GITHUB_TOKEN: ${{ github.token }}
244+
run: |
245+
# create reset branch from main
246+
reset_branch="post-release-${{ steps.latest_tag.outputs.tag }}-reset"
247+
git switch -c $reset_branch
248+
249+
# increment minor version
250+
major_version=$(echo "${{ steps.latest_tag.outputs.tag }}" | cut -d. -f1)
251+
minor_version=$(echo "${{ steps.latest_tag.outputs.tag }}" | cut -d. -f2)
252+
patch_version=0
253+
version="$major_version.$((minor_version + 1)).$patch_version"
254+
python scripts/update_version.py -v "$version"
255+
256+
# format Python files
257+
python scripts/pull_request_prepare.py
258+
259+
# commit and push reset branch
260+
git config core.sharedRepository true
261+
git config user.name "github-actions[bot]"
262+
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
263+
git add -A
264+
git commit -m "ci(release): update to development version $version"
265+
git push -u origin $reset_branch
266+
267+
# create PR into develop
268+
body='
269+
# Reinitialize for development
270+
271+
Updates the `develop` branch from `main` following a successful release. Increments the minor version number.
272+
'
273+
gh pr create -B "develop" -H "$reset_branch" --title "Reinitialize develop branch" --draft --body "$body"

cliff.toml

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# configuration file for git-cliff (0.1.0)
2+
3+
[changelog]
4+
body = """
5+
{% if version %}\
6+
### Version {{ version | trim_start_matches(pat="v") }}
7+
{% else %}\
8+
### [unreleased]
9+
{% endif %}\
10+
{% for group, commits in commits | group_by(attribute="group") %}
11+
#### {{ group | upper_first }}
12+
{% for commit in commits %}
13+
* [{{ commit.group }}{% if commit.scope %}({{ commit.scope }}){% endif %}](https://github.com/MODFLOW-USGS/modflowapi/commit/{{ commit.id }}): {% if commit.breaking %}[**breaking**] {% endif %}{{ commit.message | upper_first }}. Committed by {{ commit.author.name }} on {{ commit.author.timestamp | date(format="%Y-%m-%d") }}.\
14+
{% endfor %}
15+
{% endfor %}\n
16+
"""
17+
trim = true
18+
19+
[git]
20+
conventional_commits = true
21+
filter_unconventional = true
22+
split_commits = false
23+
commit_parsers = [
24+
{ message = "^[fF]eat", group = "feat"},
25+
{ message = "^[fF]ix", group = "fix"},
26+
{ message = "^[bB]ug", group = "fix"},
27+
{ message = "^[pP]erf", group = "perf"},
28+
{ message = "^[rR]efactor", group = "refactor"},
29+
{ message = "^[uU]pdate", group = "refactor"},
30+
{ message = "^[aA]dd", group = "refactor"},
31+
{ message = "^[dD]oc.*", group = "docs", skip = true},
32+
{ message = "^[bB]inder", group = "docs", skip = true},
33+
{ message = "^[nN]otebook.*", group = "docs", skip = true},
34+
{ message = "^[rR][eE][aA][dD].*", group = "docs", skip = true},
35+
{ message = "^[sS]tyl.*", group = "style", skip = true},
36+
{ message = "^[tT]est.*", group = "test", skip = true},
37+
{ message = "^[cC][iI]", skip = true},
38+
{ message = "^[cC][iI]\\(release\\):", skip = true},
39+
{ message = "^[rR]elease", skip = true},
40+
{ message = "^[cC]hore", group = "chore", skip = true},
41+
]
42+
protect_breaking_commits = false
43+
filter_commits = false
44+
tag_pattern = "[0-9].[0-9].[0-9]"
45+
ignore_tags = ""
46+
sort_commits = "oldest"

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ test = [
4949
lint = [
5050
"black",
5151
"flake8",
52+
"isort",
5253
"pylint",
5354
]
5455

scripts/pull_request_prepare.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import os
2+
3+
try:
4+
import isort
5+
6+
print(f"isort version: {isort.__version__}")
7+
except ModuleNotFoundError:
8+
print("isort not installed\n\tInstall using pip install isort")
9+
10+
try:
11+
import black
12+
13+
print(f"black version: {black.__version__}")
14+
except ModuleNotFoundError:
15+
print("black not installed\n\tInstall using pip install black")
16+
17+
# uncomment if/when isort used
18+
# print("running isort...")
19+
# os.system("isort -v ../modflowapi")
20+
21+
print("running black...")
22+
os.system("black -v ../modflowapi")

0 commit comments

Comments
 (0)