Skip to content

Commit a22e68b

Browse files
feat: add workflow for releases (#312)
* releases: adds workflow for releases * releases: remove compressed artifacts * releases: use softprops/action-gh-release@v1 * releases: fix assets address * releases: adds 'write' permitions * releases: adds empty line at the end + remove 'generate release notes' * Update .github/workflows/release.yml Co-authored-by: Yagiz Nizipli <[email protected]> * Update .github/workflows/release.yml Co-authored-by: Yagiz Nizipli <[email protected]> * Update .github/workflows/release.yml Co-authored-by: Yagiz Nizipli <[email protected]> * Update .github/workflows/release.yml Co-authored-by: Yagiz Nizipli <[email protected]> * releases: apply review suggestions + adds singleheader.zip * releases: apply review suggestions + adds singleheader.zip * releases: change step description * release: WIPP release.py * releases: refactoring get_new_contributors * releases: pass last_release as a parameter * releases: iintroducing tests + create_release function * releases: introducing tests * releases: remove wrong 'This should fail' * releases: adds more tests + create release notes * releases: adds contruct_release_notes function + tests * releases: WIP update_cmakelists_version * releases: adds update doxygen and update ada_version.h + tests * releases: fix versions tests + adds folders structure * releases: create_release and update_versions as executables * releases: adds workflow_dispatch * releases: update releases.yml + use PyGithub to upload release artifacts * releases: update Release workflow * releases: fix typo in release workflow * releases: fix typos && NEXT_TAG * releases: debug 'push updates' * releases: fix release.yml * releases: switch from '.' to 'source' * releases: removes unnecessary singleheader.zio creation * releases: adds missing newline for versions.py * releases: adds 'required: true' to workflow_dispatch * releases: introducing release-script-tests.yml * releases: remove unnecessary workflow variables --------- Co-authored-by: Yagiz Nizipli <[email protected]>
1 parent d3aa7db commit a22e68b

19 files changed

+853
-0
lines changed
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
name: Release Script Tests
2+
3+
on:
4+
push:
5+
branches:
6+
- "*"
7+
pull_request:
8+
branches:
9+
- "*"
10+
11+
permissions:
12+
contents: write
13+
14+
jobs:
15+
release-script-test:
16+
runs-on: ubuntu-latest
17+
defaults:
18+
run:
19+
working-directory: ./tools/release
20+
21+
steps:
22+
- uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # v3.1.0
23+
- name: Install dependencies
24+
run: pip3 install -r requirements.txt
25+
- name: Run tests
26+
run: pytest -v

.github/workflows/release.yml

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
name: Release
2+
3+
on:
4+
workflow_dispatch:
5+
inputs:
6+
tag:
7+
type: string
8+
required: true
9+
description: "Tag for the next release. Ex.: v5.0.0"
10+
11+
env:
12+
NEXT_RELEASE_TAG: ${{ github.event.inputs.tag }}
13+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
14+
15+
permissions:
16+
contents: write
17+
18+
jobs:
19+
release-script-test:
20+
runs-on: ubuntu-latest
21+
defaults:
22+
run:
23+
working-directory: ./tools/release
24+
25+
steps:
26+
- uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # v3.1.0
27+
- name: Cache Python venv
28+
uses: actions/cache@v3
29+
with:
30+
path: ./tools/release/venv
31+
key: venv-${{ hashFiles('tools/release/requirements.txt') }}
32+
- name: Install dependencies
33+
run: |
34+
python3 -m venv venv
35+
source ./venv/bin/activate
36+
pip install -r requirements.txt
37+
- name: Run tests
38+
run: |
39+
source ./venv/bin/activate
40+
pytest -v
41+
42+
release:
43+
needs: release-script-test
44+
runs-on: ubuntu-latest
45+
if: ${{ needs.release-script-test.result == 'success' }}
46+
env:
47+
CXX: clang++-14
48+
steps:
49+
- uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # v3.1.0
50+
- name: Restore Python venv (Release script) from cache
51+
uses: actions/cache@v3
52+
with:
53+
path: ./tools/release/venv
54+
key: venv-${{ hashFiles('tools/release/requirements.txt') }}
55+
restore-keys: venv-${{ hashFiles('tools/release/requirements.txt') }}
56+
57+
- name: Update versions
58+
run: |
59+
source ./tools/release/venv/bin/activate
60+
./tools/release/update_versions.py
61+
62+
- name: Ada Build
63+
run: cmake -B build && cmake --build build
64+
- name: Ada Test
65+
run: ctest --output-on-failure --test-dir build
66+
67+
- name: Amalgamation
68+
run: |
69+
./singleheader/amalgamate.py
70+
71+
- name: Push updates for new release
72+
run: |
73+
git config --global user.email "[email protected]"
74+
git config --global user.name "GitHub Actions"
75+
76+
git add doxygen include/ada/ada_version.h CMakeLists.txt
77+
78+
git commit -m "New Release: ${{ env.NEXT_RELEASE_TAG }}"
79+
git push
80+
81+
- name: "Create Release"
82+
run: |
83+
source ./tools/release/venv/bin/activate
84+
./tools/release/create_release.py

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ build
44

55
# Python cache
66
__pycache__
7+
venv
78

89
cmake-build-debug
910

tools/release/__init__.py

Whitespace-only changes.

tools/release/create_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+
3+
import os
4+
from github import Github
5+
import lib.release as release
6+
7+
WORK_DIR = os.path.dirname(os.path.abspath(__file__)).replace("/tools/release", "")
8+
9+
NEXT_TAG = os.environ["NEXT_RELEASE_TAG"]
10+
REPO_NAME = os.environ["GITHUB_REPOSITORY"]
11+
TOKEN = os.environ["GITHUB_TOKEN"]
12+
if not NEXT_TAG or not REPO_NAME or not TOKEN:
13+
raise Exception(
14+
f"Bad environment variables. Invalid GITHUB_REPOSITORY, GITHUB_TOKEN or NEXT_RELEASE_TAG"
15+
)
16+
17+
g = Github(TOKEN)
18+
repo = g.get_repo(REPO_NAME)
19+
20+
release_notes = release.contruct_release_notes(repo, NEXT_TAG)
21+
22+
release.create_release(repo, NEXT_TAG, release_notes)
23+
24+
release = repo.get_release(NEXT_TAG)
25+
release.upload_asset("singleheader/ada.cpp")
26+
release.upload_asset("singleheader/ada.h")
27+
release.upload_asset("singleheader/singleheader.zip")

tools/release/lib/__init__.py

Whitespace-only changes.

tools/release/lib/release.py

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
import re
2+
3+
4+
def is_valid_tag(tag):
5+
tag_regex = r"^v\d+\.\d+\.\d+$"
6+
return bool(re.match(tag_regex, tag))
7+
8+
9+
def create_release(repository, tag, notes):
10+
if not is_valid_tag(tag):
11+
raise Exception(f"Invalid tag: {tag}")
12+
13+
try:
14+
repository.create_git_release(
15+
tag=tag, name=tag, message=notes, draft=False, prerelease=False
16+
)
17+
18+
except Exception as e:
19+
raise Exception(f"create_release: Error creating release/tag {tag}: {str(e)}")
20+
21+
22+
def get_release_merged_pulls(repository, last_release):
23+
pulls = repository.get_pulls(state="closed")
24+
merged_pulls = [
25+
pull
26+
for pull in pulls
27+
if pull.merged and pull.merged_at > last_release.created_at
28+
]
29+
return sorted(merged_pulls, key=lambda pull: pull.number)
30+
31+
32+
def get_new_contributors(repository, last_release):
33+
# Get list of contributors up to the last release
34+
merged_pulls = [
35+
pull
36+
for pull in repository.get_pulls(state="closed")
37+
if pull.merged and pull.merged_at <= last_release.created_at
38+
]
39+
contributors = set()
40+
for pull in merged_pulls:
41+
contributors.add(pull.user.login)
42+
43+
# Adds into the dict the new contributors and thair respective merged PRs
44+
new_contributors = {}
45+
release_merged_pulls = get_release_merged_pulls(repository, last_release)
46+
for pull in release_merged_pulls:
47+
contributor = pull.user.login
48+
if contributor not in contributors:
49+
if contributor not in new_contributors.keys():
50+
new_contributors[contributor] = [pull]
51+
else:
52+
new_contributors[contributor] += [pull]
53+
54+
for contributor in new_contributors.keys():
55+
new_contributors[contributor] = sorted(
56+
new_contributors[f"{contributor}"], key=lambda pull: pull.merged_at
57+
)
58+
59+
return new_contributors
60+
61+
62+
def get_last_release(repository):
63+
sorted_releases = sorted(
64+
repository.get_releases(), key=lambda r: r.created_at, reverse=True
65+
)
66+
67+
last_release = repository
68+
if len(sorted_releases) >= 2:
69+
last_release = sorted_releases[0]
70+
71+
return last_release
72+
73+
74+
def whats_changed_md(repository, last_release):
75+
release_merged_pulls = get_release_merged_pulls(repository, last_release)
76+
whats_changed = []
77+
for pull in release_merged_pulls:
78+
whats_changed.append(
79+
f"* {pull.title} by @{pull.user.login} in https://github.com/{repository.full_name}/pull/{pull.number}"
80+
)
81+
82+
return whats_changed
83+
84+
85+
def new_contributors_md(repository, last_release):
86+
new_contributors = get_new_contributors(repository, last_release)
87+
88+
contributors_md = []
89+
for contributor in new_contributors.keys():
90+
pr_number = new_contributors[contributor][0].number # 0 is the first one merged
91+
contributors_md.append(
92+
f"* @{contributor} made their first contribution in https://github.com/{repository.full_name}/pull/{pr_number}"
93+
)
94+
95+
return contributors_md
96+
97+
98+
def full_changelog_md(repository_name, last_tag_name, next_tag_name):
99+
if type(last_tag_name) != str or type(next_tag_name) != str:
100+
raise Exception("full_changelog_md: Tag names should be strings.")
101+
102+
return f"**Full Changelog**: https://github.com/{repository_name}/compare/{last_tag_name}...{next_tag_name}"
103+
104+
105+
def contruct_release_notes(repository, next_tag_name):
106+
last_tag = get_last_release(repository)
107+
whats_changed = whats_changed_md(repository, last_tag)
108+
new_contributors = new_contributors_md(repository, last_tag)
109+
full_changelog = full_changelog_md(
110+
repository.full_name, last_tag.title, next_tag_name
111+
)
112+
113+
notes = "## What's Changed\n"
114+
for changes in whats_changed:
115+
notes += changes + "\n"
116+
117+
notes += "\n"
118+
119+
if len(new_contributors):
120+
notes += "## New Contributors\n"
121+
for new_contributor in new_contributors:
122+
notes += new_contributor + "\n"
123+
124+
notes += "\n"
125+
126+
notes += full_changelog
127+
return notes

tools/release/lib/tests/__init__.py

Whitespace-only changes.
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/**
2+
* @file ada_version.h
3+
* @brief Definitions for Ada's version number.
4+
*/
5+
#ifndef ADA_ADA_VERSION_H
6+
#define ADA_ADA_VERSION_H
7+
8+
#define ADA_VERSION "1.0.0"
9+
10+
namespace ada {
11+
12+
enum {
13+
ADA_VERSION_MAJOR = 1,
14+
ADA_VERSION_MINOR = 0,
15+
ADA_VERSION_REVISION = 0,
16+
};
17+
18+
} // namespace ada
19+
20+
#endif // ADA_ADA_VERSION_H
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/**
2+
* @file ada_version.h
3+
* @brief Definitions for Ada's version number.
4+
*/
5+
#ifndef ADA_ADA_VERSION_H
6+
#define ADA_ADA_VERSION_H
7+
8+
#define ADA_VERSION "2.0.0"
9+
10+
namespace ada {
11+
12+
enum {
13+
ADA_VERSION_MAJOR = 2,
14+
ADA_VERSION_MINOR = 0,
15+
ADA_VERSION_REVISION = 0,
16+
};
17+
18+
} // namespace ada
19+
20+
#endif // ADA_ADA_VERSION_H

0 commit comments

Comments
 (0)