Nick Anthony | 7ea8ca9 | 2020-02-11 11:11:37 -0500 | [diff] [blame^] | 1 | #!/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 | |
| 18 | from ReleaseNoteMarkdown import * |
| 19 | from GitClient import CommitType, getTitleFromCommitType |
| 20 | |
| 21 | class 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 | |
| 68 | class GitilesDiffLogLink(MarkdownLink): |
| 69 | def __str__(self): |
| 70 | return "[%s](%s)" % (self.linkText, self.linkUrl) |
| 71 | |
| 72 | def 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 | |
| 92 | class 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 | |
| 104 | class 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) |