blob: f4803d45983eee334e0745eb1224c75e1c4a6418 [file] [log] [blame]
#!/usr/bin/python3
#
# Copyright (C) 2020 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
from ReleaseNoteMarkdown import *
from GitClient import CommitType, getTitleFromCommitType, getChangeIdAOSPLink, getBuganizerLink
class CommitMarkdownList:
"""Generates the markdown list of commits with sections defined by enum [CommitType], in the format:
**New Features**
- <[Commit.summary]> <[getChangeIdAOSPLink]> <[getBuganizerLink] 1> <[getBuganizerLink] 2>...
**API Changes**
- <[Commit.summary]> <[getChangeIdAOSPLink]> <[getBuganizerLink] 1> <[getBuganizerLink] 2>...
**Bug Fixes**
- <[Commit.summary]> <[getChangeIdAOSPLink]> <[getBuganizerLink] 1> <[getBuganizerLink] 2>...
**External Contribution**
- <[Commit.summary]> <[getChangeIdAOSPLink]> <[getBuganizerLink] 1> <[getBuganizerLink] 2>...
"""
def __init__(self, commits=[], forceIncludeAllCommits=False):
self.forceIncludeAllCommits = forceIncludeAllCommits
self.commits = commits
def add(self, commit):
self.commits.append(commit)
def getListItemStr(self):
return "- "
def formatReleaseNoteString(self, commit):
if commit.releaseNote != "":
releaseNoteString = commit.releaseNote
else:
releaseNoteString = self.getListItemStr() + commit.summary
newLineCharCount = releaseNoteString.count("\n")
if releaseNoteString[-1] == "\n":
newLineCharCount = newLineCharCount - 1
releaseNoteString = releaseNoteString.replace("\n", "\n ", newLineCharCount)
releaseNoteString += " (" + str(getChangeIdAOSPLink(commit.changeId))
for bug in commit.bugs:
releaseNoteString += ", " + str(getBuganizerLink(bug))
releaseNoteString += ")"
return self.getListItemStr() + releaseNoteString
def makeReleaseNotesSection(self, sectionCommitType):
sectionHeader = MarkdownBoldText(getTitleFromCommitType(sectionCommitType))
markdownStringSection = ""
for commit in self.commits:
if commit.changeType != sectionCommitType: continue
commitString = self.formatReleaseNoteString(commit)
if self.forceIncludeAllCommits or commit.releaseNote != "":
markdownStringSection = markdownStringSection + commitString
if markdownStringSection[-1] != '\n':
markdownStringSection += '\n'
markdownStringSection = "\n%s\n\n%s" % (sectionHeader, markdownStringSection)
return markdownStringSection
def __str__(self):
markdownString = ""
for commitType in CommitType:
markdownString += self.makeReleaseNotesSection(commitType)
return markdownString
class GitilesDiffLogLink(MarkdownLink):
def __str__(self):
return "[%s](%s)" % (self.linkText, self.linkUrl)
def getGitilesDiffLogLink(version, startSHA, endSHA, projectDir):
"""
@param startSHA the SHA at which to start the diff log (exclusive)
@param endSHA the last SHA to include in the diff log (inclusive)
@param projectDir the local directory of the project, in relation to frameworks/support
@return A [MarkdownLink] to the public Gitiles diff log
"""
baseGitilesUrl = "https://android.googlesource.com/platform/frameworks/support/+log/"
# The root project directory is already existent in the url path, so the directory here
# should be relative to frameworks/support/.
if ("frameworks/support" in projectDir):
print_e("Gitiles directory should be relative to frameworks/support; received incorrect directory: $projectDir")
exit(1)
if startSHA != "":
return GitilesDiffLogLink("Version %s contains these commits." % version, "%s%s..%s/%s" % (baseGitilesUrl, startSHA, endSHA, projectDir))
else:
return GitilesDiffLogLink("Version %s contains these commits." % version, "%s%s/%s" % (baseGitilesUrl, endSHA, projectDir))
class LibraryHeader(MarkdownHeader):
"""
Markdown class for a Library Header in the format:
### Version <version> {:#<artifactIdTag-version>}
An artifactId tag is required because artifactIds may be can be grouped by version, in which case the tag is not obvious
"""
def __init__(self, groupId="", version="", artifactIdTag=""):
self.markdownType = HeaderType.H3
self.text = "%s Version %s {:#%s%s}" % (groupId, version, artifactIdTag, version)
class ChannelSummaryItem:
"""Generates the summary list item in the channel.md pages, which take the format:
* [<Title> Version <version>](/jetpack/androidx/releases/<groupid>#<version>)
where <header> is usually the GroupId.
"""
def __init__(self, formattedTitle, groupId, version, artifactIdTag=""):
self.text = "* [%s Version %s](/jetpack/androidx/releases/%s#%s%s)\n" % (formattedTitle, version, groupId.lower(), artifactIdTag, version)
def __str__(self):
return self.text
class LibraryReleaseNotes:
""" Structured release notes class, that connects all parts of the release notes. Creates release
notes in the format:
<pre>
<[LibraryHeader]>
<Date>
`androidx.<groupId>:<artifactId>:<version>` is released. The commits included in this version
can be found <[MarkdownLink]>.
<[CommitMarkdownList]>
</pre>
"""
def __init__(self, groupId, artifactIds, version, releaseDate, fromSHA, untilSHA, projectDir, requiresSameVersion, commitList=[], forceIncludeAllCommits=False):
"""
@property groupId Library GroupId.
@property artifactIds List of ArtifactIds included in these release notes.
@property version Version of the library, assuming all artifactIds have the same version.
@property releaseDate Date the release will go live. Defaults to the current date.
@property fromSHA The oldest SHA to which to query for release notes. It will be
excluded from release notes, but the next newer SHA will be included.
@property untilSHA The newest SHA to be included in the release notes.
@property projectDir The filepath relative to the parent directory of the .git directory.
@property requiresSameVersion True if the groupId of this module requires the same version for
all artifactIds in the groupId. When true, uses the GroupId for the release notes
header. When false, uses the list of artifactIds for the header.
@property commitList The initial list of Commits to include in these release notes. Defaults to an
empty list. Users can always add more commits with [LibraryReleaseNotes.addCommit]
@param forceIncludeAllCommits Set to true to include all commits, both with and without a
release note field in the commit message. Defaults to false, which means only commits
with a release note field are included in the release notes.
"""
self.groupId = groupId
self.artifactIds = artifactIds
self.version = version
self.releaseDate = MarkdownDate(releaseDate)
self.fromSHA = fromSHA
self.untilSHA = untilSHA
self.projectDir = projectDir
self.commitList = commitList
self.requiresSameVersion = requiresSameVersion
self.forceIncludeAllCommits = forceIncludeAllCommits
self.diffLogLink = MarkdownLink()
self.commits = commitList
self.commitMarkdownList = CommitMarkdownList(commitList, forceIncludeAllCommits)
self.summary = ""
self.bugsFixed = []
self.channelSummary = None
if version == "" or groupId == "":
raise RuntimeError("Tried to create Library Release Notes Header without setting " +
"the groupId or version!")
if requiresSameVersion:
formattedGroupId = groupId.replace("androidx.", "")
formattedGroupId = self.capitalizeTitle(formattedGroupId)
self.header = LibraryHeader(formattedGroupId, version)
self.channelSummary = ChannelSummaryItem(formattedGroupId, formattedGroupId, version)
else:
artifactIdTag = artifactIds[0] + "-" if len(artifactIds) == 1 else ""
formattedArtifactIds = (" ".join(artifactIds))
if len(artifactIds) > 3:
formattedArtifactIds = groupId.replace("androidx.", "")
formattedArtifactIds = self.capitalizeTitle(formattedArtifactIds)
self.header = LibraryHeader(formattedArtifactIds, version, artifactIdTag)
formattedGroupId = groupId.replace("androidx.", "")
self.channelSummary = ChannelSummaryItem(formattedArtifactIds, formattedGroupId, version, artifactIdTag)
self.diffLogLink = getGitilesDiffLogLink(version, fromSHA, untilSHA, projectDir)
def getFormattedReleaseSummary(self):
numberArtifacts = len(self.artifactIds)
for i in range(0, numberArtifacts):
currentArtifactId = self.artifactIds[i]
if numberArtifacts == 1:
self.summary = "`%s:%s:%s` is released. " % (self.groupId, currentArtifactId, self.version)
elif numberArtifacts == 2:
if i == 0: self.summary = "`%s:%s:%s` and " % (self.groupId, currentArtifactId, self.version)
if i == 1: self.summary += "`%s:%s:%s` are released. " % (self.groupId, currentArtifactId, self.version)
elif numberArtifacts == 3:
if (i < numberArtifacts - 1):
self.summary += "`%s:%s:%s`, " % (self.groupId, currentArtifactId, self.version)
else:
self.summary += "and `%s:%s:%s` are released. " % (self.groupId, currentArtifactId, self.version)
else:
commonArtifactIdSubstring = self.artifactIds[0].split('-')[0]
self.summary = "`%s:%s-*:%s` is released. " % (
self.groupId,
commonArtifactIdSubstring,
self.version
)
self.summary += "%s\n" % self.diffLogLink
return self.summary
def capitalizeTitle(self, artifactWord):
artifactWord = artifactWord.title()
keywords = ["Animated", "Animation", "Callback", "Compat", "Drawable", "File", "Layout",
"Pager", "Pane", "Parcelable", "Provider", "Refresh", "SQL", "State", "TV",
"Target", "View", "Inflater"]
for keyword in keywords:
artifactWord = artifactWord.replace(keyword.lower(), keyword)
return artifactWord
def addCommit(self, newCommit):
for bug in newCommit.bugs:
bugsFixed.append(bug)
commits.append(newCommit)
commitMarkdownList.add(newCommit)
def __str__(self):
return "%s\n%s\n\n%s%s" % (self.header, self.releaseDate, self.getFormattedReleaseSummary(), self.commitMarkdownList)