blob: 7c8f4504700467dcb8c9b91490a953497dd5aba7 [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
43 def makeReleaseNotesSection(self, sectionCommitType):
44 sectionHeader = MarkdownBoldText(getTitleFromCommitType(sectionCommitType))
45 markdownStringSection = ""
46 for commit in self.commits:
47 if commit.changeType != sectionCommitType: continue
48 if commit.releaseNote != "":
49 commitString = self.getListItemStr() + commit.getReleaseNoteString()
50 else:
51 commitString = self.getListItemStr() + str(commit)
52 if self.forceIncludeAllCommits or commit.releaseNote != "":
53 markdownStringSection = markdownStringSection + commitString
54 if markdownStringSection[-1] != '\n':
55 markdownStringSection += '\n'
Nick Anthony8f18f3d2020-05-28 20:15:56 -040056 markdownStringSection = "\n%s\n\n%s" % (sectionHeader, markdownStringSection)
Nick Anthony7ea8ca92020-02-11 11:11:37 -050057 return markdownStringSection
58
59 def __str__(self):
60 markdownString = ""
61 for commitType in CommitType:
62 markdownString += self.makeReleaseNotesSection(commitType)
63 return markdownString
64
65class GitilesDiffLogLink(MarkdownLink):
66 def __str__(self):
67 return "[%s](%s)" % (self.linkText, self.linkUrl)
68
69def getGitilesDiffLogLink(version, startSHA, endSHA, projectDir):
70 """
71 @param startSHA the SHA at which to start the diff log (exclusive)
72 @param endSHA the last SHA to include in the diff log (inclusive)
73 @param projectDir the local directory of the project, in relation to frameworks/support
74
75 @return A [MarkdownLink] to the public Gitiles diff log
76 """
77 baseGitilesUrl = "https://android.googlesource.com/platform/frameworks/support/+log/"
78 # The root project directory is already existent in the url path, so the directory here
79 # should be relative to frameworks/support/.
80 if ("frameworks/support" in projectDir):
81 print_e("Gitiles directory should be relative to frameworks/support; received incorrect directory: $projectDir")
82 exit(1)
Nick Anthony917014b2020-05-08 08:24:09 -040083 if startSHA != "":
84 return GitilesDiffLogLink("Version %s contains these commits." % version, "%s%s..%s/%s" % (baseGitilesUrl, startSHA, endSHA, projectDir))
85 else:
86 return GitilesDiffLogLink("Version %s contains these commits." % version, "%s%s/%s" % (baseGitilesUrl, endSHA, projectDir))
Nick Anthony7ea8ca92020-02-11 11:11:37 -050087
88class LibraryHeader(MarkdownHeader):
89 """
90 Markdown class for a Library Header in the format:
91
92 ### Version <version> {:#<artifactIdTag-version>}
93
94 An artifactId tag is required because artifactIds may be can be grouped by version, in which case the tag is not obvious
95 """
96 def __init__(self, groupId="", version="", artifactIdTag=""):
97 self.markdownType = HeaderType.H3
98 self.text = "%s Version %s {:#%s%s}" % (groupId, version, artifactIdTag, version)
99
100class LibraryReleaseNotes:
101 """ Structured release notes class, that connects all parts of the release notes. Creates release
102 notes in the format:
103 <pre>
104 <[LibraryHeader]>
105 <Date>
106
107 `androidx.<groupId>:<artifactId>:<version>` is released. The commits included in this version
108 can be found <[MarkdownLink]>.
109
110 <[CommitMarkdownList]>
111 </pre>
112 """
113 def __init__(self, groupId, artifactIds, version, releaseDate, fromSHA, untilSHA, projectDir, requiresSameVersion, commitList=[], forceIncludeAllCommits=False):
114 """
115 @property groupId Library GroupId.
116 @property artifactIds List of ArtifactIds included in these release notes.
117 @property version Version of the library, assuming all artifactIds have the same version.
118 @property releaseDate Date the release will go live. Defaults to the current date.
119 @property fromSHA The oldest SHA to which to query for release notes. It will be
120 excluded from release notes, but the next newer SHA will be included.
121 @property untilSHA The newest SHA to be included in the release notes.
122 @property projectDir The filepath relative to the parent directory of the .git directory.
123 @property requiresSameVersion True if the groupId of this module requires the same version for
124 all artifactIds in the groupId. When true, uses the GroupId for the release notes
125 header. When false, uses the list of artifactIds for the header.
126 @property commitList The initial list of Commits to include in these release notes. Defaults to an
127 empty list. Users can always add more commits with [LibraryReleaseNotes.addCommit]
128 @param forceIncludeAllCommits Set to true to include all commits, both with and without a
129 release note field in the commit message. Defaults to false, which means only commits
130 with a release note field are included in the release notes.
131 """
132 self.groupId = groupId
133 self.artifactIds = artifactIds
134 self.version = version
Nick Anthonyc687a4b2020-04-23 07:59:13 -0400135 self.releaseDate = MarkdownDate(releaseDate)
Nick Anthony7ea8ca92020-02-11 11:11:37 -0500136 self.fromSHA = fromSHA
137 self.untilSHA = untilSHA
138 self.projectDir = projectDir
139 self.commitList = commitList
140 self.requiresSameVersion = requiresSameVersion
141 self.forceIncludeAllCommits = forceIncludeAllCommits
142 self.diffLogLink = MarkdownLink()
143 self.commits = commitList
144 self.commitMarkdownList = CommitMarkdownList(commitList, forceIncludeAllCommits)
145 self.summary = ""
146 self.bugsFixed = []
147
148 if version == "" or groupId == "":
149 raise RuntimeError("Tried to create Library Release Notes Header without setting " +
150 "the groupId or version!")
151 if requiresSameVersion:
Nick Anthony8f18f3d2020-05-28 20:15:56 -0400152 formattedGroupId = groupId.replace("androidx.", "")
153 formattedGroupId = self.capitalizeTitle(formattedGroupId)
154 self.header = LibraryHeader(formattedGroupId, version)
Nick Anthony7ea8ca92020-02-11 11:11:37 -0500155 else:
Nick Anthony8f18f3d2020-05-28 20:15:56 -0400156 artifactIdTag = artifactIds[0] + "-" if len(artifactIds) == 1 else ""
157 formattedArtifactIds = (" ".join(artifactIds))
158 if len(artifactIds) > 3:
159 formattedArtifactIds = groupId.replace("androidx.", "")
160 formattedArtifactIds = self.capitalizeTitle(formattedArtifactIds)
161 self.header = LibraryHeader(formattedArtifactIds, version, artifactIdTag)
Nick Anthony7ea8ca92020-02-11 11:11:37 -0500162 self.diffLogLink = getGitilesDiffLogLink(version, fromSHA, untilSHA, projectDir)
163
164 def getFormattedReleaseSummary(self):
165 numberArtifacts = len(self.artifactIds)
166 for i in range(0, numberArtifacts):
167 currentArtifactId = self.artifactIds[i]
168 if numberArtifacts == 1:
169 self.summary = "`%s:%s:%s` is released. " % (self.groupId, currentArtifactId, self.version)
170 elif numberArtifacts == 2:
171 if i == 0: self.summary = "`%s:%s:%s` and " % (self.groupId, currentArtifactId, self.version)
172 if i == 1: self.summary += "`%s:%s:%s` are released. " % (self.groupId, currentArtifactId, self.version)
Nick Anthony8f18f3d2020-05-28 20:15:56 -0400173 elif numberArtifacts == 3:
Nick Anthony7ea8ca92020-02-11 11:11:37 -0500174 if (i < numberArtifacts - 1):
175 self.summary += "`%s:%s:%s`, " % (self.groupId, currentArtifactId, self.version)
176 else:
177 self.summary += "and `%s:%s:%s` are released. " % (self.groupId, currentArtifactId, self.version)
Nick Anthony8f18f3d2020-05-28 20:15:56 -0400178 else:
179 commonArtifactIdSubstring = self.artifactIds[0].split('-')[0]
180 self.summary = "`%s:%s-*:%s` is released. " % (
181 self.groupId,
182 commonArtifactIdSubstring,
183 self.version
184 )
Nick Anthony7ea8ca92020-02-11 11:11:37 -0500185 self.summary += "%s\n" % self.diffLogLink
186 return self.summary
187
Nick Anthony8f18f3d2020-05-28 20:15:56 -0400188 def capitalizeTitle(self, artifactWord):
189 artifactWord = artifactWord.title()
190 keywords = ["Animated", "Animation", "Callback", "Compat", "Drawable", "File", "Layout",
191 "Pager", "Pane", "Parcelable", "Provider", "Refresh", "SQL", "State", "TV",
192 "Target", "View", "Inflater"]
193 for keyword in keywords:
194 artifactWord = artifactWord.replace(keyword.lower(), keyword)
195 return artifactWord
196
Nick Anthony7ea8ca92020-02-11 11:11:37 -0500197 def addCommit(self, newCommit):
198 for bug in newCommit.bugs:
199 bugsFixed.append(bug)
200 commits.append(newCommit)
201 commitMarkdownList.add(newCommit)
202
203 def __str__(self):
204 return "%s\n%s\n\n%s%s" % (self.header, self.releaseDate, self.getFormattedReleaseSummary(), self.commitMarkdownList)