blob: 9c82ae2d1e8096c8ea95176501e913eaff113a08 [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'
56 if markdownStringSection == "":
57 markdownStringSection = "\n%s\n\n%s" % (MarkdownComment(sectionHeader), markdownStringSection)
58 else:
59 markdownStringSection = "\n%s\n\n%s" % (sectionHeader, markdownStringSection)
60 return markdownStringSection
61
62 def __str__(self):
63 markdownString = ""
64 for commitType in CommitType:
65 markdownString += self.makeReleaseNotesSection(commitType)
66 return markdownString
67
68class GitilesDiffLogLink(MarkdownLink):
69 def __str__(self):
70 return "[%s](%s)" % (self.linkText, self.linkUrl)
71
72def getGitilesDiffLogLink(version, startSHA, endSHA, projectDir):
73 """
74 @param startSHA the SHA at which to start the diff log (exclusive)
75 @param endSHA the last SHA to include in the diff log (inclusive)
76 @param projectDir the local directory of the project, in relation to frameworks/support
77
78 @return A [MarkdownLink] to the public Gitiles diff log
79 """
80 baseGitilesUrl = "https://android.googlesource.com/platform/frameworks/support/+log/"
81 # The root project directory is already existent in the url path, so the directory here
82 # should be relative to frameworks/support/.
83 if ("frameworks/support" in projectDir):
84 print_e("Gitiles directory should be relative to frameworks/support; received incorrect directory: $projectDir")
85 exit(1)
86 # Remove extra preceeding directory slashes, if they exist
87 simplifiedProjectDir = projectDir
88 while (simplifiedProjectDir[0] == '/'):
89 simplifiedProjectDir = simplifiedProjectDir[1:]
90 return GitilesDiffLogLink("Version %s contains these commits." % version, "%s%s..%s/%s" % (baseGitilesUrl, startSHA, endSHA, simplifiedProjectDir))
91
92class LibraryHeader(MarkdownHeader):
93 """
94 Markdown class for a Library Header in the format:
95
96 ### Version <version> {:#<artifactIdTag-version>}
97
98 An artifactId tag is required because artifactIds may be can be grouped by version, in which case the tag is not obvious
99 """
100 def __init__(self, groupId="", version="", artifactIdTag=""):
101 self.markdownType = HeaderType.H3
102 self.text = "%s Version %s {:#%s%s}" % (groupId, version, artifactIdTag, version)
103
104class LibraryReleaseNotes:
105 """ Structured release notes class, that connects all parts of the release notes. Creates release
106 notes in the format:
107 <pre>
108 <[LibraryHeader]>
109 <Date>
110
111 `androidx.<groupId>:<artifactId>:<version>` is released. The commits included in this version
112 can be found <[MarkdownLink]>.
113
114 <[CommitMarkdownList]>
115 </pre>
116 """
117 def __init__(self, groupId, artifactIds, version, releaseDate, fromSHA, untilSHA, projectDir, requiresSameVersion, commitList=[], forceIncludeAllCommits=False):
118 """
119 @property groupId Library GroupId.
120 @property artifactIds List of ArtifactIds included in these release notes.
121 @property version Version of the library, assuming all artifactIds have the same version.
122 @property releaseDate Date the release will go live. Defaults to the current date.
123 @property fromSHA The oldest SHA to which to query for release notes. It will be
124 excluded from release notes, but the next newer SHA will be included.
125 @property untilSHA The newest SHA to be included in the release notes.
126 @property projectDir The filepath relative to the parent directory of the .git directory.
127 @property requiresSameVersion True if the groupId of this module requires the same version for
128 all artifactIds in the groupId. When true, uses the GroupId for the release notes
129 header. When false, uses the list of artifactIds for the header.
130 @property commitList The initial list of Commits to include in these release notes. Defaults to an
131 empty list. Users can always add more commits with [LibraryReleaseNotes.addCommit]
132 @param forceIncludeAllCommits Set to true to include all commits, both with and without a
133 release note field in the commit message. Defaults to false, which means only commits
134 with a release note field are included in the release notes.
135 """
136 self.groupId = groupId
137 self.artifactIds = artifactIds
138 self.version = version
139 self.releaseDate = releaseDate
140 self.fromSHA = fromSHA
141 self.untilSHA = untilSHA
142 self.projectDir = projectDir
143 self.commitList = commitList
144 self.requiresSameVersion = requiresSameVersion
145 self.forceIncludeAllCommits = forceIncludeAllCommits
146 self.diffLogLink = MarkdownLink()
147 self.commits = commitList
148 self.commitMarkdownList = CommitMarkdownList(commitList, forceIncludeAllCommits)
149 self.summary = ""
150 self.bugsFixed = []
151
152 if version == "" or groupId == "":
153 raise RuntimeError("Tried to create Library Release Notes Header without setting " +
154 "the groupId or version!")
155 if requiresSameVersion:
156 shortenedGroupId = groupId.replace("androidx.", "").capitalize()
157 self.header = LibraryHeader(shortenedGroupId, version)
158 else:
159 forrmattedArtifactIds = (" ".join(artifactIds) + " ").title()
160 artifactIdTag = ""
161 if len(artifactIds) == 1:
162 artifactIdTag = artifactIds[0] + "-"
163 self.header = LibraryHeader(forrmattedArtifactIds, version, artifactIdTag)
164 self.diffLogLink = getGitilesDiffLogLink(version, fromSHA, untilSHA, projectDir)
165
166 def getFormattedReleaseSummary(self):
167 numberArtifacts = len(self.artifactIds)
168 for i in range(0, numberArtifacts):
169 currentArtifactId = self.artifactIds[i]
170 if numberArtifacts == 1:
171 self.summary = "`%s:%s:%s` is released. " % (self.groupId, currentArtifactId, self.version)
172 elif numberArtifacts == 2:
173 if i == 0: self.summary = "`%s:%s:%s` and " % (self.groupId, currentArtifactId, self.version)
174 if i == 1: self.summary += "`%s:%s:%s` are released. " % (self.groupId, currentArtifactId, self.version)
175 else:
176 if (i < numberArtifacts - 1):
177 self.summary += "`%s:%s:%s`, " % (self.groupId, currentArtifactId, self.version)
178 else:
179 self.summary += "and `%s:%s:%s` are released. " % (self.groupId, currentArtifactId, self.version)
180
181 self.summary += "%s\n" % self.diffLogLink
182 return self.summary
183
184 def addCommit(self, newCommit):
185 for bug in newCommit.bugs:
186 bugsFixed.append(bug)
187 commits.append(newCommit)
188 commitMarkdownList.add(newCommit)
189
190 def __str__(self):
191 return "%s\n%s\n\n%s%s" % (self.header, self.releaseDate, self.getFormattedReleaseSummary(), self.commitMarkdownList)