Skip to content

Commit b49d448

Browse files
authored
dataconnect: add firebase-dataconnect:connectors:updateJson task (#6358)
1 parent 18fd9f7 commit b49d448

File tree

3 files changed

+235
-0
lines changed

3 files changed

+235
-0
lines changed

firebase-dataconnect/connectors/connectors.gradle.kts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616

1717
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
18+
import com.google.firebase.dataconnect.gradle.plugin.UpdateDataConnectExecutableVersionsTask
1819

1920
plugins {
2021
id("com.android.library")
@@ -109,3 +110,41 @@ tasks.withType<KotlinCompile>().all {
109110
}
110111
}
111112
}
113+
114+
// Adds a Gradle task that updates the JSON file that stores the list of Data Connect
115+
// executable versions.
116+
//
117+
// Example 1: Add versions 1.4.3 and 1.4.4 to the JSON file, and set 1.4.4 as the default:
118+
// ../../gradlew -Pversions=1.4.3,1.4.4 -PdefaultVersion=1.4.4 updateJson --info
119+
//
120+
// Example 2: Add version 1.2.3 to the JSON file, but do not change the default version:
121+
// ../../gradlew -Pversion=1.2.3 updateJson --info
122+
//
123+
// The `--info` argument can be omitted; it merely controls the level of log output.
124+
tasks.register<UpdateDataConnectExecutableVersionsTask>("updateJson") {
125+
outputs.upToDateWhen { false }
126+
jsonFile.set(project.layout.projectDirectory.file(
127+
"../gradleplugin/plugin/src/main/resources/com/google/firebase/dataconnect/gradle/" +
128+
"plugin/DataConnectExecutableVersions.json"))
129+
workDirectory.set(project.layout.buildDirectory.dir("updateJson"))
130+
131+
val singleVersion: String? = project.providers.gradleProperty("version").orNull
132+
val multipleVersions: List<String>? = project.providers.gradleProperty("versions").orNull?.split(',')
133+
versions.set(buildList {
134+
singleVersion?.let{add(it)}
135+
multipleVersions?.let{addAll(it)}
136+
if (isEmpty()) {
137+
throw Exception("bm6d5ezxzd 'version' or 'versions' property must be specified")
138+
}
139+
})
140+
141+
updateMode.set(project.providers.gradleProperty("updateMode").map {
142+
when (it) {
143+
"overwrite" -> UpdateDataConnectExecutableVersionsTask.UpdateMode.Overwrite
144+
"update" -> UpdateDataConnectExecutableVersionsTask.UpdateMode.Update
145+
else -> throw Exception("ahe4zadcjs 'updateMode' must be 'overwrite' or 'update', but got: $it")
146+
}
147+
})
148+
149+
defaultVersion.set(project.providers.gradleProperty("defaultVersion"))
150+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
/*
2+
* Copyright 2024 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.firebase.dataconnect.gradle.plugin
18+
19+
import com.google.firebase.dataconnect.gradle.plugin.DataConnectExecutableDownloadTask.Companion.downloadDataConnectExecutable
20+
import com.google.firebase.dataconnect.gradle.plugin.DataConnectExecutableDownloadTask.FileInfo
21+
import java.io.File
22+
import kotlin.random.Random
23+
import org.gradle.api.DefaultTask
24+
import org.gradle.api.file.DirectoryProperty
25+
import org.gradle.api.file.RegularFileProperty
26+
import org.gradle.api.provider.ListProperty
27+
import org.gradle.api.provider.Property
28+
import org.gradle.api.tasks.Input
29+
import org.gradle.api.tasks.InputFile
30+
import org.gradle.api.tasks.Internal
31+
import org.gradle.api.tasks.Optional
32+
import org.gradle.api.tasks.TaskAction
33+
34+
@Suppress("unused")
35+
abstract class UpdateDataConnectExecutableVersionsTask : DefaultTask() {
36+
37+
@get:InputFile abstract val jsonFile: RegularFileProperty
38+
39+
@get:Input abstract val versions: ListProperty<String>
40+
41+
@get:Input @get:Optional abstract val defaultVersion: Property<String>
42+
43+
@get:Input @get:Optional abstract val updateMode: Property<UpdateMode>
44+
45+
@get:Internal abstract val workDirectory: DirectoryProperty
46+
47+
@TaskAction
48+
fun run() {
49+
val jsonFile: File = jsonFile.get().asFile
50+
val versions: List<String> = versions.get()
51+
val defaultVersion: String? = defaultVersion.orNull
52+
val updateMode: UpdateMode? = updateMode.orNull
53+
val workDirectory: File = workDirectory.get().asFile
54+
55+
logger.info("jsonFile={}", jsonFile.absolutePath)
56+
logger.info("versions={}", versions)
57+
logger.info("defaultVersion={}", defaultVersion)
58+
logger.info("updateMode={}", updateMode)
59+
logger.info("workDirectory={}", workDirectory)
60+
61+
var json: DataConnectExecutableVersionsRegistry.Root =
62+
if (updateMode == UpdateMode.Overwrite) {
63+
DataConnectExecutableVersionsRegistry.Root(
64+
defaultVersion = "<unspecified>",
65+
versions = emptyList()
66+
)
67+
} else {
68+
logger.info("Loading JSON file {}", jsonFile.absolutePath)
69+
DataConnectExecutableVersionsRegistry.load(jsonFile)
70+
}
71+
72+
if (defaultVersion !== null) {
73+
json = json.copy(defaultVersion = defaultVersion)
74+
}
75+
76+
for (version in versions) {
77+
val windowsExecutable = download(version, OperatingSystem.Windows, workDirectory)
78+
val macosExecutable = download(version, OperatingSystem.MacOS, workDirectory)
79+
val linuxExecutable = download(version, OperatingSystem.Linux, workDirectory)
80+
json = json.withVersions(version, windowsExecutable, macosExecutable, linuxExecutable)
81+
}
82+
83+
logger.info(
84+
"Writing information about versions {} to file with updateMode={}: {}",
85+
versions.joinToString(", "),
86+
updateMode,
87+
jsonFile.absolutePath
88+
)
89+
DataConnectExecutableVersionsRegistry.save(json, jsonFile)
90+
}
91+
92+
private fun DataConnectExecutableVersionsRegistry.Root.withVersions(
93+
version: String,
94+
windows: DownloadedFile,
95+
macos: DownloadedFile,
96+
linux: DownloadedFile
97+
): DataConnectExecutableVersionsRegistry.Root {
98+
data class UpdatedVersion(
99+
val operatingSystem: OperatingSystem,
100+
val sizeInBytes: Long,
101+
val sha512DigestHex: String,
102+
) {
103+
constructor(
104+
operatingSystem: OperatingSystem,
105+
downloadedFile: DownloadedFile
106+
) : this(operatingSystem, downloadedFile.sizeInBytes, downloadedFile.sha512DigestHex)
107+
}
108+
val updatedVersions =
109+
listOf(
110+
UpdatedVersion(OperatingSystem.Windows, windows),
111+
UpdatedVersion(OperatingSystem.MacOS, macos),
112+
UpdatedVersion(OperatingSystem.Linux, linux),
113+
)
114+
115+
val newVersions = versions.toMutableList()
116+
for (updatedVersion in updatedVersions) {
117+
val index =
118+
newVersions.indexOfFirst {
119+
it.version == version && it.os == updatedVersion.operatingSystem
120+
}
121+
if (index >= 0) {
122+
val newVersion =
123+
newVersions[index].copy(
124+
size = updatedVersion.sizeInBytes,
125+
sha512DigestHex = updatedVersion.sha512DigestHex,
126+
)
127+
newVersions[index] = newVersion
128+
} else {
129+
val newVersion =
130+
DataConnectExecutableVersionsRegistry.VersionInfo(
131+
version = version,
132+
os = updatedVersion.operatingSystem,
133+
size = updatedVersion.sizeInBytes,
134+
sha512DigestHex = updatedVersion.sha512DigestHex,
135+
)
136+
newVersions.add(newVersion)
137+
}
138+
}
139+
140+
return this.copy(versions = newVersions.toList())
141+
}
142+
143+
private fun download(
144+
version: String,
145+
operatingSystem: OperatingSystem,
146+
outputDirectory: File
147+
): DownloadedFile {
148+
val randomId = Random.nextAlphanumericString(length = 20)
149+
val outputFile =
150+
File(outputDirectory, "DataConnectToolkit_${version}_${operatingSystem}_$randomId")
151+
152+
downloadDataConnectExecutable(version, operatingSystem, outputFile)
153+
154+
logger.info("Calculating SHA512 hash of file: {}", outputFile.absolutePath)
155+
val fileInfo = FileInfo.forFile(outputFile)
156+
157+
return DownloadedFile(
158+
file = outputFile,
159+
sizeInBytes = fileInfo.sizeInBytes,
160+
sha512DigestHex = fileInfo.sha512DigestHex,
161+
)
162+
}
163+
164+
private data class DownloadedFile(
165+
val file: File,
166+
val sizeInBytes: Long,
167+
val sha512DigestHex: String,
168+
)
169+
170+
enum class UpdateMode {
171+
Overwrite,
172+
Update
173+
}
174+
}

firebase-dataconnect/gradleplugin/plugin/src/main/kotlin/com/google/firebase/dataconnect/gradle/plugin/Util.kt

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ package com.google.firebase.dataconnect.gradle.plugin
1717

1818
import java.util.Locale
1919
import java.util.concurrent.atomic.AtomicLong
20+
import kotlin.random.Random
2021
import kotlin.time.Duration
2122
import kotlin.time.DurationUnit
2223
import kotlin.time.toDuration
@@ -46,3 +47,24 @@ class Debouncer(val period: Duration) {
4647
}
4748
}
4849
}
50+
51+
/**
52+
* Generates and returns a string containing random alphanumeric characters.
53+
*
54+
* The characters returned are taken from the set of characters comprising of the 10 numeric digits
55+
* and the 26 lowercase English characters.
56+
*
57+
* @param length the number of random characters to generate and include in the returned string;
58+
* must be greater than or equal to zero.
59+
* @return a string containing the given number of random alphanumeric characters.
60+
*/
61+
fun Random.nextAlphanumericString(length: Int): String {
62+
require(length >= 0) { "invalid length: $length" }
63+
return (0 until length).map { ALPHANUMERIC_ALPHABET.random(this) }.joinToString(separator = "")
64+
}
65+
66+
// The set of characters consisting of the 10 numeric digits and the 26 lowercase letters of the
67+
// English alphabet with some characters removed that can look similar in different fonts, like
68+
// '1', 'l', and 'i'.
69+
@Suppress("SpellCheckingInspection")
70+
private const val ALPHANUMERIC_ALPHABET = "23456789abcdefghjkmnpqrstvwxyz"

0 commit comments

Comments
 (0)