blob: d22a6494e7ba9167e537968963f6d1c5240816d0 [file] [log] [blame]
Nick Anthonyb1753a92019-12-11 10:49:39 -05001#!/usr/bin/python3
Nick Anthony7ea8ca92020-02-11 11:11:37 -05002#
3# Copyright (C) 2020 The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16#
17
18from GitClient import *
19from ReleaseNoteMarkdown import *
20from AndroidXMarkdown import LibraryReleaseNotes
Nick Anthonyb1753a92019-12-11 10:49:39 -050021
22import sys
23import os
24import argparse
25import subprocess
26import json
27import datetime
Nick Anthony48a57152020-04-23 07:38:58 -040028from shutil import rmtree
Nick Anthonyb1753a92019-12-11 10:49:39 -050029
30# This script is meant as a drop in replacement until we have git tags implemented in androidx
31# See b/147606199
32#
33
Nick Anthony2d9b73c2020-06-29 21:24:55 -040034# Import the JetpadClient from the parent directory
35sys.path.append("..")
36from JetpadClient import *
37
Nick Anthonyb1753a92019-12-11 10:49:39 -050038# cd into directory of script
39os.chdir(os.path.dirname(os.path.abspath(__file__)))
40
41# Set up input arguments
42parser = argparse.ArgumentParser(
43 description=("""Genereates AndroidX Release Notes for a given date. This script takes in a the release date as millisecond since the epoch,
44 which is the unique id for the release in Jetpad. It queries the Jetpad db, then creates an output json file with the release information.
45 Finally, it passes that json file to the gradle generateReleaseNotes task, which actually produces the release notes.
46 See the ArtifactToCommitMap.kt file in the buildSrc directory for the Kotlin class that is getting serialized here."""))
47parser.add_argument(
48 'date',
49 help='Milliseconds since epoch')
50parser.add_argument(
51 '--include-all-commits', action="store_true",
52 help='If specified, includes all commits in the release notes regardless of the release note tag')
53
54def print_e(*args, **kwargs):
55 print(*args, file=sys.stderr, **kwargs)
56
57def rm(path):
58 if os.path.isdir(path):
59 rmtree(path)
60 elif os.path.exists(path):
61 os.remove(path)
62
Nick Anthony7ea8ca92020-02-11 11:11:37 -050063def isExcludedAuthorEmail(authorEmail):
64 """ Check if an email address is a robot
65 @param authorEmail email to check
66 """
67 excludedAuthorEmails = {
68 "[email protected]",
69 "[email protected]",
70 "[email protected]"
71 }
72 return authorEmail in excludedAuthorEmails
73
74def getVersionToReleaseNotesMap(releaseJsonObject, groupId):
75 """ Iterates over the LibraryReleaseNotes list and creates a map from project.version to List of
76 LibraryReleaseNotes. Thus, each artifactId of the same version will be collected together
77 as list for that version. This is done so that release notes can be collected for all
78 artifactIds of the same version.
79
80 @param releaseJsonObject The json object containing all information about the release
81 @param groupId the groupId to generate this mapping for
82 """
83 versionToArtifactRNMap = {}
84 for artifact in releaseJsonObject["modules"][groupId]:
85 if artifact["version"] in versionToArtifactRNMap:
86 versionToArtifactRNMap[artifact["version"]].append(artifact)
87 else:
88 versionToArtifactRNMap[artifact["version"]] = [artifact]
89 return versionToArtifactRNMap
90
91def mergeCommitListBIntoCommitListA(commitListA, commitListB):
92 """ Merges CommitListB into CommitListA and removes duplicates.
93 """
94 commitListAShaSet = set([])
95 for commitA in commitListA:
96 commitListAShaSet.add(commitA.sha)
97 for commitB in commitListB:
98 if commitB.sha not in commitListAShaSet:
99 commitListA.append(commitB)
100
Nick Anthony8f18f3d2020-05-28 20:15:56 -0400101def getCommonPathPrefix(pathA, pathB):
Nick Anthony7ea8ca92020-02-11 11:11:37 -0500102 pathAList = pathA.split('/')
103 pathBList = pathB.split('/')
104
105 stringAPathLen = len(pathAList)
106 stringBPathLen = len(pathBList)
107 lastCommonIndex = 0
108 for i in range(0, stringAPathLen):
109 if i < stringBPathLen and pathAList[i] == pathBList[i]:
110 lastCommonIndex = i
111 return "/".join(pathAList[:lastCommonIndex + 1])
112
Nick Anthony6526d3b2020-07-09 16:41:41 -0400113def writeArtifactIdReleaseNotesToFile(groupId, artifactId, version, releaseNotesString, channelSummary, outputDir):
Nick Anthony7ea8ca92020-02-11 11:11:37 -0500114 releaseNotesFileName = "%s_%s_%s_release_notes.txt" % (groupId, artifactId, version)
Nick Anthony48a57152020-04-23 07:38:58 -0400115 groupIdDir = "%s/%s" % (outputDir, groupId)
Nick Anthony6526d3b2020-07-09 16:41:41 -0400116 writeReleaseNotesToNewFile(groupIdDir, releaseNotesFileName, channelSummary + "\n" + releaseNotesString)
Nick Anthony7ea8ca92020-02-11 11:11:37 -0500117
Nick Anthony6526d3b2020-07-09 16:41:41 -0400118def writeGroupIdReleaseNotesToFile(groupId, releaseNotesString, channelSummary, outputDir):
Nick Anthony7ea8ca92020-02-11 11:11:37 -0500119 releaseNotesFileName = "%s_release_notes.txt" % (groupId)
Nick Anthony48a57152020-04-23 07:38:58 -0400120 groupIdDir = "%s/%s" % (outputDir, groupId)
Nick Anthony6526d3b2020-07-09 16:41:41 -0400121 writeReleaseNotesToNewFile(groupIdDir, releaseNotesFileName, channelSummary + "\n" + releaseNotesString)
Nick Anthony7ea8ca92020-02-11 11:11:37 -0500122
123def writeReleaseNotesToNewFile(groupIdDir, releaseNotesFileName, releaseNotesString):
124 if not os.path.exists(groupIdDir):
125 os.makedirs(groupIdDir)
126 fullReleaseNotesFilePath = "%s/%s" % (groupIdDir, releaseNotesFileName)
127 with open(fullReleaseNotesFilePath, 'w') as f:
128 f.write(releaseNotesString)
129
Nick Anthony2d9b73c2020-06-29 21:24:55 -0400130def generateAllReleaseNotes(releaseDate, include_all_commits, outputDir):
Nick Anthony7ea8ca92020-02-11 11:11:37 -0500131 """ Creates all the release notes. Creates each individual artifactId release notes, each
132 individual groupId release notes, then creates an aggregrate release notes file that
133 contains all of the groupId release Notes
134 @param releaseDate The release date of the entire release
135 @param includeAllCommits Set to true to include all commits regardless of whether or not they
136 have the release notes tag
Nick Anthony7ea8ca92020-02-11 11:11:37 -0500137 """
138 gitClient = GitClient(os.getcwd())
Nick Anthony2d9b73c2020-06-29 21:24:55 -0400139 releaseJsonObject = getJetpadRelease(releaseDate, include_all_commits)
140 print("Creating release notes...")
Nick Anthony7ea8ca92020-02-11 11:11:37 -0500141 allReleaseNotes = ""
Nick Anthony6526d3b2020-07-09 16:41:41 -0400142 allReleaseNotesSummary = ""
Nick Anthony7ea8ca92020-02-11 11:11:37 -0500143 for groupId in releaseJsonObject["modules"]:
Nick Anthony6526d3b2020-07-09 16:41:41 -0400144 groupReleaseNotes, groupReleaseNotesSummary = generateGroupIdReleaseNotes(gitClient, releaseJsonObject, groupId, outputDir)
145 allReleaseNotes += "\n\n" + groupReleaseNotes
146 allReleaseNotesSummary += groupReleaseNotesSummary
Nick Anthony20819d42020-07-24 08:21:36 -0400147 formattedReleaseDate = str(MarkdownHeader(
148 HeaderType.H3,
149 str(MarkdownDate(releaseJsonObject["releaseDate"])))
150 ) + "\n"
Nick Anthony6526d3b2020-07-09 16:41:41 -0400151 allReleaseNotesSummary = formattedReleaseDate + allReleaseNotesSummary
152 allReleaseNotes = allReleaseNotesSummary + "\n" + allReleaseNotes
Nick Anthony48a57152020-04-23 07:38:58 -0400153 writeReleaseNotesToNewFile(outputDir, "all_androidx_release_notes.txt", allReleaseNotes)
Nick Anthony7ea8ca92020-02-11 11:11:37 -0500154
Nick Anthony48a57152020-04-23 07:38:58 -0400155def generateGroupIdReleaseNotes(gitClient, releaseJsonObject, groupId, outputDir):
Nick Anthony7ea8ca92020-02-11 11:11:37 -0500156 """ Creates the groupId release notes using the list of artifactId LibraryReleaseNotes
157 Groups artifactIds of the same version.
158
159 @param libraryReleaseNotesList The list of artifactId [LibraryReleaseNotes] objects which
160 are read in from the artifactId release note .json files
161 @param releaseDate The release date of the entire release
162 @param includeAllCommits Set to true to include all commits regardless of whether or not they
163 have the release notes tag
164 """
165 versionToArtifactRNMap = getVersionToReleaseNotesMap(releaseJsonObject, groupId)
166
167 groupReleaseNotesStringList = []
Nick Anthony6526d3b2020-07-09 16:41:41 -0400168 groupReleaseNotesSummaryList = []
Nick Anthony7ea8ca92020-02-11 11:11:37 -0500169 # For each version, collect and write the release notes for all artifactIds of that version
170 for (version, versionRNList) in versionToArtifactRNMap.items():
171 versionArtifactIds = []
172 versionGroupCommitList = []
173 fromSHA = ""
174 untilSHA = ""
175 groupIdCommonDir = versionRNList[0]["path"]
176 requiresSameVersion = versionRNList[0]["requiresSameVersion"]
177 for artifact in versionRNList:
178 versionArtifactIds.append(artifact["artifactId"])
179 ## Get and merge commits lists
Nick Anthony48a57152020-04-23 07:38:58 -0400180 artifactIdReleaseNotes = generateArtifactIdReleaseNotes(gitClient, artifact, releaseJsonObject["releaseDate"], releaseJsonObject["includeAllCommits"], outputDir)
Nick Anthony7ea8ca92020-02-11 11:11:37 -0500181 mergeCommitListBIntoCommitListA(
182 versionGroupCommitList,
183 artifactIdReleaseNotes.commitList
184 )
185 fromSHA = artifact["fromSHA"]
186 untilSHA = artifact["untilSHA"]
Nick Anthony8f18f3d2020-05-28 20:15:56 -0400187 groupIdCommonDir = getCommonPathPrefix(groupIdCommonDir, artifact["path"])
Nick Anthony7ea8ca92020-02-11 11:11:37 -0500188 for commit in versionGroupCommitList:
189 if isExcludedAuthorEmail(commit.authorEmail):
190 versionGroupCommitList.remove(commit)
191
192 releaseNotes = LibraryReleaseNotes(
193 groupId,
194 versionArtifactIds,
195 version,
196 releaseJsonObject["releaseDate"],
Nick Anthony917014b2020-05-08 08:24:09 -0400197 fromSHA if (fromSHA != "NULL") else "",
Nick Anthony7ea8ca92020-02-11 11:11:37 -0500198 untilSHA,
199 groupIdCommonDir,
200 requiresSameVersion,
201 versionGroupCommitList,
202 releaseJsonObject["includeAllCommits"]
203 )
204
205 groupReleaseNotesStringList.append(str(releaseNotes))
Nick Anthony6526d3b2020-07-09 16:41:41 -0400206 groupReleaseNotesSummaryList.append(str(releaseNotes.channelSummary))
Nick Anthony7ea8ca92020-02-11 11:11:37 -0500207
Nick Anthony7ea8ca92020-02-11 11:11:37 -0500208 completeGroupIdReleaseNotes = "\n\n".join((groupReleaseNotesStringList))
Nick Anthony6526d3b2020-07-09 16:41:41 -0400209 completeGroupReleaseNotesSummary = "".join(groupReleaseNotesSummaryList)
Nick Anthony7ea8ca92020-02-11 11:11:37 -0500210 writeGroupIdReleaseNotesToFile(
211 groupId,
Nick Anthony48a57152020-04-23 07:38:58 -0400212 completeGroupIdReleaseNotes,
Nick Anthony6526d3b2020-07-09 16:41:41 -0400213 completeGroupReleaseNotesSummary,
Nick Anthony48a57152020-04-23 07:38:58 -0400214 outputDir
Nick Anthony7ea8ca92020-02-11 11:11:37 -0500215 )
Nick Anthony6526d3b2020-07-09 16:41:41 -0400216 return completeGroupIdReleaseNotes, completeGroupReleaseNotesSummary
Nick Anthony7ea8ca92020-02-11 11:11:37 -0500217
218
Nick Anthony48a57152020-04-23 07:38:58 -0400219def generateArtifactIdReleaseNotes(gitClient, artifact, releaseDate, includeAllCommits, outputDir):
Nick Anthony7ea8ca92020-02-11 11:11:37 -0500220 # If there are is no fromCommit specified for this artifact, then simply return because
221 # we don't know how far back to query the commit log
222 fromSHA = artifact["fromSHA"]
223 if fromSHA == "NULL":
224 fromSHA = ""
Nick Anthony41831442020-04-15 18:12:35 -0400225
Nick Anthony7ea8ca92020-02-11 11:11:37 -0500226 untilSHA = artifact["untilSHA"]
227 if untilSHA == "NULL" or untilSHA == "":
228 untilSHA = "HEAD"
229
Nick Anthony41831442020-04-15 18:12:35 -0400230 commitList = gitClient.getGitLog(
231 fromExclusiveSha = fromSHA,
232 untilInclusiveSha = untilSHA,
233 keepMerges = False,
234 subProjectDir = artifact["path"]
235 )
Nick Anthony7ea8ca92020-02-11 11:11:37 -0500236
237 if len(commitList) == 0:
238 print_e("WARNING: Found no commits for %s:%s from " % (artifact["groupId"], artifact["artifactId"]) + \
239 "start SHA %s to end SHA %s. To double check, you can run " % (fromSHA, untilSHA) + \
240 "`git log --no-merges %s..%s -- %s` " % (fromSHA, untilSHA, artifact["path"]) + \
241 "in the root git directory")
242
243 for commit in commitList:
244 if isExcludedAuthorEmail(commit.authorEmail):
245 commitList.remove(commit)
246
247 artifactIdReleaseNotes = LibraryReleaseNotes(
248 artifact["groupId"],
249 [artifact["artifactId"]],
250 artifact["version"],
251 releaseDate,
252 fromSHA,
253 untilSHA,
254 artifact["path"],
255 False,
256 commitList,
257 includeAllCommits
258 )
259 writeArtifactIdReleaseNotesToFile(
260 artifact["groupId"],
261 artifact["artifactId"],
262 artifact["version"],
Nick Anthony48a57152020-04-23 07:38:58 -0400263 str(artifactIdReleaseNotes),
Nick Anthony6526d3b2020-07-09 16:41:41 -0400264 str(artifactIdReleaseNotes.channelSummary),
Nick Anthony48a57152020-04-23 07:38:58 -0400265 outputDir
Nick Anthony7ea8ca92020-02-11 11:11:37 -0500266 )
267 return artifactIdReleaseNotes
268
Nick Anthonyb1753a92019-12-11 10:49:39 -0500269def main(args):
270 # Parse arguments and check for existence of build ID or file
271 args = parser.parse_args()
272 if not args.date:
273 parser.error("You must specify a release date in Milliseconds since epoch")
274 sys.exit(1)
Nick Anthony48a57152020-04-23 07:38:58 -0400275 outputDir = "./out"
276 # Remove the local output dir so that leftover release notes from the previous run are removed
277 rm(outputDir)
Nick Anthony2d9b73c2020-06-29 21:24:55 -0400278 generateAllReleaseNotes(args.date, args.include_all_commits, outputDir)
Nick Anthony7ea8ca92020-02-11 11:11:37 -0500279 print("Successful.")
Nick Anthony48a57152020-04-23 07:38:58 -0400280 print("Release notes have been written to %s" % outputDir)
Nick Anthonyb1753a92019-12-11 10:49:39 -0500281
Nick Anthony7ea8ca92020-02-11 11:11:37 -0500282if __name__ == '__main__':
283 main(sys.argv)