-
Notifications
You must be signed in to change notification settings - Fork 793
Expand file tree
/
Copy pathgenerate_release_notes.py
More file actions
205 lines (164 loc) · 5.81 KB
/
generate_release_notes.py
File metadata and controls
205 lines (164 loc) · 5.81 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
# Copyright (C) 2019 Intel Corporation. All rights reserved.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
# get the last release tag from git, and use it to find all merged PRs since
# that tag. Since their titles might not following the same format, we use
# gh cli to search merged PR by commit SHA.
#
# Once having those PRs' information, extract their titles, labels and PR
# numbers and classify them into break changes, new features, enhancements,
# bug fixes, and others based on their labels.
#
# The release version is generated based on the last release tag. The tag
# should be in the format of "WAMR-major.minor.patch", where major, minor,
# and patch are numbers. If there is new feature in merged PRs, the minor
# version should be increased by 1, and the patch version should be reset to 0.
# If there is no new feature, the patch version should be increased by 1.
#
# new content should be inserted into the beginning of the RELEASE_NOTES.md file.
# in a form like:
#
# ``` markdown
# ## WAMR-major.minor.patch
#
# ### Breaking Changes
#
# ### New Features
#
# ### Bug Fixes
#
# ### Enhancements
#
# ### Others
# ```
# The path of RELEASE_NOTES.md is passed in as an command line argument.
import json
import os
import subprocess
import sys
def run_cmd(cmd):
result = subprocess.run(
cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True
)
if result.returncode != 0:
print(f"Error running command: {cmd}\n{result.stderr}")
sys.exit(1)
return result.stdout.strip()
def get_last_release_tag():
tags = run_cmd("git tag --sort=-creatordate").splitlines()
for tag in tags:
if tag.startswith("WAMR-"):
return tag
return None
def get_merged_prs_since(tag):
# Get commits since the last release tag
log_cmd = f'git log {tag}..HEAD --pretty=format:"%s %H"'
logs = run_cmd(log_cmd).splitlines()
print(f"Found {len(logs)} merge commits since last tag '{tag}'.")
pr_numbers = []
for line in logs:
_, sha = line.rsplit(" ", 1)
# Use SHA to find merged PRs
pr_cmd = f"gh pr list --search {sha} --state merged --json number,title"
pr_json = run_cmd(pr_cmd)
pr_data = json.loads(pr_json)
for pr in pr_data:
pr_number = pr.get("number")
print(f"Found PR #{pr_number} {pr['title']}")
if pr_number and pr_number not in pr_numbers:
pr_numbers.append(f"{pr_number}")
return pr_numbers
def get_pr_info(pr_number):
# Use GitHub CLI to get PR info
pr_json = run_cmd(f"gh pr view {pr_number} --json title,labels,url")
pr_data = json.loads(pr_json)
title = pr_data.get("title", "")
labels = [label["name"] for label in pr_data.get("labels", [])]
url = pr_data.get("url", "")
return title, labels, url
def classify_pr(title, labels, url):
entry = f"- {title} (#{url.split('/')[-1]})"
if "breaking-change" in labels:
return "Breaking Changes", entry
elif "new feature" in labels:
return "New Features", entry
elif "enhancement" in labels:
return "Enhancements", entry
elif "bug-fix" in labels:
return "Bug Fixes", entry
else:
return "Others", entry
def generate_release_notes(pr_numbers):
sections = {
"Breaking Changes": [],
"New Features": [],
"Bug Fixes": [],
"Enhancements": [],
"Others": [],
}
for pr_num in pr_numbers:
title, labels, url = get_pr_info(pr_num)
section, entry = classify_pr(title, labels, url)
sections[section].append(entry)
return sections
def generate_version_string(last_tag, sections):
last_tag_parts = last_tag.split("-")[-1]
major, minor, patch = map(int, last_tag_parts.split("."))
if sections["New Features"]:
minor += 1
patch = 0
else:
patch += 1
return f"WAMR-{major}.{minor}.{patch}"
def format_release_notes(version, sections):
notes = [f"## {version}\n"]
for section in [
"Breaking Changes",
"New Features",
"Bug Fixes",
"Enhancements",
"Others",
]:
notes.append(f"### {section}\n")
if sections[section]:
notes.extend(sections[section])
else:
notes.append("")
notes.append("")
return "\n".join(notes) + "\n"
def insert_release_notes(notes, RELEASE_NOTES_FILE):
with open(RELEASE_NOTES_FILE, "r", encoding="utf-8") as f:
old_content = f.read()
with open(RELEASE_NOTES_FILE, "w", encoding="utf-8") as f:
f.write(notes + old_content)
def set_action_output(name, value):
"""Set the output for GitHub Actions."""
if not os.getenv("GITHUB_OUTPUT"):
return
print(f"{name}={value}")
def main(RELEASE_NOTES_FILE):
last_tag = get_last_release_tag()
if not last_tag:
print("No release tag found.")
sys.exit(1)
print(f"Last release tag: {last_tag}")
pr_numbers = get_merged_prs_since(last_tag)
if not pr_numbers:
print("No merged PRs since last release.")
sys.exit(0)
print(f"Found {len(pr_numbers)} merged PRs since last release.")
print(f"PR numbers: {', '.join(pr_numbers)}")
sections = generate_release_notes(pr_numbers)
next_version = generate_version_string(last_tag, sections)
print(f"Next version will be: {next_version}")
notes = format_release_notes(next_version, sections)
insert_release_notes(notes, RELEASE_NOTES_FILE)
print(f"Release notes for {next_version} generated and inserted.")
set_action_output("next_version", next_version)
if __name__ == "__main__":
if len(sys.argv) > 1:
RELEASE_NOTES_FILE = sys.argv[1]
else:
RELEASE_NOTES_FILE = os.path.join(
os.path.dirname(__file__), "../../RELEASE_NOTES.md"
)
main(RELEASE_NOTES_FILE)