Skip to content

Commit 933d191

Browse files
authored
Implement a task for updating changelog files after a release (#5181)
* Update changelog files to match new format * Migrate KTX transitive text data out of MakeReleaseNotesTask * Implement encoding for Changelog data class * Implement task for moving unreleased changes * Connect the task with PostReleasePlugin * Add util method for copying files to directories * Provide a base project for buildSrc tests * Implement a test for moveUnreleasedChanges * Register the new task with PostReleasePlugin * Apply publishing plugin in basic project NO_RELEASE_CHANGE
1 parent 79d24f0 commit 933d191

File tree

23 files changed

+522
-268
lines changed

23 files changed

+522
-268
lines changed

buildSrc/src/main/java/com/google/firebase/gradle/plugins/Changelog.kt

Lines changed: 71 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ import java.io.File
2727
* @see fromString
2828
*/
2929
data class Changelog(val releases: List<ReleaseEntry>) {
30+
31+
override fun toString(): String = releases.joinToString("\n") + "\n"
32+
3033
companion object {
3134
/**
3235
* Regex for finding the version titles in a changelog file.
@@ -114,7 +117,41 @@ data class ReleaseEntry(
114117
val content: ReleaseContent,
115118
val ktx: ReleaseContent?
116119
) {
120+
121+
override fun toString(): String {
122+
return """
123+
|# ${version ?: "Unreleased"}
124+
|$content
125+
|${ktx?.let { """
126+
|
127+
|## Kotlin
128+
|$it
129+
|""" }.orEmpty()}
130+
"""
131+
.trimMargin()
132+
}
133+
134+
/**
135+
* If there is any content in this release.
136+
*
137+
* Meaning that there is content in either the [base release][content] or [ktx].
138+
*
139+
* @see ReleaseContent.hasContent
140+
*/
141+
fun hasContent() = content.hasContent() || ktx?.hasContent() ?: false
142+
117143
companion object {
144+
145+
/**
146+
* A static instance of a [ReleaseEntry] without any content.
147+
*
148+
* This exists to provide a means for tooling to create new sections explicitly, versus offering
149+
* default values to [ReleaseEntry]
150+
* - as this could lead to edge case scenarios where empty [ReleaseEntry] instances are
151+
* accidentally created.
152+
*/
153+
val Empty = ReleaseEntry(null, ReleaseContent("", emptyList()), null)
154+
118155
/**
119156
* Regex for finding the Kotlin header in a changelog file.
120157
*
@@ -164,18 +201,12 @@ data class ReleaseEntry(
164201
val ktx = ktxString?.let { ReleaseContent.fromString(it) }
165202
val version = ModuleVersion.fromStringOrNull(versionString)
166203

204+
if (ktx?.hasContent() == false)
205+
throw RuntimeException("KTX header found without any content:\n $string")
206+
167207
return ReleaseEntry(version, content, ktx)
168208
}
169209
}
170-
171-
/**
172-
* If there is any content in this release.
173-
*
174-
* Meaning that there is content in either the [base release][content] or [ktx].
175-
*
176-
* @see ReleaseContent.hasContent
177-
*/
178-
fun hasContent() = content.hasContent() || ktx?.hasContent() ?: false
179210
}
180211

181212
/**
@@ -186,6 +217,25 @@ data class ReleaseEntry(
186217
* @see fromString
187218
*/
188219
data class ReleaseContent(val subtext: String, val changes: List<Change>) {
220+
221+
override fun toString(): String {
222+
val changes = changes.joinToString("\n")
223+
224+
return when {
225+
subtext.isNotBlank() && changes.isNotBlank() -> "$subtext\n\n$changes"
226+
subtext.isNotBlank() -> subtext
227+
changes.isNotBlank() -> changes
228+
else -> ""
229+
}
230+
}
231+
232+
/**
233+
* If there is any content in this release.
234+
*
235+
* Meaning that there is either [changes] or [subtext] present.
236+
*/
237+
fun hasContent() = changes.isNotEmpty() || subtext.isNotBlank()
238+
189239
companion object {
190240
/**
191241
* Regex for finding the changes in a release.
@@ -235,7 +285,7 @@ data class ReleaseContent(val subtext: String, val changes: List<Change>) {
235285
* "This release contains a known bug. We will address this in a future bugfix."
236286
* ```
237287
*/
238-
val SUBTEXT_REGEX = Regex("^([^\\*\\s][\\s\\S]+?)(\\n\\n|(?![\\s\\S]))", RegexOption.MULTILINE)
288+
val SUBTEXT_REGEX = Regex("^([^\\*\\s][\\s\\S]+?)(\\n\\n|(?![\\s\\S]))")
239289

240290
/**
241291
* Parses [ReleaseContent] from a [String].
@@ -254,19 +304,14 @@ data class ReleaseContent(val subtext: String, val changes: List<Change>) {
254304
* @see Change
255305
*/
256306
fun fromString(string: String): ReleaseContent {
257-
val subtext = SUBTEXT_REGEX.find(string)?.value.orEmpty().trim()
258-
val changes = CHANGE_REGEX.findAll(string).map { Change.fromString(it.firstCapturedValue) }
307+
val changes =
308+
CHANGE_REGEX.findAll(string).map { Change.fromString(it.firstCapturedValue) }.toList()
309+
val firstChange = CHANGE_REGEX.find(string)
310+
val subtext = if (firstChange != null) string.substringBefore(firstChange.value) else string
259311

260-
return ReleaseContent(subtext, changes.toList())
312+
return ReleaseContent(subtext.trim(), changes.toList())
261313
}
262314
}
263-
264-
/**
265-
* If there is any content in this release.
266-
*
267-
* Meaning that there is either [changes] or [subtext] present.
268-
*/
269-
fun hasContent() = changes.isNotEmpty() || subtext.isNotBlank()
270315
}
271316

272317
/**
@@ -277,6 +322,9 @@ data class ReleaseContent(val subtext: String, val changes: List<Change>) {
277322
* @see fromString
278323
*/
279324
data class Change(val type: ChangeType, val message: String) {
325+
326+
override fun toString(): String = "* [$type] $message"
327+
280328
companion object {
281329
/**
282330
* Regex for finding the information about a [Change].
@@ -331,5 +379,7 @@ enum class ChangeType {
331379
CHANGED,
332380
UNCHANGED,
333381
REMOVED,
334-
DEPRECATED
382+
DEPRECATED;
383+
384+
override fun toString(): String = name.toLowerCase()
335385
}

buildSrc/src/main/java/com/google/firebase/gradle/plugins/GradleUtils.kt

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,25 @@ fun DefaultTask.tempFile(path: String) = provider { temporaryDir.childFile(path)
109109
*/
110110
fun File.listFilesOrEmpty() = listFiles().orEmpty()
111111

112+
/**
113+
* Copies this file to the specified directory.
114+
*
115+
* The new file will retain the same [name][File.getName] and [extension][File.extension] as this
116+
* file.
117+
*
118+
* @param target The directory to copy the file to.
119+
* @param overwrite Whether to overwrite the file if it already exists.
120+
* @param bufferSize The size of the buffer to use for the copy operation.
121+
* @return The new file.
122+
*
123+
* @see copyTo
124+
*/
125+
fun File.copyToDirectory(
126+
target: File,
127+
overwrite: Boolean = false,
128+
bufferSize: Int = DEFAULT_BUFFER_SIZE
129+
): File = copyTo(target.childFile(name), overwrite, bufferSize)
130+
112131
/**
113132
* Submits a piece of work to be executed asynchronously.
114133
*

buildSrc/src/main/java/com/google/firebase/gradle/plugins/MakeReleaseNotesTask.kt

Lines changed: 1 addition & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ abstract class MakeReleaseNotesTask : DefaultTask() {
9797
"""
9898
|#### ${metadata.name} Kotlin extensions version $version {: #${metadata.versionName}-ktx_v$versionClassifier}
9999
|
100-
|${unreleased.ktx?.toReleaseNotes() ?: KotlinTransitiveRelease(project.name)}
100+
|${unreleased.ktx?.toReleaseNotes() ?: KTXTransitiveReleaseText(project.name)}
101101
"""
102102
.trimMargin()
103103
.trim()
@@ -115,32 +115,6 @@ abstract class MakeReleaseNotesTask : DefaultTask() {
115115
releaseNotesFile.asFile.get().writeText(releaseNotes)
116116
}
117117

118-
/**
119-
* Provides default text for releasing KTX libs that are transitively invoked in a release,
120-
* because their parent module is releasing. This only applies to `-ktx` libs, not Kotlin SDKs.
121-
*/
122-
private fun KotlinTransitiveRelease(projectName: String) =
123-
"""
124-
|The Kotlin extensions library transitively includes the updated
125-
|`${ProjectNameToKTXPlaceholder(projectName)}` library. The Kotlin extensions library has no additional
126-
|updates.
127-
"""
128-
.trimMargin()
129-
.trim()
130-
131-
/**
132-
* Maps a project's name to a KTX suitable placeholder.
133-
*
134-
* Some libraries produce artifacts with different coordinates than their project name. This
135-
* method helps to map that gap for [KotlinTransitiveRelease].
136-
*/
137-
private fun ProjectNameToKTXPlaceholder(projectName: String) =
138-
when (projectName) {
139-
"firebase-perf" -> "firebase-performance"
140-
"firebase-appcheck" -> "firebase-appcheck"
141-
else -> projectName
142-
}
143-
144118
/**
145119
* Converts a [ReleaseContent] to a [String] to be used in a release note.
146120
*
@@ -174,55 +148,6 @@ abstract class MakeReleaseNotesTask : DefaultTask() {
174148
return "* {{${type.name.toLowerCase()}}} $fixedMessage"
175149
}
176150

177-
/**
178-
* Maps the name of a project to its potential [ReleaseNotesMetadata].
179-
*
180-
* @throws StopActionException If a mapping is not found
181-
*/
182-
// TODO() - Should we expose these as firebaselib configuration points; especially for new SDKS?
183-
private fun convertToMetadata(string: String) =
184-
when (string) {
185-
"firebase-abt" -> ReleaseNotesMetadata("{{ab_testing}}", "ab_testing", false)
186-
"firebase-appdistribution" -> ReleaseNotesMetadata("{{appdistro}}", "app-distro", false)
187-
"firebase-appdistribution-api" -> ReleaseNotesMetadata("{{appdistro}} API", "app-distro-api")
188-
"firebase-config" -> ReleaseNotesMetadata("{{remote_config}}", "remote-config")
189-
"firebase-crashlytics" -> ReleaseNotesMetadata("{{crashlytics}}", "crashlytics")
190-
"firebase-crashlytics-ndk" ->
191-
ReleaseNotesMetadata("{{crashlytics}} NDK", "crashlytics-ndk", false)
192-
"firebase-database" -> ReleaseNotesMetadata("{{database}}", "realtime-database")
193-
"firebase-dynamic-links" -> ReleaseNotesMetadata("{{ddls}}", "dynamic-links")
194-
"firebase-firestore" -> ReleaseNotesMetadata("{{firestore}}", "firestore")
195-
"firebase-functions" -> ReleaseNotesMetadata("{{functions_client}}", "functions-client")
196-
"firebase-dynamic-module-support" ->
197-
ReleaseNotesMetadata(
198-
"Dynamic feature modules support",
199-
"dynamic-feature-modules-support",
200-
false
201-
)
202-
"firebase-inappmessaging" -> ReleaseNotesMetadata("{{inappmessaging}}", "inappmessaging")
203-
"firebase-inappmessaging-display" ->
204-
ReleaseNotesMetadata("{{inappmessaging}} Display", "inappmessaging-display")
205-
"firebase-installations" ->
206-
ReleaseNotesMetadata("{{firebase_installations}}", "installations")
207-
"firebase-messaging" -> ReleaseNotesMetadata("{{messaging_longer}}", "messaging")
208-
"firebase-messaging-directboot" ->
209-
ReleaseNotesMetadata("Cloud Messaging Direct Boot", "messaging-directboot", false)
210-
"firebase-ml-modeldownloader" ->
211-
ReleaseNotesMetadata("{{firebase_ml}}", "firebaseml-modeldownloader")
212-
"firebase-perf" -> ReleaseNotesMetadata("{{perfmon}}", "performance")
213-
"firebase-storage" -> ReleaseNotesMetadata("{{firebase_storage_full}}", "storage")
214-
"firebase-appcheck" -> ReleaseNotesMetadata("{{app_check}}", "appcheck")
215-
"firebase-appcheck-debug" ->
216-
ReleaseNotesMetadata("{{app_check}} Debug", "appcheck-debug", false)
217-
"firebase-appcheck-debug-testing" ->
218-
ReleaseNotesMetadata("{{app_check}} Debug Testing", "appcheck-debug-testing", false)
219-
"firebase-appcheck-playintegrity" ->
220-
ReleaseNotesMetadata("{{app_check}} Play integrity", "appcheck-playintegrity", false)
221-
"firebase-appcheck-safetynet" ->
222-
ReleaseNotesMetadata("{{app_check}} SafetyNet", "appcheck-safetynet", false)
223-
else -> throw StopActionException("No metadata mapping found for project: $string")
224-
}
225-
226151
companion object {
227152
/**
228153
* Regex for GitHub issue links in change messages.
@@ -268,20 +193,3 @@ abstract class MakeReleaseNotesTask : DefaultTask() {
268193
)
269194
}
270195
}
271-
272-
/**
273-
* Provides extra metadata needed to create release notes for a given project.
274-
*
275-
* This data is needed for g3 internal mappings, and does not really have any implications for
276-
* public repo actions.
277-
*
278-
* @property name The variable name for a project in a release note
279-
* @property vesionName The variable name given to the versions of a project
280-
* @property hasKTX The module has a KTX submodule (not to be confused with having KTX files)
281-
* @see MakeReleaseNotesTask
282-
*/
283-
data class ReleaseNotesMetadata(
284-
val name: String,
285-
val versionName: String,
286-
val hasKTX: Boolean = true
287-
)

0 commit comments

Comments
 (0)