blob: fd86098a3916390567edb5d1ffaf18d8106a9b4c [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 *
19from GitClient import CommitType, getTitleFromCommitType
20
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 Anthony4c1cad72020-06-07 18:26:37 -040043 def formatReleaseNoteString(self):
44 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
112class LibraryReleaseNotes:
113 """ Structured release notes class, that connects all parts of the release notes. Creates release
114 notes in the format:
115 <pre>
116 <[LibraryHeader]>
117 <Date>
118
119 `androidx.<groupId>:<artifactId>:<version>` is released. The commits included in this version
120 can be found <[MarkdownLink]>.
121
122 <[CommitMarkdownList]>
123 </pre>
124 """
125 def __init__(self, groupId, artifactIds, version, releaseDate, fromSHA, untilSHA, projectDir, requiresSameVersion, commitList=[], forceIncludeAllCommits=False):
126 """
127 @property groupId Library GroupId.
128 @property artifactIds List of ArtifactIds included in these release notes.
129 @property version Version of the library, assuming all artifactIds have the same version.
130 @property releaseDate Date the release will go live. Defaults to the current date.
131 @property fromSHA The oldest SHA to which to query for release notes. It will be
132 excluded from release notes, but the next newer SHA will be included.
133 @property untilSHA The newest SHA to be included in the release notes.
134 @property projectDir The filepath relative to the parent directory of the .git directory.
135 @property requiresSameVersion True if the groupId of this module requires the same version for
136 all artifactIds in the groupId. When true, uses the GroupId for the release notes
137 header. When false, uses the list of artifactIds for the header.
138 @property commitList The initial list of Commits to include in these release notes. Defaults to an
139 empty list. Users can always add more commits with [LibraryReleaseNotes.addCommit]
140 @param forceIncludeAllCommits Set to true to include all commits, both with and without a
141 release note field in the commit message. Defaults to false, which means only commits
142 with a release note field are included in the release notes.
143 """
144 self.groupId = groupId
145 self.artifactIds = artifactIds
146 self.version = version
Nick Anthonyc687a4b2020-04-23 07:59:13 -0400147 self.releaseDate = MarkdownDate(releaseDate)
Nick Anthony7ea8ca92020-02-11 11:11:37 -0500148 self.fromSHA = fromSHA
149 self.untilSHA = untilSHA
150 self.projectDir = projectDir
151 self.commitList = commitList
152 self.requiresSameVersion = requiresSameVersion
153 self.forceIncludeAllCommits = forceIncludeAllCommits
154 self.diffLogLink = MarkdownLink()
155 self.commits = commitList
156 self.commitMarkdownList = CommitMarkdownList(commitList, forceIncludeAllCommits)
157 self.summary = ""
158 self.bugsFixed = []
159
160 if version == "" or groupId == "":
161 raise RuntimeError("Tried to create Library Release Notes Header without setting " +
162 "the groupId or version!")
163 if requiresSameVersion:
Nick Anthony8f18f3d2020-05-28 20:15:56 -0400164 formattedGroupId = groupId.replace("androidx.", "")
165 formattedGroupId = self.capitalizeTitle(formattedGroupId)
166 self.header = LibraryHeader(formattedGroupId, version)
Nick Anthony7ea8ca92020-02-11 11:11:37 -0500167 else:
Nick Anthony8f18f3d2020-05-28 20:15:56 -0400168 artifactIdTag = artifactIds[0] + "-" if len(artifactIds) == 1 else ""
169 formattedArtifactIds = (" ".join(artifactIds))
170 if len(artifactIds) > 3:
171 formattedArtifactIds = groupId.replace("androidx.", "")
172 formattedArtifactIds = self.capitalizeTitle(formattedArtifactIds)
173 self.header = LibraryHeader(formattedArtifactIds, version, artifactIdTag)
Nick Anthony7ea8ca92020-02-11 11:11:37 -0500174 self.diffLogLink = getGitilesDiffLogLink(version, fromSHA, untilSHA, projectDir)
175
176 def getFormattedReleaseSummary(self):
177 numberArtifacts = len(self.artifactIds)
178 for i in range(0, numberArtifacts):
179 currentArtifactId = self.artifactIds[i]
180 if numberArtifacts == 1:
181 self.summary = "`%s:%s:%s` is released. " % (self.groupId, currentArtifactId, self.version)
182 elif numberArtifacts == 2:
183 if i == 0: self.summary = "`%s:%s:%s` and " % (self.groupId, currentArtifactId, self.version)
184 if i == 1: self.summary += "`%s:%s:%s` are released. " % (self.groupId, currentArtifactId, self.version)
Nick Anthony8f18f3d2020-05-28 20:15:56 -0400185 elif numberArtifacts == 3:
Nick Anthony7ea8ca92020-02-11 11:11:37 -0500186 if (i < numberArtifacts - 1):
187 self.summary += "`%s:%s:%s`, " % (self.groupId, currentArtifactId, self.version)
188 else:
189 self.summary += "and `%s:%s:%s` are released. " % (self.groupId, currentArtifactId, self.version)
Nick Anthony8f18f3d2020-05-28 20:15:56 -0400190 else:
191 commonArtifactIdSubstring = self.artifactIds[0].split('-')[0]
192 self.summary = "`%s:%s-*:%s` is released. " % (
193 self.groupId,
194 commonArtifactIdSubstring,
195 self.version
196 )
Nick Anthony7ea8ca92020-02-11 11:11:37 -0500197 self.summary += "%s\n" % self.diffLogLink
198 return self.summary
199
Nick Anthony8f18f3d2020-05-28 20:15:56 -0400200 def capitalizeTitle(self, artifactWord):
201 artifactWord = artifactWord.title()
202 keywords = ["Animated", "Animation", "Callback", "Compat", "Drawable", "File", "Layout",
203 "Pager", "Pane", "Parcelable", "Provider", "Refresh", "SQL", "State", "TV",
204 "Target", "View", "Inflater"]
205 for keyword in keywords:
206 artifactWord = artifactWord.replace(keyword.lower(), keyword)
207 return artifactWord
208
Nick Anthony7ea8ca92020-02-11 11:11:37 -0500209 def addCommit(self, newCommit):
210 for bug in newCommit.bugs:
211 bugsFixed.append(bug)
212 commits.append(newCommit)
213 commitMarkdownList.add(newCommit)
214
215 def __str__(self):
216 return "%s\n%s\n\n%s%s" % (self.header, self.releaseDate, self.getFormattedReleaseSummary(), self.commitMarkdownList)