xref: /aosp_15_r20/external/grpc-grpc/tools/release/release_notes.py (revision cc02d7e222339f7a4f6ba5f422e6413f4bd931f2)
1*cc02d7e2SAndroid Build Coastguard Worker# Copyright 2019 gRPC authors.
2*cc02d7e2SAndroid Build Coastguard Worker#
3*cc02d7e2SAndroid Build Coastguard Worker# Licensed under the Apache License, Version 2.0 (the "License");
4*cc02d7e2SAndroid Build Coastguard Worker# you may not use this file except in compliance with the License.
5*cc02d7e2SAndroid Build Coastguard Worker# You may obtain a copy of the License at
6*cc02d7e2SAndroid Build Coastguard Worker#
7*cc02d7e2SAndroid Build Coastguard Worker#     http://www.apache.org/licenses/LICENSE-2.0
8*cc02d7e2SAndroid Build Coastguard Worker#
9*cc02d7e2SAndroid Build Coastguard Worker# Unless required by applicable law or agreed to in writing, software
10*cc02d7e2SAndroid Build Coastguard Worker# distributed under the License is distributed on an "AS IS" BASIS,
11*cc02d7e2SAndroid Build Coastguard Worker# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12*cc02d7e2SAndroid Build Coastguard Worker# See the License for the specific language governing permissions and
13*cc02d7e2SAndroid Build Coastguard Worker# limitations under the License.
14*cc02d7e2SAndroid Build Coastguard Worker"""Generate draft and release notes in Markdown from Github PRs.
15*cc02d7e2SAndroid Build Coastguard Worker
16*cc02d7e2SAndroid Build Coastguard WorkerYou'll need a github API token to avoid being rate-limited. See
17*cc02d7e2SAndroid Build Coastguard Workerhttps://help.github.com/articles/creating-a-personal-access-token-for-the-command-line/
18*cc02d7e2SAndroid Build Coastguard Worker
19*cc02d7e2SAndroid Build Coastguard WorkerThis script collects PRs using "git log X..Y" from local repo where X and Y are
20*cc02d7e2SAndroid Build Coastguard Workertags or release branch names of previous and current releases respectively.
21*cc02d7e2SAndroid Build Coastguard WorkerTypically, notes are generated before the release branch is labelled so Y is
22*cc02d7e2SAndroid Build Coastguard Workeralmost always the name of the release branch. X is the previous release branch
23*cc02d7e2SAndroid Build Coastguard Workerif this is not a patch release. Otherwise, it is the previous release tag.
24*cc02d7e2SAndroid Build Coastguard WorkerFor example, for release v1.17.0, X will be origin/v1.16.x and for release v1.17.3,
25*cc02d7e2SAndroid Build Coastguard WorkerX will be v1.17.2. In both cases Y will be origin/v1.17.x.
26*cc02d7e2SAndroid Build Coastguard Worker
27*cc02d7e2SAndroid Build Coastguard Worker"""
28*cc02d7e2SAndroid Build Coastguard Worker
29*cc02d7e2SAndroid Build Coastguard Workerfrom collections import defaultdict
30*cc02d7e2SAndroid Build Coastguard Workerimport json
31*cc02d7e2SAndroid Build Coastguard Workerimport logging
32*cc02d7e2SAndroid Build Coastguard Workerimport re
33*cc02d7e2SAndroid Build Coastguard Workerimport subprocess
34*cc02d7e2SAndroid Build Coastguard Worker
35*cc02d7e2SAndroid Build Coastguard Workerimport urllib3
36*cc02d7e2SAndroid Build Coastguard Worker
37*cc02d7e2SAndroid Build Coastguard Workerlogging.basicConfig(level=logging.WARNING)
38*cc02d7e2SAndroid Build Coastguard Worker
39*cc02d7e2SAndroid Build Coastguard Workercontent_header = """Draft Release Notes For {version}
40*cc02d7e2SAndroid Build Coastguard Worker--
41*cc02d7e2SAndroid Build Coastguard WorkerFinal release notes will be generated from the PR titles that have *"release notes:yes"* label. If you have any additional notes please add them below. These will be appended to auto generated release notes. Previous release notes are [here](https://github.com/grpc/grpc/releases).
42*cc02d7e2SAndroid Build Coastguard Worker
43*cc02d7e2SAndroid Build Coastguard Worker**Also, look at the PRs listed below against your name.** Please apply the missing labels and make necessary corrections (like fixing the title) to the PR in Github. Final release notes will be generated just before the release on {date}.
44*cc02d7e2SAndroid Build Coastguard Worker
45*cc02d7e2SAndroid Build Coastguard WorkerAdd additional notes not in PRs
46*cc02d7e2SAndroid Build Coastguard Worker--
47*cc02d7e2SAndroid Build Coastguard Worker
48*cc02d7e2SAndroid Build Coastguard WorkerCore
49*cc02d7e2SAndroid Build Coastguard Worker-
50*cc02d7e2SAndroid Build Coastguard Worker
51*cc02d7e2SAndroid Build Coastguard Worker
52*cc02d7e2SAndroid Build Coastguard WorkerC++
53*cc02d7e2SAndroid Build Coastguard Worker-
54*cc02d7e2SAndroid Build Coastguard Worker
55*cc02d7e2SAndroid Build Coastguard Worker
56*cc02d7e2SAndroid Build Coastguard WorkerC#
57*cc02d7e2SAndroid Build Coastguard Worker-
58*cc02d7e2SAndroid Build Coastguard Worker
59*cc02d7e2SAndroid Build Coastguard Worker
60*cc02d7e2SAndroid Build Coastguard WorkerObjective-C
61*cc02d7e2SAndroid Build Coastguard Worker-
62*cc02d7e2SAndroid Build Coastguard Worker
63*cc02d7e2SAndroid Build Coastguard Worker
64*cc02d7e2SAndroid Build Coastguard WorkerPHP
65*cc02d7e2SAndroid Build Coastguard Worker-
66*cc02d7e2SAndroid Build Coastguard Worker
67*cc02d7e2SAndroid Build Coastguard Worker
68*cc02d7e2SAndroid Build Coastguard WorkerPython
69*cc02d7e2SAndroid Build Coastguard Worker-
70*cc02d7e2SAndroid Build Coastguard Worker
71*cc02d7e2SAndroid Build Coastguard Worker
72*cc02d7e2SAndroid Build Coastguard WorkerRuby
73*cc02d7e2SAndroid Build Coastguard Worker-
74*cc02d7e2SAndroid Build Coastguard Worker
75*cc02d7e2SAndroid Build Coastguard Worker
76*cc02d7e2SAndroid Build Coastguard Worker"""
77*cc02d7e2SAndroid Build Coastguard Worker
78*cc02d7e2SAndroid Build Coastguard Workerrl_header = """This is release {version} ([{name}](https://github.com/grpc/grpc/blob/master/doc/g_stands_for.md)) of gRPC Core.
79*cc02d7e2SAndroid Build Coastguard Worker
80*cc02d7e2SAndroid Build Coastguard WorkerFor gRPC documentation, see [grpc.io](https://grpc.io/). For previous releases, see [Releases](https://github.com/grpc/grpc/releases).
81*cc02d7e2SAndroid Build Coastguard Worker
82*cc02d7e2SAndroid Build Coastguard WorkerThis release contains refinements, improvements, and bug fixes, with highlights listed below.
83*cc02d7e2SAndroid Build Coastguard Worker
84*cc02d7e2SAndroid Build Coastguard Worker
85*cc02d7e2SAndroid Build Coastguard Worker"""
86*cc02d7e2SAndroid Build Coastguard Worker
87*cc02d7e2SAndroid Build Coastguard WorkerHTML_URL = "https://github.com/grpc/grpc/pull/"
88*cc02d7e2SAndroid Build Coastguard WorkerAPI_URL = "https://api.github.com/repos/grpc/grpc/pulls/"
89*cc02d7e2SAndroid Build Coastguard Worker
90*cc02d7e2SAndroid Build Coastguard Worker
91*cc02d7e2SAndroid Build Coastguard Workerdef get_commit_detail(commit):
92*cc02d7e2SAndroid Build Coastguard Worker    """Print commit and CL info for the commits that are submitted with CL-first workflow and warn the release manager to check manually."""
93*cc02d7e2SAndroid Build Coastguard Worker    glg_command = [
94*cc02d7e2SAndroid Build Coastguard Worker        "git",
95*cc02d7e2SAndroid Build Coastguard Worker        "log",
96*cc02d7e2SAndroid Build Coastguard Worker        "-n 1",
97*cc02d7e2SAndroid Build Coastguard Worker        "%s" % commit,
98*cc02d7e2SAndroid Build Coastguard Worker    ]
99*cc02d7e2SAndroid Build Coastguard Worker    output = subprocess.check_output(glg_command).decode("utf-8", "ignore")
100*cc02d7e2SAndroid Build Coastguard Worker    matches = re.search("Author:.*<(.*@).*>", output)
101*cc02d7e2SAndroid Build Coastguard Worker    author = matches.group(1)
102*cc02d7e2SAndroid Build Coastguard Worker    detail = "- " + author + " "
103*cc02d7e2SAndroid Build Coastguard Worker    title = output.splitlines()[4].strip()
104*cc02d7e2SAndroid Build Coastguard Worker    detail += "- " + title
105*cc02d7e2SAndroid Build Coastguard Worker    if not title.endswith("."):
106*cc02d7e2SAndroid Build Coastguard Worker        detail += "."
107*cc02d7e2SAndroid Build Coastguard Worker    matches = re.search("PiperOrigin-RevId: ([0-9]+)$", output)
108*cc02d7e2SAndroid Build Coastguard Worker    cl_num = matches.group(1)
109*cc02d7e2SAndroid Build Coastguard Worker    detail += (
110*cc02d7e2SAndroid Build Coastguard Worker        " ([commit](https://github.com/grpc/grpc/commit/"
111*cc02d7e2SAndroid Build Coastguard Worker        + commit
112*cc02d7e2SAndroid Build Coastguard Worker        + ")) ([CL](https://critique.corp.google.com/cl/"
113*cc02d7e2SAndroid Build Coastguard Worker        + cl_num
114*cc02d7e2SAndroid Build Coastguard Worker        + "))"
115*cc02d7e2SAndroid Build Coastguard Worker    )
116*cc02d7e2SAndroid Build Coastguard Worker    return detail
117*cc02d7e2SAndroid Build Coastguard Worker
118*cc02d7e2SAndroid Build Coastguard Worker
119*cc02d7e2SAndroid Build Coastguard Workerdef get_commit_log(prevRelLabel, relBranch):
120*cc02d7e2SAndroid Build Coastguard Worker    """Return the output of 'git log prevRelLabel..relBranch'"""
121*cc02d7e2SAndroid Build Coastguard Worker
122*cc02d7e2SAndroid Build Coastguard Worker    import subprocess
123*cc02d7e2SAndroid Build Coastguard Worker
124*cc02d7e2SAndroid Build Coastguard Worker    glg_command = [
125*cc02d7e2SAndroid Build Coastguard Worker        "git",
126*cc02d7e2SAndroid Build Coastguard Worker        "log",
127*cc02d7e2SAndroid Build Coastguard Worker        "--pretty=oneline",
128*cc02d7e2SAndroid Build Coastguard Worker        "%s..%s" % (prevRelLabel, relBranch),
129*cc02d7e2SAndroid Build Coastguard Worker    ]
130*cc02d7e2SAndroid Build Coastguard Worker    print(("Running ", " ".join(glg_command)))
131*cc02d7e2SAndroid Build Coastguard Worker    return subprocess.check_output(glg_command).decode("utf-8", "ignore")
132*cc02d7e2SAndroid Build Coastguard Worker
133*cc02d7e2SAndroid Build Coastguard Worker
134*cc02d7e2SAndroid Build Coastguard Workerdef get_pr_data(pr_num):
135*cc02d7e2SAndroid Build Coastguard Worker    """Get the PR data from github. Return 'error' on exception"""
136*cc02d7e2SAndroid Build Coastguard Worker    http = urllib3.PoolManager(
137*cc02d7e2SAndroid Build Coastguard Worker        retries=urllib3.Retry(total=7, backoff_factor=1), timeout=4.0
138*cc02d7e2SAndroid Build Coastguard Worker    )
139*cc02d7e2SAndroid Build Coastguard Worker    url = API_URL + pr_num
140*cc02d7e2SAndroid Build Coastguard Worker    try:
141*cc02d7e2SAndroid Build Coastguard Worker        response = http.request(
142*cc02d7e2SAndroid Build Coastguard Worker            "GET", url, headers={"Authorization": "token %s" % TOKEN}
143*cc02d7e2SAndroid Build Coastguard Worker        )
144*cc02d7e2SAndroid Build Coastguard Worker    except urllib3.exceptions.HTTPError as e:
145*cc02d7e2SAndroid Build Coastguard Worker        print("Request error:", e.reason)
146*cc02d7e2SAndroid Build Coastguard Worker        return "error"
147*cc02d7e2SAndroid Build Coastguard Worker    return json.loads(response.data.decode("utf-8"))
148*cc02d7e2SAndroid Build Coastguard Worker
149*cc02d7e2SAndroid Build Coastguard Worker
150*cc02d7e2SAndroid Build Coastguard Workerdef get_pr_titles(gitLogs):
151*cc02d7e2SAndroid Build Coastguard Worker    import re
152*cc02d7e2SAndroid Build Coastguard Worker
153*cc02d7e2SAndroid Build Coastguard Worker    # All commits
154*cc02d7e2SAndroid Build Coastguard Worker    match_commit = "^([a-fA-F0-9]+) "
155*cc02d7e2SAndroid Build Coastguard Worker    all_commits_set = set(re.findall(match_commit, gitLogs, re.MULTILINE))
156*cc02d7e2SAndroid Build Coastguard Worker
157*cc02d7e2SAndroid Build Coastguard Worker    error_count = 0
158*cc02d7e2SAndroid Build Coastguard Worker    # PRs with merge commits
159*cc02d7e2SAndroid Build Coastguard Worker    match_merge_pr = "^([a-fA-F0-9]+) .*Merge pull request #(\d+)"
160*cc02d7e2SAndroid Build Coastguard Worker    matches = re.findall(match_merge_pr, gitLogs, re.MULTILINE)
161*cc02d7e2SAndroid Build Coastguard Worker    merge_commits = []
162*cc02d7e2SAndroid Build Coastguard Worker    prlist_merge_pr = []
163*cc02d7e2SAndroid Build Coastguard Worker    if matches:
164*cc02d7e2SAndroid Build Coastguard Worker        merge_commits, prlist_merge_pr = zip(*matches)
165*cc02d7e2SAndroid Build Coastguard Worker    merge_commits_set = set(merge_commits)
166*cc02d7e2SAndroid Build Coastguard Worker    print("\nPRs matching 'Merge pull request #<num>':")
167*cc02d7e2SAndroid Build Coastguard Worker    print(prlist_merge_pr)
168*cc02d7e2SAndroid Build Coastguard Worker    print("\n")
169*cc02d7e2SAndroid Build Coastguard Worker
170*cc02d7e2SAndroid Build Coastguard Worker    # PRs using Github's squash & merge feature
171*cc02d7e2SAndroid Build Coastguard Worker    match_sq = "^([a-fA-F0-9]+) .*\(#(\d+)\)$"
172*cc02d7e2SAndroid Build Coastguard Worker    matches = re.findall(match_sq, gitLogs, re.MULTILINE)
173*cc02d7e2SAndroid Build Coastguard Worker    if matches:
174*cc02d7e2SAndroid Build Coastguard Worker        sq_commits, prlist_sq = zip(*matches)
175*cc02d7e2SAndroid Build Coastguard Worker    sq_commits_set = set(sq_commits)
176*cc02d7e2SAndroid Build Coastguard Worker    print("\nPRs matching '[PR Description](#<num>)$'")
177*cc02d7e2SAndroid Build Coastguard Worker    print(prlist_sq)
178*cc02d7e2SAndroid Build Coastguard Worker    print("\n")
179*cc02d7e2SAndroid Build Coastguard Worker    prlist = list(prlist_merge_pr) + list(prlist_sq)
180*cc02d7e2SAndroid Build Coastguard Worker    langs_pr = defaultdict(list)
181*cc02d7e2SAndroid Build Coastguard Worker    for pr_num in prlist:
182*cc02d7e2SAndroid Build Coastguard Worker        pr_num = str(pr_num)
183*cc02d7e2SAndroid Build Coastguard Worker        print(("---------- getting data for PR " + pr_num))
184*cc02d7e2SAndroid Build Coastguard Worker        pr = get_pr_data(pr_num)
185*cc02d7e2SAndroid Build Coastguard Worker        if pr == "error":
186*cc02d7e2SAndroid Build Coastguard Worker            print(
187*cc02d7e2SAndroid Build Coastguard Worker                ("\n***ERROR*** Error in getting data for PR " + pr_num + "\n")
188*cc02d7e2SAndroid Build Coastguard Worker            )
189*cc02d7e2SAndroid Build Coastguard Worker            error_count += 1
190*cc02d7e2SAndroid Build Coastguard Worker            continue
191*cc02d7e2SAndroid Build Coastguard Worker        rl_no_found = False
192*cc02d7e2SAndroid Build Coastguard Worker        rl_yes_found = False
193*cc02d7e2SAndroid Build Coastguard Worker        lang_found = False
194*cc02d7e2SAndroid Build Coastguard Worker        for label in pr["labels"]:
195*cc02d7e2SAndroid Build Coastguard Worker            if label["name"] == "release notes: yes":
196*cc02d7e2SAndroid Build Coastguard Worker                rl_yes_found = True
197*cc02d7e2SAndroid Build Coastguard Worker            elif label["name"] == "release notes: no":
198*cc02d7e2SAndroid Build Coastguard Worker                rl_no_found = True
199*cc02d7e2SAndroid Build Coastguard Worker            elif label["name"].startswith("lang/"):
200*cc02d7e2SAndroid Build Coastguard Worker                lang_found = True
201*cc02d7e2SAndroid Build Coastguard Worker                lang = label["name"].split("/")[1].lower()
202*cc02d7e2SAndroid Build Coastguard Worker                # lang = lang[0].upper() + lang[1:]
203*cc02d7e2SAndroid Build Coastguard Worker        body = pr["title"]
204*cc02d7e2SAndroid Build Coastguard Worker        if not body.endswith("."):
205*cc02d7e2SAndroid Build Coastguard Worker            body = body + "."
206*cc02d7e2SAndroid Build Coastguard Worker
207*cc02d7e2SAndroid Build Coastguard Worker        prline = (
208*cc02d7e2SAndroid Build Coastguard Worker            "-  " + body + " ([#" + pr_num + "](" + HTML_URL + pr_num + "))"
209*cc02d7e2SAndroid Build Coastguard Worker        )
210*cc02d7e2SAndroid Build Coastguard Worker        detail = "- " + pr["user"]["login"] + "@ " + prline
211*cc02d7e2SAndroid Build Coastguard Worker        print(detail)
212*cc02d7e2SAndroid Build Coastguard Worker        # if no RL label
213*cc02d7e2SAndroid Build Coastguard Worker        if not rl_no_found and not rl_yes_found:
214*cc02d7e2SAndroid Build Coastguard Worker            print(("Release notes label missing for " + pr_num))
215*cc02d7e2SAndroid Build Coastguard Worker            langs_pr["nolabel"].append(detail)
216*cc02d7e2SAndroid Build Coastguard Worker        elif rl_yes_found and not lang_found:
217*cc02d7e2SAndroid Build Coastguard Worker            print(("Lang label missing for " + pr_num))
218*cc02d7e2SAndroid Build Coastguard Worker            langs_pr["nolang"].append(detail)
219*cc02d7e2SAndroid Build Coastguard Worker        elif rl_no_found:
220*cc02d7e2SAndroid Build Coastguard Worker            print(("'Release notes:no' found for " + pr_num))
221*cc02d7e2SAndroid Build Coastguard Worker            langs_pr["notinrel"].append(detail)
222*cc02d7e2SAndroid Build Coastguard Worker        elif rl_yes_found:
223*cc02d7e2SAndroid Build Coastguard Worker            print(
224*cc02d7e2SAndroid Build Coastguard Worker                (
225*cc02d7e2SAndroid Build Coastguard Worker                    "'Release notes:yes' found for "
226*cc02d7e2SAndroid Build Coastguard Worker                    + pr_num
227*cc02d7e2SAndroid Build Coastguard Worker                    + " with lang "
228*cc02d7e2SAndroid Build Coastguard Worker                    + lang
229*cc02d7e2SAndroid Build Coastguard Worker                )
230*cc02d7e2SAndroid Build Coastguard Worker            )
231*cc02d7e2SAndroid Build Coastguard Worker            langs_pr["inrel"].append(detail)
232*cc02d7e2SAndroid Build Coastguard Worker            langs_pr[lang].append(prline)
233*cc02d7e2SAndroid Build Coastguard Worker    commits_wo_pr = all_commits_set - merge_commits_set - sq_commits_set
234*cc02d7e2SAndroid Build Coastguard Worker    for commit in commits_wo_pr:
235*cc02d7e2SAndroid Build Coastguard Worker        langs_pr["nopr"].append(get_commit_detail(commit))
236*cc02d7e2SAndroid Build Coastguard Worker
237*cc02d7e2SAndroid Build Coastguard Worker    return langs_pr, error_count
238*cc02d7e2SAndroid Build Coastguard Worker
239*cc02d7e2SAndroid Build Coastguard Worker
240*cc02d7e2SAndroid Build Coastguard Workerdef write_draft(langs_pr, file, version, date):
241*cc02d7e2SAndroid Build Coastguard Worker    file.write(content_header.format(version=version, date=date))
242*cc02d7e2SAndroid Build Coastguard Worker    file.write(
243*cc02d7e2SAndroid Build Coastguard Worker        "Commits with missing PR number - please lookup the PR info in the corresponding CL and add to the additional notes if necessary.\n"
244*cc02d7e2SAndroid Build Coastguard Worker    )
245*cc02d7e2SAndroid Build Coastguard Worker    file.write("---\n")
246*cc02d7e2SAndroid Build Coastguard Worker    file.write("\n")
247*cc02d7e2SAndroid Build Coastguard Worker    if langs_pr["nopr"]:
248*cc02d7e2SAndroid Build Coastguard Worker        file.write("\n".join(langs_pr["nopr"]))
249*cc02d7e2SAndroid Build Coastguard Worker    else:
250*cc02d7e2SAndroid Build Coastguard Worker        file.write("- None")
251*cc02d7e2SAndroid Build Coastguard Worker    file.write("\n")
252*cc02d7e2SAndroid Build Coastguard Worker    file.write("\n")
253*cc02d7e2SAndroid Build Coastguard Worker    file.write("PRs with missing release notes label - please fix in Github\n")
254*cc02d7e2SAndroid Build Coastguard Worker    file.write("---\n")
255*cc02d7e2SAndroid Build Coastguard Worker    file.write("\n")
256*cc02d7e2SAndroid Build Coastguard Worker    if langs_pr["nolabel"]:
257*cc02d7e2SAndroid Build Coastguard Worker        langs_pr["nolabel"].sort()
258*cc02d7e2SAndroid Build Coastguard Worker        file.write("\n".join(langs_pr["nolabel"]))
259*cc02d7e2SAndroid Build Coastguard Worker    else:
260*cc02d7e2SAndroid Build Coastguard Worker        file.write("- None")
261*cc02d7e2SAndroid Build Coastguard Worker    file.write("\n")
262*cc02d7e2SAndroid Build Coastguard Worker    file.write("\n")
263*cc02d7e2SAndroid Build Coastguard Worker    file.write("PRs with missing lang label - please fix in Github\n")
264*cc02d7e2SAndroid Build Coastguard Worker    file.write("---\n")
265*cc02d7e2SAndroid Build Coastguard Worker    file.write("\n")
266*cc02d7e2SAndroid Build Coastguard Worker    if langs_pr["nolang"]:
267*cc02d7e2SAndroid Build Coastguard Worker        langs_pr["nolang"].sort()
268*cc02d7e2SAndroid Build Coastguard Worker        file.write("\n".join(langs_pr["nolang"]))
269*cc02d7e2SAndroid Build Coastguard Worker    else:
270*cc02d7e2SAndroid Build Coastguard Worker        file.write("- None")
271*cc02d7e2SAndroid Build Coastguard Worker    file.write("\n")
272*cc02d7e2SAndroid Build Coastguard Worker    file.write("\n")
273*cc02d7e2SAndroid Build Coastguard Worker    file.write(
274*cc02d7e2SAndroid Build Coastguard Worker        "PRs going into release notes - please check title and fix in Github."
275*cc02d7e2SAndroid Build Coastguard Worker        " Do not edit here.\n"
276*cc02d7e2SAndroid Build Coastguard Worker    )
277*cc02d7e2SAndroid Build Coastguard Worker    file.write("---\n")
278*cc02d7e2SAndroid Build Coastguard Worker    file.write("\n")
279*cc02d7e2SAndroid Build Coastguard Worker    if langs_pr["inrel"]:
280*cc02d7e2SAndroid Build Coastguard Worker        langs_pr["inrel"].sort()
281*cc02d7e2SAndroid Build Coastguard Worker        file.write("\n".join(langs_pr["inrel"]))
282*cc02d7e2SAndroid Build Coastguard Worker    else:
283*cc02d7e2SAndroid Build Coastguard Worker        file.write("- None")
284*cc02d7e2SAndroid Build Coastguard Worker    file.write("\n")
285*cc02d7e2SAndroid Build Coastguard Worker    file.write("\n")
286*cc02d7e2SAndroid Build Coastguard Worker    file.write("PRs not going into release notes\n")
287*cc02d7e2SAndroid Build Coastguard Worker    file.write("---\n")
288*cc02d7e2SAndroid Build Coastguard Worker    file.write("\n")
289*cc02d7e2SAndroid Build Coastguard Worker    if langs_pr["notinrel"]:
290*cc02d7e2SAndroid Build Coastguard Worker        langs_pr["notinrel"].sort()
291*cc02d7e2SAndroid Build Coastguard Worker        file.write("\n".join(langs_pr["notinrel"]))
292*cc02d7e2SAndroid Build Coastguard Worker    else:
293*cc02d7e2SAndroid Build Coastguard Worker        file.write("- None")
294*cc02d7e2SAndroid Build Coastguard Worker    file.write("\n")
295*cc02d7e2SAndroid Build Coastguard Worker    file.write("\n")
296*cc02d7e2SAndroid Build Coastguard Worker
297*cc02d7e2SAndroid Build Coastguard Worker
298*cc02d7e2SAndroid Build Coastguard Workerdef write_rel_notes(langs_pr, file, version, name):
299*cc02d7e2SAndroid Build Coastguard Worker    file.write(rl_header.format(version=version, name=name))
300*cc02d7e2SAndroid Build Coastguard Worker    if langs_pr["core"]:
301*cc02d7e2SAndroid Build Coastguard Worker        file.write("Core\n---\n\n")
302*cc02d7e2SAndroid Build Coastguard Worker        file.write("\n".join(langs_pr["core"]))
303*cc02d7e2SAndroid Build Coastguard Worker        file.write("\n")
304*cc02d7e2SAndroid Build Coastguard Worker        file.write("\n")
305*cc02d7e2SAndroid Build Coastguard Worker    if langs_pr["c++"]:
306*cc02d7e2SAndroid Build Coastguard Worker        file.write("C++\n---\n\n")
307*cc02d7e2SAndroid Build Coastguard Worker        file.write("\n".join(langs_pr["c++"]))
308*cc02d7e2SAndroid Build Coastguard Worker        file.write("\n")
309*cc02d7e2SAndroid Build Coastguard Worker        file.write("\n")
310*cc02d7e2SAndroid Build Coastguard Worker    if langs_pr["c#"]:
311*cc02d7e2SAndroid Build Coastguard Worker        file.write("C#\n---\n\n")
312*cc02d7e2SAndroid Build Coastguard Worker        file.write("\n".join(langs_pr["c#"]))
313*cc02d7e2SAndroid Build Coastguard Worker        file.write("\n")
314*cc02d7e2SAndroid Build Coastguard Worker        file.write("\n")
315*cc02d7e2SAndroid Build Coastguard Worker    if langs_pr["go"]:
316*cc02d7e2SAndroid Build Coastguard Worker        file.write("Go\n---\n\n")
317*cc02d7e2SAndroid Build Coastguard Worker        file.write("\n".join(langs_pr["go"]))
318*cc02d7e2SAndroid Build Coastguard Worker        file.write("\n")
319*cc02d7e2SAndroid Build Coastguard Worker        file.write("\n")
320*cc02d7e2SAndroid Build Coastguard Worker    if langs_pr["Java"]:
321*cc02d7e2SAndroid Build Coastguard Worker        file.write("Java\n---\n\n")
322*cc02d7e2SAndroid Build Coastguard Worker        file.write("\n".join(langs_pr["Java"]))
323*cc02d7e2SAndroid Build Coastguard Worker        file.write("\n")
324*cc02d7e2SAndroid Build Coastguard Worker        file.write("\n")
325*cc02d7e2SAndroid Build Coastguard Worker    if langs_pr["node"]:
326*cc02d7e2SAndroid Build Coastguard Worker        file.write("Node\n---\n\n")
327*cc02d7e2SAndroid Build Coastguard Worker        file.write("\n".join(langs_pr["node"]))
328*cc02d7e2SAndroid Build Coastguard Worker        file.write("\n")
329*cc02d7e2SAndroid Build Coastguard Worker        file.write("\n")
330*cc02d7e2SAndroid Build Coastguard Worker    if langs_pr["objc"]:
331*cc02d7e2SAndroid Build Coastguard Worker        file.write("Objective-C\n---\n\n")
332*cc02d7e2SAndroid Build Coastguard Worker        file.write("\n".join(langs_pr["objc"]))
333*cc02d7e2SAndroid Build Coastguard Worker        file.write("\n")
334*cc02d7e2SAndroid Build Coastguard Worker        file.write("\n")
335*cc02d7e2SAndroid Build Coastguard Worker    if langs_pr["php"]:
336*cc02d7e2SAndroid Build Coastguard Worker        file.write("PHP\n---\n\n")
337*cc02d7e2SAndroid Build Coastguard Worker        file.write("\n".join(langs_pr["php"]))
338*cc02d7e2SAndroid Build Coastguard Worker        file.write("\n")
339*cc02d7e2SAndroid Build Coastguard Worker        file.write("\n")
340*cc02d7e2SAndroid Build Coastguard Worker    if langs_pr["python"]:
341*cc02d7e2SAndroid Build Coastguard Worker        file.write("Python\n---\n\n")
342*cc02d7e2SAndroid Build Coastguard Worker        file.write("\n".join(langs_pr["python"]))
343*cc02d7e2SAndroid Build Coastguard Worker        file.write("\n")
344*cc02d7e2SAndroid Build Coastguard Worker        file.write("\n")
345*cc02d7e2SAndroid Build Coastguard Worker    if langs_pr["ruby"]:
346*cc02d7e2SAndroid Build Coastguard Worker        file.write("Ruby\n---\n\n")
347*cc02d7e2SAndroid Build Coastguard Worker        file.write("\n".join(langs_pr["ruby"]))
348*cc02d7e2SAndroid Build Coastguard Worker        file.write("\n")
349*cc02d7e2SAndroid Build Coastguard Worker        file.write("\n")
350*cc02d7e2SAndroid Build Coastguard Worker    if langs_pr["other"]:
351*cc02d7e2SAndroid Build Coastguard Worker        file.write("Other\n---\n\n")
352*cc02d7e2SAndroid Build Coastguard Worker        file.write("\n".join(langs_pr["other"]))
353*cc02d7e2SAndroid Build Coastguard Worker        file.write("\n")
354*cc02d7e2SAndroid Build Coastguard Worker        file.write("\n")
355*cc02d7e2SAndroid Build Coastguard Worker
356*cc02d7e2SAndroid Build Coastguard Worker
357*cc02d7e2SAndroid Build Coastguard Workerdef build_args_parser():
358*cc02d7e2SAndroid Build Coastguard Worker    import argparse
359*cc02d7e2SAndroid Build Coastguard Worker
360*cc02d7e2SAndroid Build Coastguard Worker    parser = argparse.ArgumentParser()
361*cc02d7e2SAndroid Build Coastguard Worker    parser.add_argument(
362*cc02d7e2SAndroid Build Coastguard Worker        "release_version", type=str, help="New release version e.g. 1.14.0"
363*cc02d7e2SAndroid Build Coastguard Worker    )
364*cc02d7e2SAndroid Build Coastguard Worker    parser.add_argument(
365*cc02d7e2SAndroid Build Coastguard Worker        "release_name", type=str, help="New release name e.g. gladiolus"
366*cc02d7e2SAndroid Build Coastguard Worker    )
367*cc02d7e2SAndroid Build Coastguard Worker    parser.add_argument(
368*cc02d7e2SAndroid Build Coastguard Worker        "release_date", type=str, help="Release date e.g. 7/30/18"
369*cc02d7e2SAndroid Build Coastguard Worker    )
370*cc02d7e2SAndroid Build Coastguard Worker    parser.add_argument(
371*cc02d7e2SAndroid Build Coastguard Worker        "previous_release_label",
372*cc02d7e2SAndroid Build Coastguard Worker        type=str,
373*cc02d7e2SAndroid Build Coastguard Worker        help="Previous release branch/tag e.g. v1.13.x",
374*cc02d7e2SAndroid Build Coastguard Worker    )
375*cc02d7e2SAndroid Build Coastguard Worker    parser.add_argument(
376*cc02d7e2SAndroid Build Coastguard Worker        "release_branch",
377*cc02d7e2SAndroid Build Coastguard Worker        type=str,
378*cc02d7e2SAndroid Build Coastguard Worker        help="Current release branch e.g. origin/v1.14.x",
379*cc02d7e2SAndroid Build Coastguard Worker    )
380*cc02d7e2SAndroid Build Coastguard Worker    parser.add_argument(
381*cc02d7e2SAndroid Build Coastguard Worker        "draft_filename", type=str, help="Name of the draft file e.g. draft.md"
382*cc02d7e2SAndroid Build Coastguard Worker    )
383*cc02d7e2SAndroid Build Coastguard Worker    parser.add_argument(
384*cc02d7e2SAndroid Build Coastguard Worker        "release_notes_filename",
385*cc02d7e2SAndroid Build Coastguard Worker        type=str,
386*cc02d7e2SAndroid Build Coastguard Worker        help="Name of the release notes file e.g. relnotes.md",
387*cc02d7e2SAndroid Build Coastguard Worker    )
388*cc02d7e2SAndroid Build Coastguard Worker    parser.add_argument(
389*cc02d7e2SAndroid Build Coastguard Worker        "--token",
390*cc02d7e2SAndroid Build Coastguard Worker        type=str,
391*cc02d7e2SAndroid Build Coastguard Worker        default="",
392*cc02d7e2SAndroid Build Coastguard Worker        help="GitHub API token to avoid being rate limited",
393*cc02d7e2SAndroid Build Coastguard Worker    )
394*cc02d7e2SAndroid Build Coastguard Worker    return parser
395*cc02d7e2SAndroid Build Coastguard Worker
396*cc02d7e2SAndroid Build Coastguard Worker
397*cc02d7e2SAndroid Build Coastguard Workerdef main():
398*cc02d7e2SAndroid Build Coastguard Worker    import os
399*cc02d7e2SAndroid Build Coastguard Worker
400*cc02d7e2SAndroid Build Coastguard Worker    global TOKEN
401*cc02d7e2SAndroid Build Coastguard Worker
402*cc02d7e2SAndroid Build Coastguard Worker    parser = build_args_parser()
403*cc02d7e2SAndroid Build Coastguard Worker    args = parser.parse_args()
404*cc02d7e2SAndroid Build Coastguard Worker    version, name, date = (
405*cc02d7e2SAndroid Build Coastguard Worker        args.release_version,
406*cc02d7e2SAndroid Build Coastguard Worker        args.release_name,
407*cc02d7e2SAndroid Build Coastguard Worker        args.release_date,
408*cc02d7e2SAndroid Build Coastguard Worker    )
409*cc02d7e2SAndroid Build Coastguard Worker    start, end = args.previous_release_label, args.release_branch
410*cc02d7e2SAndroid Build Coastguard Worker
411*cc02d7e2SAndroid Build Coastguard Worker    TOKEN = args.token
412*cc02d7e2SAndroid Build Coastguard Worker    if TOKEN == "":
413*cc02d7e2SAndroid Build Coastguard Worker        try:
414*cc02d7e2SAndroid Build Coastguard Worker            TOKEN = os.environ["GITHUB_TOKEN"]
415*cc02d7e2SAndroid Build Coastguard Worker        except:
416*cc02d7e2SAndroid Build Coastguard Worker            pass
417*cc02d7e2SAndroid Build Coastguard Worker    if TOKEN == "":
418*cc02d7e2SAndroid Build Coastguard Worker        print(
419*cc02d7e2SAndroid Build Coastguard Worker            "Error: Github API token required. Either include param"
420*cc02d7e2SAndroid Build Coastguard Worker            " --token=<your github token> or set environment variable"
421*cc02d7e2SAndroid Build Coastguard Worker            " GITHUB_TOKEN to your github token"
422*cc02d7e2SAndroid Build Coastguard Worker        )
423*cc02d7e2SAndroid Build Coastguard Worker        return
424*cc02d7e2SAndroid Build Coastguard Worker
425*cc02d7e2SAndroid Build Coastguard Worker    langs_pr, error_count = get_pr_titles(get_commit_log(start, end))
426*cc02d7e2SAndroid Build Coastguard Worker
427*cc02d7e2SAndroid Build Coastguard Worker    draft_file, rel_file = args.draft_filename, args.release_notes_filename
428*cc02d7e2SAndroid Build Coastguard Worker    filename = os.path.abspath(draft_file)
429*cc02d7e2SAndroid Build Coastguard Worker    if os.path.exists(filename):
430*cc02d7e2SAndroid Build Coastguard Worker        file = open(filename, "r+")
431*cc02d7e2SAndroid Build Coastguard Worker    else:
432*cc02d7e2SAndroid Build Coastguard Worker        file = open(filename, "w")
433*cc02d7e2SAndroid Build Coastguard Worker
434*cc02d7e2SAndroid Build Coastguard Worker    file.seek(0)
435*cc02d7e2SAndroid Build Coastguard Worker    write_draft(langs_pr, file, version, date)
436*cc02d7e2SAndroid Build Coastguard Worker    file.truncate()
437*cc02d7e2SAndroid Build Coastguard Worker    file.close()
438*cc02d7e2SAndroid Build Coastguard Worker    print(("\nDraft notes written to " + filename))
439*cc02d7e2SAndroid Build Coastguard Worker
440*cc02d7e2SAndroid Build Coastguard Worker    filename = os.path.abspath(rel_file)
441*cc02d7e2SAndroid Build Coastguard Worker    if os.path.exists(filename):
442*cc02d7e2SAndroid Build Coastguard Worker        file = open(filename, "r+")
443*cc02d7e2SAndroid Build Coastguard Worker    else:
444*cc02d7e2SAndroid Build Coastguard Worker        file = open(filename, "w")
445*cc02d7e2SAndroid Build Coastguard Worker
446*cc02d7e2SAndroid Build Coastguard Worker    file.seek(0)
447*cc02d7e2SAndroid Build Coastguard Worker    write_rel_notes(langs_pr, file, version, name)
448*cc02d7e2SAndroid Build Coastguard Worker    file.truncate()
449*cc02d7e2SAndroid Build Coastguard Worker    file.close()
450*cc02d7e2SAndroid Build Coastguard Worker    print(("\nRelease notes written to " + filename))
451*cc02d7e2SAndroid Build Coastguard Worker    if error_count > 0:
452*cc02d7e2SAndroid Build Coastguard Worker        print("\n\n*** Errors were encountered. See log. *********\n")
453*cc02d7e2SAndroid Build Coastguard Worker
454*cc02d7e2SAndroid Build Coastguard Worker
455*cc02d7e2SAndroid Build Coastguard Workerif __name__ == "__main__":
456*cc02d7e2SAndroid Build Coastguard Worker    main()
457