blob: f4803d45983eee334e0745eb1224c75e1c4a6418 [file] [log] [blame]
Nick Anthony7ea8ca92020-02-11 11:11:37 -05001#!/usr/bin/python3
2#
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 ReleaseNoteMarkdown import *
Nick Anthony2d9b73c2020-06-29 21:24:55 -040019from GitClient import CommitType, getTitleFromCommitType, getChangeIdAOSPLink, getBuganizerLink
Nick Anthony7ea8ca92020-02-11 11:11:37 -050020
21class CommitMarkdownList:
22 """Generates the markdown list of commits with sections defined by enum [CommitType], in the format:
23
24 **New Features**
25 - <[Commit.summary]> <[getChangeIdAOSPLink]> <[getBuganizerLink] 1> <[getBuganizerLink] 2>...
26 **API Changes**
27 - <[Commit.summary]> <[getChangeIdAOSPLink]> <[getBuganizerLink] 1> <[getBuganizerLink] 2>...
28 **Bug Fixes**
29 - <[Commit.summary]> <[getChangeIdAOSPLink]> <[getBuganizerLink] 1> <[getBuganizerLink] 2>...
30 **External Contribution**
31 - <[Commit.summary]> <[getChangeIdAOSPLink]> <[getBuganizerLink] 1> <[getBuganizerLink] 2>...
32 """
33 def __init__(self, commits=[], forceIncludeAllCommits=False):
34 self.forceIncludeAllCommits = forceIncludeAllCommits
35 self.commits = commits
36
37 def add(self, commit):
38 self.commits.append(commit)
39
40 def getListItemStr(self):
41 return "- "
42
Nick Anthony2d9b73c2020-06-29 21:24:55 -040043 def formatReleaseNoteString(self, commit):
Nick Anthony4c1cad72020-06-07 18:26:37 -040044 if commit.releaseNote != "":
45 releaseNoteString = commit.releaseNote
46 else:
47 releaseNoteString = self.getListItemStr() + commit.summary
48 newLineCharCount = releaseNoteString.count("\n")
49 if releaseNoteString[-1] == "\n":
50 newLineCharCount = newLineCharCount - 1
51 releaseNoteString = releaseNoteString.replace("\n", "\n ", newLineCharCount)
52 releaseNoteString += " (" + str(getChangeIdAOSPLink(commit.changeId))
53 for bug in commit.bugs:
54 releaseNoteString += ", " + str(getBuganizerLink(bug))
55 releaseNoteString += ")"
56 return self.getListItemStr() + releaseNoteString
57
Nick Anthony7ea8ca92020-02-11 11:11:37 -050058 def makeReleaseNotesSection(self, sectionCommitType):
59 sectionHeader = MarkdownBoldText(getTitleFromCommitType(sectionCommitType))
60 markdownStringSection = ""
61 for commit in self.commits:
62 if commit.changeType != sectionCommitType: continue
Nick Anthony4c1cad72020-06-07 18:26:37 -040063 commitString = self.formatReleaseNoteString(commit)
Nick Anthony7ea8ca92020-02-11 11:11:37 -050064 if self.forceIncludeAllCommits or commit.releaseNote != "":
65 markdownStringSection = markdownStringSection + commitString
66 if markdownStringSection[-1] != '\n':
67 markdownStringSection += '\n'
Nick Anthony8f18f3d2020-05-28 20:15:56 -040068 markdownStringSection = "\n%s\n\n%s" % (sectionHeader, markdownStringSection)
Nick Anthony7ea8ca92020-02-11 11:11:37 -050069 return markdownStringSection
70
71 def __str__(self):
72 markdownString = ""
73 for commitType in CommitType:
74 markdownString += self.makeReleaseNotesSection(commitType)
75 return markdownString
76
77class GitilesDiffLogLink(MarkdownLink):
78 def __str__(self):
79 return "[%s](%s)" % (self.linkText, self.linkUrl)
80
81def getGitilesDiffLogLink(version, startSHA, endSHA, projectDir):
82 """
83 @param startSHA the SHA at which to start the diff log (exclusive)
84 @param endSHA the last SHA to include in the diff log (inclusive)
85 @param projectDir the local directory of the project, in relation to frameworks/support
86
87 @return A [MarkdownLink] to the public Gitiles diff log
88 """
89 baseGitilesUrl = "https://android.googlesource.com/platform/frameworks/support/+log/"
90 # The root project directory is already existent in the url path, so the directory here
91 # should be relative to frameworks/support/.
92 if ("frameworks/support" in projectDir):
93 print_e("Gitiles directory should be relative to frameworks/support; received incorrect directory: $projectDir")
94 exit(1)
Nick Anthony917014b2020-05-08 08:24:09 -040095 if startSHA != "":
96 return GitilesDiffLogLink("Version %s contains these commits." % version, "%s%s..%s/%s" % (baseGitilesUrl, startSHA, endSHA, projectDir))
97 else:
98 return GitilesDiffLogLink("Version %s contains these commits." % version, "%s%s/%s" % (baseGitilesUrl, endSHA, projectDir))
Nick Anthony7ea8ca92020-02-11 11:11:37 -050099
100class LibraryHeader(MarkdownHeader):
101 """
102 Markdown class for a Library Header in the format:
103
104 ### Version <version> {:#<artifactIdTag-version>}
105
106 An artifactId tag is required because artifactIds may be can be grouped by version, in which case the tag is not obvious
107 """
108 def __init__(self, groupId="", version="", artifactIdTag=""):
109 self.markdownType = HeaderType.H3
110 self.text = "%s Version %s {:#%s%s}" % (groupId, version, artifactIdTag, version)
111
Nick Anthony6526d3b2020-07-09 16:41:41 -0400112class ChannelSummaryItem:
113 """Generates the summary list item in the channel.md pages, which take the format:
114
115 * [<Title> Version <version>](/jetpack/androidx/releases/<groupid>#<version>)
116
117 where <header> is usually the GroupId.
118 """
119 def __init__(self, formattedTitle, groupId, version, artifactIdTag=""):
120 self.text = "* [%s Version %s](/jetpack/androidx/releases/%s#%s%s)\n" % (formattedTitle, version, groupId.lower(), artifactIdTag, version)
121
122 def __str__(self):
123 return self.text
124
Nick Anthony7ea8ca92020-02-11 11:11:37 -0500125class LibraryReleaseNotes:
126 """ Structured release notes class, that connects all parts of the release notes. Creates release
127 notes in the format:
128 <pre>
129 <[LibraryHeader]>
130 <Date>
131
132 `androidx.<groupId>:<artifactId>:<version>` is released. The commits included in this version
133 can be found <[MarkdownLink]>.
134
135 <[CommitMarkdownList]>
136 </pre>
137 """
138 def __init__(self, groupId, artifactIds, version, releaseDate, fromSHA, untilSHA, projectDir, requiresSameVersion, commitList=[], forceIncludeAllCommits=False):
139 """
140 @property groupId Library GroupId.
141 @property artifactIds List of ArtifactIds included in these release notes.
142 @property version Version of the library, assuming all artifactIds have the same version.
143 @property releaseDate Date the release will go live. Defaults to the current date.
144 @property fromSHA The oldest SHA to which to query for release notes. It will be
145 excluded from release notes, but the next newer SHA will be included.
146 @property untilSHA The newest SHA to be included in the release notes.
147 @property projectDir The filepath relative to the parent directory of the .git directory.
148 @property requiresSameVersion True if the groupId of this module requires the same version for
149 all artifactIds in the groupId. When true, uses the GroupId for the release notes
150 header. When false, uses the list of artifactIds for the header.
151 @property commitList The initial list of Commits to include in these release notes. Defaults to an
152 empty list. Users can always add more commits with [LibraryReleaseNotes.addCommit]
153 @param forceIncludeAllCommits Set to true to include all commits, both with and without a
154 release note field in the commit message. Defaults to false, which means only commits
155 with a release note field are included in the release notes.
156 """
157 self.groupId = groupId
158 self.artifactIds = artifactIds
159 self.version = version
Nick Anthonyc687a4b2020-04-23 07:59:13 -0400160 self.releaseDate = MarkdownDate(releaseDate)
Nick Anthony7ea8ca92020-02-11 11:11:37 -0500161 self.fromSHA = fromSHA
162 self.untilSHA = untilSHA
163 self.projectDir = projectDir
164 self.commitList = commitList
165 self.requiresSameVersion = requiresSameVersion
166 self.forceIncludeAllCommits = forceIncludeAllCommits
167 self.diffLogLink = MarkdownLink()
168 self.commits = commitList
169 self.commitMarkdownList = CommitMarkdownList(commitList, forceIncludeAllCommits)
170 self.summary = ""
171 self.bugsFixed = []
Nick Anthony6526d3b2020-07-09 16:41:41 -0400172 self.channelSummary = None
Nick Anthony7ea8ca92020-02-11 11:11:37 -0500173
174 if version == "" or groupId == "":
175 raise RuntimeError("Tried to create Library Release Notes Header without setting " +
176 "the groupId or version!")
177 if requiresSameVersion:
Nick Anthony8f18f3d2020-05-28 20:15:56 -0400178 formattedGroupId = groupId.replace("androidx.", "")
179 formattedGroupId = self.capitalizeTitle(formattedGroupId)
180 self.header = LibraryHeader(formattedGroupId, version)
Nick Anthony6526d3b2020-07-09 16:41:41 -0400181 self.channelSummary = ChannelSummaryItem(formattedGroupId, formattedGroupId, version)
Nick Anthony7ea8ca92020-02-11 11:11:37 -0500182 else:
Nick Anthony8f18f3d2020-05-28 20:15:56 -0400183 artifactIdTag = artifactIds[0] + "-" if len(artifactIds) == 1 else ""
184 formattedArtifactIds = (" ".join(artifactIds))
185 if len(artifactIds) > 3:
186 formattedArtifactIds = groupId.replace("androidx.", "")
187 formattedArtifactIds = self.capitalizeTitle(formattedArtifactIds)
188 self.header = LibraryHeader(formattedArtifactIds, version, artifactIdTag)
Nick Anthony6526d3b2020-07-09 16:41:41 -0400189 formattedGroupId = groupId.replace("androidx.", "")
190 self.channelSummary = ChannelSummaryItem(formattedArtifactIds, formattedGroupId, version, artifactIdTag)
Nick Anthony7ea8ca92020-02-11 11:11:37 -0500191 self.diffLogLink = getGitilesDiffLogLink(version, fromSHA, untilSHA, projectDir)
192
193 def getFormattedReleaseSummary(self):
194 numberArtifacts = len(self.artifactIds)
195 for i in range(0, numberArtifacts):
196 currentArtifactId = self.artifactIds[i]
197 if numberArtifacts == 1:
198 self.summary = "`%s:%s:%s` is released. " % (self.groupId, currentArtifactId, self.version)
199 elif numberArtifacts == 2:
200 if i == 0: self.summary = "`%s:%s:%s` and " % (self.groupId, currentArtifactId, self.version)
201 if i == 1: self.summary += "`%s:%s:%s` are released. " % (self.groupId, currentArtifactId, self.version)
Nick Anthony8f18f3d2020-05-28 20:15:56 -0400202 elif numberArtifacts == 3:
Nick Anthony7ea8ca92020-02-11 11:11:37 -0500203 if (i < numberArtifacts - 1):
204 self.summary += "`%s:%s:%s`, " % (self.groupId, currentArtifactId, self.version)
205 else:
206 self.summary += "and `%s:%s:%s` are released. " % (self.groupId, currentArtifactId, self.version)
Nick Anthony8f18f3d2020-05-28 20:15:56 -0400207 else:
208 commonArtifactIdSubstring = self.artifactIds[0].split('-')[0]
209 self.summary = "`%s:%s-*:%s` is released. " % (
210 self.groupId,
211 commonArtifactIdSubstring,
212 self.version
213 )
Nick Anthony7ea8ca92020-02-11 11:11:37 -0500214 self.summary += "%s\n" % self.diffLogLink
215 return self.summary
216
Nick Anthony8f18f3d2020-05-28 20:15:56 -0400217 def capitalizeTitle(self, artifactWord):
218 artifactWord = artifactWord.title()
219 keywords = ["Animated", "Animation", "Callback", "Compat", "Drawable", "File", "Layout",
220 "Pager", "Pane", "Parcelable", "Provider", "Refresh", "SQL", "State", "TV",
221 "Target", "View", "Inflater"]
222 for keyword in keywords:
223 artifactWord = artifactWord.replace(keyword.lower(), keyword)
224 return artifactWord
225
Nick Anthony7ea8ca92020-02-11 11:11:37 -0500226 def addCommit(self, newCommit):
227 for bug in newCommit.bugs:
228 bugsFixed.append(bug)
229 commits.append(newCommit)
230 commitMarkdownList.add(newCommit)
231
232 def __str__(self):
233 return "%s\n%s\n\n%s%s" % (self.header, self.releaseDate, self.getFormattedReleaseSummary(), self.commitMarkdownList)