13
13
// limitations under the License.
14
14
package com.google.firebase.gradle
15
15
16
+ import com.google.common.collect.ImmutableList
17
+ import com.google.firebase.gradle.plugins.FirebaseLibraryExtension
16
18
import java.io.File
17
19
import org.eclipse.jgit.api.Git
18
20
import org.eclipse.jgit.api.ListBranchCommand
19
21
import org.eclipse.jgit.api.errors.GitAPIException
20
22
import org.eclipse.jgit.lib.Constants
21
23
import org.eclipse.jgit.lib.ObjectId
24
+ import org.eclipse.jgit.revwalk.RevCommit
22
25
import org.gradle.api.DefaultTask
26
+ import org.gradle.api.Project
27
+ import org.gradle.api.file.RegularFileProperty
28
+ import org.gradle.api.provider.Property
29
+ import org.gradle.api.tasks.Input
30
+ import org.gradle.api.tasks.OutputFile
23
31
import org.gradle.api.tasks.TaskAction
32
+ import org.gradle.kotlin.dsl.findByType
24
33
25
34
data class FirebaseLibrary (val moduleNames : List <String >, val directories : List <String >)
26
35
27
- open class ReleaseGenerator : DefaultTask () {
36
+ data class CommitDiff (
37
+ val commitId : String ,
38
+ val author : String ,
39
+ val message : String ,
40
+ ) {
41
+ constructor (
42
+ revCommit: RevCommit
43
+ ) : this (revCommit.id.name, revCommit.authorIdent.name, revCommit.fullMessage) {}
44
+
45
+ override fun toString (): String =
46
+ """
47
+ |* ${message.split(" \n " ).first()}
48
+ | https://github.com/firebase/firebase-android-sdk/commit/${commitId} [${author} ]
49
+
50
+ """
51
+ .trimMargin()
52
+ }
53
+
54
+ abstract class ReleaseGenerator : DefaultTask () {
55
+
56
+ @get:Input abstract val currentRelease: Property <String >
57
+
58
+ @get:Input abstract val pastRelease: Property <String >
59
+
60
+ @get:Input abstract val printReleaseConfig: Property <String >
61
+
62
+ @get:OutputFile abstract val releaseConfigFile: RegularFileProperty
63
+
64
+ @get:OutputFile abstract val releaseReportFile: RegularFileProperty
65
+
28
66
@TaskAction
29
67
@Throws(Exception ::class )
30
68
fun generateReleaseConfig () {
31
- val currentRelease = project.property(" currentRelease" ).toString()
32
- val pastRelease = project.property(" pastRelease" ).toString()
33
- val printReleaseConfig = project.property(" printOutput" ).toString().toBoolean()
34
69
val rootDir = project.rootDir
35
- val availableModules = parseSubProjects(rootDir)
36
- val firebaseLibraries = extractLibraries(availableModules, rootDir)
70
+ val availableModules = project.subprojects.filter { it.plugins.hasPlugin(" firebase-library" ) }
37
71
38
72
val repo = Git .open(rootDir)
39
73
val headRef = repo.repository.resolve(Constants .HEAD )
40
- val branchRef = getObjectRefForBranchName(repo, pastRelease)
74
+ val branchRef = getObjectRefForBranchName(repo, pastRelease.get())
75
+
76
+ val libsToRelease = getChangedChangelogs(repo, branchRef, headRef, availableModules)
77
+ val changedLibsWithNoChangelog =
78
+ getChangedLibraries(repo, branchRef, headRef, availableModules) subtract
79
+ libsToRelease.map { it.path }.toSet()
41
80
42
- val changedLibraries = getChangedLibraries(repo, branchRef, headRef, firebaseLibraries)
43
- writeReleaseConfig(rootDir, changedLibraries, currentRelease)
44
- if (printReleaseConfig) {
45
- println (changedLibraries.joinToString(" ," , " LIBRARIES TO RELEASE: " ))
81
+ val changes = getChangesForLibraries(repo, branchRef, headRef, libsToRelease)
82
+ writeReleaseConfig(
83
+ releaseConfigFile.get().asFile,
84
+ ReleaseConfig (currentRelease.get(), libsToRelease.map { it.path }.toSet())
85
+ )
86
+ val releaseReport = generateReleaseReport(changes, changedLibsWithNoChangelog)
87
+ if (printReleaseConfig.get().toBoolean()) {
88
+ project.logger.info(releaseReport)
46
89
}
90
+ writeReleaseReport(releaseReportFile.get().asFile, releaseReport)
47
91
}
48
92
49
- private fun extractLibraries (
50
- availableModules : Set <String >,
51
- rootDir : File
52
- ): List <FirebaseLibrary > {
53
- val nonKtxModules = availableModules.filter { ! it.endsWith(" ktx" ) }.toSet()
54
- return nonKtxModules
55
- .map { moduleName ->
56
- val ktxModuleName = " $moduleName :ktx"
57
-
58
- val moduleNames = listOf (moduleName, ktxModuleName).filter { availableModules.contains(it) }
59
- val directories = moduleNames.map { it.replace(" :" , " /" ) }
93
+ private fun generateReleaseReport (
94
+ changes : Map <String , List <CommitDiff >>,
95
+ changedLibrariesWithNoChangelog : Set <String >
96
+ ) =
97
+ """
98
+ |# Release Report
99
+ |${
100
+ changes.entries.joinToString(" \n " ) {
101
+ """
102
+ |## ${it.key}
103
+
104
+ |${it.value.joinToString(" \n " ) { it.toString() }}
105
+ """ .trimMargin()
106
+ }
107
+ }
108
+ |
109
+ |## SDKs with changes, but no changelogs
110
+ |${changedLibrariesWithNoChangelog.joinToString(" \n " )}
111
+ """
112
+ .trimMargin()
60
113
61
- FirebaseLibrary (moduleNames, directories)
62
- }
63
- .filter { firebaseLibrary ->
64
- firebaseLibrary.directories.first().let { File (rootDir, " $it /gradle.properties" ).exists() }
65
- }
66
- }
114
+ private fun getChangesForLibraries (
115
+ repo : Git ,
116
+ branchRef : ObjectId ,
117
+ headRef : ObjectId ,
118
+ changedLibraries : Set <Project >
119
+ ) =
120
+ changedLibraries
121
+ .map { getRelativeDir(it) }
122
+ .associateWith { getDirChanges(repo, branchRef, headRef, it) }
123
+ .toMap()
67
124
68
125
private fun parseSubProjects (rootDir : File ) =
69
126
File (rootDir, " subprojects.cfg" )
@@ -77,21 +134,46 @@ open class ReleaseGenerator : DefaultTask() {
77
134
.branchList()
78
135
.setListMode(ListBranchCommand .ListMode .REMOTE )
79
136
.call()
80
- .firstOrNull { it.name == " refs/remotes/origin/$branchName " }
137
+ .firstOrNull { it.name == " refs/remotes/origin/releases/ $branchName " }
81
138
?.objectId
82
139
? : throw RuntimeException (" Could not find branch named $branchName " )
83
140
84
141
private fun getChangedLibraries (
85
142
repo : Git ,
86
143
previousReleaseRef : ObjectId ,
87
144
currentReleaseRef : ObjectId ,
88
- libraries : List <FirebaseLibrary >
145
+ libraries : List <Project >
146
+ ) =
147
+ libraries
148
+ .filter {
149
+ checkDirChanges(repo, previousReleaseRef, currentReleaseRef, " ${getRelativeDir(it)} /" )
150
+ }
151
+ .flatMap {
152
+ it.extensions.findByType<FirebaseLibraryExtension >()?.projectsToRelease?.map { it.path }
153
+ ? : emptyList()
154
+ }
155
+ .toSet()
156
+
157
+ private fun getChangedChangelogs (
158
+ repo : Git ,
159
+ previousReleaseRef : ObjectId ,
160
+ currentReleaseRef : ObjectId ,
161
+ libraries : List <Project >
89
162
) =
90
163
libraries
91
164
.filter { library ->
92
- library.directories.any { checkDirChanges(repo, previousReleaseRef, currentReleaseRef, it) }
165
+ checkDirChanges(
166
+ repo,
167
+ previousReleaseRef,
168
+ currentReleaseRef,
169
+ " ${getRelativeDir(library)} /CHANGELOG.md"
170
+ )
171
+ }
172
+ .flatMap {
173
+ it.extensions.findByType<FirebaseLibraryExtension >()?.projectsToRelease
174
+ ? : ImmutableList .of(it)
93
175
}
94
- .flatMap { it.moduleNames }
176
+ .toSet()
95
177
96
178
private fun checkDirChanges (
97
179
repo : Git ,
@@ -101,25 +183,49 @@ open class ReleaseGenerator : DefaultTask() {
101
183
) =
102
184
repo
103
185
.log()
104
- .addPath(" $ directory/ " )
186
+ .addPath(directory)
105
187
.addRange(previousReleaseRef, currentReleaseRef)
106
188
.setMaxCount(1 )
107
189
.call()
108
190
.iterator()
109
191
.hasNext()
110
192
111
- private fun writeReleaseConfig (configPath : File , libraries : List <String >, releaseName : String ) {
112
- File (configPath, " release.cfg" )
113
- .writeText(
114
- """
115
- [release]
116
- name = $releaseName
117
- mode = RELEASE
118
-
119
- [modules]
120
- ${libraries.joinToString(" \n " .padEnd(21 , ' ' ))}
121
- """
122
- .trimIndent()
123
- )
193
+ private fun getDirChanges (
194
+ repo : Git ,
195
+ previousReleaseRef : ObjectId ,
196
+ currentReleaseRef : ObjectId ,
197
+ directory : String
198
+ ) =
199
+ repo.log().addPath(directory).addRange(previousReleaseRef, currentReleaseRef).call().map {
200
+ CommitDiff (it)
201
+ }
202
+
203
+ private fun writeReleaseReport (file : File , report : String ) = file.writeText(report)
204
+
205
+ private fun writeReleaseConfig (file : File , config : ReleaseConfig ) =
206
+ file.writeText(config.toFile())
207
+
208
+ private fun getRelativeDir (project : Project ) = project.path.substring(1 ).replace(' :' , ' /' )
209
+ }
210
+
211
+ data class ReleaseConfig (val releaseName : String , val libs : Set <String >) {
212
+ companion object {
213
+ fun fromFile (file : File ): ReleaseConfig {
214
+ val contents = file.readLines()
215
+ val libs = contents.filter { it.startsWith(" :" ) }.toSet()
216
+ val releaseName = contents.first { it.startsWith(" name" ) }.substringAfter(" =" ).trim()
217
+ return ReleaseConfig (releaseName, libs)
218
+ }
124
219
}
220
+
221
+ fun toFile () =
222
+ """
223
+ |[release]
224
+ |name = $releaseName
225
+ |mode = RELEASE
226
+
227
+ |[modules]
228
+ |${libs.sorted().joinToString(" \n " )}
229
+ """
230
+ .trimMargin()
125
231
}
0 commit comments