Skip to content

dataconnect: add firebase-dataconnect:connectors:updateJson task #6358

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions firebase-dataconnect/connectors/connectors.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/

import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import com.google.firebase.dataconnect.gradle.plugin.UpdateDataConnectExecutableVersionsTask

plugins {
id("com.android.library")
Expand Down Expand Up @@ -109,3 +110,41 @@ tasks.withType<KotlinCompile>().all {
}
}
}

// Adds a Gradle task that updates the JSON file that stores the list of Data Connect
// executable versions.
//
// Example 1: Add versions 1.4.3 and 1.4.4 to the JSON file, and set 1.4.4 as the default:
// ../../gradlew -Pversions=1.4.3,1.4.4 -PdefaultVersion=1.4.4 updateJson --info
//
// Example 2: Add version 1.2.3 to the JSON file, but do not change the default version:
// ../../gradlew -Pversion=1.2.3 updateJson --info
//
// The `--info` argument can be omitted; it merely controls the level of log output.
tasks.register<UpdateDataConnectExecutableVersionsTask>("updateJson") {
outputs.upToDateWhen { false }
jsonFile.set(project.layout.projectDirectory.file(
"../gradleplugin/plugin/src/main/resources/com/google/firebase/dataconnect/gradle/" +
"plugin/DataConnectExecutableVersions.json"))
workDirectory.set(project.layout.buildDirectory.dir("updateJson"))

val singleVersion: String? = project.providers.gradleProperty("version").orNull
val multipleVersions: List<String>? = project.providers.gradleProperty("versions").orNull?.split(',')
versions.set(buildList {
singleVersion?.let{add(it)}
multipleVersions?.let{addAll(it)}
if (isEmpty()) {
throw Exception("bm6d5ezxzd 'version' or 'versions' property must be specified")
}
})

updateMode.set(project.providers.gradleProperty("updateMode").map {
when (it) {
"overwrite" -> UpdateDataConnectExecutableVersionsTask.UpdateMode.Overwrite
"update" -> UpdateDataConnectExecutableVersionsTask.UpdateMode.Update
else -> throw Exception("ahe4zadcjs 'updateMode' must be 'overwrite' or 'update', but got: $it")
}
})

defaultVersion.set(project.providers.gradleProperty("defaultVersion"))
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
/*
* Copyright 2024 Google LLC
*
* 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.
*/

package com.google.firebase.dataconnect.gradle.plugin

import com.google.firebase.dataconnect.gradle.plugin.DataConnectExecutableDownloadTask.Companion.downloadDataConnectExecutable
import com.google.firebase.dataconnect.gradle.plugin.DataConnectExecutableDownloadTask.FileInfo
import java.io.File
import kotlin.random.Random
import org.gradle.api.DefaultTask
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.Optional
import org.gradle.api.tasks.TaskAction

@Suppress("unused")
abstract class UpdateDataConnectExecutableVersionsTask : DefaultTask() {

@get:InputFile abstract val jsonFile: RegularFileProperty

@get:Input abstract val versions: ListProperty<String>

@get:Input @get:Optional abstract val defaultVersion: Property<String>

@get:Input @get:Optional abstract val updateMode: Property<UpdateMode>

@get:Internal abstract val workDirectory: DirectoryProperty

@TaskAction
fun run() {
val jsonFile: File = jsonFile.get().asFile
val versions: List<String> = versions.get()
val defaultVersion: String? = defaultVersion.orNull
val updateMode: UpdateMode? = updateMode.orNull
val workDirectory: File = workDirectory.get().asFile

logger.info("jsonFile={}", jsonFile.absolutePath)
logger.info("versions={}", versions)
logger.info("defaultVersion={}", defaultVersion)
logger.info("updateMode={}", updateMode)
logger.info("workDirectory={}", workDirectory)

var json: DataConnectExecutableVersionsRegistry.Root =
if (updateMode == UpdateMode.Overwrite) {
DataConnectExecutableVersionsRegistry.Root(
defaultVersion = "<unspecified>",
versions = emptyList()
)
} else {
logger.info("Loading JSON file {}", jsonFile.absolutePath)
DataConnectExecutableVersionsRegistry.load(jsonFile)
}

if (defaultVersion !== null) {
json = json.copy(defaultVersion = defaultVersion)
}

for (version in versions) {
val windowsExecutable = download(version, OperatingSystem.Windows, workDirectory)
val macosExecutable = download(version, OperatingSystem.MacOS, workDirectory)
val linuxExecutable = download(version, OperatingSystem.Linux, workDirectory)
json = json.withVersions(version, windowsExecutable, macosExecutable, linuxExecutable)
}

logger.info(
"Writing information about versions {} to file with updateMode={}: {}",
versions.joinToString(", "),
updateMode,
jsonFile.absolutePath
)
DataConnectExecutableVersionsRegistry.save(json, jsonFile)
}

private fun DataConnectExecutableVersionsRegistry.Root.withVersions(
version: String,
windows: DownloadedFile,
macos: DownloadedFile,
linux: DownloadedFile
): DataConnectExecutableVersionsRegistry.Root {
data class UpdatedVersion(
val operatingSystem: OperatingSystem,
val sizeInBytes: Long,
val sha512DigestHex: String,
) {
constructor(
operatingSystem: OperatingSystem,
downloadedFile: DownloadedFile
) : this(operatingSystem, downloadedFile.sizeInBytes, downloadedFile.sha512DigestHex)
}
val updatedVersions =
listOf(
UpdatedVersion(OperatingSystem.Windows, windows),
UpdatedVersion(OperatingSystem.MacOS, macos),
UpdatedVersion(OperatingSystem.Linux, linux),
)

val newVersions = versions.toMutableList()
for (updatedVersion in updatedVersions) {
val index =
newVersions.indexOfFirst {
it.version == version && it.os == updatedVersion.operatingSystem
}
if (index >= 0) {
val newVersion =
newVersions[index].copy(
size = updatedVersion.sizeInBytes,
sha512DigestHex = updatedVersion.sha512DigestHex,
)
newVersions[index] = newVersion
} else {
val newVersion =
DataConnectExecutableVersionsRegistry.VersionInfo(
version = version,
os = updatedVersion.operatingSystem,
size = updatedVersion.sizeInBytes,
sha512DigestHex = updatedVersion.sha512DigestHex,
)
newVersions.add(newVersion)
}
}

return this.copy(versions = newVersions.toList())
}

private fun download(
version: String,
operatingSystem: OperatingSystem,
outputDirectory: File
): DownloadedFile {
val randomId = Random.nextAlphanumericString(length = 20)
val outputFile =
File(outputDirectory, "DataConnectToolkit_${version}_${operatingSystem}_$randomId")

downloadDataConnectExecutable(version, operatingSystem, outputFile)

logger.info("Calculating SHA512 hash of file: {}", outputFile.absolutePath)
val fileInfo = FileInfo.forFile(outputFile)

return DownloadedFile(
file = outputFile,
sizeInBytes = fileInfo.sizeInBytes,
sha512DigestHex = fileInfo.sha512DigestHex,
)
}

private data class DownloadedFile(
val file: File,
val sizeInBytes: Long,
val sha512DigestHex: String,
)

enum class UpdateMode {
Overwrite,
Update
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package com.google.firebase.dataconnect.gradle.plugin

import java.util.Locale
import java.util.concurrent.atomic.AtomicLong
import kotlin.random.Random
import kotlin.time.Duration
import kotlin.time.DurationUnit
import kotlin.time.toDuration
Expand Down Expand Up @@ -46,3 +47,24 @@ class Debouncer(val period: Duration) {
}
}
}

/**
* Generates and returns a string containing random alphanumeric characters.
*
* The characters returned are taken from the set of characters comprising of the 10 numeric digits
* and the 26 lowercase English characters.
*
* @param length the number of random characters to generate and include in the returned string;
* must be greater than or equal to zero.
* @return a string containing the given number of random alphanumeric characters.
*/
fun Random.nextAlphanumericString(length: Int): String {
require(length >= 0) { "invalid length: $length" }
return (0 until length).map { ALPHANUMERIC_ALPHABET.random(this) }.joinToString(separator = "")
}

// The set of characters consisting of the 10 numeric digits and the 26 lowercase letters of the
// English alphabet with some characters removed that can look similar in different fonts, like
// '1', 'l', and 'i'.
@Suppress("SpellCheckingInspection")
private const val ALPHANUMERIC_ALPHABET = "23456789abcdefghjkmnpqrstvwxyz"
Loading