From 87c483235c80415337255b427a2360976452ccf2 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Mon, 18 Nov 2024 10:15:56 +0000 Subject: [PATCH 01/79] dataconnect: integrate codegen into the gradle build --- .github/workflows/android.yml | 7 +- dataconnect/app/build.gradle.kts | 15 ++- dataconnect/buildSrc/build.gradle.kts | 45 ++++++++ dataconnect/buildSrc/settings.gradle.kts | 15 +++ .../example/dataconnect/gradle/CodegenTask.kt | 75 ++++++++++++ .../example/dataconnect/gradle/Commands.kt | 42 +++++++ .../gradle/ConnectorYamlTweaking.kt | 89 +++++++++++++++ .../gradle/DataConnectExtension.kt | 24 ++++ .../gradle/DataConnectGradlePlugin.kt | 63 +++++++++++ .../gradle/FirebaseToolsSetupTask.kt | 64 +++++++++++ .../example/dataconnect/gradle/Providers.kt | 107 ++++++++++++++++++ dataconnect/dataconnect/.gitignore | 1 + .../movie-connector/connector.yaml | 6 +- gradle/libs.versions.toml | 4 + scripts/ci_remove_fdc.py | 8 -- settings.gradle.kts | 3 + 16 files changed, 550 insertions(+), 18 deletions(-) create mode 100644 dataconnect/buildSrc/build.gradle.kts create mode 100644 dataconnect/buildSrc/settings.gradle.kts create mode 100644 dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/CodegenTask.kt create mode 100644 dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/Commands.kt create mode 100644 dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/ConnectorYamlTweaking.kt create mode 100644 dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectExtension.kt create mode 100644 dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectGradlePlugin.kt create mode 100644 dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/FirebaseToolsSetupTask.kt create mode 100644 dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/Providers.kt create mode 100644 dataconnect/dataconnect/.gitignore delete mode 100644 scripts/ci_remove_fdc.py diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index 8a635beff6..f89439480c 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -16,11 +16,12 @@ jobs: java-version: 17 - name: Setup Gradle uses: gradle/gradle-build-action@v2 + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 22 - name: Check Snippets run: python scripts/checksnippets.py - # TODO(thatfiredev): remove this once github.com/firebase/quickstart-android/issues/1672 is fixed - - name: Remove Firebase Data Connect from CI - run: python scripts/ci_remove_fdc.py - name: Copy mock google_services.json run: ./copy_mock_google_services_json.sh - name: Build with Gradle (Pull Request) diff --git a/dataconnect/app/build.gradle.kts b/dataconnect/app/build.gradle.kts index 2abc8043fc..a5cc12e775 100644 --- a/dataconnect/app/build.gradle.kts +++ b/dataconnect/app/build.gradle.kts @@ -4,6 +4,7 @@ plugins { alias(libs.plugins.kotlin.serialization) alias(libs.plugins.google.services) alias(libs.plugins.compose.compiler) + id("com.google.firebase.example.dataconnect.gradle") } android { @@ -50,13 +51,9 @@ android { excludes += "/META-INF/{AL2.0,LGPL2.1}" } } - sourceSets.getByName("main") { - java.srcDirs("build/generated/sources") - } } dependencies { - implementation(libs.androidx.core.ktx) implementation(libs.androidx.lifecycle.runtime.ktx) implementation(libs.androidx.lifecycle.viewmodel.compose) @@ -83,3 +80,13 @@ dependencies { debugImplementation(libs.androidx.ui.tooling) debugImplementation(libs.androidx.ui.test.manifest) } + +dataconnect { + // The version of https://www.npmjs.com/package/firebase-tools to use to perform the + // Data Connect code generation. + firebaseToolsVersion = "13.23.0" + + // The directory that contains dataconnect.yaml to use as input when performing + // the Data Connect code generation. + dataConnectConfigDir = file("../dataconnect") +} diff --git a/dataconnect/buildSrc/build.gradle.kts b/dataconnect/buildSrc/build.gradle.kts new file mode 100644 index 0000000000..36b9fb605d --- /dev/null +++ b/dataconnect/buildSrc/build.gradle.kts @@ -0,0 +1,45 @@ +/* + * 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. + */ + +plugins { + // See https://docs.gradle.org/current/userguide/kotlin_dsl.html#sec:kotlin-dsl_plugin + `kotlin-dsl` + alias(libs.plugins.spotless) +} + +java { toolchain { languageVersion.set(JavaLanguageVersion.of(17)) } } + +dependencies { + implementation(libs.android.gradlePlugin.api) + implementation(libs.snakeyaml) +} + +gradlePlugin { + plugins { + create("dataconnect") { + id = "com.google.firebase.example.dataconnect.gradle" + implementationClass = "com.google.firebase.example.dataconnect.gradle.DataConnectGradlePlugin" + } + } +} + +spotless { + kotlin { ktfmt(libs.versions.ktfmt.get()).googleStyle() } + kotlinGradle { + target("*.gradle.kts") + ktfmt(libs.versions.ktfmt.get()).googleStyle() + } +} diff --git a/dataconnect/buildSrc/settings.gradle.kts b/dataconnect/buildSrc/settings.gradle.kts new file mode 100644 index 0000000000..89a5d455e0 --- /dev/null +++ b/dataconnect/buildSrc/settings.gradle.kts @@ -0,0 +1,15 @@ +pluginManagement { + repositories { + gradlePluginPortal() + google() + mavenCentral() + } +} + +dependencyResolutionManagement { + repositories { + google() + mavenCentral() + } + versionCatalogs { create("libs") { from(files("../../gradle/libs.versions.toml")) } } +} diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/CodegenTask.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/CodegenTask.kt new file mode 100644 index 0000000000..90dedc587f --- /dev/null +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/CodegenTask.kt @@ -0,0 +1,75 @@ +/* + * 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.example.dataconnect.gradle + +import java.io.File +import org.gradle.api.DefaultTask +import org.gradle.api.file.DirectoryProperty +import org.gradle.api.file.RegularFileProperty +import org.gradle.api.tasks.InputDirectory +import org.gradle.api.tasks.InputFile +import org.gradle.api.tasks.Internal +import org.gradle.api.tasks.OutputDirectory +import org.gradle.api.tasks.TaskAction + +abstract class CodegenTask : DefaultTask() { + + @get:InputDirectory abstract val dataConnectConfigDir: DirectoryProperty + + @get:InputFile abstract val firebaseExecutable: RegularFileProperty + + @get:OutputDirectory abstract val outputDirectory: DirectoryProperty + + @get:Internal abstract val tweakedDataConnectConfigDir: DirectoryProperty + + @TaskAction + fun run() { + val dataConnectConfigDir = dataConnectConfigDir.get().asFile + val firebaseExecutable = firebaseExecutable.get().asFile + val outputDirectory = outputDirectory.get().asFile + val tweakedDataConnectConfigDir = tweakedDataConnectConfigDir.get().asFile + + logger.info("dataConnectConfigDir: {}", dataConnectConfigDir) + logger.info("firebaseExecutable: {}", firebaseExecutable) + logger.info("outputDirectory: {}", outputDirectory) + logger.info("tweakedDataConnectConfigDir: {}", tweakedDataConnectConfigDir) + + project.delete(outputDirectory) + project.delete(tweakedDataConnectConfigDir) + project.mkdir(tweakedDataConnectConfigDir) + + project.copy { + from(dataConnectConfigDir) + into(tweakedDataConnectConfigDir) + } + tweakConnectorYamlFiles(tweakedDataConnectConfigDir, outputDirectory.absolutePath) + + runCommand(File(tweakedDataConnectConfigDir, "generate.log.txt")) { + commandLine(firebaseExecutable.absolutePath, "--debug", "dataconnect:sdk:generate") + // Specify a fake project because dataconnect:sdk:generate unnecessarily + // requires one. The actual value does not matter. + args("--project", "zzyzx") + workingDir(tweakedDataConnectConfigDir) + } + } + + internal fun configureFrom(providers: MyVariantProviders) { + dataConnectConfigDir.set(providers.dataConnectConfigDir) + firebaseExecutable.set(providers.firebaseExecutable) + tweakedDataConnectConfigDir.set(providers.buildDirectory.map { it.dir("config") }) + } +} diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/Commands.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/Commands.kt new file mode 100644 index 0000000000..0a53b06ae8 --- /dev/null +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/Commands.kt @@ -0,0 +1,42 @@ +/* + * 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.example.dataconnect.gradle + +import java.io.File +import org.gradle.api.Task +import org.gradle.process.ExecSpec + +internal fun Task.runCommand(logFile: File, configure: ExecSpec.() -> Unit) { + val effectiveLogFile = if (logger.isInfoEnabled) null else logFile + val result = + effectiveLogFile?.outputStream().use { logStream -> + project.runCatching { + exec { + isIgnoreExitValue = false + if (logStream !== null) { + standardOutput = logStream + errorOutput = logStream + } + configure(this) + } + } + } + result.onFailure { exception -> + effectiveLogFile?.let { logger.warn("{}", it.readText()) } + throw exception + } +} diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/ConnectorYamlTweaking.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/ConnectorYamlTweaking.kt new file mode 100644 index 0000000000..fbd9dcf09a --- /dev/null +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/ConnectorYamlTweaking.kt @@ -0,0 +1,89 @@ +/* + * 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.example.dataconnect.gradle + +import java.io.File +import org.gradle.api.GradleException +import org.gradle.api.Task +import org.yaml.snakeyaml.Yaml + +internal fun Task.tweakConnectorYamlFiles(dir: File, newOutputDir: String) { + logger.info("Tweaking connector.yaml files in {}", dir.absolutePath) + dir.walk().forEach { file -> + if (file.isFile && file.name == "connector.yaml") { + tweakConnectorYamlFile(file, newOutputDir) + } else { + logger.debug("skipping file: {}", file.absolutePath) + } + } +} + +internal fun Task.tweakConnectorYamlFile(file: File, newOutputDir: String) { + logger.info("Tweaking connector.yaml file: {}", file.absolutePath) + + fun Map<*, *>.withTweakedKotlinSdk() = + filterKeys { it == "kotlinSdk" } + .mapValues { (_, value) -> + val kotlinSdkMap = + value as? Map<*, *> + ?: throw GradleException( + "Parsing ${file.absolutePath} failed: \"kotlinSdk\" is " + + (if (value === null) "null" else value::class.qualifiedName) + + ", but expected ${Map::class.qualifiedName} " + + "(error code m697s27yxn)" + ) + kotlinSdkMap.mapValues { (key, value) -> + if (key == "outputDir") { + newOutputDir + } else { + value + } + } + } + + fun Map<*, *>.withTweakedGenerateNode() = mapValues { (key, value) -> + if (key != "generate") { + value + } else { + val generateMap = + value as? Map<*, *> + ?: throw GradleException( + "Parsing ${file.absolutePath} failed: \"generate\" is " + + (if (value === null) "null" else value::class.qualifiedName) + + ", but expected ${Map::class.qualifiedName} " + + "(error code 9c2p857gq6)" + ) + generateMap.withTweakedKotlinSdk() + } + } + + val yaml = Yaml() + val rootObject = file.reader(Charsets.UTF_8).use { reader -> yaml.load(reader) } + + val rootMap = + rootObject as? Map<*, *> + ?: throw GradleException( + "Parsing ${file.absolutePath} failed: root is " + + (if (rootObject === null) "null" else rootObject::class.qualifiedName) + + ", but expected ${Map::class.qualifiedName} " + + "(error code 45dw8jx8jd)" + ) + + val newRootMap = rootMap.withTweakedGenerateNode() + + file.writer(Charsets.UTF_8).use { writer -> yaml.dump(newRootMap, writer) } +} diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectExtension.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectExtension.kt new file mode 100644 index 0000000000..7dadff6d7c --- /dev/null +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectExtension.kt @@ -0,0 +1,24 @@ +/* + * 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.example.dataconnect.gradle + +import java.io.File + +interface DataConnectExtension { + var firebaseToolsVersion: String? + var dataConnectConfigDir: File? +} diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectGradlePlugin.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectGradlePlugin.kt new file mode 100644 index 0000000000..57403344ca --- /dev/null +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectGradlePlugin.kt @@ -0,0 +1,63 @@ +/* + * 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.example.dataconnect.gradle + +import com.android.build.api.variant.ApplicationAndroidComponentsExtension +import com.android.build.api.variant.ApplicationVariant +import java.util.Locale +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.kotlin.dsl.getByType +import org.gradle.kotlin.dsl.newInstance +import org.gradle.kotlin.dsl.register + +@Suppress("unused") +abstract class DataConnectGradlePlugin : Plugin { + + override fun apply(project: Project) { + project.extensions.create("dataconnect", DataConnectExtension::class.java) + val providers = project.objects.newInstance() + + project.tasks.register("setupFirebaseToolsForDataConnect") { + configureFrom(providers) + } + + val androidComponents = project.extensions.getByType() + androidComponents.onVariants { variant -> + val variantProviders = project.objects.newInstance(variant) + registerVariantTasks(project, variant, variantProviders) + } + } + + private fun registerVariantTasks( + project: Project, + variant: ApplicationVariant, + providers: MyVariantProviders, + ) { + val variantNameTitleCase = variant.name.replaceFirstChar { it.titlecase(Locale.US) } + + val generateCodeTask = + project.tasks.register("generate${variantNameTitleCase}DataConnectSources") { + configureFrom(providers) + } + + variant.sources.java!!.addGeneratedSourceDirectory( + generateCodeTask, + CodegenTask::outputDirectory, + ) + } +} diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/FirebaseToolsSetupTask.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/FirebaseToolsSetupTask.kt new file mode 100644 index 0000000000..48828f6318 --- /dev/null +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/FirebaseToolsSetupTask.kt @@ -0,0 +1,64 @@ +/* + * 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.example.dataconnect.gradle + +import java.io.File +import org.gradle.api.DefaultTask +import org.gradle.api.file.DirectoryProperty +import org.gradle.api.file.RegularFile +import org.gradle.api.provider.Property +import org.gradle.api.provider.Provider +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.Internal +import org.gradle.api.tasks.OutputDirectory +import org.gradle.api.tasks.TaskAction + +abstract class FirebaseToolsSetupTask : DefaultTask() { + + @get:Input abstract val version: Property + + @get:OutputDirectory abstract val outputDirectory: DirectoryProperty + + @get:Internal + val firebaseExecutable: Provider + get() = outputDirectory.map { it.file("node_modules/.bin/firebase") } + + @TaskAction + fun run() { + val version: String = version.get() + val outputDirectory: File = outputDirectory.get().asFile + + logger.info("version: {}", version) + logger.info("outputDirectory: {}", outputDirectory.absolutePath) + + project.delete(outputDirectory) + project.mkdir(outputDirectory) + + val packageJsonFile = File(outputDirectory, "package.json") + packageJsonFile.writeText("{}", Charsets.UTF_8) + + runCommand(File(outputDirectory, "install.log.txt")) { + commandLine("npm", "install", "firebase-tools@$version") + workingDir(outputDirectory) + } + } + + internal fun configureFrom(providers: MyProjectProviders) { + version.set(providers.firebaseToolsVersion) + outputDirectory.set(providers.buildDirectory.map { it.dir("firebase-tools") }) + } +} diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/Providers.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/Providers.kt new file mode 100644 index 0000000000..dda65d47e0 --- /dev/null +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/Providers.kt @@ -0,0 +1,107 @@ +/* + * 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.example.dataconnect.gradle + +import com.android.build.api.variant.ApplicationVariant +import javax.inject.Inject +import org.gradle.api.GradleException +import org.gradle.api.Project +import org.gradle.api.file.Directory +import org.gradle.api.file.DirectoryProperty +import org.gradle.api.file.RegularFile +import org.gradle.api.model.ObjectFactory +import org.gradle.api.provider.Provider +import org.gradle.api.provider.ProviderFactory +import org.gradle.kotlin.dsl.getByType +import org.gradle.kotlin.dsl.newInstance + +internal open class MyProjectProviders( + projectBuildDirectory: DirectoryProperty, + providerFactory: ProviderFactory, + ext: DataConnectExtension, +) { + + @Suppress("unused") + @Inject + constructor( + project: Project, + ) : this( + projectBuildDirectory = project.layout.buildDirectory, + providerFactory = project.providers, + ext = project.extensions.getByType(), + ) + + val buildDirectory: Provider = projectBuildDirectory.map { it.dir("dataconnect") } + + val firebaseToolsVersion: Provider = + providerFactory.provider { + ext.firebaseToolsVersion + ?: throw GradleException( + "dataconnect.firebaseToolsVersion must be set in your build.gradle or build.gradle.kts " + + "(error code xbmvkc3mtr)" + ) + } +} + +internal open class MyVariantProviders( + variant: ApplicationVariant, + myProjectProviders: MyProjectProviders, + ext: DataConnectExtension, + firebaseToolsSetupTask: FirebaseToolsSetupTask, + objectFactory: ObjectFactory, +) { + + @Suppress("unused") + @Inject + constructor( + variant: ApplicationVariant, + project: Project + ) : this( + variant = variant, + myProjectProviders = project.objects.newInstance(), + ext = project.extensions.getByType(), + firebaseToolsSetupTask = project.firebaseToolsSetupTask, + objectFactory = project.objects, + ) + + val buildDirectory: Provider = + myProjectProviders.buildDirectory.map { it.dir("variants/${variant.name}") } + + val dataConnectConfigDir: Provider = run { + val dir = + ext.dataConnectConfigDir + ?: throw GradleException( + "dataconnect.dataConnectConfigDir must be set in your build.gradle or build.gradle.kts " + + "(error code xbmvkc3mtr)" + ) + objectFactory.directoryProperty().also { property -> property.set(dir) } + } + + val firebaseExecutable: Provider = firebaseToolsSetupTask.firebaseExecutable +} + +private val Project.firebaseToolsSetupTask: FirebaseToolsSetupTask + get() { + val tasks = tasks.filterIsInstance() + if (tasks.size != 1) { + throw GradleException( + "expected exactly 1 FirebaseToolsSetupTask task to be registered, but found " + + "${tasks.size}: [${tasks.map { it.name }.sorted().joinToString(", ")}]" + ) + } + return tasks.single() + } diff --git a/dataconnect/dataconnect/.gitignore b/dataconnect/dataconnect/.gitignore new file mode 100644 index 0000000000..ecb842d705 --- /dev/null +++ b/dataconnect/dataconnect/.gitignore @@ -0,0 +1 @@ +.generated/ diff --git a/dataconnect/dataconnect/movie-connector/connector.yaml b/dataconnect/dataconnect/movie-connector/connector.yaml index 2ba51749dc..eb00af0c66 100644 --- a/dataconnect/dataconnect/movie-connector/connector.yaml +++ b/dataconnect/dataconnect/movie-connector/connector.yaml @@ -8,6 +8,6 @@ generate: kotlinSdk: # Create a custom package name for your generated SDK package: com.google.firebase.dataconnect.movies - # Specify where to store the generated SDK - # We're using the build/ directory so that generated code doesn't get checked into git - outputDir: ../../app/build/generated/sources/com/google/firebase/dataconnect/movies + # Use an arbitrary directory for generating the code, as the custom task in + # app/build.gradle.kts will generate into the appropriate directory. + outputDir: ../.generated diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 00284342f5..4daeaa725c 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -14,8 +14,10 @@ activityCompose = "1.9.3" composeBom = "2024.11.00" googleServices = "4.4.2" composeNavigation = "2.8.4" +ktfmt = "0.43" [libraries] +android-gradlePlugin-api = { group = "com.android.tools.build", name = "gradle-api", version.ref = "agp" } androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } androidx-lifecycle-runtime-compose-android = { module = "androidx.lifecycle:lifecycle-runtime-compose-android", version.ref = "lifecycle" } androidx-lifecycle-viewmodel-compose = { module = "androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "lifecycle" } @@ -37,6 +39,7 @@ androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit androidx-material3 = { group = "androidx.compose.material3", name = "material3" } compose-navigation = { group = "androidx.navigation", name = "navigation-compose", version.ref = "composeNavigation"} kotlinx-serialization-core = { module = "org.jetbrains.kotlinx:kotlinx-serialization-core", version.ref = "kotlinxSerializationCore" } +snakeyaml = { module = "org.yaml:snakeyaml", version = "2.3" } [plugins] android-application = { id = "com.android.application", version.ref = "agp" } @@ -44,3 +47,4 @@ jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } google-services = { id = "com.google.gms.google-services", version.ref = "googleServices" } compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } +spotless = { id = "com.diffplug.spotless", version = "7.0.0.BETA3" } diff --git a/scripts/ci_remove_fdc.py b/scripts/ci_remove_fdc.py deleted file mode 100644 index c425af615b..0000000000 --- a/scripts/ci_remove_fdc.py +++ /dev/null @@ -1,8 +0,0 @@ -# TODO(thatfiredev): remove this once github.com/firebase/quickstart-android/issues/1672 is fixed -with open('settings.gradle.kts', 'r') as file: - filedata = file.read() - -filedata = filedata.replace('":dataconnect:app",', '') - -with open('settings.gradle.kts', 'w') as file: - file.write(filedata) diff --git a/settings.gradle.kts b/settings.gradle.kts index af004974bc..6962a01794 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,4 +1,7 @@ pluginManagement { + includeBuild("dataconnect/buildSrc") { + name = "dataConnectBuildSrc" + } repositories { google() mavenCentral() From bcbc8df68f2f2f7112f0dff7c3e3894eaf84cea6 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Mon, 18 Nov 2024 21:55:16 +0000 Subject: [PATCH 02/79] minor doc cleanups --- dataconnect/README.md | 17 +++++++++++++++++ dataconnect/app/build.gradle.kts | 1 + .../dataconnect/movie-connector/connector.yaml | 6 ++++-- 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/dataconnect/README.md b/dataconnect/README.md index 8366d2dfac..2175c5afdc 100644 --- a/dataconnect/README.md +++ b/dataconnect/README.md @@ -71,3 +71,20 @@ open `quickstart-android/dataconnect/movie-connector/queries.gql` and run the `L ### 5. Running the app Press the Run button in Android Studio to run the sample app on your device. + +### 6. Re-generating the source code for Data Connect connectors + +This project defines a custom Gradle plugin that looks after generating the Kotlin code for the +queries and mutations defined in the GraphQL files. The code will be generated on-demand when +needed by a build. To generate the code manually, run: + +``` +../gradlew generateDebugDataConnectSources +``` + +If you are making frequent changes to the GraphQL files, it may be convenient to run the code +generation upon _every_ change, without waiting for a build. To do that, run: + +``` +../gradlew generateDebugDataConnectSources --continuous +``` diff --git a/dataconnect/app/build.gradle.kts b/dataconnect/app/build.gradle.kts index a5cc12e775..c22fff40e5 100644 --- a/dataconnect/app/build.gradle.kts +++ b/dataconnect/app/build.gradle.kts @@ -54,6 +54,7 @@ android { } dependencies { + implementation(libs.androidx.core.ktx) implementation(libs.androidx.lifecycle.runtime.ktx) implementation(libs.androidx.lifecycle.viewmodel.compose) diff --git a/dataconnect/dataconnect/movie-connector/connector.yaml b/dataconnect/dataconnect/movie-connector/connector.yaml index eb00af0c66..4fe59ab1d2 100644 --- a/dataconnect/dataconnect/movie-connector/connector.yaml +++ b/dataconnect/dataconnect/movie-connector/connector.yaml @@ -8,6 +8,8 @@ generate: kotlinSdk: # Create a custom package name for your generated SDK package: com.google.firebase.dataconnect.movies - # Use an arbitrary directory for generating the code, as the custom task in - # app/build.gradle.kts will generate into the appropriate directory. + # Use an arbitrary directory for generating the code, as the + # "com.google.firebase.example.dataconnect.gradle" custom Gradle plugin applied by + # app/build.gradle.kts will look after generating the code on-demand into the + # appropriate directory. outputDir: ../.generated From 985d3f2d1474850f4831383148f1d6abfd8d7100 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Tue, 19 Nov 2024 18:56:24 +0000 Subject: [PATCH 03/79] workflows/android.yml: change node-version to 20 (was 22) based on review feedback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Rosário P. Fernandes --- .github/workflows/android.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index f89439480c..ac114d9a8e 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -19,7 +19,7 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v4 with: - node-version: 22 + node-version: 20 - name: Check Snippets run: python scripts/checksnippets.py - name: Copy mock google_services.json From 5ab7abe4ec6c9937a11155b1f45e83fab7c2a129 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Tue, 19 Nov 2024 18:56:49 +0000 Subject: [PATCH 04/79] README.md: mark gradle codegen tasks as "optional" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Rosário P. Fernandes --- dataconnect/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dataconnect/README.md b/dataconnect/README.md index 2175c5afdc..0c6a3918f4 100644 --- a/dataconnect/README.md +++ b/dataconnect/README.md @@ -72,7 +72,7 @@ open `quickstart-android/dataconnect/movie-connector/queries.gql` and run the `L Press the Run button in Android Studio to run the sample app on your device. -### 6. Re-generating the source code for Data Connect connectors +### 6. (Optional) Re-generating the source code for Data Connect connectors This project defines a custom Gradle plugin that looks after generating the Kotlin code for the queries and mutations defined in the GraphQL files. The code will be generated on-demand when From 9005866c64f8ac9f1e981dbefdcd22d073027ca5 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Tue, 19 Nov 2024 18:59:17 +0000 Subject: [PATCH 05/79] movie-connector/connector.yaml: update comment to give a concrete example of the directory into which the code may be generated --- dataconnect/dataconnect/movie-connector/connector.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dataconnect/dataconnect/movie-connector/connector.yaml b/dataconnect/dataconnect/movie-connector/connector.yaml index 4fe59ab1d2..61163cf957 100644 --- a/dataconnect/dataconnect/movie-connector/connector.yaml +++ b/dataconnect/dataconnect/movie-connector/connector.yaml @@ -10,6 +10,6 @@ generate: package: com.google.firebase.dataconnect.movies # Use an arbitrary directory for generating the code, as the # "com.google.firebase.example.dataconnect.gradle" custom Gradle plugin applied by - # app/build.gradle.kts will look after generating the code on-demand into the - # appropriate directory. + # app/build.gradle.kts will look after generating the code on-demand into the directory + # prescribed by Gradle (e.g. app/build/generated/java/generateDebugDataConnectSources). outputDir: ../.generated From 1fce12488dcfe4672516e6d83d0801bcc2c7aade Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Tue, 19 Nov 2024 19:10:07 +0000 Subject: [PATCH 06/79] get rid of ktfmt and spotless, since ktlint is what is used in this repo --- dataconnect/buildSrc/build.gradle.kts | 9 --------- gradle/libs.versions.toml | 2 -- 2 files changed, 11 deletions(-) diff --git a/dataconnect/buildSrc/build.gradle.kts b/dataconnect/buildSrc/build.gradle.kts index 36b9fb605d..77eaa8bd5e 100644 --- a/dataconnect/buildSrc/build.gradle.kts +++ b/dataconnect/buildSrc/build.gradle.kts @@ -17,7 +17,6 @@ plugins { // See https://docs.gradle.org/current/userguide/kotlin_dsl.html#sec:kotlin-dsl_plugin `kotlin-dsl` - alias(libs.plugins.spotless) } java { toolchain { languageVersion.set(JavaLanguageVersion.of(17)) } } @@ -35,11 +34,3 @@ gradlePlugin { } } } - -spotless { - kotlin { ktfmt(libs.versions.ktfmt.get()).googleStyle() } - kotlinGradle { - target("*.gradle.kts") - ktfmt(libs.versions.ktfmt.get()).googleStyle() - } -} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 6f49364f5a..4f210b3411 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -14,7 +14,6 @@ activityCompose = "1.9.3" composeBom = "2024.11.00" googleServices = "4.4.2" composeNavigation = "2.8.4" -ktfmt = "0.43" [libraries] android-gradlePlugin-api = { group = "com.android.tools.build", name = "gradle-api", version.ref = "agp" } @@ -47,4 +46,3 @@ jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } google-services = { id = "com.google.gms.google-services", version.ref = "googleServices" } compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } -spotless = { id = "com.diffplug.spotless", version = "7.0.0.BETA3" } From 510f2585850cca41ba42bb0c29190a81b2da63bd Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Tue, 19 Nov 2024 20:09:04 +0000 Subject: [PATCH 07/79] ktlint format the code in dataconnect/buildSrc by running ./gradlew ktlintCheck --- .../example/dataconnect/gradle/CodegenTask.kt | 68 +++++----- .../example/dataconnect/gradle/Commands.kt | 32 ++--- .../gradle/ConnectorYamlTweaking.kt | 104 +++++++-------- .../gradle/DataConnectExtension.kt | 4 +- .../gradle/DataConnectGradlePlugin.kt | 58 ++++----- .../gradle/FirebaseToolsSetupTask.kt | 46 +++---- .../example/dataconnect/gradle/Providers.kt | 118 +++++++++--------- 7 files changed, 215 insertions(+), 215 deletions(-) diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/CodegenTask.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/CodegenTask.kt index 90dedc587f..ed7041a858 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/CodegenTask.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/CodegenTask.kt @@ -28,48 +28,48 @@ import org.gradle.api.tasks.TaskAction abstract class CodegenTask : DefaultTask() { - @get:InputDirectory abstract val dataConnectConfigDir: DirectoryProperty + @get:InputDirectory abstract val dataConnectConfigDir: DirectoryProperty - @get:InputFile abstract val firebaseExecutable: RegularFileProperty + @get:InputFile abstract val firebaseExecutable: RegularFileProperty - @get:OutputDirectory abstract val outputDirectory: DirectoryProperty + @get:OutputDirectory abstract val outputDirectory: DirectoryProperty - @get:Internal abstract val tweakedDataConnectConfigDir: DirectoryProperty + @get:Internal abstract val tweakedDataConnectConfigDir: DirectoryProperty - @TaskAction - fun run() { - val dataConnectConfigDir = dataConnectConfigDir.get().asFile - val firebaseExecutable = firebaseExecutable.get().asFile - val outputDirectory = outputDirectory.get().asFile - val tweakedDataConnectConfigDir = tweakedDataConnectConfigDir.get().asFile + @TaskAction + fun run() { + val dataConnectConfigDir = dataConnectConfigDir.get().asFile + val firebaseExecutable = firebaseExecutable.get().asFile + val outputDirectory = outputDirectory.get().asFile + val tweakedDataConnectConfigDir = tweakedDataConnectConfigDir.get().asFile - logger.info("dataConnectConfigDir: {}", dataConnectConfigDir) - logger.info("firebaseExecutable: {}", firebaseExecutable) - logger.info("outputDirectory: {}", outputDirectory) - logger.info("tweakedDataConnectConfigDir: {}", tweakedDataConnectConfigDir) + logger.info("dataConnectConfigDir: {}", dataConnectConfigDir) + logger.info("firebaseExecutable: {}", firebaseExecutable) + logger.info("outputDirectory: {}", outputDirectory) + logger.info("tweakedDataConnectConfigDir: {}", tweakedDataConnectConfigDir) - project.delete(outputDirectory) - project.delete(tweakedDataConnectConfigDir) - project.mkdir(tweakedDataConnectConfigDir) + project.delete(outputDirectory) + project.delete(tweakedDataConnectConfigDir) + project.mkdir(tweakedDataConnectConfigDir) - project.copy { - from(dataConnectConfigDir) - into(tweakedDataConnectConfigDir) - } - tweakConnectorYamlFiles(tweakedDataConnectConfigDir, outputDirectory.absolutePath) + project.copy { + from(dataConnectConfigDir) + into(tweakedDataConnectConfigDir) + } + tweakConnectorYamlFiles(tweakedDataConnectConfigDir, outputDirectory.absolutePath) - runCommand(File(tweakedDataConnectConfigDir, "generate.log.txt")) { - commandLine(firebaseExecutable.absolutePath, "--debug", "dataconnect:sdk:generate") - // Specify a fake project because dataconnect:sdk:generate unnecessarily - // requires one. The actual value does not matter. - args("--project", "zzyzx") - workingDir(tweakedDataConnectConfigDir) + runCommand(File(tweakedDataConnectConfigDir, "generate.log.txt")) { + commandLine(firebaseExecutable.absolutePath, "--debug", "dataconnect:sdk:generate") + // Specify a fake project because dataconnect:sdk:generate unnecessarily + // requires one. The actual value does not matter. + args("--project", "zzyzx") + workingDir(tweakedDataConnectConfigDir) + } } - } - internal fun configureFrom(providers: MyVariantProviders) { - dataConnectConfigDir.set(providers.dataConnectConfigDir) - firebaseExecutable.set(providers.firebaseExecutable) - tweakedDataConnectConfigDir.set(providers.buildDirectory.map { it.dir("config") }) - } + internal fun configureFrom(providers: MyVariantProviders) { + dataConnectConfigDir.set(providers.dataConnectConfigDir) + firebaseExecutable.set(providers.firebaseExecutable) + tweakedDataConnectConfigDir.set(providers.buildDirectory.map { it.dir("config") }) + } } diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/Commands.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/Commands.kt index 0a53b06ae8..d84ec4b248 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/Commands.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/Commands.kt @@ -21,22 +21,22 @@ import org.gradle.api.Task import org.gradle.process.ExecSpec internal fun Task.runCommand(logFile: File, configure: ExecSpec.() -> Unit) { - val effectiveLogFile = if (logger.isInfoEnabled) null else logFile - val result = - effectiveLogFile?.outputStream().use { logStream -> - project.runCatching { - exec { - isIgnoreExitValue = false - if (logStream !== null) { - standardOutput = logStream - errorOutput = logStream - } - configure(this) + val effectiveLogFile = if (logger.isInfoEnabled) null else logFile + val result = + effectiveLogFile?.outputStream().use { logStream -> + project.runCatching { + exec { + isIgnoreExitValue = false + if (logStream !== null) { + standardOutput = logStream + errorOutput = logStream + } + configure(this) + } + } } - } + result.onFailure { exception -> + effectiveLogFile?.let { logger.warn("{}", it.readText()) } + throw exception } - result.onFailure { exception -> - effectiveLogFile?.let { logger.warn("{}", it.readText()) } - throw exception - } } diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/ConnectorYamlTweaking.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/ConnectorYamlTweaking.kt index fbd9dcf09a..da60a6b6a3 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/ConnectorYamlTweaking.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/ConnectorYamlTweaking.kt @@ -22,68 +22,68 @@ import org.gradle.api.Task import org.yaml.snakeyaml.Yaml internal fun Task.tweakConnectorYamlFiles(dir: File, newOutputDir: String) { - logger.info("Tweaking connector.yaml files in {}", dir.absolutePath) - dir.walk().forEach { file -> - if (file.isFile && file.name == "connector.yaml") { - tweakConnectorYamlFile(file, newOutputDir) - } else { - logger.debug("skipping file: {}", file.absolutePath) + logger.info("Tweaking connector.yaml files in {}", dir.absolutePath) + dir.walk().forEach { file -> + if (file.isFile && file.name == "connector.yaml") { + tweakConnectorYamlFile(file, newOutputDir) + } else { + logger.debug("skipping file: {}", file.absolutePath) + } } - } } internal fun Task.tweakConnectorYamlFile(file: File, newOutputDir: String) { - logger.info("Tweaking connector.yaml file: {}", file.absolutePath) + logger.info("Tweaking connector.yaml file: {}", file.absolutePath) - fun Map<*, *>.withTweakedKotlinSdk() = - filterKeys { it == "kotlinSdk" } - .mapValues { (_, value) -> - val kotlinSdkMap = - value as? Map<*, *> - ?: throw GradleException( - "Parsing ${file.absolutePath} failed: \"kotlinSdk\" is " + - (if (value === null) "null" else value::class.qualifiedName) + - ", but expected ${Map::class.qualifiedName} " + - "(error code m697s27yxn)" - ) - kotlinSdkMap.mapValues { (key, value) -> - if (key == "outputDir") { - newOutputDir - } else { + fun Map<*, *>.withTweakedKotlinSdk() = + filterKeys { it == "kotlinSdk" } + .mapValues { (_, value) -> + val kotlinSdkMap = + value as? Map<*, *> + ?: throw GradleException( + "Parsing ${file.absolutePath} failed: \"kotlinSdk\" is " + + (if (value === null) "null" else value::class.qualifiedName) + + ", but expected ${Map::class.qualifiedName} " + + "(error code m697s27yxn)" + ) + kotlinSdkMap.mapValues { (key, value) -> + if (key == "outputDir") { + newOutputDir + } else { + value + } + } + } + + fun Map<*, *>.withTweakedGenerateNode() = mapValues { (key, value) -> + if (key != "generate") { value - } + } else { + val generateMap = + value as? Map<*, *> + ?: throw GradleException( + "Parsing ${file.absolutePath} failed: \"generate\" is " + + (if (value === null) "null" else value::class.qualifiedName) + + ", but expected ${Map::class.qualifiedName} " + + "(error code 9c2p857gq6)" + ) + generateMap.withTweakedKotlinSdk() } - } - - fun Map<*, *>.withTweakedGenerateNode() = mapValues { (key, value) -> - if (key != "generate") { - value - } else { - val generateMap = - value as? Map<*, *> - ?: throw GradleException( - "Parsing ${file.absolutePath} failed: \"generate\" is " + - (if (value === null) "null" else value::class.qualifiedName) + - ", but expected ${Map::class.qualifiedName} " + - "(error code 9c2p857gq6)" - ) - generateMap.withTweakedKotlinSdk() } - } - val yaml = Yaml() - val rootObject = file.reader(Charsets.UTF_8).use { reader -> yaml.load(reader) } + val yaml = Yaml() + val rootObject = file.reader(Charsets.UTF_8).use { reader -> yaml.load(reader) } - val rootMap = - rootObject as? Map<*, *> - ?: throw GradleException( - "Parsing ${file.absolutePath} failed: root is " + - (if (rootObject === null) "null" else rootObject::class.qualifiedName) + - ", but expected ${Map::class.qualifiedName} " + - "(error code 45dw8jx8jd)" - ) + val rootMap = + rootObject as? Map<*, *> + ?: throw GradleException( + "Parsing ${file.absolutePath} failed: root is " + + (if (rootObject === null) "null" else rootObject::class.qualifiedName) + + ", but expected ${Map::class.qualifiedName} " + + "(error code 45dw8jx8jd)" + ) - val newRootMap = rootMap.withTweakedGenerateNode() + val newRootMap = rootMap.withTweakedGenerateNode() - file.writer(Charsets.UTF_8).use { writer -> yaml.dump(newRootMap, writer) } + file.writer(Charsets.UTF_8).use { writer -> yaml.dump(newRootMap, writer) } } diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectExtension.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectExtension.kt index 7dadff6d7c..0c383f3321 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectExtension.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectExtension.kt @@ -19,6 +19,6 @@ package com.google.firebase.example.dataconnect.gradle import java.io.File interface DataConnectExtension { - var firebaseToolsVersion: String? - var dataConnectConfigDir: File? + var firebaseToolsVersion: String? + var dataConnectConfigDir: File? } diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectGradlePlugin.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectGradlePlugin.kt index 57403344ca..b197307374 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectGradlePlugin.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectGradlePlugin.kt @@ -28,36 +28,36 @@ import org.gradle.kotlin.dsl.register @Suppress("unused") abstract class DataConnectGradlePlugin : Plugin { - override fun apply(project: Project) { - project.extensions.create("dataconnect", DataConnectExtension::class.java) - val providers = project.objects.newInstance() - - project.tasks.register("setupFirebaseToolsForDataConnect") { - configureFrom(providers) + override fun apply(project: Project) { + project.extensions.create("dataconnect", DataConnectExtension::class.java) + val providers = project.objects.newInstance() + + project.tasks.register("setupFirebaseToolsForDataConnect") { + configureFrom(providers) + } + + val androidComponents = project.extensions.getByType() + androidComponents.onVariants { variant -> + val variantProviders = project.objects.newInstance(variant) + registerVariantTasks(project, variant, variantProviders) + } } - val androidComponents = project.extensions.getByType() - androidComponents.onVariants { variant -> - val variantProviders = project.objects.newInstance(variant) - registerVariantTasks(project, variant, variantProviders) + private fun registerVariantTasks( + project: Project, + variant: ApplicationVariant, + providers: MyVariantProviders + ) { + val variantNameTitleCase = variant.name.replaceFirstChar { it.titlecase(Locale.US) } + + val generateCodeTask = + project.tasks.register("generate${variantNameTitleCase}DataConnectSources") { + configureFrom(providers) + } + + variant.sources.java!!.addGeneratedSourceDirectory( + generateCodeTask, + CodegenTask::outputDirectory + ) } - } - - private fun registerVariantTasks( - project: Project, - variant: ApplicationVariant, - providers: MyVariantProviders, - ) { - val variantNameTitleCase = variant.name.replaceFirstChar { it.titlecase(Locale.US) } - - val generateCodeTask = - project.tasks.register("generate${variantNameTitleCase}DataConnectSources") { - configureFrom(providers) - } - - variant.sources.java!!.addGeneratedSourceDirectory( - generateCodeTask, - CodegenTask::outputDirectory, - ) - } } diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/FirebaseToolsSetupTask.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/FirebaseToolsSetupTask.kt index 48828f6318..ef55ee718c 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/FirebaseToolsSetupTask.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/FirebaseToolsSetupTask.kt @@ -29,36 +29,36 @@ import org.gradle.api.tasks.TaskAction abstract class FirebaseToolsSetupTask : DefaultTask() { - @get:Input abstract val version: Property + @get:Input abstract val version: Property - @get:OutputDirectory abstract val outputDirectory: DirectoryProperty + @get:OutputDirectory abstract val outputDirectory: DirectoryProperty - @get:Internal - val firebaseExecutable: Provider - get() = outputDirectory.map { it.file("node_modules/.bin/firebase") } + @get:Internal + val firebaseExecutable: Provider + get() = outputDirectory.map { it.file("node_modules/.bin/firebase") } - @TaskAction - fun run() { - val version: String = version.get() - val outputDirectory: File = outputDirectory.get().asFile + @TaskAction + fun run() { + val version: String = version.get() + val outputDirectory: File = outputDirectory.get().asFile - logger.info("version: {}", version) - logger.info("outputDirectory: {}", outputDirectory.absolutePath) + logger.info("version: {}", version) + logger.info("outputDirectory: {}", outputDirectory.absolutePath) - project.delete(outputDirectory) - project.mkdir(outputDirectory) + project.delete(outputDirectory) + project.mkdir(outputDirectory) - val packageJsonFile = File(outputDirectory, "package.json") - packageJsonFile.writeText("{}", Charsets.UTF_8) + val packageJsonFile = File(outputDirectory, "package.json") + packageJsonFile.writeText("{}", Charsets.UTF_8) - runCommand(File(outputDirectory, "install.log.txt")) { - commandLine("npm", "install", "firebase-tools@$version") - workingDir(outputDirectory) + runCommand(File(outputDirectory, "install.log.txt")) { + commandLine("npm", "install", "firebase-tools@$version") + workingDir(outputDirectory) + } } - } - internal fun configureFrom(providers: MyProjectProviders) { - version.set(providers.firebaseToolsVersion) - outputDirectory.set(providers.buildDirectory.map { it.dir("firebase-tools") }) - } + internal fun configureFrom(providers: MyProjectProviders) { + version.set(providers.firebaseToolsVersion) + outputDirectory.set(providers.buildDirectory.map { it.dir("firebase-tools") }) + } } diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/Providers.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/Providers.kt index dda65d47e0..ec9c5fd0ef 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/Providers.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/Providers.kt @@ -30,78 +30,78 @@ import org.gradle.kotlin.dsl.getByType import org.gradle.kotlin.dsl.newInstance internal open class MyProjectProviders( - projectBuildDirectory: DirectoryProperty, - providerFactory: ProviderFactory, - ext: DataConnectExtension, + projectBuildDirectory: DirectoryProperty, + providerFactory: ProviderFactory, + ext: DataConnectExtension ) { - @Suppress("unused") - @Inject - constructor( - project: Project, - ) : this( - projectBuildDirectory = project.layout.buildDirectory, - providerFactory = project.providers, - ext = project.extensions.getByType(), - ) + @Suppress("unused") + @Inject + constructor( + project: Project + ) : this( + projectBuildDirectory = project.layout.buildDirectory, + providerFactory = project.providers, + ext = project.extensions.getByType() + ) - val buildDirectory: Provider = projectBuildDirectory.map { it.dir("dataconnect") } + val buildDirectory: Provider = projectBuildDirectory.map { it.dir("dataconnect") } - val firebaseToolsVersion: Provider = - providerFactory.provider { - ext.firebaseToolsVersion - ?: throw GradleException( - "dataconnect.firebaseToolsVersion must be set in your build.gradle or build.gradle.kts " + - "(error code xbmvkc3mtr)" - ) - } + val firebaseToolsVersion: Provider = + providerFactory.provider { + ext.firebaseToolsVersion + ?: throw GradleException( + "dataconnect.firebaseToolsVersion must be set in your build.gradle or build.gradle.kts " + + "(error code xbmvkc3mtr)" + ) + } } internal open class MyVariantProviders( - variant: ApplicationVariant, - myProjectProviders: MyProjectProviders, - ext: DataConnectExtension, - firebaseToolsSetupTask: FirebaseToolsSetupTask, - objectFactory: ObjectFactory, + variant: ApplicationVariant, + myProjectProviders: MyProjectProviders, + ext: DataConnectExtension, + firebaseToolsSetupTask: FirebaseToolsSetupTask, + objectFactory: ObjectFactory ) { - @Suppress("unused") - @Inject - constructor( - variant: ApplicationVariant, - project: Project - ) : this( - variant = variant, - myProjectProviders = project.objects.newInstance(), - ext = project.extensions.getByType(), - firebaseToolsSetupTask = project.firebaseToolsSetupTask, - objectFactory = project.objects, - ) + @Suppress("unused") + @Inject + constructor( + variant: ApplicationVariant, + project: Project + ) : this( + variant = variant, + myProjectProviders = project.objects.newInstance(), + ext = project.extensions.getByType(), + firebaseToolsSetupTask = project.firebaseToolsSetupTask, + objectFactory = project.objects + ) - val buildDirectory: Provider = - myProjectProviders.buildDirectory.map { it.dir("variants/${variant.name}") } + val buildDirectory: Provider = + myProjectProviders.buildDirectory.map { it.dir("variants/${variant.name}") } - val dataConnectConfigDir: Provider = run { - val dir = - ext.dataConnectConfigDir - ?: throw GradleException( - "dataconnect.dataConnectConfigDir must be set in your build.gradle or build.gradle.kts " + - "(error code xbmvkc3mtr)" - ) - objectFactory.directoryProperty().also { property -> property.set(dir) } - } + val dataConnectConfigDir: Provider = run { + val dir = + ext.dataConnectConfigDir + ?: throw GradleException( + "dataconnect.dataConnectConfigDir must be set in your build.gradle or build.gradle.kts " + + "(error code xbmvkc3mtr)" + ) + objectFactory.directoryProperty().also { property -> property.set(dir) } + } - val firebaseExecutable: Provider = firebaseToolsSetupTask.firebaseExecutable + val firebaseExecutable: Provider = firebaseToolsSetupTask.firebaseExecutable } private val Project.firebaseToolsSetupTask: FirebaseToolsSetupTask - get() { - val tasks = tasks.filterIsInstance() - if (tasks.size != 1) { - throw GradleException( - "expected exactly 1 FirebaseToolsSetupTask task to be registered, but found " + - "${tasks.size}: [${tasks.map { it.name }.sorted().joinToString(", ")}]" - ) + get() { + val tasks = tasks.filterIsInstance() + if (tasks.size != 1) { + throw GradleException( + "expected exactly 1 FirebaseToolsSetupTask task to be registered, but found " + + "${tasks.size}: [${tasks.map { it.name }.sorted().joinToString(", ")}]" + ) + } + return tasks.single() } - return tasks.single() - } From a66e0ae4ed8e13231807df087cd142b7d3792943 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Tue, 19 Nov 2024 22:08:58 +0000 Subject: [PATCH 08/79] add the ability to explictly specify the path to the npm executable --- dataconnect/.gitignore | 4 ++ dataconnect/buildSrc/build.gradle.kts | 9 +++ .../gradle/TransformerInterop.java | 37 ++++++++++++ .../gradle/FirebaseToolsSetupTask.kt | 32 +++++++++- .../example/dataconnect/gradle/LocalConfig.kt | 30 ++++++++++ .../example/dataconnect/gradle/Providers.kt | 59 ++++++++++++++++++- dataconnect/dataconnect.local.toml | 10 ++++ 7 files changed, 179 insertions(+), 2 deletions(-) create mode 100644 dataconnect/buildSrc/src/main/java/com/google/firebase/example/dataconnect/gradle/TransformerInterop.java create mode 100644 dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/LocalConfig.kt create mode 100644 dataconnect/dataconnect.local.toml diff --git a/dataconnect/.gitignore b/dataconnect/.gitignore index 5b4ab9b06c..02a251f3ea 100644 --- a/dataconnect/.gitignore +++ b/dataconnect/.gitignore @@ -15,3 +15,7 @@ local.properties .dataconnect/ .firebaserc + +# The file used by our custom gradle plugin to specify _local_ settings. +# Since the settings are "local", they should *not* be checked into source control. +dataconnect.local.toml diff --git a/dataconnect/buildSrc/build.gradle.kts b/dataconnect/buildSrc/build.gradle.kts index 77eaa8bd5e..c90e5a9018 100644 --- a/dataconnect/buildSrc/build.gradle.kts +++ b/dataconnect/buildSrc/build.gradle.kts @@ -17,6 +17,7 @@ plugins { // See https://docs.gradle.org/current/userguide/kotlin_dsl.html#sec:kotlin-dsl_plugin `kotlin-dsl` + kotlin("plugin.serialization") version embeddedKotlinVersion } java { toolchain { languageVersion.set(JavaLanguageVersion.of(17)) } } @@ -24,6 +25,14 @@ java { toolchain { languageVersion.set(JavaLanguageVersion.of(17)) } } dependencies { implementation(libs.android.gradlePlugin.api) implementation(libs.snakeyaml) + + // TODO: Upgrade the `tomlkt` dependency to 0.4.0 or later once the gradle + // wrapper version used by this project uses a sufficiently-recent version + // of kotlin. At the time of writing, `embeddedKotlinVersion` is 1.9.22, + // which requires an older version of `tomlkt` because the newer versions + // depend on a newer version of the `kotlinx.serialization` plugin, which + // requires a newer version of Kotlin. + implementation("net.peanuuutz.tomlkt:tomlkt:0.3.7") } gradlePlugin { diff --git a/dataconnect/buildSrc/src/main/java/com/google/firebase/example/dataconnect/gradle/TransformerInterop.java b/dataconnect/buildSrc/src/main/java/com/google/firebase/example/dataconnect/gradle/TransformerInterop.java new file mode 100644 index 0000000000..d4fb88ee1a --- /dev/null +++ b/dataconnect/buildSrc/src/main/java/com/google/firebase/example/dataconnect/gradle/TransformerInterop.java @@ -0,0 +1,37 @@ +/* + * 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.example.dataconnect.gradle; + +import org.gradle.api.Transformer; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +// TODO: Remove this interface and use Transformer directly once the Kotlin +// version is upgraded to a later version that doesn't require it, such as +// 1.9.25. At the time of writing, the Kotlin version in use is 1.9.22. +// +// Using this interface works around the following Kotlin compiler error: +// +// > Task :plugin:compileKotlin FAILED +// e: DataConnectGradlePlugin.kt:93:15 Type mismatch: inferred type is RegularFile? but TypeVariable(S) was expected +// e: DataConnectGradlePlugin.kt:102:15 Type mismatch: inferred type is String? but TypeVariable(S) was expected +// e: DataConnectGradlePlugin.kt:111:15 Type mismatch: inferred type is DataConnectExecutable.VerificationInfo? but TypeVariable(S) was expected +public interface TransformerInterop extends Transformer { + + @Override + @Nullable OUT transform(@NotNull IN in); + +} \ No newline at end of file diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/FirebaseToolsSetupTask.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/FirebaseToolsSetupTask.kt index ef55ee718c..02fb84bac7 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/FirebaseToolsSetupTask.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/FirebaseToolsSetupTask.kt @@ -18,12 +18,15 @@ package com.google.firebase.example.dataconnect.gradle import java.io.File import org.gradle.api.DefaultTask +import org.gradle.api.GradleException import org.gradle.api.file.DirectoryProperty import org.gradle.api.file.RegularFile import org.gradle.api.provider.Property import org.gradle.api.provider.Provider 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.OutputDirectory import org.gradle.api.tasks.TaskAction @@ -31,6 +34,9 @@ abstract class FirebaseToolsSetupTask : DefaultTask() { @get:Input abstract val version: Property + @get:InputFile @get:Optional + abstract val npmExecutable: Property + @get:OutputDirectory abstract val outputDirectory: DirectoryProperty @get:Internal @@ -40,9 +46,11 @@ abstract class FirebaseToolsSetupTask : DefaultTask() { @TaskAction fun run() { val version: String = version.get() + val npmExecutable: File? = npmExecutable.orNull val outputDirectory: File = outputDirectory.get().asFile logger.info("version: {}", version) + logger.info("npmExecutable: {}", npmExecutable?.absolutePath) logger.info("outputDirectory: {}", outputDirectory.absolutePath) project.delete(outputDirectory) @@ -52,7 +60,8 @@ abstract class FirebaseToolsSetupTask : DefaultTask() { packageJsonFile.writeText("{}", Charsets.UTF_8) runCommand(File(outputDirectory, "install.log.txt")) { - commandLine("npm", "install", "firebase-tools@$version") + val arg0 = npmExecutable?.absolutePath ?: "npm" + commandLine(arg0, "install", "firebase-tools@$version") workingDir(outputDirectory) } } @@ -60,5 +69,26 @@ abstract class FirebaseToolsSetupTask : DefaultTask() { internal fun configureFrom(providers: MyProjectProviders) { version.set(providers.firebaseToolsVersion) outputDirectory.set(providers.buildDirectory.map { it.dir("firebase-tools") }) + + npmExecutable.set( + providers.localConfigs.map( + TransformerInterop { localConfigs -> + val result = localConfigs.filter { + it.npmExecutable !== null + }.map { Pair(it.srcFile, it.npmExecutable!!) }.firstOrNull() + result?.let { (configFile, npmExecutablePath) -> + File(npmExecutablePath).also { + if (!it.exists()) { + throw GradleException( + "npmExecutable specified in ${configFile?.absolutePath} " + + "does not exist: ${it.absolutePath} " + + "(error code eaw5gppkep)" + ) + } + } + } + } + ) + ) } } diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/LocalConfig.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/LocalConfig.kt new file mode 100644 index 0000000000..7a557f2a30 --- /dev/null +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/LocalConfig.kt @@ -0,0 +1,30 @@ +/* + * 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.example.dataconnect.gradle + +import java.io.File +import kotlinx.serialization.Serializable +import kotlinx.serialization.Transient + +@Serializable +internal data class LocalConfig(val npmExecutable: String? = null, @Transient val srcFile: File? = null) : + java.io.Serializable { + companion object { + @Suppress("ConstPropertyName") + private const val serialVersionUID = 6103369922496556758L + } +} diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/Providers.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/Providers.kt index ec9c5fd0ef..0dc1a88dca 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/Providers.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/Providers.kt @@ -17,7 +17,10 @@ package com.google.firebase.example.dataconnect.gradle import com.android.build.api.variant.ApplicationVariant +import java.io.FileNotFoundException import javax.inject.Inject +import net.peanuuutz.tomlkt.Toml +import net.peanuuutz.tomlkt.decodeFromString import org.gradle.api.GradleException import org.gradle.api.Project import org.gradle.api.file.Directory @@ -32,6 +35,7 @@ import org.gradle.kotlin.dsl.newInstance internal open class MyProjectProviders( projectBuildDirectory: DirectoryProperty, providerFactory: ProviderFactory, + projectDirectoryHierarchy: List, ext: DataConnectExtension ) { @@ -42,6 +46,7 @@ internal open class MyProjectProviders( ) : this( projectBuildDirectory = project.layout.buildDirectory, providerFactory = project.providers, + projectDirectoryHierarchy = project.projectDirectoryHierarchy(), ext = project.extensions.getByType() ) @@ -51,10 +56,54 @@ internal open class MyProjectProviders( providerFactory.provider { ext.firebaseToolsVersion ?: throw GradleException( - "dataconnect.firebaseToolsVersion must be set in your build.gradle or build.gradle.kts " + + "dataconnect.firebaseToolsVersion must be set in your " + + "build.gradle or build.gradle.kts " + "(error code xbmvkc3mtr)" ) } + + val localConfigFiles: Provider> = providerFactory.provider { + projectDirectoryHierarchy.map { it.file("dataconnect.local.toml") } + } + + val localConfigs: Provider> = run { + val lazyResult = lazy(LazyThreadSafetyMode.PUBLICATION) { + projectDirectoryHierarchy + .map { it.file("dataconnect.local.toml").asFile } + .mapNotNull { file -> + val text = file.runCatching { readText() }.fold( + onSuccess = { it }, + onFailure = { exception -> + if (exception is FileNotFoundException) { + null // ignore non-existent config files + } else { + throw GradleException( + "reading file failed: ${file.absolutePath} ($exception)" + + " (error code bj7dxvvw5p)", + exception + ) + } + } + ) + if (text === null) null else Pair(file, text) + }.map { (file, text) -> + val toml = Toml { this.ignoreUnknownKeys = true } + toml.runCatching { + decodeFromString(text, "dataconnect").copy(srcFile = file) + }.fold( + onSuccess = { it }, + onFailure = { exception -> + throw GradleException( + "parsing toml file failed: ${file.absolutePath} ($exception)" + + " (error code 44dkc2vvpq)", + exception + ) + } + ) + } + } + providerFactory.provider { lazyResult.value } + } } internal open class MyVariantProviders( @@ -105,3 +154,11 @@ private val Project.firebaseToolsSetupTask: FirebaseToolsSetupTask } return tasks.single() } + +private fun Project.projectDirectoryHierarchy(): List = buildList { + var curProject: Project? = this@projectDirectoryHierarchy + while (curProject !== null) { + add(curProject.layout.projectDirectory) + curProject = curProject.parent + } +} diff --git a/dataconnect/dataconnect.local.toml b/dataconnect/dataconnect.local.toml new file mode 100644 index 0000000000..0da54c13aa --- /dev/null +++ b/dataconnect/dataconnect.local.toml @@ -0,0 +1,10 @@ +[dataconnect] + +# The path of the "npm" executable to use to install firebase-tools. +# Setting this is normally not necessary; however, if "npm" is not in the global +# PATH, or the wrong version is in the global PATH, then setting this to the absolute +# path of the npm executable to use works around that problem. Setting it to null +# uses "npm" as found in the PATH. +# +# eg. npmExecutable = "/home/myusername/local/nvm/versions/node/v20.13.1/bin/npm" +npmExecutable = null From b598af6d1e651d3c05cce4f972ce269a555e23b6 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Tue, 19 Nov 2024 22:11:48 +0000 Subject: [PATCH 09/79] First start at supporting a nodeExecutable setting in dataconnect.local.toml --- .../example/dataconnect/gradle/LocalConfig.kt | 6 +++++- dataconnect/dataconnect.local.toml | 15 +++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/LocalConfig.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/LocalConfig.kt index 7a557f2a30..6f330a4c26 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/LocalConfig.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/LocalConfig.kt @@ -21,7 +21,11 @@ import kotlinx.serialization.Serializable import kotlinx.serialization.Transient @Serializable -internal data class LocalConfig(val npmExecutable: String? = null, @Transient val srcFile: File? = null) : +internal data class LocalConfig( + val npmExecutable: String? = null, + val nodeExecutable: String? = null, + @Transient val srcFile: File? = null +) : java.io.Serializable { companion object { @Suppress("ConstPropertyName") diff --git a/dataconnect/dataconnect.local.toml b/dataconnect/dataconnect.local.toml index 0da54c13aa..01b53835e3 100644 --- a/dataconnect/dataconnect.local.toml +++ b/dataconnect/dataconnect.local.toml @@ -6,5 +6,20 @@ # path of the npm executable to use works around that problem. Setting it to null # uses "npm" as found in the PATH. # +# NOTE: If this value is set then you almost certainly want to set nodeExecutable +# as well. +# # eg. npmExecutable = "/home/myusername/local/nvm/versions/node/v20.13.1/bin/npm" npmExecutable = null + +# The path of the "node" executable to use to run the "firebase" command. +# Setting this is normally not necessary; however, if "node" is not in the global +# PATH, or the wrong version is in the global PATH, then setting this to the absolute +# path of the node executable to use works around that problem. Setting it to null +# uses "node" as found in the PATH. +# +# NOTE: If this value is set then you almost certainly want to set npmExecutable +# as well. +# +# eg. nodeExecutable = "/home/myusername/local/nvm/versions/node/v20.13.1/bin/node" +nodeExecutable = null From ee276410af81b0e810ae65267e4e50198829984d Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Wed, 20 Nov 2024 04:26:43 +0000 Subject: [PATCH 10/79] enable specifying nodeExecutable in dataconnect.local.toml as well as npmExecutable --- .../example/dataconnect/gradle/CodegenTask.kt | 9 +- .../gradle/DataConnectGradlePlugin.kt | 2 +- .../gradle/FirebaseToolsSetupTask.kt | 32 +---- .../example/dataconnect/gradle/LocalConfig.kt | 7 +- .../example/dataconnect/gradle/Providers.kt | 121 +++++++++++++----- 5 files changed, 107 insertions(+), 64 deletions(-) diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/CodegenTask.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/CodegenTask.kt index ed7041a858..2b2685eb1c 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/CodegenTask.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/CodegenTask.kt @@ -32,6 +32,9 @@ abstract class CodegenTask : DefaultTask() { @get:InputFile abstract val firebaseExecutable: RegularFileProperty + @get:Internal + abstract val nodeExecutable: RegularFileProperty + @get:OutputDirectory abstract val outputDirectory: DirectoryProperty @get:Internal abstract val tweakedDataConnectConfigDir: DirectoryProperty @@ -40,11 +43,13 @@ abstract class CodegenTask : DefaultTask() { fun run() { val dataConnectConfigDir = dataConnectConfigDir.get().asFile val firebaseExecutable = firebaseExecutable.get().asFile + val nodeExecutable = nodeExecutable.orNull?.asFile val outputDirectory = outputDirectory.get().asFile val tweakedDataConnectConfigDir = tweakedDataConnectConfigDir.get().asFile logger.info("dataConnectConfigDir: {}", dataConnectConfigDir) logger.info("firebaseExecutable: {}", firebaseExecutable) + logger.info("nodeExecutable: {}", nodeExecutable) logger.info("outputDirectory: {}", outputDirectory) logger.info("tweakedDataConnectConfigDir: {}", tweakedDataConnectConfigDir) @@ -59,7 +64,8 @@ abstract class CodegenTask : DefaultTask() { tweakConnectorYamlFiles(tweakedDataConnectConfigDir, outputDirectory.absolutePath) runCommand(File(tweakedDataConnectConfigDir, "generate.log.txt")) { - commandLine(firebaseExecutable.absolutePath, "--debug", "dataconnect:sdk:generate") + val arg0 = if (nodeExecutable === null) "node" else nodeExecutable.absolutePath + commandLine(arg0, firebaseExecutable.absolutePath, "--debug", "dataconnect:sdk:generate") // Specify a fake project because dataconnect:sdk:generate unnecessarily // requires one. The actual value does not matter. args("--project", "zzyzx") @@ -70,6 +76,7 @@ abstract class CodegenTask : DefaultTask() { internal fun configureFrom(providers: MyVariantProviders) { dataConnectConfigDir.set(providers.dataConnectConfigDir) firebaseExecutable.set(providers.firebaseExecutable) + nodeExecutable.set(providers.projectProviders.nodeExecutable) tweakedDataConnectConfigDir.set(providers.buildDirectory.map { it.dir("config") }) } } diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectGradlePlugin.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectGradlePlugin.kt index b197307374..fb4b845aab 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectGradlePlugin.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectGradlePlugin.kt @@ -38,7 +38,7 @@ abstract class DataConnectGradlePlugin : Plugin { val androidComponents = project.extensions.getByType() androidComponents.onVariants { variant -> - val variantProviders = project.objects.newInstance(variant) + val variantProviders = project.objects.newInstance(variant, providers) registerVariantTasks(project, variant, variantProviders) } } diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/FirebaseToolsSetupTask.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/FirebaseToolsSetupTask.kt index 02fb84bac7..5d59d05917 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/FirebaseToolsSetupTask.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/FirebaseToolsSetupTask.kt @@ -18,15 +18,13 @@ package com.google.firebase.example.dataconnect.gradle import java.io.File import org.gradle.api.DefaultTask -import org.gradle.api.GradleException import org.gradle.api.file.DirectoryProperty import org.gradle.api.file.RegularFile +import org.gradle.api.file.RegularFileProperty import org.gradle.api.provider.Property import org.gradle.api.provider.Provider 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.OutputDirectory import org.gradle.api.tasks.TaskAction @@ -34,8 +32,8 @@ abstract class FirebaseToolsSetupTask : DefaultTask() { @get:Input abstract val version: Property - @get:InputFile @get:Optional - abstract val npmExecutable: Property + @get:Internal + abstract val npmExecutable: RegularFileProperty @get:OutputDirectory abstract val outputDirectory: DirectoryProperty @@ -46,7 +44,7 @@ abstract class FirebaseToolsSetupTask : DefaultTask() { @TaskAction fun run() { val version: String = version.get() - val npmExecutable: File? = npmExecutable.orNull + val npmExecutable: File? = npmExecutable.orNull?.asFile val outputDirectory: File = outputDirectory.get().asFile logger.info("version: {}", version) @@ -68,27 +66,7 @@ abstract class FirebaseToolsSetupTask : DefaultTask() { internal fun configureFrom(providers: MyProjectProviders) { version.set(providers.firebaseToolsVersion) + npmExecutable.set(providers.npmExecutable) outputDirectory.set(providers.buildDirectory.map { it.dir("firebase-tools") }) - - npmExecutable.set( - providers.localConfigs.map( - TransformerInterop { localConfigs -> - val result = localConfigs.filter { - it.npmExecutable !== null - }.map { Pair(it.srcFile, it.npmExecutable!!) }.firstOrNull() - result?.let { (configFile, npmExecutablePath) -> - File(npmExecutablePath).also { - if (!it.exists()) { - throw GradleException( - "npmExecutable specified in ${configFile?.absolutePath} " + - "does not exist: ${it.absolutePath} " + - "(error code eaw5gppkep)" - ) - } - } - } - } - ) - ) } } diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/LocalConfig.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/LocalConfig.kt index 6f330a4c26..bf82fff85f 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/LocalConfig.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/LocalConfig.kt @@ -16,19 +16,16 @@ package com.google.firebase.example.dataconnect.gradle -import java.io.File import kotlinx.serialization.Serializable -import kotlinx.serialization.Transient @Serializable internal data class LocalConfig( val npmExecutable: String? = null, - val nodeExecutable: String? = null, - @Transient val srcFile: File? = null + val nodeExecutable: String? = null ) : java.io.Serializable { companion object { @Suppress("ConstPropertyName") - private const val serialVersionUID = 6103369922496556758L + private const val serialVersionUID = 6103369929496556758L } } diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/Providers.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/Providers.kt index 0dc1a88dca..4dc44f4da4 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/Providers.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/Providers.kt @@ -17,6 +17,7 @@ package com.google.firebase.example.dataconnect.gradle import com.android.build.api.variant.ApplicationVariant +import java.io.File import java.io.FileNotFoundException import javax.inject.Inject import net.peanuuutz.tomlkt.Toml @@ -25,18 +26,21 @@ import org.gradle.api.GradleException import org.gradle.api.Project import org.gradle.api.file.Directory import org.gradle.api.file.DirectoryProperty +import org.gradle.api.file.ProjectLayout import org.gradle.api.file.RegularFile +import org.gradle.api.logging.Logger import org.gradle.api.model.ObjectFactory import org.gradle.api.provider.Provider import org.gradle.api.provider.ProviderFactory import org.gradle.kotlin.dsl.getByType -import org.gradle.kotlin.dsl.newInstance internal open class MyProjectProviders( + private val projectLayout: ProjectLayout, projectBuildDirectory: DirectoryProperty, - providerFactory: ProviderFactory, + private val providerFactory: ProviderFactory, projectDirectoryHierarchy: List, - ext: DataConnectExtension + ext: DataConnectExtension, + private val logger: Logger ) { @Suppress("unused") @@ -44,10 +48,12 @@ internal open class MyProjectProviders( constructor( project: Project ) : this( + projectLayout = project.layout, projectBuildDirectory = project.layout.buildDirectory, providerFactory = project.providers, projectDirectoryHierarchy = project.projectDirectoryHierarchy(), - ext = project.extensions.getByType() + ext = project.extensions.getByType(), + project.logger ) val buildDirectory: Provider = projectBuildDirectory.map { it.dir("dataconnect") } @@ -62,19 +68,20 @@ internal open class MyProjectProviders( ) } - val localConfigFiles: Provider> = providerFactory.provider { - projectDirectoryHierarchy.map { it.file("dataconnect.local.toml") } - } + private val localConfigs: Provider> = run { + val lazyResult: Lazy> = lazy { + val toml = Toml { this.ignoreUnknownKeys = true } - val localConfigs: Provider> = run { - val lazyResult = lazy(LazyThreadSafetyMode.PUBLICATION) { projectDirectoryHierarchy - .map { it.file("dataconnect.local.toml").asFile } - .mapNotNull { file -> + .map { it.file("dataconnect.local.toml") } + .map { regularFile -> + val file = regularFile.asFile + logger.info("Loading local config file: ${file.absolutePath}") val text = file.runCatching { readText() }.fold( onSuccess = { it }, onFailure = { exception -> if (exception is FileNotFoundException) { + logger.info("Ignoring non-existent local config file: ${file.absolutePath}") null // ignore non-existent config files } else { throw GradleException( @@ -85,30 +92,77 @@ internal open class MyProjectProviders( } } ) - if (text === null) null else Pair(file, text) - }.map { (file, text) -> - val toml = Toml { this.ignoreUnknownKeys = true } - toml.runCatching { - decodeFromString(text, "dataconnect").copy(srcFile = file) - }.fold( - onSuccess = { it }, - onFailure = { exception -> - throw GradleException( - "parsing toml file failed: ${file.absolutePath} ($exception)" + - " (error code 44dkc2vvpq)", - exception - ) - } - ) + Triple(text, file, regularFile) + }.map { (text, file, regularFile) -> + val localConfig = if (text === null) { + null + } else { + toml.runCatching { + decodeFromString(text, "dataconnect") + }.fold( + onSuccess = { + logger.info("Loaded local config file ${file.absolutePath}: $it") + it + }, + onFailure = { exception -> + throw GradleException( + "parsing toml file failed: ${file.absolutePath} ($exception)" + + " (error code 44dkc2vvpq)", + exception + ) + } + ) + } + LocalConfigInfo(localConfig, file, regularFile) } } providerFactory.provider { lazyResult.value } } + + val npmExecutable: Provider = provideFileFromLocalSettings("npmExecutable") { + it.npmExecutable + } + + val nodeExecutable: Provider = provideFileFromLocalSettings("nodeExecutable") { + it.nodeExecutable + } + + private fun provideFileFromLocalSettings(name: String, retriever: (LocalConfig) -> String?): Provider = + providerFactory.provider { + val localConfigsList = localConfigs.get() + val regularFile = localConfigsList.firstNotNullOfOrNull { localConfigInfo -> + val localConfig = localConfigInfo.localConfig ?: return@firstNotNullOfOrNull null + val value = retriever(localConfig) + if (value === null) { + null + } else { + val regularFile = projectLayout.projectDirectory.file(value) + val regularFileAbsolutePath = regularFile.asFile.absolutePath + logger.info( + "Found {} defined in {}: {} (which resolves to: {})", + name, + localConfigInfo.file.absolutePath, + value, + regularFileAbsolutePath + ) + regularFile + } + } + if (regularFile === null) { + logger.info( + "None of the {} found local settings files defined {}: {}", + localConfigsList.size, + name, + localConfigsList.joinToString(", ") { it.file.absolutePath } + ) + } + regularFile + } } internal open class MyVariantProviders( variant: ApplicationVariant, - myProjectProviders: MyProjectProviders, + val projectProviders: MyProjectProviders, ext: DataConnectExtension, firebaseToolsSetupTask: FirebaseToolsSetupTask, objectFactory: ObjectFactory @@ -118,17 +172,18 @@ internal open class MyVariantProviders( @Inject constructor( variant: ApplicationVariant, - project: Project + project: Project, + projectProviders: MyProjectProviders ) : this( variant = variant, - myProjectProviders = project.objects.newInstance(), + projectProviders = projectProviders, ext = project.extensions.getByType(), firebaseToolsSetupTask = project.firebaseToolsSetupTask, objectFactory = project.objects ) val buildDirectory: Provider = - myProjectProviders.buildDirectory.map { it.dir("variants/${variant.name}") } + projectProviders.buildDirectory.map { it.dir("variants/${variant.name}") } val dataConnectConfigDir: Provider = run { val dir = @@ -162,3 +217,9 @@ private fun Project.projectDirectoryHierarchy(): List = buildList { curProject = curProject.parent } } + +private data class LocalConfigInfo( + val localConfig: LocalConfig?, + val file: File, + val regularFile: RegularFile +) From fda7364b07428d381ca3ecfd135d3feb952e83f0 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Wed, 20 Nov 2024 04:28:49 +0000 Subject: [PATCH 11/79] rename dataconnect.local.toml -> dataconnect.local.toml.example --- ...ect.local.toml => dataconnect.local.toml.example} | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) rename dataconnect/{dataconnect.local.toml => dataconnect.local.toml.example} (68%) diff --git a/dataconnect/dataconnect.local.toml b/dataconnect/dataconnect.local.toml.example similarity index 68% rename from dataconnect/dataconnect.local.toml rename to dataconnect/dataconnect.local.toml.example index 01b53835e3..1a7dd2595b 100644 --- a/dataconnect/dataconnect.local.toml +++ b/dataconnect/dataconnect.local.toml.example @@ -1,3 +1,7 @@ +# This is an example dataconnect.local.toml file. +# Copy it to dataconnect.local.toml file in order for it to be picked up by the +# com.google.firebase.example.dataconnect.gradle custom plugin. + [dataconnect] # The path of the "npm" executable to use to install firebase-tools. @@ -6,8 +10,8 @@ # path of the npm executable to use works around that problem. Setting it to null # uses "npm" as found in the PATH. # -# NOTE: If this value is set then you almost certainly want to set nodeExecutable -# as well. +# NOTE: If this value is set to a value other than null then you almost certainly +# want to set nodeExecutable as well. # # eg. npmExecutable = "/home/myusername/local/nvm/versions/node/v20.13.1/bin/npm" npmExecutable = null @@ -18,8 +22,8 @@ npmExecutable = null # path of the node executable to use works around that problem. Setting it to null # uses "node" as found in the PATH. # -# NOTE: If this value is set then you almost certainly want to set npmExecutable -# as well. +# NOTE: If this value is set to a value other than null then you almost certainly +# want to set npmExecutable as well. # # eg. nodeExecutable = "/home/myusername/local/nvm/versions/node/v20.13.1/bin/node" nodeExecutable = null From f188cf1aa2a2b29be0d321095a5bf10977bdfae8 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Wed, 20 Nov 2024 06:05:51 +0000 Subject: [PATCH 12/79] fix calling npm when node is not in the path --- .../example/dataconnect/gradle/CodegenTask.kt | 15 +++++- .../gradle/FirebaseToolsSetupTask.kt | 44 ++++++++++++---- .../example/dataconnect/gradle/Providers.kt | 51 ++++++++++++++----- dataconnect/dataconnect.local.toml.example | 37 +++++++------- 4 files changed, 104 insertions(+), 43 deletions(-) diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/CodegenTask.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/CodegenTask.kt index 2b2685eb1c..1357d2e6f5 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/CodegenTask.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/CodegenTask.kt @@ -20,6 +20,7 @@ import java.io.File import org.gradle.api.DefaultTask import org.gradle.api.file.DirectoryProperty import org.gradle.api.file.RegularFileProperty +import org.gradle.api.provider.Property import org.gradle.api.tasks.InputDirectory import org.gradle.api.tasks.InputFile import org.gradle.api.tasks.Internal @@ -35,6 +36,9 @@ abstract class CodegenTask : DefaultTask() { @get:Internal abstract val nodeExecutable: RegularFileProperty + @get:Internal + abstract val pathEnvironmentVariable: Property + @get:OutputDirectory abstract val outputDirectory: DirectoryProperty @get:Internal abstract val tweakedDataConnectConfigDir: DirectoryProperty @@ -64,8 +68,15 @@ abstract class CodegenTask : DefaultTask() { tweakConnectorYamlFiles(tweakedDataConnectConfigDir, outputDirectory.absolutePath) runCommand(File(tweakedDataConnectConfigDir, "generate.log.txt")) { - val arg0 = if (nodeExecutable === null) "node" else nodeExecutable.absolutePath - commandLine(arg0, firebaseExecutable.absolutePath, "--debug", "dataconnect:sdk:generate") + if (nodeExecutable === null) { + commandLine("node") + } else { + val oldPath = pathEnvironmentVariable.orElse("") + val newPath = nodeExecutable.absoluteFile.parent + File.pathSeparator + oldPath + environment("PATH", newPath) + } + + commandLine(firebaseExecutable.absolutePath, "--debug", "dataconnect:sdk:generate") // Specify a fake project because dataconnect:sdk:generate unnecessarily // requires one. The actual value does not matter. args("--project", "zzyzx") diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/FirebaseToolsSetupTask.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/FirebaseToolsSetupTask.kt index 5d59d05917..96870f2051 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/FirebaseToolsSetupTask.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/FirebaseToolsSetupTask.kt @@ -30,25 +30,35 @@ import org.gradle.api.tasks.TaskAction abstract class FirebaseToolsSetupTask : DefaultTask() { - @get:Input abstract val version: Property + @get:Input + abstract val version: Property @get:Internal abstract val npmExecutable: RegularFileProperty - @get:OutputDirectory abstract val outputDirectory: DirectoryProperty + @get:Internal + abstract val nodeExecutable: RegularFileProperty + + @get:OutputDirectory + abstract val outputDirectory: DirectoryProperty @get:Internal val firebaseExecutable: Provider get() = outputDirectory.map { it.file("node_modules/.bin/firebase") } + @get:Internal + abstract val pathEnvironmentVariable: Property + @TaskAction fun run() { val version: String = version.get() val npmExecutable: File? = npmExecutable.orNull?.asFile + val nodeExecutable: File? = nodeExecutable.orNull?.asFile val outputDirectory: File = outputDirectory.get().asFile logger.info("version: {}", version) logger.info("npmExecutable: {}", npmExecutable?.absolutePath) + logger.info("nodeExecutable: {}", nodeExecutable?.absolutePath) logger.info("outputDirectory: {}", outputDirectory.absolutePath) project.delete(outputDirectory) @@ -58,15 +68,31 @@ abstract class FirebaseToolsSetupTask : DefaultTask() { packageJsonFile.writeText("{}", Charsets.UTF_8) runCommand(File(outputDirectory, "install.log.txt")) { - val arg0 = npmExecutable?.absolutePath ?: "npm" - commandLine(arg0, "install", "firebase-tools@$version") + if (nodeExecutable !== null) { + val oldPath = pathEnvironmentVariable.getOrElse("") + val newPath = nodeExecutable.absoluteFile.parent + File.pathSeparator + oldPath + environment("PATH", newPath) + if (npmExecutable !== null) { + commandLine(npmExecutable.absolutePath) + } else { + commandLine(File(nodeExecutable.absoluteFile.parentFile, "npm")) + } + } else if (npmExecutable !== null) { + commandLine(npmExecutable.absolutePath) + } else { + commandLine("npm") + } + + args("install", "firebase-tools@$version") workingDir(outputDirectory) } } +} - internal fun configureFrom(providers: MyProjectProviders) { - version.set(providers.firebaseToolsVersion) - npmExecutable.set(providers.npmExecutable) - outputDirectory.set(providers.buildDirectory.map { it.dir("firebase-tools") }) - } +internal fun FirebaseToolsSetupTask.configureFrom(providers: MyProjectProviders) { + version.set(providers.firebaseToolsVersion) + npmExecutable.set(providers.npmExecutable) + nodeExecutable.set(providers.nodeExecutable) + outputDirectory.set(providers.buildDirectory.map { it.dir("firebase-tools") }) + pathEnvironmentVariable.set(providers.pathEnvironmentVariable) } diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/Providers.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/Providers.kt index 4dc44f4da4..cdeaca9c64 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/Providers.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/Providers.kt @@ -56,6 +56,8 @@ internal open class MyProjectProviders( project.logger ) + val pathEnvironmentVariable: Provider = providerFactory.environmentVariable("PATH") + val buildDirectory: Provider = projectBuildDirectory.map { it.dir("dataconnect") } val firebaseToolsVersion: Provider = @@ -70,13 +72,19 @@ internal open class MyProjectProviders( private val localConfigs: Provider> = run { val lazyResult: Lazy> = lazy { + val localConfigFiles = projectDirectoryHierarchy.map { + val regularFile = it.file("dataconnect.local.toml") + Triple(it, regularFile, regularFile.asFile) + } + logger.info( + "Loading {} local config files: {}", + localConfigFiles.size, + localConfigFiles.joinToString(", ") { it.third.absolutePath } + ) val toml = Toml { this.ignoreUnknownKeys = true } - projectDirectoryHierarchy - .map { it.file("dataconnect.local.toml") } - .map { regularFile -> - val file = regularFile.asFile - logger.info("Loading local config file: ${file.absolutePath}") + localConfigFiles + .map { (directory: Directory, regularFile: RegularFile, file: File) -> val text = file.runCatching { readText() }.fold( onSuccess = { it }, onFailure = { exception -> @@ -85,15 +93,15 @@ internal open class MyProjectProviders( null // ignore non-existent config files } else { throw GradleException( - "reading file failed: ${file.absolutePath} ($exception)" + + "reading local config file failed: ${file.absolutePath} ($exception)" + " (error code bj7dxvvw5p)", exception ) } } ) - Triple(text, file, regularFile) - }.map { (text, file, regularFile) -> + LocalConfigTextInfo(text, file, directory, regularFile) + }.map { (text, file, directory, regularFile) -> val localConfig = if (text === null) { null } else { @@ -101,19 +109,19 @@ internal open class MyProjectProviders( decodeFromString(text, "dataconnect") }.fold( onSuccess = { - logger.info("Loaded local config file ${file.absolutePath}: $it") + logger.info("Loaded local config file {}: {}", file.absolutePath, it) it }, onFailure = { exception -> throw GradleException( - "parsing toml file failed: ${file.absolutePath} ($exception)" + + "parsing local config file failed: ${file.absolutePath} ($exception)" + " (error code 44dkc2vvpq)", exception ) } ) } - LocalConfigInfo(localConfig, file, regularFile) + LocalConfigInfo(localConfig, file, directory, regularFile) } } providerFactory.provider { lazyResult.value } @@ -136,15 +144,22 @@ internal open class MyProjectProviders( if (value === null) { null } else { - val regularFile = projectLayout.projectDirectory.file(value) - val regularFileAbsolutePath = regularFile.asFile.absolutePath + val regularFile = localConfigInfo.directory.file(value) + val file = regularFile.asFile logger.info( "Found {} defined in {}: {} (which resolves to: {})", name, localConfigInfo.file.absolutePath, value, - regularFileAbsolutePath + file.absolutePath ) + if (!file.exists()) { + throw GradleException( + "file not found: ${file.absolutePath} " + + "as specified for \"$name\" in ${localConfigInfo.file.absolutePath} " + + "(error code g3b59pdate)" + ) + } regularFile } } @@ -218,8 +233,16 @@ private fun Project.projectDirectoryHierarchy(): List = buildList { } } +private data class LocalConfigTextInfo( + val localConfigText: String?, + val file: File, + val directory: Directory, + val regularFile: RegularFile +) + private data class LocalConfigInfo( val localConfig: LocalConfig?, val file: File, + val directory: Directory, val regularFile: RegularFile ) diff --git a/dataconnect/dataconnect.local.toml.example b/dataconnect/dataconnect.local.toml.example index 1a7dd2595b..f3a7bb384c 100644 --- a/dataconnect/dataconnect.local.toml.example +++ b/dataconnect/dataconnect.local.toml.example @@ -4,26 +4,27 @@ [dataconnect] -# The path of the "npm" executable to use to install firebase-tools. -# Setting this is normally not necessary; however, if "npm" is not in the global -# PATH, or the wrong version is in the global PATH, then setting this to the absolute -# path of the npm executable to use works around that problem. Setting it to null -# uses "npm" as found in the PATH. +# The path of the "node" executable to use. # -# NOTE: If this value is set to a value other than null then you almost certainly -# want to set nodeExecutable as well. +# Setting this is normally not necessary; however, if "node" is not in the PATH +# environment variable, or the node version in the PATH is not the desired one, +# then setting this to the absolute path of the node executable to use works +# around that problem. A relative path is calculated relative to the directory +# containing this file. # -# eg. npmExecutable = "/home/myusername/local/nvm/versions/node/v20.13.1/bin/npm" -npmExecutable = null - -# The path of the "node" executable to use to run the "firebase" command. -# Setting this is normally not necessary; however, if "node" is not in the global -# PATH, or the wrong version is in the global PATH, then setting this to the absolute -# path of the node executable to use works around that problem. Setting it to null -# uses "node" as found in the PATH. -# -# NOTE: If this value is set to a value other than null then you almost certainly -# want to set npmExecutable as well. +# If not set or set to null, "node" is used. # # eg. nodeExecutable = "/home/myusername/local/nvm/versions/node/v20.13.1/bin/node" nodeExecutable = null + +# The path of the "npm" executable to use. +# +# Setting this is normally not necessary, as its value is usually inferred +# correctly. If not set, or set to null, then its value is inferred by first +# checking the value of `nodeExecutable`; if `nodeExecutable` is set to a non- +# null value, the the "npm" exectuable from the same directory is used. +# Otherwise, "npm" is used. A relative path is calculated relative to the +# directory containing this file. +# +# eg. npmExecutable = "/home/myusername/local/nvm/versions/node/v20.13.1/bin/npm" +npmExecutable = null From 1e9d05b0ce15702227a1bb5aa449c219788700ba Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Wed, 20 Nov 2024 06:15:25 +0000 Subject: [PATCH 13/79] minor plugin refactor --- .../dataconnect/gradle/DataConnectGradlePlugin.kt | 12 ++++++------ ...enTask.kt => GenerateDataConnectSourcesTask.kt} | 14 +++++++------- .../example/dataconnect/gradle/Providers.kt | 12 +++++------- ...ToolsSetupTask.kt => SetupFirebaseToolsTask.kt} | 4 ++-- 4 files changed, 20 insertions(+), 22 deletions(-) rename dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/{CodegenTask.kt => GenerateDataConnectSourcesTask.kt} (88%) rename dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/{FirebaseToolsSetupTask.kt => SetupFirebaseToolsTask.kt} (96%) diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectGradlePlugin.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectGradlePlugin.kt index fb4b845aab..844dbedfa2 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectGradlePlugin.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectGradlePlugin.kt @@ -32,7 +32,7 @@ abstract class DataConnectGradlePlugin : Plugin { project.extensions.create("dataconnect", DataConnectExtension::class.java) val providers = project.objects.newInstance() - project.tasks.register("setupFirebaseToolsForDataConnect") { + project.tasks.register("setupFirebaseToolsForDataConnect") { configureFrom(providers) } @@ -50,14 +50,14 @@ abstract class DataConnectGradlePlugin : Plugin { ) { val variantNameTitleCase = variant.name.replaceFirstChar { it.titlecase(Locale.US) } - val generateCodeTask = - project.tasks.register("generate${variantNameTitleCase}DataConnectSources") { - configureFrom(providers) - } + val generateCodeTaskName = "generate${variantNameTitleCase}DataConnectSources" + val generateCodeTask = project.tasks.register(generateCodeTaskName) { + configureFrom(providers) + } variant.sources.java!!.addGeneratedSourceDirectory( generateCodeTask, - CodegenTask::outputDirectory + GenerateDataConnectSourcesTask::outputDirectory ) } } diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/CodegenTask.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/GenerateDataConnectSourcesTask.kt similarity index 88% rename from dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/CodegenTask.kt rename to dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/GenerateDataConnectSourcesTask.kt index 1357d2e6f5..d9e06b5a7b 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/CodegenTask.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/GenerateDataConnectSourcesTask.kt @@ -27,7 +27,7 @@ import org.gradle.api.tasks.Internal import org.gradle.api.tasks.OutputDirectory import org.gradle.api.tasks.TaskAction -abstract class CodegenTask : DefaultTask() { +abstract class GenerateDataConnectSourcesTask : DefaultTask() { @get:InputDirectory abstract val dataConnectConfigDir: DirectoryProperty @@ -83,11 +83,11 @@ abstract class CodegenTask : DefaultTask() { workingDir(tweakedDataConnectConfigDir) } } +} - internal fun configureFrom(providers: MyVariantProviders) { - dataConnectConfigDir.set(providers.dataConnectConfigDir) - firebaseExecutable.set(providers.firebaseExecutable) - nodeExecutable.set(providers.projectProviders.nodeExecutable) - tweakedDataConnectConfigDir.set(providers.buildDirectory.map { it.dir("config") }) - } +internal fun GenerateDataConnectSourcesTask.configureFrom(providers: MyVariantProviders) { + dataConnectConfigDir.set(providers.dataConnectConfigDir) + firebaseExecutable.set(providers.firebaseExecutable) + nodeExecutable.set(providers.projectProviders.nodeExecutable) + tweakedDataConnectConfigDir.set(providers.buildDirectory.map { it.dir("config") }) } diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/Providers.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/Providers.kt index cdeaca9c64..ad546dbcdf 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/Providers.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/Providers.kt @@ -178,8 +178,8 @@ internal open class MyProjectProviders( internal open class MyVariantProviders( variant: ApplicationVariant, val projectProviders: MyProjectProviders, + val firebaseExecutable: Provider, ext: DataConnectExtension, - firebaseToolsSetupTask: FirebaseToolsSetupTask, objectFactory: ObjectFactory ) { @@ -192,8 +192,8 @@ internal open class MyVariantProviders( ) : this( variant = variant, projectProviders = projectProviders, + firebaseExecutable = project.firebaseToolsSetupTask.firebaseExecutable, ext = project.extensions.getByType(), - firebaseToolsSetupTask = project.firebaseToolsSetupTask, objectFactory = project.objects ) @@ -209,16 +209,14 @@ internal open class MyVariantProviders( ) objectFactory.directoryProperty().also { property -> property.set(dir) } } - - val firebaseExecutable: Provider = firebaseToolsSetupTask.firebaseExecutable } -private val Project.firebaseToolsSetupTask: FirebaseToolsSetupTask +private val Project.firebaseToolsSetupTask: SetupFirebaseToolsTask get() { - val tasks = tasks.filterIsInstance() + val tasks = tasks.filterIsInstance() if (tasks.size != 1) { throw GradleException( - "expected exactly 1 FirebaseToolsSetupTask task to be registered, but found " + + "expected exactly 1 SetupFirebaseToolsTask task to be registered, but found " + "${tasks.size}: [${tasks.map { it.name }.sorted().joinToString(", ")}]" ) } diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/FirebaseToolsSetupTask.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/SetupFirebaseToolsTask.kt similarity index 96% rename from dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/FirebaseToolsSetupTask.kt rename to dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/SetupFirebaseToolsTask.kt index 96870f2051..9d287eca44 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/FirebaseToolsSetupTask.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/SetupFirebaseToolsTask.kt @@ -28,7 +28,7 @@ import org.gradle.api.tasks.Internal import org.gradle.api.tasks.OutputDirectory import org.gradle.api.tasks.TaskAction -abstract class FirebaseToolsSetupTask : DefaultTask() { +abstract class SetupFirebaseToolsTask : DefaultTask() { @get:Input abstract val version: Property @@ -89,7 +89,7 @@ abstract class FirebaseToolsSetupTask : DefaultTask() { } } -internal fun FirebaseToolsSetupTask.configureFrom(providers: MyProjectProviders) { +internal fun SetupFirebaseToolsTask.configureFrom(providers: MyProjectProviders) { version.set(providers.firebaseToolsVersion) npmExecutable.set(providers.npmExecutable) nodeExecutable.set(providers.nodeExecutable) From c71a458d7cab27b72c86475d1fbfdc6f7556e241 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Wed, 20 Nov 2024 06:17:59 +0000 Subject: [PATCH 14/79] move tasks into their own package --- .../example/dataconnect/gradle/DataConnectGradlePlugin.kt | 3 +++ .../google/firebase/example/dataconnect/gradle/Providers.kt | 1 + .../gradle/{ => tasks}/GenerateDataConnectSourcesTask.kt | 5 ++++- .../dataconnect/gradle/{ => tasks}/SetupFirebaseToolsTask.kt | 4 +++- 4 files changed, 11 insertions(+), 2 deletions(-) rename dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/{ => tasks}/GenerateDataConnectSourcesTask.kt (92%) rename dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/{ => tasks}/SetupFirebaseToolsTask.kt (94%) diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectGradlePlugin.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectGradlePlugin.kt index 844dbedfa2..70f9a2a05f 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectGradlePlugin.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectGradlePlugin.kt @@ -18,6 +18,9 @@ package com.google.firebase.example.dataconnect.gradle import com.android.build.api.variant.ApplicationAndroidComponentsExtension import com.android.build.api.variant.ApplicationVariant +import com.google.firebase.example.dataconnect.gradle.tasks.GenerateDataConnectSourcesTask +import com.google.firebase.example.dataconnect.gradle.tasks.SetupFirebaseToolsTask +import com.google.firebase.example.dataconnect.gradle.tasks.configureFrom import java.util.Locale import org.gradle.api.Plugin import org.gradle.api.Project diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/Providers.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/Providers.kt index ad546dbcdf..9007b1437e 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/Providers.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/Providers.kt @@ -17,6 +17,7 @@ package com.google.firebase.example.dataconnect.gradle import com.android.build.api.variant.ApplicationVariant +import com.google.firebase.example.dataconnect.gradle.tasks.SetupFirebaseToolsTask import java.io.File import java.io.FileNotFoundException import javax.inject.Inject diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/GenerateDataConnectSourcesTask.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/GenerateDataConnectSourcesTask.kt similarity index 92% rename from dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/GenerateDataConnectSourcesTask.kt rename to dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/GenerateDataConnectSourcesTask.kt index d9e06b5a7b..93410cb854 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/GenerateDataConnectSourcesTask.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/GenerateDataConnectSourcesTask.kt @@ -14,8 +14,11 @@ * limitations under the License. */ -package com.google.firebase.example.dataconnect.gradle +package com.google.firebase.example.dataconnect.gradle.tasks +import com.google.firebase.example.dataconnect.gradle.MyVariantProviders +import com.google.firebase.example.dataconnect.gradle.runCommand +import com.google.firebase.example.dataconnect.gradle.tweakConnectorYamlFiles import java.io.File import org.gradle.api.DefaultTask import org.gradle.api.file.DirectoryProperty diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/SetupFirebaseToolsTask.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/SetupFirebaseToolsTask.kt similarity index 94% rename from dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/SetupFirebaseToolsTask.kt rename to dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/SetupFirebaseToolsTask.kt index 9d287eca44..18d6673718 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/SetupFirebaseToolsTask.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/SetupFirebaseToolsTask.kt @@ -14,8 +14,10 @@ * limitations under the License. */ -package com.google.firebase.example.dataconnect.gradle +package com.google.firebase.example.dataconnect.gradle.tasks +import com.google.firebase.example.dataconnect.gradle.MyProjectProviders +import com.google.firebase.example.dataconnect.gradle.runCommand import java.io.File import org.gradle.api.DefaultTask import org.gradle.api.file.DirectoryProperty From 957dfed72ac6bd95aed12c1e54197448dfdb128e Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Wed, 20 Nov 2024 06:37:12 +0000 Subject: [PATCH 15/79] more file reorganization --- .../gradle/ConnectorYamlTweaking.kt | 89 -------- .../gradle/DataConnectGradlePlugin.kt | 2 + .../example/dataconnect/gradle/LocalConfig.kt | 31 --- .../LocalConfigProviders.kt} | 197 +++++------------- .../dataconnect/gradle/providers/providers.kt | 132 ++++++++++++ .../tasks/GenerateDataConnectSourcesTask.kt | 77 ++++++- .../gradle/tasks/SetupFirebaseToolsTask.kt | 3 +- .../gradle/{Commands.kt => tasks/util.kt} | 4 +- 8 files changed, 265 insertions(+), 270 deletions(-) delete mode 100644 dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/ConnectorYamlTweaking.kt delete mode 100644 dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/LocalConfig.kt rename dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/{Providers.kt => providers/LocalConfigProviders.kt} (60%) create mode 100644 dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/providers/providers.kt rename dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/{Commands.kt => tasks/util.kt} (95%) diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/ConnectorYamlTweaking.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/ConnectorYamlTweaking.kt deleted file mode 100644 index da60a6b6a3..0000000000 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/ConnectorYamlTweaking.kt +++ /dev/null @@ -1,89 +0,0 @@ -/* - * 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.example.dataconnect.gradle - -import java.io.File -import org.gradle.api.GradleException -import org.gradle.api.Task -import org.yaml.snakeyaml.Yaml - -internal fun Task.tweakConnectorYamlFiles(dir: File, newOutputDir: String) { - logger.info("Tweaking connector.yaml files in {}", dir.absolutePath) - dir.walk().forEach { file -> - if (file.isFile && file.name == "connector.yaml") { - tweakConnectorYamlFile(file, newOutputDir) - } else { - logger.debug("skipping file: {}", file.absolutePath) - } - } -} - -internal fun Task.tweakConnectorYamlFile(file: File, newOutputDir: String) { - logger.info("Tweaking connector.yaml file: {}", file.absolutePath) - - fun Map<*, *>.withTweakedKotlinSdk() = - filterKeys { it == "kotlinSdk" } - .mapValues { (_, value) -> - val kotlinSdkMap = - value as? Map<*, *> - ?: throw GradleException( - "Parsing ${file.absolutePath} failed: \"kotlinSdk\" is " + - (if (value === null) "null" else value::class.qualifiedName) + - ", but expected ${Map::class.qualifiedName} " + - "(error code m697s27yxn)" - ) - kotlinSdkMap.mapValues { (key, value) -> - if (key == "outputDir") { - newOutputDir - } else { - value - } - } - } - - fun Map<*, *>.withTweakedGenerateNode() = mapValues { (key, value) -> - if (key != "generate") { - value - } else { - val generateMap = - value as? Map<*, *> - ?: throw GradleException( - "Parsing ${file.absolutePath} failed: \"generate\" is " + - (if (value === null) "null" else value::class.qualifiedName) + - ", but expected ${Map::class.qualifiedName} " + - "(error code 9c2p857gq6)" - ) - generateMap.withTweakedKotlinSdk() - } - } - - val yaml = Yaml() - val rootObject = file.reader(Charsets.UTF_8).use { reader -> yaml.load(reader) } - - val rootMap = - rootObject as? Map<*, *> - ?: throw GradleException( - "Parsing ${file.absolutePath} failed: root is " + - (if (rootObject === null) "null" else rootObject::class.qualifiedName) + - ", but expected ${Map::class.qualifiedName} " + - "(error code 45dw8jx8jd)" - ) - - val newRootMap = rootMap.withTweakedGenerateNode() - - file.writer(Charsets.UTF_8).use { writer -> yaml.dump(newRootMap, writer) } -} diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectGradlePlugin.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectGradlePlugin.kt index 70f9a2a05f..86e6c77d46 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectGradlePlugin.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectGradlePlugin.kt @@ -18,6 +18,8 @@ package com.google.firebase.example.dataconnect.gradle import com.android.build.api.variant.ApplicationAndroidComponentsExtension import com.android.build.api.variant.ApplicationVariant +import com.google.firebase.example.dataconnect.gradle.providers.MyProjectProviders +import com.google.firebase.example.dataconnect.gradle.providers.MyVariantProviders import com.google.firebase.example.dataconnect.gradle.tasks.GenerateDataConnectSourcesTask import com.google.firebase.example.dataconnect.gradle.tasks.SetupFirebaseToolsTask import com.google.firebase.example.dataconnect.gradle.tasks.configureFrom diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/LocalConfig.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/LocalConfig.kt deleted file mode 100644 index bf82fff85f..0000000000 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/LocalConfig.kt +++ /dev/null @@ -1,31 +0,0 @@ -/* - * 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.example.dataconnect.gradle - -import kotlinx.serialization.Serializable - -@Serializable -internal data class LocalConfig( - val npmExecutable: String? = null, - val nodeExecutable: String? = null -) : - java.io.Serializable { - companion object { - @Suppress("ConstPropertyName") - private const val serialVersionUID = 6103369929496556758L - } -} diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/Providers.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/providers/LocalConfigProviders.kt similarity index 60% rename from dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/Providers.kt rename to dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/providers/LocalConfigProviders.kt index 9007b1437e..f288242203 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/Providers.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/providers/LocalConfigProviders.kt @@ -14,61 +14,71 @@ * limitations under the License. */ -package com.google.firebase.example.dataconnect.gradle +package com.google.firebase.example.dataconnect.gradle.providers -import com.android.build.api.variant.ApplicationVariant -import com.google.firebase.example.dataconnect.gradle.tasks.SetupFirebaseToolsTask -import java.io.File -import java.io.FileNotFoundException -import javax.inject.Inject +import kotlinx.serialization.Serializable import net.peanuuutz.tomlkt.Toml import net.peanuuutz.tomlkt.decodeFromString import org.gradle.api.GradleException -import org.gradle.api.Project import org.gradle.api.file.Directory -import org.gradle.api.file.DirectoryProperty -import org.gradle.api.file.ProjectLayout import org.gradle.api.file.RegularFile import org.gradle.api.logging.Logger -import org.gradle.api.model.ObjectFactory import org.gradle.api.provider.Provider import org.gradle.api.provider.ProviderFactory -import org.gradle.kotlin.dsl.getByType +import java.io.File +import java.io.FileNotFoundException -internal open class MyProjectProviders( - private val projectLayout: ProjectLayout, - projectBuildDirectory: DirectoryProperty, - private val providerFactory: ProviderFactory, +internal class LocalConfigProviders( projectDirectoryHierarchy: List, - ext: DataConnectExtension, - private val logger: Logger + private val providerFactory: ProviderFactory, + private val logger: Logger, ) { - @Suppress("unused") - @Inject - constructor( - project: Project - ) : this( - projectLayout = project.layout, - projectBuildDirectory = project.layout.buildDirectory, - providerFactory = project.providers, - projectDirectoryHierarchy = project.projectDirectoryHierarchy(), - ext = project.extensions.getByType(), - project.logger - ) - - val pathEnvironmentVariable: Provider = providerFactory.environmentVariable("PATH") + val npmExecutable: Provider = provideFileFromLocalSettings("npmExecutable") { + it.npmExecutable + } - val buildDirectory: Provider = projectBuildDirectory.map { it.dir("dataconnect") } + val nodeExecutable: Provider = provideFileFromLocalSettings("nodeExecutable") { + it.nodeExecutable + } - val firebaseToolsVersion: Provider = + private fun provideFileFromLocalSettings(name: String, retriever: (LocalConfig) -> String?): Provider = providerFactory.provider { - ext.firebaseToolsVersion - ?: throw GradleException( - "dataconnect.firebaseToolsVersion must be set in your " + - "build.gradle or build.gradle.kts " + - "(error code xbmvkc3mtr)" + val localConfigsList = localConfigs.get() + val regularFile = localConfigsList.firstNotNullOfOrNull { localConfigInfo -> + val localConfig = localConfigInfo.localConfig ?: return@firstNotNullOfOrNull null + val value = retriever(localConfig) + if (value === null) { + null + } else { + val regularFile = localConfigInfo.directory.file(value) + val file = regularFile.asFile + logger.info( + "Found {} defined in {}: {} (which resolves to: {})", + name, + localConfigInfo.file.absolutePath, + value, + file.absolutePath + ) + if (!file.exists()) { + throw GradleException( + "file not found: ${file.absolutePath} " + + "as specified for \"$name\" in ${localConfigInfo.file.absolutePath} " + + "(error code g3b59pdate)" + ) + } + regularFile + } + } + if (regularFile === null) { + logger.info( + "None of the {} found local settings files defined {}: {}", + localConfigsList.size, + name, + localConfigsList.joinToString(", ") { it.file.absolutePath } ) + } + regularFile } private val localConfigs: Provider> = run { @@ -95,7 +105,7 @@ internal open class MyProjectProviders( } else { throw GradleException( "reading local config file failed: ${file.absolutePath} ($exception)" + - " (error code bj7dxvvw5p)", + " (error code bj7dxvvw5p)", exception ) } @@ -116,7 +126,7 @@ internal open class MyProjectProviders( onFailure = { exception -> throw GradleException( "parsing local config file failed: ${file.absolutePath} ($exception)" + - " (error code 44dkc2vvpq)", + " (error code 44dkc2vvpq)", exception ) } @@ -128,110 +138,13 @@ internal open class MyProjectProviders( providerFactory.provider { lazyResult.value } } - val npmExecutable: Provider = provideFileFromLocalSettings("npmExecutable") { - it.npmExecutable - } - - val nodeExecutable: Provider = provideFileFromLocalSettings("nodeExecutable") { - it.nodeExecutable - } - - private fun provideFileFromLocalSettings(name: String, retriever: (LocalConfig) -> String?): Provider = - providerFactory.provider { - val localConfigsList = localConfigs.get() - val regularFile = localConfigsList.firstNotNullOfOrNull { localConfigInfo -> - val localConfig = localConfigInfo.localConfig ?: return@firstNotNullOfOrNull null - val value = retriever(localConfig) - if (value === null) { - null - } else { - val regularFile = localConfigInfo.directory.file(value) - val file = regularFile.asFile - logger.info( - "Found {} defined in {}: {} (which resolves to: {})", - name, - localConfigInfo.file.absolutePath, - value, - file.absolutePath - ) - if (!file.exists()) { - throw GradleException( - "file not found: ${file.absolutePath} " + - "as specified for \"$name\" in ${localConfigInfo.file.absolutePath} " + - "(error code g3b59pdate)" - ) - } - regularFile - } - } - if (regularFile === null) { - logger.info( - "None of the {} found local settings files defined {}: {}", - localConfigsList.size, - name, - localConfigsList.joinToString(", ") { it.file.absolutePath } - ) - } - regularFile - } -} - -internal open class MyVariantProviders( - variant: ApplicationVariant, - val projectProviders: MyProjectProviders, - val firebaseExecutable: Provider, - ext: DataConnectExtension, - objectFactory: ObjectFactory -) { - - @Suppress("unused") - @Inject - constructor( - variant: ApplicationVariant, - project: Project, - projectProviders: MyProjectProviders - ) : this( - variant = variant, - projectProviders = projectProviders, - firebaseExecutable = project.firebaseToolsSetupTask.firebaseExecutable, - ext = project.extensions.getByType(), - objectFactory = project.objects - ) - - val buildDirectory: Provider = - projectProviders.buildDirectory.map { it.dir("variants/${variant.name}") } - - val dataConnectConfigDir: Provider = run { - val dir = - ext.dataConnectConfigDir - ?: throw GradleException( - "dataconnect.dataConnectConfigDir must be set in your build.gradle or build.gradle.kts " + - "(error code xbmvkc3mtr)" - ) - objectFactory.directoryProperty().also { property -> property.set(dir) } - } -} - -private val Project.firebaseToolsSetupTask: SetupFirebaseToolsTask - get() { - val tasks = tasks.filterIsInstance() - if (tasks.size != 1) { - throw GradleException( - "expected exactly 1 SetupFirebaseToolsTask task to be registered, but found " + - "${tasks.size}: [${tasks.map { it.name }.sorted().joinToString(", ")}]" - ) - } - return tasks.single() - } - -private fun Project.projectDirectoryHierarchy(): List = buildList { - var curProject: Project? = this@projectDirectoryHierarchy - while (curProject !== null) { - add(curProject.layout.projectDirectory) - curProject = curProject.parent - } } +@Serializable +private data class LocalConfig( + val npmExecutable: String? = null, + val nodeExecutable: String? = null +) private data class LocalConfigTextInfo( val localConfigText: String?, val file: File, diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/providers/providers.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/providers/providers.kt new file mode 100644 index 0000000000..3b01698f52 --- /dev/null +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/providers/providers.kt @@ -0,0 +1,132 @@ +/* + * 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.example.dataconnect.gradle.providers + +import com.android.build.api.variant.ApplicationVariant +import com.google.firebase.example.dataconnect.gradle.DataConnectExtension +import com.google.firebase.example.dataconnect.gradle.tasks.SetupFirebaseToolsTask +import net.peanuuutz.tomlkt.Toml +import org.gradle.api.GradleException +import org.gradle.api.Project +import org.gradle.api.file.Directory +import org.gradle.api.file.DirectoryProperty +import org.gradle.api.file.RegularFile +import org.gradle.api.logging.Logger +import org.gradle.api.model.ObjectFactory +import org.gradle.api.provider.Provider +import org.gradle.api.provider.ProviderFactory +import org.gradle.kotlin.dsl.getByType +import java.io.File +import java.io.FileNotFoundException +import javax.inject.Inject + +internal open class MyProjectProviders( + projectBuildDirectory: DirectoryProperty, + private val providerFactory: ProviderFactory, + projectDirectoryHierarchy: List, + ext: DataConnectExtension, + private val logger: Logger +) { + + @Suppress("unused") + @Inject + constructor( + project: Project + ) : this( + projectBuildDirectory = project.layout.buildDirectory, + providerFactory = project.providers, + projectDirectoryHierarchy = project.projectDirectoryHierarchy(), + ext = project.extensions.getByType(), + project.logger + ) + + val pathEnvironmentVariable: Provider = providerFactory.environmentVariable("PATH") + + val buildDirectory: Provider = projectBuildDirectory.map { it.dir("dataconnect") } + + val firebaseToolsVersion: Provider = + providerFactory.provider { + ext.firebaseToolsVersion + ?: throw GradleException( + "dataconnect.firebaseToolsVersion must be set in your " + + "build.gradle or build.gradle.kts " + + "(error code xbmvkc3mtr)" + ) + } + + private val localConfigProviders = LocalConfigProviders(projectDirectoryHierarchy, providerFactory, logger) + + val npmExecutable: Provider by localConfigProviders::npmExecutable + + val nodeExecutable: Provider by localConfigProviders::nodeExecutable +} + +internal open class MyVariantProviders( + variant: ApplicationVariant, + val projectProviders: MyProjectProviders, + val firebaseExecutable: Provider, + ext: DataConnectExtension, + objectFactory: ObjectFactory +) { + + @Suppress("unused") + @Inject + constructor( + variant: ApplicationVariant, + project: Project, + projectProviders: MyProjectProviders + ) : this( + variant = variant, + projectProviders = projectProviders, + firebaseExecutable = project.firebaseToolsSetupTask.firebaseExecutable, + ext = project.extensions.getByType(), + objectFactory = project.objects + ) + + val buildDirectory: Provider = + projectProviders.buildDirectory.map { it.dir("variants/${variant.name}") } + + val dataConnectConfigDir: Provider = run { + val dir = + ext.dataConnectConfigDir + ?: throw GradleException( + "dataconnect.dataConnectConfigDir must be set in your build.gradle or build.gradle.kts " + + "(error code xbmvkc3mtr)" + ) + objectFactory.directoryProperty().also { property -> property.set(dir) } + } +} + +private val Project.firebaseToolsSetupTask: SetupFirebaseToolsTask + get() { + val tasks = tasks.filterIsInstance() + if (tasks.size != 1) { + throw GradleException( + "expected exactly 1 SetupFirebaseToolsTask task to be registered, but found " + + "${tasks.size}: [${tasks.map { it.name }.sorted().joinToString(", ")}]" + ) + } + return tasks.single() + } + +private fun Project.projectDirectoryHierarchy(): List = buildList { + var curProject: Project? = this@projectDirectoryHierarchy + while (curProject !== null) { + add(curProject.layout.projectDirectory) + curProject = curProject.parent + } +} diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/GenerateDataConnectSourcesTask.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/GenerateDataConnectSourcesTask.kt index 93410cb854..b9345bf885 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/GenerateDataConnectSourcesTask.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/GenerateDataConnectSourcesTask.kt @@ -16,19 +16,21 @@ package com.google.firebase.example.dataconnect.gradle.tasks -import com.google.firebase.example.dataconnect.gradle.MyVariantProviders -import com.google.firebase.example.dataconnect.gradle.runCommand -import com.google.firebase.example.dataconnect.gradle.tweakConnectorYamlFiles + +import com.google.firebase.example.dataconnect.gradle.providers.MyVariantProviders import java.io.File import org.gradle.api.DefaultTask +import org.gradle.api.GradleException import org.gradle.api.file.DirectoryProperty import org.gradle.api.file.RegularFileProperty +import org.gradle.api.logging.Logger import org.gradle.api.provider.Property import org.gradle.api.tasks.InputDirectory import org.gradle.api.tasks.InputFile import org.gradle.api.tasks.Internal import org.gradle.api.tasks.OutputDirectory import org.gradle.api.tasks.TaskAction +import org.yaml.snakeyaml.Yaml abstract class GenerateDataConnectSourcesTask : DefaultTask() { @@ -68,7 +70,7 @@ abstract class GenerateDataConnectSourcesTask : DefaultTask() { from(dataConnectConfigDir) into(tweakedDataConnectConfigDir) } - tweakConnectorYamlFiles(tweakedDataConnectConfigDir, outputDirectory.absolutePath) + tweakConnectorYamlFiles(tweakedDataConnectConfigDir, outputDirectory.absolutePath, logger) runCommand(File(tweakedDataConnectConfigDir, "generate.log.txt")) { if (nodeExecutable === null) { @@ -94,3 +96,70 @@ internal fun GenerateDataConnectSourcesTask.configureFrom(providers: MyVariantPr nodeExecutable.set(providers.projectProviders.nodeExecutable) tweakedDataConnectConfigDir.set(providers.buildDirectory.map { it.dir("config") }) } + +private fun tweakConnectorYamlFiles(dir: File, newOutputDir: String, logger: Logger) { + logger.info("Tweaking connector.yaml files in {}", dir.absolutePath) + dir.walk().forEach { file -> + if (file.isFile && file.name == "connector.yaml") { + tweakConnectorYamlFile(file, newOutputDir, logger) + } else { + logger.debug("skipping file: {}", file.absolutePath) + } + } +} + +private fun tweakConnectorYamlFile(file: File, newOutputDir: String, logger: Logger) { + logger.info("Tweaking connector.yaml file: {}", file.absolutePath) + + fun Map<*, *>.withTweakedKotlinSdk() = + filterKeys { it == "kotlinSdk" } + .mapValues { (_, value) -> + val kotlinSdkMap = + value as? Map<*, *> + ?: throw GradleException( + "Parsing ${file.absolutePath} failed: \"kotlinSdk\" is " + + (if (value === null) "null" else value::class.qualifiedName) + + ", but expected ${Map::class.qualifiedName} " + + "(error code m697s27yxn)" + ) + kotlinSdkMap.mapValues { (key, value) -> + if (key == "outputDir") { + newOutputDir + } else { + value + } + } + } + + fun Map<*, *>.withTweakedGenerateNode() = mapValues { (key, value) -> + if (key != "generate") { + value + } else { + val generateMap = + value as? Map<*, *> + ?: throw GradleException( + "Parsing ${file.absolutePath} failed: \"generate\" is " + + (if (value === null) "null" else value::class.qualifiedName) + + ", but expected ${Map::class.qualifiedName} " + + "(error code 9c2p857gq6)" + ) + generateMap.withTweakedKotlinSdk() + } + } + + val yaml = Yaml() + val rootObject = file.reader(Charsets.UTF_8).use { reader -> yaml.load(reader) } + + val rootMap = + rootObject as? Map<*, *> + ?: throw GradleException( + "Parsing ${file.absolutePath} failed: root is " + + (if (rootObject === null) "null" else rootObject::class.qualifiedName) + + ", but expected ${Map::class.qualifiedName} " + + "(error code 45dw8jx8jd)" + ) + + val newRootMap = rootMap.withTweakedGenerateNode() + + file.writer(Charsets.UTF_8).use { writer -> yaml.dump(newRootMap, writer) } +} diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/SetupFirebaseToolsTask.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/SetupFirebaseToolsTask.kt index 18d6673718..6c89d639a7 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/SetupFirebaseToolsTask.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/SetupFirebaseToolsTask.kt @@ -16,8 +16,7 @@ package com.google.firebase.example.dataconnect.gradle.tasks -import com.google.firebase.example.dataconnect.gradle.MyProjectProviders -import com.google.firebase.example.dataconnect.gradle.runCommand +import com.google.firebase.example.dataconnect.gradle.providers.MyProjectProviders import java.io.File import org.gradle.api.DefaultTask import org.gradle.api.file.DirectoryProperty diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/Commands.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/util.kt similarity index 95% rename from dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/Commands.kt rename to dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/util.kt index d84ec4b248..4660bc1f5e 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/Commands.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/util.kt @@ -14,11 +14,11 @@ * limitations under the License. */ -package com.google.firebase.example.dataconnect.gradle +package com.google.firebase.example.dataconnect.gradle.tasks -import java.io.File import org.gradle.api.Task import org.gradle.process.ExecSpec +import java.io.File internal fun Task.runCommand(logFile: File, configure: ExecSpec.() -> Unit) { val effectiveLogFile = if (logger.isInfoEnabled) null else logFile From 62840114f04e75aa9df362f264e0e8c783e34131 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Wed, 20 Nov 2024 06:49:40 +0000 Subject: [PATCH 16/79] format code with ktlint --- dataconnect/.editorconfig | 5 +++++ .../gradle/providers/LocalConfigProviders.kt | 15 +++++++-------- .../dataconnect/gradle/providers/providers.kt | 5 +---- .../tasks/GenerateDataConnectSourcesTask.kt | 19 +++++++++---------- .../example/dataconnect/gradle/tasks/util.kt | 2 +- 5 files changed, 23 insertions(+), 23 deletions(-) create mode 100644 dataconnect/.editorconfig diff --git a/dataconnect/.editorconfig b/dataconnect/.editorconfig new file mode 100644 index 0000000000..a70ea32ef2 --- /dev/null +++ b/dataconnect/.editorconfig @@ -0,0 +1,5 @@ +[buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/providers/providers.kt] +ktlint_standard_filename = disabled + +[buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/util.kt] +ktlint_standard_filename = disabled diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/providers/LocalConfigProviders.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/providers/LocalConfigProviders.kt index f288242203..bdfca4c2a1 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/providers/LocalConfigProviders.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/providers/LocalConfigProviders.kt @@ -16,6 +16,8 @@ package com.google.firebase.example.dataconnect.gradle.providers +import java.io.File +import java.io.FileNotFoundException import kotlinx.serialization.Serializable import net.peanuuutz.tomlkt.Toml import net.peanuuutz.tomlkt.decodeFromString @@ -25,13 +27,11 @@ import org.gradle.api.file.RegularFile import org.gradle.api.logging.Logger import org.gradle.api.provider.Provider import org.gradle.api.provider.ProviderFactory -import java.io.File -import java.io.FileNotFoundException internal class LocalConfigProviders( projectDirectoryHierarchy: List, private val providerFactory: ProviderFactory, - private val logger: Logger, + private val logger: Logger ) { val npmExecutable: Provider = provideFileFromLocalSettings("npmExecutable") { @@ -63,8 +63,8 @@ internal class LocalConfigProviders( if (!file.exists()) { throw GradleException( "file not found: ${file.absolutePath} " + - "as specified for \"$name\" in ${localConfigInfo.file.absolutePath} " + - "(error code g3b59pdate)" + "as specified for \"$name\" in ${localConfigInfo.file.absolutePath} " + + "(error code g3b59pdate)" ) } regularFile @@ -105,7 +105,7 @@ internal class LocalConfigProviders( } else { throw GradleException( "reading local config file failed: ${file.absolutePath} ($exception)" + - " (error code bj7dxvvw5p)", + " (error code bj7dxvvw5p)", exception ) } @@ -126,7 +126,7 @@ internal class LocalConfigProviders( onFailure = { exception -> throw GradleException( "parsing local config file failed: ${file.absolutePath} ($exception)" + - " (error code 44dkc2vvpq)", + " (error code 44dkc2vvpq)", exception ) } @@ -137,7 +137,6 @@ internal class LocalConfigProviders( } providerFactory.provider { lazyResult.value } } - } @Serializable diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/providers/providers.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/providers/providers.kt index 3b01698f52..6326c759f7 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/providers/providers.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/providers/providers.kt @@ -19,7 +19,7 @@ package com.google.firebase.example.dataconnect.gradle.providers import com.android.build.api.variant.ApplicationVariant import com.google.firebase.example.dataconnect.gradle.DataConnectExtension import com.google.firebase.example.dataconnect.gradle.tasks.SetupFirebaseToolsTask -import net.peanuuutz.tomlkt.Toml +import javax.inject.Inject import org.gradle.api.GradleException import org.gradle.api.Project import org.gradle.api.file.Directory @@ -30,9 +30,6 @@ import org.gradle.api.model.ObjectFactory import org.gradle.api.provider.Provider import org.gradle.api.provider.ProviderFactory import org.gradle.kotlin.dsl.getByType -import java.io.File -import java.io.FileNotFoundException -import javax.inject.Inject internal open class MyProjectProviders( projectBuildDirectory: DirectoryProperty, diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/GenerateDataConnectSourcesTask.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/GenerateDataConnectSourcesTask.kt index b9345bf885..4d2d64c5d9 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/GenerateDataConnectSourcesTask.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/GenerateDataConnectSourcesTask.kt @@ -16,7 +16,6 @@ package com.google.firebase.example.dataconnect.gradle.tasks - import com.google.firebase.example.dataconnect.gradle.providers.MyVariantProviders import java.io.File import org.gradle.api.DefaultTask @@ -118,9 +117,9 @@ private fun tweakConnectorYamlFile(file: File, newOutputDir: String, logger: Log value as? Map<*, *> ?: throw GradleException( "Parsing ${file.absolutePath} failed: \"kotlinSdk\" is " + - (if (value === null) "null" else value::class.qualifiedName) + - ", but expected ${Map::class.qualifiedName} " + - "(error code m697s27yxn)" + (if (value === null) "null" else value::class.qualifiedName) + + ", but expected ${Map::class.qualifiedName} " + + "(error code m697s27yxn)" ) kotlinSdkMap.mapValues { (key, value) -> if (key == "outputDir") { @@ -139,9 +138,9 @@ private fun tweakConnectorYamlFile(file: File, newOutputDir: String, logger: Log value as? Map<*, *> ?: throw GradleException( "Parsing ${file.absolutePath} failed: \"generate\" is " + - (if (value === null) "null" else value::class.qualifiedName) + - ", but expected ${Map::class.qualifiedName} " + - "(error code 9c2p857gq6)" + (if (value === null) "null" else value::class.qualifiedName) + + ", but expected ${Map::class.qualifiedName} " + + "(error code 9c2p857gq6)" ) generateMap.withTweakedKotlinSdk() } @@ -154,9 +153,9 @@ private fun tweakConnectorYamlFile(file: File, newOutputDir: String, logger: Log rootObject as? Map<*, *> ?: throw GradleException( "Parsing ${file.absolutePath} failed: root is " + - (if (rootObject === null) "null" else rootObject::class.qualifiedName) + - ", but expected ${Map::class.qualifiedName} " + - "(error code 45dw8jx8jd)" + (if (rootObject === null) "null" else rootObject::class.qualifiedName) + + ", but expected ${Map::class.qualifiedName} " + + "(error code 45dw8jx8jd)" ) val newRootMap = rootMap.withTweakedGenerateNode() diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/util.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/util.kt index 4660bc1f5e..66426f8198 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/util.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/util.kt @@ -16,9 +16,9 @@ package com.google.firebase.example.dataconnect.gradle.tasks +import java.io.File import org.gradle.api.Task import org.gradle.process.ExecSpec -import java.io.File internal fun Task.runCommand(logFile: File, configure: ExecSpec.() -> Unit) { val effectiveLogFile = if (logger.isInfoEnabled) null else logFile From b65499cea47a4eeb4f2f3f18ff838f9a8e7f95cd Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Wed, 20 Nov 2024 09:11:50 +0000 Subject: [PATCH 17/79] REVERT ME: detect system info --- dataconnect/buildSrc/build.gradle.kts | 2 ++ .../gradle/DataConnectGradlePlugin.kt | 29 +++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/dataconnect/buildSrc/build.gradle.kts b/dataconnect/buildSrc/build.gradle.kts index c90e5a9018..26e8cbdde8 100644 --- a/dataconnect/buildSrc/build.gradle.kts +++ b/dataconnect/buildSrc/build.gradle.kts @@ -33,6 +33,8 @@ dependencies { // depend on a newer version of the `kotlinx.serialization` plugin, which // requires a newer version of Kotlin. implementation("net.peanuuutz.tomlkt:tomlkt:0.3.7") + + implementation("com.github.oshi:oshi-core:6.6.5") } gradlePlugin { diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectGradlePlugin.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectGradlePlugin.kt index 86e6c77d46..676bb304e5 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectGradlePlugin.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectGradlePlugin.kt @@ -29,11 +29,40 @@ import org.gradle.api.Project import org.gradle.kotlin.dsl.getByType import org.gradle.kotlin.dsl.newInstance import org.gradle.kotlin.dsl.register +import oshi.SystemInfo @Suppress("unused") abstract class DataConnectGradlePlugin : Plugin { override fun apply(project: Project) { + val si = SystemInfo() + si.operatingSystem.let { + println("family: ${it.family}") + println("bitness: ${it.bitness}") + println("manufacturer: ${it.manufacturer}") + println("versionInfo.version: ${it.versionInfo.version}") + println("versionInfo.codeName: ${it.versionInfo.codeName}") + println("versionInfo.buildNumber: ${it.versionInfo.buildNumber}") + } + si.hardware.let { + println("computerSystem.model: ${it.computerSystem.model}") + println("computerSystem.manufacturer: ${it.computerSystem.manufacturer}") + println("computerSystem.firmware: ${it.computerSystem.firmware}") + println("computerSystem.baseboard: ${it.computerSystem.baseboard}") + println("computerSystem.hardwareUUID: ${it.computerSystem.hardwareUUID}") + println("computerSystem.serialNumber: ${it.computerSystem.serialNumber}") + println("processorIdentifier.family: ${it.processor.processorIdentifier.family}") + println("processorIdentifier.name: ${it.processor.processorIdentifier.name}") + println("processorIdentifier.model: ${it.processor.processorIdentifier.model}") + println("processorIdentifier.identifier: ${it.processor.processorIdentifier.identifier}") + println("processorIdentifier.processorID: ${it.processor.processorIdentifier.processorID}") + println("processorIdentifier.isCpu64bit: ${it.processor.processorIdentifier.isCpu64bit}") + println("processorIdentifier.microarchitecture: ${it.processor.processorIdentifier.microarchitecture}") + println("processorIdentifier.stepping: ${it.processor.processorIdentifier.stepping}") + println("processorIdentifier.vendor: ${it.processor.processorIdentifier.vendor}") + println("processorIdentifier.vendorFreq: ${it.processor.processorIdentifier.vendorFreq}") + } + project.extensions.create("dataconnect", DataConnectExtension::class.java) val providers = project.objects.newInstance() From 427e5d7d1f9c94885a37d0310df5fe9d861644cb Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Wed, 20 Nov 2024 18:27:59 +0000 Subject: [PATCH 18/79] OperatingSystemProvider.kt added --- dataconnect/buildSrc/build.gradle.kts | 2 - .../gradle/DataConnectGradlePlugin.kt | 34 +---- .../providers/OperatingSystemProvider.kt | 138 ++++++++++++++++++ .../dataconnect/gradle/providers/providers.kt | 8 +- .../gradle/tasks/DownloadNodeJSTask.kt | 88 +++++++++++ .../example/dataconnect/gradle/tasks/util.kt | 2 +- 6 files changed, 238 insertions(+), 34 deletions(-) create mode 100644 dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/providers/OperatingSystemProvider.kt create mode 100644 dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJSTask.kt diff --git a/dataconnect/buildSrc/build.gradle.kts b/dataconnect/buildSrc/build.gradle.kts index 26e8cbdde8..c90e5a9018 100644 --- a/dataconnect/buildSrc/build.gradle.kts +++ b/dataconnect/buildSrc/build.gradle.kts @@ -33,8 +33,6 @@ dependencies { // depend on a newer version of the `kotlinx.serialization` plugin, which // requires a newer version of Kotlin. implementation("net.peanuuutz.tomlkt:tomlkt:0.3.7") - - implementation("com.github.oshi:oshi-core:6.6.5") } gradlePlugin { diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectGradlePlugin.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectGradlePlugin.kt index 676bb304e5..261333f66d 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectGradlePlugin.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectGradlePlugin.kt @@ -20,6 +20,7 @@ import com.android.build.api.variant.ApplicationAndroidComponentsExtension import com.android.build.api.variant.ApplicationVariant import com.google.firebase.example.dataconnect.gradle.providers.MyProjectProviders import com.google.firebase.example.dataconnect.gradle.providers.MyVariantProviders +import com.google.firebase.example.dataconnect.gradle.tasks.DownloadNodeJSTask import com.google.firebase.example.dataconnect.gradle.tasks.GenerateDataConnectSourcesTask import com.google.firebase.example.dataconnect.gradle.tasks.SetupFirebaseToolsTask import com.google.firebase.example.dataconnect.gradle.tasks.configureFrom @@ -29,43 +30,18 @@ import org.gradle.api.Project import org.gradle.kotlin.dsl.getByType import org.gradle.kotlin.dsl.newInstance import org.gradle.kotlin.dsl.register -import oshi.SystemInfo @Suppress("unused") abstract class DataConnectGradlePlugin : Plugin { override fun apply(project: Project) { - val si = SystemInfo() - si.operatingSystem.let { - println("family: ${it.family}") - println("bitness: ${it.bitness}") - println("manufacturer: ${it.manufacturer}") - println("versionInfo.version: ${it.versionInfo.version}") - println("versionInfo.codeName: ${it.versionInfo.codeName}") - println("versionInfo.buildNumber: ${it.versionInfo.buildNumber}") - } - si.hardware.let { - println("computerSystem.model: ${it.computerSystem.model}") - println("computerSystem.manufacturer: ${it.computerSystem.manufacturer}") - println("computerSystem.firmware: ${it.computerSystem.firmware}") - println("computerSystem.baseboard: ${it.computerSystem.baseboard}") - println("computerSystem.hardwareUUID: ${it.computerSystem.hardwareUUID}") - println("computerSystem.serialNumber: ${it.computerSystem.serialNumber}") - println("processorIdentifier.family: ${it.processor.processorIdentifier.family}") - println("processorIdentifier.name: ${it.processor.processorIdentifier.name}") - println("processorIdentifier.model: ${it.processor.processorIdentifier.model}") - println("processorIdentifier.identifier: ${it.processor.processorIdentifier.identifier}") - println("processorIdentifier.processorID: ${it.processor.processorIdentifier.processorID}") - println("processorIdentifier.isCpu64bit: ${it.processor.processorIdentifier.isCpu64bit}") - println("processorIdentifier.microarchitecture: ${it.processor.processorIdentifier.microarchitecture}") - println("processorIdentifier.stepping: ${it.processor.processorIdentifier.stepping}") - println("processorIdentifier.vendor: ${it.processor.processorIdentifier.vendor}") - println("processorIdentifier.vendorFreq: ${it.processor.processorIdentifier.vendorFreq}") - } - project.extensions.create("dataconnect", DataConnectExtension::class.java) val providers = project.objects.newInstance() + project.tasks.register("downloadNodeJS") { + configureFrom(providers) + } + project.tasks.register("setupFirebaseToolsForDataConnect") { configureFrom(providers) } diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/providers/OperatingSystemProvider.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/providers/OperatingSystemProvider.kt new file mode 100644 index 0000000000..b8adf3764b --- /dev/null +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/providers/OperatingSystemProvider.kt @@ -0,0 +1,138 @@ +/* + * 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.example.dataconnect.gradle.providers + +import org.gradle.api.GradleException +import org.gradle.api.logging.Logger +import org.gradle.api.model.ObjectFactory +import org.gradle.api.provider.Provider +import org.gradle.api.provider.ProviderFactory +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.Internal +import org.gradle.kotlin.dsl.property + +data class OperatingSystem( + @get:Input val type: Type, + @get:Input val arch: Architecture, + @get:Internal val description: String?, +) : java.io.Serializable { + + enum class Type { + Windows, + Linux, + MacOS, + FreeBSD, + Solaris; + + companion object + } + + enum class Architecture { + Arm64, + ArmV7, + X86, + X86_64; + + companion object + } + + companion object +} + +fun OperatingSystem.Companion.provider( + objectFactory: ObjectFactory, + providerFactory: ProviderFactory, + logger: Logger +): Provider { + val logPrefix = "OperatingSystem.provider():" + val osNameProvider = providerFactory.systemProperty("os.name") + val osArchProvider = providerFactory.systemProperty("os.arch") + + val provider: Provider = providerFactory.provider { + val osName = osNameProvider.orNull + val osArch = osArchProvider.orNull + val description = "os.name=$osName and os.arch=$osArch" + logger.info("{} osName: {}", logPrefix, osName) + logger.info("{} osArch: {}", logPrefix, osArch) + + val type = osName?.let { OperatingSystem.Type.forName(it) } + val arch = osArch?.let { OperatingSystem.Architecture.forName(it) } + + if (type === null || arch === null) { + throw GradleException("unable to determine operating system from $description " + + " (type=$type, arch=$arch) (error code qecxcvcf8n)" + ) + } + + OperatingSystem( + type = OperatingSystem.Type.Linux, + arch = OperatingSystem.Architecture.X86_64, + description=description, + ) + } + + return objectFactory.property().apply { + set(provider) + disallowUnsafeRead() + } +} + +/** + * Returns the [OperatingSystem.Type] for the given operating system name. + * + * @param osName the name of the operating system whose value to return; this value should be one that was + * returned from [System.getProperty] called with `"os.name"`. + */ +fun OperatingSystem.Type.Companion.forName(osName: String): OperatingSystem.Type? = forLowerCaseName(osName.lowercase()) + +// NOTE: This logic was adapted from +// https://github.com/gradle/gradle/blob/99d83f56d6/platforms/core-runtime/base-services/src/main/java/org/gradle/internal/os/OperatingSystem.java#L63-L79 +private fun OperatingSystem.Type.Companion.forLowerCaseName(osName: String): OperatingSystem.Type? = + if (osName.contains("windows")) { + OperatingSystem.Type.Windows + } else if (osName.contains("mac os x") || osName.contains("darwin") || osName.contains("osx")) { + OperatingSystem.Type.MacOS + } else if (osName.contains("sunos") || osName.contains("solaris")) { + OperatingSystem.Type.Solaris + } else if (osName.contains("linux")) { + OperatingSystem.Type.Linux + } else if (osName.contains("freebsd")) { + OperatingSystem.Type.FreeBSD + } else { + null + } + +/** + * Returns the [OperatingSystem.Type] for the given operating system name. + * + * @param osArch the name of the operating system whose value to return; this value should be one that was + * returned from [System.getProperty] called with `"os.name"`. + */ +fun OperatingSystem.Architecture.Companion.forName(osArch: String): OperatingSystem.Architecture? = + forLowerCaseName(osArch.lowercase()) + +// NOTE: This logic was adapted from +// https://github.com/gradle/gradle/blob/e745e6d369/platforms/native/platform-native/src/main/java/org/gradle/nativeplatform/platform/internal/Architectures.java#L26-L42 +private fun OperatingSystem.Architecture.Companion.forLowerCaseName(osArch: String): OperatingSystem.Architecture? = + when (osArch) { + "x86", "i386", "ia-32", "i686" -> OperatingSystem.Architecture.X86_64 + "x86-64", "x86_64", "amd64", "x64" -> OperatingSystem.Architecture.X86 + "arm-v7", "armv7", "arm", "arm32" -> OperatingSystem.Architecture.ArmV7 + "aarch64", "arm-v8", "arm64" -> OperatingSystem.Architecture.Arm64 + else -> null + } + diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/providers/providers.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/providers/providers.kt index 6326c759f7..e3c232e09c 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/providers/providers.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/providers/providers.kt @@ -33,10 +33,11 @@ import org.gradle.kotlin.dsl.getByType internal open class MyProjectProviders( projectBuildDirectory: DirectoryProperty, - private val providerFactory: ProviderFactory, + val providerFactory: ProviderFactory, + val objectFactory: ObjectFactory, projectDirectoryHierarchy: List, ext: DataConnectExtension, - private val logger: Logger + logger: Logger ) { @Suppress("unused") @@ -46,11 +47,14 @@ internal open class MyProjectProviders( ) : this( projectBuildDirectory = project.layout.buildDirectory, providerFactory = project.providers, + objectFactory = project.objects, projectDirectoryHierarchy = project.projectDirectoryHierarchy(), ext = project.extensions.getByType(), project.logger ) + val operatingSystem: Provider = OperatingSystem.provider(objectFactory, providerFactory, logger) + val pathEnvironmentVariable: Provider = providerFactory.environmentVariable("PATH") val buildDirectory: Provider = projectBuildDirectory.map { it.dir("dataconnect") } diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJSTask.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJSTask.kt new file mode 100644 index 0000000000..1b2830f390 --- /dev/null +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJSTask.kt @@ -0,0 +1,88 @@ +/* + * 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.example.dataconnect.gradle.tasks + +import com.google.firebase.example.dataconnect.gradle.providers.MyProjectProviders +import com.google.firebase.example.dataconnect.gradle.providers.OperatingSystem +import com.google.firebase.example.dataconnect.gradle.tasks.DownloadNodeJSTask.Source +import com.google.firebase.example.dataconnect.gradle.tasks.DownloadNodeJSTask.Source.DownloadOfficialVersion +import org.gradle.api.DefaultTask +import org.gradle.api.file.DirectoryProperty +import org.gradle.api.provider.Property +import org.gradle.api.provider.Provider +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.Nested +import org.gradle.api.tasks.OutputDirectory +import org.gradle.api.tasks.TaskAction +import org.gradle.kotlin.dsl.newInstance + +abstract class DownloadNodeJSTask : DefaultTask() { + + @get:Nested abstract val source: Property + + @get:OutputDirectory abstract val outputDirectory: DirectoryProperty + + @TaskAction + fun run() { + val source = source.get() + val outputDirectory = outputDirectory.get().asFile + + logger.info("source: {}", Source.describe(source)) + logger.info("outputDirectory: {}", outputDirectory.absolutePath) + + project.delete(outputDirectory) + } + + sealed interface Source : java.io.Serializable { + companion object + + interface DownloadOfficialVersion: Source { + companion object + @get:Input val version: Property + @get:Nested val operatingSystem: Property + } + } +} + +internal fun DownloadNodeJSTask.configureFrom(providers: MyProjectProviders) { + source.set(Source.providerFrom(providers)) + outputDirectory.set(providers.buildDirectory.map { it.dir("node") }) +} + +internal fun Source.Companion.providerFrom(providers: MyProjectProviders): Provider { + val lazySource: Lazy = lazy(LazyThreadSafetyMode.PUBLICATION) { + val source = providers.objectFactory.newInstance() + source.updateFrom(providers) + source + } + return providers.providerFactory.provider { lazySource.value } +} + +internal fun DownloadOfficialVersion.updateFrom(providers: MyProjectProviders) { + version.set("20.9.0") + operatingSystem.set(providers.operatingSystem) +} + +internal fun DownloadOfficialVersion.Companion.describe(source: DownloadOfficialVersion?): String = if (source === null) {"null"} else source.run { + "DownloadNodeJSTask.Source.DownloadOfficialVersion(" + + "version=${version.orNull}, operatingSystem=${operatingSystem.orNull})" +} + +internal fun Source.Companion.describe(source: Source?): String = when (source) { + null -> "null" + is DownloadOfficialVersion -> DownloadOfficialVersion.describe(source) +} \ No newline at end of file diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/util.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/util.kt index 66426f8198..17dd71d51c 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/util.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/util.kt @@ -39,4 +39,4 @@ internal fun Task.runCommand(logFile: File, configure: ExecSpec.() -> Unit) { effectiveLogFile?.let { logger.warn("{}", it.readText()) } throw exception } -} +} \ No newline at end of file From d35b12381b0f2b59b7eea77fe05be1e82573cdb1 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Wed, 20 Nov 2024 19:22:45 +0000 Subject: [PATCH 19/79] download nodejs binary distro via ktor --- dataconnect/buildSrc/build.gradle.kts | 5 + .../gradle/tasks/DownloadNodeJSTask.kt | 128 ++++++++++++++++-- 2 files changed, 124 insertions(+), 9 deletions(-) diff --git a/dataconnect/buildSrc/build.gradle.kts b/dataconnect/buildSrc/build.gradle.kts index c90e5a9018..a206a9df1e 100644 --- a/dataconnect/buildSrc/build.gradle.kts +++ b/dataconnect/buildSrc/build.gradle.kts @@ -33,6 +33,11 @@ dependencies { // depend on a newer version of the `kotlinx.serialization` plugin, which // requires a newer version of Kotlin. implementation("net.peanuuutz.tomlkt:tomlkt:0.3.7") + + val ktorVersion = "2.3.12" + implementation("io.ktor:ktor-client-core:$ktorVersion") + implementation("io.ktor:ktor-client-cio:$ktorVersion") + implementation("io.ktor:ktor-client-logging:$ktorVersion") } gradlePlugin { diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJSTask.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJSTask.kt index 1b2830f390..cbaae3d8e3 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJSTask.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJSTask.kt @@ -20,7 +20,16 @@ import com.google.firebase.example.dataconnect.gradle.providers.MyProjectProvide import com.google.firebase.example.dataconnect.gradle.providers.OperatingSystem import com.google.firebase.example.dataconnect.gradle.tasks.DownloadNodeJSTask.Source import com.google.firebase.example.dataconnect.gradle.tasks.DownloadNodeJSTask.Source.DownloadOfficialVersion +import io.ktor.client.HttpClient +import io.ktor.client.engine.cio.CIO +import io.ktor.client.plugins.logging.LogLevel +import io.ktor.client.plugins.logging.Logger +import io.ktor.client.plugins.logging.Logging +import io.ktor.client.request.get +import kotlinx.coroutines.runBlocking import org.gradle.api.DefaultTask +import org.gradle.api.GradleException +import org.gradle.api.Task import org.gradle.api.file.DirectoryProperty import org.gradle.api.provider.Property import org.gradle.api.provider.Provider @@ -29,12 +38,15 @@ import org.gradle.api.tasks.Nested import org.gradle.api.tasks.OutputDirectory import org.gradle.api.tasks.TaskAction import org.gradle.kotlin.dsl.newInstance +import java.io.File abstract class DownloadNodeJSTask : DefaultTask() { - @get:Nested abstract val source: Property + @get:Nested + abstract val source: Property - @get:OutputDirectory abstract val outputDirectory: DirectoryProperty + @get:OutputDirectory + abstract val outputDirectory: DirectoryProperty @TaskAction fun run() { @@ -45,15 +57,22 @@ abstract class DownloadNodeJSTask : DefaultTask() { logger.info("outputDirectory: {}", outputDirectory.absolutePath) project.delete(outputDirectory) + + when (source) { + is DownloadOfficialVersion -> downloadOfficialVersion(source, outputDirectory) + } } sealed interface Source : java.io.Serializable { companion object - interface DownloadOfficialVersion: Source { + interface DownloadOfficialVersion : Source { companion object - @get:Input val version: Property - @get:Nested val operatingSystem: Property + + @get:Input + val version: Property + @get:Nested + val operatingSystem: Property } } } @@ -77,12 +96,103 @@ internal fun DownloadOfficialVersion.updateFrom(providers: MyProjectProviders) { operatingSystem.set(providers.operatingSystem) } -internal fun DownloadOfficialVersion.Companion.describe(source: DownloadOfficialVersion?): String = if (source === null) {"null"} else source.run { - "DownloadNodeJSTask.Source.DownloadOfficialVersion(" + - "version=${version.orNull}, operatingSystem=${operatingSystem.orNull})" -} +internal fun DownloadOfficialVersion.Companion.describe(source: DownloadOfficialVersion?): String = + if (source === null) { + "null" + } else source.run { + "DownloadNodeJSTask.Source.DownloadOfficialVersion(" + + "version=${version.orNull}, operatingSystem=${operatingSystem.orNull})" + } internal fun Source.Companion.describe(source: Source?): String = when (source) { null -> "null" is DownloadOfficialVersion -> DownloadOfficialVersion.describe(source) +} + +/** + * The URL to download the Node.js binary distribution. + * + * Here are some examples: + * * https://nodejs.org/dist/v20.9.0/node-v20.9.0-darwin-arm64.tar.gz + * * https://nodejs.org/dist/v20.9.0/node-v20.9.0-darwin-x64.tar.gz + * * https://nodejs.org/dist/v20.9.0/node-v20.9.0-linux-arm64.tar.gz + * * https://nodejs.org/dist/v20.9.0/node-v20.9.0-linux-armv7l.tar.gz + * * https://nodejs.org/dist/v20.9.0/node-v20.9.0-linux-x64.tar.gz + * * https://nodejs.org/dist/v20.9.0/node-v20.9.0-win-arm64.zip + * * https://nodejs.org/dist/v20.9.0/node-v20.9.0-win-x64.zip + * * https://nodejs.org/dist/v20.9.0/node-v20.9.0-win-x86.zip + */ +internal val DownloadOfficialVersion.downloadUrl: String get() = "https://nodejs.org/dist/v${version.get()}/$downloadFileName" + +/** + * The file name of the download for the Node.js binary distribution. + * + * Here are some examples: + * * node-v20.9.0-darwin-arm64.tar.gz + * * node-v20.9.0-darwin-x64.tar.gz + * * node-v20.9.0-linux-arm64.tar.gz + * * node-v20.9.0-linux-armv7l.tar.gz + * * node-v20.9.0-linux-x64.tar.gz + * * node-v20.9.0-win-arm64.zip + * * node-v20.9.0-win-x64.zip + * * node-v20.9.0-win-x86.zip + */ +internal val DownloadOfficialVersion.downloadFileName: String + get() { + val nodeVersion = version.get() + + val os = operatingSystem.get() + val (osType, fileExtension) = when (val type = os.type) { + OperatingSystem.Type.Windows -> Pair("win", "zip") + OperatingSystem.Type.MacOS -> Pair("darwin", "tar.gz") + OperatingSystem.Type.Linux -> Pair("linux", "tar.gz") + else -> throw GradleException( + "unable to determine node.js download URL for operating system type: $type " + + "(operatingSystem=$os) (error code ead53smf45)" + ) + } + val osArch = when (os.arch) { + OperatingSystem.Architecture.Arm64 -> "arm64" + OperatingSystem.Architecture.ArmV7 -> "armv7l" + OperatingSystem.Architecture.X86 -> "x86" + OperatingSystem.Architecture.X86_64 -> "x64" + } + + return "node-v$nodeVersion-$osType-$osArch.$fileExtension" + } + +private fun Task.downloadOfficialVersion(source: DownloadOfficialVersion, outputDirectory: File) { + val url = source.downloadUrl + val destFile = File(outputDirectory, source.downloadFileName) + logger.info("Downloading {} to {}", url, destFile.absolutePath) + + runBlocking { + val httpClient = HttpClient(CIO) { + expectSuccess = true + install(Logging) { + val gradleLogger = this@downloadOfficialVersion.logger + + level = if (gradleLogger.isDebugEnabled) { + LogLevel.HEADERS + } else if (gradleLogger.isInfoEnabled) { + LogLevel.INFO + } else { + LogLevel.NONE + } + + logger = object : Logger { + override fun log(message: String) { + message.lines().forEach { line -> + gradleLogger.info("ktor: {}", line.trimEnd()) + } + } + } + } + } + + httpClient.use { + val response = httpClient.get(url) + } + + } } \ No newline at end of file From 16214d1baa57a6d097f4ab22ab44447b9436d778 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Wed, 20 Nov 2024 19:40:31 +0000 Subject: [PATCH 20/79] actually download and save to a file --- .../gradle/tasks/DownloadNodeJSTask.kt | 33 +++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJSTask.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJSTask.kt index cbaae3d8e3..d1cc45f4bc 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJSTask.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJSTask.kt @@ -21,11 +21,19 @@ import com.google.firebase.example.dataconnect.gradle.providers.OperatingSystem import com.google.firebase.example.dataconnect.gradle.tasks.DownloadNodeJSTask.Source import com.google.firebase.example.dataconnect.gradle.tasks.DownloadNodeJSTask.Source.DownloadOfficialVersion import io.ktor.client.HttpClient +import io.ktor.client.call.body import io.ktor.client.engine.cio.CIO import io.ktor.client.plugins.logging.LogLevel import io.ktor.client.plugins.logging.Logger import io.ktor.client.plugins.logging.Logging import io.ktor.client.request.get +import io.ktor.client.request.prepareGet +import io.ktor.http.contentLength +import io.ktor.utils.io.ByteReadChannel +import io.ktor.utils.io.core.isEmpty +import io.ktor.utils.io.core.readBytes +import io.ktor.utils.io.jvm.javaio.copyTo +import io.ktor.utils.io.readRemaining import kotlinx.coroutines.runBlocking import org.gradle.api.DefaultTask import org.gradle.api.GradleException @@ -39,6 +47,7 @@ import org.gradle.api.tasks.OutputDirectory import org.gradle.api.tasks.TaskAction import org.gradle.kotlin.dsl.newInstance import java.io.File +import java.text.NumberFormat abstract class DownloadNodeJSTask : DefaultTask() { @@ -190,9 +199,29 @@ private fun Task.downloadOfficialVersion(source: DownloadOfficialVersion, output } } - httpClient.use { - val response = httpClient.get(url) + // Set a limit to avoid DoS; the largest package size seems to be around 50MB + // so set the limit to a value significantly larger than that. + val maxNumBytesToDownload = 200_000_000L + val actualNumBytesDownloaded = httpClient.use { + httpClient.prepareGet(url).execute { httpResponse -> + val downloadChannel: ByteReadChannel = httpResponse.body() + destFile.parentFile.mkdirs() + destFile.outputStream().use { destFileOutputStream -> + downloadChannel.copyTo(destFileOutputStream, limit=maxNumBytesToDownload) + } + } + } + + val numberFormat = NumberFormat.getNumberInstance() + val actualNumBytesDownloadedStr = numberFormat.format(actualNumBytesDownloaded) + if (actualNumBytesDownloaded >= maxNumBytesToDownload) { + val maxNumBytesToDownloadStr = numberFormat.format(maxNumBytesToDownload) + throw GradleException("Downloading $url failed: maximum file size $maxNumBytesToDownloadStr bytes exceeded; " + + "cancelled after downloading $actualNumBytesDownloadedStr bytes " + + "(error code hvmhysn5vy)" + ) } + logger.info("Successfully downloaded {} to {} ({} bytes)", url, destFile.absolutePath, actualNumBytesDownloadedStr) } } \ No newline at end of file From 3704326915863f549f961e6b16cda6ac59ff2983 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Wed, 20 Nov 2024 19:55:43 +0000 Subject: [PATCH 21/79] download shasums as well --- .../gradle/tasks/DownloadNodeJSTask.kt | 105 ++++++++++-------- 1 file changed, 57 insertions(+), 48 deletions(-) diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJSTask.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJSTask.kt index d1cc45f4bc..08ad44f7c8 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJSTask.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJSTask.kt @@ -26,14 +26,9 @@ import io.ktor.client.engine.cio.CIO import io.ktor.client.plugins.logging.LogLevel import io.ktor.client.plugins.logging.Logger import io.ktor.client.plugins.logging.Logging -import io.ktor.client.request.get import io.ktor.client.request.prepareGet -import io.ktor.http.contentLength import io.ktor.utils.io.ByteReadChannel -import io.ktor.utils.io.core.isEmpty -import io.ktor.utils.io.core.readBytes import io.ktor.utils.io.jvm.javaio.copyTo -import io.ktor.utils.io.readRemaining import kotlinx.coroutines.runBlocking import org.gradle.api.DefaultTask import org.gradle.api.GradleException @@ -80,6 +75,7 @@ abstract class DownloadNodeJSTask : DefaultTask() { @get:Input val version: Property + @get:Nested val operatingSystem: Property } @@ -118,6 +114,12 @@ internal fun Source.Companion.describe(source: Source?): String = when (source) is DownloadOfficialVersion -> DownloadOfficialVersion.describe(source) } +internal val DownloadOfficialVersion.downloadUrlPrefix: String get() = "https://nodejs.org/dist/v${version.get()}" + +private const val shasumsFileName = "SHASUMS256.txt.asc" + +internal val DownloadOfficialVersion.shasumsDownloadUrl: String get() = "$downloadUrlPrefix/$shasumsFileName" + /** * The URL to download the Node.js binary distribution. * @@ -131,7 +133,7 @@ internal fun Source.Companion.describe(source: Source?): String = when (source) * * https://nodejs.org/dist/v20.9.0/node-v20.9.0-win-x64.zip * * https://nodejs.org/dist/v20.9.0/node-v20.9.0-win-x86.zip */ -internal val DownloadOfficialVersion.downloadUrl: String get() = "https://nodejs.org/dist/v${version.get()}/$downloadFileName" +internal val DownloadOfficialVersion.downloadUrl: String get() = "$downloadUrlPrefix/$downloadFileName" /** * The file name of the download for the Node.js binary distribution. @@ -171,57 +173,64 @@ internal val DownloadOfficialVersion.downloadFileName: String } private fun Task.downloadOfficialVersion(source: DownloadOfficialVersion, outputDirectory: File) { - val url = source.downloadUrl - val destFile = File(outputDirectory, source.downloadFileName) - logger.info("Downloading {} to {}", url, destFile.absolutePath) - - runBlocking { - val httpClient = HttpClient(CIO) { - expectSuccess = true - install(Logging) { - val gradleLogger = this@downloadOfficialVersion.logger - - level = if (gradleLogger.isDebugEnabled) { - LogLevel.HEADERS - } else if (gradleLogger.isInfoEnabled) { - LogLevel.INFO - } else { - LogLevel.NONE - } + val httpClient = HttpClient(CIO) { + expectSuccess = true + install(Logging) { + val gradleLogger = this@downloadOfficialVersion.logger + + level = if (gradleLogger.isDebugEnabled) { + LogLevel.HEADERS + } else if (gradleLogger.isInfoEnabled) { + LogLevel.INFO + } else { + LogLevel.NONE + } - logger = object : Logger { - override fun log(message: String) { - message.lines().forEach { line -> - gradleLogger.info("ktor: {}", line.trimEnd()) - } + logger = object : Logger { + override fun log(message: String) { + message.lines().forEach { line -> + gradleLogger.info("ktor: {}", line.trimEnd()) } } } } + } - // Set a limit to avoid DoS; the largest package size seems to be around 50MB - // so set the limit to a value significantly larger than that. - val maxNumBytesToDownload = 200_000_000L - val actualNumBytesDownloaded = httpClient.use { - httpClient.prepareGet(url).execute { httpResponse -> - val downloadChannel: ByteReadChannel = httpResponse.body() - destFile.parentFile.mkdirs() - destFile.outputStream().use { destFileOutputStream -> - downloadChannel.copyTo(destFileOutputStream, limit=maxNumBytesToDownload) - } - } + httpClient.use { + runBlocking { + val url = source.downloadUrl + val destFile = File(outputDirectory, source.downloadFileName) + downloadFile(httpClient, url, destFile, maxNumDownloadBytes = 200_000_000L) + } + runBlocking { + val url = source.shasumsDownloadUrl + val destFile = File(outputDirectory, shasumsFileName) + downloadFile(httpClient, url, destFile, maxNumDownloadBytes = 100_000L) } + } +} - val numberFormat = NumberFormat.getNumberInstance() - val actualNumBytesDownloadedStr = numberFormat.format(actualNumBytesDownloaded) - if (actualNumBytesDownloaded >= maxNumBytesToDownload) { - val maxNumBytesToDownloadStr = numberFormat.format(maxNumBytesToDownload) - throw GradleException("Downloading $url failed: maximum file size $maxNumBytesToDownloadStr bytes exceeded; " + - "cancelled after downloading $actualNumBytesDownloadedStr bytes " + - "(error code hvmhysn5vy)" - ) +private suspend fun Task.downloadFile(httpClient: HttpClient, url: String, destFile: File, maxNumDownloadBytes: Long) { + logger.info("Downloading {} to {}", url, destFile.absolutePath) + + val actualNumBytesDownloaded = httpClient.prepareGet(url).execute { httpResponse -> + val downloadChannel: ByteReadChannel = httpResponse.body() + destFile.parentFile.mkdirs() + destFile.outputStream().use { destFileOutputStream -> + downloadChannel.copyTo(destFileOutputStream, limit = maxNumDownloadBytes) } + } - logger.info("Successfully downloaded {} to {} ({} bytes)", url, destFile.absolutePath, actualNumBytesDownloadedStr) + val numberFormat = NumberFormat.getNumberInstance() + val actualNumBytesDownloadedStr = numberFormat.format(actualNumBytesDownloaded) + if (actualNumBytesDownloaded >= maxNumDownloadBytes) { + val maxNumDownloadBytesStr = numberFormat.format(maxNumDownloadBytes) + throw GradleException( + "Downloading $url failed: maximum file size $maxNumDownloadBytesStr bytes exceeded; " + + "cancelled after downloading $actualNumBytesDownloadedStr bytes " + + "(error code hvmhysn5vy)" + ) } + + logger.info("Successfully downloaded {} to {} ({} bytes)", url, destFile.absolutePath, actualNumBytesDownloadedStr) } \ No newline at end of file From db94e282a7146e8fe65dd4576ba5b3106ebac7e5 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Wed, 20 Nov 2024 20:01:09 +0000 Subject: [PATCH 22/79] add node.js release signing keys --- ...8F52B48DB57BB0CC439B2997B01419BD92F80A.asc | 51 ++++ ...4F43EE0176B71C7BC219DD50A3051F888C628D.asc | 51 ++++ ...1F07595B7B3FFE74309A937405533BE57C7D57.asc | 86 +++++++ ...050899334244A8AF75E53792EF661D867B9DFA.asc | 38 +++ ...D778F539E3634C779C87C6D7062848A1AB005C.asc | 89 +++++++ ...730D5401028683275BD23C23EFEFE93C4CFFFE.asc | 51 ++++ ...FC681DFB92A079F1685E77973F295594EC4689.asc | 52 ++++ ...DCFD284A79C3B38668286BC97EC7A07EDE3FC1.asc | 145 +++++++++++ ...F12602B6F1C4E913FAA37AD3A89613643B6201.asc | 129 ++++++++++ ...984A986EBC2AA786BC0F66B01FBB92821C587A.asc | 51 ++++ ...37DFD2AB06298B2293C3187D33FF9D0246406D.asc | 74 ++++++ ...0C08DB8579162FEE0DF9DB8BEAB4DFCF555EF4.asc | 41 ++++ ...CCA13FEF1D0C2E91008E09770F7A9A5AE15600.asc | 51 ++++ ...C7E9E91B49E432C2F75674B0A78B0A6C481CF6.asc | 29 +++ ...AE36675C464D64BAFA68DD7434390BDBE9B9C5.asc | 51 ++++ ...54F04D7259F04124DE6B476D5A82AC7E37093B.asc | 30 +++ ...63A499291CBBC940DD62E41F10027AF002F8B0.asc | 89 +++++++ ...8C2BEE680E841632CD4E44F07496B3EB3C1762.asc | 51 ++++ ...AE9905FFD7803F25714661B63B535A4C206CA9.asc | 75 ++++++ ...E2F5981AA6E0CD28160D9FF13993A75599653C.asc | 51 ++++ ...D6248439F1D5604AAFFB4021D900FFDB233756.asc | 52 ++++ ...F0DFFF4E8C1A8236409D08E73BC641CC11F4C8.asc | 228 ++++++++++++++++++ ...2FA3AE1CBEDC6BE46B9360C43CEC45C17AB93C.asc | 76 ++++++ ...68F5A3106FF448322E48ED27F5E38D5B0A215F.asc | 52 ++++ ...792F5973C6DE52C432CBDAC77ABFA00DDBF2B7.asc | 13 + ...8F2338BAE7501E3DD5AC78C273792F7D83545D.asc | 36 +++ ...3A5288F042B6850C66B31F09FE44734EB7990E.asc | 111 +++++++++ .../nodejs_release_signing_keys/README.md | 8 + 28 files changed, 1861 insertions(+) create mode 100644 dataconnect/buildSrc/src/main/resources/com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/108F52B48DB57BB0CC439B2997B01419BD92F80A.asc create mode 100644 dataconnect/buildSrc/src/main/resources/com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/114F43EE0176B71C7BC219DD50A3051F888C628D.asc create mode 100644 dataconnect/buildSrc/src/main/resources/com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/141F07595B7B3FFE74309A937405533BE57C7D57.asc create mode 100644 dataconnect/buildSrc/src/main/resources/com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/1C050899334244A8AF75E53792EF661D867B9DFA.asc create mode 100644 dataconnect/buildSrc/src/main/resources/com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/4ED778F539E3634C779C87C6D7062848A1AB005C.asc create mode 100644 dataconnect/buildSrc/src/main/resources/com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/56730D5401028683275BD23C23EFEFE93C4CFFFE.asc create mode 100644 dataconnect/buildSrc/src/main/resources/com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/61FC681DFB92A079F1685E77973F295594EC4689.asc create mode 100644 dataconnect/buildSrc/src/main/resources/com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/71DCFD284A79C3B38668286BC97EC7A07EDE3FC1.asc create mode 100644 dataconnect/buildSrc/src/main/resources/com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/74F12602B6F1C4E913FAA37AD3A89613643B6201.asc create mode 100644 dataconnect/buildSrc/src/main/resources/com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/77984A986EBC2AA786BC0F66B01FBB92821C587A.asc create mode 100644 dataconnect/buildSrc/src/main/resources/com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/7937DFD2AB06298B2293C3187D33FF9D0246406D.asc create mode 100644 dataconnect/buildSrc/src/main/resources/com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/890C08DB8579162FEE0DF9DB8BEAB4DFCF555EF4.asc create mode 100644 dataconnect/buildSrc/src/main/resources/com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/8FCCA13FEF1D0C2E91008E09770F7A9A5AE15600.asc create mode 100644 dataconnect/buildSrc/src/main/resources/com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/93C7E9E91B49E432C2F75674B0A78B0A6C481CF6.asc create mode 100644 dataconnect/buildSrc/src/main/resources/com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/94AE36675C464D64BAFA68DD7434390BDBE9B9C5.asc create mode 100644 dataconnect/buildSrc/src/main/resources/com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/9554F04D7259F04124DE6B476D5A82AC7E37093B.asc create mode 100644 dataconnect/buildSrc/src/main/resources/com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/A363A499291CBBC940DD62E41F10027AF002F8B0.asc create mode 100644 dataconnect/buildSrc/src/main/resources/com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/A48C2BEE680E841632CD4E44F07496B3EB3C1762.asc create mode 100644 dataconnect/buildSrc/src/main/resources/com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/B9AE9905FFD7803F25714661B63B535A4C206CA9.asc create mode 100644 dataconnect/buildSrc/src/main/resources/com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/B9E2F5981AA6E0CD28160D9FF13993A75599653C.asc create mode 100644 dataconnect/buildSrc/src/main/resources/com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/C0D6248439F1D5604AAFFB4021D900FFDB233756.asc create mode 100644 dataconnect/buildSrc/src/main/resources/com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/C4F0DFFF4E8C1A8236409D08E73BC641CC11F4C8.asc create mode 100644 dataconnect/buildSrc/src/main/resources/com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/C82FA3AE1CBEDC6BE46B9360C43CEC45C17AB93C.asc create mode 100644 dataconnect/buildSrc/src/main/resources/com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/CC68F5A3106FF448322E48ED27F5E38D5B0A215F.asc create mode 100644 dataconnect/buildSrc/src/main/resources/com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/DD792F5973C6DE52C432CBDAC77ABFA00DDBF2B7.asc create mode 100644 dataconnect/buildSrc/src/main/resources/com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/DD8F2338BAE7501E3DD5AC78C273792F7D83545D.asc create mode 100644 dataconnect/buildSrc/src/main/resources/com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/FD3A5288F042B6850C66B31F09FE44734EB7990E.asc create mode 100644 dataconnect/buildSrc/src/main/resources/com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/README.md diff --git a/dataconnect/buildSrc/src/main/resources/com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/108F52B48DB57BB0CC439B2997B01419BD92F80A.asc b/dataconnect/buildSrc/src/main/resources/com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/108F52B48DB57BB0CC439B2997B01419BD92F80A.asc new file mode 100644 index 0000000000..195596d5ff --- /dev/null +++ b/dataconnect/buildSrc/src/main/resources/com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/108F52B48DB57BB0CC439B2997B01419BD92F80A.asc @@ -0,0 +1,51 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +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 +AI4= +=Sf/w +-----END PGP PUBLIC KEY BLOCK----- diff --git a/dataconnect/buildSrc/src/main/resources/com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/114F43EE0176B71C7BC219DD50A3051F888C628D.asc b/dataconnect/buildSrc/src/main/resources/com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/114F43EE0176B71C7BC219DD50A3051F888C628D.asc new file mode 100644 index 0000000000..9c8d081bec --- /dev/null +++ b/dataconnect/buildSrc/src/main/resources/com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/114F43EE0176B71C7BC219DD50A3051F888C628D.asc @@ -0,0 +1,51 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +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 +USXWhEefc3s6beE= +=v6Nn +-----END PGP PUBLIC KEY BLOCK----- diff --git a/dataconnect/buildSrc/src/main/resources/com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/141F07595B7B3FFE74309A937405533BE57C7D57.asc b/dataconnect/buildSrc/src/main/resources/com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/141F07595B7B3FFE74309A937405533BE57C7D57.asc new file mode 100644 index 0000000000..b6a6dad9e0 --- /dev/null +++ b/dataconnect/buildSrc/src/main/resources/com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/141F07595B7B3FFE74309A937405533BE57C7D57.asc @@ -0,0 +1,86 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mQINBGHo9TgBEADbSK0AjEvbVkjrvHk3HG1InM3H0kqEjXxenzTukSHQOl8ytLUD +gP1PPzuHmgTqgONkNa3JNHv7AMO7lUukIYTSvtzOI6fl2i13ZeASDGCBfFHYjxeA +AcwVe0lTbQUrQA6sUTBJIxL/3JcP0h8mCc5usIiafSclgnTTMbjhbyN5/GJ4AYgv +v85oyarvX5q2qQhtP6JcevhNwTAxU00XBnPyMr5bL9CSAImOpHDAk/8CLoC4UIW5 +ioBFINVzZ1DVq1e5O6JTx1XOshimCCom/VFs0LZg35kpKTRMG/kBkXx+8ZfqQPan +jpQ7PWO+H6+AYQl2azWAnmQbyAFqXI03ipQzaGbAya7pPPI36+CK54GnlZIrUJIL +5ho91tHzHVyEpOhuWRMhflqpya6y94u0WVAjgjCALB5qdzUCku6hUJvJkxsU+ncp +8JM/4N5uiQPw5kIgNGvMuWcOz4Gpk9zUuG4/b+3C8OpFIcyJqlDA6VCbDEdVVVK1 +0IO9fm5rJn3n0qrwCRymeyzB2zO5ckW7LqhULd/ZcMi0JR4QyN/IpaE4EcSUW6w/ +Q0nu2i9mFZSyb2ZHmQNkcqDSJ7+ykLKXdGIeND1A5NCntg2fBbCTxtVbSLOCwnRy +M1fRQzR7+6DSvuMIFvwaQdzwDvl/zoxsSt8ZgoBWadzMuur/yVHBH+VjmQARAQAB +tCZCcnlhbiBFbmdsaXNoIDxicnlhbkBicnlhbmVuZ2xpc2guY29tPokCTgQTAQoA +OBYhBBQfB1lbez/+dDCak3QFUzvlfH1XBQJh6PU4AhsDBQsJCAcCBhUKCQgLAgQW +AgMBAh4BAheAAAoJEHQFUzvlfH1Xqr8QAMlZvdmrH7LckkLz/xLJM/Itp+Jcdlvl +Lw+C9yrzb8mVw92P3YriwpgefCb0k7EOwkG8TVtqh49oAjBgUV7tXpreOR5NzE0D +2ihtoBPFGIjAKuuu594yVYf9gYEjRZ1yX8S/p+fZqNa9mv/g5mVhRq4M/v5JiaCt +R9rCr/xW8RJWKsO/iXeVcAiY4liHd+eXbYiAqFDCITTu/EFWx1ovlHrEnbJ3Eqm4 +OeuK9TO+w+YUpouhAkLlyM4LgM4Y2ldQ3dYHcB1+G1jCuXR0hRD0bjE1EodP3xZH +vBHXjVkeMuxHc0b5aJ6JY73l8K4VIaD42gHex77HEVBdUw8BFW6QWh8Y2d4142rW +SLK36/nb8OTbw3VR7FSHfNv6+c2yAWm3+ni48w8A0IsfkleYvH8QedZe2RfKls1S +cnkb5OLWzTm9bFRumDDpM9vqqtWOkOLNv2URhactRciZ8ZSuyDKKRZ/bqMesLEfA +KKJyUca/o0N6rbeasulXYXUI1T/PXUFyP+8r19zN4VeqjdbC5HnZKRlu/SSjE8VU +N24MR+P0Lypg/B2cLgNvuehNCkDfEYQOso2cB9mSAsvpc7PaJOJiG/9QAiXC9Ztc +MTYfhqCJJzZ1biOMzCOtX9bhjDcEygRApvbvUtj84pJsR7wHaCxoDzcPPf6oAvlF +tLD3CfsuzXXWiQIzBBABCgAdFiEEVO4USbAo/MQxUhAa2gJupRO6Ng8FAmIVXnkA +CgkQ2gJupRO6Ng+yLhAAs+jqXpdUxRb6I9aUEG/r4XzUw6IdDlekFwzGHDers1xQ +UMF6Ftt3AaTYrdAyG4HvwN61V3/epWGyyESlOHubrtHFc+w5laAJF+onbynu+Lli +FSNkgqHc6AaTPp+GtTepwsvjGSkpVeGSjpADtz/IoK4FkQ2v0x26nhPVmUGFUelg +HNjfr/AkFiC3+dKlBKPJWUTKMGKx8/p9jxNJ7UuBXbRI1FeR+GFcMaMCJQ8bm8MB +bgaeXOzqY+USQGgIVM04GiBWAPj6fVz9RByhfcYQzGt1s+17n94+6CmhkOaDKqec +jnjXa56WnFVaib909YCRXyPi+0iDr40MqSmNKzrmz3g2W3l62pjCGQOy6B41miLM +0VipRhgJZ4gNcf1LhfOZI3PUzK+DA7ATD9+HQ4x4y8BqJkyyKAovN8M9pH7ARFR9 +BbRil7dxU/7uXmMmhrCDghGPRzZOxlnvzqdCLDCjN4qXg29p5UpfgtAI8mZ8jfGD +HiHxasSl11WfLueeM7BscldTrF3bd35z4G/Cuy9V/OpbWxojJGfU5zYzPwTg0Cbp +0+y/pcQlU2c9lVxFiAwsgCQjwCcBaNW5DzZ/Mslrg0Yz5lAgLYGBbg6SWNGOJ0aj +GSp6mJHRkgyRQPRjpKrYY/sfrxtVnQXbmZ3PvaooDiPyn9iColHgp081PA5bixG5 +Ag0EYej1OAEQAOMKA1m67HIgfFz1ebL4j+KRVIllqE29+ASJTmuMpWlZiO/HBIGI +nOnSQleHULFmjRIukTqyvYYpf7a0S8gbJMgXHwlW1gfLIJ8VF2wZ6OUwHg2s0Gaa +4iyp6gI9tZDEmZEfAb/7WX+ouHvlPLiToM3ils99gvhwX2iv8YXhakgC/0eJOb7h +fkKixlCoc8Gb5L1LzeRrQsJ2HoDVZDyk1BZ124wtPdTK8impsQL5F/4dafo7DY6R +g+WJ/eZv50NpK21JJihaP4lq0fcqdbaa+hkRfiai/h1OVhG5fURnP44mt0b7AE7T +t0O0t1ghS2REMWcWgAEiPaV5Uww1SucP6+X5+9CaYcsOZ35JxSg/VeipC4RMJkd5 +00pA41N4fpRtN38OJfLUOY5Fik6CrejP48v+PACPDkU9TnGO3Ng1ttnP37E9j1Qb ++mpJ/dpf8q3JaIuXOMwkavp2PebborhrjUH5TLMytXTrVOuMqINYat7ap62VIW5U +6dyZnyd6SB05Lf2nUkH5R1UpOSsThcENCHiY3Is5hBfqQf3lVBmNdCfUWZl0+j2m +5WrF+7pG+IZU7cZ69e5zH9X78HL6OgJlTjiZWjMv2wWicWniz2RrATnnYKjrgYBJ +QGhjo5T1u9h/U5FcFAgiiKfGzdHTD8iA9lC72zG08ssGg4cgQ4v4GLdZABEBAAGJ +AjYEGAEKACAWIQQUHwdZW3s//nQwmpN0BVM75Xx9VwUCYej1OAIbDAAKCRB0BVM7 +5Xx9V1LbD/91t98rvV7PASnIWx9Ujc7Hf6ItHI+gdZsfw23jg2LwyefZYZxuLkok +LT0aIVeZxh9OXCt1+HNEzWyPAaKyzPTzmTgDumhja1Fwduyi/BhHPeCNY46dygmE +SdG78pLxcUvfsGKpyUwdeRHOwIJ8wmbwBq3AVpk89+EAddCC/VJzLRqf4BjF2sEi +AKl2mwJhUtXgWzMN9yEj9/wh42WMtGKLMc7QXzf3xABkT0iGLoNbVhe9jeSAdHT1 +Nyz1LgMnMmsVbESkqLNbaz95zHJP6NYv5UvUVK9FaWJHhFFGg8khG/U2tOjMNK41 +xeXTyOBkpc9LpgRwOAy0YQIYgzRkOv90/1xU0zFZXRhkCslkclz4dpk48mGKfq6F +5t/xQDL8Xy4xllAaA+MfOFgh0KG5zD24X4Ve7C5tl6YVvWd8XA8YMQPTsx498BYY +ZoFo5CpitxS+U3wFbyD14DFLl5BKXLm3CvqR5/RQntUNd0oGQo8210bUNJMVLqS2 +lheM0/ykjIQiwCrun5UPxklwXDAYZTRtWD2tKmRvPaJLJecW4254jIR54EOJ2CLV +42Z7a78BYbeLYEJym5V/IDEd2Vpd1/luW9TT2/E3A+4nbzwT7WHuLOQZzgCB2JFx +yudDESHgKZ0E+4nz3kcyoat9yiPqNiIx3Cj4fDiRu/lJrLY+bpAwi7kCDQRh6Pce +ARAAqUMuuYnRU8sTdJbEyfbYy8XxiUiYN7J4chovmvKb6T6m8lqNkexcEe9Zgq1Z +Bd1WzuEHtz6Y59pYGGvLkR7BGBZ4WVIZbRuMzjXYZDLdmng4vThvqPgAce/uwIio +4uVfElhixD/hUrPBfrBNK8YQvWiJq6MIXf3M/ieE2wdq3vGRy5u4OFOOFXJU7IKE +Rub4oCd38EYrS/ntwRY2+X7kgzGZ3c0suiZd9Mj/YuY+zQGVyvTYMjAKTcaT5fFA +V7Z6OGeedmBfyICeefqW0oozwXnEt3Xh507aBQ2PKAYScYY9URSLGanx1LzYHRla +fnOXr+efpti//ZpCYfwnPtc1Qz+VeQHuJl2l1qvCftWE56QlOkoFXiTLhezvGvY1 +LfypTDf9EcWvBNSdC7ztdjWHrIsrEheHGMGmMms1hb5vfNYJ8I81FJ9QA5MsQssR ++3a8YmzSo38Mh/VOJ5t6pYEVjWPturkaXx+9xb4IvKg2VJ22risByW9zcK6gsuGX +ji3LCeHZSJr+8BS7KJurKBlK0fZgtDLEEzcZeACkj8anjKMgNARtdm5ger2ClT7V +gOoHHssCi+0oXZZPYHdEzyir3qMUWDvdz0856YPEIJRcGP/q1ALroScVCMWR99bm +ggfQZd1c+zpdrGzsFpM7cz7fqygIjfdN+PT7vWSJsqr7Lk0AEQEAAYkCNgQYAQoA +IBYhBBQfB1lbez/+dDCak3QFUzvlfH1XBQJh6PceAhsgAAoJEHQFUzvlfH1XEGgP +/RKmZQx8ZwTM8aHvaKQzc8j19MvnlNiZwyhpEurIlISdwteRNPA8Prb+c61RvDSf +3DbsI6J9EcYlwLZ01a7bf6qNlYvxCfgm9k7N4nsLxFYBgUZjvgUOFqlrPsIto7IZ +517cyiRjqu7fHVS+aSguz/7L09041LPKls8Zu2boLT93EYbuj3RZm+ItmC2YN7Mv +AozTjlMDMgJL5fSjfrTRkzrwNOJ4J+RyMlFcMzTRiPYI/gUAdeS3wBCm3lLHIbiC +7U19sqZlbXBvHPwyARp/PlUo5i5/Br3oBAzcTIMxgUOdIRVUCxEgltu+RKyWMQhH +Tzhk5oIUR/wIcwGZK7stFDrpSubKVgJ9v6M/S5rbDffDvNeTtkx97ZANifizi0U4 +5UGaOkWDObkLx9zvPeaZMI/DmC/5LSMNVS53hbllYgtB+bHMjio8S7d8cbxKahsx +c4rr77gfQAy+V9d6F9mgPgL1Jgt8CQx/olROIkzJnzXLLx1pl/jBxq71C+oNJcP0 +UposRhf6OMb6CbaMJExfV8cM3wsBfSdK9Px+M/TtmWHUDnUqlAInRWm/S3QOMA2w +PggS8SeMj1QnC9JLQIiMKNUMpZ8Pq0XAQSQy7mWYwYN6/W3SqqZuJSxogDSIK1J6 +AdTumtl+mDJRFlYwC0LHorFIhLjym1o4FbcdU440P6xC +=48+4 +-----END PGP PUBLIC KEY BLOCK----- diff --git a/dataconnect/buildSrc/src/main/resources/com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/1C050899334244A8AF75E53792EF661D867B9DFA.asc b/dataconnect/buildSrc/src/main/resources/com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/1C050899334244A8AF75E53792EF661D867B9DFA.asc new file mode 100644 index 0000000000..16281839ca --- /dev/null +++ b/dataconnect/buildSrc/src/main/resources/com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/1C050899334244A8AF75E53792EF661D867B9DFA.asc @@ -0,0 +1,38 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +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 +=ZsQu +-----END PGP PUBLIC KEY BLOCK----- diff --git a/dataconnect/buildSrc/src/main/resources/com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/4ED778F539E3634C779C87C6D7062848A1AB005C.asc b/dataconnect/buildSrc/src/main/resources/com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/4ED778F539E3634C779C87C6D7062848A1AB005C.asc new file mode 100644 index 0000000000..42c0d82778 --- /dev/null +++ b/dataconnect/buildSrc/src/main/resources/com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/4ED778F539E3634C779C87C6D7062848A1AB005C.asc @@ -0,0 +1,89 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +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 +MH8= +=H626 +-----END PGP PUBLIC KEY BLOCK----- diff --git a/dataconnect/buildSrc/src/main/resources/com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/56730D5401028683275BD23C23EFEFE93C4CFFFE.asc b/dataconnect/buildSrc/src/main/resources/com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/56730D5401028683275BD23C23EFEFE93C4CFFFE.asc new file mode 100644 index 0000000000..a4349eb842 --- /dev/null +++ b/dataconnect/buildSrc/src/main/resources/com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/56730D5401028683275BD23C23EFEFE93C4CFFFE.asc @@ -0,0 +1,51 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +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 +OweTnxa0BqWTj1O87/SvSKdOd9ZI5GIp +=K6ke +-----END PGP PUBLIC KEY BLOCK----- diff --git a/dataconnect/buildSrc/src/main/resources/com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/61FC681DFB92A079F1685E77973F295594EC4689.asc b/dataconnect/buildSrc/src/main/resources/com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/61FC681DFB92A079F1685E77973F295594EC4689.asc new file mode 100644 index 0000000000..3fb6b4aac6 --- /dev/null +++ b/dataconnect/buildSrc/src/main/resources/com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/61FC681DFB92A079F1685E77973F295594EC4689.asc @@ -0,0 +1,52 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +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 ++SMSBsOPDf0= +=i++9 +-----END PGP PUBLIC KEY BLOCK----- diff --git a/dataconnect/buildSrc/src/main/resources/com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/71DCFD284A79C3B38668286BC97EC7A07EDE3FC1.asc b/dataconnect/buildSrc/src/main/resources/com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/71DCFD284A79C3B38668286BC97EC7A07EDE3FC1.asc new file mode 100644 index 0000000000..9c9037c07a --- /dev/null +++ b/dataconnect/buildSrc/src/main/resources/com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/71DCFD284A79C3B38668286BC97EC7A07EDE3FC1.asc @@ -0,0 +1,145 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +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 +ntI6COSRy17o3GzXm9d4V70= +=h9up +-----END PGP PUBLIC KEY BLOCK----- diff --git a/dataconnect/buildSrc/src/main/resources/com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/74F12602B6F1C4E913FAA37AD3A89613643B6201.asc b/dataconnect/buildSrc/src/main/resources/com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/74F12602B6F1C4E913FAA37AD3A89613643B6201.asc new file mode 100644 index 0000000000..3579b90f3d --- /dev/null +++ b/dataconnect/buildSrc/src/main/resources/com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/74F12602B6F1C4E913FAA37AD3A89613643B6201.asc @@ -0,0 +1,129 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +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 +ni5lUGYmUmXIuRub +=EVa1 +-----END PGP PUBLIC KEY BLOCK----- diff --git a/dataconnect/buildSrc/src/main/resources/com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/77984A986EBC2AA786BC0F66B01FBB92821C587A.asc b/dataconnect/buildSrc/src/main/resources/com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/77984A986EBC2AA786BC0F66B01FBB92821C587A.asc new file mode 100644 index 0000000000..b899fae67b --- /dev/null +++ b/dataconnect/buildSrc/src/main/resources/com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/77984A986EBC2AA786BC0F66B01FBB92821C587A.asc @@ -0,0 +1,51 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +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 +KoZmFSI= +=Fwcs +-----END PGP PUBLIC KEY BLOCK----- diff --git a/dataconnect/buildSrc/src/main/resources/com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/7937DFD2AB06298B2293C3187D33FF9D0246406D.asc b/dataconnect/buildSrc/src/main/resources/com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/7937DFD2AB06298B2293C3187D33FF9D0246406D.asc new file mode 100644 index 0000000000..c269e5a9a4 --- /dev/null +++ b/dataconnect/buildSrc/src/main/resources/com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/7937DFD2AB06298B2293C3187D33FF9D0246406D.asc @@ -0,0 +1,74 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mQGiBEPOjg4RBAC2iPU+EHukOrmApMnhYym03gV/VbdPDydVVj+fc7TyjULlKWP7 +iEMFZb58EBjjK4Db3O5JiC9yZ7NRgYvqCFGP7hTVBTykJucXIyT8wCmaAImrtAlg +hA7LKTKlMdkPMz+iTiaVivt5F2W24Lfy/f664J5POcUH3Y5e1dhImNY6fwCg9TN8 +T2QKrFuudsbvqdw+w9I9ZD8EALBfpb39xdZenQllhq6MnODIzeHf5+wQV0slAzaO +RZCAVWodGtNx3NYomvmOuhY3iwFrTiGW7r+3CDqt/4us1vhn/y/A8JijReltOeF0 +nbTM9HzD+3MrlTZMSfR9lzpu941IDkvw67NeFdSHPxLEEcAxI+eoS5dYsdjM3isb +Ovv1A/9jJFYcPg+CRDPYVsY7ok+JW6olXfDzK0pg9zKS2gNbYq2AhyvKKjM/L1Ca +RkmpliBQZEq3QarXrirdwtR9283JKQ7K5UgigkCDqxE2C7jF4XZ6BA41Wh63GPFF +hV1J7R+LI/FZiG0gE0y+bckeSB68+wdh8jnb2JCkNkegbBU70LQvVGltb3RoeSBK +IEZvbnRhaW5lIChPRlRDKSA8dGpmb250YWluZUBvZnRjLm5ldD6IaAQTEQIAKAIb +AwYLCQgHAwIGFQgCCQoLBBYCAwECHgECF4AFAlMeUUgFCREw9rcACgkQfTP/nQJG +QG1lcgCfdK0g8n4Rwe7ng5CiGsK2aeFu3tMAn2GJN5GDVyVpxIvDMAITPIcbpJFi +iGgEExECACgCGwMGCwkIBwMCBhUIAgkKCwQWAgMBAh4BAheABQJVFZjCBQkTKD4x +AAoJEH0z/50CRkBt9DUAn3JLT9PifudI9mbufjOvNhTRjNzBAKDnPR1ceqWO3F78 +O02juhMZp82YfYhoBBMRAgAoBQJRPNokAhsDBQkPT38mBgsJCAcDAgYVCAIJCgsE +FgIDAQIeAQIXgAAKCRB9M/+dAkZAbSbgAJ4q2SMC4N9bzJwiU1fdz8evS2z5xgCg +hyS/CRrGGzlBbaQfLsY2h6CVftO0MlRpbW90aHkgSiBGb250YWluZSAoV29yaykg +PHRqLmZvbnRhaW5lQGpveWVudC5jb20+iGgEExECACgCGwMGCwkIBwMCBhUIAgkK +CwQWAgMBAh4BAheABQJTHlFIBQkRMPa3AAoJEH0z/50CRkBtgOgAoKCVOmY7DUec +ppH25/ZNtzhUZUBQAKCv6nNAXhafNJ1KEi9nPLsweUv6CohoBBMRAgAoAhsDBgsJ +CAcDAgYVCAIJCgsEFgIDAQIeAQIXgAUCVRWYwgUJEyg+MQAKCRB9M/+dAkZAbSXB +AJ9T4QxHcQiglAj6unDkpNCfQd8gAwCfd3zAVN6xtlDI119a/Gvb5H2JskeIaAQT +EQIAKAUCUTzaRgIbAwUJD09/JgYLCQgHAwIGFQgCCQoLBBYCAwECHgECF4AACgkQ +fTP/nQJGQG0vcQCfXBAfor5DHZEd0vrUAtHoZXIiRNEAoKs9sryP9dD65Uf8P0eL +W7NpB/Y5tDRUaW1vdGh5IEogRm9udGFpbmUgKFBlcnNvbmFsKSA8dGpmb250YWlu +ZUBnbWFpbC5jb20+iGsEExECACsCGwMFCQ9PfyYGCwkIBwMCBhUIAgkKCwQWAgMB +Ah4BAheABQJRPNqyAhkBAAoJEH0z/50CRkBtqIwAn2HZ88QHvZFReoqNo/saO4ax +zqxyAJ94cWr+bwMO8wbx2JHTtGxRKx+s+ohrBBMRAgArAhsDBgsJCAcDAgYVCAIJ +CgsEFgIDAQIeAQIXgAIZAQUCUx5RRQUJETD2twAKCRB9M/+dAkZAbe1gAKC/YnU4 +K3YvGN3zDZZqA45F6dGUywCcCzQs5rgW4ivr4kVTWb0JJXh6BNCIawQTEQIAKwIb +AwYLCQgHAwIGFQgCCQoLBBYCAwECHgECF4ACGQEFAlUVmL8FCRMoPjEACgkQfTP/ +nQJGQG1yCgCgrxLj45QV/4cZ8rarKJjetNdBj8gAoMxW6LSPod4qwTE2jw2Qf4wP +lXC7tEBUaW1vdGh5IEogRm9udGFpbmUgKFBlcnNvbmFsIEtleSkgPHRqZm9udGFp +bmVAYXR4Y29uc3VsdGluZy5jb20+iGQEExECACQCGwMGCwkIBwMCAxUCAwMWAgEC +HgECF4AFAk1yezwFCQ1mVBUACgkQfTP/nQJGQG2ITACg3Zd9owEpcatNeuPrWpmL +M3yORKIAn2kmpLHReRpKQ+buF/I2SUq5uLXbiGQEExECACQCGwMGCwkIBwMCAxUC +AwMWAgECHgECF4AFAlE82bQFCQ9PfyYACgkQfTP/nQJGQG0qEQCfbXfhJFcYc/7k +lu56ErfoVejcFdEAoOdUqZR1xrvo2KlJ5VjLLfYQSrjAiGQEExECACQCGwMGCwkI +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 +Uhw2iju+XrCVJ/AXUtwH +=AG5V +-----END PGP PUBLIC KEY BLOCK----- diff --git a/dataconnect/buildSrc/src/main/resources/com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/890C08DB8579162FEE0DF9DB8BEAB4DFCF555EF4.asc b/dataconnect/buildSrc/src/main/resources/com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/890C08DB8579162FEE0DF9DB8BEAB4DFCF555EF4.asc new file mode 100644 index 0000000000..acbebcb64a --- /dev/null +++ b/dataconnect/buildSrc/src/main/resources/com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/890C08DB8579162FEE0DF9DB8BEAB4DFCF555EF4.asc @@ -0,0 +1,41 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +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 +H9y08K6zB4JFN7saJiCj2Q7W2nXuEUhRVs2K +=L7AX +-----END PGP PUBLIC KEY BLOCK----- diff --git a/dataconnect/buildSrc/src/main/resources/com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/8FCCA13FEF1D0C2E91008E09770F7A9A5AE15600.asc b/dataconnect/buildSrc/src/main/resources/com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/8FCCA13FEF1D0C2E91008E09770F7A9A5AE15600.asc new file mode 100644 index 0000000000..434e770e80 --- /dev/null +++ b/dataconnect/buildSrc/src/main/resources/com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/8FCCA13FEF1D0C2E91008E09770F7A9A5AE15600.asc @@ -0,0 +1,51 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +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 +HQH6ZS1ibGU9KRlmgqU= +=bwC0 +-----END PGP PUBLIC KEY BLOCK----- diff --git a/dataconnect/buildSrc/src/main/resources/com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/93C7E9E91B49E432C2F75674B0A78B0A6C481CF6.asc b/dataconnect/buildSrc/src/main/resources/com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/93C7E9E91B49E432C2F75674B0A78B0A6C481CF6.asc new file mode 100644 index 0000000000..089b9d6202 --- /dev/null +++ b/dataconnect/buildSrc/src/main/resources/com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/93C7E9E91B49E432C2F75674B0A78B0A6C481CF6.asc @@ -0,0 +1,29 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mQENBEx8nbMBCACjmblSAGggunFHAWRGZLWLKltA2PG6rIM0bOokJFWtGwRqBCAa +dKuNwBGy7eBwFQAxuHnNwDKqyGgCKUBe4RUuz0gKr1WgkPAzHZppo/n5BhE4iIxr +lWJBKVKk/gjXVUBPhRPWNTa23JbzJHntxzHqeMQWIwIdBXoyOf0tOuXcfH2oVBdq +p8nFaeJQ906F6olUD8U12LgIhkg6/NTDLymwkau4YcOe8c2repNITVFaP6w5FmSj +aWTnyFXpdnVF4fWpbrLXsPBG72UMsAyWW4XuMtO07t4MvzgKBQALmGqlGMVtVkCI +jBgYD250NqBGKPmBrfJQVYbEesOZqNAF7bdNABEBAAG0J2lzYWFjcyAoaHR0cDov +L2Jsb2cuaXpzLm1lLykgPGlAaXpzLm1lPokBOAQTAQIAIgUCTHydswIbAwYLCQgH +AwIGFQgCCQoLBBYCAwECHgECF4AACgkQsKeLCmxIHPYyyAf8CUkbVGxWid46g9jq +hYJd4o2Au0l6Y5prhraugbMw72PUstn6VAqx77Voe4NS5J/8942HHWjtlMIn42gU +B5DS6gnGDFpo/64d3AZZ66meOE7OHcD2qo/kvziIPq77paOu4ZLF6XcY1xycCCkq +L+8zzH409o6uR/LN4APeUcCmnqugibA1vOmQcvU1Ks+X0hKEHOkjDjws8F3yUokG +ahUrtvYqJ7qNx5RQvKzvcNbhM7omZ2KXH57YyhyvSdUvfjzaFv7D2CABDlVk4xxT +GAZjmAKrKI6mvJLU36Ip9XJTH4J7iAqOe2phUSmOdZOfRrF6qFkvmNNNkH4MbDu/ +p9IPOLkBDQRMfJ2zAQgArSXiNKQXWZizpdNy/C5fWIqmczpW8MwnVY99PfB6+gnA +RxZ1GX4J1APd1rniOEOEEgWiB+QsihuMLHYuGHBhQo1zvrrmvUAZpNwz4NovcCAq +3AzMMk+VQYDYWqqkUFnct1dLblf6SuuZ/beWUk+Rju1mRQoi9heMMwjvNlqPBc0v +N8y3I8WhxCtQpWIMvQs7LN3v0s8AWNCV9VsQxmgtYpx5kMe3aYMJWEa+RzqhNvgP +FwfOUaBXQ07dBqLOhTjMomEVeBoCkBU8mrIrgqoOkucKZE5/Hlqb8d1y0AiO02bs +zCHPc5Wx5aCpq+EYWhskM8Zg3CpXhHQx3rE8oMnX7wARAQABiQEfBBgBAgAJBQJM +fJ2zAhsMAAoJELCniwpsSBz2bckH/0+0te0aioSQWEb39JYZCcBvXvvnpL/pHgPa +Z6tOizCf8/3tSRbwxI2CQ19p8ji95v+A0c1G4LwiTfZY3lEkIk0ZL1M1Ky82x/lc +6hhsIVQ3JdrCSuOUzMhgQQk+I+xPr8gx4+1GL+Ia9F44o0CP0WgPeLshmK03hmjy +ZqC+u0j/R8OQqS/kVe7qt41nmNZzwsROuKuJt1xFD2GMquL+Z4oCu4YLKM13WPkd ++ntZI9Gu0N7YDY4tKQCtcjs7z7AwVpfCesnuJtMtFb1+xuyt5DErMVN83Mi1VrUu +pYtVXNkSmiSUWMmh7QfVNFrWDqlP8vUi3BIEV3FtjXblPkbKbow= +=o1Jn +-----END PGP PUBLIC KEY BLOCK----- diff --git a/dataconnect/buildSrc/src/main/resources/com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/94AE36675C464D64BAFA68DD7434390BDBE9B9C5.asc b/dataconnect/buildSrc/src/main/resources/com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/94AE36675C464D64BAFA68DD7434390BDBE9B9C5.asc new file mode 100644 index 0000000000..806041d9f2 --- /dev/null +++ b/dataconnect/buildSrc/src/main/resources/com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/94AE36675C464D64BAFA68DD7434390BDBE9B9C5.asc @@ -0,0 +1,51 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +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 +mMJZL+1dR71q +=sm/t +-----END PGP PUBLIC KEY BLOCK----- diff --git a/dataconnect/buildSrc/src/main/resources/com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/9554F04D7259F04124DE6B476D5A82AC7E37093B.asc b/dataconnect/buildSrc/src/main/resources/com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/9554F04D7259F04124DE6B476D5A82AC7E37093B.asc new file mode 100644 index 0000000000..928a08f7ca --- /dev/null +++ b/dataconnect/buildSrc/src/main/resources/com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/9554F04D7259F04124DE6B476D5A82AC7E37093B.asc @@ -0,0 +1,30 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mQENBFTROWUBCACslqx8p2znj3CwEqWK+bEgyfxykVC1iFEABB6UCQ5UrlAiYTjI +0vwFaTlbWkD3dWBxiFN2+n24Lro549ATmXGO+i0Sacr7DTgawZdkJuM17nNmlAW1 +c1lo6D/KfQ1K7QrakloQw10LzLLW3uxi3OXVoefY1JKTeQn43jolrCHho9iNvZ0N +xyihQYzSiRzqMQGltKw5UDdbhwuzr9jROgHuUH+msOzax9IkLb8mm2VqJ3HaDJWk +oAaU92leGoicEBhR8qC3zGzua7pRFK8qhjaSSL/N/NpCu4ud6axHaBomfn5HJQNT +dTHB6R6wsVgMED9vfZHqJeyltDvA8FDw4uQnABEBAAG0OUNocmlzdG9waGVyIERp +Y2tpbnNvbiA8Y2hyaXN0b3BoZXIucy5kaWNraW5zb25AZ21haWwuY29tPokBOAQT +AQIAIgUCVNE5ZQIbAwYLCQgHAwIGFQgCCQoLBBYCAwECHgECF4AACgkQbVqCrH43 +CTvBaAf9HEU1MhfIF8qlGno0WQojtEW8hLskw0Dd4OeG3cLsSmCa72zEjEVHw82X +g862h3vfEybf7nd+mxdoSDT3iLwD8NOW6pnzW4/RjRGwvKKUhYggu8oi3xBSNj+h +GRg2dkRWpCtqJo1q0XUdh34iqJUpk2isYvsQuHJrbcglPLO+tuFFNZ0JTf1f895s +OnP1f+N1ygOnhehRoxO8gjncU3M8TXSsX8LQuCYfrBXayT5kZ49j5/+HOS8dHxYE +R9e4LpPIWpfWQtXgXZKxcpyu0x25ZO6jkQLhleXtRpCfDO3DJw7l80suGf7Vjt0P +XiYk5K2Z6x7h9rhWkEN92kTmRSbErrkBDQRU0TllAQgA1g9MDLyGI3/W+oXwrl2r +2DKvJFRALeUHyedU+oaGo47XBKvNk4UdIDeHuUvf5DzYJW6t8GH1G55ZdYGik9/M +16/6b1eYA2T7P6kjOmCGtv3KMkCLbymlFxb5SWEWwVR16L+5eXfVKbEBT7n2MGwT +c42DCJzg7APZasaJuRQDoPgQFVQ1WX+0eXimAgdYlbtVjZmrkaIZbzGIcET1lJdB +xOj+bR0pXIXbOvo5FlCVMmGAefrDePhqfuFMAQup8d4Gd7V19AcV8FyNUrVkji/a +iupi4FcBKLuF5yybJLY5d/Ij0pZ7MGUcbUhe9CbOZv9fvZBnL1Ukl9bxfchzrM2h +VwARAQABiQEfBBgBAgAJBQJU0TllAhsMAAoJEG1agqx+Nwk7vzAH/iTv3qDimLGA +9YdgSJ0h1aWZBgU2okJLLEeZQQuNHzM+ekQDhBA6Bdb938VtNUAdCSABBKUlFeq7 ++cqpcH1YgHyBv6K6avTI0F2Y1+nYIqZoe4n1QlcWg858fg4zzjBPU+s2eVIl9rOZ +kMioU0UgM53DOhz/KjCHIWeLCpZAdfXsK2+lzWLINoYJ767wTNkXWPcxj2mGtht3 +hCOcTHK5toVhlytM7vGVacn0xwOhNSNMBwFq6rucIi/fgLheNDFgLPykE48XmLau +fb3Fyii+q1BCWWZsyZjkLW7kcpf62i/YlrJHjWRheWLblLtxqy4ZiKqqLCtyfh9a +xr5u2jsokh8= +=2Ugv +-----END PGP PUBLIC KEY BLOCK----- diff --git a/dataconnect/buildSrc/src/main/resources/com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/A363A499291CBBC940DD62E41F10027AF002F8B0.asc b/dataconnect/buildSrc/src/main/resources/com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/A363A499291CBBC940DD62E41F10027AF002F8B0.asc new file mode 100644 index 0000000000..f082b653d4 --- /dev/null +++ b/dataconnect/buildSrc/src/main/resources/com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/A363A499291CBBC940DD62E41F10027AF002F8B0.asc @@ -0,0 +1,89 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +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 +wUp1P1BBxrBTTg== +=D9cV +-----END PGP PUBLIC KEY BLOCK----- diff --git a/dataconnect/buildSrc/src/main/resources/com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/A48C2BEE680E841632CD4E44F07496B3EB3C1762.asc b/dataconnect/buildSrc/src/main/resources/com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/A48C2BEE680E841632CD4E44F07496B3EB3C1762.asc new file mode 100644 index 0000000000..2c711558fc --- /dev/null +++ b/dataconnect/buildSrc/src/main/resources/com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/A48C2BEE680E841632CD4E44F07496B3EB3C1762.asc @@ -0,0 +1,51 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mQINBFhJehkBEADNX8qrO9msK8u1znGaBG+Fr0FS5qzMxpC9oStGZV8abX/rrLkN +NiImTUMQG6Ooz7luOBSSF7VbYT2xSrgzuYV8WrSJE4Oo0AjVNn2YnbwgyzcQ5FhY +oDcfthDk1HALwXPxjyxvWA2RYf9NZj5OsT2j7nzVoRrQZRtDDB4l2dyEObZ4Am5R +MRdmAApgHYB1vdTqseHIQjB14V5RcuEzZaE6qez/vJuCHWQby5y2c7JhbY4Hj7gw +MCkB4I7ToHX0PSIpDOAqBXb1OZiI834Y+Q9pP/HF5ormpcK1hccoARQWXano7AQb +fiQnSYh6kr6HJbzNR/YVxIHdW+yuxrZheX39EerKejndgUx7RrHmRm9K+7ostMEM +Kq++K3HYLdMag6loazn6qdX6DWgNhSJdsblJUOoqCnPQBmZMGtxpZqh3Oet3tlWm +xF/ksmc5NmvcdZUtSndqSYa4xsegHl34mkefw4s8GmkIP6d3eqNpunC7Kh0FzkgP +kWLENpusSYpF0qVtJ40uAgW7U42d5AIf9kc3zFn/yF3c/RHy5NjE4x/fAq6iB0s0 +EFnLXW8397YJY1PvHnvQzTQOWSkzwwyym85kjU22UWn7vKl/NlYmol6RydlCZvW0 +K0C7qg5AgWp/TkJGiX+6Wj0jVvswn0LelEIgb+yBiBjb4mEYLwgA5zadkwARAQAB +tChSdWJlbiBCcmlkZ2V3YXRlciA8cnViZW5AYnJpZGdld2F0ZXIuZGU+iQI9BBMB +CAAnBQJY27LjAhsjBQkJZgGABQsJCAcCBhUICQoLAgQWAgMBAh4BAheAAAoJEPB0 +lrPrPBdijzcP/iNelF4LLAJrz9sCfe8hAm0lk4cg3AM3XBuR/k1M4LsJQNd0iUb0 +TQNQeVcuFpZ7WANKgTtNXlU1C9u2QYNSikn+NJwglc0g6ukL80CKu5tdpOTCRlFj +VaHacRyg5tE3tjo03E6YHPS/bfmfOOVCccJfo6w9S9zncmxM2qxYZTjWA/3QLDwd +BMS7jIIBcM9fZTqANIGhjzctpqHfGoB+wZisx3UxyGu3OIa5nswP5alqIOyajwLn +Jirzmc9/+CEf85ap33gul6bEX68nEVaDUo9ft531z02tlNrFfX4YaT+um8t1oBdx +kYq2qB0vvxme/q2Lzy5JIzLKkSaimHyXzahI3YhflQUUatVsfZBSnclpNrPvKxLN +UI1GVkODrbR9ps9JAHo+IRhFk+HoC28CpvfEsOhzB45xwbEgzprQVyoRJQ2KGnOl +YoTYr9L+o4/dxQVCCQganVQW1FyCKcAip5dfCwHQUBmMlhkc/QdeZotEBN2mKOzE ++hUPkiWIiQMFJ1x2AXcOjjnEIOJ3zHFmnQ134K17P+Alkwwp4V9hCqoLg2tG5wlF +aUTAThqDZNdxI/VTl4SK+YIYoUZfKLohfSk5eOx3g2d0SYjlMf/6pR29XOez7Nee +SGzuYv58ireisaWNeYbCCYU84KTVatsyh/4JD0bJG788nbLYDqB0yk6PuQINBFhJ +ehkBEADDZLZSbGS2Y8FCL9v1SwDcTfZnJzx0Qau+HX4Are3/lVipHFDSg33lrtjs +sKkNrQNnBp6IV8udSvh17XnitH/oV4DA72MdFWBlxoZ74Lo3V3+n0KJdBOAmn9Bd +pPq+8kdBIH3tCKHuEWEw7XtoyYlu1FZ/bsR74x4TcDi7UG8nmiYALZ2hOfnSnUp8 +ax2ZzzUZUzh72qUEWa1eSp9p4rLTJMnwygcSxJcS1Jv97D4fq03mW9Zxen4wuXQ/ +386Ec0Yc8LGmv461PbPOjtuV1vEeOTWWSTx8k0Qsch/TJXxKLbcVxOfQ0sZyOq/i +osWrMFSa6+JOtZYZIEXtPxdDzvYZFcc1euzdhVTN+tfYulWmBjE92ILdM2Rcgl4R +1r4c+9C/Q8DbNXf/4ZiSxoL7rGWhIwCp0nD5mNOTQyY2v+PTlqIrg52ZsS/t0pwy +v39KLiZICUJFHuE4I2qLonriLxnZrgtYfPyqDQmtNidoqG4++nsFX4SNoSxUXaAw +PLxnxfwMDdrmzIHquQP4OHIQCOwQx1PNwo1+XmMVD2v/IXjDUP9yeAFYC3Evf3yE +uq8ldbHxKHsS2aoOlEWniWpRIBVtJnqgCbtbP80p0itCse1SkzYZfsHCO9XqBOrS +e63R5eprdFO1ixA60k00xYVVB1pFPmUYzeoUXEmNNVzeu0sRiwARAQABiQIlBBgB +CAAPBQJYSXoZAhsMBQkJZgGAAAoJEPB0lrPrPBdiO+MP+wbMbkkDN5D9gjz4Dnty +ZW4P/47oGLuOiH8SdIui+XlPt8eWSie6iYZPEiS9jrenb/qism0ejpFuOk7wiLCI +YnM8G8C42y5FfFSVVQzKHJ4SyCovpkB8ENcxAdvpbtcX4e5ASwrGFUWdeZ+sErEy +KPe9TqDpjgHLqzFENSiR7hXHm6+BGslMRrn9VnrKQPIQeN9VB0YZIP5fPcsMvxm0 +o8Cw9FkMXcZqrJzNH6wX1HIqO6GD8aT+dUPKupKmzDyG3YX1SJ3IyCXNtpnB15jV +Kzd8hM1cguuihc248pUA73uqn5pD6zZJzpIfZIr0CUhVyevJTiRzqLcuK9dizMI/ +bgYPpYuFmUlm/D/AciG2vTNGJ/yit9Yk0+7NIt979okP5aDP4fleLcGydbZOzOmI +s4PDe1TmA59jphVDnNqZ52M9XWPYIT4xo4HI1WEGh4pQ+gU6n4W6mh9unSrgdvjO +U1PLlzRL7h/ZhkUkLP30vgHco35muGBMW6/Ni/RQwxblR1TdpZ2RwvDr5t2u5HBU +npqSZaj8YOWI+DPDYGYzqQwFgyGLH0J32l5zzpcggwhEoDsQSMHKxekHD9bX0Bck +4gpJCw5QwoZHeeSczEFZq6JGvWM6zIM8JKg4gGwIcZcJse/s9+H3+WvrwYFk1Nto +fyCIa9sx6lcYB+gDJgESL0Nt +=hSfG +-----END PGP PUBLIC KEY BLOCK----- diff --git a/dataconnect/buildSrc/src/main/resources/com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/B9AE9905FFD7803F25714661B63B535A4C206CA9.asc b/dataconnect/buildSrc/src/main/resources/com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/B9AE9905FFD7803F25714661B63B535A4C206CA9.asc new file mode 100644 index 0000000000..c2b6d06343 --- /dev/null +++ b/dataconnect/buildSrc/src/main/resources/com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/B9AE9905FFD7803F25714661B63B535A4C206CA9.asc @@ -0,0 +1,75 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +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 +=CMP0 +-----END PGP PUBLIC KEY BLOCK----- diff --git a/dataconnect/buildSrc/src/main/resources/com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/B9E2F5981AA6E0CD28160D9FF13993A75599653C.asc b/dataconnect/buildSrc/src/main/resources/com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/B9E2F5981AA6E0CD28160D9FF13993A75599653C.asc new file mode 100644 index 0000000000..8ece24d18f --- /dev/null +++ b/dataconnect/buildSrc/src/main/resources/com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/B9E2F5981AA6E0CD28160D9FF13993A75599653C.asc @@ -0,0 +1,51 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +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 +1j7Q6fERAXlFlv7ciEuKBz5A3SK7Xt7Eb7vQ +=kARQ +-----END PGP PUBLIC KEY BLOCK----- diff --git a/dataconnect/buildSrc/src/main/resources/com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/C0D6248439F1D5604AAFFB4021D900FFDB233756.asc b/dataconnect/buildSrc/src/main/resources/com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/C0D6248439F1D5604AAFFB4021D900FFDB233756.asc new file mode 100644 index 0000000000..67211b7b86 --- /dev/null +++ b/dataconnect/buildSrc/src/main/resources/com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/C0D6248439F1D5604AAFFB4021D900FFDB233756.asc @@ -0,0 +1,52 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +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 +Th80Z8v5rFX5xNw+zn0Ee+yr +=SZFY +-----END PGP PUBLIC KEY BLOCK----- diff --git a/dataconnect/buildSrc/src/main/resources/com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/C4F0DFFF4E8C1A8236409D08E73BC641CC11F4C8.asc b/dataconnect/buildSrc/src/main/resources/com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/C4F0DFFF4E8C1A8236409D08E73BC641CC11F4C8.asc new file mode 100644 index 0000000000..2baa8ea5ef --- /dev/null +++ b/dataconnect/buildSrc/src/main/resources/com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/C4F0DFFF4E8C1A8236409D08E73BC641CC11F4C8.asc @@ -0,0 +1,228 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +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 +ftyH6K7HWwkSmrXf0//7kPEF7+P0LvSX4Ri/X2zfa+GgkfmIK66n8Vl/+YXSZxr8 +3Yf06k16Nl0loYZqYYkhQvdxKIyrXKx4zLiIU3QOKWFUy5EvAFwg++i5s8YvSvxb +72p5tlhZ7C2I5/zzZG6+VPFEa4kCOQQTAQgAIwUCWJ4DrwIbAwcLCQgHAwIBBhUI +AgkKCwQWAgMBAh4BAheAAAoJEOc7xkHMEfTI3RUQAKagnz10ZI0WUXYbQlZ09eUG +zDzxA8gpsMAoEb31+vo3bwFJ8hlErSkWe/WsHoaF7y0BiqWlUYibdpKrdmNnivbs +j5TTb5MEndP/kKYsfKQZtbNZwP5ITU3lKVEPHCBZbDYQWKPdQBSq/6qvQ1D1/lSr +6VEhML0p/XbFy9st22jMqTWiSEu6xHyOf7t6+niLcLuBo/edR/+jOEF8qizGYZaU +R7eFtRkWD5I8y8sPWtjjiRmbnV/1YcJNfZKDNu4ReVzXh9iJBt3iC8HY1nUFo2ic +2bZ31odwpGLrfebHtL63jyu7BW1GrlQK7NwrQbgFg2ePpNCv62Pzz+LUkXl4UH2Q +KFRBhJ+MMmq9Tx4IIAUrcOx/wc292S8+VOoH404Scp8y13dsW4v1YmvbO7zobomF +eTA1TZ371q7OqumxtCl2B8gnAFxzBPH7W6O3YHSs2zEpwtd3r20VS/WZ2owl8+HL +1hGg1AY+LCVBsX4BiBF/4e4qr/6BghvScximLbIThS9jLJCJs0jphhPKudS7dMAQ +veGSlWJUJ1stLqoWkmQuo0ndArLtnHQF+pFRtNcaIqmn2ZOQM+GD91PxIlMmR/hw +dKzVf6b5ncwvfS2di0ySl8v50SgBvZTbLm/C2Qh5VcB9NolzVHqICtoVO/fD4I3P +gS52Asdx6Deeq188yoXUiQJQBBMBCAA6AhsDBwsJCAcDAgEGFQgCCQoLBBYCAwEC +HgECF4AWIQTE8N//TowagjZAnQjnO8ZBzBH0yAUCW/Q5HwAKCRDnO8ZBzBH0yPtu +EACUVtMYUeB8NTZC5e4ceP8D0fciwxvcXKngRSm/BItY/Hi4gWn01Us5cG/7hh6y +nW16f2DLWMTQL8EIcUZ7kiKvBCJc/v6X/4XCPGJthGrsiFzPiaFUv2qzhSooQkGB +72Qi1WhdvLn1ocSkUDWbFeEJCnGxH5bVBSntDu0nVU8DwVTB/NQ8V2HZDl4e/mWo +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 +4G5Uhcw= +=YIVB +-----END PGP PUBLIC KEY BLOCK----- diff --git a/dataconnect/buildSrc/src/main/resources/com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/C82FA3AE1CBEDC6BE46B9360C43CEC45C17AB93C.asc b/dataconnect/buildSrc/src/main/resources/com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/C82FA3AE1CBEDC6BE46B9360C43CEC45C17AB93C.asc new file mode 100644 index 0000000000..5a6eb622b2 --- /dev/null +++ b/dataconnect/buildSrc/src/main/resources/com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/C82FA3AE1CBEDC6BE46B9360C43CEC45C17AB93C.asc @@ -0,0 +1,76 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +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 +=pqm4 +-----END PGP PUBLIC KEY BLOCK----- diff --git a/dataconnect/buildSrc/src/main/resources/com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/CC68F5A3106FF448322E48ED27F5E38D5B0A215F.asc b/dataconnect/buildSrc/src/main/resources/com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/CC68F5A3106FF448322E48ED27F5E38D5B0A215F.asc new file mode 100644 index 0000000000..0ea9c21cf2 --- /dev/null +++ b/dataconnect/buildSrc/src/main/resources/com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/CC68F5A3106FF448322E48ED27F5E38D5B0A215F.asc @@ -0,0 +1,52 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mQINBGYFoj0BEACm4UKYcykICb5oxZQQxSZRYwzkSngpeFcrruHVHfg2jcQ+VmRV +C3NrbhSrBQuJ0pMx/zq/yZB6K4JS+EMf5GpaX2ZsVsj/MPoSKVHcyXR4clIulCxN +rUyKYS78awl3bE+dwf9U+IY2fMoMVLwNL8kT2Yr28dI2u47bOPRqxDTxJ8VkRMR2 +4Nv8VbFn2kZVm4u/ZE4lVlAr82vuM8dOdo+RA6OTfnJRBtuwp8YmSLQnoE+BeR+i +LgbmqOFSqAsQ4z5tl6PlwUMQn7k/GiYfGGKzgpZ9eq265xu7u7f2cXk3SAnxf2Tm +v3JLsdLd3wbxGOSAd9Ciy+VNmhW06khd9JGriVyslapSNu0ZdH4RepPqjTEItHLE +dnUwlcmJGKnbE3n7Q6mTez2pMtNYNAeA4LK26qHkHqkgAlkgIZKG3SMlD9wc3FKf +SpMdEQw9RqAZivO0CoiFRC+VknRVFy4N/F0nrvC4uHDEomIueJswN2r0LWhMDmlV +j0CGfDQ1SDeU0QpVtuQ5wjpp3UumLtj+uzfU6Y01mrtxH2hNbXKWiYFlDSH17wra +zYGyEWDnz7owLbxEN1c7sQgHVTVgFzQs/zRjS27HE3bWK2O+vXWD+mceXMHUL0om +04V2TFig5GaGPr2GSD4eY5Em4G2FzmwPGhjB+nP8nHmsQLAuMUhIR47yNwARAQAB +tCptYXJjby1pcHBvbGl0byA8bWFyY29pcHBvbGl0bzU0QGdtYWlsLmNvbT6JAlcE +EwEIAEEWIQTMaPWjEG/0SDIuSO0n9eONWwohXwUCZgWiPQIbAwUJEswDAAULCQgH +AgIiAgYVCgkICwIEFgIDAQIeBwIXgAAKCRAn9eONWwohX7lMD/4yCMpJqxGKaSsc +5hDbr5ua0uQRnziFBUPz/RFF6RmSDDCZ+Guck2A+8d7WHh/bmXhz9sUIRp04oLpn +sJAkbbdNJaePmRxEOoi1Z1yUhLlfpq9ZB1Y8z9Hgsk4fzbBcpvyGrfpmuRx1B8F5 +4QO02VGPp+i/Jek+PPCpyXSSFVVe41ROHeAFoAdAsk/Pn2K/xP8sFWf2yZJDxauE +NUP67aK7q84N4iC93ioVaW/tdVGdOKKwSCo1jxEnWqMCHe44/BMzDzjNzcGNFNNS +2Wp5x1Bzmsj4SDHjWpfgfNzOuWzbdH0H52KiQW5I/TDw/WnAMDAm/ECe1V0n/+ON +gmCMCf/iRmYlLWRf27aGK5OlH+cpF/fsuWe14QvSbLKgO6d3nZ3kJ6bdrkeI0+eM +snPVMtc9Sfo1Begl4XMOMLXoGA5Q0tZpCue+o7HfJ2hEtYQVL3G2yIgWwLcw//Zt +/N9sYOJAGvMi2GQubqTryBloskV/DAT8cH3ttokOWF/EZarWkJmtOMpGIT+tpnyt +YnpV/R6sAqT0whqxo4A4Me8ncFIhbviJNBdy/hi0qJxHvVDURGHaMFCcYGzzfjlH +/nXOGCfInzEmmaLUkyqoM+mcLOvWBacprlXpm8Vd+OprgljeBI6JyCZYnIJycNQq +U2drHjHrgFl2JEmvHwCzuP0Vqr/GxbkCDQRmBaI9ARAA4jotNS9OKK8tT3ORqpqE +Ns4j1MMHQW9tJ9K2M3rqLLsUx72MN3NIEzidEzGyr7HKdBQ88XC25TRqtKhljUFp +3m3sw7jauZcTCHF/vaW5Vkfix9qL5BDiqQ7T54o05nmCxXBWKDa64JFA0GcR5xZe +YORi/EujoeU2xWaXZQBuU0RLItraGJnIIUCmsPxrSde0EBTpjNJ0zEKqdUWwx2JD +5sxs2Ln1olJFA4hKCuGnYhjojQxapB7HKanmqMJD6mvQVCjUmw1FNaNDLfFq/hx9 +yF2vTNZ1BzUvQfYBqKswnD6/Q+ButpjaDyGP8w2+NVWCQlPxVXiHcOBDNh8JSySM +ESHi5vltXujKZAkr+q67OZrKpa53Mtw0PAEuM5wl+Dv2Ut3Z3mWIa+8h8AO/SXnA +RXzzsM6M8sHMiF12IIzxAtfve8eINor+gEhB9LJdobOq+o8tu6la9UOotY+dJPL1 +py6SuZBbOSpJRio7PwuDz478PbbfyD3HSiRcv9UWCUgpdfiPNLJz8NCCYwwM0CwN +lKOvc0pEBQxufHOMDxEWv7RxOdxWiODwLZrlyE6eLh5BaxM/AMwOWJH5ReGaAA0V +DRjXHRm+vOCXLENeH+ZlTqnKIHHmKDPJdybzllsxaFp02+c6sf7Gj9BPdA0rzJrd +prnboL8oBr8HJzvDw6m02kMAEQEAAYkCPAQYAQgAJhYhBMxo9aMQb/RIMi5I7Sf1 +441bCiFfBQJmBaI9AhsMBQkSzAMAAAoJECf1441bCiFfXugP/1iYMqMM7MRifFe/ +HIwhBmUGBOXvRrOdYEnoQOQM5CV+ro1mgVLr3alHo6xc5ZYwINq3AvfS0XTLG0z1 +g7zQpictpK4mo2sTRujeJpf6TPgJ7aI9+fYDnfq+SmhDgKIlR20NUxLMgK8u2eBc +EF8gqqGBldHV6b6TbDBZGW6xAVGXe49NLd8Q1rHPCUVA4SsDF0Wgn9gaiarMqlmO +tcrsalTvGrbsDzyHY8p+OktYeJPCVy0iaiT5RTwkGjyhInSzH0Qyb91aYXKJdH74 +c6BPFjoXeEM/n/pH3cu5h4x3m+8Z7X5l9/UrV9kBM5TxwinTwGUuQDLes0mjwspU +c3kgPGgPGzRp5+wTcRfiF00luEFUxRtBLCId6PKSH3ZhDjRA25M0Yp4qP81wgu6S +qlbB+goIZtbEAJeIxWNerMVeC1FobuFa9S6t9PAUlvX7mMlBAMDOv6czFkrl7rSj +yQw4lcYv23z/o42yFG+EcnEQ3l7K3j1qmkFDiEfopbQVBNpE69stjpO1sQV4fVYr +eu0agvd1+yKZrcoEo+npXyzXPckRsHchS6pbhck1vFtgKwXpPCjSC0e6IwrDgAGw +0smdXeIIwNoMVaY3oksWA8DdRdNCaHqYalW+8LytiOOcBvgFCMcUsr0NcLstWwyi +ZWf0a3VP6Gco5bmDPhvGoLEs9Vw5 +=57kS +-----END PGP PUBLIC KEY BLOCK----- diff --git a/dataconnect/buildSrc/src/main/resources/com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/DD792F5973C6DE52C432CBDAC77ABFA00DDBF2B7.asc b/dataconnect/buildSrc/src/main/resources/com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/DD792F5973C6DE52C432CBDAC77ABFA00DDBF2B7.asc new file mode 100644 index 0000000000..1e3f56f74b --- /dev/null +++ b/dataconnect/buildSrc/src/main/resources/com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/DD792F5973C6DE52C432CBDAC77ABFA00DDBF2B7.asc @@ -0,0 +1,13 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mDMEZAFttBYJKwYBBAHaRw8BAQdA9UUQNclFp0rIrgtQnNw6BgjDINkFPoVbuS4H +sQNEf+e0LEp1YW4gSm9zw6kgQXJib2xlZGEgPHNveWp1YW5hcmJvbEBnbWFpbC5j +b20+iJMEExYKADsWIQTdeS9Zc8beUsQyy9rHer+gDdvytwUCZAFttAIbAwULCQgH +AgIiAgYVCgkICwIEFgIDAQIeBwIXgAAKCRDHer+gDdvyt3PYAP9A8XbvZYT+N7k9 +2xnRMfrAUep6TLfWbx9uf7k/hm/+KAEAmqUYV1afcuwU2xXE5I8m25VMnCEKFFEF +52tW2baWkQW4OARkAW20EgorBgEEAZdVAQUBAQdAtwscgbxby3vew2hn9F+KlVPL +vFBPjnvODcnsqlO2mXQDAQgHiHgEGBYKACAWIQTdeS9Zc8beUsQyy9rHer+gDdvy +twUCZAFttAIbDAAKCRDHer+gDdvytzxaAQDvYX4o1Y6R30bYwIXemGgbO8GlCkgk +5it7MjeSk8vUygEApE5zIi5OtP8TlPiMgu2MoJbIltqqDCKWTtHPIQRNIwk= +=IisY +-----END PGP PUBLIC KEY BLOCK----- diff --git a/dataconnect/buildSrc/src/main/resources/com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/DD8F2338BAE7501E3DD5AC78C273792F7D83545D.asc b/dataconnect/buildSrc/src/main/resources/com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/DD8F2338BAE7501E3DD5AC78C273792F7D83545D.asc new file mode 100644 index 0000000000..50e3ec9657 --- /dev/null +++ b/dataconnect/buildSrc/src/main/resources/com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/DD8F2338BAE7501E3DD5AC78C273792F7D83545D.asc @@ -0,0 +1,36 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +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 +8FLoLzom0A1FNxCvgzOraMITOobs +=dTMc +-----END PGP PUBLIC KEY BLOCK----- diff --git a/dataconnect/buildSrc/src/main/resources/com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/FD3A5288F042B6850C66B31F09FE44734EB7990E.asc b/dataconnect/buildSrc/src/main/resources/com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/FD3A5288F042B6850C66B31F09FE44734EB7990E.asc new file mode 100644 index 0000000000..c8d0bd579a --- /dev/null +++ b/dataconnect/buildSrc/src/main/resources/com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/FD3A5288F042B6850C66B31F09FE44734EB7990E.asc @@ -0,0 +1,111 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +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 +=NfOh +-----END PGP PUBLIC KEY BLOCK----- diff --git a/dataconnect/buildSrc/src/main/resources/com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/README.md b/dataconnect/buildSrc/src/main/resources/com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/README.md new file mode 100644 index 0000000000..0b106ce3a6 --- /dev/null +++ b/dataconnect/buildSrc/src/main/resources/com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/README.md @@ -0,0 +1,8 @@ +This directory contains the Node.js release signing certificates. + +At the time of writing, these were downloaded from +https://github.com/nodejs/release-keys/tree/main/keys +on Wed Nov 20, 2024 at commit 604322f38d5cc74bc7552af75f1d3b4d6e8825ac. + +These keys are used to verify the authenticity of the Node.js binary +distributions downloaded by the plugin. From 3ee1118c15d6c198f3d588702665285b1003ce93 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Wed, 20 Nov 2024 20:46:08 +0000 Subject: [PATCH 23/79] verify downloaded nodejs signatures --- dataconnect/buildSrc/build.gradle.kts | 2 + .../gradle/tasks/DownloadNodeJSTask.kt | 78 +++++++++++++++++-- .../nodejs_release_signing_keys/keys.list | 27 +++++++ 3 files changed, 100 insertions(+), 7 deletions(-) create mode 100644 dataconnect/buildSrc/src/main/resources/com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/keys.list diff --git a/dataconnect/buildSrc/build.gradle.kts b/dataconnect/buildSrc/build.gradle.kts index a206a9df1e..536af14ac6 100644 --- a/dataconnect/buildSrc/build.gradle.kts +++ b/dataconnect/buildSrc/build.gradle.kts @@ -38,6 +38,8 @@ dependencies { implementation("io.ktor:ktor-client-core:$ktorVersion") implementation("io.ktor:ktor-client-cio:$ktorVersion") implementation("io.ktor:ktor-client-logging:$ktorVersion") + + implementation("org.pgpainless:pgpainless-sop:1.7.2") } gradlePlugin { diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJSTask.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJSTask.kt index 08ad44f7c8..6392fdcac5 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJSTask.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJSTask.kt @@ -41,6 +41,7 @@ import org.gradle.api.tasks.Nested import org.gradle.api.tasks.OutputDirectory import org.gradle.api.tasks.TaskAction import org.gradle.kotlin.dsl.newInstance +import org.pgpainless.sop.SOPImpl import java.io.File import java.text.NumberFormat @@ -196,18 +197,21 @@ private fun Task.downloadOfficialVersion(source: DownloadOfficialVersion, output } } + val nodejsBinaryDistributionFile = File(outputDirectory, source.downloadFileName) + val shasumsFile = File(outputDirectory, shasumsFileName) + httpClient.use { runBlocking { - val url = source.downloadUrl - val destFile = File(outputDirectory, source.downloadFileName) - downloadFile(httpClient, url, destFile, maxNumDownloadBytes = 200_000_000L) + val url = source.shasumsDownloadUrl + downloadFile(httpClient, url, shasumsFile, maxNumDownloadBytes = 100_000L) } runBlocking { - val url = source.shasumsDownloadUrl - val destFile = File(outputDirectory, shasumsFileName) - downloadFile(httpClient, url, destFile, maxNumDownloadBytes = 100_000L) + val url = source.downloadUrl + downloadFile(httpClient, url, nodejsBinaryDistributionFile, maxNumDownloadBytes = 200_000_000L) } } + + verifyNodeJSShaSumsSignature(shasumsFile) } private suspend fun Task.downloadFile(httpClient: HttpClient, url: String, destFile: File, maxNumDownloadBytes: Long) { @@ -233,4 +237,64 @@ private suspend fun Task.downloadFile(httpClient: HttpClient, url: String, destF } logger.info("Successfully downloaded {} to {} ({} bytes)", url, destFile.absolutePath, actualNumBytesDownloadedStr) -} \ No newline at end of file +} + +private fun Task.verifyNodeJSShaSumsSignature(file: File): ByteArray { + logger.info("Verifying that ${file.absolutePath} has a valid signature " + + "from the node.js release signing keys") + + val keysListPath = "com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/keys.list" + val keyNames: List = String(loadResource(keysListPath)).lines().map{it.trim()}.filter { it.isNotBlank() } + logger.info("Loaded the names of ${keyNames.size} keys from resource: $keysListPath") + + val sop = SOPImpl() + val inlineVerify = sop.inlineVerify() + keyNames.forEach { keyName -> + val certificateBytes = loadResource("com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/$keyName.asc") + inlineVerify.cert(certificateBytes) + } + logger.info("Loading {} to verify its signature", file.absolutePath) + val verificationResult = file.inputStream().use { inputStream -> + inlineVerify.data(inputStream).toByteArrayAndResult() + } + logger.info("Signature of {} successfully verified", file.absolutePath) + + return verificationResult.bytes +} + +private fun Task.loadResource(path: String): ByteArray { + val classLoader = this::class.java.classLoader + logger.info("Loading resource: {}", path) + return classLoader.getResourceAsStream(path).let { unmanagedInputStream -> + unmanagedInputStream.use { inputStream -> + if (inputStream === null) { + throw GradleException("resource not found: $path (error code 6ygyz2dj2n)") + } + inputStream.readAllBytes() + } + } +} + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dataconnect/buildSrc/src/main/resources/com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/keys.list b/dataconnect/buildSrc/src/main/resources/com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/keys.list new file mode 100644 index 0000000000..acaed558a5 --- /dev/null +++ b/dataconnect/buildSrc/src/main/resources/com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/keys.list @@ -0,0 +1,27 @@ +4ED778F539E3634C779C87C6D7062848A1AB005C +94AE36675C464D64BAFA68DD7434390BDBE9B9C5 +1C050899334244A8AF75E53792EF661D867B9DFA +B9AE9905FFD7803F25714661B63B535A4C206CA9 +77984A986EBC2AA786BC0F66B01FBB92821C587A +71DCFD284A79C3B38668286BC97EC7A07EDE3FC1 +61FC681DFB92A079F1685E77973F295594EC4689 +FD3A5288F042B6850C66B31F09FE44734EB7990E +8FCCA13FEF1D0C2E91008E09770F7A9A5AE15600 +C4F0DFFF4E8C1A8236409D08E73BC641CC11F4C8 +890C08DB8579162FEE0DF9DB8BEAB4DFCF555EF4 +C82FA3AE1CBEDC6BE46B9360C43CEC45C17AB93C +DD8F2338BAE7501E3DD5AC78C273792F7D83545D +A48C2BEE680E841632CD4E44F07496B3EB3C1762 +B9E2F5981AA6E0CD28160D9FF13993A75599653C +108F52B48DB57BB0CC439B2997B01419BD92F80A +9554F04D7259F04124DE6B476D5A82AC7E37093B +93C7E9E91B49E432C2F75674B0A78B0A6C481CF6 +56730D5401028683275BD23C23EFEFE93C4CFFFE +114F43EE0176B71C7BC219DD50A3051F888C628D +7937DFD2AB06298B2293C3187D33FF9D0246406D +74F12602B6F1C4E913FAA37AD3A89613643B6201 +141F07595B7B3FFE74309A937405533BE57C7D57 +DD792F5973C6DE52C432CBDAC77ABFA00DDBF2B7 +A363A499291CBBC940DD62E41F10027AF002F8B0 +CC68F5A3106FF448322E48ED27F5E38D5B0A215F +C0D6248439F1D5604AAFFB4021D900FFDB233756 From 608dd81bf9b77ca41c66eb29da691cc5ed3259e5 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Wed, 20 Nov 2024 20:51:19 +0000 Subject: [PATCH 24/79] minor refactor --- .../gradle/tasks/DownloadNodeJSTask.kt | 21 +++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJSTask.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJSTask.kt index 6392fdcac5..3efbdcca84 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJSTask.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJSTask.kt @@ -173,11 +173,21 @@ internal val DownloadOfficialVersion.downloadFileName: String return "node-v$nodeVersion-$osType-$osArch.$fileExtension" } +private data class DownloadedNodeJsFiles( + val binaryDistribution: File, + val shasums: File, +) + private fun Task.downloadOfficialVersion(source: DownloadOfficialVersion, outputDirectory: File) { + val downloadedFiles = downloadNodeJsBinaryDistribution(source, outputDirectory) + verifyNodeJSShaSumsSignature(downloadedFiles.shasums) +} + +private fun Task.downloadNodeJsBinaryDistribution(source: DownloadOfficialVersion, outputDirectory: File): DownloadedNodeJsFiles { val httpClient = HttpClient(CIO) { expectSuccess = true install(Logging) { - val gradleLogger = this@downloadOfficialVersion.logger + val gradleLogger = this@downloadNodeJsBinaryDistribution.logger level = if (gradleLogger.isDebugEnabled) { LogLevel.HEADERS @@ -197,7 +207,7 @@ private fun Task.downloadOfficialVersion(source: DownloadOfficialVersion, output } } - val nodejsBinaryDistributionFile = File(outputDirectory, source.downloadFileName) + val binaryDistributionFile = File(outputDirectory, source.downloadFileName) val shasumsFile = File(outputDirectory, shasumsFileName) httpClient.use { @@ -207,11 +217,14 @@ private fun Task.downloadOfficialVersion(source: DownloadOfficialVersion, output } runBlocking { val url = source.downloadUrl - downloadFile(httpClient, url, nodejsBinaryDistributionFile, maxNumDownloadBytes = 200_000_000L) + downloadFile(httpClient, url, binaryDistributionFile, maxNumDownloadBytes = 200_000_000L) } } - verifyNodeJSShaSumsSignature(shasumsFile) + return DownloadedNodeJsFiles( + binaryDistribution = binaryDistributionFile, + shasums = shasumsFile, + ) } private suspend fun Task.downloadFile(httpClient: HttpClient, url: String, destFile: File, maxNumDownloadBytes: Long) { From 54b6d8bcd1001f23dbe2a671fc9e58cac4c39fe8 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Wed, 20 Nov 2024 21:39:18 +0000 Subject: [PATCH 25/79] verify the sha256 hash of the nodejs binary distribution --- dataconnect/buildSrc/build.gradle.kts | 1 + .../gradle/tasks/DownloadNodeJSTask.kt | 74 +++++++++++++++++-- 2 files changed, 68 insertions(+), 7 deletions(-) diff --git a/dataconnect/buildSrc/build.gradle.kts b/dataconnect/buildSrc/build.gradle.kts index 536af14ac6..9df689c181 100644 --- a/dataconnect/buildSrc/build.gradle.kts +++ b/dataconnect/buildSrc/build.gradle.kts @@ -40,6 +40,7 @@ dependencies { implementation("io.ktor:ktor-client-logging:$ktorVersion") implementation("org.pgpainless:pgpainless-sop:1.7.2") + implementation("org.bouncycastle:bcprov-jdk18on:1.78.1") } gradlePlugin { diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJSTask.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJSTask.kt index 3efbdcca84..dff124d08a 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJSTask.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJSTask.kt @@ -15,7 +15,6 @@ */ package com.google.firebase.example.dataconnect.gradle.tasks - import com.google.firebase.example.dataconnect.gradle.providers.MyProjectProviders import com.google.firebase.example.dataconnect.gradle.providers.OperatingSystem import com.google.firebase.example.dataconnect.gradle.tasks.DownloadNodeJSTask.Source @@ -30,6 +29,7 @@ import io.ktor.client.request.prepareGet import io.ktor.utils.io.ByteReadChannel import io.ktor.utils.io.jvm.javaio.copyTo import kotlinx.coroutines.runBlocking +import org.bouncycastle.util.encoders.Hex import org.gradle.api.DefaultTask import org.gradle.api.GradleException import org.gradle.api.Task @@ -43,6 +43,7 @@ import org.gradle.api.tasks.TaskAction import org.gradle.kotlin.dsl.newInstance import org.pgpainless.sop.SOPImpl import java.io.File +import java.security.MessageDigest import java.text.NumberFormat abstract class DownloadNodeJSTask : DefaultTask() { @@ -180,10 +181,66 @@ private data class DownloadedNodeJsFiles( private fun Task.downloadOfficialVersion(source: DownloadOfficialVersion, outputDirectory: File) { val downloadedFiles = downloadNodeJsBinaryDistribution(source, outputDirectory) - verifyNodeJSShaSumsSignature(downloadedFiles.shasums) + val shasums = verifyNodeJSShaSumsSignature(downloadedFiles.shasums) + val expectedSha256Digest = + getExpectedSha256DigestFromShasumsFile(downloadedFiles.shasums.absolutePath, shasums, source.downloadFileName) + verifySha256Digest(downloadedFiles.binaryDistribution, expectedSha256Digest) } -private fun Task.downloadNodeJsBinaryDistribution(source: DownloadOfficialVersion, outputDirectory: File): DownloadedNodeJsFiles { +private fun Task.verifySha256Digest(file: File, expectedSha256Digest: String) { + val actualSha256Digest = file.inputStream().use { inputStream -> + val messageDigest = MessageDigest.getInstance("SHA-256") + val buffer = ByteArray(8192) + while (true) { + val readCount = inputStream.read(buffer) + if (readCount < 0) { + break + } + messageDigest.update(buffer, 0, readCount) + } + val digest = messageDigest.digest() + Hex.toHexString(digest) + } + + if (expectedSha256Digest == actualSha256Digest) { + logger.info("{} had the expected SHA256 digest: {}", file.absolutePath, expectedSha256Digest) + } else { + throw GradleException("Incorrect SHA256 digest of ${file.absolutePath}: " + + "$actualSha256Digest (expected $expectedSha256Digest)") + } +} + +private fun Task.getExpectedSha256DigestFromShasumsFile( + shasumsFilePath: String, + shasumsFileBytes: ByteArray, + desiredFileName: String +): String { + logger.info("Looking for SHA256 sum of {} in {}", desiredFileName, shasumsFilePath) + val lines = String(shasumsFileBytes).lines() + val regex = Regex("""\s*(\w+)\s+(.*)\s*""") + val shas = lines.mapNotNull { line -> + regex.matchEntire(line)?.let { matchResult -> + if (matchResult.groupValues[2] == desiredFileName) { + matchResult.groupValues[1] + } else { + null + } + } + }.distinct() + + val sha = shas.singleOrNull() ?: throw GradleException( + "$shasumsFilePath defines ${shas.size} SHA256 hashes for " + + "$desiredFileName, but expected exactly 1" + ) + + logger.info("Found SHA256 sum of {} in {}: {}", desiredFileName, shasumsFilePath, sha) + return sha +} + +private fun Task.downloadNodeJsBinaryDistribution( + source: DownloadOfficialVersion, + outputDirectory: File +): DownloadedNodeJsFiles { val httpClient = HttpClient(CIO) { expectSuccess = true install(Logging) { @@ -253,17 +310,20 @@ private suspend fun Task.downloadFile(httpClient: HttpClient, url: String, destF } private fun Task.verifyNodeJSShaSumsSignature(file: File): ByteArray { - logger.info("Verifying that ${file.absolutePath} has a valid signature " + - "from the node.js release signing keys") + logger.info( + "Verifying that ${file.absolutePath} has a valid signature " + + "from the node.js release signing keys" + ) val keysListPath = "com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/keys.list" - val keyNames: List = String(loadResource(keysListPath)).lines().map{it.trim()}.filter { it.isNotBlank() } + val keyNames: List = String(loadResource(keysListPath)).lines().map { it.trim() }.filter { it.isNotBlank() } logger.info("Loaded the names of ${keyNames.size} keys from resource: $keysListPath") val sop = SOPImpl() val inlineVerify = sop.inlineVerify() keyNames.forEach { keyName -> - val certificateBytes = loadResource("com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/$keyName.asc") + val certificateBytes = + loadResource("com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/$keyName.asc") inlineVerify.cert(certificateBytes) } logger.info("Loading {} to verify its signature", file.absolutePath) From d01b098bb3225964d852e188c45c8246eda33b94 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Wed, 20 Nov 2024 21:41:08 +0000 Subject: [PATCH 26/79] NodeJS -> NodeJs --- .../dataconnect/gradle/DataConnectGradlePlugin.kt | 4 ++-- .../dataconnect/gradle/tasks/DownloadNodeJSTask.kt | 14 +++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectGradlePlugin.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectGradlePlugin.kt index 261333f66d..41116d91b1 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectGradlePlugin.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectGradlePlugin.kt @@ -20,7 +20,7 @@ import com.android.build.api.variant.ApplicationAndroidComponentsExtension import com.android.build.api.variant.ApplicationVariant import com.google.firebase.example.dataconnect.gradle.providers.MyProjectProviders import com.google.firebase.example.dataconnect.gradle.providers.MyVariantProviders -import com.google.firebase.example.dataconnect.gradle.tasks.DownloadNodeJSTask +import com.google.firebase.example.dataconnect.gradle.tasks.DownloadNodeJsTask import com.google.firebase.example.dataconnect.gradle.tasks.GenerateDataConnectSourcesTask import com.google.firebase.example.dataconnect.gradle.tasks.SetupFirebaseToolsTask import com.google.firebase.example.dataconnect.gradle.tasks.configureFrom @@ -38,7 +38,7 @@ abstract class DataConnectGradlePlugin : Plugin { project.extensions.create("dataconnect", DataConnectExtension::class.java) val providers = project.objects.newInstance() - project.tasks.register("downloadNodeJS") { + project.tasks.register("downloadNodeJs") { configureFrom(providers) } diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJSTask.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJSTask.kt index dff124d08a..c4517aa8e3 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJSTask.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJSTask.kt @@ -17,8 +17,8 @@ package com.google.firebase.example.dataconnect.gradle.tasks import com.google.firebase.example.dataconnect.gradle.providers.MyProjectProviders import com.google.firebase.example.dataconnect.gradle.providers.OperatingSystem -import com.google.firebase.example.dataconnect.gradle.tasks.DownloadNodeJSTask.Source -import com.google.firebase.example.dataconnect.gradle.tasks.DownloadNodeJSTask.Source.DownloadOfficialVersion +import com.google.firebase.example.dataconnect.gradle.tasks.DownloadNodeJsTask.Source +import com.google.firebase.example.dataconnect.gradle.tasks.DownloadNodeJsTask.Source.DownloadOfficialVersion import io.ktor.client.HttpClient import io.ktor.client.call.body import io.ktor.client.engine.cio.CIO @@ -46,7 +46,7 @@ import java.io.File import java.security.MessageDigest import java.text.NumberFormat -abstract class DownloadNodeJSTask : DefaultTask() { +abstract class DownloadNodeJsTask : DefaultTask() { @get:Nested abstract val source: Property @@ -84,7 +84,7 @@ abstract class DownloadNodeJSTask : DefaultTask() { } } -internal fun DownloadNodeJSTask.configureFrom(providers: MyProjectProviders) { +internal fun DownloadNodeJsTask.configureFrom(providers: MyProjectProviders) { source.set(Source.providerFrom(providers)) outputDirectory.set(providers.buildDirectory.map { it.dir("node") }) } @@ -107,7 +107,7 @@ internal fun DownloadOfficialVersion.Companion.describe(source: DownloadOfficial if (source === null) { "null" } else source.run { - "DownloadNodeJSTask.Source.DownloadOfficialVersion(" + + "DownloadNodeJsTask.Source.DownloadOfficialVersion(" + "version=${version.orNull}, operatingSystem=${operatingSystem.orNull})" } @@ -181,7 +181,7 @@ private data class DownloadedNodeJsFiles( private fun Task.downloadOfficialVersion(source: DownloadOfficialVersion, outputDirectory: File) { val downloadedFiles = downloadNodeJsBinaryDistribution(source, outputDirectory) - val shasums = verifyNodeJSShaSumsSignature(downloadedFiles.shasums) + val shasums = verifyNodeJsShaSumsSignature(downloadedFiles.shasums) val expectedSha256Digest = getExpectedSha256DigestFromShasumsFile(downloadedFiles.shasums.absolutePath, shasums, source.downloadFileName) verifySha256Digest(downloadedFiles.binaryDistribution, expectedSha256Digest) @@ -309,7 +309,7 @@ private suspend fun Task.downloadFile(httpClient: HttpClient, url: String, destF logger.info("Successfully downloaded {} to {} ({} bytes)", url, destFile.absolutePath, actualNumBytesDownloadedStr) } -private fun Task.verifyNodeJSShaSumsSignature(file: File): ByteArray { +private fun Task.verifyNodeJsShaSumsSignature(file: File): ByteArray { logger.info( "Verifying that ${file.absolutePath} has a valid signature " + "from the node.js release signing keys" From 67319da521a7893b319a7ddb4cbebc3f81da2216 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Wed, 20 Nov 2024 22:17:46 +0000 Subject: [PATCH 27/79] refactor --- dataconnect/buildSrc/build.gradle.kts | 1 + .../gradle/tasks/DownloadNodeJSTask.kt | 62 +++++++++++++++++++ 2 files changed, 63 insertions(+) diff --git a/dataconnect/buildSrc/build.gradle.kts b/dataconnect/buildSrc/build.gradle.kts index 9df689c181..edd5ab0d90 100644 --- a/dataconnect/buildSrc/build.gradle.kts +++ b/dataconnect/buildSrc/build.gradle.kts @@ -41,6 +41,7 @@ dependencies { implementation("org.pgpainless:pgpainless-sop:1.7.2") implementation("org.bouncycastle:bcprov-jdk18on:1.78.1") + implementation("org.apache.commons:commons-compress:1.27.1") } gradlePlugin { diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJSTask.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJSTask.kt index c4517aa8e3..8c1325b03a 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJSTask.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJSTask.kt @@ -29,6 +29,9 @@ import io.ktor.client.request.prepareGet import io.ktor.utils.io.ByteReadChannel import io.ktor.utils.io.jvm.javaio.copyTo import kotlinx.coroutines.runBlocking +import org.apache.commons.compress.archivers.tar.TarArchiveEntry +import org.apache.commons.compress.archivers.tar.TarArchiveInputStream +import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream import org.bouncycastle.util.encoders.Hex import org.gradle.api.DefaultTask import org.gradle.api.GradleException @@ -43,6 +46,10 @@ import org.gradle.api.tasks.TaskAction import org.gradle.kotlin.dsl.newInstance import org.pgpainless.sop.SOPImpl import java.io.File +import java.io.IOException +import java.nio.file.Files +import java.nio.file.attribute.FileTime +import java.nio.file.attribute.PosixFilePermission import java.security.MessageDigest import java.text.NumberFormat @@ -185,6 +192,61 @@ private fun Task.downloadOfficialVersion(source: DownloadOfficialVersion, output val expectedSha256Digest = getExpectedSha256DigestFromShasumsFile(downloadedFiles.shasums.absolutePath, shasums, source.downloadFileName) verifySha256Digest(downloadedFiles.binaryDistribution, expectedSha256Digest) + untar(downloadedFiles.binaryDistribution, outputDirectory) +} + +private fun Task.untar(file: File, destDir: File) { + logger.info("Extracting {} to {}", file.absolutePath, destDir.absolutePath) + var extractedFileCount = 0 + var extractedByteCount = 0L + file.inputStream().use { fileInputStream -> + GzipCompressorInputStream(fileInputStream).use { gzipInputStream -> + TarArchiveInputStream(gzipInputStream).use { tarInputStream -> + while (true) { + val tarEntry: TarArchiveEntry = tarInputStream.nextEntry ?: break + if (!tarEntry.isFile) { + continue + } + val outputFile = File(destDir, tarEntry.name).absoluteFile + logger.debug("Extracting {}", outputFile.absolutePath) + outputFile.parentFile.mkdirs() + outputFile.outputStream().use { fileOutputStream -> + extractedByteCount += tarInputStream.copyTo(fileOutputStream) + } + extractedFileCount++ + + val lastModifiedTime = FileTime.from(tarEntry.lastModifiedTime.toInstant()) + try { + Files.setLastModifiedTime(outputFile.toPath(), lastModifiedTime) + } catch (e: IOException) { + logger.debug("Ignoring error from Files.setLastModifiedTime({}, {}): {}", outputFile.absolutePath, lastModifiedTime, e.toString()) + } + + val newPermissions = buildSet { + add(PosixFilePermission.OWNER_READ) + add(PosixFilePermission.OWNER_WRITE) + + add(PosixFilePermission.GROUP_READ) + add(PosixFilePermission.OTHERS_READ) + + val mode = tarEntry.mode + if ((mode and 0x100) == 0x100) { + add(PosixFilePermission.OWNER_EXECUTE) + add(PosixFilePermission.GROUP_EXECUTE) + add(PosixFilePermission.OTHERS_EXECUTE) + } + } + try { + Files.setPosixFilePermissions(outputFile.toPath(), newPermissions) + } catch (e: UnsupportedOperationException) { + logger.debug("Ignoring error from Files.setPosixFilePermissions({}, {}}): {}", outputFile.absolutePath, newPermissions, e.toString()) + } + } + } + } + } + val extractedByteCountStr = NumberFormat.getNumberInstance().format(extractedByteCount) + logger.info("Extracted {} files ({} bytes) from {} to {}", extractedFileCount, extractedByteCountStr, file.absolutePath, destDir.absolutePath) } private fun Task.verifySha256Digest(file: File, expectedSha256Digest: String) { From 4600046e7e9110158f3508ebbe1b297da59a8e42 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Wed, 20 Nov 2024 22:33:50 +0000 Subject: [PATCH 28/79] unzip too --- .../gradle/tasks/DownloadNodeJSTask.kt | 68 ++++++++++++++++++- 1 file changed, 67 insertions(+), 1 deletion(-) diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJSTask.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJSTask.kt index 8c1325b03a..3f69ff4423 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJSTask.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJSTask.kt @@ -31,6 +31,8 @@ import io.ktor.utils.io.jvm.javaio.copyTo import kotlinx.coroutines.runBlocking import org.apache.commons.compress.archivers.tar.TarArchiveEntry import org.apache.commons.compress.archivers.tar.TarArchiveInputStream +import org.apache.commons.compress.archivers.zip.ZipArchiveEntry +import org.apache.commons.compress.archivers.zip.ZipArchiveInputStream import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream import org.bouncycastle.util.encoders.Hex import org.gradle.api.DefaultTask @@ -93,7 +95,9 @@ abstract class DownloadNodeJsTask : DefaultTask() { internal fun DownloadNodeJsTask.configureFrom(providers: MyProjectProviders) { source.set(Source.providerFrom(providers)) + source.disallowUnsafeRead() outputDirectory.set(providers.buildDirectory.map { it.dir("node") }) + outputDirectory.disallowUnsafeRead() } internal fun Source.Companion.providerFrom(providers: MyProjectProviders): Provider { @@ -107,7 +111,9 @@ internal fun Source.Companion.providerFrom(providers: MyProjectProviders): Provi internal fun DownloadOfficialVersion.updateFrom(providers: MyProjectProviders) { version.set("20.9.0") + version.disallowUnsafeRead() operatingSystem.set(providers.operatingSystem) + operatingSystem.disallowUnsafeRead() } internal fun DownloadOfficialVersion.Companion.describe(source: DownloadOfficialVersion?): String = @@ -192,7 +198,15 @@ private fun Task.downloadOfficialVersion(source: DownloadOfficialVersion, output val expectedSha256Digest = getExpectedSha256DigestFromShasumsFile(downloadedFiles.shasums.absolutePath, shasums, source.downloadFileName) verifySha256Digest(downloadedFiles.binaryDistribution, expectedSha256Digest) - untar(downloadedFiles.binaryDistribution, outputDirectory) + + if (downloadedFiles.binaryDistribution.name.endsWith(".tar.gz")) { + untar(downloadedFiles.binaryDistribution, outputDirectory) + } else if (downloadedFiles.binaryDistribution.name.endsWith(".zip")) { + unzip(downloadedFiles.binaryDistribution, outputDirectory) + } else { + throw GradleException("Unsupported archive: ${downloadedFiles.binaryDistribution.absolutePath} " + + "(only .tar.gz and .zip extensions are supported) (error code pvrvw8sk9t)") + } } private fun Task.untar(file: File, destDir: File) { @@ -248,6 +262,58 @@ private fun Task.untar(file: File, destDir: File) { val extractedByteCountStr = NumberFormat.getNumberInstance().format(extractedByteCount) logger.info("Extracted {} files ({} bytes) from {} to {}", extractedFileCount, extractedByteCountStr, file.absolutePath, destDir.absolutePath) } +private fun Task.unzip(file: File, destDir: File) { + logger.info("Extracting {} to {}", file.absolutePath, destDir.absolutePath) + var extractedFileCount = 0 + var extractedByteCount = 0L + file.inputStream().use { fileInputStream -> + ZipArchiveInputStream(fileInputStream).use { zipInputStream -> + while (true) { + val zipEntry: ZipArchiveEntry = zipInputStream.nextEntry ?: break + if (zipEntry.isDirectory) { + continue + } + val outputFile = File(destDir, zipEntry.name).absoluteFile + logger.debug("Extracting {}", outputFile.absolutePath) + outputFile.parentFile.mkdirs() + outputFile.outputStream().use { fileOutputStream -> + extractedByteCount += zipInputStream.copyTo(fileOutputStream) + } + extractedFileCount++ + + val lastModifiedTime = FileTime.from(zipEntry.lastModifiedTime.toInstant()) + try { + Files.setLastModifiedTime(outputFile.toPath(), lastModifiedTime) + } catch (e: IOException) { + logger.debug("Ignoring error from Files.setLastModifiedTime({}, {}): {}", outputFile.absolutePath, lastModifiedTime, e.toString()) + } + + val newPermissions = buildSet { + add(PosixFilePermission.OWNER_READ) + add(PosixFilePermission.OWNER_WRITE) + + add(PosixFilePermission.GROUP_READ) + add(PosixFilePermission.OTHERS_READ) + + val mode = zipEntry.unixMode + if ((mode and 0x100) == 0x100) { + add(PosixFilePermission.OWNER_EXECUTE) + add(PosixFilePermission.GROUP_EXECUTE) + add(PosixFilePermission.OTHERS_EXECUTE) + } + } + try { + Files.setPosixFilePermissions(outputFile.toPath(), newPermissions) + } catch (e: UnsupportedOperationException) { + logger.debug("Ignoring error from Files.setPosixFilePermissions({}, {}}): {}", outputFile.absolutePath, newPermissions, e.toString()) + } + + } + } + } + val extractedByteCountStr = NumberFormat.getNumberInstance().format(extractedByteCount) + logger.info("Extracted {} files ({} bytes) from {} to {}", extractedFileCount, extractedByteCountStr, file.absolutePath, destDir.absolutePath) +} private fun Task.verifySha256Digest(file: File, expectedSha256Digest: String) { val actualSha256Digest = file.inputStream().use { inputStream -> From 0c413730879449bafa9d187c5b95eddb86d11498 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Wed, 20 Nov 2024 22:48:21 +0000 Subject: [PATCH 29/79] ktlint format the code in dataconnect/buildSrc by running ./gradlew ktlintCheck --- ...ngSystemProvider.kt => OperatingSystem.kt} | 10 +- .../gradle/tasks/DownloadNodeJSTask.kt | 179 ++++++++++-------- .../example/dataconnect/gradle/tasks/util.kt | 2 +- 3 files changed, 101 insertions(+), 90 deletions(-) rename dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/providers/{OperatingSystemProvider.kt => OperatingSystem.kt} (94%) diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/providers/OperatingSystemProvider.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/providers/OperatingSystem.kt similarity index 94% rename from dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/providers/OperatingSystemProvider.kt rename to dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/providers/OperatingSystem.kt index b8adf3764b..445547cca9 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/providers/OperatingSystemProvider.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/providers/OperatingSystem.kt @@ -28,7 +28,7 @@ import org.gradle.kotlin.dsl.property data class OperatingSystem( @get:Input val type: Type, @get:Input val arch: Architecture, - @get:Internal val description: String?, + @get:Internal val description: String? ) : java.io.Serializable { enum class Type { @@ -73,15 +73,16 @@ fun OperatingSystem.Companion.provider( val arch = osArch?.let { OperatingSystem.Architecture.forName(it) } if (type === null || arch === null) { - throw GradleException("unable to determine operating system from $description " + - " (type=$type, arch=$arch) (error code qecxcvcf8n)" + throw GradleException( + "unable to determine operating system from $description " + + " (type=$type, arch=$arch) (error code qecxcvcf8n)" ) } OperatingSystem( type = OperatingSystem.Type.Linux, arch = OperatingSystem.Architecture.X86_64, - description=description, + description = description ) } @@ -135,4 +136,3 @@ private fun OperatingSystem.Architecture.Companion.forLowerCaseName(osArch: Stri "aarch64", "arm-v8", "arm64" -> OperatingSystem.Architecture.Arm64 else -> null } - diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJSTask.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJSTask.kt index 3f69ff4423..6e3bd65dec 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJSTask.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJSTask.kt @@ -28,6 +28,13 @@ import io.ktor.client.plugins.logging.Logging import io.ktor.client.request.prepareGet import io.ktor.utils.io.ByteReadChannel import io.ktor.utils.io.jvm.javaio.copyTo +import java.io.File +import java.io.IOException +import java.nio.file.Files +import java.nio.file.attribute.FileTime +import java.nio.file.attribute.PosixFilePermission +import java.security.MessageDigest +import java.text.NumberFormat import kotlinx.coroutines.runBlocking import org.apache.commons.compress.archivers.tar.TarArchiveEntry import org.apache.commons.compress.archivers.tar.TarArchiveInputStream @@ -47,13 +54,6 @@ import org.gradle.api.tasks.OutputDirectory import org.gradle.api.tasks.TaskAction import org.gradle.kotlin.dsl.newInstance import org.pgpainless.sop.SOPImpl -import java.io.File -import java.io.IOException -import java.nio.file.Files -import java.nio.file.attribute.FileTime -import java.nio.file.attribute.PosixFilePermission -import java.security.MessageDigest -import java.text.NumberFormat abstract class DownloadNodeJsTask : DefaultTask() { @@ -121,7 +121,7 @@ internal fun DownloadOfficialVersion.Companion.describe(source: DownloadOfficial "null" } else source.run { "DownloadNodeJsTask.Source.DownloadOfficialVersion(" + - "version=${version.orNull}, operatingSystem=${operatingSystem.orNull})" + "version=${version.orNull}, operatingSystem=${operatingSystem.orNull})" } internal fun Source.Companion.describe(source: Source?): String = when (source) { @@ -174,7 +174,7 @@ internal val DownloadOfficialVersion.downloadFileName: String OperatingSystem.Type.Linux -> Pair("linux", "tar.gz") else -> throw GradleException( "unable to determine node.js download URL for operating system type: $type " + - "(operatingSystem=$os) (error code ead53smf45)" + "(operatingSystem=$os) (error code ead53smf45)" ) } val osArch = when (os.arch) { @@ -189,7 +189,7 @@ internal val DownloadOfficialVersion.downloadFileName: String private data class DownloadedNodeJsFiles( val binaryDistribution: File, - val shasums: File, + val shasums: File ) private fun Task.downloadOfficialVersion(source: DownloadOfficialVersion, outputDirectory: File) { @@ -204,8 +204,10 @@ private fun Task.downloadOfficialVersion(source: DownloadOfficialVersion, output } else if (downloadedFiles.binaryDistribution.name.endsWith(".zip")) { unzip(downloadedFiles.binaryDistribution, outputDirectory) } else { - throw GradleException("Unsupported archive: ${downloadedFiles.binaryDistribution.absolutePath} " + - "(only .tar.gz and .zip extensions are supported) (error code pvrvw8sk9t)") + throw GradleException( + "Unsupported archive: ${downloadedFiles.binaryDistribution.absolutePath} " + + "(only .tar.gz and .zip extensions are supported) (error code pvrvw8sk9t)" + ) } } @@ -233,7 +235,12 @@ private fun Task.untar(file: File, destDir: File) { try { Files.setLastModifiedTime(outputFile.toPath(), lastModifiedTime) } catch (e: IOException) { - logger.debug("Ignoring error from Files.setLastModifiedTime({}, {}): {}", outputFile.absolutePath, lastModifiedTime, e.toString()) + logger.debug( + "Ignoring error from Files.setLastModifiedTime({}, {}): {}", + outputFile.absolutePath, + lastModifiedTime, + e.toString() + ) } val newPermissions = buildSet { @@ -253,14 +260,25 @@ private fun Task.untar(file: File, destDir: File) { try { Files.setPosixFilePermissions(outputFile.toPath(), newPermissions) } catch (e: UnsupportedOperationException) { - logger.debug("Ignoring error from Files.setPosixFilePermissions({}, {}}): {}", outputFile.absolutePath, newPermissions, e.toString()) + logger.debug( + "Ignoring error from Files.setPosixFilePermissions({}, {}}): {}", + outputFile.absolutePath, + newPermissions, + e.toString() + ) } } } } } val extractedByteCountStr = NumberFormat.getNumberInstance().format(extractedByteCount) - logger.info("Extracted {} files ({} bytes) from {} to {}", extractedFileCount, extractedByteCountStr, file.absolutePath, destDir.absolutePath) + logger.info( + "Extracted {} files ({} bytes) from {} to {}", + extractedFileCount, + extractedByteCountStr, + file.absolutePath, + destDir.absolutePath + ) } private fun Task.unzip(file: File, destDir: File) { logger.info("Extracting {} to {}", file.absolutePath, destDir.absolutePath) @@ -268,51 +286,66 @@ private fun Task.unzip(file: File, destDir: File) { var extractedByteCount = 0L file.inputStream().use { fileInputStream -> ZipArchiveInputStream(fileInputStream).use { zipInputStream -> - while (true) { - val zipEntry: ZipArchiveEntry = zipInputStream.nextEntry ?: break - if (zipEntry.isDirectory) { - continue - } - val outputFile = File(destDir, zipEntry.name).absoluteFile - logger.debug("Extracting {}", outputFile.absolutePath) - outputFile.parentFile.mkdirs() - outputFile.outputStream().use { fileOutputStream -> - extractedByteCount += zipInputStream.copyTo(fileOutputStream) - } - extractedFileCount++ - - val lastModifiedTime = FileTime.from(zipEntry.lastModifiedTime.toInstant()) - try { - Files.setLastModifiedTime(outputFile.toPath(), lastModifiedTime) - } catch (e: IOException) { - logger.debug("Ignoring error from Files.setLastModifiedTime({}, {}): {}", outputFile.absolutePath, lastModifiedTime, e.toString()) - } + while (true) { + val zipEntry: ZipArchiveEntry = zipInputStream.nextEntry ?: break + if (zipEntry.isDirectory) { + continue + } + val outputFile = File(destDir, zipEntry.name).absoluteFile + logger.debug("Extracting {}", outputFile.absolutePath) + outputFile.parentFile.mkdirs() + outputFile.outputStream().use { fileOutputStream -> + extractedByteCount += zipInputStream.copyTo(fileOutputStream) + } + extractedFileCount++ + + val lastModifiedTime = FileTime.from(zipEntry.lastModifiedTime.toInstant()) + try { + Files.setLastModifiedTime(outputFile.toPath(), lastModifiedTime) + } catch (e: IOException) { + logger.debug( + "Ignoring error from Files.setLastModifiedTime({}, {}): {}", + outputFile.absolutePath, + lastModifiedTime, + e.toString() + ) + } - val newPermissions = buildSet { - add(PosixFilePermission.OWNER_READ) - add(PosixFilePermission.OWNER_WRITE) + val newPermissions = buildSet { + add(PosixFilePermission.OWNER_READ) + add(PosixFilePermission.OWNER_WRITE) - add(PosixFilePermission.GROUP_READ) - add(PosixFilePermission.OTHERS_READ) + add(PosixFilePermission.GROUP_READ) + add(PosixFilePermission.OTHERS_READ) - val mode = zipEntry.unixMode - if ((mode and 0x100) == 0x100) { - add(PosixFilePermission.OWNER_EXECUTE) - add(PosixFilePermission.GROUP_EXECUTE) - add(PosixFilePermission.OTHERS_EXECUTE) - } - } - try { - Files.setPosixFilePermissions(outputFile.toPath(), newPermissions) - } catch (e: UnsupportedOperationException) { - logger.debug("Ignoring error from Files.setPosixFilePermissions({}, {}}): {}", outputFile.absolutePath, newPermissions, e.toString()) + val mode = zipEntry.unixMode + if ((mode and 0x100) == 0x100) { + add(PosixFilePermission.OWNER_EXECUTE) + add(PosixFilePermission.GROUP_EXECUTE) + add(PosixFilePermission.OTHERS_EXECUTE) } - + } + try { + Files.setPosixFilePermissions(outputFile.toPath(), newPermissions) + } catch (e: UnsupportedOperationException) { + logger.debug( + "Ignoring error from Files.setPosixFilePermissions({}, {}}): {}", + outputFile.absolutePath, + newPermissions, + e.toString() + ) + } } } } val extractedByteCountStr = NumberFormat.getNumberInstance().format(extractedByteCount) - logger.info("Extracted {} files ({} bytes) from {} to {}", extractedFileCount, extractedByteCountStr, file.absolutePath, destDir.absolutePath) + logger.info( + "Extracted {} files ({} bytes) from {} to {}", + extractedFileCount, + extractedByteCountStr, + file.absolutePath, + destDir.absolutePath + ) } private fun Task.verifySha256Digest(file: File, expectedSha256Digest: String) { @@ -333,8 +366,10 @@ private fun Task.verifySha256Digest(file: File, expectedSha256Digest: String) { if (expectedSha256Digest == actualSha256Digest) { logger.info("{} had the expected SHA256 digest: {}", file.absolutePath, expectedSha256Digest) } else { - throw GradleException("Incorrect SHA256 digest of ${file.absolutePath}: " + - "$actualSha256Digest (expected $expectedSha256Digest)") + throw GradleException( + "Incorrect SHA256 digest of ${file.absolutePath}: " + + "$actualSha256Digest (expected $expectedSha256Digest)" + ) } } @@ -357,8 +392,8 @@ private fun Task.getExpectedSha256DigestFromShasumsFile( }.distinct() val sha = shas.singleOrNull() ?: throw GradleException( - "$shasumsFilePath defines ${shas.size} SHA256 hashes for " - + "$desiredFileName, but expected exactly 1" + "$shasumsFilePath defines ${shas.size} SHA256 hashes for " + + "$desiredFileName, but expected exactly 1" ) logger.info("Found SHA256 sum of {} in {}: {}", desiredFileName, shasumsFilePath, sha) @@ -408,7 +443,7 @@ private fun Task.downloadNodeJsBinaryDistribution( return DownloadedNodeJsFiles( binaryDistribution = binaryDistributionFile, - shasums = shasumsFile, + shasums = shasumsFile ) } @@ -429,8 +464,8 @@ private suspend fun Task.downloadFile(httpClient: HttpClient, url: String, destF val maxNumDownloadBytesStr = numberFormat.format(maxNumDownloadBytes) throw GradleException( "Downloading $url failed: maximum file size $maxNumDownloadBytesStr bytes exceeded; " + - "cancelled after downloading $actualNumBytesDownloadedStr bytes " + - "(error code hvmhysn5vy)" + "cancelled after downloading $actualNumBytesDownloadedStr bytes " + + "(error code hvmhysn5vy)" ) } @@ -440,7 +475,7 @@ private suspend fun Task.downloadFile(httpClient: HttpClient, url: String, destF private fun Task.verifyNodeJsShaSumsSignature(file: File): ByteArray { logger.info( "Verifying that ${file.absolutePath} has a valid signature " + - "from the node.js release signing keys" + "from the node.js release signing keys" ) val keysListPath = "com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/keys.list" @@ -475,27 +510,3 @@ private fun Task.loadResource(path: String): ByteArray { } } } - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/util.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/util.kt index 17dd71d51c..66426f8198 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/util.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/util.kt @@ -39,4 +39,4 @@ internal fun Task.runCommand(logFile: File, configure: ExecSpec.() -> Unit) { effectiveLogFile?.let { logger.warn("{}", it.readText()) } throw exception } -} \ No newline at end of file +} From 8ef8480bab0c74df3770f596e0623289507083e9 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Wed, 20 Nov 2024 23:28:55 +0000 Subject: [PATCH 30/79] wire up node executable between tasks --- .../gradle/DataConnectGradlePlugin.kt | 21 +++++++++++++- .../gradle/tasks/DownloadNodeJSTask.kt | 28 ++++++++++++++++++- 2 files changed, 47 insertions(+), 2 deletions(-) diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectGradlePlugin.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectGradlePlugin.kt index 41116d91b1..917ad7cad2 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectGradlePlugin.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectGradlePlugin.kt @@ -25,12 +25,27 @@ import com.google.firebase.example.dataconnect.gradle.tasks.GenerateDataConnectS import com.google.firebase.example.dataconnect.gradle.tasks.SetupFirebaseToolsTask import com.google.firebase.example.dataconnect.gradle.tasks.configureFrom import java.util.Locale +import org.gradle.api.DefaultTask import org.gradle.api.Plugin import org.gradle.api.Project +import org.gradle.api.file.RegularFileProperty +import org.gradle.api.tasks.InputFile +import org.gradle.api.tasks.TaskAction import org.gradle.kotlin.dsl.getByType import org.gradle.kotlin.dsl.newInstance import org.gradle.kotlin.dsl.register +abstract class FooTask : DefaultTask() { + + @get:InputFile + abstract val nodeExecutable: RegularFileProperty + + @TaskAction + fun run() { + throw Exception("in task $name, nodeExecutable=${nodeExecutable.get().asFile}") + } +} + @Suppress("unused") abstract class DataConnectGradlePlugin : Plugin { @@ -38,10 +53,14 @@ abstract class DataConnectGradlePlugin : Plugin { project.extensions.create("dataconnect", DataConnectExtension::class.java) val providers = project.objects.newInstance() - project.tasks.register("downloadNodeJs") { + val x = project.tasks.register("downloadNodeJs") { configureFrom(providers) } + project.tasks.register("foo") { + nodeExecutable.set(x.flatMap { it.nodeExecutable }) + } + project.tasks.register("setupFirebaseToolsForDataConnect") { configureFrom(providers) } diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJSTask.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJSTask.kt index 6e3bd65dec..1d025756b6 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJSTask.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJSTask.kt @@ -46,13 +46,16 @@ import org.gradle.api.DefaultTask import org.gradle.api.GradleException import org.gradle.api.Task import org.gradle.api.file.DirectoryProperty +import org.gradle.api.file.RegularFile import org.gradle.api.provider.Property import org.gradle.api.provider.Provider import org.gradle.api.tasks.Input +import org.gradle.api.tasks.Internal import org.gradle.api.tasks.Nested import org.gradle.api.tasks.OutputDirectory import org.gradle.api.tasks.TaskAction import org.gradle.kotlin.dsl.newInstance +import org.gradle.kotlin.dsl.property import org.pgpainless.sop.SOPImpl abstract class DownloadNodeJsTask : DefaultTask() { @@ -63,10 +66,18 @@ abstract class DownloadNodeJsTask : DefaultTask() { @get:OutputDirectory abstract val outputDirectory: DirectoryProperty + @get:Internal + val nodeExecutable: Provider by lazy { + outputDirectory.map { it.file(nodeExecutablePathUnderOutputDirectory.get()) } + } + + private val nodeExecutablePathUnderOutputDirectory: Property = project.objects.property() + @TaskAction fun run() { val source = source.get() - val outputDirectory = outputDirectory.get().asFile + val outputDirectoryRegularFile = outputDirectory.get() + val outputDirectory = outputDirectoryRegularFile.asFile logger.info("source: {}", Source.describe(source)) logger.info("outputDirectory: {}", outputDirectory.absolutePath) @@ -76,6 +87,21 @@ abstract class DownloadNodeJsTask : DefaultTask() { when (source) { is DownloadOfficialVersion -> downloadOfficialVersion(source, outputDirectory) } + + val nodeExecutableFiles = outputDirectory.walk().filter { + it.isFile && it.name == "node" + }.toList() + val nodeExecutableFile = nodeExecutableFiles.singleOrNull() ?: throw GradleException( + "Found ${nodeExecutableFiles.size} node executable files " + + "in ${outputDirectory.absolutePath}, but expected exactly 1: " + + nodeExecutableFiles.joinToString(", ") { it.absolutePath } + + "(error code v6n2g6my3y)" + ) + + nodeExecutablePathUnderOutputDirectory.apply { + set(outputDirectory.toPath().relativize(nodeExecutableFile.toPath()).toString()) + finalizeValue() + } } sealed interface Source : java.io.Serializable { From 9fa40e451f23a009fa1353c9bcde55895f283200 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Thu, 21 Nov 2024 04:09:22 +0000 Subject: [PATCH 31/79] improvements --- .../gradle/DataConnectGradlePlugin.kt | 36 +++---------- .../dataconnect/gradle/providers/providers.kt | 37 ++++--------- ...oadNodeJSTask.kt => DownloadNodeJsTask.kt} | 30 ++++++++--- .../gradle/tasks/SetupFirebaseToolsTask.kt | 54 ++++++++----------- 4 files changed, 63 insertions(+), 94 deletions(-) rename dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/{DownloadNodeJSTask.kt => DownloadNodeJsTask.kt} (95%) diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectGradlePlugin.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectGradlePlugin.kt index 917ad7cad2..9db50d7f8e 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectGradlePlugin.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectGradlePlugin.kt @@ -25,49 +25,27 @@ import com.google.firebase.example.dataconnect.gradle.tasks.GenerateDataConnectS import com.google.firebase.example.dataconnect.gradle.tasks.SetupFirebaseToolsTask import com.google.firebase.example.dataconnect.gradle.tasks.configureFrom import java.util.Locale -import org.gradle.api.DefaultTask import org.gradle.api.Plugin import org.gradle.api.Project -import org.gradle.api.file.RegularFileProperty -import org.gradle.api.tasks.InputFile -import org.gradle.api.tasks.TaskAction import org.gradle.kotlin.dsl.getByType -import org.gradle.kotlin.dsl.newInstance import org.gradle.kotlin.dsl.register -abstract class FooTask : DefaultTask() { - - @get:InputFile - abstract val nodeExecutable: RegularFileProperty - - @TaskAction - fun run() { - throw Exception("in task $name, nodeExecutable=${nodeExecutable.get().asFile}") - } -} - @Suppress("unused") abstract class DataConnectGradlePlugin : Plugin { override fun apply(project: Project) { - project.extensions.create("dataconnect", DataConnectExtension::class.java) - val providers = project.objects.newInstance() + val downloadNodeJsTask = project.tasks.register("downloadNodeJs") + val setupFirebaseToolsTask = project.tasks.register("setupFirebaseToolsForDataConnect") - val x = project.tasks.register("downloadNodeJs") { - configureFrom(providers) - } - - project.tasks.register("foo") { - nodeExecutable.set(x.flatMap { it.nodeExecutable }) - } + project.extensions.create("dataconnect", DataConnectExtension::class.java) + val providers = MyProjectProviders(project, downloadNodeJsTask) - project.tasks.register("setupFirebaseToolsForDataConnect") { - configureFrom(providers) - } + downloadNodeJsTask.configure { configureFrom(providers) } + setupFirebaseToolsTask.configure { configureFrom(providers) } val androidComponents = project.extensions.getByType() androidComponents.onVariants { variant -> - val variantProviders = project.objects.newInstance(variant, providers) + val variantProviders = MyVariantProviders(project, variant, setupFirebaseToolsTask, providers) registerVariantTasks(project, variant, variantProviders) } } diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/providers/providers.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/providers/providers.kt index e3c232e09c..b5f3866429 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/providers/providers.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/providers/providers.kt @@ -18,8 +18,8 @@ package com.google.firebase.example.dataconnect.gradle.providers import com.android.build.api.variant.ApplicationVariant import com.google.firebase.example.dataconnect.gradle.DataConnectExtension +import com.google.firebase.example.dataconnect.gradle.tasks.DownloadNodeJsTask import com.google.firebase.example.dataconnect.gradle.tasks.SetupFirebaseToolsTask -import javax.inject.Inject import org.gradle.api.GradleException import org.gradle.api.Project import org.gradle.api.file.Directory @@ -29,6 +29,7 @@ import org.gradle.api.logging.Logger import org.gradle.api.model.ObjectFactory import org.gradle.api.provider.Provider import org.gradle.api.provider.ProviderFactory +import org.gradle.api.tasks.TaskProvider import org.gradle.kotlin.dsl.getByType internal open class MyProjectProviders( @@ -36,27 +37,28 @@ internal open class MyProjectProviders( val providerFactory: ProviderFactory, val objectFactory: ObjectFactory, projectDirectoryHierarchy: List, + val nodeExecutable: Provider, + val npmExecutable: Provider, ext: DataConnectExtension, logger: Logger ) { - @Suppress("unused") - @Inject constructor( - project: Project + project: Project, + downloadNodeJsTask: TaskProvider ) : this( projectBuildDirectory = project.layout.buildDirectory, providerFactory = project.providers, objectFactory = project.objects, projectDirectoryHierarchy = project.projectDirectoryHierarchy(), + nodeExecutable = downloadNodeJsTask.flatMap { it.nodeExecutable }, + npmExecutable = downloadNodeJsTask.flatMap { it.npmExecutable }, ext = project.extensions.getByType(), project.logger ) val operatingSystem: Provider = OperatingSystem.provider(objectFactory, providerFactory, logger) - val pathEnvironmentVariable: Provider = providerFactory.environmentVariable("PATH") - val buildDirectory: Provider = projectBuildDirectory.map { it.dir("dataconnect") } val firebaseToolsVersion: Provider = @@ -70,10 +72,6 @@ internal open class MyProjectProviders( } private val localConfigProviders = LocalConfigProviders(projectDirectoryHierarchy, providerFactory, logger) - - val npmExecutable: Provider by localConfigProviders::npmExecutable - - val nodeExecutable: Provider by localConfigProviders::nodeExecutable } internal open class MyVariantProviders( @@ -84,16 +82,15 @@ internal open class MyVariantProviders( objectFactory: ObjectFactory ) { - @Suppress("unused") - @Inject constructor( - variant: ApplicationVariant, project: Project, + variant: ApplicationVariant, + setupFirebaseToolsTask: TaskProvider, projectProviders: MyProjectProviders ) : this( variant = variant, projectProviders = projectProviders, - firebaseExecutable = project.firebaseToolsSetupTask.firebaseExecutable, + firebaseExecutable = setupFirebaseToolsTask.flatMap { it.firebaseExecutable }, ext = project.extensions.getByType(), objectFactory = project.objects ) @@ -112,18 +109,6 @@ internal open class MyVariantProviders( } } -private val Project.firebaseToolsSetupTask: SetupFirebaseToolsTask - get() { - val tasks = tasks.filterIsInstance() - if (tasks.size != 1) { - throw GradleException( - "expected exactly 1 SetupFirebaseToolsTask task to be registered, but found " + - "${tasks.size}: [${tasks.map { it.name }.sorted().joinToString(", ")}]" - ) - } - return tasks.single() - } - private fun Project.projectDirectoryHierarchy(): List = buildList { var curProject: Project? = this@projectDirectoryHierarchy while (curProject !== null) { diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJSTask.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsTask.kt similarity index 95% rename from dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJSTask.kt rename to dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsTask.kt index 1d025756b6..85272897dd 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJSTask.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsTask.kt @@ -68,10 +68,16 @@ abstract class DownloadNodeJsTask : DefaultTask() { @get:Internal val nodeExecutable: Provider by lazy { - outputDirectory.map { it.file(nodeExecutablePathUnderOutputDirectory.get()) } + outputDirectory.map { it.file(nodeExecutableRelativePath.get()) } } - private val nodeExecutablePathUnderOutputDirectory: Property = project.objects.property() + @get:Internal + val npmExecutable: Provider by lazy { + outputDirectory.map { it.file(npmExecutableRelativePath.get()) } + } + + private val nodeExecutableRelativePath: Property = project.objects.property() + private val npmExecutableRelativePath: Property = project.objects.property() @TaskAction fun run() { @@ -91,17 +97,23 @@ abstract class DownloadNodeJsTask : DefaultTask() { val nodeExecutableFiles = outputDirectory.walk().filter { it.isFile && it.name == "node" }.toList() + val nodeExecutableFile = nodeExecutableFiles.singleOrNull() ?: throw GradleException( "Found ${nodeExecutableFiles.size} node executable files " + "in ${outputDirectory.absolutePath}, but expected exactly 1: " + nodeExecutableFiles.joinToString(", ") { it.absolutePath } + "(error code v6n2g6my3y)" ) + val npmExecutableFile = File(nodeExecutableFile.absoluteFile.parent, "npm") - nodeExecutablePathUnderOutputDirectory.apply { + nodeExecutableRelativePath.apply { set(outputDirectory.toPath().relativize(nodeExecutableFile.toPath()).toString()) finalizeValue() } + npmExecutableRelativePath.apply { + set(outputDirectory.toPath().relativize(npmExecutableFile.toPath()).toString()) + finalizeValue() + } } sealed interface Source : java.io.Serializable { @@ -120,10 +132,14 @@ abstract class DownloadNodeJsTask : DefaultTask() { } internal fun DownloadNodeJsTask.configureFrom(providers: MyProjectProviders) { - source.set(Source.providerFrom(providers)) - source.disallowUnsafeRead() - outputDirectory.set(providers.buildDirectory.map { it.dir("node") }) - outputDirectory.disallowUnsafeRead() + source.run { + set(Source.providerFrom(providers)) + disallowUnsafeRead() + } + outputDirectory.run { + set(providers.buildDirectory.map { it.dir("node") }) + disallowUnsafeRead() + } } internal fun Source.Companion.providerFrom(providers: MyProjectProviders): Provider { diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/SetupFirebaseToolsTask.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/SetupFirebaseToolsTask.kt index 6c89d639a7..8e9ccb5356 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/SetupFirebaseToolsTask.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/SetupFirebaseToolsTask.kt @@ -25,6 +25,7 @@ import org.gradle.api.file.RegularFileProperty import org.gradle.api.provider.Property import org.gradle.api.provider.Provider import org.gradle.api.tasks.Input +import org.gradle.api.tasks.InputFile import org.gradle.api.tasks.Internal import org.gradle.api.tasks.OutputDirectory import org.gradle.api.tasks.TaskAction @@ -32,68 +33,57 @@ import org.gradle.api.tasks.TaskAction abstract class SetupFirebaseToolsTask : DefaultTask() { @get:Input - abstract val version: Property + abstract val firebaseToolsVersion: Property - @get:Internal + @get:InputFile abstract val npmExecutable: RegularFileProperty - @get:Internal + @get:InputFile abstract val nodeExecutable: RegularFileProperty @get:OutputDirectory abstract val outputDirectory: DirectoryProperty @get:Internal - val firebaseExecutable: Provider - get() = outputDirectory.map { it.file("node_modules/.bin/firebase") } + val firebaseExecutable: Provider by lazy { + outputDirectory.map { it.file("node_modules/.bin/firebase") } + } - @get:Internal - abstract val pathEnvironmentVariable: Property + private val pathEnvironmentVariable: Provider get() = project.providers.environmentVariable("PATH") @TaskAction fun run() { - val version: String = version.get() - val npmExecutable: File? = npmExecutable.orNull?.asFile - val nodeExecutable: File? = nodeExecutable.orNull?.asFile + val firebaseToolsVersion: String = firebaseToolsVersion.get() + val npmExecutable: File = npmExecutable.get().asFile + val nodeExecutable: File = nodeExecutable.get().asFile val outputDirectory: File = outputDirectory.get().asFile - logger.info("version: {}", version) - logger.info("npmExecutable: {}", npmExecutable?.absolutePath) - logger.info("nodeExecutable: {}", nodeExecutable?.absolutePath) + logger.info("firebaseToolsVersion: {}", firebaseToolsVersion) + logger.info("npmExecutable: {}", npmExecutable.absolutePath) + logger.info("nodeExecutable: {}", nodeExecutable.absolutePath) logger.info("outputDirectory: {}", outputDirectory.absolutePath) project.delete(outputDirectory) project.mkdir(outputDirectory) + val oldPath = pathEnvironmentVariable.getOrElse("") + val newPath = nodeExecutable.absoluteFile.parent + File.pathSeparator + oldPath + val packageJsonFile = File(outputDirectory, "package.json") packageJsonFile.writeText("{}", Charsets.UTF_8) - runCommand(File(outputDirectory, "install.log.txt")) { - if (nodeExecutable !== null) { - val oldPath = pathEnvironmentVariable.getOrElse("") - val newPath = nodeExecutable.absoluteFile.parent + File.pathSeparator + oldPath - environment("PATH", newPath) - if (npmExecutable !== null) { - commandLine(npmExecutable.absolutePath) - } else { - commandLine(File(nodeExecutable.absoluteFile.parentFile, "npm")) - } - } else if (npmExecutable !== null) { - commandLine(npmExecutable.absolutePath) - } else { - commandLine("npm") - } - - args("install", "firebase-tools@$version") + val installLogFile = File(outputDirectory, "install.log.txt") + runCommand(installLogFile) { + environment("PATH", newPath) + commandLine(npmExecutable.absolutePath, "install", "firebase-tools@$firebaseToolsVersion") workingDir(outputDirectory) } } } internal fun SetupFirebaseToolsTask.configureFrom(providers: MyProjectProviders) { - version.set(providers.firebaseToolsVersion) + firebaseToolsVersion.set(providers.firebaseToolsVersion) npmExecutable.set(providers.npmExecutable) nodeExecutable.set(providers.nodeExecutable) outputDirectory.set(providers.buildDirectory.map { it.dir("firebase-tools") }) - pathEnvironmentVariable.set(providers.pathEnvironmentVariable) } From 668a7de4df14e2837b544671913fc7d810bf26e8 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Thu, 21 Nov 2024 04:18:49 +0000 Subject: [PATCH 32/79] fix untarring symlinks --- .../gradle/tasks/DownloadNodeJsTask.kt | 90 ++++++++++--------- 1 file changed, 49 insertions(+), 41 deletions(-) diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsTask.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsTask.kt index 85272897dd..7e3979387e 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsTask.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsTask.kt @@ -15,6 +15,7 @@ */ package com.google.firebase.example.dataconnect.gradle.tasks + import com.google.firebase.example.dataconnect.gradle.providers.MyProjectProviders import com.google.firebase.example.dataconnect.gradle.providers.OperatingSystem import com.google.firebase.example.dataconnect.gradle.tasks.DownloadNodeJsTask.Source @@ -31,6 +32,7 @@ import io.ktor.utils.io.jvm.javaio.copyTo import java.io.File import java.io.IOException import java.nio.file.Files +import java.nio.file.Paths import java.nio.file.attribute.FileTime import java.nio.file.attribute.PosixFilePermission import java.security.MessageDigest @@ -262,52 +264,57 @@ private fun Task.untar(file: File, destDir: File) { TarArchiveInputStream(gzipInputStream).use { tarInputStream -> while (true) { val tarEntry: TarArchiveEntry = tarInputStream.nextEntry ?: break - if (!tarEntry.isFile) { - continue - } val outputFile = File(destDir, tarEntry.name).absoluteFile - logger.debug("Extracting {}", outputFile.absolutePath) - outputFile.parentFile.mkdirs() - outputFile.outputStream().use { fileOutputStream -> - extractedByteCount += tarInputStream.copyTo(fileOutputStream) - } - extractedFileCount++ - - val lastModifiedTime = FileTime.from(tarEntry.lastModifiedTime.toInstant()) - try { - Files.setLastModifiedTime(outputFile.toPath(), lastModifiedTime) - } catch (e: IOException) { - logger.debug( - "Ignoring error from Files.setLastModifiedTime({}, {}): {}", - outputFile.absolutePath, - lastModifiedTime, - e.toString() - ) - } + if (tarEntry.isSymbolicLink) { + logger.debug("Creating symlink {} -> {}", outputFile.absolutePath, tarEntry.linkName) + Files.createSymbolicLink(outputFile.toPath(), Paths.get(tarEntry.linkName)) + extractedFileCount++ + } else if (tarEntry.isFile) { + logger.debug("Extracting {}", outputFile.absolutePath) + outputFile.parentFile.mkdirs() + outputFile.outputStream().use { fileOutputStream -> + extractedByteCount += tarInputStream.copyTo(fileOutputStream) + } + extractedFileCount++ + + val lastModifiedTime = FileTime.from(tarEntry.lastModifiedTime.toInstant()) + try { + Files.setLastModifiedTime(outputFile.toPath(), lastModifiedTime) + } catch (e: IOException) { + logger.debug( + "Ignoring error from Files.setLastModifiedTime({}, {}): {}", + outputFile.absolutePath, + lastModifiedTime, + e.toString() + ) + } - val newPermissions = buildSet { - add(PosixFilePermission.OWNER_READ) - add(PosixFilePermission.OWNER_WRITE) + val newPermissions = buildSet { + add(PosixFilePermission.OWNER_READ) + add(PosixFilePermission.OWNER_WRITE) - add(PosixFilePermission.GROUP_READ) - add(PosixFilePermission.OTHERS_READ) + add(PosixFilePermission.GROUP_READ) + add(PosixFilePermission.OTHERS_READ) - val mode = tarEntry.mode - if ((mode and 0x100) == 0x100) { - add(PosixFilePermission.OWNER_EXECUTE) - add(PosixFilePermission.GROUP_EXECUTE) - add(PosixFilePermission.OTHERS_EXECUTE) + val mode = tarEntry.mode + if ((mode and 0x100) == 0x100) { + add(PosixFilePermission.OWNER_EXECUTE) + add(PosixFilePermission.GROUP_EXECUTE) + add(PosixFilePermission.OTHERS_EXECUTE) + } } - } - try { - Files.setPosixFilePermissions(outputFile.toPath(), newPermissions) - } catch (e: UnsupportedOperationException) { - logger.debug( - "Ignoring error from Files.setPosixFilePermissions({}, {}}): {}", - outputFile.absolutePath, - newPermissions, - e.toString() - ) + try { + Files.setPosixFilePermissions(outputFile.toPath(), newPermissions) + } catch (e: UnsupportedOperationException) { + logger.debug( + "Ignoring error from Files.setPosixFilePermissions({}, {}}): {}", + outputFile.absolutePath, + newPermissions, + e.toString() + ) + } + } else { + continue } } } @@ -322,6 +329,7 @@ private fun Task.untar(file: File, destDir: File) { destDir.absolutePath ) } + private fun Task.unzip(file: File, destDir: File) { logger.info("Extracting {} to {}", file.absolutePath, destDir.absolutePath) var extractedFileCount = 0 From dc010b178c811c1c1eec3570e7f5caa8e9b692b2 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Fri, 22 Nov 2024 19:07:36 +0000 Subject: [PATCH 33/79] DownloadNodeJsTask.kt fixed --- .../gradle/tasks/DownloadNodeJsTask.kt | 79 +++++++------------ 1 file changed, 30 insertions(+), 49 deletions(-) diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsTask.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsTask.kt index 7e3979387e..2386081dfa 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsTask.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsTask.kt @@ -29,14 +29,6 @@ import io.ktor.client.plugins.logging.Logging import io.ktor.client.request.prepareGet import io.ktor.utils.io.ByteReadChannel import io.ktor.utils.io.jvm.javaio.copyTo -import java.io.File -import java.io.IOException -import java.nio.file.Files -import java.nio.file.Paths -import java.nio.file.attribute.FileTime -import java.nio.file.attribute.PosixFilePermission -import java.security.MessageDigest -import java.text.NumberFormat import kotlinx.coroutines.runBlocking import org.apache.commons.compress.archivers.tar.TarArchiveEntry import org.apache.commons.compress.archivers.tar.TarArchiveInputStream @@ -48,17 +40,26 @@ import org.gradle.api.DefaultTask import org.gradle.api.GradleException import org.gradle.api.Task import org.gradle.api.file.DirectoryProperty -import org.gradle.api.file.RegularFile +import org.gradle.api.file.RegularFileProperty import org.gradle.api.provider.Property import org.gradle.api.provider.Provider import org.gradle.api.tasks.Input import org.gradle.api.tasks.Internal import org.gradle.api.tasks.Nested import org.gradle.api.tasks.OutputDirectory +import org.gradle.api.tasks.OutputFile +import org.gradle.api.tasks.OutputFiles import org.gradle.api.tasks.TaskAction import org.gradle.kotlin.dsl.newInstance -import org.gradle.kotlin.dsl.property import org.pgpainless.sop.SOPImpl +import java.io.File +import java.io.IOException +import java.nio.file.Files +import java.nio.file.Paths +import java.nio.file.attribute.FileTime +import java.nio.file.attribute.PosixFilePermission +import java.security.MessageDigest +import java.text.NumberFormat abstract class DownloadNodeJsTask : DefaultTask() { @@ -68,18 +69,11 @@ abstract class DownloadNodeJsTask : DefaultTask() { @get:OutputDirectory abstract val outputDirectory: DirectoryProperty - @get:Internal - val nodeExecutable: Provider by lazy { - outputDirectory.map { it.file(nodeExecutableRelativePath.get()) } - } - - @get:Internal - val npmExecutable: Provider by lazy { - outputDirectory.map { it.file(npmExecutableRelativePath.get()) } - } + @get:OutputFile + abstract val nodeExecutable: RegularFileProperty - private val nodeExecutableRelativePath: Property = project.objects.property() - private val npmExecutableRelativePath: Property = project.objects.property() + @get:OutputFile + abstract val npmExecutable: RegularFileProperty @TaskAction fun run() { @@ -95,27 +89,6 @@ abstract class DownloadNodeJsTask : DefaultTask() { when (source) { is DownloadOfficialVersion -> downloadOfficialVersion(source, outputDirectory) } - - val nodeExecutableFiles = outputDirectory.walk().filter { - it.isFile && it.name == "node" - }.toList() - - val nodeExecutableFile = nodeExecutableFiles.singleOrNull() ?: throw GradleException( - "Found ${nodeExecutableFiles.size} node executable files " + - "in ${outputDirectory.absolutePath}, but expected exactly 1: " + - nodeExecutableFiles.joinToString(", ") { it.absolutePath } + - "(error code v6n2g6my3y)" - ) - val npmExecutableFile = File(nodeExecutableFile.absoluteFile.parent, "npm") - - nodeExecutableRelativePath.apply { - set(outputDirectory.toPath().relativize(nodeExecutableFile.toPath()).toString()) - finalizeValue() - } - npmExecutableRelativePath.apply { - set(outputDirectory.toPath().relativize(npmExecutableFile.toPath()).toString()) - finalizeValue() - } } sealed interface Source : java.io.Serializable { @@ -135,22 +108,30 @@ abstract class DownloadNodeJsTask : DefaultTask() { internal fun DownloadNodeJsTask.configureFrom(providers: MyProjectProviders) { source.run { - set(Source.providerFrom(providers)) + set(providers.source) disallowUnsafeRead() } outputDirectory.run { set(providers.buildDirectory.map { it.dir("node") }) disallowUnsafeRead() } + nodeExecutable.run { + set(providers.buildDirectory.map { it.file("node/node-v20.9.0-linux-x64/bin/node") }) + disallowUnsafeRead() + } + npmExecutable.run { + set(providers.buildDirectory.map { it.file("node/node-v20.9.0-linux-x64/bin/npm") }) + disallowUnsafeRead() + } } -internal fun Source.Companion.providerFrom(providers: MyProjectProviders): Provider { - val lazySource: Lazy = lazy(LazyThreadSafetyMode.PUBLICATION) { - val source = providers.objectFactory.newInstance() - source.updateFrom(providers) - source +internal val MyProjectProviders.source: Provider get() { + val lazySource: Lazy = lazy { + objectFactory.newInstance().also { + it.updateFrom(this@source) + } } - return providers.providerFactory.provider { lazySource.value } + return providerFactory.provider { lazySource.value } } internal fun DownloadOfficialVersion.updateFrom(providers: MyProjectProviders) { From f09fc200ed507b9f6672ac1fbc33c9450e270b9e Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Fri, 22 Nov 2024 22:03:27 +0000 Subject: [PATCH 34/79] it finally works in android studio --- .../dataconnect/gradle/providers/providers.kt | 4 +- .../gradle/tasks/DownloadNodeJsTask.kt | 125 ++++++++++++------ 2 files changed, 86 insertions(+), 43 deletions(-) diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/providers/providers.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/providers/providers.kt index b5f3866429..d2f671cd17 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/providers/providers.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/providers/providers.kt @@ -51,8 +51,8 @@ internal open class MyProjectProviders( providerFactory = project.providers, objectFactory = project.objects, projectDirectoryHierarchy = project.projectDirectoryHierarchy(), - nodeExecutable = downloadNodeJsTask.flatMap { it.nodeExecutable }, - npmExecutable = downloadNodeJsTask.flatMap { it.npmExecutable }, + nodeExecutable = downloadNodeJsTask.map { it.nodeOutputFiles.nodeExecutable }, + npmExecutable = downloadNodeJsTask.map { it.nodeOutputFiles.npmExecutable }, ext = project.extensions.getByType(), project.logger ) diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsTask.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsTask.kt index 2386081dfa..3fbf38c32b 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsTask.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsTask.kt @@ -18,8 +18,8 @@ package com.google.firebase.example.dataconnect.gradle.tasks import com.google.firebase.example.dataconnect.gradle.providers.MyProjectProviders import com.google.firebase.example.dataconnect.gradle.providers.OperatingSystem +import com.google.firebase.example.dataconnect.gradle.tasks.DownloadNodeJsTask.DownloadOfficialVersion import com.google.firebase.example.dataconnect.gradle.tasks.DownloadNodeJsTask.Source -import com.google.firebase.example.dataconnect.gradle.tasks.DownloadNodeJsTask.Source.DownloadOfficialVersion import io.ktor.client.HttpClient import io.ktor.client.call.body import io.ktor.client.engine.cio.CIO @@ -40,21 +40,20 @@ import org.gradle.api.DefaultTask import org.gradle.api.GradleException import org.gradle.api.Task import org.gradle.api.file.DirectoryProperty -import org.gradle.api.file.RegularFileProperty +import org.gradle.api.file.RegularFile import org.gradle.api.provider.Property import org.gradle.api.provider.Provider import org.gradle.api.tasks.Input import org.gradle.api.tasks.Internal import org.gradle.api.tasks.Nested import org.gradle.api.tasks.OutputDirectory -import org.gradle.api.tasks.OutputFile -import org.gradle.api.tasks.OutputFiles import org.gradle.api.tasks.TaskAction import org.gradle.kotlin.dsl.newInstance import org.pgpainless.sop.SOPImpl import java.io.File import java.io.IOException import java.nio.file.Files +import java.nio.file.Path import java.nio.file.Paths import java.nio.file.attribute.FileTime import java.nio.file.attribute.PosixFilePermission @@ -69,11 +68,21 @@ abstract class DownloadNodeJsTask : DefaultTask() { @get:OutputDirectory abstract val outputDirectory: DirectoryProperty - @get:OutputFile - abstract val nodeExecutable: RegularFileProperty + interface NodeOutputFiles { + val nodeExecutable: RegularFile + val npmExecutable: RegularFile + } + + @get:Internal + val nodeOutputFiles: NodeOutputFiles = object : NodeOutputFiles { + override val nodeExecutable: RegularFile get() = nodePathOf { it.nodeExecutable } + override val npmExecutable: RegularFile get() = nodePathOf { it.npmExecutable } - @get:OutputFile - abstract val npmExecutable: RegularFileProperty + private fun nodePathOf(block: (Source) -> String): RegularFile { + val executable: String = block(source.get()) + return outputDirectory.map { it.file(executable) }.get() + } + } @TaskAction fun run() { @@ -94,35 +103,42 @@ abstract class DownloadNodeJsTask : DefaultTask() { sealed interface Source : java.io.Serializable { companion object - interface DownloadOfficialVersion : Source { - companion object + @get:Internal + val nodeExecutable: String + + @get:Internal + val npmExecutable: String + } + + abstract class DownloadOfficialVersion : Source { + companion object + + @get:Input + abstract val version: Property + + @get:Nested + abstract val operatingSystem: Property - @get:Input - val version: Property + override val nodeExecutable: String get() = nodePathOf { it.nodeExecutable } - @get:Nested - val operatingSystem: Property + override val npmExecutable: String get() = nodePathOf { it.npmExecutable } + + private fun nodePathOf(block: (OperatingSystem.Type) -> Path): String { + val osType: OperatingSystem.Type = operatingSystem.get().type + val relativePath: Path = block(osType) + val path: Path = Path.of(downloadFileNameBase).resolve(relativePath) + return path.toString() } } } +private val OperatingSystem.Type.nodeExecutable: Path get() = if (this == OperatingSystem.Type.Windows) { Path.of("node.exe") } else { Path.of("bin", "node") } + +private val OperatingSystem.Type.npmExecutable: Path get() = if (this == OperatingSystem.Type.Windows) { Path.of("npm.cmd") } else { Path.of("bin", "npm") } + internal fun DownloadNodeJsTask.configureFrom(providers: MyProjectProviders) { - source.run { - set(providers.source) - disallowUnsafeRead() - } - outputDirectory.run { - set(providers.buildDirectory.map { it.dir("node") }) - disallowUnsafeRead() - } - nodeExecutable.run { - set(providers.buildDirectory.map { it.file("node/node-v20.9.0-linux-x64/bin/node") }) - disallowUnsafeRead() - } - npmExecutable.run { - set(providers.buildDirectory.map { it.file("node/node-v20.9.0-linux-x64/bin/npm") }) - disallowUnsafeRead() - } + source.set(providers.source) + outputDirectory.set(providers.buildDirectory.map { it.dir("node") }) } internal val MyProjectProviders.source: Provider get() { @@ -136,9 +152,7 @@ internal val MyProjectProviders.source: Provider get() { internal fun DownloadOfficialVersion.updateFrom(providers: MyProjectProviders) { version.set("20.9.0") - version.disallowUnsafeRead() operatingSystem.set(providers.operatingSystem) - operatingSystem.disallowUnsafeRead() } internal fun DownloadOfficialVersion.Companion.describe(source: DownloadOfficialVersion?): String = @@ -190,26 +204,55 @@ internal val DownloadOfficialVersion.downloadUrl: String get() = "$downloadUrlPr */ internal val DownloadOfficialVersion.downloadFileName: String get() { - val nodeVersion = version.get() - val os = operatingSystem.get() - val (osType, fileExtension) = when (val type = os.type) { - OperatingSystem.Type.Windows -> Pair("win", "zip") - OperatingSystem.Type.MacOS -> Pair("darwin", "tar.gz") - OperatingSystem.Type.Linux -> Pair("linux", "tar.gz") + val fileExtension: String = when (val type = os.type) { + OperatingSystem.Type.Windows -> "zip" + OperatingSystem.Type.MacOS -> "tar.gz" + OperatingSystem.Type.Linux -> "tar.gz" else -> throw GradleException( - "unable to determine node.js download URL for operating system type: $type " + + "unable to determine node.js download file extension for operating system type: $type " + "(operatingSystem=$os) (error code ead53smf45)" ) } - val osArch = when (os.arch) { + return "$downloadFileNameBase.$fileExtension" + } + +/** + * The base file name of the download for the Node.js binary distribution; + * that is, the file name without the ".zip" or ".tar.gz" extension. + * + * Here are some examples: + * * node-v20.9.0-darwin-arm64 + * * node-v20.9.0-darwin-x64 + * * node-v20.9.0-linux-arm64 + * * node-v20.9.0-linux-armv7l + * * node-v20.9.0-linux-x64 + * * node-v20.9.0-win-arm64 + * * node-v20.9.0-win-x64 + * * node-v20.9.0-win-x86 + */ +internal val DownloadOfficialVersion.downloadFileNameBase: String + get() { + val os: OperatingSystem = operatingSystem.get() + val osType: String = when (val type = os.type) { + OperatingSystem.Type.Windows -> "win" + OperatingSystem.Type.MacOS -> "darwin" + OperatingSystem.Type.Linux -> "linux" + else -> throw GradleException( + "unable to determine node.js download base file name for operating system type: $type " + + "(operatingSystem=$os) (error code m2grw3h7xz)" + ) + } + + val osArch: String = when (os.arch) { OperatingSystem.Architecture.Arm64 -> "arm64" OperatingSystem.Architecture.ArmV7 -> "armv7l" OperatingSystem.Architecture.X86 -> "x86" OperatingSystem.Architecture.X86_64 -> "x64" } - return "node-v$nodeVersion-$osType-$osArch.$fileExtension" + val nodeVersion = version.get() + return "node-v$nodeVersion-$osType-$osArch" } private data class DownloadedNodeJsFiles( From 585c9b26b52e0424f1eeb7ea9ed09410be2c3d91 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Fri, 22 Nov 2024 22:07:36 +0000 Subject: [PATCH 35/79] fixed it all --- .../dataconnect/gradle/providers/providers.kt | 4 ++-- .../gradle/tasks/DownloadNodeJsTask.kt | 17 ++++++----------- 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/providers/providers.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/providers/providers.kt index d2f671cd17..f105aff1ff 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/providers/providers.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/providers/providers.kt @@ -51,8 +51,8 @@ internal open class MyProjectProviders( providerFactory = project.providers, objectFactory = project.objects, projectDirectoryHierarchy = project.projectDirectoryHierarchy(), - nodeExecutable = downloadNodeJsTask.map { it.nodeOutputFiles.nodeExecutable }, - npmExecutable = downloadNodeJsTask.map { it.nodeOutputFiles.npmExecutable }, + nodeExecutable = downloadNodeJsTask.map { it.nodeExecutable }, + npmExecutable = downloadNodeJsTask.map { it.npmExecutable }, ext = project.extensions.getByType(), project.logger ) diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsTask.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsTask.kt index 3fbf38c32b..125769e2a6 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsTask.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsTask.kt @@ -68,20 +68,15 @@ abstract class DownloadNodeJsTask : DefaultTask() { @get:OutputDirectory abstract val outputDirectory: DirectoryProperty - interface NodeOutputFiles { - val nodeExecutable: RegularFile - val npmExecutable: RegularFile - } + @get:Internal + val nodeExecutable: RegularFile get() = nodePathOf { it.nodeExecutable } @get:Internal - val nodeOutputFiles: NodeOutputFiles = object : NodeOutputFiles { - override val nodeExecutable: RegularFile get() = nodePathOf { it.nodeExecutable } - override val npmExecutable: RegularFile get() = nodePathOf { it.npmExecutable } + val npmExecutable: RegularFile get() = nodePathOf { it.npmExecutable } - private fun nodePathOf(block: (Source) -> String): RegularFile { - val executable: String = block(source.get()) - return outputDirectory.map { it.file(executable) }.get() - } + private fun nodePathOf(block: (Source) -> String): RegularFile { + val executable: String = block(source.get()) + return outputDirectory.map { it.file(executable) }.get() } @TaskAction From a4e16ea8f15e6471a1fcd2afd63263760cbad1d2 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Fri, 22 Nov 2024 22:10:19 +0000 Subject: [PATCH 36/79] ktlint --- .../gradle/tasks/DownloadNodeJsTask.kt | 36 ++++++++++++------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsTask.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsTask.kt index 125769e2a6..14a4d0a824 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsTask.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsTask.kt @@ -29,6 +29,15 @@ import io.ktor.client.plugins.logging.Logging import io.ktor.client.request.prepareGet import io.ktor.utils.io.ByteReadChannel import io.ktor.utils.io.jvm.javaio.copyTo +import java.io.File +import java.io.IOException +import java.nio.file.Files +import java.nio.file.Path +import java.nio.file.Paths +import java.nio.file.attribute.FileTime +import java.nio.file.attribute.PosixFilePermission +import java.security.MessageDigest +import java.text.NumberFormat import kotlinx.coroutines.runBlocking import org.apache.commons.compress.archivers.tar.TarArchiveEntry import org.apache.commons.compress.archivers.tar.TarArchiveInputStream @@ -50,15 +59,6 @@ import org.gradle.api.tasks.OutputDirectory import org.gradle.api.tasks.TaskAction import org.gradle.kotlin.dsl.newInstance import org.pgpainless.sop.SOPImpl -import java.io.File -import java.io.IOException -import java.nio.file.Files -import java.nio.file.Path -import java.nio.file.Paths -import java.nio.file.attribute.FileTime -import java.nio.file.attribute.PosixFilePermission -import java.security.MessageDigest -import java.text.NumberFormat abstract class DownloadNodeJsTask : DefaultTask() { @@ -118,7 +118,7 @@ abstract class DownloadNodeJsTask : DefaultTask() { override val npmExecutable: String get() = nodePathOf { it.npmExecutable } - private fun nodePathOf(block: (OperatingSystem.Type) -> Path): String { + private fun nodePathOf(block: (OperatingSystem.Type) -> Path): String { val osType: OperatingSystem.Type = operatingSystem.get().type val relativePath: Path = block(osType) val path: Path = Path.of(downloadFileNameBase).resolve(relativePath) @@ -127,9 +127,19 @@ abstract class DownloadNodeJsTask : DefaultTask() { } } -private val OperatingSystem.Type.nodeExecutable: Path get() = if (this == OperatingSystem.Type.Windows) { Path.of("node.exe") } else { Path.of("bin", "node") } +private val OperatingSystem.Type.nodeExecutable: Path get() = + if (this == OperatingSystem.Type.Windows) { + Path.of("node.exe") + } else { + Path.of("bin", "node") + } -private val OperatingSystem.Type.npmExecutable: Path get() = if (this == OperatingSystem.Type.Windows) { Path.of("npm.cmd") } else { Path.of("bin", "npm") } +private val OperatingSystem.Type.npmExecutable: Path get() = + if (this == OperatingSystem.Type.Windows) { + Path.of("npm.cmd") + } else { + Path.of("bin", "npm") + } internal fun DownloadNodeJsTask.configureFrom(providers: MyProjectProviders) { source.set(providers.source) @@ -235,7 +245,7 @@ internal val DownloadOfficialVersion.downloadFileNameBase: String OperatingSystem.Type.Linux -> "linux" else -> throw GradleException( "unable to determine node.js download base file name for operating system type: $type " + - "(operatingSystem=$os) (error code m2grw3h7xz)" + "(operatingSystem=$os) (error code m2grw3h7xz)" ) } From 6b771be1a15386f25f5a7c493470d7a732a8f139 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Mon, 25 Nov 2024 12:08:38 -0500 Subject: [PATCH 37/79] OperatingSystem.kt: unhardcode linux-x64 (oops!) --- .../example/dataconnect/gradle/providers/OperatingSystem.kt | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/providers/OperatingSystem.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/providers/OperatingSystem.kt index 445547cca9..563577d30d 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/providers/OperatingSystem.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/providers/OperatingSystem.kt @@ -79,11 +79,7 @@ fun OperatingSystem.Companion.provider( ) } - OperatingSystem( - type = OperatingSystem.Type.Linux, - arch = OperatingSystem.Architecture.X86_64, - description = description - ) + OperatingSystem(type = type, arch = arch, description = description) } return objectFactory.property().apply { From 343f660309a700a113d0c9dddcaf00a9ce184d5c Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Mon, 25 Nov 2024 12:10:25 -0500 Subject: [PATCH 38/79] .github/workflows/android.yml: remove installation of node.js since that is now handled by the gradle plugin --- .github/workflows/android.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index ac114d9a8e..55dd574bcf 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -16,10 +16,6 @@ jobs: java-version: 17 - name: Setup Gradle uses: gradle/gradle-build-action@v2 - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: 20 - name: Check Snippets run: python scripts/checksnippets.py - name: Copy mock google_services.json From e47c9ec7d2259228dfc7a57765a036fc062fbc8f Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Mon, 25 Nov 2024 12:10:46 -0500 Subject: [PATCH 39/79] specify --stacktrace to gradle when run from github actions, to assist with debugigng gradle issues --- .github/workflows/android.yml | 2 +- build_pull_request.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index 55dd574bcf..3631aaba73 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -24,5 +24,5 @@ jobs: run: ./build_pull_request.sh if: github.event_name == 'pull_request' - name: Build with Gradle (Push) - run: ./gradlew clean ktlint assemble + run: ./gradlew --stacktrace clean ktlint assemble if: github.event_name != 'pull_request' diff --git a/build_pull_request.sh b/build_pull_request.sh index a6de4083a3..ee818ac245 100755 --- a/build_pull_request.sh +++ b/build_pull_request.sh @@ -31,4 +31,4 @@ done # Build echo "Building Pull Request with" echo $build_commands -eval "./gradlew clean ktlint ${build_commands}" +eval "./gradlew --stacktrace clean ktlint ${build_commands}" From fd6d44afbfeeada0f87424322a07b9592f737be6 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Mon, 25 Nov 2024 12:10:46 -0500 Subject: [PATCH 40/79] specify --info to gradle when run from github actions, to assist with debugigng gradle issues --- .github/workflows/android.yml | 2 +- build_pull_request.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index 3631aaba73..cdfc31d4df 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -24,5 +24,5 @@ jobs: run: ./build_pull_request.sh if: github.event_name == 'pull_request' - name: Build with Gradle (Push) - run: ./gradlew --stacktrace clean ktlint assemble + run: ./gradlew --info --stacktrace clean ktlint assemble if: github.event_name != 'pull_request' diff --git a/build_pull_request.sh b/build_pull_request.sh index ee818ac245..8c9706f043 100755 --- a/build_pull_request.sh +++ b/build_pull_request.sh @@ -31,4 +31,4 @@ done # Build echo "Building Pull Request with" echo $build_commands -eval "./gradlew --stacktrace clean ktlint ${build_commands}" +eval "./gradlew --info --stacktrace clean ktlint ${build_commands}" From c021ea851e2006226a37ba83543aa30baf35eb8d Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Mon, 25 Nov 2024 14:29:14 -0500 Subject: [PATCH 41/79] OperatingSystem.kt: fix os detection: got x86 and x86_64 backwards --- .../example/dataconnect/gradle/providers/OperatingSystem.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/providers/OperatingSystem.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/providers/OperatingSystem.kt index 563577d30d..dad53526ea 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/providers/OperatingSystem.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/providers/OperatingSystem.kt @@ -126,8 +126,8 @@ fun OperatingSystem.Architecture.Companion.forName(osArch: String): OperatingSys // https://github.com/gradle/gradle/blob/e745e6d369/platforms/native/platform-native/src/main/java/org/gradle/nativeplatform/platform/internal/Architectures.java#L26-L42 private fun OperatingSystem.Architecture.Companion.forLowerCaseName(osArch: String): OperatingSystem.Architecture? = when (osArch) { - "x86", "i386", "ia-32", "i686" -> OperatingSystem.Architecture.X86_64 - "x86-64", "x86_64", "amd64", "x64" -> OperatingSystem.Architecture.X86 + "x86", "i386", "ia-32", "i686" -> OperatingSystem.Architecture.X86 + "x86-64", "x86_64", "amd64", "x64" -> OperatingSystem.Architecture.X86_64 "arm-v7", "armv7", "arm", "arm32" -> OperatingSystem.Architecture.ArmV7 "aarch64", "arm-v8", "arm64" -> OperatingSystem.Architecture.Arm64 else -> null From 01cce18447490ddc47efaae606bd30c50b04082d Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Mon, 25 Nov 2024 15:14:26 -0500 Subject: [PATCH 42/79] DownloadNodeJsTask.kt: make it compatible with both org.apache.commons:commons-compress version 1.21 and 1.27.1. This fixes the following build error: ``` java.lang.NoSuchMethodError: 'org.apache.commons.compress.archivers.tar.TarArchiveEntry org.apache.commons.compress.archivers.tar.TarArchiveInputStream.getNextEntry()' ``` --- .../dataconnect/gradle/tasks/DownloadNodeJsTask.kt | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsTask.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsTask.kt index 14a4d0a824..1ba861917e 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsTask.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsTask.kt @@ -39,6 +39,7 @@ import java.nio.file.attribute.PosixFilePermission import java.security.MessageDigest import java.text.NumberFormat import kotlinx.coroutines.runBlocking +import org.apache.commons.compress.archivers.ArchiveEntry import org.apache.commons.compress.archivers.tar.TarArchiveEntry import org.apache.commons.compress.archivers.tar.TarArchiveInputStream import org.apache.commons.compress.archivers.zip.ZipArchiveEntry @@ -292,7 +293,10 @@ private fun Task.untar(file: File, destDir: File) { GzipCompressorInputStream(fileInputStream).use { gzipInputStream -> TarArchiveInputStream(gzipInputStream).use { tarInputStream -> while (true) { - val tarEntry: TarArchiveEntry = tarInputStream.nextEntry ?: break + val tarEntry: ArchiveEntry = tarInputStream.nextEntry ?: break + if (tarEntry !is TarArchiveEntry) { + continue + } val outputFile = File(destDir, tarEntry.name).absoluteFile if (tarEntry.isSymbolicLink) { logger.debug("Creating symlink {} -> {}", outputFile.absolutePath, tarEntry.linkName) @@ -306,7 +310,7 @@ private fun Task.untar(file: File, destDir: File) { } extractedFileCount++ - val lastModifiedTime = FileTime.from(tarEntry.lastModifiedTime.toInstant()) + val lastModifiedTime = FileTime.from(tarEntry.lastModifiedDate.toInstant()) try { Files.setLastModifiedTime(outputFile.toPath(), lastModifiedTime) } catch (e: IOException) { @@ -366,8 +370,8 @@ private fun Task.unzip(file: File, destDir: File) { file.inputStream().use { fileInputStream -> ZipArchiveInputStream(fileInputStream).use { zipInputStream -> while (true) { - val zipEntry: ZipArchiveEntry = zipInputStream.nextEntry ?: break - if (zipEntry.isDirectory) { + val zipEntry: ArchiveEntry = zipInputStream.nextEntry ?: break + if (zipEntry.isDirectory || zipEntry !is ZipArchiveEntry) { continue } val outputFile = File(destDir, zipEntry.name).absoluteFile @@ -378,7 +382,7 @@ private fun Task.unzip(file: File, destDir: File) { } extractedFileCount++ - val lastModifiedTime = FileTime.from(zipEntry.lastModifiedTime.toInstant()) + val lastModifiedTime = FileTime.from(zipEntry.lastModifiedDate.toInstant()) try { Files.setLastModifiedTime(outputFile.toPath(), lastModifiedTime) } catch (e: IOException) { From 6fd0016a4ee29c85142909303ad0882768b26efe Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Mon, 25 Nov 2024 15:14:26 -0500 Subject: [PATCH 43/79] DownloadNodeJsTask.kt: more fixes for NoSuchMethodError --- .../example/dataconnect/gradle/tasks/DownloadNodeJsTask.kt | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsTask.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsTask.kt index 1ba861917e..cb70c79eff 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsTask.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsTask.kt @@ -40,6 +40,7 @@ import java.security.MessageDigest import java.text.NumberFormat import kotlinx.coroutines.runBlocking import org.apache.commons.compress.archivers.ArchiveEntry +import org.apache.commons.compress.archivers.ArchiveInputStream import org.apache.commons.compress.archivers.tar.TarArchiveEntry import org.apache.commons.compress.archivers.tar.TarArchiveInputStream import org.apache.commons.compress.archivers.zip.ZipArchiveEntry @@ -291,7 +292,8 @@ private fun Task.untar(file: File, destDir: File) { var extractedByteCount = 0L file.inputStream().use { fileInputStream -> GzipCompressorInputStream(fileInputStream).use { gzipInputStream -> - TarArchiveInputStream(gzipInputStream).use { tarInputStream -> + val archiveInputStream: ArchiveInputStream<*> = TarArchiveInputStream(gzipInputStream) + archiveInputStream.use { tarInputStream -> while (true) { val tarEntry: ArchiveEntry = tarInputStream.nextEntry ?: break if (tarEntry !is TarArchiveEntry) { @@ -368,7 +370,8 @@ private fun Task.unzip(file: File, destDir: File) { var extractedFileCount = 0 var extractedByteCount = 0L file.inputStream().use { fileInputStream -> - ZipArchiveInputStream(fileInputStream).use { zipInputStream -> + val archiveInputStream: ArchiveInputStream<*> = ZipArchiveInputStream(fileInputStream) + archiveInputStream.use { zipInputStream -> while (true) { val zipEntry: ArchiveEntry = zipInputStream.nextEntry ?: break if (zipEntry.isDirectory || zipEntry !is ZipArchiveEntry) { From 2899860e38cc808cd77bc1f803993c4a49747e47 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Tue, 26 Nov 2024 03:49:52 +0000 Subject: [PATCH 44/79] Allow nodeVersion to be specified in build.gradle.kts --- dataconnect/app/build.gradle.kts | 18 +- dataconnect/buildSrc/build.gradle.kts | 10 +- .../gradle/DataConnectExtension.kt | 3 +- .../gradle/DataConnectGradlePlugin.kt | 2 +- .../gradle/providers/LocalConfigProviders.kt | 159 ------------------ .../dataconnect/gradle/providers/providers.kt | 81 +++++---- .../gradle/tasks/DownloadNodeJsTask.kt | 2 +- .../tasks/GenerateDataConnectSourcesTask.kt | 2 +- .../gradle/tasks/SetupFirebaseToolsTask.kt | 10 +- 9 files changed, 71 insertions(+), 216 deletions(-) delete mode 100644 dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/providers/LocalConfigProviders.kt diff --git a/dataconnect/app/build.gradle.kts b/dataconnect/app/build.gradle.kts index c8e4dbb5c1..8d442ede2d 100644 --- a/dataconnect/app/build.gradle.kts +++ b/dataconnect/app/build.gradle.kts @@ -82,12 +82,20 @@ dependencies { debugImplementation(libs.androidx.ui.test.manifest) } +// This "dataconnect" section is a Gradle "extension" that is added by the +// "com.google.firebase.example.dataconnect.gradle" Gradle plugin. It must be +// present to specify configuration details for the Data Connect Gradle plugin. dataconnect { - // The version of https://www.npmjs.com/package/firebase-tools to use to perform the - // Data Connect code generation. - firebaseToolsVersion = "13.25.0" + // The version of Node.js (https://nodejs.org) to use to install and run the + // Firebase CLI. This version of Node.js will be downloaded and extracted + // for exclusive use of the Data Connect Gradle plugin. + nodeVersion = "20.18.1" - // The directory that contains dataconnect.yaml to use as input when performing - // the Data Connect code generation. + // The version of the Firebase CLI (https://www.npmjs.com/package/firebase-tools) + // to use to perform the Data Connect Kotlin code generation. + firebaseCliVersion = "13.25.0" + + // The directory that contains dataconnect.yaml that specifies the Data + // Connect schema and connectors whose code to generate. dataConnectConfigDir = file("../dataconnect") } diff --git a/dataconnect/buildSrc/build.gradle.kts b/dataconnect/buildSrc/build.gradle.kts index edd5ab0d90..b518929408 100644 --- a/dataconnect/buildSrc/build.gradle.kts +++ b/dataconnect/buildSrc/build.gradle.kts @@ -26,15 +26,7 @@ dependencies { implementation(libs.android.gradlePlugin.api) implementation(libs.snakeyaml) - // TODO: Upgrade the `tomlkt` dependency to 0.4.0 or later once the gradle - // wrapper version used by this project uses a sufficiently-recent version - // of kotlin. At the time of writing, `embeddedKotlinVersion` is 1.9.22, - // which requires an older version of `tomlkt` because the newer versions - // depend on a newer version of the `kotlinx.serialization` plugin, which - // requires a newer version of Kotlin. - implementation("net.peanuuutz.tomlkt:tomlkt:0.3.7") - - val ktorVersion = "2.3.12" + val ktorVersion = "2.3.13" implementation("io.ktor:ktor-client-core:$ktorVersion") implementation("io.ktor:ktor-client-cio:$ktorVersion") implementation("io.ktor:ktor-client-logging:$ktorVersion") diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectExtension.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectExtension.kt index 0c383f3321..c073bc4dea 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectExtension.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectExtension.kt @@ -19,6 +19,7 @@ package com.google.firebase.example.dataconnect.gradle import java.io.File interface DataConnectExtension { - var firebaseToolsVersion: String? + var firebaseCliVersion: String? + var nodeVersion: String? var dataConnectConfigDir: File? } diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectGradlePlugin.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectGradlePlugin.kt index 9db50d7f8e..05f24571a8 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectGradlePlugin.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectGradlePlugin.kt @@ -45,7 +45,7 @@ abstract class DataConnectGradlePlugin : Plugin { val androidComponents = project.extensions.getByType() androidComponents.onVariants { variant -> - val variantProviders = MyVariantProviders(project, variant, setupFirebaseToolsTask, providers) + val variantProviders = MyVariantProviders(variant, setupFirebaseToolsTask, providers) registerVariantTasks(project, variant, variantProviders) } } diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/providers/LocalConfigProviders.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/providers/LocalConfigProviders.kt deleted file mode 100644 index bdfca4c2a1..0000000000 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/providers/LocalConfigProviders.kt +++ /dev/null @@ -1,159 +0,0 @@ -/* - * 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.example.dataconnect.gradle.providers - -import java.io.File -import java.io.FileNotFoundException -import kotlinx.serialization.Serializable -import net.peanuuutz.tomlkt.Toml -import net.peanuuutz.tomlkt.decodeFromString -import org.gradle.api.GradleException -import org.gradle.api.file.Directory -import org.gradle.api.file.RegularFile -import org.gradle.api.logging.Logger -import org.gradle.api.provider.Provider -import org.gradle.api.provider.ProviderFactory - -internal class LocalConfigProviders( - projectDirectoryHierarchy: List, - private val providerFactory: ProviderFactory, - private val logger: Logger -) { - - val npmExecutable: Provider = provideFileFromLocalSettings("npmExecutable") { - it.npmExecutable - } - - val nodeExecutable: Provider = provideFileFromLocalSettings("nodeExecutable") { - it.nodeExecutable - } - - private fun provideFileFromLocalSettings(name: String, retriever: (LocalConfig) -> String?): Provider = - providerFactory.provider { - val localConfigsList = localConfigs.get() - val regularFile = localConfigsList.firstNotNullOfOrNull { localConfigInfo -> - val localConfig = localConfigInfo.localConfig ?: return@firstNotNullOfOrNull null - val value = retriever(localConfig) - if (value === null) { - null - } else { - val regularFile = localConfigInfo.directory.file(value) - val file = regularFile.asFile - logger.info( - "Found {} defined in {}: {} (which resolves to: {})", - name, - localConfigInfo.file.absolutePath, - value, - file.absolutePath - ) - if (!file.exists()) { - throw GradleException( - "file not found: ${file.absolutePath} " + - "as specified for \"$name\" in ${localConfigInfo.file.absolutePath} " + - "(error code g3b59pdate)" - ) - } - regularFile - } - } - if (regularFile === null) { - logger.info( - "None of the {} found local settings files defined {}: {}", - localConfigsList.size, - name, - localConfigsList.joinToString(", ") { it.file.absolutePath } - ) - } - regularFile - } - - private val localConfigs: Provider> = run { - val lazyResult: Lazy> = lazy { - val localConfigFiles = projectDirectoryHierarchy.map { - val regularFile = it.file("dataconnect.local.toml") - Triple(it, regularFile, regularFile.asFile) - } - logger.info( - "Loading {} local config files: {}", - localConfigFiles.size, - localConfigFiles.joinToString(", ") { it.third.absolutePath } - ) - val toml = Toml { this.ignoreUnknownKeys = true } - - localConfigFiles - .map { (directory: Directory, regularFile: RegularFile, file: File) -> - val text = file.runCatching { readText() }.fold( - onSuccess = { it }, - onFailure = { exception -> - if (exception is FileNotFoundException) { - logger.info("Ignoring non-existent local config file: ${file.absolutePath}") - null // ignore non-existent config files - } else { - throw GradleException( - "reading local config file failed: ${file.absolutePath} ($exception)" + - " (error code bj7dxvvw5p)", - exception - ) - } - } - ) - LocalConfigTextInfo(text, file, directory, regularFile) - }.map { (text, file, directory, regularFile) -> - val localConfig = if (text === null) { - null - } else { - toml.runCatching { - decodeFromString(text, "dataconnect") - }.fold( - onSuccess = { - logger.info("Loaded local config file {}: {}", file.absolutePath, it) - it - }, - onFailure = { exception -> - throw GradleException( - "parsing local config file failed: ${file.absolutePath} ($exception)" + - " (error code 44dkc2vvpq)", - exception - ) - } - ) - } - LocalConfigInfo(localConfig, file, directory, regularFile) - } - } - providerFactory.provider { lazyResult.value } - } -} - -@Serializable -private data class LocalConfig( - val npmExecutable: String? = null, - val nodeExecutable: String? = null -) -private data class LocalConfigTextInfo( - val localConfigText: String?, - val file: File, - val directory: Directory, - val regularFile: RegularFile -) - -private data class LocalConfigInfo( - val localConfig: LocalConfig?, - val file: File, - val directory: Directory, - val regularFile: RegularFile -) diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/providers/providers.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/providers/providers.kt index f105aff1ff..f98def662e 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/providers/providers.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/providers/providers.kt @@ -20,10 +20,12 @@ import com.android.build.api.variant.ApplicationVariant import com.google.firebase.example.dataconnect.gradle.DataConnectExtension import com.google.firebase.example.dataconnect.gradle.tasks.DownloadNodeJsTask import com.google.firebase.example.dataconnect.gradle.tasks.SetupFirebaseToolsTask +import java.io.File import org.gradle.api.GradleException import org.gradle.api.Project import org.gradle.api.file.Directory import org.gradle.api.file.DirectoryProperty +import org.gradle.api.file.ProjectLayout import org.gradle.api.file.RegularFile import org.gradle.api.logging.Logger import org.gradle.api.model.ObjectFactory @@ -36,7 +38,7 @@ internal open class MyProjectProviders( projectBuildDirectory: DirectoryProperty, val providerFactory: ProviderFactory, val objectFactory: ObjectFactory, - projectDirectoryHierarchy: List, + val projectLayout: ProjectLayout, val nodeExecutable: Provider, val npmExecutable: Provider, ext: DataConnectExtension, @@ -50,7 +52,7 @@ internal open class MyProjectProviders( projectBuildDirectory = project.layout.buildDirectory, providerFactory = project.providers, objectFactory = project.objects, - projectDirectoryHierarchy = project.projectDirectoryHierarchy(), + projectLayout = project.layout, nodeExecutable = downloadNodeJsTask.map { it.nodeExecutable }, npmExecutable = downloadNodeJsTask.map { it.npmExecutable }, ext = project.extensions.getByType(), @@ -61,58 +63,69 @@ internal open class MyProjectProviders( val buildDirectory: Provider = projectBuildDirectory.map { it.dir("dataconnect") } - val firebaseToolsVersion: Provider = - providerFactory.provider { - ext.firebaseToolsVersion + val firebaseCliVersion: Provider = run { + val lazyFirebaseCliVersion: Lazy = lazy { + ext.firebaseCliVersion ?: throw GradleException( - "dataconnect.firebaseToolsVersion must be set in your " + - "build.gradle or build.gradle.kts " + - "(error code xbmvkc3mtr)" + "dataconnect.firebaseCliVersion must be set in " + + "build.gradle or build.gradle.kts to " + + "specify the version of the Firebase CLI npm package " + + "(https://www.npmjs.com/package/firebase-tools) to use " + + "(e.g. \"13.25.0\") (error code xbmvkc3mtr)" ) } + providerFactory.provider { lazyFirebaseCliVersion.value } + } + + val nodeVersion: Provider = run { + val lazyNodeVersion: Lazy = lazy { + ext.nodeVersion + ?: throw GradleException( + "dataconnect.nodeVersion must be set in " + + "build.gradle or build.gradle.kts to " + + "specify the version of Node.js (https://nodejs.org) " + + "to install (e.g. \"20.9.0\") (error code 3acj27az2c)" + ) + } + providerFactory.provider { lazyNodeVersion.value } + } - private val localConfigProviders = LocalConfigProviders(projectDirectoryHierarchy, providerFactory, logger) + val dataConnectConfigDir: Provider = run { + val lazyDataConnectConfigDir: Lazy = lazy { + @Suppress("ktlint:standard:max-line-length") + val dataConnectConfigDirFile: File = + ext.dataConnectConfigDir + ?: throw GradleException( + "dataconnect.dataConnectConfigDir must be set in " + + "build.gradle or build.gradle.kts to " + + "specify the directory that defines the Data Connect schema and " + + "connectors whose Kotlin code to generate code. That is, the directory " + + "containing the dataconnect.yaml file. For details, see " + + "https://firebase.google.com/docs/data-connect/configuration-reference#dataconnect.yaml-configuration " + + "(e.g. file(\"../dataconnect\")) (error code a3ch245mbd)" + ) + projectLayout.projectDirectory.dir(dataConnectConfigDirFile.path) + } + providerFactory.provider { lazyDataConnectConfigDir.value } + } } internal open class MyVariantProviders( variant: ApplicationVariant, val projectProviders: MyProjectProviders, - val firebaseExecutable: Provider, - ext: DataConnectExtension, - objectFactory: ObjectFactory + val firebaseExecutable: Provider ) { constructor( - project: Project, variant: ApplicationVariant, setupFirebaseToolsTask: TaskProvider, projectProviders: MyProjectProviders ) : this( variant = variant, projectProviders = projectProviders, - firebaseExecutable = setupFirebaseToolsTask.flatMap { it.firebaseExecutable }, - ext = project.extensions.getByType(), - objectFactory = project.objects + firebaseExecutable = setupFirebaseToolsTask.flatMap { it.firebaseExecutable } ) val buildDirectory: Provider = projectProviders.buildDirectory.map { it.dir("variants/${variant.name}") } - - val dataConnectConfigDir: Provider = run { - val dir = - ext.dataConnectConfigDir - ?: throw GradleException( - "dataconnect.dataConnectConfigDir must be set in your build.gradle or build.gradle.kts " + - "(error code xbmvkc3mtr)" - ) - objectFactory.directoryProperty().also { property -> property.set(dir) } - } -} - -private fun Project.projectDirectoryHierarchy(): List = buildList { - var curProject: Project? = this@projectDirectoryHierarchy - while (curProject !== null) { - add(curProject.layout.projectDirectory) - curProject = curProject.parent - } } diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsTask.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsTask.kt index cb70c79eff..3f82cfb67d 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsTask.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsTask.kt @@ -158,7 +158,7 @@ internal val MyProjectProviders.source: Provider get() { } internal fun DownloadOfficialVersion.updateFrom(providers: MyProjectProviders) { - version.set("20.9.0") + version.set(providers.nodeVersion) operatingSystem.set(providers.operatingSystem) } diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/GenerateDataConnectSourcesTask.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/GenerateDataConnectSourcesTask.kt index 4d2d64c5d9..3ce80ce134 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/GenerateDataConnectSourcesTask.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/GenerateDataConnectSourcesTask.kt @@ -90,7 +90,7 @@ abstract class GenerateDataConnectSourcesTask : DefaultTask() { } internal fun GenerateDataConnectSourcesTask.configureFrom(providers: MyVariantProviders) { - dataConnectConfigDir.set(providers.dataConnectConfigDir) + dataConnectConfigDir.set(providers.projectProviders.dataConnectConfigDir) firebaseExecutable.set(providers.firebaseExecutable) nodeExecutable.set(providers.projectProviders.nodeExecutable) tweakedDataConnectConfigDir.set(providers.buildDirectory.map { it.dir("config") }) diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/SetupFirebaseToolsTask.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/SetupFirebaseToolsTask.kt index 8e9ccb5356..e9f265c4cc 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/SetupFirebaseToolsTask.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/SetupFirebaseToolsTask.kt @@ -33,7 +33,7 @@ import org.gradle.api.tasks.TaskAction abstract class SetupFirebaseToolsTask : DefaultTask() { @get:Input - abstract val firebaseToolsVersion: Property + abstract val firebaseCliVersion: Property @get:InputFile abstract val npmExecutable: RegularFileProperty @@ -53,12 +53,12 @@ abstract class SetupFirebaseToolsTask : DefaultTask() { @TaskAction fun run() { - val firebaseToolsVersion: String = firebaseToolsVersion.get() + val firebaseCliVersion: String = firebaseCliVersion.get() val npmExecutable: File = npmExecutable.get().asFile val nodeExecutable: File = nodeExecutable.get().asFile val outputDirectory: File = outputDirectory.get().asFile - logger.info("firebaseToolsVersion: {}", firebaseToolsVersion) + logger.info("firebaseCliVersion: {}", firebaseCliVersion) logger.info("npmExecutable: {}", npmExecutable.absolutePath) logger.info("nodeExecutable: {}", nodeExecutable.absolutePath) logger.info("outputDirectory: {}", outputDirectory.absolutePath) @@ -75,14 +75,14 @@ abstract class SetupFirebaseToolsTask : DefaultTask() { val installLogFile = File(outputDirectory, "install.log.txt") runCommand(installLogFile) { environment("PATH", newPath) - commandLine(npmExecutable.absolutePath, "install", "firebase-tools@$firebaseToolsVersion") + commandLine(npmExecutable.absolutePath, "install", "firebase-tools@$firebaseCliVersion") workingDir(outputDirectory) } } } internal fun SetupFirebaseToolsTask.configureFrom(providers: MyProjectProviders) { - firebaseToolsVersion.set(providers.firebaseToolsVersion) + firebaseCliVersion.set(providers.firebaseCliVersion) npmExecutable.set(providers.npmExecutable) nodeExecutable.set(providers.nodeExecutable) outputDirectory.set(providers.buildDirectory.map { it.dir("firebase-tools") }) From 882f073e66292ac55fca85707bc891937f0485ad Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Tue, 26 Nov 2024 12:30:49 +0000 Subject: [PATCH 45/79] suppoer caching --- dataconnect/app/build.gradle.kts | 6 + dataconnect/buildSrc/build.gradle.kts | 1 + .../gradle/DataConnectExtension.kt | 27 ++- .../example/dataconnect/gradle/cache/cache.kt | 164 ++++++++++++++++++ .../dataconnect/gradle/providers/providers.kt | 19 ++ .../gradle/tasks/DownloadNodeJsTask.kt | 146 ++++++++++------ 6 files changed, 312 insertions(+), 51 deletions(-) create mode 100644 dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/cache/cache.kt diff --git a/dataconnect/app/build.gradle.kts b/dataconnect/app/build.gradle.kts index 8d442ede2d..49c51e2512 100644 --- a/dataconnect/app/build.gradle.kts +++ b/dataconnect/app/build.gradle.kts @@ -98,4 +98,10 @@ dataconnect { // The directory that contains dataconnect.yaml that specifies the Data // Connect schema and connectors whose code to generate. dataConnectConfigDir = file("../dataconnect") + + // The directory where the Data Connect gradle plugin will cache its data. + // This can be set with the "DataConnectGradleCacheDir" project property, + // such as by specifying -PDataConnectGradleCacheDir=myCacheDir on the + // Gradle command line. + cacheDir = findProperty("DataConnectGradleCacheDir")?.let { file(it) } } diff --git a/dataconnect/buildSrc/build.gradle.kts b/dataconnect/buildSrc/build.gradle.kts index b518929408..6533d339cd 100644 --- a/dataconnect/buildSrc/build.gradle.kts +++ b/dataconnect/buildSrc/build.gradle.kts @@ -25,6 +25,7 @@ java { toolchain { languageVersion.set(JavaLanguageVersion.of(17)) } } dependencies { implementation(libs.android.gradlePlugin.api) implementation(libs.snakeyaml) + implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3") val ktorVersion = "2.3.13" implementation("io.ktor:ktor-client-core:$ktorVersion") diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectExtension.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectExtension.kt index c073bc4dea..9508774a31 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectExtension.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectExtension.kt @@ -19,7 +19,32 @@ package com.google.firebase.example.dataconnect.gradle import java.io.File interface DataConnectExtension { - var firebaseCliVersion: String? + + /** + * The version of Node.js (https://nodejs.org) to use to install and run the + * Firebase CLI. This version of Node.js will be downloaded and extracted + * for exclusive use of the Data Connect Gradle plugin. + */ var nodeVersion: String? + + /** + * The version of the Firebase CLI (https://www.npmjs.com/package/firebase-tools) + * to use to perform the Data Connect Kotlin code generation. + */ + var firebaseCliVersion: String? + + /** + * The directory that contains dataconnect.yaml that specifies the Data + * Connect schema and connectors whose code to generate. + */ var dataConnectConfigDir: File? + + /** + * The directory into which cached data for the Data Connect plugin will be + * stored. This property may be `null`. If it is not `null` then the + * directory will be created on-demand when it is needed. The contents of + * this directory are never deleted; therefore, it is prudent to + * periodically delete the contents of this directory. + */ + var cacheDir: File? } diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/cache/cache.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/cache/cache.kt new file mode 100644 index 0000000000..3baecf1f55 --- /dev/null +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/cache/cache.kt @@ -0,0 +1,164 @@ +/* + * 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.example.dataconnect.gradle.cache + +import kotlinx.serialization.Serializable +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json +import org.gradle.api.logging.Logger +import java.io.ByteArrayOutputStream +import java.io.File +import java.nio.ByteBuffer +import java.nio.channels.FileChannel +import java.nio.channels.ReadableByteChannel +import java.nio.charset.StandardCharsets +import java.nio.file.StandardOpenOption +import java.util.UUID + +class CacheManager(val rootDirectory: File) { + + private val entriesFile: File = File(rootDirectory, ENTRIES_FILENAME) + + private var allocatedDirectories = mutableListOf() + + fun isAllocated(dir: File): Boolean { + val allocatedEntry: AllocatedDirectory? = synchronized(allocatedDirectories) { + allocatedDirectories.firstOrNull {it.directory == dir} + } + return allocatedEntry !== null + } + + fun isCommitted(dir: File, logger: Logger): Boolean { + if (dir.parentFile != rootDirectory) { + return false + } + val directoryName = dir.name + val globalEntry: GlobalEntry? = loadGlobalEntries(logger).firstOrNull { it.directory == directoryName } + return globalEntry !== null + } + + fun getOrAllocateDir(domain: String, key: String, logger: Logger): File = findDir(domain=domain, key=key, logger) ?: allocateDir(domain=domain, key=key) + + fun findDir(domain: String, key: String, logger: Logger): File? { + val globalEntries = loadGlobalEntries(logger) + return globalEntries + .filter { it.domain == domain && it.key == key } + .map { File(rootDirectory, it.directory) } + .singleOrNull() + } + + fun allocateDir(domain: String, key: String): File { + val directory = File(rootDirectory, UUID.randomUUID().toString()) + val allocatedDirectory = AllocatedDirectory(domain=domain, key=key, directory=directory) + synchronized(allocatedDirectories) { + allocatedDirectories.add(allocatedDirectory) + } + return directory + } + + fun commitDir(dir: File, logger: Logger) { + val allocatedDirectory: AllocatedDirectory = synchronized(allocatedDirectories) { + val index = allocatedDirectories.indexOfFirst { it.directory == dir } + require(index >= 0) { + "The given directory, $dir, has not been allocated or was already committed" + } + allocatedDirectories.removeAt(index) + } + + val newGlobalEntry = GlobalEntry( + domain=allocatedDirectory.domain, + key=allocatedDirectory.key, + directory = dir.name + ) + + withEntriesFile(logger) { entriesFile, channel -> + logger.info("Inserting or updating cache entry {} in file: {}", newGlobalEntry, entriesFile.absolutePath) + val globalEntries = loadGlobalEntries(channel).filterNot { it.domain == newGlobalEntry.domain && it.key == newGlobalEntry.key } + + val json = Json { prettyPrint = true } + val newText = json.encodeToString(globalEntries + listOf(newGlobalEntry)) + + channel.truncate(0) + channel.position(0) + channel.write(ByteBuffer.wrap(newText.toByteArray(StandardCharsets.UTF_8))) + } + + } + + private fun withEntriesFile(logger: Logger, block: (File, FileChannel) -> T): T { + logger.debug("Opening Data Connect cache metadata file: {}", entriesFile.absolutePath) + entriesFile.parentFile.mkdirs() + return FileChannel.open(entriesFile.toPath(), StandardOpenOption.READ, StandardOpenOption.WRITE, StandardOpenOption.CREATE).use { channel -> + channel.lock().use { + block(entriesFile, channel) + } + } + } + + private fun loadGlobalEntries(logger: Logger): List = + withEntriesFile(logger) { _, channel -> + loadGlobalEntries(channel) + } + + + private fun loadGlobalEntries(channel: FileChannel): List { + val fileBytes = channel.readAllBytes() + val fileText = String(fileBytes, StandardCharsets.UTF_8) + if (fileText.isBlank()) { + return emptyList() + } + return Json.decodeFromString>(fileText) + } + + override fun toString() = "CacheManager(${rootDirectory.absolutePath})" + + @Serializable + private data class GlobalEntry( + val domain: String, + val key: String, + val directory: String + ) + + private data class AllocatedDirectory( + val domain: String, + val key: String, + val directory: File + ) + + companion object { + + private const val ENTRIES_FILENAME = "entries.json" + + } +} + +private fun ReadableByteChannel.readAllBytes(): ByteArray { + val buffer = ByteArray(8192) + val byteBuffer = ByteBuffer.wrap(buffer) + val baos = ByteArrayOutputStream() + + while (true) { + val numBytesRead = read(byteBuffer) + if (numBytesRead < 0) { + break + } + baos.write(buffer, 0, numBytesRead) + byteBuffer.flip() + } + + return baos.toByteArray() +} \ No newline at end of file diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/providers/providers.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/providers/providers.kt index f98def662e..a16b3145cd 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/providers/providers.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/providers/providers.kt @@ -18,6 +18,7 @@ package com.google.firebase.example.dataconnect.gradle.providers import com.android.build.api.variant.ApplicationVariant import com.google.firebase.example.dataconnect.gradle.DataConnectExtension +import com.google.firebase.example.dataconnect.gradle.cache.CacheManager import com.google.firebase.example.dataconnect.gradle.tasks.DownloadNodeJsTask import com.google.firebase.example.dataconnect.gradle.tasks.SetupFirebaseToolsTask import java.io.File @@ -108,6 +109,24 @@ internal open class MyProjectProviders( } providerFactory.provider { lazyDataConnectConfigDir.value } } + + private val dataConnectCacheDir: Provider = run { + val lazyDataConnectCacheDir: Lazy = lazy { + ext.cacheDir?.let { cacheDir -> + projectLayout.projectDirectory.dir(cacheDir.path).dir("v1") + } + } + providerFactory.provider { lazyDataConnectCacheDir.value } + } + + val cacheManager: Provider = run { + val lazyCacheManager: Lazy = lazy { + dataConnectCacheDir.orNull?.let { + CacheManager(it.asFile) + } + } + providerFactory.provider { lazyCacheManager.value } + } } internal open class MyVariantProviders( diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsTask.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsTask.kt index 3f82cfb67d..48c5473912 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsTask.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsTask.kt @@ -16,6 +16,7 @@ package com.google.firebase.example.dataconnect.gradle.tasks +import com.google.firebase.example.dataconnect.gradle.cache.CacheManager import com.google.firebase.example.dataconnect.gradle.providers.MyProjectProviders import com.google.firebase.example.dataconnect.gradle.providers.OperatingSystem import com.google.firebase.example.dataconnect.gradle.tasks.DownloadNodeJsTask.DownloadOfficialVersion @@ -29,16 +30,9 @@ import io.ktor.client.plugins.logging.Logging import io.ktor.client.request.prepareGet import io.ktor.utils.io.ByteReadChannel import io.ktor.utils.io.jvm.javaio.copyTo -import java.io.File -import java.io.IOException -import java.nio.file.Files -import java.nio.file.Path -import java.nio.file.Paths -import java.nio.file.attribute.FileTime -import java.nio.file.attribute.PosixFilePermission -import java.security.MessageDigest -import java.text.NumberFormat import kotlinx.coroutines.runBlocking +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json import org.apache.commons.compress.archivers.ArchiveEntry import org.apache.commons.compress.archivers.ArchiveInputStream import org.apache.commons.compress.archivers.tar.TarArchiveEntry @@ -50,6 +44,7 @@ import org.bouncycastle.util.encoders.Hex import org.gradle.api.DefaultTask import org.gradle.api.GradleException import org.gradle.api.Task +import org.gradle.api.file.Directory import org.gradle.api.file.DirectoryProperty import org.gradle.api.file.RegularFile import org.gradle.api.provider.Property @@ -61,6 +56,15 @@ import org.gradle.api.tasks.OutputDirectory import org.gradle.api.tasks.TaskAction import org.gradle.kotlin.dsl.newInstance import org.pgpainless.sop.SOPImpl +import java.io.File +import java.io.IOException +import java.nio.file.Files +import java.nio.file.Path +import java.nio.file.Paths +import java.nio.file.attribute.FileTime +import java.nio.file.attribute.PosixFilePermission +import java.security.MessageDigest +import java.text.NumberFormat abstract class DownloadNodeJsTask : DefaultTask() { @@ -70,6 +74,9 @@ abstract class DownloadNodeJsTask : DefaultTask() { @get:OutputDirectory abstract val outputDirectory: DirectoryProperty + @get:Internal + abstract val cacheManager: Property + @get:Internal val nodeExecutable: RegularFile get() = nodePathOf { it.nodeExecutable } @@ -83,18 +90,27 @@ abstract class DownloadNodeJsTask : DefaultTask() { @TaskAction fun run() { - val source = source.get() - val outputDirectoryRegularFile = outputDirectory.get() - val outputDirectory = outputDirectoryRegularFile.asFile + val source: Source = source.get() + val outputDirectoryRegularFile: Directory = outputDirectory.get() + val outputDirectory: File = outputDirectoryRegularFile.asFile + val cacheManager: CacheManager? = cacheManager.orNull logger.info("source: {}", Source.describe(source)) logger.info("outputDirectory: {}", outputDirectory.absolutePath) + logger.info("cacheManager: {}", cacheManager) + + if (cacheManager !== null && cacheManager.isCommitted(outputDirectory, logger)) { + logger.info("Using cached data from directory: {}", outputDirectory.absolutePath) + return + } project.delete(outputDirectory) when (source) { is DownloadOfficialVersion -> downloadOfficialVersion(source, outputDirectory) } + + cacheManager?.commitDir(outputDirectory, logger) } sealed interface Source : java.io.Serializable { @@ -105,6 +121,9 @@ abstract class DownloadNodeJsTask : DefaultTask() { @get:Internal val npmExecutable: String + + @get:Internal + val cacheKey: String } abstract class DownloadOfficialVersion : Source { @@ -120,6 +139,21 @@ abstract class DownloadNodeJsTask : DefaultTask() { override val npmExecutable: String get() = nodePathOf { it.npmExecutable } + override val cacheKey: String + get() { + val os: OperatingSystem = operatingSystem.get() + return buildString { + append("DownloadOfficialVersion{") + append("nodeVersion=") + append(Json.encodeToString(version.get())) + append(", os=") + append(Json.encodeToString(os.type.name.lowercase())) + append(", arch=") + append(Json.encodeToString(os.arch.name.lowercase())) + append("}") + } + } + private fun nodePathOf(block: (OperatingSystem.Type) -> Path): String { val osType: OperatingSystem.Type = operatingSystem.get().type val relativePath: Path = block(osType) @@ -129,33 +163,49 @@ abstract class DownloadNodeJsTask : DefaultTask() { } } -private val OperatingSystem.Type.nodeExecutable: Path get() = - if (this == OperatingSystem.Type.Windows) { - Path.of("node.exe") - } else { - Path.of("bin", "node") - } +private val OperatingSystem.Type.nodeExecutable: Path + get() = + if (this == OperatingSystem.Type.Windows) { + Path.of("node.exe") + } else { + Path.of("bin", "node") + } -private val OperatingSystem.Type.npmExecutable: Path get() = - if (this == OperatingSystem.Type.Windows) { - Path.of("npm.cmd") - } else { - Path.of("bin", "npm") - } +private val OperatingSystem.Type.npmExecutable: Path + get() = + if (this == OperatingSystem.Type.Windows) { + Path.of("npm.cmd") + } else { + Path.of("bin", "npm") + } internal fun DownloadNodeJsTask.configureFrom(providers: MyProjectProviders) { source.set(providers.source) - outputDirectory.set(providers.buildDirectory.map { it.dir("node") }) + cacheManager.set(providers.cacheManager) + + outputDirectory.set(providers.providerFactory.provider { + val cacheManager = cacheManager.orNull + val directoryProvider: Provider = if (cacheManager === null) { + providers.buildDirectory.map { it.dir("node") } + } else { + val cacheDomain = "DownloadNodeJsTask" + val cacheKey = source.get().cacheKey + val cacheDir = cacheManager.getOrAllocateDir(domain = cacheDomain, key = cacheKey, logger) + providers.objectFactory.directoryProperty().also { it.set(cacheDir) } + } + directoryProvider.get() + }) } -internal val MyProjectProviders.source: Provider get() { - val lazySource: Lazy = lazy { - objectFactory.newInstance().also { - it.updateFrom(this@source) +internal val MyProjectProviders.source: Provider + get() { + val lazySource: Lazy = lazy { + objectFactory.newInstance().also { + it.updateFrom(this@source) + } } + return providerFactory.provider { lazySource.value } } - return providerFactory.provider { lazySource.value } -} internal fun DownloadOfficialVersion.updateFrom(providers: MyProjectProviders) { version.set(providers.nodeVersion) @@ -167,7 +217,7 @@ internal fun DownloadOfficialVersion.Companion.describe(source: DownloadOfficial "null" } else source.run { "DownloadNodeJsTask.Source.DownloadOfficialVersion(" + - "version=${version.orNull}, operatingSystem=${operatingSystem.orNull})" + "version=${version.orNull}, operatingSystem=${operatingSystem.orNull})" } internal fun Source.Companion.describe(source: Source?): String = when (source) { @@ -218,7 +268,7 @@ internal val DownloadOfficialVersion.downloadFileName: String OperatingSystem.Type.Linux -> "tar.gz" else -> throw GradleException( "unable to determine node.js download file extension for operating system type: $type " + - "(operatingSystem=$os) (error code ead53smf45)" + "(operatingSystem=$os) (error code ead53smf45)" ) } return "$downloadFileNameBase.$fileExtension" @@ -247,7 +297,7 @@ internal val DownloadOfficialVersion.downloadFileNameBase: String OperatingSystem.Type.Linux -> "linux" else -> throw GradleException( "unable to determine node.js download base file name for operating system type: $type " + - "(operatingSystem=$os) (error code m2grw3h7xz)" + "(operatingSystem=$os) (error code m2grw3h7xz)" ) } @@ -281,7 +331,7 @@ private fun Task.downloadOfficialVersion(source: DownloadOfficialVersion, output } else { throw GradleException( "Unsupported archive: ${downloadedFiles.binaryDistribution.absolutePath} " + - "(only .tar.gz and .zip extensions are supported) (error code pvrvw8sk9t)" + "(only .tar.gz and .zip extensions are supported) (error code pvrvw8sk9t)" ) } } @@ -454,7 +504,7 @@ private fun Task.verifySha256Digest(file: File, expectedSha256Digest: String) { } else { throw GradleException( "Incorrect SHA256 digest of ${file.absolutePath}: " + - "$actualSha256Digest (expected $expectedSha256Digest)" + "$actualSha256Digest (expected $expectedSha256Digest)" ) } } @@ -479,7 +529,7 @@ private fun Task.getExpectedSha256DigestFromShasumsFile( val sha = shas.singleOrNull() ?: throw GradleException( "$shasumsFilePath defines ${shas.size} SHA256 hashes for " + - "$desiredFileName, but expected exactly 1" + "$desiredFileName, but expected exactly 1" ) logger.info("Found SHA256 sum of {} in {}: {}", desiredFileName, shasumsFilePath, sha) @@ -488,8 +538,11 @@ private fun Task.getExpectedSha256DigestFromShasumsFile( private fun Task.downloadNodeJsBinaryDistribution( source: DownloadOfficialVersion, - outputDirectory: File + outputDirectory: File, ): DownloadedNodeJsFiles { + val shasumsFile = File(outputDirectory, shasumsFileName) + val binaryDistributionFile = File(outputDirectory, source.downloadFileName) + val httpClient = HttpClient(CIO) { expectSuccess = true install(Logging) { @@ -513,17 +566,10 @@ private fun Task.downloadNodeJsBinaryDistribution( } } - val binaryDistributionFile = File(outputDirectory, source.downloadFileName) - val shasumsFile = File(outputDirectory, shasumsFileName) - httpClient.use { runBlocking { - val url = source.shasumsDownloadUrl - downloadFile(httpClient, url, shasumsFile, maxNumDownloadBytes = 100_000L) - } - runBlocking { - val url = source.downloadUrl - downloadFile(httpClient, url, binaryDistributionFile, maxNumDownloadBytes = 200_000_000L) + downloadFile(httpClient, source.shasumsDownloadUrl, shasumsFile, maxNumDownloadBytes = 100_000L) + downloadFile(httpClient, source.downloadUrl, binaryDistributionFile, maxNumDownloadBytes = 200_000_000L) } } @@ -550,8 +596,8 @@ private suspend fun Task.downloadFile(httpClient: HttpClient, url: String, destF val maxNumDownloadBytesStr = numberFormat.format(maxNumDownloadBytes) throw GradleException( "Downloading $url failed: maximum file size $maxNumDownloadBytesStr bytes exceeded; " + - "cancelled after downloading $actualNumBytesDownloadedStr bytes " + - "(error code hvmhysn5vy)" + "cancelled after downloading $actualNumBytesDownloadedStr bytes " + + "(error code hvmhysn5vy)" ) } @@ -561,7 +607,7 @@ private suspend fun Task.downloadFile(httpClient: HttpClient, url: String, destF private fun Task.verifyNodeJsShaSumsSignature(file: File): ByteArray { logger.info( "Verifying that ${file.absolutePath} has a valid signature " + - "from the node.js release signing keys" + "from the node.js release signing keys" ) val keysListPath = "com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/keys.list" From ca5a06b899529b6d838bab9eec37cec81d2d20f8 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Tue, 26 Nov 2024 12:32:21 +0000 Subject: [PATCH 46/79] code cleanup --- .../example/dataconnect/gradle/cache/cache.kt | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/cache/cache.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/cache/cache.kt index 3baecf1f55..79208ae8f6 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/cache/cache.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/cache/cache.kt @@ -29,19 +29,12 @@ import java.nio.charset.StandardCharsets import java.nio.file.StandardOpenOption import java.util.UUID -class CacheManager(val rootDirectory: File) { +class CacheManager(private val rootDirectory: File) { private val entriesFile: File = File(rootDirectory, ENTRIES_FILENAME) private var allocatedDirectories = mutableListOf() - fun isAllocated(dir: File): Boolean { - val allocatedEntry: AllocatedDirectory? = synchronized(allocatedDirectories) { - allocatedDirectories.firstOrNull {it.directory == dir} - } - return allocatedEntry !== null - } - fun isCommitted(dir: File, logger: Logger): Boolean { if (dir.parentFile != rootDirectory) { return false @@ -53,7 +46,7 @@ class CacheManager(val rootDirectory: File) { fun getOrAllocateDir(domain: String, key: String, logger: Logger): File = findDir(domain=domain, key=key, logger) ?: allocateDir(domain=domain, key=key) - fun findDir(domain: String, key: String, logger: Logger): File? { + private fun findDir(domain: String, key: String, logger: Logger): File? { val globalEntries = loadGlobalEntries(logger) return globalEntries .filter { it.domain == domain && it.key == key } @@ -61,7 +54,7 @@ class CacheManager(val rootDirectory: File) { .singleOrNull() } - fun allocateDir(domain: String, key: String): File { + private fun allocateDir(domain: String, key: String): File { val directory = File(rootDirectory, UUID.randomUUID().toString()) val allocatedDirectory = AllocatedDirectory(domain=domain, key=key, directory=directory) synchronized(allocatedDirectories) { @@ -149,16 +142,16 @@ class CacheManager(val rootDirectory: File) { private fun ReadableByteChannel.readAllBytes(): ByteArray { val buffer = ByteArray(8192) val byteBuffer = ByteBuffer.wrap(buffer) - val baos = ByteArrayOutputStream() + val byteArrayOutputStream = ByteArrayOutputStream() while (true) { val numBytesRead = read(byteBuffer) if (numBytesRead < 0) { break } - baos.write(buffer, 0, numBytesRead) + byteArrayOutputStream.write(buffer, 0, numBytesRead) byteBuffer.flip() } - return baos.toByteArray() + return byteArrayOutputStream.toByteArray() } \ No newline at end of file From e2f50e9837e716b28c1b8d3eb81eeac3e81a4dcf Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Tue, 26 Nov 2024 13:02:15 +0000 Subject: [PATCH 47/79] skip building if dataConnectConfigDir is not set --- .../gradle/DataConnectExtension.kt | 18 ++++++++--- .../gradle/DataConnectGradlePlugin.kt | 9 ++++++ .../dataconnect/gradle/providers/providers.kt | 18 +++-------- .../gradle/tasks/DownloadNodeJsTask.kt | 1 + .../gradle/tasks/SetupFirebaseToolsTask.kt | 32 ++++++++++++++++++- 5 files changed, 58 insertions(+), 20 deletions(-) diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectExtension.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectExtension.kt index 9508774a31..87a61a72f3 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectExtension.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectExtension.kt @@ -35,16 +35,24 @@ interface DataConnectExtension { /** * The directory that contains dataconnect.yaml that specifies the Data - * Connect schema and connectors whose code to generate. + * Connect schema and connectors whose code to generate. If the file is a + * relative directory (as opposed to an absolute directory) then it will be + * calculated relative to the evaluating project's directory. + * + * If this value is null then no Data Connect code generation will occur + * as part of the build. */ var dataConnectConfigDir: File? /** * The directory into which cached data for the Data Connect plugin will be - * stored. This property may be `null`. If it is not `null` then the - * directory will be created on-demand when it is needed. The contents of - * this directory are never deleted; therefore, it is prudent to - * periodically delete the contents of this directory. + * stored, and from which cached data will be read. This property may be + * `null` (the default) to _not_ perform any explicit caching. If it is not + * `null` then the directory will be created on-demand when it is needed. + * The contents of this directory are never deleted; therefore, it is + * prudent to periodically delete the contents of this directory. If the + * file is a relative directory (as opposed to an absolute directory) then + * it will be calculated relative to the evaluating project's directory. */ var cacheDir: File? } diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectGradlePlugin.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectGradlePlugin.kt index 05f24571a8..95c3a75c87 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectGradlePlugin.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectGradlePlugin.kt @@ -60,6 +60,15 @@ abstract class DataConnectGradlePlugin : Plugin { val generateCodeTaskName = "generate${variantNameTitleCase}DataConnectSources" val generateCodeTask = project.tasks.register(generateCodeTaskName) { configureFrom(providers) + setOnlyIf( + "dataconnect.dataConnectConfigDir is null; to enable the \"$name\" task, " + + "set dataconnect.dataConnectConfigDir in build.gradle or build.gradle.kts to " + + "the directory that defines the Data Connect schema and " + + "connectors whose Kotlin code to generate code. That is, the directory " + + "containing the dataconnect.yaml file. For details, see " + + "https://firebase.google.com/docs/data-connect/configuration-reference#dataconnect.yaml-configuration " + + "(e.g. file(\"../dataconnect\")) (message code a3ch245mbd)" + ) { dataConnectConfigDir.isPresent } } variant.sources.java!!.addGeneratedSourceDirectory( diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/providers/providers.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/providers/providers.kt index a16b3145cd..27cb2b5002 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/providers/providers.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/providers/providers.kt @@ -92,20 +92,10 @@ internal open class MyProjectProviders( } val dataConnectConfigDir: Provider = run { - val lazyDataConnectConfigDir: Lazy = lazy { - @Suppress("ktlint:standard:max-line-length") - val dataConnectConfigDirFile: File = - ext.dataConnectConfigDir - ?: throw GradleException( - "dataconnect.dataConnectConfigDir must be set in " + - "build.gradle or build.gradle.kts to " + - "specify the directory that defines the Data Connect schema and " + - "connectors whose Kotlin code to generate code. That is, the directory " + - "containing the dataconnect.yaml file. For details, see " + - "https://firebase.google.com/docs/data-connect/configuration-reference#dataconnect.yaml-configuration " + - "(e.g. file(\"../dataconnect\")) (error code a3ch245mbd)" - ) - projectLayout.projectDirectory.dir(dataConnectConfigDirFile.path) + val lazyDataConnectConfigDir: Lazy = lazy { + ext.dataConnectConfigDir?.let { + projectLayout.projectDirectory.dir(it.path) + } } providerFactory.provider { lazyDataConnectConfigDir.value } } diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsTask.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsTask.kt index 48c5473912..2dbdb7163e 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsTask.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsTask.kt @@ -101,6 +101,7 @@ abstract class DownloadNodeJsTask : DefaultTask() { if (cacheManager !== null && cacheManager.isCommitted(outputDirectory, logger)) { logger.info("Using cached data from directory: {}", outputDirectory.absolutePath) + didWork = false return } diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/SetupFirebaseToolsTask.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/SetupFirebaseToolsTask.kt index e9f265c4cc..3305daa69f 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/SetupFirebaseToolsTask.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/SetupFirebaseToolsTask.kt @@ -16,9 +16,11 @@ package com.google.firebase.example.dataconnect.gradle.tasks +import com.google.firebase.example.dataconnect.gradle.cache.CacheManager import com.google.firebase.example.dataconnect.gradle.providers.MyProjectProviders import java.io.File import org.gradle.api.DefaultTask +import org.gradle.api.file.Directory import org.gradle.api.file.DirectoryProperty import org.gradle.api.file.RegularFile import org.gradle.api.file.RegularFileProperty @@ -44,6 +46,9 @@ abstract class SetupFirebaseToolsTask : DefaultTask() { @get:OutputDirectory abstract val outputDirectory: DirectoryProperty + @get:Internal + abstract val cacheManager: Property + @get:Internal val firebaseExecutable: Provider by lazy { outputDirectory.map { it.file("node_modules/.bin/firebase") } @@ -57,11 +62,19 @@ abstract class SetupFirebaseToolsTask : DefaultTask() { val npmExecutable: File = npmExecutable.get().asFile val nodeExecutable: File = nodeExecutable.get().asFile val outputDirectory: File = outputDirectory.get().asFile + val cacheManager: CacheManager? = cacheManager.orNull logger.info("firebaseCliVersion: {}", firebaseCliVersion) logger.info("npmExecutable: {}", npmExecutable.absolutePath) logger.info("nodeExecutable: {}", nodeExecutable.absolutePath) logger.info("outputDirectory: {}", outputDirectory.absolutePath) + logger.info("cacheManager: {}", cacheManager) + + if (cacheManager !== null && cacheManager.isCommitted(outputDirectory, logger)) { + logger.info("Using cached data from directory: {}", outputDirectory.absolutePath) + didWork = false + return + } project.delete(outputDirectory) project.mkdir(outputDirectory) @@ -78,6 +91,8 @@ abstract class SetupFirebaseToolsTask : DefaultTask() { commandLine(npmExecutable.absolutePath, "install", "firebase-tools@$firebaseCliVersion") workingDir(outputDirectory) } + + cacheManager?.commitDir(outputDirectory, logger) } } @@ -85,5 +100,20 @@ internal fun SetupFirebaseToolsTask.configureFrom(providers: MyProjectProviders) firebaseCliVersion.set(providers.firebaseCliVersion) npmExecutable.set(providers.npmExecutable) nodeExecutable.set(providers.nodeExecutable) - outputDirectory.set(providers.buildDirectory.map { it.dir("firebase-tools") }) + cacheManager.set(providers.cacheManager) + cacheManager.set(providers.cacheManager) + + outputDirectory.set(providers.providerFactory.provider { + val cacheManager = cacheManager.orNull + val directoryProvider: Provider = if (cacheManager === null) { + providers.buildDirectory.map { it.dir("firebase-tools") } + } else { + val cacheDomain = "SetupFirebaseToolsTask" + val cacheKey = "firebaseCliVersion=${firebaseCliVersion.get()}" + val cacheDir = cacheManager.getOrAllocateDir(domain = cacheDomain, key = cacheKey, logger) + providers.objectFactory.directoryProperty().also { it.set(cacheDir) } + } + directoryProvider.get() + }) + } From 250d11d4d3a87458a991e2537fe9c519cbb8e454 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Tue, 26 Nov 2024 13:08:03 +0000 Subject: [PATCH 48/79] ktlint format the code in dataconnect/buildSrc by running ./gradlew ktlintCheck --- .../gradle/DataConnectExtension.kt | 6 +- .../gradle/DataConnectGradlePlugin.kt | 1 + .../cache/{cache.kt => CacheManager.kt} | 38 +++++++----- .../dataconnect/gradle/providers/providers.kt | 1 - .../gradle/tasks/DownloadNodeJsTask.kt | 62 ++++++++++--------- .../gradle/tasks/SetupFirebaseToolsTask.kt | 25 ++++---- 6 files changed, 72 insertions(+), 61 deletions(-) rename dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/cache/{cache.kt => CacheManager.kt} (87%) diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectExtension.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectExtension.kt index 87a61a72f3..595ef4d8ba 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectExtension.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectExtension.kt @@ -21,9 +21,9 @@ import java.io.File interface DataConnectExtension { /** - * The version of Node.js (https://nodejs.org) to use to install and run the - * Firebase CLI. This version of Node.js will be downloaded and extracted - * for exclusive use of the Data Connect Gradle plugin. + * The version of Node.js (https://nodejs.org) to use to install and run the + * Firebase CLI. This version of Node.js will be downloaded and extracted + * for exclusive use of the Data Connect Gradle plugin. */ var nodeVersion: String? diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectGradlePlugin.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectGradlePlugin.kt index 95c3a75c87..3dd81c09f6 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectGradlePlugin.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectGradlePlugin.kt @@ -60,6 +60,7 @@ abstract class DataConnectGradlePlugin : Plugin { val generateCodeTaskName = "generate${variantNameTitleCase}DataConnectSources" val generateCodeTask = project.tasks.register(generateCodeTaskName) { configureFrom(providers) + @Suppress("ktlint:standard:max-line-length") setOnlyIf( "dataconnect.dataConnectConfigDir is null; to enable the \"$name\" task, " + "set dataconnect.dataConnectConfigDir in build.gradle or build.gradle.kts to " + diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/cache/cache.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/cache/CacheManager.kt similarity index 87% rename from dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/cache/cache.kt rename to dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/cache/CacheManager.kt index 79208ae8f6..da54b95141 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/cache/cache.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/cache/CacheManager.kt @@ -16,10 +16,6 @@ package com.google.firebase.example.dataconnect.gradle.cache -import kotlinx.serialization.Serializable -import kotlinx.serialization.encodeToString -import kotlinx.serialization.json.Json -import org.gradle.api.logging.Logger import java.io.ByteArrayOutputStream import java.io.File import java.nio.ByteBuffer @@ -28,10 +24,14 @@ import java.nio.channels.ReadableByteChannel import java.nio.charset.StandardCharsets import java.nio.file.StandardOpenOption import java.util.UUID +import kotlinx.serialization.Serializable +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json +import org.gradle.api.logging.Logger class CacheManager(private val rootDirectory: File) { - private val entriesFile: File = File(rootDirectory, ENTRIES_FILENAME) + private val entriesFile: File = File(rootDirectory, ENTRIES_FILENAME) private var allocatedDirectories = mutableListOf() @@ -44,7 +44,11 @@ class CacheManager(private val rootDirectory: File) { return globalEntry !== null } - fun getOrAllocateDir(domain: String, key: String, logger: Logger): File = findDir(domain=domain, key=key, logger) ?: allocateDir(domain=domain, key=key) + fun getOrAllocateDir(domain: String, key: String, logger: Logger): File = findDir( + domain = domain, + key = key, + logger + ) ?: allocateDir(domain = domain, key = key) private fun findDir(domain: String, key: String, logger: Logger): File? { val globalEntries = loadGlobalEntries(logger) @@ -56,7 +60,7 @@ class CacheManager(private val rootDirectory: File) { private fun allocateDir(domain: String, key: String): File { val directory = File(rootDirectory, UUID.randomUUID().toString()) - val allocatedDirectory = AllocatedDirectory(domain=domain, key=key, directory=directory) + val allocatedDirectory = AllocatedDirectory(domain = domain, key = key, directory = directory) synchronized(allocatedDirectories) { allocatedDirectories.add(allocatedDirectory) } @@ -73,14 +77,16 @@ class CacheManager(private val rootDirectory: File) { } val newGlobalEntry = GlobalEntry( - domain=allocatedDirectory.domain, - key=allocatedDirectory.key, + domain = allocatedDirectory.domain, + key = allocatedDirectory.key, directory = dir.name ) withEntriesFile(logger) { entriesFile, channel -> logger.info("Inserting or updating cache entry {} in file: {}", newGlobalEntry, entriesFile.absolutePath) - val globalEntries = loadGlobalEntries(channel).filterNot { it.domain == newGlobalEntry.domain && it.key == newGlobalEntry.key } + val globalEntries = loadGlobalEntries(channel).filterNot { + it.domain == newGlobalEntry.domain && it.key == newGlobalEntry.key + } val json = Json { prettyPrint = true } val newText = json.encodeToString(globalEntries + listOf(newGlobalEntry)) @@ -89,13 +95,17 @@ class CacheManager(private val rootDirectory: File) { channel.position(0) channel.write(ByteBuffer.wrap(newText.toByteArray(StandardCharsets.UTF_8))) } - } private fun withEntriesFile(logger: Logger, block: (File, FileChannel) -> T): T { logger.debug("Opening Data Connect cache metadata file: {}", entriesFile.absolutePath) entriesFile.parentFile.mkdirs() - return FileChannel.open(entriesFile.toPath(), StandardOpenOption.READ, StandardOpenOption.WRITE, StandardOpenOption.CREATE).use { channel -> + return FileChannel.open( + entriesFile.toPath(), + StandardOpenOption.READ, + StandardOpenOption.WRITE, + StandardOpenOption.CREATE + ).use { channel -> channel.lock().use { block(entriesFile, channel) } @@ -107,7 +117,6 @@ class CacheManager(private val rootDirectory: File) { loadGlobalEntries(channel) } - private fun loadGlobalEntries(channel: FileChannel): List { val fileBytes = channel.readAllBytes() val fileText = String(fileBytes, StandardCharsets.UTF_8) @@ -135,7 +144,6 @@ class CacheManager(private val rootDirectory: File) { companion object { private const val ENTRIES_FILENAME = "entries.json" - } } @@ -154,4 +162,4 @@ private fun ReadableByteChannel.readAllBytes(): ByteArray { } return byteArrayOutputStream.toByteArray() -} \ No newline at end of file +} diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/providers/providers.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/providers/providers.kt index 27cb2b5002..cfc697ea2c 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/providers/providers.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/providers/providers.kt @@ -21,7 +21,6 @@ import com.google.firebase.example.dataconnect.gradle.DataConnectExtension import com.google.firebase.example.dataconnect.gradle.cache.CacheManager import com.google.firebase.example.dataconnect.gradle.tasks.DownloadNodeJsTask import com.google.firebase.example.dataconnect.gradle.tasks.SetupFirebaseToolsTask -import java.io.File import org.gradle.api.GradleException import org.gradle.api.Project import org.gradle.api.file.Directory diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsTask.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsTask.kt index 2dbdb7163e..298c5e6053 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsTask.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsTask.kt @@ -30,6 +30,15 @@ import io.ktor.client.plugins.logging.Logging import io.ktor.client.request.prepareGet import io.ktor.utils.io.ByteReadChannel import io.ktor.utils.io.jvm.javaio.copyTo +import java.io.File +import java.io.IOException +import java.nio.file.Files +import java.nio.file.Path +import java.nio.file.Paths +import java.nio.file.attribute.FileTime +import java.nio.file.attribute.PosixFilePermission +import java.security.MessageDigest +import java.text.NumberFormat import kotlinx.coroutines.runBlocking import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json @@ -56,15 +65,6 @@ import org.gradle.api.tasks.OutputDirectory import org.gradle.api.tasks.TaskAction import org.gradle.kotlin.dsl.newInstance import org.pgpainless.sop.SOPImpl -import java.io.File -import java.io.IOException -import java.nio.file.Files -import java.nio.file.Path -import java.nio.file.Paths -import java.nio.file.attribute.FileTime -import java.nio.file.attribute.PosixFilePermission -import java.security.MessageDigest -import java.text.NumberFormat abstract class DownloadNodeJsTask : DefaultTask() { @@ -184,18 +184,20 @@ internal fun DownloadNodeJsTask.configureFrom(providers: MyProjectProviders) { source.set(providers.source) cacheManager.set(providers.cacheManager) - outputDirectory.set(providers.providerFactory.provider { - val cacheManager = cacheManager.orNull - val directoryProvider: Provider = if (cacheManager === null) { - providers.buildDirectory.map { it.dir("node") } - } else { - val cacheDomain = "DownloadNodeJsTask" - val cacheKey = source.get().cacheKey - val cacheDir = cacheManager.getOrAllocateDir(domain = cacheDomain, key = cacheKey, logger) - providers.objectFactory.directoryProperty().also { it.set(cacheDir) } + outputDirectory.set( + providers.providerFactory.provider { + val cacheManager = cacheManager.orNull + val directoryProvider: Provider = if (cacheManager === null) { + providers.buildDirectory.map { it.dir("node") } + } else { + val cacheDomain = "DownloadNodeJsTask" + val cacheKey = source.get().cacheKey + val cacheDir = cacheManager.getOrAllocateDir(domain = cacheDomain, key = cacheKey, logger) + providers.objectFactory.directoryProperty().also { it.set(cacheDir) } + } + directoryProvider.get() } - directoryProvider.get() - }) + ) } internal val MyProjectProviders.source: Provider @@ -218,7 +220,7 @@ internal fun DownloadOfficialVersion.Companion.describe(source: DownloadOfficial "null" } else source.run { "DownloadNodeJsTask.Source.DownloadOfficialVersion(" + - "version=${version.orNull}, operatingSystem=${operatingSystem.orNull})" + "version=${version.orNull}, operatingSystem=${operatingSystem.orNull})" } internal fun Source.Companion.describe(source: Source?): String = when (source) { @@ -269,7 +271,7 @@ internal val DownloadOfficialVersion.downloadFileName: String OperatingSystem.Type.Linux -> "tar.gz" else -> throw GradleException( "unable to determine node.js download file extension for operating system type: $type " + - "(operatingSystem=$os) (error code ead53smf45)" + "(operatingSystem=$os) (error code ead53smf45)" ) } return "$downloadFileNameBase.$fileExtension" @@ -298,7 +300,7 @@ internal val DownloadOfficialVersion.downloadFileNameBase: String OperatingSystem.Type.Linux -> "linux" else -> throw GradleException( "unable to determine node.js download base file name for operating system type: $type " + - "(operatingSystem=$os) (error code m2grw3h7xz)" + "(operatingSystem=$os) (error code m2grw3h7xz)" ) } @@ -332,7 +334,7 @@ private fun Task.downloadOfficialVersion(source: DownloadOfficialVersion, output } else { throw GradleException( "Unsupported archive: ${downloadedFiles.binaryDistribution.absolutePath} " + - "(only .tar.gz and .zip extensions are supported) (error code pvrvw8sk9t)" + "(only .tar.gz and .zip extensions are supported) (error code pvrvw8sk9t)" ) } } @@ -505,7 +507,7 @@ private fun Task.verifySha256Digest(file: File, expectedSha256Digest: String) { } else { throw GradleException( "Incorrect SHA256 digest of ${file.absolutePath}: " + - "$actualSha256Digest (expected $expectedSha256Digest)" + "$actualSha256Digest (expected $expectedSha256Digest)" ) } } @@ -530,7 +532,7 @@ private fun Task.getExpectedSha256DigestFromShasumsFile( val sha = shas.singleOrNull() ?: throw GradleException( "$shasumsFilePath defines ${shas.size} SHA256 hashes for " + - "$desiredFileName, but expected exactly 1" + "$desiredFileName, but expected exactly 1" ) logger.info("Found SHA256 sum of {} in {}: {}", desiredFileName, shasumsFilePath, sha) @@ -539,7 +541,7 @@ private fun Task.getExpectedSha256DigestFromShasumsFile( private fun Task.downloadNodeJsBinaryDistribution( source: DownloadOfficialVersion, - outputDirectory: File, + outputDirectory: File ): DownloadedNodeJsFiles { val shasumsFile = File(outputDirectory, shasumsFileName) val binaryDistributionFile = File(outputDirectory, source.downloadFileName) @@ -597,8 +599,8 @@ private suspend fun Task.downloadFile(httpClient: HttpClient, url: String, destF val maxNumDownloadBytesStr = numberFormat.format(maxNumDownloadBytes) throw GradleException( "Downloading $url failed: maximum file size $maxNumDownloadBytesStr bytes exceeded; " + - "cancelled after downloading $actualNumBytesDownloadedStr bytes " + - "(error code hvmhysn5vy)" + "cancelled after downloading $actualNumBytesDownloadedStr bytes " + + "(error code hvmhysn5vy)" ) } @@ -608,7 +610,7 @@ private suspend fun Task.downloadFile(httpClient: HttpClient, url: String, destF private fun Task.verifyNodeJsShaSumsSignature(file: File): ByteArray { logger.info( "Verifying that ${file.absolutePath} has a valid signature " + - "from the node.js release signing keys" + "from the node.js release signing keys" ) val keysListPath = "com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/keys.list" diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/SetupFirebaseToolsTask.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/SetupFirebaseToolsTask.kt index 3305daa69f..6dc8d634dc 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/SetupFirebaseToolsTask.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/SetupFirebaseToolsTask.kt @@ -103,17 +103,18 @@ internal fun SetupFirebaseToolsTask.configureFrom(providers: MyProjectProviders) cacheManager.set(providers.cacheManager) cacheManager.set(providers.cacheManager) - outputDirectory.set(providers.providerFactory.provider { - val cacheManager = cacheManager.orNull - val directoryProvider: Provider = if (cacheManager === null) { - providers.buildDirectory.map { it.dir("firebase-tools") } - } else { - val cacheDomain = "SetupFirebaseToolsTask" - val cacheKey = "firebaseCliVersion=${firebaseCliVersion.get()}" - val cacheDir = cacheManager.getOrAllocateDir(domain = cacheDomain, key = cacheKey, logger) - providers.objectFactory.directoryProperty().also { it.set(cacheDir) } + outputDirectory.set( + providers.providerFactory.provider { + val cacheManager = cacheManager.orNull + val directoryProvider: Provider = if (cacheManager === null) { + providers.buildDirectory.map { it.dir("firebase-tools") } + } else { + val cacheDomain = "SetupFirebaseToolsTask" + val cacheKey = "firebaseCliVersion=${firebaseCliVersion.get()}" + val cacheDir = cacheManager.getOrAllocateDir(domain = cacheDomain, key = cacheKey, logger) + providers.objectFactory.directoryProperty().also { it.set(cacheDir) } + } + directoryProvider.get() } - directoryProvider.get() - }) - + ) } From d803c27bed123fa89aefd7140750672a66c3df5e Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Tue, 26 Nov 2024 10:05:24 -0500 Subject: [PATCH 49/79] revert changes to dataconnect.local.toml --- dataconnect/.gitignore | 4 ---- 1 file changed, 4 deletions(-) diff --git a/dataconnect/.gitignore b/dataconnect/.gitignore index 1f9af270ec..9da855c735 100644 --- a/dataconnect/.gitignore +++ b/dataconnect/.gitignore @@ -17,7 +17,3 @@ local.properties .firebaserc .firebase/ *.log - -# The file used by our custom gradle plugin to specify _local_ settings. -# Since the settings are "local", they should *not* be checked into source control. -dataconnect.local.toml From 38b4a400d972973e77ccffd9a1bbf57aa990d80a Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Tue, 26 Nov 2024 10:22:40 -0500 Subject: [PATCH 50/79] Use ExecOperations since project.exec is deprecated --- .../google/firebase/example/dataconnect/gradle/tasks/util.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/util.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/util.kt index 66426f8198..e1722d8c6a 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/util.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/util.kt @@ -18,13 +18,16 @@ package com.google.firebase.example.dataconnect.gradle.tasks import java.io.File import org.gradle.api.Task +import org.gradle.kotlin.dsl.newInstance +import org.gradle.process.ExecOperations import org.gradle.process.ExecSpec internal fun Task.runCommand(logFile: File, configure: ExecSpec.() -> Unit) { val effectiveLogFile = if (logger.isInfoEnabled) null else logFile + val execOperations: ExecOperations = project.objects.newInstance() val result = effectiveLogFile?.outputStream().use { logStream -> - project.runCatching { + execOperations.runCatching { exec { isIgnoreExitValue = false if (logStream !== null) { From e5d6589ff85a61e7e8aa47bfc01fd0f0c1965a19 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Tue, 26 Nov 2024 10:48:31 -0500 Subject: [PATCH 51/79] dataconnect.local.toml.example deleted, as it is obsolete --- dataconnect/dataconnect.local.toml.example | 30 ---------------------- 1 file changed, 30 deletions(-) delete mode 100644 dataconnect/dataconnect.local.toml.example diff --git a/dataconnect/dataconnect.local.toml.example b/dataconnect/dataconnect.local.toml.example deleted file mode 100644 index f3a7bb384c..0000000000 --- a/dataconnect/dataconnect.local.toml.example +++ /dev/null @@ -1,30 +0,0 @@ -# This is an example dataconnect.local.toml file. -# Copy it to dataconnect.local.toml file in order for it to be picked up by the -# com.google.firebase.example.dataconnect.gradle custom plugin. - -[dataconnect] - -# The path of the "node" executable to use. -# -# Setting this is normally not necessary; however, if "node" is not in the PATH -# environment variable, or the node version in the PATH is not the desired one, -# then setting this to the absolute path of the node executable to use works -# around that problem. A relative path is calculated relative to the directory -# containing this file. -# -# If not set or set to null, "node" is used. -# -# eg. nodeExecutable = "/home/myusername/local/nvm/versions/node/v20.13.1/bin/node" -nodeExecutable = null - -# The path of the "npm" executable to use. -# -# Setting this is normally not necessary, as its value is usually inferred -# correctly. If not set, or set to null, then its value is inferred by first -# checking the value of `nodeExecutable`; if `nodeExecutable` is set to a non- -# null value, the the "npm" exectuable from the same directory is used. -# Otherwise, "npm" is used. A relative path is calculated relative to the -# directory containing this file. -# -# eg. npmExecutable = "/home/myusername/local/nvm/versions/node/v20.13.1/bin/npm" -npmExecutable = null From bd49216c3cc6fcdeeb623cab1d5096a622cf3f1e Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Tue, 26 Nov 2024 10:50:26 -0500 Subject: [PATCH 52/79] remove --info --stacktrace from CI gradle invocation; it was only there for temporary debugging --- .github/workflows/android.yml | 2 +- build_pull_request.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index cdfc31d4df..55dd574bcf 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -24,5 +24,5 @@ jobs: run: ./build_pull_request.sh if: github.event_name == 'pull_request' - name: Build with Gradle (Push) - run: ./gradlew --info --stacktrace clean ktlint assemble + run: ./gradlew clean ktlint assemble if: github.event_name != 'pull_request' diff --git a/build_pull_request.sh b/build_pull_request.sh index 8c9706f043..a6de4083a3 100755 --- a/build_pull_request.sh +++ b/build_pull_request.sh @@ -31,4 +31,4 @@ done # Build echo "Building Pull Request with" echo $build_commands -eval "./gradlew --info --stacktrace clean ktlint ${build_commands}" +eval "./gradlew clean ktlint ${build_commands}" From 096bd4576baa319833a7126c57498aee973c99e6 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Tue, 26 Nov 2024 10:57:31 -0500 Subject: [PATCH 53/79] fix ExecOperations usage --- .../firebase/example/dataconnect/gradle/tasks/util.kt | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/util.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/util.kt index e1722d8c6a..906bbe955f 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/util.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/util.kt @@ -17,6 +17,7 @@ package com.google.firebase.example.dataconnect.gradle.tasks import java.io.File +import javax.inject.Inject import org.gradle.api.Task import org.gradle.kotlin.dsl.newInstance import org.gradle.process.ExecOperations @@ -24,10 +25,10 @@ import org.gradle.process.ExecSpec internal fun Task.runCommand(logFile: File, configure: ExecSpec.() -> Unit) { val effectiveLogFile = if (logger.isInfoEnabled) null else logFile - val execOperations: ExecOperations = project.objects.newInstance() + val runCommandObjects: RunCommandObjects = project.objects.newInstance() val result = effectiveLogFile?.outputStream().use { logStream -> - execOperations.runCatching { + runCommandObjects.execOperations.runCatching { exec { isIgnoreExitValue = false if (logStream !== null) { @@ -43,3 +44,8 @@ internal fun Task.runCommand(logFile: File, configure: ExecSpec.() -> Unit) { throw exception } } + +private interface RunCommandObjects { + @get:Inject + val execOperations: ExecOperations +} From acc83597cfdff209929af115f3fc2e9c48d45f97 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Tue, 26 Nov 2024 11:17:05 -0500 Subject: [PATCH 54/79] rename the gradle property DataConnectGradleCacheDir to dataconnect.gradleCacheDir --- dataconnect/app/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dataconnect/app/build.gradle.kts b/dataconnect/app/build.gradle.kts index 49c51e2512..d7079bce5e 100644 --- a/dataconnect/app/build.gradle.kts +++ b/dataconnect/app/build.gradle.kts @@ -103,5 +103,5 @@ dataconnect { // This can be set with the "DataConnectGradleCacheDir" project property, // such as by specifying -PDataConnectGradleCacheDir=myCacheDir on the // Gradle command line. - cacheDir = findProperty("DataConnectGradleCacheDir")?.let { file(it) } + cacheDir = findProperty("dataconnect.gradleCacheDir")?.let { file(it) } } From 553e816475a4ce9ac8691a9fec7f0b9a113b364d Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Tue, 26 Nov 2024 11:56:20 -0500 Subject: [PATCH 55/79] fix gradle configuration cache issues --- .../dataconnect/gradle/providers/providers.kt | 4 +-- .../gradle/tasks/DownloadNodeJsTask.kt | 32 +++++++++++++------ .../tasks/GenerateDataConnectSourcesTask.kt | 29 ++++++++++++++--- .../gradle/tasks/SetupFirebaseToolsTask.kt | 7 +++- .../example/dataconnect/gradle/tasks/util.kt | 14 ++------ 5 files changed, 57 insertions(+), 29 deletions(-) diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/providers/providers.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/providers/providers.kt index cfc697ea2c..3796160123 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/providers/providers.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/providers/providers.kt @@ -53,8 +53,8 @@ internal open class MyProjectProviders( providerFactory = project.providers, objectFactory = project.objects, projectLayout = project.layout, - nodeExecutable = downloadNodeJsTask.map { it.nodeExecutable }, - npmExecutable = downloadNodeJsTask.map { it.npmExecutable }, + nodeExecutable = downloadNodeJsTask.flatMap { it.nodeExecutable }, + npmExecutable = downloadNodeJsTask.flatMap { it.npmExecutable }, ext = project.extensions.getByType(), project.logger ) diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsTask.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsTask.kt index 298c5e6053..2523e29046 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsTask.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsTask.kt @@ -55,13 +55,14 @@ import org.gradle.api.GradleException import org.gradle.api.Task import org.gradle.api.file.Directory import org.gradle.api.file.DirectoryProperty -import org.gradle.api.file.RegularFile +import org.gradle.api.file.RegularFileProperty import org.gradle.api.provider.Property import org.gradle.api.provider.Provider import org.gradle.api.tasks.Input import org.gradle.api.tasks.Internal import org.gradle.api.tasks.Nested import org.gradle.api.tasks.OutputDirectory +import org.gradle.api.tasks.OutputFile import org.gradle.api.tasks.TaskAction import org.gradle.kotlin.dsl.newInstance import org.pgpainless.sop.SOPImpl @@ -77,16 +78,11 @@ abstract class DownloadNodeJsTask : DefaultTask() { @get:Internal abstract val cacheManager: Property - @get:Internal - val nodeExecutable: RegularFile get() = nodePathOf { it.nodeExecutable } + @get:OutputFile + abstract val nodeExecutable: RegularFileProperty - @get:Internal - val npmExecutable: RegularFile get() = nodePathOf { it.npmExecutable } - - private fun nodePathOf(block: (Source) -> String): RegularFile { - val executable: String = block(source.get()) - return outputDirectory.map { it.file(executable) }.get() - } + @get:OutputFile + abstract val npmExecutable: RegularFileProperty @TaskAction fun run() { @@ -184,6 +180,22 @@ internal fun DownloadNodeJsTask.configureFrom(providers: MyProjectProviders) { source.set(providers.source) cacheManager.set(providers.cacheManager) + nodeExecutable.set( + project.provider { + val source = source.get() + val outputDirectory = outputDirectory.get() + outputDirectory.file(source.nodeExecutable) + } + ) + + npmExecutable.set( + project.provider { + val source = source.get() + val outputDirectory = outputDirectory.get() + outputDirectory.file(source.npmExecutable) + } + ) + outputDirectory.set( providers.providerFactory.provider { val cacheManager = cacheManager.orNull diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/GenerateDataConnectSourcesTask.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/GenerateDataConnectSourcesTask.kt index 3ce80ce134..8eadee5ed7 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/GenerateDataConnectSourcesTask.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/GenerateDataConnectSourcesTask.kt @@ -18,9 +18,11 @@ package com.google.firebase.example.dataconnect.gradle.tasks import com.google.firebase.example.dataconnect.gradle.providers.MyVariantProviders import java.io.File +import javax.inject.Inject import org.gradle.api.DefaultTask import org.gradle.api.GradleException import org.gradle.api.file.DirectoryProperty +import org.gradle.api.file.FileSystemOperations import org.gradle.api.file.RegularFileProperty import org.gradle.api.logging.Logger import org.gradle.api.provider.Property @@ -29,6 +31,7 @@ import org.gradle.api.tasks.InputFile import org.gradle.api.tasks.Internal import org.gradle.api.tasks.OutputDirectory import org.gradle.api.tasks.TaskAction +import org.gradle.process.ExecOperations import org.yaml.snakeyaml.Yaml abstract class GenerateDataConnectSourcesTask : DefaultTask() { @@ -47,6 +50,12 @@ abstract class GenerateDataConnectSourcesTask : DefaultTask() { @get:Internal abstract val tweakedDataConnectConfigDir: DirectoryProperty + @get:Inject + protected abstract val fileSystemOperations: FileSystemOperations + + @get:Inject + protected abstract val execOperations: ExecOperations + @TaskAction fun run() { val dataConnectConfigDir = dataConnectConfigDir.get().asFile @@ -61,17 +70,27 @@ abstract class GenerateDataConnectSourcesTask : DefaultTask() { logger.info("outputDirectory: {}", outputDirectory) logger.info("tweakedDataConnectConfigDir: {}", tweakedDataConnectConfigDir) - project.delete(outputDirectory) - project.delete(tweakedDataConnectConfigDir) - project.mkdir(tweakedDataConnectConfigDir) + fileSystemOperations.delete { + delete(outputDirectory) + delete(tweakedDataConnectConfigDir) + } - project.copy { + if (!tweakedDataConnectConfigDir.mkdirs()) { + throw GradleException( + "Could not create directory: ${tweakedDataConnectConfigDir.absolutePath} " + + "(error code q6dyy7vhbc)" + ) + } + + fileSystemOperations.copy { from(dataConnectConfigDir) into(tweakedDataConnectConfigDir) } + tweakConnectorYamlFiles(tweakedDataConnectConfigDir, outputDirectory.absolutePath, logger) - runCommand(File(tweakedDataConnectConfigDir, "generate.log.txt")) { + val commandLogFile = File(tweakedDataConnectConfigDir, "generate.log.txt") + execOperations.runCommand(commandLogFile, logger) { if (nodeExecutable === null) { commandLine("node") } else { diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/SetupFirebaseToolsTask.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/SetupFirebaseToolsTask.kt index 6dc8d634dc..2478513209 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/SetupFirebaseToolsTask.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/SetupFirebaseToolsTask.kt @@ -19,6 +19,7 @@ package com.google.firebase.example.dataconnect.gradle.tasks import com.google.firebase.example.dataconnect.gradle.cache.CacheManager import com.google.firebase.example.dataconnect.gradle.providers.MyProjectProviders import java.io.File +import javax.inject.Inject import org.gradle.api.DefaultTask import org.gradle.api.file.Directory import org.gradle.api.file.DirectoryProperty @@ -31,6 +32,7 @@ import org.gradle.api.tasks.InputFile import org.gradle.api.tasks.Internal import org.gradle.api.tasks.OutputDirectory import org.gradle.api.tasks.TaskAction +import org.gradle.process.ExecOperations abstract class SetupFirebaseToolsTask : DefaultTask() { @@ -54,6 +56,9 @@ abstract class SetupFirebaseToolsTask : DefaultTask() { outputDirectory.map { it.file("node_modules/.bin/firebase") } } + @get:Inject + protected abstract val execOperations: ExecOperations + private val pathEnvironmentVariable: Provider get() = project.providers.environmentVariable("PATH") @TaskAction @@ -86,7 +91,7 @@ abstract class SetupFirebaseToolsTask : DefaultTask() { packageJsonFile.writeText("{}", Charsets.UTF_8) val installLogFile = File(outputDirectory, "install.log.txt") - runCommand(installLogFile) { + execOperations.runCommand(installLogFile, logger) { environment("PATH", newPath) commandLine(npmExecutable.absolutePath, "install", "firebase-tools@$firebaseCliVersion") workingDir(outputDirectory) diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/util.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/util.kt index 906bbe955f..ed4178c477 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/util.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/util.kt @@ -17,18 +17,15 @@ package com.google.firebase.example.dataconnect.gradle.tasks import java.io.File -import javax.inject.Inject -import org.gradle.api.Task -import org.gradle.kotlin.dsl.newInstance +import org.gradle.api.logging.Logger import org.gradle.process.ExecOperations import org.gradle.process.ExecSpec -internal fun Task.runCommand(logFile: File, configure: ExecSpec.() -> Unit) { +internal fun ExecOperations.runCommand(logFile: File, logger: Logger, configure: ExecSpec.() -> Unit) { val effectiveLogFile = if (logger.isInfoEnabled) null else logFile - val runCommandObjects: RunCommandObjects = project.objects.newInstance() val result = effectiveLogFile?.outputStream().use { logStream -> - runCommandObjects.execOperations.runCatching { + runCatching { exec { isIgnoreExitValue = false if (logStream !== null) { @@ -44,8 +41,3 @@ internal fun Task.runCommand(logFile: File, configure: ExecSpec.() -> Unit) { throw exception } } - -private interface RunCommandObjects { - @get:Inject - val execOperations: ExecOperations -} From 1e5d5c44f97461b87e369d29b1fd567ab3d39945 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Tue, 26 Nov 2024 12:15:00 -0500 Subject: [PATCH 56/79] on the way to a cacheable task --- .../dataconnect/gradle/providers/providers.kt | 6 ++-- .../gradle/tasks/DownloadNodeJsTask.kt | 35 ++++++++----------- .../tasks/GenerateDataConnectSourcesTask.kt | 6 ++-- .../gradle/tasks/SetupFirebaseToolsTask.kt | 17 ++++----- 4 files changed, 31 insertions(+), 33 deletions(-) diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/providers/providers.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/providers/providers.kt index 3796160123..c052f496e9 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/providers/providers.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/providers/providers.kt @@ -53,8 +53,8 @@ internal open class MyProjectProviders( providerFactory = project.providers, objectFactory = project.objects, projectLayout = project.layout, - nodeExecutable = downloadNodeJsTask.flatMap { it.nodeExecutable }, - npmExecutable = downloadNodeJsTask.flatMap { it.npmExecutable }, + nodeExecutable = downloadNodeJsTask.map { it.nodeExecutable }, + npmExecutable = downloadNodeJsTask.map { it.npmExecutable }, ext = project.extensions.getByType(), project.logger ) @@ -131,7 +131,7 @@ internal open class MyVariantProviders( ) : this( variant = variant, projectProviders = projectProviders, - firebaseExecutable = setupFirebaseToolsTask.flatMap { it.firebaseExecutable } + firebaseExecutable = setupFirebaseToolsTask.map { it.firebaseExecutable } ) val buildDirectory: Provider = diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsTask.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsTask.kt index 2523e29046..df3e94d204 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsTask.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsTask.kt @@ -55,9 +55,11 @@ import org.gradle.api.GradleException import org.gradle.api.Task import org.gradle.api.file.Directory import org.gradle.api.file.DirectoryProperty +import org.gradle.api.file.RegularFile import org.gradle.api.file.RegularFileProperty import org.gradle.api.provider.Property import org.gradle.api.provider.Provider +import org.gradle.api.tasks.CacheableTask import org.gradle.api.tasks.Input import org.gradle.api.tasks.Internal import org.gradle.api.tasks.Nested @@ -67,6 +69,7 @@ import org.gradle.api.tasks.TaskAction import org.gradle.kotlin.dsl.newInstance import org.pgpainless.sop.SOPImpl +@CacheableTask abstract class DownloadNodeJsTask : DefaultTask() { @get:Nested @@ -78,11 +81,19 @@ abstract class DownloadNodeJsTask : DefaultTask() { @get:Internal abstract val cacheManager: Property - @get:OutputFile - abstract val nodeExecutable: RegularFileProperty + @get:Internal + val nodeExecutable: RegularFile get() { + val source = source.get() + val outputDirectory = outputDirectory.get() + return outputDirectory.file(source.nodeExecutable) + } - @get:OutputFile - abstract val npmExecutable: RegularFileProperty + @get:Internal + val npmExecutable: RegularFile get() { + val source = source.get() + val outputDirectory = outputDirectory.get() + return outputDirectory.file(source.npmExecutable) + } @TaskAction fun run() { @@ -180,22 +191,6 @@ internal fun DownloadNodeJsTask.configureFrom(providers: MyProjectProviders) { source.set(providers.source) cacheManager.set(providers.cacheManager) - nodeExecutable.set( - project.provider { - val source = source.get() - val outputDirectory = outputDirectory.get() - outputDirectory.file(source.nodeExecutable) - } - ) - - npmExecutable.set( - project.provider { - val source = source.get() - val outputDirectory = outputDirectory.get() - outputDirectory.file(source.npmExecutable) - } - ) - outputDirectory.set( providers.providerFactory.provider { val cacheManager = cacheManager.orNull diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/GenerateDataConnectSourcesTask.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/GenerateDataConnectSourcesTask.kt index 8eadee5ed7..3e26dc4281 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/GenerateDataConnectSourcesTask.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/GenerateDataConnectSourcesTask.kt @@ -26,6 +26,7 @@ import org.gradle.api.file.FileSystemOperations import org.gradle.api.file.RegularFileProperty import org.gradle.api.logging.Logger import org.gradle.api.provider.Property +import org.gradle.api.tasks.CacheableTask import org.gradle.api.tasks.InputDirectory import org.gradle.api.tasks.InputFile import org.gradle.api.tasks.Internal @@ -34,20 +35,21 @@ import org.gradle.api.tasks.TaskAction import org.gradle.process.ExecOperations import org.yaml.snakeyaml.Yaml +@CacheableTask abstract class GenerateDataConnectSourcesTask : DefaultTask() { @get:InputDirectory abstract val dataConnectConfigDir: DirectoryProperty @get:InputFile abstract val firebaseExecutable: RegularFileProperty + @get:OutputDirectory abstract val outputDirectory: DirectoryProperty + @get:Internal abstract val nodeExecutable: RegularFileProperty @get:Internal abstract val pathEnvironmentVariable: Property - @get:OutputDirectory abstract val outputDirectory: DirectoryProperty - @get:Internal abstract val tweakedDataConnectConfigDir: DirectoryProperty @get:Inject diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/SetupFirebaseToolsTask.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/SetupFirebaseToolsTask.kt index 2478513209..99730410c6 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/SetupFirebaseToolsTask.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/SetupFirebaseToolsTask.kt @@ -27,34 +27,35 @@ import org.gradle.api.file.RegularFile import org.gradle.api.file.RegularFileProperty import org.gradle.api.provider.Property import org.gradle.api.provider.Provider +import org.gradle.api.tasks.CacheableTask import org.gradle.api.tasks.Input import org.gradle.api.tasks.InputFile import org.gradle.api.tasks.Internal import org.gradle.api.tasks.OutputDirectory +import org.gradle.api.tasks.OutputFile import org.gradle.api.tasks.TaskAction import org.gradle.process.ExecOperations +@CacheableTask abstract class SetupFirebaseToolsTask : DefaultTask() { @get:Input abstract val firebaseCliVersion: Property - @get:InputFile + @get:OutputDirectory + abstract val outputDirectory: DirectoryProperty + + @get:Internal abstract val npmExecutable: RegularFileProperty - @get:InputFile + @get:Internal abstract val nodeExecutable: RegularFileProperty - @get:OutputDirectory - abstract val outputDirectory: DirectoryProperty - @get:Internal abstract val cacheManager: Property @get:Internal - val firebaseExecutable: Provider by lazy { - outputDirectory.map { it.file("node_modules/.bin/firebase") } - } + val firebaseExecutable: RegularFile get() = outputDirectory.get().file("node_modules/.bin/firebase") @get:Inject protected abstract val execOperations: ExecOperations From 69c19616ce1be6f0c1ca1e15a48e6442fca781d8 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Tue, 26 Nov 2024 20:49:55 +0000 Subject: [PATCH 57/79] DownloadNodeJsBinaryDistributionArchiveTask.kt started --- .../gradle/DataConnectGradlePlugin.kt | 5 + .../gradle/providers/OperatingSystem.kt | 22 +- ...loadNodeJsBinaryDistributionArchiveTask.kt | 252 ++++++++++++++++++ 3 files changed, 277 insertions(+), 2 deletions(-) create mode 100644 dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsBinaryDistributionArchiveTask.kt diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectGradlePlugin.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectGradlePlugin.kt index 3dd81c09f6..b2f8d13e5b 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectGradlePlugin.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectGradlePlugin.kt @@ -20,6 +20,7 @@ import com.android.build.api.variant.ApplicationAndroidComponentsExtension import com.android.build.api.variant.ApplicationVariant import com.google.firebase.example.dataconnect.gradle.providers.MyProjectProviders import com.google.firebase.example.dataconnect.gradle.providers.MyVariantProviders +import com.google.firebase.example.dataconnect.gradle.tasks.DownloadNodeJsBinaryDistributionArchiveTask import com.google.firebase.example.dataconnect.gradle.tasks.DownloadNodeJsTask import com.google.firebase.example.dataconnect.gradle.tasks.GenerateDataConnectSourcesTask import com.google.firebase.example.dataconnect.gradle.tasks.SetupFirebaseToolsTask @@ -40,6 +41,10 @@ abstract class DataConnectGradlePlugin : Plugin { project.extensions.create("dataconnect", DataConnectExtension::class.java) val providers = MyProjectProviders(project, downloadNodeJsTask) + project.tasks.register("downloadNodeJsBinaryDistributionArchive") { + configureFrom(providers) + } + downloadNodeJsTask.configure { configureFrom(providers) } setupFirebaseToolsTask.configure { configureFrom(providers) } diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/providers/OperatingSystem.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/providers/OperatingSystem.kt index dad53526ea..c08da5e2b6 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/providers/OperatingSystem.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/providers/OperatingSystem.kt @@ -16,6 +16,8 @@ package com.google.firebase.example.dataconnect.gradle.providers +import kotlinx.serialization.Serializable +import kotlinx.serialization.Transient import org.gradle.api.GradleException import org.gradle.api.logging.Logger import org.gradle.api.model.ObjectFactory @@ -25,11 +27,27 @@ import org.gradle.api.tasks.Input import org.gradle.api.tasks.Internal import org.gradle.kotlin.dsl.property +@Serializable data class OperatingSystem( @get:Input val type: Type, @get:Input val arch: Architecture, - @get:Internal val description: String? -) : java.io.Serializable { +) { + constructor( + type: Type, + arch: Architecture, + description: String + ) : this(type, arch) { + this.description = description + } + + // Declare `description` outside the primary constructor so that it does + // not get serialized or included in equals() and hashCode() computations. + @get:Internal + var description: String? = null + private set + + // Override toString() to include the description + override fun toString(): String = "OperatingSystem(type=$type, arch=$arch, description=$description)" enum class Type { Windows, diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsBinaryDistributionArchiveTask.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsBinaryDistributionArchiveTask.kt new file mode 100644 index 0000000000..c18d9e373e --- /dev/null +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsBinaryDistributionArchiveTask.kt @@ -0,0 +1,252 @@ +/* + * 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.example.dataconnect.gradle.tasks + +import com.google.firebase.example.dataconnect.gradle.cache.CacheManager +import com.google.firebase.example.dataconnect.gradle.providers.MyProjectProviders +import com.google.firebase.example.dataconnect.gradle.providers.OperatingSystem +import com.google.firebase.example.dataconnect.gradle.tasks.DownloadNodeJsBinaryDistributionArchiveTask.Inputs +import kotlinx.serialization.Serializable +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json +import org.gradle.api.DefaultTask +import org.gradle.api.GradleException +import org.gradle.api.file.DirectoryProperty +import org.gradle.api.file.FileSystemOperations +import org.gradle.api.file.ProjectLayout +import org.gradle.api.file.RegularFile +import org.gradle.api.model.ObjectFactory +import org.gradle.api.provider.Property +import org.gradle.api.provider.ProviderFactory +import org.gradle.api.tasks.CacheableTask +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.Internal +import org.gradle.api.tasks.Nested +import org.gradle.api.tasks.OutputDirectory +import org.gradle.api.tasks.SkipWhenEmpty +import org.gradle.api.tasks.TaskAction +import java.io.File +import javax.inject.Inject + +@CacheableTask +abstract class DownloadNodeJsBinaryDistributionArchiveTask : DefaultTask() { + + /** + * The inputs required to execute this task. + * + * This property is _required_, meaning that it must be set; that is, + * [Property.isPresent] must return `true`. + */ + @get:Nested + abstract val inputData: Property + + /** + * The directory into which to place the downloaded artifact(s). + * + * This property is _required_, meaning that it must be set; that is, + * [Property.isPresent] must return `true`. + * + * This directory will be deleted and re-created by the task unless it is a "committed" cache + * directory, as determined by [CacheManager.isCommitted]. + */ + @get:OutputDirectory + abstract val outputDirectory: DirectoryProperty + + /** + * The object to use to manage this task's cached data. + * + * The property is _optional_; however, if it _is_ set (that is, [Property.isPresent] returns + * `true`) then the [outputDirectory] must be either a "committed" or "allocated" directory + * returned by the [CacheManager]. + */ + @get:Internal + abstract val cacheManager: Property + + /** + * The path of the downloaded Node.js binary distribution archive. + * + * This property's value is computed from [inputs] and [outputDirectory]. + * + * This property must not be accessed until after the task executes. + */ + @get:Internal + val downloadedFile: RegularFile + get() { + val inputs = inputData.get() + val outputDirectory = outputDirectory.get() + val downloadedFileName = inputs.calculateDownloadFileName() + return outputDirectory.file(downloadedFileName) + } + + @get:Inject + abstract val providerFactory: ProviderFactory + + @get:Inject + abstract val projectLayout: ProjectLayout + + @get:Inject + abstract val fileSystemOperations: FileSystemOperations + + @TaskAction + fun run() { + val inputs: Inputs = inputData.get() + val outputDirectory: File = outputDirectory.get().asFile + val downloadedFile: File = downloadedFile.asFile + val cacheManager: CacheManager? = cacheManager.orNull + + logger.info("inputs: {}", inputs) + logger.info("outputDirectory: {}", outputDirectory.absolutePath) + logger.info("downloadedFile: {}", downloadedFile.absolutePath) + logger.info("cacheManager: {}", cacheManager) + + if (cacheManager !== null && cacheManager.isCommitted(outputDirectory, logger)) { + logger.info("Using cached data from directory: {}", outputDirectory.absolutePath) + didWork = false + return + } + + fileSystemOperations.delete { + delete(outputDirectory) + } + + cacheManager?.commitDir(outputDirectory, logger) + } + + /** + * The inputs for [DownloadNodeJsBinaryDistributionArchiveTask]. + * + * @property operatingSystem The operating system whose Node.js binary distribution archive to + * download. + * @property nodeJsVersion The version of Node.js whose binary distribution archive to download. + */ + @Serializable + data class Inputs( + @get:Nested val operatingSystem: OperatingSystem, + @get:Input val nodeJsVersion: String, + ) +} + +internal fun DownloadNodeJsBinaryDistributionArchiveTask.configureFrom(myProviders: MyProjectProviders) { + cacheManager.set(myProviders.cacheManager) + + inputData.set(providerFactory.provider { + Inputs( + myProviders.operatingSystem.get(), + myProviders.nodeVersion.get() + ) + }) + + outputDirectory.set(providerFactory.provider { + val cacheManager = cacheManager.orNull + val cacheDomain = "DownloadNodeJsBinaryDistributionArchive" + if (cacheManager === null) { + myProviders.buildDirectory.get().dir(cacheDomain) + } else { + val cacheKey = Json.encodeToString(inputData.get()) + val cacheDir = cacheManager.getOrAllocateDir(domain = cacheDomain, key = cacheKey, logger) + projectLayout.projectDirectory.dir(cacheDir.path) + } + }) + + setOnlyIf("inputData was specified", { + inputData.isPresent + }) +} + +internal fun Inputs.calculateDownloadUrlPrefix(): String = "https://nodejs.org/dist/v$nodeJsVersion" + +/** + * The URL to download the Node.js binary distribution. + * + * Here are some examples: + * * https://nodejs.org/dist/v20.9.0/node-v20.9.0-darwin-arm64.tar.gz + * * https://nodejs.org/dist/v20.9.0/node-v20.9.0-darwin-x64.tar.gz + * * https://nodejs.org/dist/v20.9.0/node-v20.9.0-linux-arm64.tar.gz + * * https://nodejs.org/dist/v20.9.0/node-v20.9.0-linux-armv7l.tar.gz + * * https://nodejs.org/dist/v20.9.0/node-v20.9.0-linux-x64.tar.gz + * * https://nodejs.org/dist/v20.9.0/node-v20.9.0-win-arm64.zip + * * https://nodejs.org/dist/v20.9.0/node-v20.9.0-win-x64.zip + * * https://nodejs.org/dist/v20.9.0/node-v20.9.0-win-x86.zip + */ +internal fun Inputs.calculateDownloadUrl(): String { + val downloadUrlPrefix = calculateDownloadUrlPrefix() + val downloadFileName = calculateDownloadFileName() + return "$downloadUrlPrefix/$downloadFileName" +} + +/** + * The file name of the download for the Node.js binary distribution. + * + * Here are some examples: + * * node-v20.9.0-darwin-arm64.tar.gz + * * node-v20.9.0-darwin-x64.tar.gz + * * node-v20.9.0-linux-arm64.tar.gz + * * node-v20.9.0-linux-armv7l.tar.gz + * * node-v20.9.0-linux-x64.tar.gz + * * node-v20.9.0-win-arm64.zip + * * node-v20.9.0-win-x64.zip + * * node-v20.9.0-win-x86.zip + */ +internal fun Inputs.calculateDownloadFileName(): String { + val fileExtension: String = when (val type = operatingSystem.type) { + OperatingSystem.Type.Windows -> "zip" + OperatingSystem.Type.MacOS -> "tar.gz" + OperatingSystem.Type.Linux -> "tar.gz" + else -> throw GradleException( + "unable to determine Node.js download file extension for operating system type: $type " + + "(operatingSystem=$operatingSystem) (error code ead53smf45)" + ) + } + + val downloadFileNameBase = calculateDownloadFileNameBase() + return "$downloadFileNameBase.$fileExtension" +} + +/** + * The base file name of the download for the Node.js binary distribution; + * that is, the file name without the ".zip" or ".tar.gz" extension. + * + * Here are some examples: + * * node-v20.9.0-darwin-arm64 + * * node-v20.9.0-darwin-x64 + * * node-v20.9.0-linux-arm64 + * * node-v20.9.0-linux-armv7l + * * node-v20.9.0-linux-x64 + * * node-v20.9.0-win-arm64 + * * node-v20.9.0-win-x64 + * * node-v20.9.0-win-x86 + */ +internal fun Inputs.calculateDownloadFileNameBase(): String { + val osType: String = when (val type = operatingSystem.type) { + OperatingSystem.Type.Windows -> "win" + OperatingSystem.Type.MacOS -> "darwin" + OperatingSystem.Type.Linux -> "linux" + else -> throw GradleException( + "unable to determine Node.js download base file name for operating system type: $type " + + "(operatingSystem=$operatingSystem) (error code m2grw3h7xz)" + ) + } + + val osArch: String = when (operatingSystem.arch) { + OperatingSystem.Architecture.Arm64 -> "arm64" + OperatingSystem.Architecture.ArmV7 -> "armv7l" + OperatingSystem.Architecture.X86 -> "x86" + OperatingSystem.Architecture.X86_64 -> "x64" + } + + return "node-v$nodeJsVersion-$osType-$osArch" +} From e379f484100cb3d634d365a0e7bc8fe9213cbd84 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Tue, 26 Nov 2024 21:40:05 +0000 Subject: [PATCH 58/79] DownloadNodeJsBinaryDistributionArchiveTask.kt more work --- .../dataconnect/gradle/cache/CacheManager.kt | 61 ++++++++++--------- ...loadNodeJsBinaryDistributionArchiveTask.kt | 55 ++++------------- .../gradle/tasks/DownloadNodeJsTask.kt | 42 ++++++++----- 3 files changed, 70 insertions(+), 88 deletions(-) diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/cache/CacheManager.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/cache/CacheManager.kt index da54b95141..11ca2eff12 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/cache/CacheManager.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/cache/CacheManager.kt @@ -16,6 +16,10 @@ package com.google.firebase.example.dataconnect.gradle.cache +import kotlinx.serialization.Serializable +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json +import org.gradle.api.logging.Logger import java.io.ByteArrayOutputStream import java.io.File import java.nio.ByteBuffer @@ -24,42 +28,35 @@ import java.nio.channels.ReadableByteChannel import java.nio.charset.StandardCharsets import java.nio.file.StandardOpenOption import java.util.UUID -import kotlinx.serialization.Serializable -import kotlinx.serialization.encodeToString -import kotlinx.serialization.json.Json -import org.gradle.api.logging.Logger class CacheManager(private val rootDirectory: File) { private val entriesFile: File = File(rootDirectory, ENTRIES_FILENAME) - private var allocatedDirectories = mutableListOf() + private val allocatedDirectories = mutableListOf() fun isCommitted(dir: File, logger: Logger): Boolean { if (dir.parentFile != rootDirectory) { return false } val directoryName = dir.name - val globalEntry: GlobalEntry? = loadGlobalEntries(logger).firstOrNull { it.directory == directoryName } - return globalEntry !== null + val cacheEntry: CacheEntry? = loadCacheEntries(logger).firstOrNull { it.directory == directoryName } + return cacheEntry !== null } - fun getOrAllocateDir(domain: String, key: String, logger: Logger): File = findDir( - domain = domain, - key = key, - logger - ) ?: allocateDir(domain = domain, key = key) + fun getOrAllocateDir(domain: String, key: String, logger: Logger): File { + val committedDir = findDir(domain = domain, key = key, logger) + return committedDir ?: allocateDir(domain = domain, key = key) + } - private fun findDir(domain: String, key: String, logger: Logger): File? { - val globalEntries = loadGlobalEntries(logger) - return globalEntries + private fun findDir(domain: String, key: String, logger: Logger): File? = + loadCacheEntries(logger) .filter { it.domain == domain && it.key == key } .map { File(rootDirectory, it.directory) } .singleOrNull() - } private fun allocateDir(domain: String, key: String): File { - val directory = File(rootDirectory, UUID.randomUUID().toString()) + val directory = File(rootDirectory, UUID.randomUUID().toString()).absoluteFile.normalize() val allocatedDirectory = AllocatedDirectory(domain = domain, key = key, directory = directory) synchronized(allocatedDirectories) { allocatedDirectories.add(allocatedDirectory) @@ -69,27 +66,33 @@ class CacheManager(private val rootDirectory: File) { fun commitDir(dir: File, logger: Logger) { val allocatedDirectory: AllocatedDirectory = synchronized(allocatedDirectories) { - val index = allocatedDirectories.indexOfFirst { it.directory == dir } + val normalizedDir = dir.absoluteFile.normalize() + val index = allocatedDirectories.indexOfFirst { it.directory == normalizedDir } require(index >= 0) { - "The given directory, $dir, has not been allocated or was already committed" + val allocatedDirectoriesStr = synchronized(allocatedDirectories) { + allocatedDirectories.map { it.directory.path }.sorted().joinToString(", ") + } + "The given directory, $dir, has not been allocated or was already committed; " + + "there are currently ${allocatedDirectories.size} allocated directories: " + + "$allocatedDirectoriesStr (error code k2gr9g36wh)" } allocatedDirectories.removeAt(index) } - val newGlobalEntry = GlobalEntry( + val newCacheEntry = CacheEntry( domain = allocatedDirectory.domain, key = allocatedDirectory.key, directory = dir.name ) withEntriesFile(logger) { entriesFile, channel -> - logger.info("Inserting or updating cache entry {} in file: {}", newGlobalEntry, entriesFile.absolutePath) - val globalEntries = loadGlobalEntries(channel).filterNot { - it.domain == newGlobalEntry.domain && it.key == newGlobalEntry.key + logger.info("Inserting or updating cache entry {} in file: {}", newCacheEntry, entriesFile.absolutePath) + val cacheEntries = loadCacheEntries(channel).filterNot { + it.domain == newCacheEntry.domain && it.key == newCacheEntry.key } val json = Json { prettyPrint = true } - val newText = json.encodeToString(globalEntries + listOf(newGlobalEntry)) + val newText = json.encodeToString(cacheEntries + listOf(newCacheEntry)) channel.truncate(0) channel.position(0) @@ -112,24 +115,24 @@ class CacheManager(private val rootDirectory: File) { } } - private fun loadGlobalEntries(logger: Logger): List = + private fun loadCacheEntries(logger: Logger): List = withEntriesFile(logger) { _, channel -> - loadGlobalEntries(channel) + loadCacheEntries(channel) } - private fun loadGlobalEntries(channel: FileChannel): List { + private fun loadCacheEntries(channel: FileChannel): List { val fileBytes = channel.readAllBytes() val fileText = String(fileBytes, StandardCharsets.UTF_8) if (fileText.isBlank()) { return emptyList() } - return Json.decodeFromString>(fileText) + return Json.decodeFromString>(fileText) } override fun toString() = "CacheManager(${rootDirectory.absolutePath})" @Serializable - private data class GlobalEntry( + private data class CacheEntry( val domain: String, val key: String, val directory: String diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsBinaryDistributionArchiveTask.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsBinaryDistributionArchiveTask.kt index c18d9e373e..28362199d2 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsBinaryDistributionArchiveTask.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsBinaryDistributionArchiveTask.kt @@ -21,15 +21,12 @@ import com.google.firebase.example.dataconnect.gradle.providers.MyProjectProvide import com.google.firebase.example.dataconnect.gradle.providers.OperatingSystem import com.google.firebase.example.dataconnect.gradle.tasks.DownloadNodeJsBinaryDistributionArchiveTask.Inputs import kotlinx.serialization.Serializable -import kotlinx.serialization.encodeToString -import kotlinx.serialization.json.Json import org.gradle.api.DefaultTask import org.gradle.api.GradleException import org.gradle.api.file.DirectoryProperty import org.gradle.api.file.FileSystemOperations import org.gradle.api.file.ProjectLayout import org.gradle.api.file.RegularFile -import org.gradle.api.model.ObjectFactory import org.gradle.api.provider.Property import org.gradle.api.provider.ProviderFactory import org.gradle.api.tasks.CacheableTask @@ -37,7 +34,6 @@ import org.gradle.api.tasks.Input import org.gradle.api.tasks.Internal import org.gradle.api.tasks.Nested import org.gradle.api.tasks.OutputDirectory -import org.gradle.api.tasks.SkipWhenEmpty import org.gradle.api.tasks.TaskAction import java.io.File import javax.inject.Inject @@ -66,16 +62,6 @@ abstract class DownloadNodeJsBinaryDistributionArchiveTask : DefaultTask() { @get:OutputDirectory abstract val outputDirectory: DirectoryProperty - /** - * The object to use to manage this task's cached data. - * - * The property is _optional_; however, if it _is_ set (that is, [Property.isPresent] returns - * `true`) then the [outputDirectory] must be either a "committed" or "allocated" directory - * returned by the [CacheManager]. - */ - @get:Internal - abstract val cacheManager: Property - /** * The path of the downloaded Node.js binary distribution archive. * @@ -106,24 +92,14 @@ abstract class DownloadNodeJsBinaryDistributionArchiveTask : DefaultTask() { val inputs: Inputs = inputData.get() val outputDirectory: File = outputDirectory.get().asFile val downloadedFile: File = downloadedFile.asFile - val cacheManager: CacheManager? = cacheManager.orNull logger.info("inputs: {}", inputs) logger.info("outputDirectory: {}", outputDirectory.absolutePath) logger.info("downloadedFile: {}", downloadedFile.absolutePath) - logger.info("cacheManager: {}", cacheManager) - - if (cacheManager !== null && cacheManager.isCommitted(outputDirectory, logger)) { - logger.info("Using cached data from directory: {}", outputDirectory.absolutePath) - didWork = false - return - } fileSystemOperations.delete { delete(outputDirectory) } - - cacheManager?.commitDir(outputDirectory, logger) } /** @@ -141,26 +117,19 @@ abstract class DownloadNodeJsBinaryDistributionArchiveTask : DefaultTask() { } internal fun DownloadNodeJsBinaryDistributionArchiveTask.configureFrom(myProviders: MyProjectProviders) { - cacheManager.set(myProviders.cacheManager) - - inputData.set(providerFactory.provider { - Inputs( - myProviders.operatingSystem.get(), - myProviders.nodeVersion.get() - ) - }) + inputData.run { + finalizeValueOnRead() + set(providerFactory.provider { + val operatingSystem = myProviders.operatingSystem.get() + val nodeVersion = myProviders.nodeVersion.get() + Inputs(operatingSystem, nodeVersion) + }) + } - outputDirectory.set(providerFactory.provider { - val cacheManager = cacheManager.orNull - val cacheDomain = "DownloadNodeJsBinaryDistributionArchive" - if (cacheManager === null) { - myProviders.buildDirectory.get().dir(cacheDomain) - } else { - val cacheKey = Json.encodeToString(inputData.get()) - val cacheDir = cacheManager.getOrAllocateDir(domain = cacheDomain, key = cacheKey, logger) - projectLayout.projectDirectory.dir(cacheDir.path) - } - }) + outputDirectory.run { + finalizeValueOnRead() + set(myProviders.buildDirectory.map { it.dir("DownloadNodeJsBinaryDistributionArchive") }) + } setOnlyIf("inputData was specified", { inputData.isPresent diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsTask.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsTask.kt index df3e94d204..c3808b6953 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsTask.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsTask.kt @@ -188,23 +188,33 @@ private val OperatingSystem.Type.npmExecutable: Path } internal fun DownloadNodeJsTask.configureFrom(providers: MyProjectProviders) { - source.set(providers.source) - cacheManager.set(providers.cacheManager) - - outputDirectory.set( - providers.providerFactory.provider { - val cacheManager = cacheManager.orNull - val directoryProvider: Provider = if (cacheManager === null) { - providers.buildDirectory.map { it.dir("node") } - } else { - val cacheDomain = "DownloadNodeJsTask" - val cacheKey = source.get().cacheKey - val cacheDir = cacheManager.getOrAllocateDir(domain = cacheDomain, key = cacheKey, logger) - providers.objectFactory.directoryProperty().also { it.set(cacheDir) } + source.run { + set(providers.source) + finalizeValueOnRead() + } + + cacheManager.run { + set(providers.cacheManager) + finalizeValueOnRead() + } + + outputDirectory.run { + set( + providers.providerFactory.provider { + val cacheManager = cacheManager.orNull + val directoryProvider: Provider = if (cacheManager === null) { + providers.buildDirectory.map { it.dir("node") } + } else { + val cacheDomain = "DownloadNodeJsTask" + val cacheKey = source.get().cacheKey + val cacheDir = cacheManager.getOrAllocateDir(domain = cacheDomain, key = cacheKey, logger) + providers.objectFactory.directoryProperty().also { it.set(cacheDir) } + } + directoryProvider.get() } - directoryProvider.get() - } - ) + ) + finalizeValueOnRead() + } } internal val MyProjectProviders.source: Provider From 22c3ed7d0130ce5b5603e5fa8e9641c2d32c745d Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Wed, 27 Nov 2024 17:45:37 +0000 Subject: [PATCH 59/79] .gitignore: add .kotlin --- dataconnect/.gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/dataconnect/.gitignore b/dataconnect/.gitignore index 9da855c735..ce97a25464 100644 --- a/dataconnect/.gitignore +++ b/dataconnect/.gitignore @@ -17,3 +17,4 @@ local.properties .firebaserc .firebase/ *.log +.kotlin From 60765f4a6609254326f419b7c7bc142278129ca6 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Wed, 27 Nov 2024 19:44:01 +0000 Subject: [PATCH 60/79] DownloadNodeJsBinaryDistributionArchiveTask.kt implements downloading (still needs to implement verify) --- .../gradle/DataConnectGradleException.kt | 21 +++ .../dataconnect/gradle/cache/CacheManager.kt | 12 +- .../gradle/providers/OperatingSystem.kt | 7 +- .../gradle/tasks/DataConnectTaskBase.kt | 55 ++++++ ...loadNodeJsBinaryDistributionArchiveTask.kt | 170 +++++++----------- .../gradle/tasks/DownloadNodeJsTask.kt | 8 +- .../tasks/GenerateDataConnectSourcesTask.kt | 1 + .../dataconnect/gradle/tasks/NodeJsPaths.kt | 131 ++++++++++++++ .../gradle/tasks/SetupFirebaseToolsTask.kt | 3 +- .../gradle/util/DataConnectGradleLogger.kt | 53 ++++++ .../util/DataConnectGradleLoggerProvider.kt | 46 +++++ .../dataconnect/gradle/util/FileDownloader.kt | 128 +++++++++++++ .../{tasks/util.kt => util/GradleKtx.kt} | 2 +- .../dataconnect/gradle/util/RandomKtx.kt | 40 +++++ 14 files changed, 553 insertions(+), 124 deletions(-) create mode 100644 dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectGradleException.kt create mode 100644 dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DataConnectTaskBase.kt create mode 100644 dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/NodeJsPaths.kt create mode 100644 dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/util/DataConnectGradleLogger.kt create mode 100644 dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/util/DataConnectGradleLoggerProvider.kt create mode 100644 dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/util/FileDownloader.kt rename dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/{tasks/util.kt => util/GradleKtx.kt} (95%) create mode 100644 dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/util/RandomKtx.kt diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectGradleException.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectGradleException.kt new file mode 100644 index 0000000000..1c092ee52a --- /dev/null +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectGradleException.kt @@ -0,0 +1,21 @@ +/* + * 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.example.dataconnect.gradle + +import org.gradle.api.GradleException + +class DataConnectGradleException(message: String, cause: Throwable? = null) : GradleException(message, cause) diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/cache/CacheManager.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/cache/CacheManager.kt index 11ca2eff12..63fbb573c6 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/cache/CacheManager.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/cache/CacheManager.kt @@ -16,10 +16,6 @@ package com.google.firebase.example.dataconnect.gradle.cache -import kotlinx.serialization.Serializable -import kotlinx.serialization.encodeToString -import kotlinx.serialization.json.Json -import org.gradle.api.logging.Logger import java.io.ByteArrayOutputStream import java.io.File import java.nio.ByteBuffer @@ -28,6 +24,10 @@ import java.nio.channels.ReadableByteChannel import java.nio.charset.StandardCharsets import java.nio.file.StandardOpenOption import java.util.UUID +import kotlinx.serialization.Serializable +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json +import org.gradle.api.logging.Logger class CacheManager(private val rootDirectory: File) { @@ -73,8 +73,8 @@ class CacheManager(private val rootDirectory: File) { allocatedDirectories.map { it.directory.path }.sorted().joinToString(", ") } "The given directory, $dir, has not been allocated or was already committed; " + - "there are currently ${allocatedDirectories.size} allocated directories: " + - "$allocatedDirectoriesStr (error code k2gr9g36wh)" + "there are currently ${allocatedDirectories.size} allocated directories: " + + "$allocatedDirectoriesStr (error code k2gr9g36wh)" } allocatedDirectories.removeAt(index) } diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/providers/OperatingSystem.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/providers/OperatingSystem.kt index c08da5e2b6..23f33d16d8 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/providers/OperatingSystem.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/providers/OperatingSystem.kt @@ -17,7 +17,6 @@ package com.google.firebase.example.dataconnect.gradle.providers import kotlinx.serialization.Serializable -import kotlinx.serialization.Transient import org.gradle.api.GradleException import org.gradle.api.logging.Logger import org.gradle.api.model.ObjectFactory @@ -30,7 +29,7 @@ import org.gradle.kotlin.dsl.property @Serializable data class OperatingSystem( @get:Input val type: Type, - @get:Input val arch: Architecture, + @get:Input val architecture: Architecture ) { constructor( type: Type, @@ -47,7 +46,9 @@ data class OperatingSystem( private set // Override toString() to include the description - override fun toString(): String = "OperatingSystem(type=$type, arch=$arch, description=$description)" + override fun toString(): String = "OperatingSystem(" + + "type=$type, architecture=$architecture, description=$description" + + ")" enum class Type { Windows, diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DataConnectTaskBase.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DataConnectTaskBase.kt new file mode 100644 index 0000000000..9784233582 --- /dev/null +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DataConnectTaskBase.kt @@ -0,0 +1,55 @@ +/* + * 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.example.dataconnect.gradle.tasks + +import com.google.firebase.example.dataconnect.gradle.util.DataConnectGradleLogger +import kotlin.reflect.full.allSupertypes +import org.gradle.api.DefaultTask +import org.gradle.api.tasks.Internal +import org.gradle.api.tasks.TaskAction + +abstract class DataConnectTaskBase(loggerIdPrefix: String) : DefaultTask() { + + @get:Internal + protected val dataConnectLogger: DataConnectGradleLogger = DataConnectGradleLogger( + loggerIdPrefix = loggerIdPrefix, + logger = logger + ) + + init { + val superTypeNames = this::class.allSupertypes.map { it.toString() }.sorted().joinToString(", ") + dataConnectLogger.info( + "Task $path created: class=${this::class.qualifiedName} (supertypes are: $superTypeNames)" + ) + } + + @TaskAction + fun run() { + dataConnectLogger.info("Task $path starting execution") + runCatching { doRun() }.fold( + onSuccess = { + dataConnectLogger.info("Task $path execution completed successfully") + }, + onFailure = { + dataConnectLogger.warn("Task $path execution failed: $it") + throw it + } + ) + } + + protected abstract fun doRun() +} diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsBinaryDistributionArchiveTask.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsBinaryDistributionArchiveTask.kt index 28362199d2..724ca3c053 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsBinaryDistributionArchiveTask.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsBinaryDistributionArchiveTask.kt @@ -20,12 +20,17 @@ import com.google.firebase.example.dataconnect.gradle.cache.CacheManager import com.google.firebase.example.dataconnect.gradle.providers.MyProjectProviders import com.google.firebase.example.dataconnect.gradle.providers.OperatingSystem import com.google.firebase.example.dataconnect.gradle.tasks.DownloadNodeJsBinaryDistributionArchiveTask.Inputs +import com.google.firebase.example.dataconnect.gradle.util.DataConnectGradleLogger +import com.google.firebase.example.dataconnect.gradle.util.DataConnectGradleLoggerProvider +import com.google.firebase.example.dataconnect.gradle.util.FileDownloader +import com.google.firebase.example.dataconnect.gradle.util.createDirectory +import com.google.firebase.example.dataconnect.gradle.util.deleteDirectory +import java.io.File +import javax.inject.Inject +import kotlinx.coroutines.runBlocking import kotlinx.serialization.Serializable -import org.gradle.api.DefaultTask -import org.gradle.api.GradleException import org.gradle.api.file.DirectoryProperty import org.gradle.api.file.FileSystemOperations -import org.gradle.api.file.ProjectLayout import org.gradle.api.file.RegularFile import org.gradle.api.provider.Property import org.gradle.api.provider.ProviderFactory @@ -34,12 +39,9 @@ import org.gradle.api.tasks.Input import org.gradle.api.tasks.Internal import org.gradle.api.tasks.Nested import org.gradle.api.tasks.OutputDirectory -import org.gradle.api.tasks.TaskAction -import java.io.File -import javax.inject.Inject @CacheableTask -abstract class DownloadNodeJsBinaryDistributionArchiveTask : DefaultTask() { +abstract class DownloadNodeJsBinaryDistributionArchiveTask : DataConnectTaskBase(LOGGER_ID_PREFIX) { /** * The inputs required to execute this task. @@ -65,7 +67,7 @@ abstract class DownloadNodeJsBinaryDistributionArchiveTask : DefaultTask() { /** * The path of the downloaded Node.js binary distribution archive. * - * This property's value is computed from [inputs] and [outputDirectory]. + * This property's value is computed from [inputData] and [outputDirectory]. * * This property must not be accessed until after the task executes. */ @@ -74,32 +76,26 @@ abstract class DownloadNodeJsBinaryDistributionArchiveTask : DefaultTask() { get() { val inputs = inputData.get() val outputDirectory = outputDirectory.get() - val downloadedFileName = inputs.calculateDownloadFileName() + val downloadedFileName = inputs.calculateNodeJsPaths().downloadFileName return outputDirectory.file(downloadedFileName) } @get:Inject abstract val providerFactory: ProviderFactory - @get:Inject - abstract val projectLayout: ProjectLayout - @get:Inject abstract val fileSystemOperations: FileSystemOperations - @TaskAction - fun run() { - val inputs: Inputs = inputData.get() - val outputDirectory: File = outputDirectory.get().asFile - val downloadedFile: File = downloadedFile.asFile - - logger.info("inputs: {}", inputs) - logger.info("outputDirectory: {}", outputDirectory.absolutePath) - logger.info("downloadedFile: {}", downloadedFile.absolutePath) - - fileSystemOperations.delete { - delete(outputDirectory) - } + override fun doRun() { + val (operatingSystem: OperatingSystem, nodeJsVersion: String) = inputData.get() + NodeJsTarballDownloader( + operatingSystemType = operatingSystem.type, + operatingSystemArchitecture = operatingSystem.architecture, + nodeJsVersion = nodeJsVersion, + outputDirectory = outputDirectory.get().asFile, + fileSystemOperations = fileSystemOperations, + logger = dataConnectLogger + ).run() } /** @@ -112,18 +108,24 @@ abstract class DownloadNodeJsBinaryDistributionArchiveTask : DefaultTask() { @Serializable data class Inputs( @get:Nested val operatingSystem: OperatingSystem, - @get:Input val nodeJsVersion: String, + @get:Input val nodeJsVersion: String ) + + companion object { + private const val LOGGER_ID_PREFIX = "dnb" + } } internal fun DownloadNodeJsBinaryDistributionArchiveTask.configureFrom(myProviders: MyProjectProviders) { inputData.run { finalizeValueOnRead() - set(providerFactory.provider { - val operatingSystem = myProviders.operatingSystem.get() - val nodeVersion = myProviders.nodeVersion.get() - Inputs(operatingSystem, nodeVersion) - }) + set( + providerFactory.provider { + val operatingSystem = myProviders.operatingSystem.get() + val nodeVersion = myProviders.nodeVersion.get() + Inputs(operatingSystem, nodeVersion) + } + ) } outputDirectory.run { @@ -136,86 +138,40 @@ internal fun DownloadNodeJsBinaryDistributionArchiveTask.configureFrom(myProvide }) } -internal fun Inputs.calculateDownloadUrlPrefix(): String = "https://nodejs.org/dist/v$nodeJsVersion" - -/** - * The URL to download the Node.js binary distribution. - * - * Here are some examples: - * * https://nodejs.org/dist/v20.9.0/node-v20.9.0-darwin-arm64.tar.gz - * * https://nodejs.org/dist/v20.9.0/node-v20.9.0-darwin-x64.tar.gz - * * https://nodejs.org/dist/v20.9.0/node-v20.9.0-linux-arm64.tar.gz - * * https://nodejs.org/dist/v20.9.0/node-v20.9.0-linux-armv7l.tar.gz - * * https://nodejs.org/dist/v20.9.0/node-v20.9.0-linux-x64.tar.gz - * * https://nodejs.org/dist/v20.9.0/node-v20.9.0-win-arm64.zip - * * https://nodejs.org/dist/v20.9.0/node-v20.9.0-win-x64.zip - * * https://nodejs.org/dist/v20.9.0/node-v20.9.0-win-x86.zip - */ -internal fun Inputs.calculateDownloadUrl(): String { - val downloadUrlPrefix = calculateDownloadUrlPrefix() - val downloadFileName = calculateDownloadFileName() - return "$downloadUrlPrefix/$downloadFileName" +private fun Inputs.calculateNodeJsPaths(): NodeJsPaths = NodeJsPaths.from( + nodeJsVersion, + operatingSystem.type, + operatingSystem.architecture +) + +private class NodeJsTarballDownloader( + val operatingSystemType: OperatingSystem.Type, + val operatingSystemArchitecture: OperatingSystem.Architecture, + val nodeJsVersion: String, + val outputDirectory: File, + val fileSystemOperations: FileSystemOperations, + override val logger: DataConnectGradleLogger +) : DataConnectGradleLoggerProvider { + val nodeJsPaths: NodeJsPaths = + NodeJsPaths.from(nodeJsVersion, operatingSystemType, operatingSystemArchitecture) } -/** - * The file name of the download for the Node.js binary distribution. - * - * Here are some examples: - * * node-v20.9.0-darwin-arm64.tar.gz - * * node-v20.9.0-darwin-x64.tar.gz - * * node-v20.9.0-linux-arm64.tar.gz - * * node-v20.9.0-linux-armv7l.tar.gz - * * node-v20.9.0-linux-x64.tar.gz - * * node-v20.9.0-win-arm64.zip - * * node-v20.9.0-win-x64.zip - * * node-v20.9.0-win-x86.zip - */ -internal fun Inputs.calculateDownloadFileName(): String { - val fileExtension: String = when (val type = operatingSystem.type) { - OperatingSystem.Type.Windows -> "zip" - OperatingSystem.Type.MacOS -> "tar.gz" - OperatingSystem.Type.Linux -> "tar.gz" - else -> throw GradleException( - "unable to determine Node.js download file extension for operating system type: $type " + - "(operatingSystem=$operatingSystem) (error code ead53smf45)" - ) - } +private fun NodeJsTarballDownloader.run() { + logger.info("operatingSystemType: $operatingSystemType") + logger.info("operatingSystemArchitecture: $operatingSystemArchitecture") + logger.info("nodeJsVersion: $nodeJsVersion") + logger.info("outputDirectory: $outputDirectory") - val downloadFileNameBase = calculateDownloadFileNameBase() - return "$downloadFileNameBase.$fileExtension" -} + deleteDirectory(outputDirectory, fileSystemOperations) + createDirectory(outputDirectory) -/** - * The base file name of the download for the Node.js binary distribution; - * that is, the file name without the ".zip" or ".tar.gz" extension. - * - * Here are some examples: - * * node-v20.9.0-darwin-arm64 - * * node-v20.9.0-darwin-x64 - * * node-v20.9.0-linux-arm64 - * * node-v20.9.0-linux-armv7l - * * node-v20.9.0-linux-x64 - * * node-v20.9.0-win-arm64 - * * node-v20.9.0-win-x64 - * * node-v20.9.0-win-x86 - */ -internal fun Inputs.calculateDownloadFileNameBase(): String { - val osType: String = when (val type = operatingSystem.type) { - OperatingSystem.Type.Windows -> "win" - OperatingSystem.Type.MacOS -> "darwin" - OperatingSystem.Type.Linux -> "linux" - else -> throw GradleException( - "unable to determine Node.js download base file name for operating system type: $type " + - "(operatingSystem=$operatingSystem) (error code m2grw3h7xz)" - ) - } + val destFile = File(outputDirectory, nodeJsPaths.downloadFileName) + download(nodeJsPaths.downloadUrl, destFile) +} - val osArch: String = when (operatingSystem.arch) { - OperatingSystem.Architecture.Arm64 -> "arm64" - OperatingSystem.Architecture.ArmV7 -> "armv7l" - OperatingSystem.Architecture.X86 -> "x86" - OperatingSystem.Architecture.X86_64 -> "x64" +private fun NodeJsTarballDownloader.download(url: String, destFile: File) { + val downloader = FileDownloader(logger) + runBlocking { + downloader.download(url, destFile, maxNumDownloadBytes = 200_000_000) } - - return "node-v$nodeJsVersion-$osType-$osArch" } diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsTask.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsTask.kt index c3808b6953..62f45e4ad2 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsTask.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsTask.kt @@ -56,7 +56,6 @@ import org.gradle.api.Task import org.gradle.api.file.Directory import org.gradle.api.file.DirectoryProperty import org.gradle.api.file.RegularFile -import org.gradle.api.file.RegularFileProperty import org.gradle.api.provider.Property import org.gradle.api.provider.Provider import org.gradle.api.tasks.CacheableTask @@ -64,7 +63,6 @@ import org.gradle.api.tasks.Input import org.gradle.api.tasks.Internal import org.gradle.api.tasks.Nested import org.gradle.api.tasks.OutputDirectory -import org.gradle.api.tasks.OutputFile import org.gradle.api.tasks.TaskAction import org.gradle.kotlin.dsl.newInstance import org.pgpainless.sop.SOPImpl @@ -156,8 +154,8 @@ abstract class DownloadNodeJsTask : DefaultTask() { append(Json.encodeToString(version.get())) append(", os=") append(Json.encodeToString(os.type.name.lowercase())) - append(", arch=") - append(Json.encodeToString(os.arch.name.lowercase())) + append(", architecture=") + append(Json.encodeToString(os.architecture.name.lowercase())) append("}") } } @@ -321,7 +319,7 @@ internal val DownloadOfficialVersion.downloadFileNameBase: String ) } - val osArch: String = when (os.arch) { + val osArch: String = when (os.architecture) { OperatingSystem.Architecture.Arm64 -> "arm64" OperatingSystem.Architecture.ArmV7 -> "armv7l" OperatingSystem.Architecture.X86 -> "x86" diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/GenerateDataConnectSourcesTask.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/GenerateDataConnectSourcesTask.kt index 3e26dc4281..4d37d2f908 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/GenerateDataConnectSourcesTask.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/GenerateDataConnectSourcesTask.kt @@ -17,6 +17,7 @@ package com.google.firebase.example.dataconnect.gradle.tasks import com.google.firebase.example.dataconnect.gradle.providers.MyVariantProviders +import com.google.firebase.example.dataconnect.gradle.util.runCommand import java.io.File import javax.inject.Inject import org.gradle.api.DefaultTask diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/NodeJsPaths.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/NodeJsPaths.kt new file mode 100644 index 0000000000..ba8b9f92c5 --- /dev/null +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/NodeJsPaths.kt @@ -0,0 +1,131 @@ +/* + * 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.example.dataconnect.gradle.tasks + +import com.google.firebase.example.dataconnect.gradle.DataConnectGradleException +import com.google.firebase.example.dataconnect.gradle.providers.OperatingSystem + +class NodeJsPaths( + val downloadUrl: String, + val downloadFileName: String +) { + companion object +} + +fun NodeJsPaths.Companion.from( + nodeJsVersion: String, + operatingSystemType: OperatingSystem.Type, + operatingSystemArchitecture: OperatingSystem.Architecture +): NodeJsPaths { + val calculator = NodeJsPathsCalculator(nodeJsVersion, operatingSystemType, operatingSystemArchitecture) + return NodeJsPaths( + downloadUrl = calculator.downloadUrl(), + downloadFileName = calculator.downloadFileName() + ) +} + +private class NodeJsPathsCalculator( + val nodeJsVersion: String, + val operatingSystemType: OperatingSystem.Type, + val operatingSystemArchitecture: OperatingSystem.Architecture +) + +private fun NodeJsPathsCalculator.downloadUrlPrefix(): String = "https://nodejs.org/dist/v$nodeJsVersion" + +/** + * The URL to download the Node.js binary distribution. + * + * Here are some examples: + * * https://nodejs.org/dist/v20.9.0/node-v20.9.0-darwin-arm64.tar.gz + * * https://nodejs.org/dist/v20.9.0/node-v20.9.0-darwin-x64.tar.gz + * * https://nodejs.org/dist/v20.9.0/node-v20.9.0-linux-arm64.tar.gz + * * https://nodejs.org/dist/v20.9.0/node-v20.9.0-linux-armv7l.tar.gz + * * https://nodejs.org/dist/v20.9.0/node-v20.9.0-linux-x64.tar.gz + * * https://nodejs.org/dist/v20.9.0/node-v20.9.0-win-arm64.zip + * * https://nodejs.org/dist/v20.9.0/node-v20.9.0-win-x64.zip + * * https://nodejs.org/dist/v20.9.0/node-v20.9.0-win-x86.zip + */ +private fun NodeJsPathsCalculator.downloadUrl(): String { + val downloadUrlPrefix = downloadUrlPrefix() + val downloadFileName = downloadFileName() + return "$downloadUrlPrefix/$downloadFileName" +} + +/** + * The file name of the download for the Node.js binary distribution. + * + * Here are some examples: + * * node-v20.9.0-darwin-arm64.tar.gz + * * node-v20.9.0-darwin-x64.tar.gz + * * node-v20.9.0-linux-arm64.tar.gz + * * node-v20.9.0-linux-armv7l.tar.gz + * * node-v20.9.0-linux-x64.tar.gz + * * node-v20.9.0-win-arm64.zip + * * node-v20.9.0-win-x64.zip + * * node-v20.9.0-win-x86.zip + */ +private fun NodeJsPathsCalculator.downloadFileName(): String { + val fileExtension: String = when (operatingSystemType) { + OperatingSystem.Type.Windows -> "zip" + OperatingSystem.Type.MacOS -> "tar.gz" + OperatingSystem.Type.Linux -> "tar.gz" + else -> throw DataConnectGradleException( + "unable to determine Node.js download file extension " + + "for operating system type: $operatingSystemType " + + "(error code ead53smf45)" + ) + } + + val downloadFileNameBase = downloadFileNameBase() + return "$downloadFileNameBase.$fileExtension" +} + +/** + * The base file name of the download for the Node.js binary distribution; + * that is, the file name without the ".zip" or ".tar.gz" extension. + * + * Here are some examples: + * * node-v20.9.0-darwin-arm64 + * * node-v20.9.0-darwin-x64 + * * node-v20.9.0-linux-arm64 + * * node-v20.9.0-linux-armv7l + * * node-v20.9.0-linux-x64 + * * node-v20.9.0-win-arm64 + * * node-v20.9.0-win-x64 + * * node-v20.9.0-win-x86 + */ +private fun NodeJsPathsCalculator.downloadFileNameBase(): String { + val osType: String = when (operatingSystemType) { + OperatingSystem.Type.Windows -> "win" + OperatingSystem.Type.MacOS -> "darwin" + OperatingSystem.Type.Linux -> "linux" + else -> throw DataConnectGradleException( + "unable to determine Node.js download base file name " + + "for operating system type: $operatingSystemType " + + "(error code m2grw3h7xz)" + ) + } + + val osArch: String = when (operatingSystemArchitecture) { + OperatingSystem.Architecture.Arm64 -> "arm64" + OperatingSystem.Architecture.ArmV7 -> "armv7l" + OperatingSystem.Architecture.X86 -> "x86" + OperatingSystem.Architecture.X86_64 -> "x64" + } + + return "node-v$nodeJsVersion-$osType-$osArch" +} diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/SetupFirebaseToolsTask.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/SetupFirebaseToolsTask.kt index 99730410c6..645b254d89 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/SetupFirebaseToolsTask.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/SetupFirebaseToolsTask.kt @@ -18,6 +18,7 @@ package com.google.firebase.example.dataconnect.gradle.tasks import com.google.firebase.example.dataconnect.gradle.cache.CacheManager import com.google.firebase.example.dataconnect.gradle.providers.MyProjectProviders +import com.google.firebase.example.dataconnect.gradle.util.runCommand import java.io.File import javax.inject.Inject import org.gradle.api.DefaultTask @@ -29,10 +30,8 @@ import org.gradle.api.provider.Property import org.gradle.api.provider.Provider import org.gradle.api.tasks.CacheableTask import org.gradle.api.tasks.Input -import org.gradle.api.tasks.InputFile import org.gradle.api.tasks.Internal import org.gradle.api.tasks.OutputDirectory -import org.gradle.api.tasks.OutputFile import org.gradle.api.tasks.TaskAction import org.gradle.process.ExecOperations diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/util/DataConnectGradleLogger.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/util/DataConnectGradleLogger.kt new file mode 100644 index 0000000000..9c9c2a7025 --- /dev/null +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/util/DataConnectGradleLogger.kt @@ -0,0 +1,53 @@ +/* + * 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.example.dataconnect.gradle.util + +import kotlin.random.Random +import org.gradle.api.logging.LogLevel +import org.gradle.api.logging.Logger + +/** + * A wrapper around a [Logger] that provides a bit of functionality used commonly by the + * Firebase Data Connect Gradle plugin and its classes. + * + * Each logged message will be prefixed with a randomly-generated alphanumeric prefix, + * making it easy to track the output of a logger throughout the sea of log messages in + * Gradle's output. + * + * @param loggerIdPrefix A string with which to prefix the randomly-generated logger ID + * included in each message logged by this object. This is typically a very short (between + * 2 and 4 characters) string whose prefix will give some indication of where the logged + * messages came from. A common strategy is to use the uppercase characters of a class name; + * for example a class named "DataFileLoader" could use the `loggerIdPrefix` of "dfl". + * @param logger The logger that will be used to do the actual logging. + */ +class DataConnectGradleLogger(loggerIdPrefix: String, private val logger: Logger) { + + private val loggerId: String = "$loggerIdPrefix${Random.nextAlphanumericString(8)}" + + val isDebugEnabled: Boolean get() = logger.isDebugEnabled + + val isInfoEnabled: Boolean get() = logger.isInfoEnabled + + fun info(message: String): Unit = log(LogLevel.INFO, message) + + fun warn(message: String): Unit = log(LogLevel.WARN, message, prefix = "WARNING: ") + + private fun log(level: LogLevel, message: String, prefix: String = "") { + logger.log(level, "[{}] {}{}", loggerId, prefix, message) + } +} diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/util/DataConnectGradleLoggerProvider.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/util/DataConnectGradleLoggerProvider.kt new file mode 100644 index 0000000000..05381fd893 --- /dev/null +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/util/DataConnectGradleLoggerProvider.kt @@ -0,0 +1,46 @@ +/* + * 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.example.dataconnect.gradle.util + +import com.google.firebase.example.dataconnect.gradle.DataConnectGradleException +import java.io.File +import org.gradle.api.file.FileSystemOperations + +interface DataConnectGradleLoggerProvider { + val logger: DataConnectGradleLogger +} + +fun DataConnectGradleLoggerProvider.deleteDirectory(dir: File, fileSystemOperations: FileSystemOperations) { + logger.info("Deleting directory: $dir") + fileSystemOperations.runCatching { delete { delete(dir) } }.onFailure { + throw DataConnectGradleException( + "unable to delete directory: ${dir.absolutePath}: $it " + + "(error code 6trngh6x47)", + it + ) + } +} + +fun DataConnectGradleLoggerProvider.createDirectory(dir: File) { + logger.info("Creating directory: $dir") + if (!dir.mkdirs()) { + throw DataConnectGradleException( + "unable to create directory: ${dir.absolutePath} " + + "(error code j7x4sw7w95)" + ) + } +} diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/util/FileDownloader.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/util/FileDownloader.kt new file mode 100644 index 0000000000..19b4f01b7d --- /dev/null +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/util/FileDownloader.kt @@ -0,0 +1,128 @@ +/* + * 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.example.dataconnect.gradle.util + +import com.google.firebase.example.dataconnect.gradle.DataConnectGradleException +import io.ktor.client.HttpClient +import io.ktor.client.HttpClientConfig +import io.ktor.client.call.body +import io.ktor.client.engine.cio.CIO +import io.ktor.client.plugins.logging.LogLevel +import io.ktor.client.plugins.logging.Logger +import io.ktor.client.plugins.logging.Logging +import io.ktor.client.request.prepareGet +import io.ktor.utils.io.ByteReadChannel +import io.ktor.utils.io.jvm.javaio.copyTo +import java.io.File +import java.text.NumberFormat +import java.util.concurrent.atomic.AtomicReference + +class FileDownloader(private val logger: DataConnectGradleLogger) : AutoCloseable { + + private val state: AtomicReference = AtomicReference(State.Uninitialized) + private val httpClient: HttpClient get() = getOrCreateHttpClient() + + suspend fun download(url: String, destFile: File, maxNumDownloadBytes: Long) { + logger.info("Downloading $url to ${destFile.absolutePath}") + + val actualNumBytesDownloaded = httpClient.prepareGet(url).execute { httpResponse -> + val downloadChannel: ByteReadChannel = httpResponse.body() + destFile.outputStream().use { destFileOutputStream -> + downloadChannel.copyTo(destFileOutputStream, limit = maxNumDownloadBytes) + } + } + + val numberFormat = NumberFormat.getNumberInstance() + val actualNumBytesDownloadedStr = numberFormat.format(actualNumBytesDownloaded) + if (actualNumBytesDownloaded >= maxNumDownloadBytes) { + val maxNumDownloadBytesStr = numberFormat.format(maxNumDownloadBytes) + throw DataConnectGradleException( + "Downloading $url failed: " + + "maximum file size $maxNumDownloadBytesStr bytes exceeded; " + + "cancelled after downloading $actualNumBytesDownloadedStr bytes " + + "(error code hvmhysn5vy)" + ) + } + + logger.info( + "Successfully downloaded $actualNumBytesDownloadedStr bytes from $url " + + "to ${destFile.absolutePath}" + ) + } + + override fun close() { + while (true) { + val oldState = state.get() + val httpClient = when (oldState) { + is State.Uninitialized -> null + is State.Open -> oldState.httpClient + is State.Closed -> break + } + if (state.compareAndSet(oldState, State.Closed)) { + httpClient?.close() + } + } + } + + private fun newHttpClient(): HttpClient = HttpClient(CIO) { + expectSuccess = true + installDataConnectLogger(logger) + } + + private fun getOrCreateHttpClient(): HttpClient { + while (true) { + when (val oldState = state.get()) { + is State.Open -> return oldState.httpClient + is State.Closed -> throw DataConnectGradleException( + "FileDownloader has been closed (error code thp7vtw9rm)" + ) + is State.Uninitialized -> { + val httpClient = newHttpClient() + if (!state.compareAndSet(oldState, State.Open(httpClient))) { + httpClient.close() + } + } + } + } + } + + private sealed interface State { + object Uninitialized : State + class Open(val httpClient: HttpClient) : State + object Closed : State + } +} + +private fun HttpClientConfig<*>.installDataConnectLogger(dataConnectGradleLogger: DataConnectGradleLogger) { + install(Logging) { + level = if (dataConnectGradleLogger.isDebugEnabled) { + LogLevel.HEADERS + } else if (dataConnectGradleLogger.isInfoEnabled) { + LogLevel.INFO + } else { + LogLevel.NONE + } + + logger = object : Logger { + override fun log(message: String) { + message.lines().forEach { line -> + dataConnectGradleLogger.info("ktor: ${line.trimEnd()}") + } + } + } + } +} diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/util.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/util/GradleKtx.kt similarity index 95% rename from dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/util.kt rename to dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/util/GradleKtx.kt index ed4178c477..07c33baef2 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/util.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/util/GradleKtx.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.google.firebase.example.dataconnect.gradle.tasks +package com.google.firebase.example.dataconnect.gradle.util import java.io.File import org.gradle.api.logging.Logger diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/util/RandomKtx.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/util/RandomKtx.kt new file mode 100644 index 0000000000..3cd89fbff3 --- /dev/null +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/util/RandomKtx.kt @@ -0,0 +1,40 @@ +/* + * 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.example.dataconnect.gradle.util + +import kotlin.random.Random + +/** + * 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 comprised 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" From 8174272d5e9c91acb92d3678d886756ee2112850 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Wed, 27 Nov 2024 19:58:54 +0000 Subject: [PATCH 61/79] download SHASUMS256.txt.asc --- ...loadNodeJsBinaryDistributionArchiveTask.kt | 38 +++++++++++++++---- .../dataconnect/gradle/tasks/NodeJsPaths.kt | 17 ++++++++- 2 files changed, 46 insertions(+), 9 deletions(-) diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsBinaryDistributionArchiveTask.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsBinaryDistributionArchiveTask.kt index 724ca3c053..08ce53c108 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsBinaryDistributionArchiveTask.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsBinaryDistributionArchiveTask.kt @@ -88,14 +88,19 @@ abstract class DownloadNodeJsBinaryDistributionArchiveTask : DataConnectTaskBase override fun doRun() { val (operatingSystem: OperatingSystem, nodeJsVersion: String) = inputData.get() - NodeJsTarballDownloader( + + val nodeJsTarballDownloader = NodeJsTarballDownloader( operatingSystemType = operatingSystem.type, operatingSystemArchitecture = operatingSystem.architecture, nodeJsVersion = nodeJsVersion, outputDirectory = outputDirectory.get().asFile, fileSystemOperations = fileSystemOperations, logger = dataConnectLogger - ).run() + ) + + nodeJsTarballDownloader.use { + it.run() + } } /** @@ -151,9 +156,15 @@ private class NodeJsTarballDownloader( val outputDirectory: File, val fileSystemOperations: FileSystemOperations, override val logger: DataConnectGradleLogger -) : DataConnectGradleLoggerProvider { +) : DataConnectGradleLoggerProvider, AutoCloseable { val nodeJsPaths: NodeJsPaths = NodeJsPaths.from(nodeJsVersion, operatingSystemType, operatingSystemArchitecture) + + val fileDownloader: FileDownloader = FileDownloader(logger) + + override fun close() { + fileDownloader.close() + } } private fun NodeJsTarballDownloader.run() { @@ -166,12 +177,25 @@ private fun NodeJsTarballDownloader.run() { createDirectory(outputDirectory) val destFile = File(outputDirectory, nodeJsPaths.downloadFileName) - download(nodeJsPaths.downloadUrl, destFile) + downloadNodeJsBinaryArchive(destFile) + verifyNodeJsReleaseSignature(destFile) } -private fun NodeJsTarballDownloader.download(url: String, destFile: File) { - val downloader = FileDownloader(logger) +private fun NodeJsTarballDownloader.downloadNodeJsBinaryArchive(destFile: File) { + val url = nodeJsPaths.downloadUrl runBlocking { - downloader.download(url, destFile, maxNumDownloadBytes = 200_000_000) + fileDownloader.download(url, destFile, maxNumDownloadBytes = 200_000_000) } } + +private fun NodeJsTarballDownloader.downloadShasumsFile(destFile: File) { + val url = nodeJsPaths.shasumsUrl + runBlocking { + fileDownloader.download(url, destFile, maxNumDownloadBytes = 100_000) + } +} + +private fun NodeJsTarballDownloader.verifyNodeJsReleaseSignature(file: File) { + val shasumsFile = File(outputDirectory, nodeJsPaths.shasumsFileName) + downloadShasumsFile(shasumsFile) +} diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/NodeJsPaths.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/NodeJsPaths.kt index ba8b9f92c5..2d11a84f02 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/NodeJsPaths.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/NodeJsPaths.kt @@ -21,7 +21,9 @@ import com.google.firebase.example.dataconnect.gradle.providers.OperatingSystem class NodeJsPaths( val downloadUrl: String, - val downloadFileName: String + val downloadFileName: String, + val shasumsUrl: String, + val shasumsFileName: String, ) { companion object } @@ -34,7 +36,9 @@ fun NodeJsPaths.Companion.from( val calculator = NodeJsPathsCalculator(nodeJsVersion, operatingSystemType, operatingSystemArchitecture) return NodeJsPaths( downloadUrl = calculator.downloadUrl(), - downloadFileName = calculator.downloadFileName() + downloadFileName = calculator.downloadFileName(), + shasumsUrl = calculator.shasumsDownloadUrl(), + shasumsFileName = calculator.shasumsDownloadFileName() ) } @@ -65,6 +69,15 @@ private fun NodeJsPathsCalculator.downloadUrl(): String { return "$downloadUrlPrefix/$downloadFileName" } +@Suppress("UnusedReceiverParameter") +private fun NodeJsPathsCalculator.shasumsDownloadFileName(): String = "SHASUMS256.txt.asc" + +private fun NodeJsPathsCalculator.shasumsDownloadUrl(): String { + val downloadUrlPrefix = downloadUrlPrefix() + val downloadFileName = shasumsDownloadFileName() + return "$downloadUrlPrefix/$downloadFileName" +} + /** * The file name of the download for the Node.js binary distribution. * From 024039d6209e65c8c7bed8fcbc5cb11d9b96ae49 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Wed, 27 Nov 2024 21:51:53 +0000 Subject: [PATCH 62/79] Sha256SignatureVerifier.kt added --- .../gradle/tasks/DataConnectTaskBase.kt | 8 +- ...loadNodeJsBinaryDistributionArchiveTask.kt | 22 ++++- .../gradle/util/DataConnectGradleLogger.kt | 22 ++++- .../util/DataConnectGradleLoggerProvider.kt | 4 +- .../dataconnect/gradle/util/FileDownloader.kt | 10 +-- .../gradle/util/Sha256SignatureVerifier.kt | 90 +++++++++++++++++++ 6 files changed, 139 insertions(+), 17 deletions(-) create mode 100644 dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/util/Sha256SignatureVerifier.kt diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DataConnectTaskBase.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DataConnectTaskBase.kt index 9784233582..ff1f73c430 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DataConnectTaskBase.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DataConnectTaskBase.kt @@ -32,17 +32,17 @@ abstract class DataConnectTaskBase(loggerIdPrefix: String) : DefaultTask() { init { val superTypeNames = this::class.allSupertypes.map { it.toString() }.sorted().joinToString(", ") - dataConnectLogger.info( + dataConnectLogger.info { "Task $path created: class=${this::class.qualifiedName} (supertypes are: $superTypeNames)" - ) + } } @TaskAction fun run() { - dataConnectLogger.info("Task $path starting execution") + dataConnectLogger.info { "Task $path starting execution" } runCatching { doRun() }.fold( onSuccess = { - dataConnectLogger.info("Task $path execution completed successfully") + dataConnectLogger.info { "Task $path execution completed successfully" } }, onFailure = { dataConnectLogger.warn("Task $path execution failed: $it") diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsBinaryDistributionArchiveTask.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsBinaryDistributionArchiveTask.kt index 08ce53c108..85deb6e353 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsBinaryDistributionArchiveTask.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsBinaryDistributionArchiveTask.kt @@ -23,6 +23,8 @@ import com.google.firebase.example.dataconnect.gradle.tasks.DownloadNodeJsBinary import com.google.firebase.example.dataconnect.gradle.util.DataConnectGradleLogger import com.google.firebase.example.dataconnect.gradle.util.DataConnectGradleLoggerProvider import com.google.firebase.example.dataconnect.gradle.util.FileDownloader +import com.google.firebase.example.dataconnect.gradle.util.Sha256SignatureVerifier +import com.google.firebase.example.dataconnect.gradle.util.installKeysFromKeyListResource import com.google.firebase.example.dataconnect.gradle.util.createDirectory import com.google.firebase.example.dataconnect.gradle.util.deleteDirectory import java.io.File @@ -39,6 +41,7 @@ import org.gradle.api.tasks.Input import org.gradle.api.tasks.Internal import org.gradle.api.tasks.Nested import org.gradle.api.tasks.OutputDirectory +import java.nio.charset.StandardCharsets @CacheableTask abstract class DownloadNodeJsBinaryDistributionArchiveTask : DataConnectTaskBase(LOGGER_ID_PREFIX) { @@ -168,10 +171,10 @@ private class NodeJsTarballDownloader( } private fun NodeJsTarballDownloader.run() { - logger.info("operatingSystemType: $operatingSystemType") - logger.info("operatingSystemArchitecture: $operatingSystemArchitecture") - logger.info("nodeJsVersion: $nodeJsVersion") - logger.info("outputDirectory: $outputDirectory") + logger.info { "operatingSystemType: $operatingSystemType" } + logger.info { "operatingSystemArchitecture: $operatingSystemArchitecture" } + logger.info { "nodeJsVersion: $nodeJsVersion" } + logger.info { "outputDirectory: $outputDirectory" } deleteDirectory(outputDirectory, fileSystemOperations) createDirectory(outputDirectory) @@ -198,4 +201,15 @@ private fun NodeJsTarballDownloader.downloadShasumsFile(destFile: File) { private fun NodeJsTarballDownloader.verifyNodeJsReleaseSignature(file: File) { val shasumsFile = File(outputDirectory, nodeJsPaths.shasumsFileName) downloadShasumsFile(shasumsFile) + + Sha256SignatureVerifier(logger).let { signatureVerifier -> + logger.info { "Loading Node.js release signing certificates " + + "from resource: $KEY_LIST_RESOURCE_PATH" } + signatureVerifier.installKeysFromKeyListResource(KEY_LIST_RESOURCE_PATH) + + logger.info { "Loading SHA256 hashes from file: ${shasumsFile.absolutePath}" } + signatureVerifier.installShasumsFromFile(shasumsFile) + } } + +private const val KEY_LIST_RESOURCE_PATH = "com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/keys.list" \ No newline at end of file diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/util/DataConnectGradleLogger.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/util/DataConnectGradleLogger.kt index 9c9c2a7025..a9db22f725 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/util/DataConnectGradleLogger.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/util/DataConnectGradleLogger.kt @@ -43,11 +43,29 @@ class DataConnectGradleLogger(loggerIdPrefix: String, private val logger: Logger val isInfoEnabled: Boolean get() = logger.isInfoEnabled - fun info(message: String): Unit = log(LogLevel.INFO, message) + val isWarnEnabled: Boolean get() = logger.isWarnEnabled + + inline fun debug(block: () -> String): Unit { + if (isDebugEnabled) { + log(LogLevel.DEBUG, block()) + } + } + + inline fun info(block: () -> String): Unit { + if (isInfoEnabled) { + log(LogLevel.INFO, block()) + } + } + + inline fun warn(block: () -> String): Unit { + if (isWarnEnabled) { + log(LogLevel.WARN, block()) + } + } fun warn(message: String): Unit = log(LogLevel.WARN, message, prefix = "WARNING: ") - private fun log(level: LogLevel, message: String, prefix: String = "") { + fun log(level: LogLevel, message: String, prefix: String = "") { logger.log(level, "[{}] {}{}", loggerId, prefix, message) } } diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/util/DataConnectGradleLoggerProvider.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/util/DataConnectGradleLoggerProvider.kt index 05381fd893..346500d485 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/util/DataConnectGradleLoggerProvider.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/util/DataConnectGradleLoggerProvider.kt @@ -25,7 +25,7 @@ interface DataConnectGradleLoggerProvider { } fun DataConnectGradleLoggerProvider.deleteDirectory(dir: File, fileSystemOperations: FileSystemOperations) { - logger.info("Deleting directory: $dir") + logger.info { "Deleting directory: $dir" } fileSystemOperations.runCatching { delete { delete(dir) } }.onFailure { throw DataConnectGradleException( "unable to delete directory: ${dir.absolutePath}: $it " + @@ -36,7 +36,7 @@ fun DataConnectGradleLoggerProvider.deleteDirectory(dir: File, fileSystemOperati } fun DataConnectGradleLoggerProvider.createDirectory(dir: File) { - logger.info("Creating directory: $dir") + logger.info { "Creating directory: $dir" } if (!dir.mkdirs()) { throw DataConnectGradleException( "unable to create directory: ${dir.absolutePath} " + diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/util/FileDownloader.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/util/FileDownloader.kt index 19b4f01b7d..edc627e6af 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/util/FileDownloader.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/util/FileDownloader.kt @@ -37,7 +37,7 @@ class FileDownloader(private val logger: DataConnectGradleLogger) : AutoCloseabl private val httpClient: HttpClient get() = getOrCreateHttpClient() suspend fun download(url: String, destFile: File, maxNumDownloadBytes: Long) { - logger.info("Downloading $url to ${destFile.absolutePath}") + logger.info {"Downloading $url to ${destFile.absolutePath}"} val actualNumBytesDownloaded = httpClient.prepareGet(url).execute { httpResponse -> val downloadChannel: ByteReadChannel = httpResponse.body() @@ -58,10 +58,10 @@ class FileDownloader(private val logger: DataConnectGradleLogger) : AutoCloseabl ) } - logger.info( + logger.info { "Successfully downloaded $actualNumBytesDownloadedStr bytes from $url " + - "to ${destFile.absolutePath}" - ) + "to ${destFile.absolutePath}" + } } override fun close() { @@ -120,7 +120,7 @@ private fun HttpClientConfig<*>.installDataConnectLogger(dataConnectGradleLogger logger = object : Logger { override fun log(message: String) { message.lines().forEach { line -> - dataConnectGradleLogger.info("ktor: ${line.trimEnd()}") + dataConnectGradleLogger.info{"ktor: ${line.trimEnd()}"} } } } diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/util/Sha256SignatureVerifier.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/util/Sha256SignatureVerifier.kt new file mode 100644 index 0000000000..82af951576 --- /dev/null +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/util/Sha256SignatureVerifier.kt @@ -0,0 +1,90 @@ +package com.google.firebase.example.dataconnect.gradle.util + +import com.google.firebase.example.dataconnect.gradle.DataConnectGradleException +import org.pgpainless.sop.SOPImpl +import java.io.File +import java.nio.charset.StandardCharsets +import java.util.concurrent.locks.ReentrantLock +import kotlin.concurrent.withLock + +class Sha256SignatureVerifier(val logger: DataConnectGradleLogger) { + + private val lock = ReentrantLock() + private val certificates = mutableListOf() + private val hashByFileName = mutableMapOf() + + fun installShasumsFromFile(shasumsFile: File) { + val shasumsFileBytes = verifyShasumsFile(shasumsFile) + val shasumsFileText = String(shasumsFileBytes, StandardCharsets.UTF_8) + val shasumsFileLines = shasumsFileText.lines() + + val regex = Regex("""\s*(.*?)\s+(.*?)\s*""") + val curHashByFileName = shasumsFileLines.mapNotNull { regex.matchEntire(it)}.associate { + val hash = it.groupValues[1] + val fileName = it.groupValues[2] + fileName to hash + } + + lock.withLock { + hashByFileName.putAll(curHashByFileName) + } + } + + private fun verifyShasumsFile(file: File): ByteArray { + val sop = SOPImpl() + + val verificationResult = sop.inlineVerify().let { verifier -> + lock.withLock { certificates.toList() }.forEach { + verifier.cert(it) + } + file.inputStream().use { inputStream -> + verifier.data(inputStream).toByteArrayAndResult() + } + } + + return verificationResult.bytes + } + + fun installKeyFromResource(resourcePath: String) { + val certificateBytes = loadResource(resourcePath) + lock.withLock { + certificates.add(certificateBytes) + } + } + +} + +fun Sha256SignatureVerifier.installKeysFromKeyListResource(keyListResourcePath: String) { + val keyNames = loadKeyNameList(keyListResourcePath) + logger.debug { "Loaded the names of ${keyNames.size} keys from resource: $keyListResourcePath" } + + val lastSlashIndex = keyListResourcePath.lastIndexOf('/') + val resourceKeyPathPrefix = if (lastSlashIndex <= 0) { + "" + } else { + keyListResourcePath.substring(0..lastSlashIndex) + } + + keyNames.forEach { + installKeyFromResource("$resourceKeyPathPrefix$it.asc") + } +} + +private fun Sha256SignatureVerifier.loadResource(path: String): ByteArray { + val classLoader = this::class.java.classLoader + return classLoader.getResourceAsStream(path).let { inputStream -> + inputStream.use { + if (inputStream === null) { + throw DataConnectGradleException("resource not found: $path (error code 6ygyz2dj2n)") + } + inputStream.readAllBytes() + } + } +} + +private fun Sha256SignatureVerifier.loadKeyNameList(resourcePath: String): List { + logger.debug { "Loading resource: $resourcePath" } + val resourceBytes = loadResource(resourcePath) + val resourceText = String(resourceBytes, StandardCharsets.UTF_8) + return resourceText.lines().map { it.trim() }.filter { it.isNotBlank() } +} From 2fa6f943955d6e7dfc27036a39fb5be9f55dcf56 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Thu, 28 Nov 2024 19:23:54 +0000 Subject: [PATCH 63/79] code cleanup --- ...loadNodeJsBinaryDistributionArchiveTask.kt | 43 ++++-- .../dataconnect/gradle/util/FileKtx.kt | 50 ++++++ .../gradle/util/SHASUMS256.txt.asc.example | 60 ++++++++ .../gradle/util/Sha256SignatureVerifier.kt | 145 ++++++++++++++---- 4 files changed, 259 insertions(+), 39 deletions(-) create mode 100644 dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/util/FileKtx.kt create mode 100644 dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/util/SHASUMS256.txt.asc.example diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsBinaryDistributionArchiveTask.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsBinaryDistributionArchiveTask.kt index 85deb6e353..a0ee851910 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsBinaryDistributionArchiveTask.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsBinaryDistributionArchiveTask.kt @@ -16,6 +16,7 @@ package com.google.firebase.example.dataconnect.gradle.tasks +import com.google.firebase.example.dataconnect.gradle.DataConnectGradleException import com.google.firebase.example.dataconnect.gradle.cache.CacheManager import com.google.firebase.example.dataconnect.gradle.providers.MyProjectProviders import com.google.firebase.example.dataconnect.gradle.providers.OperatingSystem @@ -24,11 +25,10 @@ import com.google.firebase.example.dataconnect.gradle.util.DataConnectGradleLogg import com.google.firebase.example.dataconnect.gradle.util.DataConnectGradleLoggerProvider import com.google.firebase.example.dataconnect.gradle.util.FileDownloader import com.google.firebase.example.dataconnect.gradle.util.Sha256SignatureVerifier -import com.google.firebase.example.dataconnect.gradle.util.installKeysFromKeyListResource +import com.google.firebase.example.dataconnect.gradle.util.addCertificatesFromKeyListResource +import com.google.firebase.example.dataconnect.gradle.util.addHashesFromShasumsFile import com.google.firebase.example.dataconnect.gradle.util.createDirectory import com.google.firebase.example.dataconnect.gradle.util.deleteDirectory -import java.io.File -import javax.inject.Inject import kotlinx.coroutines.runBlocking import kotlinx.serialization.Serializable import org.gradle.api.file.DirectoryProperty @@ -41,7 +41,8 @@ import org.gradle.api.tasks.Input import org.gradle.api.tasks.Internal import org.gradle.api.tasks.Nested import org.gradle.api.tasks.OutputDirectory -import java.nio.charset.StandardCharsets +import java.io.File +import javax.inject.Inject @CacheableTask abstract class DownloadNodeJsBinaryDistributionArchiveTask : DataConnectTaskBase(LOGGER_ID_PREFIX) { @@ -202,14 +203,34 @@ private fun NodeJsTarballDownloader.verifyNodeJsReleaseSignature(file: File) { val shasumsFile = File(outputDirectory, nodeJsPaths.shasumsFileName) downloadShasumsFile(shasumsFile) - Sha256SignatureVerifier(logger).let { signatureVerifier -> - logger.info { "Loading Node.js release signing certificates " + - "from resource: $KEY_LIST_RESOURCE_PATH" } - signatureVerifier.installKeysFromKeyListResource(KEY_LIST_RESOURCE_PATH) + val signatureVerifier = Sha256SignatureVerifier() + logger.info { + "Loading Node.js release signing certificates " + + "from resource: $KEY_LIST_RESOURCE_PATH" + } + val numCertificatesAdded = signatureVerifier.addCertificatesFromKeyListResource(KEY_LIST_RESOURCE_PATH) + logger.info { "Loaded $numCertificatesAdded certificates from $KEY_LIST_RESOURCE_PATH" } + + logger.info { "Loading SHA256 hashes from file: ${shasumsFile.absolutePath}" } + val fileNamesWithLoadedHash = signatureVerifier.addHashesFromShasumsFile(shasumsFile) + logger.info { + "Loaded ${fileNamesWithLoadedHash.size} hashes from ${shasumsFile.absolutePath} " + + "for file names: ${fileNamesWithLoadedHash.sorted()}" + } + + if (!fileNamesWithLoadedHash.contains(file.name)) { + throw DataConnectGradleException( + "hash for file name ${file.name} " + + "not found in ${shasumsFile.absolutePath} " + + "(error code yx3g25s926)" + ) + } - logger.info { "Loading SHA256 hashes from file: ${shasumsFile.absolutePath}" } - signatureVerifier.installShasumsFromFile(shasumsFile) + file.inputStream().use { inputStream -> + logger.info { "Verifying SHA256 hash of file: ${file.absolutePath}"} + signatureVerifier.verifyHash(inputStream, file.name) } } -private const val KEY_LIST_RESOURCE_PATH = "com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/keys.list" \ No newline at end of file +private const val KEY_LIST_RESOURCE_PATH = + "com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/keys.list" \ No newline at end of file diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/util/FileKtx.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/util/FileKtx.kt new file mode 100644 index 0000000000..110c6b56cc --- /dev/null +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/util/FileKtx.kt @@ -0,0 +1,50 @@ +/* + * 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.example.dataconnect.gradle.util + +import java.io.ByteArrayOutputStream +import java.io.File +import java.io.IOException + +fun File.readBytes(byteLimit: Int): ByteArray = inputStream().use { inputStream -> + require(byteLimit >= 0) { "invalid byte limit: $byteLimit " + + "(must be greater than or equal to zero) (error code vrr6daevyw)" } + + val buffer = ByteArray(8192) + val byteArrayOutputStream = ByteArrayOutputStream() + var totalByteReadCount = 0 + while (true) { + val numBytesJustRead = inputStream.read(buffer) + if (numBytesJustRead < 0) { + break + } + + totalByteReadCount += numBytesJustRead + if (totalByteReadCount > byteLimit) { + val byteLimitStr = String.format("%,d", byteLimit) + val totalByteReadCountStr = String.format("%,d", totalByteReadCount) + throw TooManyBytesReadException("too many bytes read: $byteLimitStr " + + "(expected at most $totalByteReadCountStr) (error code fnaxenqmxs)") + } + + byteArrayOutputStream.write(buffer, 0, numBytesJustRead) + } + + return byteArrayOutputStream.toByteArray() +} + +class TooManyBytesReadException(message: String) : IOException(message) \ No newline at end of file diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/util/SHASUMS256.txt.asc.example b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/util/SHASUMS256.txt.asc.example new file mode 100644 index 0000000000..0cc270fdf5 --- /dev/null +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/util/SHASUMS256.txt.asc.example @@ -0,0 +1,60 @@ +-----BEGIN PGP SIGNED MESSAGE----- +Hash: SHA256 + +dc148e207d1a6456af05bf55d2c5af0185a2c79139fa64c8278ca257dc4894d5 node-v20.18.1-aix-ppc64.tar.gz +1f05a889ee095bebae179d794d409d2c4a6b42f63be891a9b1e16740712891e6 node-v20.18.1-arm64.msi +9e92ce1032455a9cc419fe71e908b27ae477799371b45a0844eedb02279922a4 node-v20.18.1-darwin-arm64.tar.gz +68dbaeb8e37be25699c47788d00f2ec748ae6599e5f1696a695b1e3b4312db97 node-v20.18.1-darwin-arm64.tar.xz +c5497dd17c8875b53712edaf99052f961013cedc203964583fc0cfc0aaf93581 node-v20.18.1-darwin-x64.tar.gz +2f40325262474304101da93df9f1a0ced16fd11eabcc7aeec96c085a2e34e10f node-v20.18.1-darwin-x64.tar.xz +4a228c99ff3c1ea9c052336f3353bf157f7bf0a339da9acabba8ec4ec728d871 node-v20.18.1-headers.tar.gz +c29a72345fbbfa06a78bbfd129fcb2db097f138be17408a520420ae38406243d node-v20.18.1-headers.tar.xz +73cd297378572e0bc9dfc187c5ec8cca8d43aee6a596c10ebea1ed5f9ec682b6 node-v20.18.1-linux-arm64.tar.gz +44d1ffc5905c005ace4515ca6f8c090c4c7cfce3a9a67df0dba35c727590b8f6 node-v20.18.1-linux-arm64.tar.xz +7b7c3315818e9fe57512737c2380fada14d8717ce88945fb6f7b8baadd3cfb92 node-v20.18.1-linux-armv7l.tar.gz +b5a02f354f67d8f4bda8c563954f4805eccb4039f66a5dcdf755168aaac4e16c node-v20.18.1-linux-armv7l.tar.xz +703fc140e330002020bf54cffcfbbf8a957413b23fe6940a9f5a147e5103e960 node-v20.18.1-linux-ppc64le.tar.gz +ad01ea5905e8fb587f306d6cf44cce62a334aaa5e689f1f086ae4d03c4eaf2f2 node-v20.18.1-linux-ppc64le.tar.xz +15724744d75fa10f5856b17d5293d141175a2832b7c17bf12df669d4b22b12bc node-v20.18.1-linux-s390x.tar.gz +1df248c207d8557ec1c554242758b5852bad5e0da49108d2dd94abcb9e9eae52 node-v20.18.1-linux-s390x.tar.xz +259e5a8bf2e15ecece65bd2a47153262eda71c0b2c9700d5e703ce4951572784 node-v20.18.1-linux-x64.tar.gz +c6fa75c841cbffac851678a472f2a5bd612fff8308ef39236190e1f8dbb0e567 node-v20.18.1-linux-x64.tar.xz +ed547fa08a8855b6ce1b88e9b8a5ec839a02a7358868a00cd772878e78b560e3 node-v20.18.1.pkg +5bad8ced873eef3b32e7daee703156bce9224920ac6044f4232f5393df0628b8 node-v20.18.1.tar.gz +91df43f8ab6c3f7be81522d73313dbdd5634bbca228ef0e6d9369fe0ab8cccd0 node-v20.18.1.tar.xz +b4cc958546b0743123f958f5c607f21cd8029bde8f7f0989cc99b9b38414e81a node-v20.18.1-win-arm64.7z +7c03744df29e81c34043a956969b3afc34171d3ab85e25fc737eb1860222444f node-v20.18.1-win-arm64.zip +08ce2180a82fb5ba3cd4dc8f0744e3f5029f1745359dd3833dfcf0e10148e7d0 node-v20.18.1-win-x64.7z +56e5aacdeee7168871721b75819ccacf2367de8761b78eaceacdecd41e04ca03 node-v20.18.1-win-x64.zip +18c03af826919dc791613ee3ff7903cc78e68627e8da44ecaf55a40eb6cd994a node-v20.18.1-win-x86.7z +08987ceb478044b652ad57e15b96597e1eaf7f06502336b5a02c545f9e403ed6 node-v20.18.1-win-x86.zip +658930e6136d01bf244146a6436d8ea146cd50557c1b2a2617a60bcd9dce0da1 node-v20.18.1-x64.msi +6f89d8ee1ff0daf73ef408b9187a8f787327ddc6c393d5b8cdcbf9c6ff04b7c6 node-v20.18.1-x86.msi +9a52905b5d22b08b5b34e86b8d5358cd22c03fbd0fcb9dbb37c9bd82a8f18c17 win-arm64/node.exe +58795bcd44e8023ff443dedabf7f9af928732a51befc5324082aafe56e0f5eb0 win-arm64/node.lib +37d60e51b1fa23027187cc63bd79ef485de5777a601bbe74d5985720dc9dfabd win-arm64/node_pdb.7z +5e716c36e4a9e46406955d4cf4598cdca28c7e61c218192d8f8de7cead3bf15e win-arm64/node_pdb.zip +06c1dec1b428927d6ff01c8f5882f119ec13b61ac77483760aa7fba215c72cf5 win-x64/node.exe +a2df424ebe89449195d830e80cb2ade2e7ea5fe9b27ca9510de173b9a06733ae win-x64/node.lib +e33ea0808185d7607dc7e8c996aa9e21499801c6a6c5ecd4ab71751ee7539474 win-x64/node_pdb.7z +2250b545b0c6b4f3bd1cf2a31dfb0f5c91b90c3de41f8656df0359f74da47900 win-x64/node_pdb.zip +ff782544e1514f58bc334955d4fdc4bb4df06b277c693c74762eb9740ac2f42b win-x86/node.exe +a2ccacb78f399dceb60f5ad275c9380c5cf498202ef1cb7f7bd789d844ab0daa win-x86/node.lib +47aca7d7e52747b0bd08c8f1a3a19eda9e512ee194c801402897ee3abc86ddc1 win-x86/node_pdb.7z +64457eb79562c7e8d97544ec9b06ad92425778010bda3771001a28f5503ff55d win-x86/node_pdb.zip +-----BEGIN PGP SIGNATURE----- + +iQIzBAEBCAAdFiEEzGj1oxBv9EgyLkjtJ/XjjVsKIV8FAmc981QACgkQJ/XjjVsK +IV+LkRAAmyE2ddbS29NI9kIYI9kETn5kGPlJr7Hkk8KV1XhHpUJ8Abb3L1e8hBmv +QVtDhccNZfbrs/tE3ChKxv9BfYQB+sqWg2ckPDlIs2NnC8S6LYfrpiDATY+sLGax +F0zT3yO9SnoqbZ/FsNbvLjr7bNqrUxc1dtPfl4/dtHSeSUGeKGGbbe/mvbUW4l40 +tnZpDIxIwWXxkgnnG0Nt6nI7R5MXeCDUaf/CuG46sumvsmC1HnMH471vQVq+nSZD +a2U+R1SsTbIM6gCjWQP4laTbtQdXIy9WRQNoXl9NRkinzAQvUY3/i0RLT4IBuonH +tUe0MFsayaIn5k3IUSOT5XHfynwIxXthx6hJBGq3GDrHLo2SYpNeSmgtKVI0yXmm +UmQWjknz2M5dHVEUyG2rWdvz58Rp9ocITmX1WmTSoig+hBRDNaoe9hipA1AxJ5x4 +A/uA/uRt0eysBkKpcqt3Jj/9uORRNV9j2+uzj2D5wkrOfTeuDLm8X1oBuXEdtpU+ +IM4UoXdVlSJ6AWL1W3P//XCj8gEYsaRbgmOg1z/U6wgqtxYDCefebDNx80ImTeap +ffKiBva4OmtGydvXtzt6pqslZFdLvjSw5UYy8diHDBnn3ZV/NCsbiVYJ7eQKHkKM +/AwHFB4JQbicXS8a2lYnnsJs0LDHO7VhdXU1Fj680SbH0FwIeFU= +=No17 +-----END PGP SIGNATURE----- diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/util/Sha256SignatureVerifier.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/util/Sha256SignatureVerifier.kt index 82af951576..0c1dd5a759 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/util/Sha256SignatureVerifier.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/util/Sha256SignatureVerifier.kt @@ -3,60 +3,114 @@ package com.google.firebase.example.dataconnect.gradle.util import com.google.firebase.example.dataconnect.gradle.DataConnectGradleException import org.pgpainless.sop.SOPImpl import java.io.File +import java.io.InputStream import java.nio.charset.StandardCharsets import java.util.concurrent.locks.ReentrantLock import kotlin.concurrent.withLock -class Sha256SignatureVerifier(val logger: DataConnectGradleLogger) { +/** + * Verifies the SHA256 hash of files. + * + * The immediate use case for this class in the Data Connect Gradle plugin is to verify the + * hash of the downloaded Node.js binary distribution archive, ensuring that it is a legitimate + * binary distribution that has not been tampered. + * + * This class is thread-safe; that is, instances of this class can safely have their methods invoked + * concurrently from multiple threads. + */ +class Sha256SignatureVerifier { private val lock = ReentrantLock() private val certificates = mutableListOf() private val hashByFileName = mutableMapOf() - fun installShasumsFromFile(shasumsFile: File) { - val shasumsFileBytes = verifyShasumsFile(shasumsFile) - val shasumsFileText = String(shasumsFileBytes, StandardCharsets.UTF_8) - val shasumsFileLines = shasumsFileText.lines() + /** + * Verifies that the SHA256 hash of the bytes matches the expected hash. + * + * @param inputStream the stream of bytes whose hash to calculate and verify. + * @param fileName the name of the file whose expected hash to use; the hash for this file name + * must have been previously added by a call to [addHashForFileName]. + */ + fun verifyHash(inputStream: InputStream, fileName: String) { + TODO() + } - val regex = Regex("""\s*(.*?)\s+(.*?)\s*""") - val curHashByFileName = shasumsFileLines.mapNotNull { regex.matchEntire(it)}.associate { - val hash = it.groupValues[1] - val fileName = it.groupValues[2] - fileName to hash + /** + * Adds the expected SHA256 hash for a file with the given name. + * + * @param fileName The name of the file whose hash to add. + * @param hash The hex encoding of the SHA256 hash for a file with the given file name + * (e.g. "dc148e207d1a6456af05bf55d2c5af0185a2c79139fa64c8278ca257dc4894d5"). + */ + fun addHashForFileName(fileName: String, hash: String) { + lock.withLock { + hashByFileName.put(fileName, hash) } + } + /** + * Adds a signing certificate to this object's internal collection of certificates. + * + * The collection of certificates that this method manipulates is the one used by [verifySignature]. + * + * The given certificate must be a OpenPGP certificate, which typically comes from a file + * with the extension ".asc" or ".gpg". + * + * @param certificate the bytes of the certificate to install. + */ + fun addCertificate(certificate: ByteArray) { lock.withLock { - hashByFileName.putAll(curHashByFileName) + certificates.add(certificate) } } - private fun verifyShasumsFile(file: File): ByteArray { + /** + * Verifies the signature of the given bytes, which are expected to be an ASCII armor encoded + * file. The certificate to use to verify the signature must have been added by a previous + * invocation of [addCertificate]. + * + * @param bytes The bytes whose signature to verify, such as the contents of a text file with + * the ".asc" extension. + * @return the given bytes with the signature information stripped; that is, the actual bytes + * whose signature was verified. + * @throws Exception if anything goes wrong with the signature verification, such as malformed + * signature or the certificate for the signature has not been added. + */ + fun verifySignature(bytes: ByteArray): ByteArray { val sop = SOPImpl() val verificationResult = sop.inlineVerify().let { verifier -> - lock.withLock { certificates.toList() }.forEach { - verifier.cert(it) - } - file.inputStream().use { inputStream -> - verifier.data(inputStream).toByteArrayAndResult() + lock.withLock { + certificates.forEach { + verifier.cert(it) + } } + verifier.data(bytes).toByteArrayAndResult() } return verificationResult.bytes } - - fun installKeyFromResource(resourcePath: String) { - val certificateBytes = loadResource(resourcePath) - lock.withLock { - certificates.add(certificateBytes) - } - } } -fun Sha256SignatureVerifier.installKeysFromKeyListResource(keyListResourcePath: String) { +/** + * Shorthand for [Sha256SignatureVerifier.addCertificate], calling it with the bytes of the given + * resource, as loaded by [ClassLoader.getResourceAsStream]. + */ +fun Sha256SignatureVerifier.addCertificateFromResource(resourcePath: String) { + val certificateBytes = loadResource(resourcePath) + addCertificate(certificateBytes) +} + +/** + * Shorthand for [addCertificateFromResource], calling it with the bytes of each + * resource specified in the given "key list" resource, as loaded by + * [ClassLoader.getResourceAsStream]. + * + * @return the number of certificates that were added by this method invocation. + */ +fun Sha256SignatureVerifier.addCertificatesFromKeyListResource(keyListResourcePath: String): Int { val keyNames = loadKeyNameList(keyListResourcePath) - logger.debug { "Loaded the names of ${keyNames.size} keys from resource: $keyListResourcePath" } val lastSlashIndex = keyListResourcePath.lastIndexOf('/') val resourceKeyPathPrefix = if (lastSlashIndex <= 0) { @@ -66,8 +120,10 @@ fun Sha256SignatureVerifier.installKeysFromKeyListResource(keyListResourcePath: } keyNames.forEach { - installKeyFromResource("$resourceKeyPathPrefix$it.asc") + addCertificateFromResource("$resourceKeyPathPrefix$it.asc") } + + return keyNames.size } private fun Sha256SignatureVerifier.loadResource(path: String): ByteArray { @@ -83,8 +139,41 @@ private fun Sha256SignatureVerifier.loadResource(path: String): ByteArray { } private fun Sha256SignatureVerifier.loadKeyNameList(resourcePath: String): List { - logger.debug { "Loading resource: $resourcePath" } val resourceBytes = loadResource(resourcePath) val resourceText = String(resourceBytes, StandardCharsets.UTF_8) return resourceText.lines().map { it.trim() }.filter { it.isNotBlank() } } + +/** + * Adds SHA256 hashes and their corresponding file names loaded from the given file by calling + * [Sha256SignatureVerifier.addHashForFileName] for each hash/filename pair loaded from the given + * file. + * + * The given file must be an "ASCII armor" text file that has a valid signature. The signature + * must have been signed by one (or more) of the certificates that installed by a previous + * invocation of [Sha256SignatureVerifier.addCertificate]. + * + * An example of such a file is https://nodejs.org/dist/v20.18.1/SHASUMS256.txt.asc, which, for + * convenience, has been downloaded into the same directory as this file with the name + * `SHASUMS256.txt.asc.example`. + * + * @return the names of the files whose hashes were added. + */ +fun Sha256SignatureVerifier.addHashesFromShasumsFile(shasumsFile: File): Set { + val shasumsFileSignedBytes = shasumsFile.readBytes(byteLimit = 1_000_000) + val shasumsFileBytes = this.verifySignature(shasumsFileSignedBytes) + val shasumsFileLines = String(shasumsFileBytes, StandardCharsets.UTF_8).lines() + + val regex = Regex("""\s*(.*?)\s+(.*?)\s*""") + val hashByFileName = shasumsFileLines.mapNotNull { regex.matchEntire(it)}.associate { + val fileName = it.groupValues[2] + val hash = it.groupValues[1] + fileName to hash + } + + hashByFileName.forEach { + addHashForFileName(fileName=it.key, hash=it.value) + } + + return hashByFileName.keys +} From 032f17c1101e696744e50c3cfcadf00db933526f Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Thu, 28 Nov 2024 19:45:17 +0000 Subject: [PATCH 64/79] DownloadNodeJsBinaryDistributionArchiveTask.kt: done --- ...loadNodeJsBinaryDistributionArchiveTask.kt | 16 +++---- .../dataconnect/gradle/tasks/NodeJsPaths.kt | 2 +- .../gradle/util/DataConnectGradleLogger.kt | 6 +-- .../dataconnect/gradle/util/FileDownloader.kt | 6 +-- .../dataconnect/gradle/util/FileKtx.kt | 14 ++++-- .../gradle/util/Sha256SignatureVerifier.kt | 47 +++++++++++++++++-- 6 files changed, 67 insertions(+), 24 deletions(-) diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsBinaryDistributionArchiveTask.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsBinaryDistributionArchiveTask.kt index a0ee851910..6080e1ffd2 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsBinaryDistributionArchiveTask.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsBinaryDistributionArchiveTask.kt @@ -29,6 +29,8 @@ import com.google.firebase.example.dataconnect.gradle.util.addCertificatesFromKe import com.google.firebase.example.dataconnect.gradle.util.addHashesFromShasumsFile import com.google.firebase.example.dataconnect.gradle.util.createDirectory import com.google.firebase.example.dataconnect.gradle.util.deleteDirectory +import java.io.File +import javax.inject.Inject import kotlinx.coroutines.runBlocking import kotlinx.serialization.Serializable import org.gradle.api.file.DirectoryProperty @@ -41,8 +43,6 @@ import org.gradle.api.tasks.Input import org.gradle.api.tasks.Internal import org.gradle.api.tasks.Nested import org.gradle.api.tasks.OutputDirectory -import java.io.File -import javax.inject.Inject @CacheableTask abstract class DownloadNodeJsBinaryDistributionArchiveTask : DataConnectTaskBase(LOGGER_ID_PREFIX) { @@ -206,7 +206,7 @@ private fun NodeJsTarballDownloader.verifyNodeJsReleaseSignature(file: File) { val signatureVerifier = Sha256SignatureVerifier() logger.info { "Loading Node.js release signing certificates " + - "from resource: $KEY_LIST_RESOURCE_PATH" + "from resource: $KEY_LIST_RESOURCE_PATH" } val numCertificatesAdded = signatureVerifier.addCertificatesFromKeyListResource(KEY_LIST_RESOURCE_PATH) logger.info { "Loaded $numCertificatesAdded certificates from $KEY_LIST_RESOURCE_PATH" } @@ -215,22 +215,22 @@ private fun NodeJsTarballDownloader.verifyNodeJsReleaseSignature(file: File) { val fileNamesWithLoadedHash = signatureVerifier.addHashesFromShasumsFile(shasumsFile) logger.info { "Loaded ${fileNamesWithLoadedHash.size} hashes from ${shasumsFile.absolutePath} " + - "for file names: ${fileNamesWithLoadedHash.sorted()}" + "for file names: ${fileNamesWithLoadedHash.sorted()}" } if (!fileNamesWithLoadedHash.contains(file.name)) { throw DataConnectGradleException( "hash for file name ${file.name} " + - "not found in ${shasumsFile.absolutePath} " + - "(error code yx3g25s926)" + "not found in ${shasumsFile.absolutePath} " + + "(error code yx3g25s926)" ) } file.inputStream().use { inputStream -> - logger.info { "Verifying SHA256 hash of file: ${file.absolutePath}"} + logger.info { "Verifying SHA256 hash of file: ${file.absolutePath}" } signatureVerifier.verifyHash(inputStream, file.name) } } private const val KEY_LIST_RESOURCE_PATH = - "com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/keys.list" \ No newline at end of file + "com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/keys.list" diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/NodeJsPaths.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/NodeJsPaths.kt index 2d11a84f02..46eb71f4f2 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/NodeJsPaths.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/NodeJsPaths.kt @@ -23,7 +23,7 @@ class NodeJsPaths( val downloadUrl: String, val downloadFileName: String, val shasumsUrl: String, - val shasumsFileName: String, + val shasumsFileName: String ) { companion object } diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/util/DataConnectGradleLogger.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/util/DataConnectGradleLogger.kt index a9db22f725..65ecdff336 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/util/DataConnectGradleLogger.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/util/DataConnectGradleLogger.kt @@ -45,19 +45,19 @@ class DataConnectGradleLogger(loggerIdPrefix: String, private val logger: Logger val isWarnEnabled: Boolean get() = logger.isWarnEnabled - inline fun debug(block: () -> String): Unit { + inline fun debug(block: () -> String) { if (isDebugEnabled) { log(LogLevel.DEBUG, block()) } } - inline fun info(block: () -> String): Unit { + inline fun info(block: () -> String) { if (isInfoEnabled) { log(LogLevel.INFO, block()) } } - inline fun warn(block: () -> String): Unit { + inline fun warn(block: () -> String) { if (isWarnEnabled) { log(LogLevel.WARN, block()) } diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/util/FileDownloader.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/util/FileDownloader.kt index edc627e6af..6677494db3 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/util/FileDownloader.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/util/FileDownloader.kt @@ -37,7 +37,7 @@ class FileDownloader(private val logger: DataConnectGradleLogger) : AutoCloseabl private val httpClient: HttpClient get() = getOrCreateHttpClient() suspend fun download(url: String, destFile: File, maxNumDownloadBytes: Long) { - logger.info {"Downloading $url to ${destFile.absolutePath}"} + logger.info { "Downloading $url to ${destFile.absolutePath}" } val actualNumBytesDownloaded = httpClient.prepareGet(url).execute { httpResponse -> val downloadChannel: ByteReadChannel = httpResponse.body() @@ -60,7 +60,7 @@ class FileDownloader(private val logger: DataConnectGradleLogger) : AutoCloseabl logger.info { "Successfully downloaded $actualNumBytesDownloadedStr bytes from $url " + - "to ${destFile.absolutePath}" + "to ${destFile.absolutePath}" } } @@ -120,7 +120,7 @@ private fun HttpClientConfig<*>.installDataConnectLogger(dataConnectGradleLogger logger = object : Logger { override fun log(message: String) { message.lines().forEach { line -> - dataConnectGradleLogger.info{"ktor: ${line.trimEnd()}"} + dataConnectGradleLogger.info { "ktor: ${line.trimEnd()}" } } } } diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/util/FileKtx.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/util/FileKtx.kt index 110c6b56cc..3c4a452219 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/util/FileKtx.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/util/FileKtx.kt @@ -21,8 +21,10 @@ import java.io.File import java.io.IOException fun File.readBytes(byteLimit: Int): ByteArray = inputStream().use { inputStream -> - require(byteLimit >= 0) { "invalid byte limit: $byteLimit " + - "(must be greater than or equal to zero) (error code vrr6daevyw)" } + require(byteLimit >= 0) { + "invalid byte limit: $byteLimit " + + "(must be greater than or equal to zero) (error code vrr6daevyw)" + } val buffer = ByteArray(8192) val byteArrayOutputStream = ByteArrayOutputStream() @@ -37,8 +39,10 @@ fun File.readBytes(byteLimit: Int): ByteArray = inputStream().use { inputStream if (totalByteReadCount > byteLimit) { val byteLimitStr = String.format("%,d", byteLimit) val totalByteReadCountStr = String.format("%,d", totalByteReadCount) - throw TooManyBytesReadException("too many bytes read: $byteLimitStr " + - "(expected at most $totalByteReadCountStr) (error code fnaxenqmxs)") + throw TooManyBytesReadException( + "too many bytes read: $totalByteReadCountStr " + + "(expected at most $byteLimitStr) (error code fnaxenqmxs)" + ) } byteArrayOutputStream.write(buffer, 0, numBytesJustRead) @@ -47,4 +51,4 @@ fun File.readBytes(byteLimit: Int): ByteArray = inputStream().use { inputStream return byteArrayOutputStream.toByteArray() } -class TooManyBytesReadException(message: String) : IOException(message) \ No newline at end of file +class TooManyBytesReadException(message: String) : IOException(message) diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/util/Sha256SignatureVerifier.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/util/Sha256SignatureVerifier.kt index 0c1dd5a759..572b5d720d 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/util/Sha256SignatureVerifier.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/util/Sha256SignatureVerifier.kt @@ -1,12 +1,14 @@ package com.google.firebase.example.dataconnect.gradle.util import com.google.firebase.example.dataconnect.gradle.DataConnectGradleException -import org.pgpainless.sop.SOPImpl import java.io.File import java.io.InputStream import java.nio.charset.StandardCharsets +import java.security.MessageDigest import java.util.concurrent.locks.ReentrantLock import kotlin.concurrent.withLock +import org.bouncycastle.util.encoders.Hex +import org.pgpainless.sop.SOPImpl /** * Verifies the SHA256 hash of files. @@ -30,9 +32,44 @@ class Sha256SignatureVerifier { * @param inputStream the stream of bytes whose hash to calculate and verify. * @param fileName the name of the file whose expected hash to use; the hash for this file name * must have been previously added by a call to [addHashForFileName]. + * @throws UnknownHashException if the hash for the given file name was _not_ added by a + * previous invocation of [addHashForFileName]. + * @throws IncorrectHashException if the hash of the bytes of the given input stream does _not_ + * match the expected hash of the given file name, as added by a previous invocation of + * [addHashForFileName]. */ fun verifyHash(inputStream: InputStream, fileName: String) { - TODO() + val expectedSha256Hash = lock.withLock { + hashByFileName[fileName] + } + if (expectedSha256Hash === null) { + throw UnknownHashException( + "expected SHA256 hash for file name not known: $fileName " + + "(did you call addHashForFileName() with the given file name?) " + + "(error code ws53hqrmd6)" + ) + } + + val actualSha256Hash = run { + val messageDigest = MessageDigest.getInstance("SHA-256") + val buffer = ByteArray(8192) + while (true) { + val readCount = inputStream.read(buffer) + if (readCount < 0) { + break + } + messageDigest.update(buffer, 0, readCount) + } + val digest = messageDigest.digest() + Hex.toHexString(digest) + } + + if (expectedSha256Hash != actualSha256Hash) { + throw IncorrectHashException( + "SHA256 hash of $fileName did not match the expected hash: " + + "$actualSha256Hash (expected $expectedSha256Hash)" + ) + } } /** @@ -91,6 +128,8 @@ class Sha256SignatureVerifier { return verificationResult.bytes } + class UnknownHashException(message: String) : Exception(message) + class IncorrectHashException(message: String) : Exception(message) } /** @@ -165,14 +204,14 @@ fun Sha256SignatureVerifier.addHashesFromShasumsFile(shasumsFile: File): Set Date: Thu, 28 Nov 2024 19:53:29 +0000 Subject: [PATCH 65/79] tweaks --- .../gradle/DataConnectGradlePlugin.kt | 16 +++++++++++++--- ...ownloadNodeJsBinaryDistributionArchiveTask.kt | 4 ++++ 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectGradlePlugin.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectGradlePlugin.kt index b2f8d13e5b..a48e1ecc8b 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectGradlePlugin.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectGradlePlugin.kt @@ -42,11 +42,17 @@ abstract class DataConnectGradlePlugin : Plugin { val providers = MyProjectProviders(project, downloadNodeJsTask) project.tasks.register("downloadNodeJsBinaryDistributionArchive") { + group = TASK_GROUP + configureFrom(providers) + } + downloadNodeJsTask.configure { + group = TASK_GROUP + configureFrom(providers) + } + setupFirebaseToolsTask.configure { + group = TASK_GROUP configureFrom(providers) } - - downloadNodeJsTask.configure { configureFrom(providers) } - setupFirebaseToolsTask.configure { configureFrom(providers) } val androidComponents = project.extensions.getByType() androidComponents.onVariants { variant -> @@ -82,4 +88,8 @@ abstract class DataConnectGradlePlugin : Plugin { GenerateDataConnectSourcesTask::outputDirectory ) } + + companion object { + private const val TASK_GROUP = "Firebase Data Connect" + } } diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsBinaryDistributionArchiveTask.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsBinaryDistributionArchiveTask.kt index 6080e1ffd2..0ab9a1be3b 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsBinaryDistributionArchiveTask.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsBinaryDistributionArchiveTask.kt @@ -90,6 +90,10 @@ abstract class DownloadNodeJsBinaryDistributionArchiveTask : DataConnectTaskBase @get:Inject abstract val fileSystemOperations: FileSystemOperations + init { + description = "Download the Node.js binary distribution archive" + } + override fun doRun() { val (operatingSystem: OperatingSystem, nodeJsVersion: String) = inputData.get() From 14aae3f6b687674927c88f64438e619071e14ba2 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Thu, 28 Nov 2024 20:02:08 +0000 Subject: [PATCH 66/79] remove manual caching --- dataconnect/app/build.gradle.kts | 6 - .../gradle/DataConnectExtension.kt | 12 -- .../dataconnect/gradle/cache/CacheManager.kt | 168 ------------------ .../dataconnect/gradle/providers/providers.kt | 19 -- ...loadNodeJsBinaryDistributionArchiveTask.kt | 8 +- .../gradle/tasks/DownloadNodeJsTask.kt | 34 +--- .../dataconnect/gradle/tasks/NodeJsPaths.kt | 40 ++--- .../gradle/tasks/SetupFirebaseToolsTask.kt | 33 +--- 8 files changed, 25 insertions(+), 295 deletions(-) delete mode 100644 dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/cache/CacheManager.kt diff --git a/dataconnect/app/build.gradle.kts b/dataconnect/app/build.gradle.kts index d7079bce5e..8d442ede2d 100644 --- a/dataconnect/app/build.gradle.kts +++ b/dataconnect/app/build.gradle.kts @@ -98,10 +98,4 @@ dataconnect { // The directory that contains dataconnect.yaml that specifies the Data // Connect schema and connectors whose code to generate. dataConnectConfigDir = file("../dataconnect") - - // The directory where the Data Connect gradle plugin will cache its data. - // This can be set with the "DataConnectGradleCacheDir" project property, - // such as by specifying -PDataConnectGradleCacheDir=myCacheDir on the - // Gradle command line. - cacheDir = findProperty("dataconnect.gradleCacheDir")?.let { file(it) } } diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectExtension.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectExtension.kt index 595ef4d8ba..113ae392d7 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectExtension.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectExtension.kt @@ -43,16 +43,4 @@ interface DataConnectExtension { * as part of the build. */ var dataConnectConfigDir: File? - - /** - * The directory into which cached data for the Data Connect plugin will be - * stored, and from which cached data will be read. This property may be - * `null` (the default) to _not_ perform any explicit caching. If it is not - * `null` then the directory will be created on-demand when it is needed. - * The contents of this directory are never deleted; therefore, it is - * prudent to periodically delete the contents of this directory. If the - * file is a relative directory (as opposed to an absolute directory) then - * it will be calculated relative to the evaluating project's directory. - */ - var cacheDir: File? } diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/cache/CacheManager.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/cache/CacheManager.kt deleted file mode 100644 index 63fbb573c6..0000000000 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/cache/CacheManager.kt +++ /dev/null @@ -1,168 +0,0 @@ -/* - * 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.example.dataconnect.gradle.cache - -import java.io.ByteArrayOutputStream -import java.io.File -import java.nio.ByteBuffer -import java.nio.channels.FileChannel -import java.nio.channels.ReadableByteChannel -import java.nio.charset.StandardCharsets -import java.nio.file.StandardOpenOption -import java.util.UUID -import kotlinx.serialization.Serializable -import kotlinx.serialization.encodeToString -import kotlinx.serialization.json.Json -import org.gradle.api.logging.Logger - -class CacheManager(private val rootDirectory: File) { - - private val entriesFile: File = File(rootDirectory, ENTRIES_FILENAME) - - private val allocatedDirectories = mutableListOf() - - fun isCommitted(dir: File, logger: Logger): Boolean { - if (dir.parentFile != rootDirectory) { - return false - } - val directoryName = dir.name - val cacheEntry: CacheEntry? = loadCacheEntries(logger).firstOrNull { it.directory == directoryName } - return cacheEntry !== null - } - - fun getOrAllocateDir(domain: String, key: String, logger: Logger): File { - val committedDir = findDir(domain = domain, key = key, logger) - return committedDir ?: allocateDir(domain = domain, key = key) - } - - private fun findDir(domain: String, key: String, logger: Logger): File? = - loadCacheEntries(logger) - .filter { it.domain == domain && it.key == key } - .map { File(rootDirectory, it.directory) } - .singleOrNull() - - private fun allocateDir(domain: String, key: String): File { - val directory = File(rootDirectory, UUID.randomUUID().toString()).absoluteFile.normalize() - val allocatedDirectory = AllocatedDirectory(domain = domain, key = key, directory = directory) - synchronized(allocatedDirectories) { - allocatedDirectories.add(allocatedDirectory) - } - return directory - } - - fun commitDir(dir: File, logger: Logger) { - val allocatedDirectory: AllocatedDirectory = synchronized(allocatedDirectories) { - val normalizedDir = dir.absoluteFile.normalize() - val index = allocatedDirectories.indexOfFirst { it.directory == normalizedDir } - require(index >= 0) { - val allocatedDirectoriesStr = synchronized(allocatedDirectories) { - allocatedDirectories.map { it.directory.path }.sorted().joinToString(", ") - } - "The given directory, $dir, has not been allocated or was already committed; " + - "there are currently ${allocatedDirectories.size} allocated directories: " + - "$allocatedDirectoriesStr (error code k2gr9g36wh)" - } - allocatedDirectories.removeAt(index) - } - - val newCacheEntry = CacheEntry( - domain = allocatedDirectory.domain, - key = allocatedDirectory.key, - directory = dir.name - ) - - withEntriesFile(logger) { entriesFile, channel -> - logger.info("Inserting or updating cache entry {} in file: {}", newCacheEntry, entriesFile.absolutePath) - val cacheEntries = loadCacheEntries(channel).filterNot { - it.domain == newCacheEntry.domain && it.key == newCacheEntry.key - } - - val json = Json { prettyPrint = true } - val newText = json.encodeToString(cacheEntries + listOf(newCacheEntry)) - - channel.truncate(0) - channel.position(0) - channel.write(ByteBuffer.wrap(newText.toByteArray(StandardCharsets.UTF_8))) - } - } - - private fun withEntriesFile(logger: Logger, block: (File, FileChannel) -> T): T { - logger.debug("Opening Data Connect cache metadata file: {}", entriesFile.absolutePath) - entriesFile.parentFile.mkdirs() - return FileChannel.open( - entriesFile.toPath(), - StandardOpenOption.READ, - StandardOpenOption.WRITE, - StandardOpenOption.CREATE - ).use { channel -> - channel.lock().use { - block(entriesFile, channel) - } - } - } - - private fun loadCacheEntries(logger: Logger): List = - withEntriesFile(logger) { _, channel -> - loadCacheEntries(channel) - } - - private fun loadCacheEntries(channel: FileChannel): List { - val fileBytes = channel.readAllBytes() - val fileText = String(fileBytes, StandardCharsets.UTF_8) - if (fileText.isBlank()) { - return emptyList() - } - return Json.decodeFromString>(fileText) - } - - override fun toString() = "CacheManager(${rootDirectory.absolutePath})" - - @Serializable - private data class CacheEntry( - val domain: String, - val key: String, - val directory: String - ) - - private data class AllocatedDirectory( - val domain: String, - val key: String, - val directory: File - ) - - companion object { - - private const val ENTRIES_FILENAME = "entries.json" - } -} - -private fun ReadableByteChannel.readAllBytes(): ByteArray { - val buffer = ByteArray(8192) - val byteBuffer = ByteBuffer.wrap(buffer) - val byteArrayOutputStream = ByteArrayOutputStream() - - while (true) { - val numBytesRead = read(byteBuffer) - if (numBytesRead < 0) { - break - } - byteArrayOutputStream.write(buffer, 0, numBytesRead) - byteBuffer.flip() - } - - return byteArrayOutputStream.toByteArray() -} diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/providers/providers.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/providers/providers.kt index c052f496e9..8e1e64ddaa 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/providers/providers.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/providers/providers.kt @@ -18,7 +18,6 @@ package com.google.firebase.example.dataconnect.gradle.providers import com.android.build.api.variant.ApplicationVariant import com.google.firebase.example.dataconnect.gradle.DataConnectExtension -import com.google.firebase.example.dataconnect.gradle.cache.CacheManager import com.google.firebase.example.dataconnect.gradle.tasks.DownloadNodeJsTask import com.google.firebase.example.dataconnect.gradle.tasks.SetupFirebaseToolsTask import org.gradle.api.GradleException @@ -98,24 +97,6 @@ internal open class MyProjectProviders( } providerFactory.provider { lazyDataConnectConfigDir.value } } - - private val dataConnectCacheDir: Provider = run { - val lazyDataConnectCacheDir: Lazy = lazy { - ext.cacheDir?.let { cacheDir -> - projectLayout.projectDirectory.dir(cacheDir.path).dir("v1") - } - } - providerFactory.provider { lazyDataConnectCacheDir.value } - } - - val cacheManager: Provider = run { - val lazyCacheManager: Lazy = lazy { - dataConnectCacheDir.orNull?.let { - CacheManager(it.asFile) - } - } - providerFactory.provider { lazyCacheManager.value } - } } internal open class MyVariantProviders( diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsBinaryDistributionArchiveTask.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsBinaryDistributionArchiveTask.kt index 0ab9a1be3b..910bf80815 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsBinaryDistributionArchiveTask.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsBinaryDistributionArchiveTask.kt @@ -17,7 +17,6 @@ package com.google.firebase.example.dataconnect.gradle.tasks import com.google.firebase.example.dataconnect.gradle.DataConnectGradleException -import com.google.firebase.example.dataconnect.gradle.cache.CacheManager import com.google.firebase.example.dataconnect.gradle.providers.MyProjectProviders import com.google.firebase.example.dataconnect.gradle.providers.OperatingSystem import com.google.firebase.example.dataconnect.gradle.tasks.DownloadNodeJsBinaryDistributionArchiveTask.Inputs @@ -59,11 +58,10 @@ abstract class DownloadNodeJsBinaryDistributionArchiveTask : DataConnectTaskBase /** * The directory into which to place the downloaded artifact(s). * - * This property is _required_, meaning that it must be set; that is, - * [Property.isPresent] must return `true`. + * This property is _required_, meaning that it must be set; that is, [Property.isPresent] must + * return `true`. * - * This directory will be deleted and re-created by the task unless it is a "committed" cache - * directory, as determined by [CacheManager.isCommitted]. + * This directory will be deleted and re-created when this task is executed. */ @get:OutputDirectory abstract val outputDirectory: DirectoryProperty diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsTask.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsTask.kt index 62f45e4ad2..e0d66fb091 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsTask.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsTask.kt @@ -16,7 +16,6 @@ package com.google.firebase.example.dataconnect.gradle.tasks -import com.google.firebase.example.dataconnect.gradle.cache.CacheManager import com.google.firebase.example.dataconnect.gradle.providers.MyProjectProviders import com.google.firebase.example.dataconnect.gradle.providers.OperatingSystem import com.google.firebase.example.dataconnect.gradle.tasks.DownloadNodeJsTask.DownloadOfficialVersion @@ -76,9 +75,6 @@ abstract class DownloadNodeJsTask : DefaultTask() { @get:OutputDirectory abstract val outputDirectory: DirectoryProperty - @get:Internal - abstract val cacheManager: Property - @get:Internal val nodeExecutable: RegularFile get() { val source = source.get() @@ -98,25 +94,15 @@ abstract class DownloadNodeJsTask : DefaultTask() { val source: Source = source.get() val outputDirectoryRegularFile: Directory = outputDirectory.get() val outputDirectory: File = outputDirectoryRegularFile.asFile - val cacheManager: CacheManager? = cacheManager.orNull logger.info("source: {}", Source.describe(source)) logger.info("outputDirectory: {}", outputDirectory.absolutePath) - logger.info("cacheManager: {}", cacheManager) - - if (cacheManager !== null && cacheManager.isCommitted(outputDirectory, logger)) { - logger.info("Using cached data from directory: {}", outputDirectory.absolutePath) - didWork = false - return - } project.delete(outputDirectory) when (source) { is DownloadOfficialVersion -> downloadOfficialVersion(source, outputDirectory) } - - cacheManager?.commitDir(outputDirectory, logger) } sealed interface Source : java.io.Serializable { @@ -191,26 +177,8 @@ internal fun DownloadNodeJsTask.configureFrom(providers: MyProjectProviders) { finalizeValueOnRead() } - cacheManager.run { - set(providers.cacheManager) - finalizeValueOnRead() - } - outputDirectory.run { - set( - providers.providerFactory.provider { - val cacheManager = cacheManager.orNull - val directoryProvider: Provider = if (cacheManager === null) { - providers.buildDirectory.map { it.dir("node") } - } else { - val cacheDomain = "DownloadNodeJsTask" - val cacheKey = source.get().cacheKey - val cacheDir = cacheManager.getOrAllocateDir(domain = cacheDomain, key = cacheKey, logger) - providers.objectFactory.directoryProperty().also { it.set(cacheDir) } - } - directoryProvider.get() - } - ) + set(providers.buildDirectory.map { it.dir("node") }) finalizeValueOnRead() } } diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/NodeJsPaths.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/NodeJsPaths.kt index 46eb71f4f2..87c77bd60e 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/NodeJsPaths.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/NodeJsPaths.kt @@ -54,14 +54,14 @@ private fun NodeJsPathsCalculator.downloadUrlPrefix(): String = "https://nodejs. * The URL to download the Node.js binary distribution. * * Here are some examples: - * * https://nodejs.org/dist/v20.9.0/node-v20.9.0-darwin-arm64.tar.gz - * * https://nodejs.org/dist/v20.9.0/node-v20.9.0-darwin-x64.tar.gz - * * https://nodejs.org/dist/v20.9.0/node-v20.9.0-linux-arm64.tar.gz - * * https://nodejs.org/dist/v20.9.0/node-v20.9.0-linux-armv7l.tar.gz - * * https://nodejs.org/dist/v20.9.0/node-v20.9.0-linux-x64.tar.gz - * * https://nodejs.org/dist/v20.9.0/node-v20.9.0-win-arm64.zip - * * https://nodejs.org/dist/v20.9.0/node-v20.9.0-win-x64.zip - * * https://nodejs.org/dist/v20.9.0/node-v20.9.0-win-x86.zip + * * https://nodejs.org/dist/v20.9.0/node-v20.9.0-darwin-arm64.tar.xz + * * https://nodejs.org/dist/v20.9.0/node-v20.9.0-darwin-x64.tar.xz + * * https://nodejs.org/dist/v20.9.0/node-v20.9.0-linux-arm64.tar.xz + * * https://nodejs.org/dist/v20.9.0/node-v20.9.0-linux-armv7l.tar.xz + * * https://nodejs.org/dist/v20.9.0/node-v20.9.0-linux-x64.tar.xz + * * https://nodejs.org/dist/v20.9.0/node-v20.9.0-win-arm64.7z + * * https://nodejs.org/dist/v20.9.0/node-v20.9.0-win-x64.7z + * * https://nodejs.org/dist/v20.9.0/node-v20.9.0-win-x86.7z */ private fun NodeJsPathsCalculator.downloadUrl(): String { val downloadUrlPrefix = downloadUrlPrefix() @@ -82,20 +82,20 @@ private fun NodeJsPathsCalculator.shasumsDownloadUrl(): String { * The file name of the download for the Node.js binary distribution. * * Here are some examples: - * * node-v20.9.0-darwin-arm64.tar.gz - * * node-v20.9.0-darwin-x64.tar.gz - * * node-v20.9.0-linux-arm64.tar.gz - * * node-v20.9.0-linux-armv7l.tar.gz - * * node-v20.9.0-linux-x64.tar.gz - * * node-v20.9.0-win-arm64.zip - * * node-v20.9.0-win-x64.zip - * * node-v20.9.0-win-x86.zip + * * node-v20.9.0-darwin-arm64.tar.xz + * * node-v20.9.0-darwin-x64.tar.xz + * * node-v20.9.0-linux-arm64.tar.xz + * * node-v20.9.0-linux-armv7l.tar.xz + * * node-v20.9.0-linux-x64.tar.xz + * * node-v20.9.0-win-arm64.7z + * * node-v20.9.0-win-x64.7z + * * node-v20.9.0-win-x86.7z */ private fun NodeJsPathsCalculator.downloadFileName(): String { val fileExtension: String = when (operatingSystemType) { - OperatingSystem.Type.Windows -> "zip" - OperatingSystem.Type.MacOS -> "tar.gz" - OperatingSystem.Type.Linux -> "tar.gz" + OperatingSystem.Type.Windows -> "7z" + OperatingSystem.Type.MacOS -> "tar.xz" + OperatingSystem.Type.Linux -> "tar.xz" else -> throw DataConnectGradleException( "unable to determine Node.js download file extension " + "for operating system type: $operatingSystemType " + @@ -109,7 +109,7 @@ private fun NodeJsPathsCalculator.downloadFileName(): String { /** * The base file name of the download for the Node.js binary distribution; - * that is, the file name without the ".zip" or ".tar.gz" extension. + * that is, the file name without the ".7z" or ".tar.xz" extension. * * Here are some examples: * * node-v20.9.0-darwin-arm64 diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/SetupFirebaseToolsTask.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/SetupFirebaseToolsTask.kt index 645b254d89..d971564ec1 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/SetupFirebaseToolsTask.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/SetupFirebaseToolsTask.kt @@ -16,13 +16,11 @@ package com.google.firebase.example.dataconnect.gradle.tasks -import com.google.firebase.example.dataconnect.gradle.cache.CacheManager import com.google.firebase.example.dataconnect.gradle.providers.MyProjectProviders import com.google.firebase.example.dataconnect.gradle.util.runCommand import java.io.File import javax.inject.Inject import org.gradle.api.DefaultTask -import org.gradle.api.file.Directory import org.gradle.api.file.DirectoryProperty import org.gradle.api.file.RegularFile import org.gradle.api.file.RegularFileProperty @@ -50,9 +48,6 @@ abstract class SetupFirebaseToolsTask : DefaultTask() { @get:Internal abstract val nodeExecutable: RegularFileProperty - @get:Internal - abstract val cacheManager: Property - @get:Internal val firebaseExecutable: RegularFile get() = outputDirectory.get().file("node_modules/.bin/firebase") @@ -67,19 +62,11 @@ abstract class SetupFirebaseToolsTask : DefaultTask() { val npmExecutable: File = npmExecutable.get().asFile val nodeExecutable: File = nodeExecutable.get().asFile val outputDirectory: File = outputDirectory.get().asFile - val cacheManager: CacheManager? = cacheManager.orNull logger.info("firebaseCliVersion: {}", firebaseCliVersion) logger.info("npmExecutable: {}", npmExecutable.absolutePath) logger.info("nodeExecutable: {}", nodeExecutable.absolutePath) logger.info("outputDirectory: {}", outputDirectory.absolutePath) - logger.info("cacheManager: {}", cacheManager) - - if (cacheManager !== null && cacheManager.isCommitted(outputDirectory, logger)) { - logger.info("Using cached data from directory: {}", outputDirectory.absolutePath) - didWork = false - return - } project.delete(outputDirectory) project.mkdir(outputDirectory) @@ -96,8 +83,6 @@ abstract class SetupFirebaseToolsTask : DefaultTask() { commandLine(npmExecutable.absolutePath, "install", "firebase-tools@$firebaseCliVersion") workingDir(outputDirectory) } - - cacheManager?.commitDir(outputDirectory, logger) } } @@ -105,21 +90,5 @@ internal fun SetupFirebaseToolsTask.configureFrom(providers: MyProjectProviders) firebaseCliVersion.set(providers.firebaseCliVersion) npmExecutable.set(providers.npmExecutable) nodeExecutable.set(providers.nodeExecutable) - cacheManager.set(providers.cacheManager) - cacheManager.set(providers.cacheManager) - - outputDirectory.set( - providers.providerFactory.provider { - val cacheManager = cacheManager.orNull - val directoryProvider: Provider = if (cacheManager === null) { - providers.buildDirectory.map { it.dir("firebase-tools") } - } else { - val cacheDomain = "SetupFirebaseToolsTask" - val cacheKey = "firebaseCliVersion=${firebaseCliVersion.get()}" - val cacheDir = cacheManager.getOrAllocateDir(domain = cacheDomain, key = cacheKey, logger) - providers.objectFactory.directoryProperty().also { it.set(cacheDir) } - } - directoryProvider.get() - } - ) + outputDirectory.set(providers.buildDirectory.map { it.dir("firebase-tools") }) } From 2f7ff97bb227a95a35eb8d7541e8479f867f9f31 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Thu, 28 Nov 2024 20:14:01 +0000 Subject: [PATCH 67/79] enable kotlin explicit api mode --- dataconnect/buildSrc/build.gradle.kts | 6 ++++ .../gradle/DataConnectExtension.kt | 8 ++--- .../gradle/DataConnectGradleException.kt | 2 +- .../gradle/DataConnectGradlePlugin.kt | 4 +-- .../gradle/providers/OperatingSystem.kt | 21 +++++++------ .../gradle/tasks/DataConnectTaskBase.kt | 7 +++-- ...loadNodeJsBinaryDistributionArchiveTask.kt | 18 +++++------ .../gradle/tasks/DownloadNodeJsTask.kt | 30 +++++++++---------- .../tasks/GenerateDataConnectSourcesTask.kt | 20 ++++++------- .../dataconnect/gradle/tasks/NodeJsPaths.kt | 14 ++++----- .../gradle/tasks/SetupFirebaseToolsTask.kt | 16 +++++----- .../gradle/util/DataConnectGradleLogger.kt | 18 +++++------ .../util/DataConnectGradleLoggerProvider.kt | 6 ++-- .../dataconnect/gradle/util/FileDownloader.kt | 2 +- .../dataconnect/gradle/util/FileKtx.kt | 4 +-- .../dataconnect/gradle/util/RandomKtx.kt | 2 +- .../gradle/util/Sha256SignatureVerifier.kt | 8 ++--- 17 files changed, 97 insertions(+), 89 deletions(-) diff --git a/dataconnect/buildSrc/build.gradle.kts b/dataconnect/buildSrc/build.gradle.kts index 6533d339cd..0d51a1b9a3 100644 --- a/dataconnect/buildSrc/build.gradle.kts +++ b/dataconnect/buildSrc/build.gradle.kts @@ -1,3 +1,5 @@ +import org.jetbrains.kotlin.gradle.dsl.ExplicitApiMode + /* * Copyright 2024 Google LLC * @@ -22,6 +24,10 @@ plugins { java { toolchain { languageVersion.set(JavaLanguageVersion.of(17)) } } +kotlin { + explicitApi = ExplicitApiMode.Strict +} + dependencies { implementation(libs.android.gradlePlugin.api) implementation(libs.snakeyaml) diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectExtension.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectExtension.kt index 113ae392d7..791a245948 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectExtension.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectExtension.kt @@ -18,20 +18,20 @@ package com.google.firebase.example.dataconnect.gradle import java.io.File -interface DataConnectExtension { +public interface DataConnectExtension { /** * The version of Node.js (https://nodejs.org) to use to install and run the * Firebase CLI. This version of Node.js will be downloaded and extracted * for exclusive use of the Data Connect Gradle plugin. */ - var nodeVersion: String? + public var nodeVersion: String? /** * The version of the Firebase CLI (https://www.npmjs.com/package/firebase-tools) * to use to perform the Data Connect Kotlin code generation. */ - var firebaseCliVersion: String? + public var firebaseCliVersion: String? /** * The directory that contains dataconnect.yaml that specifies the Data @@ -42,5 +42,5 @@ interface DataConnectExtension { * If this value is null then no Data Connect code generation will occur * as part of the build. */ - var dataConnectConfigDir: File? + public var dataConnectConfigDir: File? } diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectGradleException.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectGradleException.kt index 1c092ee52a..badf9c3760 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectGradleException.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectGradleException.kt @@ -18,4 +18,4 @@ package com.google.firebase.example.dataconnect.gradle import org.gradle.api.GradleException -class DataConnectGradleException(message: String, cause: Throwable? = null) : GradleException(message, cause) +public class DataConnectGradleException(message: String, cause: Throwable? = null) : GradleException(message, cause) diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectGradlePlugin.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectGradlePlugin.kt index a48e1ecc8b..ef03195689 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectGradlePlugin.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectGradlePlugin.kt @@ -32,7 +32,7 @@ import org.gradle.kotlin.dsl.getByType import org.gradle.kotlin.dsl.register @Suppress("unused") -abstract class DataConnectGradlePlugin : Plugin { +public abstract class DataConnectGradlePlugin : Plugin { override fun apply(project: Project) { val downloadNodeJsTask = project.tasks.register("downloadNodeJs") @@ -89,7 +89,7 @@ abstract class DataConnectGradlePlugin : Plugin { ) } - companion object { + private companion object { private const val TASK_GROUP = "Firebase Data Connect" } } diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/providers/OperatingSystem.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/providers/OperatingSystem.kt index 23f33d16d8..24c3b364e1 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/providers/OperatingSystem.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/providers/OperatingSystem.kt @@ -27,11 +27,11 @@ import org.gradle.api.tasks.Internal import org.gradle.kotlin.dsl.property @Serializable -data class OperatingSystem( +public data class OperatingSystem( @get:Input val type: Type, @get:Input val architecture: Architecture ) { - constructor( + public constructor( type: Type, arch: Architecture, description: String @@ -50,29 +50,29 @@ data class OperatingSystem( "type=$type, architecture=$architecture, description=$description" + ")" - enum class Type { + public enum class Type { Windows, Linux, MacOS, FreeBSD, Solaris; - companion object + public companion object } - enum class Architecture { + public enum class Architecture { Arm64, ArmV7, X86, X86_64; - companion object + public companion object } - companion object + public companion object } -fun OperatingSystem.Companion.provider( +internal fun OperatingSystem.Companion.provider( objectFactory: ObjectFactory, providerFactory: ProviderFactory, logger: Logger @@ -113,7 +113,7 @@ fun OperatingSystem.Companion.provider( * @param osName the name of the operating system whose value to return; this value should be one that was * returned from [System.getProperty] called with `"os.name"`. */ -fun OperatingSystem.Type.Companion.forName(osName: String): OperatingSystem.Type? = forLowerCaseName(osName.lowercase()) +public fun OperatingSystem.Type.Companion.forName(osName: String): OperatingSystem.Type? = forLowerCaseName(osName.lowercase()) // NOTE: This logic was adapted from // https://github.com/gradle/gradle/blob/99d83f56d6/platforms/core-runtime/base-services/src/main/java/org/gradle/internal/os/OperatingSystem.java#L63-L79 @@ -138,8 +138,7 @@ private fun OperatingSystem.Type.Companion.forLowerCaseName(osName: String): Ope * @param osArch the name of the operating system whose value to return; this value should be one that was * returned from [System.getProperty] called with `"os.name"`. */ -fun OperatingSystem.Architecture.Companion.forName(osArch: String): OperatingSystem.Architecture? = - forLowerCaseName(osArch.lowercase()) +public fun OperatingSystem.Architecture.Companion.forName(osArch: String): OperatingSystem.Architecture? = forLowerCaseName(osArch.lowercase()) // NOTE: This logic was adapted from // https://github.com/gradle/gradle/blob/e745e6d369/platforms/native/platform-native/src/main/java/org/gradle/nativeplatform/platform/internal/Architectures.java#L26-L42 diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DataConnectTaskBase.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DataConnectTaskBase.kt index ff1f73c430..4e42da27d0 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DataConnectTaskBase.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DataConnectTaskBase.kt @@ -22,7 +22,7 @@ import org.gradle.api.DefaultTask import org.gradle.api.tasks.Internal import org.gradle.api.tasks.TaskAction -abstract class DataConnectTaskBase(loggerIdPrefix: String) : DefaultTask() { +public abstract class DataConnectTaskBase(loggerIdPrefix: String) : DefaultTask() { @get:Internal protected val dataConnectLogger: DataConnectGradleLogger = DataConnectGradleLogger( @@ -38,7 +38,7 @@ abstract class DataConnectTaskBase(loggerIdPrefix: String) : DefaultTask() { } @TaskAction - fun run() { + public fun run() { dataConnectLogger.info { "Task $path starting execution" } runCatching { doRun() }.fold( onSuccess = { @@ -51,5 +51,8 @@ abstract class DataConnectTaskBase(loggerIdPrefix: String) : DefaultTask() { ) } + /** + * Called by [run] to actually do the work of this task. + */ protected abstract fun doRun() } diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsBinaryDistributionArchiveTask.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsBinaryDistributionArchiveTask.kt index 910bf80815..6ae4ba0bff 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsBinaryDistributionArchiveTask.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsBinaryDistributionArchiveTask.kt @@ -44,7 +44,7 @@ import org.gradle.api.tasks.Nested import org.gradle.api.tasks.OutputDirectory @CacheableTask -abstract class DownloadNodeJsBinaryDistributionArchiveTask : DataConnectTaskBase(LOGGER_ID_PREFIX) { +public abstract class DownloadNodeJsBinaryDistributionArchiveTask : DataConnectTaskBase(LOGGER_ID_PREFIX) { /** * The inputs required to execute this task. @@ -53,7 +53,7 @@ abstract class DownloadNodeJsBinaryDistributionArchiveTask : DataConnectTaskBase * [Property.isPresent] must return `true`. */ @get:Nested - abstract val inputData: Property + public abstract val inputData: Property /** * The directory into which to place the downloaded artifact(s). @@ -64,7 +64,7 @@ abstract class DownloadNodeJsBinaryDistributionArchiveTask : DataConnectTaskBase * This directory will be deleted and re-created when this task is executed. */ @get:OutputDirectory - abstract val outputDirectory: DirectoryProperty + public abstract val outputDirectory: DirectoryProperty /** * The path of the downloaded Node.js binary distribution archive. @@ -74,7 +74,7 @@ abstract class DownloadNodeJsBinaryDistributionArchiveTask : DataConnectTaskBase * This property must not be accessed until after the task executes. */ @get:Internal - val downloadedFile: RegularFile + public val downloadedFile: RegularFile get() { val inputs = inputData.get() val outputDirectory = outputDirectory.get() @@ -83,10 +83,10 @@ abstract class DownloadNodeJsBinaryDistributionArchiveTask : DataConnectTaskBase } @get:Inject - abstract val providerFactory: ProviderFactory + internal abstract val providerFactory: ProviderFactory @get:Inject - abstract val fileSystemOperations: FileSystemOperations + internal abstract val fileSystemOperations: FileSystemOperations init { description = "Download the Node.js binary distribution archive" @@ -117,13 +117,13 @@ abstract class DownloadNodeJsBinaryDistributionArchiveTask : DataConnectTaskBase * @property nodeJsVersion The version of Node.js whose binary distribution archive to download. */ @Serializable - data class Inputs( + public data class Inputs( @get:Nested val operatingSystem: OperatingSystem, @get:Input val nodeJsVersion: String ) - companion object { - private const val LOGGER_ID_PREFIX = "dnb" + private companion object { + const val LOGGER_ID_PREFIX = "dnb" } } diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsTask.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsTask.kt index e0d66fb091..0ee18237d2 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsTask.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsTask.kt @@ -67,30 +67,30 @@ import org.gradle.kotlin.dsl.newInstance import org.pgpainless.sop.SOPImpl @CacheableTask -abstract class DownloadNodeJsTask : DefaultTask() { +public abstract class DownloadNodeJsTask : DefaultTask() { @get:Nested - abstract val source: Property + public abstract val source: Property @get:OutputDirectory - abstract val outputDirectory: DirectoryProperty + public abstract val outputDirectory: DirectoryProperty @get:Internal - val nodeExecutable: RegularFile get() { + public val nodeExecutable: RegularFile get() { val source = source.get() val outputDirectory = outputDirectory.get() return outputDirectory.file(source.nodeExecutable) } @get:Internal - val npmExecutable: RegularFile get() { + public val npmExecutable: RegularFile get() { val source = source.get() val outputDirectory = outputDirectory.get() return outputDirectory.file(source.npmExecutable) } @TaskAction - fun run() { + public fun run() { val source: Source = source.get() val outputDirectoryRegularFile: Directory = outputDirectory.get() val outputDirectory: File = outputDirectoryRegularFile.asFile @@ -105,27 +105,27 @@ abstract class DownloadNodeJsTask : DefaultTask() { } } - sealed interface Source : java.io.Serializable { - companion object + public sealed interface Source : java.io.Serializable { + public companion object @get:Internal - val nodeExecutable: String + public val nodeExecutable: String @get:Internal - val npmExecutable: String + public val npmExecutable: String @get:Internal - val cacheKey: String + public val cacheKey: String } - abstract class DownloadOfficialVersion : Source { - companion object + public abstract class DownloadOfficialVersion : Source { + public companion object @get:Input - abstract val version: Property + public abstract val version: Property @get:Nested - abstract val operatingSystem: Property + public abstract val operatingSystem: Property override val nodeExecutable: String get() = nodePathOf { it.nodeExecutable } diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/GenerateDataConnectSourcesTask.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/GenerateDataConnectSourcesTask.kt index 4d37d2f908..a5f5ba4017 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/GenerateDataConnectSourcesTask.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/GenerateDataConnectSourcesTask.kt @@ -37,30 +37,30 @@ import org.gradle.process.ExecOperations import org.yaml.snakeyaml.Yaml @CacheableTask -abstract class GenerateDataConnectSourcesTask : DefaultTask() { +public abstract class GenerateDataConnectSourcesTask : DefaultTask() { - @get:InputDirectory abstract val dataConnectConfigDir: DirectoryProperty + @get:InputDirectory public abstract val dataConnectConfigDir: DirectoryProperty - @get:InputFile abstract val firebaseExecutable: RegularFileProperty + @get:InputFile public abstract val firebaseExecutable: RegularFileProperty - @get:OutputDirectory abstract val outputDirectory: DirectoryProperty + @get:OutputDirectory public abstract val outputDirectory: DirectoryProperty @get:Internal - abstract val nodeExecutable: RegularFileProperty + public abstract val nodeExecutable: RegularFileProperty @get:Internal - abstract val pathEnvironmentVariable: Property + public abstract val pathEnvironmentVariable: Property - @get:Internal abstract val tweakedDataConnectConfigDir: DirectoryProperty + @get:Internal public abstract val tweakedDataConnectConfigDir: DirectoryProperty @get:Inject - protected abstract val fileSystemOperations: FileSystemOperations + internal abstract val fileSystemOperations: FileSystemOperations @get:Inject - protected abstract val execOperations: ExecOperations + internal abstract val execOperations: ExecOperations @TaskAction - fun run() { + public fun run() { val dataConnectConfigDir = dataConnectConfigDir.get().asFile val firebaseExecutable = firebaseExecutable.get().asFile val nodeExecutable = nodeExecutable.orNull?.asFile diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/NodeJsPaths.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/NodeJsPaths.kt index 87c77bd60e..f2e3b9d80b 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/NodeJsPaths.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/NodeJsPaths.kt @@ -19,16 +19,16 @@ package com.google.firebase.example.dataconnect.gradle.tasks import com.google.firebase.example.dataconnect.gradle.DataConnectGradleException import com.google.firebase.example.dataconnect.gradle.providers.OperatingSystem -class NodeJsPaths( - val downloadUrl: String, - val downloadFileName: String, - val shasumsUrl: String, - val shasumsFileName: String +public class NodeJsPaths( + public val downloadUrl: String, + public val downloadFileName: String, + public val shasumsUrl: String, + public val shasumsFileName: String ) { - companion object + public companion object } -fun NodeJsPaths.Companion.from( +public fun NodeJsPaths.Companion.from( nodeJsVersion: String, operatingSystemType: OperatingSystem.Type, operatingSystemArchitecture: OperatingSystem.Architecture diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/SetupFirebaseToolsTask.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/SetupFirebaseToolsTask.kt index d971564ec1..648e024fbc 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/SetupFirebaseToolsTask.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/SetupFirebaseToolsTask.kt @@ -34,30 +34,30 @@ import org.gradle.api.tasks.TaskAction import org.gradle.process.ExecOperations @CacheableTask -abstract class SetupFirebaseToolsTask : DefaultTask() { +public abstract class SetupFirebaseToolsTask : DefaultTask() { @get:Input - abstract val firebaseCliVersion: Property + public abstract val firebaseCliVersion: Property @get:OutputDirectory - abstract val outputDirectory: DirectoryProperty + public abstract val outputDirectory: DirectoryProperty @get:Internal - abstract val npmExecutable: RegularFileProperty + public abstract val npmExecutable: RegularFileProperty @get:Internal - abstract val nodeExecutable: RegularFileProperty + public abstract val nodeExecutable: RegularFileProperty @get:Internal - val firebaseExecutable: RegularFile get() = outputDirectory.get().file("node_modules/.bin/firebase") + public val firebaseExecutable: RegularFile get() = outputDirectory.get().file("node_modules/.bin/firebase") @get:Inject - protected abstract val execOperations: ExecOperations + internal abstract val execOperations: ExecOperations private val pathEnvironmentVariable: Provider get() = project.providers.environmentVariable("PATH") @TaskAction - fun run() { + public fun run() { val firebaseCliVersion: String = firebaseCliVersion.get() val npmExecutable: File = npmExecutable.get().asFile val nodeExecutable: File = nodeExecutable.get().asFile diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/util/DataConnectGradleLogger.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/util/DataConnectGradleLogger.kt index 65ecdff336..a0e0262157 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/util/DataConnectGradleLogger.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/util/DataConnectGradleLogger.kt @@ -35,37 +35,37 @@ import org.gradle.api.logging.Logger * for example a class named "DataFileLoader" could use the `loggerIdPrefix` of "dfl". * @param logger The logger that will be used to do the actual logging. */ -class DataConnectGradleLogger(loggerIdPrefix: String, private val logger: Logger) { +public class DataConnectGradleLogger(loggerIdPrefix: String, private val logger: Logger) { private val loggerId: String = "$loggerIdPrefix${Random.nextAlphanumericString(8)}" - val isDebugEnabled: Boolean get() = logger.isDebugEnabled + public val isDebugEnabled: Boolean get() = logger.isDebugEnabled - val isInfoEnabled: Boolean get() = logger.isInfoEnabled + public val isInfoEnabled: Boolean get() = logger.isInfoEnabled - val isWarnEnabled: Boolean get() = logger.isWarnEnabled + public val isWarnEnabled: Boolean get() = logger.isWarnEnabled - inline fun debug(block: () -> String) { + public inline fun debug(block: () -> String) { if (isDebugEnabled) { log(LogLevel.DEBUG, block()) } } - inline fun info(block: () -> String) { + public inline fun info(block: () -> String) { if (isInfoEnabled) { log(LogLevel.INFO, block()) } } - inline fun warn(block: () -> String) { + public inline fun warn(block: () -> String) { if (isWarnEnabled) { log(LogLevel.WARN, block()) } } - fun warn(message: String): Unit = log(LogLevel.WARN, message, prefix = "WARNING: ") + public fun warn(message: String): Unit = log(LogLevel.WARN, message, prefix = "WARNING: ") - fun log(level: LogLevel, message: String, prefix: String = "") { + public fun log(level: LogLevel, message: String, prefix: String = "") { logger.log(level, "[{}] {}{}", loggerId, prefix, message) } } diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/util/DataConnectGradleLoggerProvider.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/util/DataConnectGradleLoggerProvider.kt index 346500d485..9b4051c2ad 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/util/DataConnectGradleLoggerProvider.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/util/DataConnectGradleLoggerProvider.kt @@ -20,11 +20,11 @@ import com.google.firebase.example.dataconnect.gradle.DataConnectGradleException import java.io.File import org.gradle.api.file.FileSystemOperations -interface DataConnectGradleLoggerProvider { +internal interface DataConnectGradleLoggerProvider { val logger: DataConnectGradleLogger } -fun DataConnectGradleLoggerProvider.deleteDirectory(dir: File, fileSystemOperations: FileSystemOperations) { +internal fun DataConnectGradleLoggerProvider.deleteDirectory(dir: File, fileSystemOperations: FileSystemOperations) { logger.info { "Deleting directory: $dir" } fileSystemOperations.runCatching { delete { delete(dir) } }.onFailure { throw DataConnectGradleException( @@ -35,7 +35,7 @@ fun DataConnectGradleLoggerProvider.deleteDirectory(dir: File, fileSystemOperati } } -fun DataConnectGradleLoggerProvider.createDirectory(dir: File) { +internal fun DataConnectGradleLoggerProvider.createDirectory(dir: File) { logger.info { "Creating directory: $dir" } if (!dir.mkdirs()) { throw DataConnectGradleException( diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/util/FileDownloader.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/util/FileDownloader.kt index 6677494db3..026d00a18b 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/util/FileDownloader.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/util/FileDownloader.kt @@ -31,7 +31,7 @@ import java.io.File import java.text.NumberFormat import java.util.concurrent.atomic.AtomicReference -class FileDownloader(private val logger: DataConnectGradleLogger) : AutoCloseable { +internal class FileDownloader(private val logger: DataConnectGradleLogger) : AutoCloseable { private val state: AtomicReference = AtomicReference(State.Uninitialized) private val httpClient: HttpClient get() = getOrCreateHttpClient() diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/util/FileKtx.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/util/FileKtx.kt index 3c4a452219..964193d7e1 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/util/FileKtx.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/util/FileKtx.kt @@ -20,7 +20,7 @@ import java.io.ByteArrayOutputStream import java.io.File import java.io.IOException -fun File.readBytes(byteLimit: Int): ByteArray = inputStream().use { inputStream -> +internal fun File.readBytes(byteLimit: Int): ByteArray = inputStream().use { inputStream -> require(byteLimit >= 0) { "invalid byte limit: $byteLimit " + "(must be greater than or equal to zero) (error code vrr6daevyw)" @@ -51,4 +51,4 @@ fun File.readBytes(byteLimit: Int): ByteArray = inputStream().use { inputStream return byteArrayOutputStream.toByteArray() } -class TooManyBytesReadException(message: String) : IOException(message) +public class TooManyBytesReadException(message: String) : IOException(message) diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/util/RandomKtx.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/util/RandomKtx.kt index 3cd89fbff3..8c9ad678bb 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/util/RandomKtx.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/util/RandomKtx.kt @@ -28,7 +28,7 @@ import kotlin.random.Random * 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 { +internal fun Random.nextAlphanumericString(length: Int): String { require(length >= 0) { "invalid length: $length" } return (0 until length).map { ALPHANUMERIC_ALPHABET.random(this) }.joinToString(separator = "") } diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/util/Sha256SignatureVerifier.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/util/Sha256SignatureVerifier.kt index 572b5d720d..13d9d7d358 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/util/Sha256SignatureVerifier.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/util/Sha256SignatureVerifier.kt @@ -20,7 +20,7 @@ import org.pgpainless.sop.SOPImpl * This class is thread-safe; that is, instances of this class can safely have their methods invoked * concurrently from multiple threads. */ -class Sha256SignatureVerifier { +internal class Sha256SignatureVerifier { private val lock = ReentrantLock() private val certificates = mutableListOf() @@ -136,7 +136,7 @@ class Sha256SignatureVerifier { * Shorthand for [Sha256SignatureVerifier.addCertificate], calling it with the bytes of the given * resource, as loaded by [ClassLoader.getResourceAsStream]. */ -fun Sha256SignatureVerifier.addCertificateFromResource(resourcePath: String) { +internal fun Sha256SignatureVerifier.addCertificateFromResource(resourcePath: String) { val certificateBytes = loadResource(resourcePath) addCertificate(certificateBytes) } @@ -148,7 +148,7 @@ fun Sha256SignatureVerifier.addCertificateFromResource(resourcePath: String) { * * @return the number of certificates that were added by this method invocation. */ -fun Sha256SignatureVerifier.addCertificatesFromKeyListResource(keyListResourcePath: String): Int { +internal fun Sha256SignatureVerifier.addCertificatesFromKeyListResource(keyListResourcePath: String): Int { val keyNames = loadKeyNameList(keyListResourcePath) val lastSlashIndex = keyListResourcePath.lastIndexOf('/') @@ -198,7 +198,7 @@ private fun Sha256SignatureVerifier.loadKeyNameList(resourcePath: String): List< * * @return the names of the files whose hashes were added. */ -fun Sha256SignatureVerifier.addHashesFromShasumsFile(shasumsFile: File): Set { +internal fun Sha256SignatureVerifier.addHashesFromShasumsFile(shasumsFile: File): Set { val shasumsFileSignedBytes = shasumsFile.readBytes(byteLimit = 1_000_000) val shasumsFileBytes = this.verifySignature(shasumsFileSignedBytes) val shasumsFileLines = String(shasumsFileBytes, StandardCharsets.UTF_8).lines() From ecf3fc1efd1f3e0cf53852ac139265c80830b810 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Thu, 28 Nov 2024 22:54:16 +0000 Subject: [PATCH 68/79] ExtractArchiveTask.kt skeleton added --- .../gradle/tasks/ExtractArchiveTask.kt | 81 +++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/ExtractArchiveTask.kt diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/ExtractArchiveTask.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/ExtractArchiveTask.kt new file mode 100644 index 0000000000..365f808642 --- /dev/null +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/ExtractArchiveTask.kt @@ -0,0 +1,81 @@ +/* + * 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.example.dataconnect.gradle.tasks + +import java.io.File +import javax.inject.Inject +import org.gradle.api.file.DirectoryProperty +import org.gradle.api.file.FileSystemOperations +import org.gradle.api.file.RegularFileProperty +import org.gradle.api.logging.Logger +import org.gradle.api.provider.Property +import org.gradle.api.tasks.InputFile +import org.gradle.api.tasks.OutputDirectory +import org.gradle.work.DisableCachingByDefault + +@DisableCachingByDefault(because = "extracting an archive is a quick operation not worth caching") +public abstract class ExtractArchiveTask : DataConnectTaskBase(LOGGER_ID_PREFIX) { + + /** + * The archive file to extract, such as a ".zip" or ".tar.xz" file. + * + * This property is _required_, meaning that it must be set; that is, [Property.isPresent] must + * return `true`. + */ + @get:InputFile + public abstract val archiveFile: RegularFileProperty + + /** + * The directory into which to extract the archive. + * + * This property is _required_, meaning that it must be set; that is, [Property.isPresent] must + * return `true`. + * + * This directory will be deleted and re-created when this task is executed. + */ + @get:OutputDirectory + public abstract val outputDirectory: DirectoryProperty + + @get:Inject + internal abstract val fileSystemOperations: FileSystemOperations + + override fun doRun() { + val archiveExtractor = ArchiveExtractor( + archiveFile = archiveFile.get().asFile, + outputDirectory = outputDirectory.get().asFile, + fileSystemOperations = fileSystemOperations, + logger = logger + ) + archiveExtractor.run() + } + + private companion object { + const val LOGGER_ID_PREFIX = "ear" + } + +} + +private class ArchiveExtractor( + val archiveFile: File, + val outputDirectory: File, + val fileSystemOperations: FileSystemOperations, + val logger: Logger +) + +private fun ArchiveExtractor.run() { + +} From 77475352e8327210670b1f7ffe4cd52005130925 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Thu, 28 Nov 2024 22:55:28 +0000 Subject: [PATCH 69/79] work --- .../gradle/DataConnectExtension.kt | 2 +- .../gradle/providers/OperatingSystem.kt | 61 ++++++------------- .../dataconnect/gradle/providers/providers.kt | 6 +- ...loadNodeJsBinaryDistributionArchiveTask.kt | 4 -- .../dataconnect/gradle/tasks/NodeJsPaths.kt | 11 ++++ 5 files changed, 34 insertions(+), 50 deletions(-) diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectExtension.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectExtension.kt index 791a245948..961629fa4c 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectExtension.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectExtension.kt @@ -25,7 +25,7 @@ public interface DataConnectExtension { * Firebase CLI. This version of Node.js will be downloaded and extracted * for exclusive use of the Data Connect Gradle plugin. */ - public var nodeVersion: String? + public var nodeJsVersion: String? /** * The version of the Firebase CLI (https://www.npmjs.com/package/firebase-tools) diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/providers/OperatingSystem.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/providers/OperatingSystem.kt index 24c3b364e1..e31115c806 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/providers/OperatingSystem.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/providers/OperatingSystem.kt @@ -16,39 +16,19 @@ package com.google.firebase.example.dataconnect.gradle.providers +import com.google.firebase.example.dataconnect.gradle.util.DataConnectGradleLogger import kotlinx.serialization.Serializable import org.gradle.api.GradleException import org.gradle.api.logging.Logger -import org.gradle.api.model.ObjectFactory import org.gradle.api.provider.Provider import org.gradle.api.provider.ProviderFactory import org.gradle.api.tasks.Input -import org.gradle.api.tasks.Internal -import org.gradle.kotlin.dsl.property @Serializable public data class OperatingSystem( @get:Input val type: Type, @get:Input val architecture: Architecture ) { - public constructor( - type: Type, - arch: Architecture, - description: String - ) : this(type, arch) { - this.description = description - } - - // Declare `description` outside the primary constructor so that it does - // not get serialized or included in equals() and hashCode() computations. - @get:Internal - var description: String? = null - private set - - // Override toString() to include the description - override fun toString(): String = "OperatingSystem(" + - "type=$type, architecture=$architecture, description=$description" + - ")" public enum class Type { Windows, @@ -72,38 +52,35 @@ public data class OperatingSystem( public companion object } -internal fun OperatingSystem.Companion.provider( - objectFactory: ObjectFactory, - providerFactory: ProviderFactory, +internal fun ProviderFactory.operatingSystem( logger: Logger ): Provider { - val logPrefix = "OperatingSystem.provider():" - val osNameProvider = providerFactory.systemProperty("os.name") - val osArchProvider = providerFactory.systemProperty("os.arch") + @Suppress("NAME_SHADOWING") val logger = DataConnectGradleLogger(loggerIdPrefix="os", logger) + val osNameSystemPropertyName = "os.name" + val osArchitectureSystemPropertyName = "os.arch" + val osNameProvider = systemProperty(osNameSystemPropertyName) + val osArchitectureProvider = systemProperty(osArchitectureSystemPropertyName) - val provider: Provider = providerFactory.provider { + return provider { val osName = osNameProvider.orNull - val osArch = osArchProvider.orNull - val description = "os.name=$osName and os.arch=$osArch" - logger.info("{} osName: {}", logPrefix, osName) - logger.info("{} osArch: {}", logPrefix, osArch) + val osArchitecture = osArchitectureProvider.orNull + logger.info { "System property \"$osNameSystemPropertyName\": $osName" } + logger.info { "System property \"$osArchitectureSystemPropertyName\": $osArchitecture" } val type = osName?.let { OperatingSystem.Type.forName(it) } - val arch = osArch?.let { OperatingSystem.Architecture.forName(it) } + val architecture = osArchitecture?.let { OperatingSystem.Architecture.forName(it) } - if (type === null || arch === null) { + if (type === null || architecture === null) { throw GradleException( - "unable to determine operating system from $description " + - " (type=$type, arch=$arch) (error code qecxcvcf8n)" + "unable to determine operating system from Java system properties: " + + "$osNameSystemPropertyName=$osName, " + + "$osArchitectureSystemPropertyName=$osArchitecture " + + "(computed values: type=$type, architecture=$architecture) " + + "(error code qecxcvcf8n)" ) } - OperatingSystem(type = type, arch = arch, description = description) - } - - return objectFactory.property().apply { - set(provider) - disallowUnsafeRead() + OperatingSystem(type = type, architecture = architecture) } } diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/providers/providers.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/providers/providers.kt index 8e1e64ddaa..636213f76a 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/providers/providers.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/providers/providers.kt @@ -76,11 +76,11 @@ internal open class MyProjectProviders( providerFactory.provider { lazyFirebaseCliVersion.value } } - val nodeVersion: Provider = run { + val nodeJsVersion: Provider = run { val lazyNodeVersion: Lazy = lazy { - ext.nodeVersion + ext.nodeJsVersion ?: throw GradleException( - "dataconnect.nodeVersion must be set in " + + "dataconnect.nodeJsVersion must be set in " + "build.gradle or build.gradle.kts to " + "specify the version of Node.js (https://nodejs.org) " + "to install (e.g. \"20.9.0\") (error code 3acj27az2c)" diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsBinaryDistributionArchiveTask.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsBinaryDistributionArchiveTask.kt index 6ae4ba0bff..ef7ddaabf5 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsBinaryDistributionArchiveTask.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsBinaryDistributionArchiveTask.kt @@ -88,10 +88,6 @@ public abstract class DownloadNodeJsBinaryDistributionArchiveTask : DataConnectT @get:Inject internal abstract val fileSystemOperations: FileSystemOperations - init { - description = "Download the Node.js binary distribution archive" - } - override fun doRun() { val (operatingSystem: OperatingSystem, nodeJsVersion: String) = inputData.get() diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/NodeJsPaths.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/NodeJsPaths.kt index f2e3b9d80b..4b91276481 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/NodeJsPaths.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/NodeJsPaths.kt @@ -18,6 +18,8 @@ package com.google.firebase.example.dataconnect.gradle.tasks import com.google.firebase.example.dataconnect.gradle.DataConnectGradleException import com.google.firebase.example.dataconnect.gradle.providers.OperatingSystem +import org.gradle.api.provider.Provider +import org.gradle.api.provider.ProviderFactory public class NodeJsPaths( public val downloadUrl: String, @@ -28,6 +30,15 @@ public class NodeJsPaths( public companion object } +internal fun ProviderFactory.nodeJsPaths( + nodeJsVersion: Provider, + operatingSystem: Provider +): Provider = provider { + @Suppress("NAME_SHADOWING") val nodeJsVersion: String = nodeJsVersion.get() + @Suppress("NAME_SHADOWING") val operatingSystem: OperatingSystem = operatingSystem.get() + NodeJsPaths.from(nodeJsVersion, operatingSystem.type, operatingSystem.architecture) +} + public fun NodeJsPaths.Companion.from( nodeJsVersion: String, operatingSystemType: OperatingSystem.Type, From fd72edd589d50f2ea8ab35b77e1e382e3a6393ab Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Thu, 28 Nov 2024 22:57:43 +0000 Subject: [PATCH 70/79] fix build --- .../example/dataconnect/gradle/providers/providers.kt | 2 +- .../tasks/DownloadNodeJsBinaryDistributionArchiveTask.kt | 4 ++-- .../example/dataconnect/gradle/tasks/DownloadNodeJsTask.kt | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/providers/providers.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/providers/providers.kt index 636213f76a..a414c1f5aa 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/providers/providers.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/providers/providers.kt @@ -58,7 +58,7 @@ internal open class MyProjectProviders( project.logger ) - val operatingSystem: Provider = OperatingSystem.provider(objectFactory, providerFactory, logger) + val operatingSystem: Provider = providerFactory.operatingSystem(logger) val buildDirectory: Provider = projectBuildDirectory.map { it.dir("dataconnect") } diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsBinaryDistributionArchiveTask.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsBinaryDistributionArchiveTask.kt index ef7ddaabf5..7409cf33a9 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsBinaryDistributionArchiveTask.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsBinaryDistributionArchiveTask.kt @@ -129,8 +129,8 @@ internal fun DownloadNodeJsBinaryDistributionArchiveTask.configureFrom(myProvide set( providerFactory.provider { val operatingSystem = myProviders.operatingSystem.get() - val nodeVersion = myProviders.nodeVersion.get() - Inputs(operatingSystem, nodeVersion) + val nodeJsVersion = myProviders.nodeJsVersion.get() + Inputs(operatingSystem, nodeJsVersion) } ) } diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsTask.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsTask.kt index 0ee18237d2..ba2aba5ad0 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsTask.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsTask.kt @@ -194,7 +194,7 @@ internal val MyProjectProviders.source: Provider } internal fun DownloadOfficialVersion.updateFrom(providers: MyProjectProviders) { - version.set(providers.nodeVersion) + version.set(providers.nodeJsVersion) operatingSystem.set(providers.operatingSystem) } From 8159400fa7833ec3034636d6a0b6ef959a5519ee Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Fri, 29 Nov 2024 00:42:26 +0000 Subject: [PATCH 71/79] implement xz --- dataconnect/app/build.gradle.kts | 2 +- dataconnect/buildSrc/build.gradle.kts | 2 + .../gradle/DataConnectGradlePlugin.kt | 28 ++- .../dataconnect/gradle/providers/providers.kt | 10 +- .../gradle/tasks/ExtractArchiveTask.kt | 73 +++++++- .../tasks/GenerateDataConnectSourcesTask.kt | 3 +- .../gradle/tasks/SetupFirebaseToolsTask.kt | 5 +- .../gradle/util/ArchiveExtraction.kt | 174 ++++++++++++++++++ .../dataconnect/gradle/util/LongKtx.kt | 21 +++ 9 files changed, 301 insertions(+), 17 deletions(-) create mode 100644 dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/util/ArchiveExtraction.kt create mode 100644 dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/util/LongKtx.kt diff --git a/dataconnect/app/build.gradle.kts b/dataconnect/app/build.gradle.kts index 8d442ede2d..bd6f284f20 100644 --- a/dataconnect/app/build.gradle.kts +++ b/dataconnect/app/build.gradle.kts @@ -89,7 +89,7 @@ dataconnect { // The version of Node.js (https://nodejs.org) to use to install and run the // Firebase CLI. This version of Node.js will be downloaded and extracted // for exclusive use of the Data Connect Gradle plugin. - nodeVersion = "20.18.1" + nodeJsVersion = "20.18.1" // The version of the Firebase CLI (https://www.npmjs.com/package/firebase-tools) // to use to perform the Data Connect Kotlin code generation. diff --git a/dataconnect/buildSrc/build.gradle.kts b/dataconnect/buildSrc/build.gradle.kts index 0d51a1b9a3..e745a5251f 100644 --- a/dataconnect/buildSrc/build.gradle.kts +++ b/dataconnect/buildSrc/build.gradle.kts @@ -41,6 +41,8 @@ dependencies { implementation("org.pgpainless:pgpainless-sop:1.7.2") implementation("org.bouncycastle:bcprov-jdk18on:1.78.1") implementation("org.apache.commons:commons-compress:1.27.1") + + runtimeOnly("org.tukaani:xz:1.10") } gradlePlugin { diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectGradlePlugin.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectGradlePlugin.kt index ef03195689..6d7ee7caf4 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectGradlePlugin.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectGradlePlugin.kt @@ -22,6 +22,7 @@ import com.google.firebase.example.dataconnect.gradle.providers.MyProjectProvide import com.google.firebase.example.dataconnect.gradle.providers.MyVariantProviders import com.google.firebase.example.dataconnect.gradle.tasks.DownloadNodeJsBinaryDistributionArchiveTask import com.google.firebase.example.dataconnect.gradle.tasks.DownloadNodeJsTask +import com.google.firebase.example.dataconnect.gradle.tasks.ExtractArchiveTask import com.google.firebase.example.dataconnect.gradle.tasks.GenerateDataConnectSourcesTask import com.google.firebase.example.dataconnect.gradle.tasks.SetupFirebaseToolsTask import com.google.firebase.example.dataconnect.gradle.tasks.configureFrom @@ -35,11 +36,36 @@ import org.gradle.kotlin.dsl.register public abstract class DataConnectGradlePlugin : Plugin { override fun apply(project: Project) { + project.extensions.create("dataconnect", DataConnectExtension::class.java) + val providers = MyProjectProviders(project) + + val dlTask = project.tasks.register("dl") { + group = TASK_GROUP + configureFrom(providers) + } + + project.tasks.register("ex") { + group = TASK_GROUP + + archiveFile.set( + dlTask.flatMap { task -> + task.outputDirectory.map { outputDirectory -> + val downloadFileName = providers.nodeJsPaths.get().downloadFileName + outputDirectory.file(downloadFileName) + } + } + ) + + outputDirectory.set(providers.buildDirectory.map { it.dir("extracted") }) + } + } + + private fun apply2(project: Project) { val downloadNodeJsTask = project.tasks.register("downloadNodeJs") val setupFirebaseToolsTask = project.tasks.register("setupFirebaseToolsForDataConnect") project.extensions.create("dataconnect", DataConnectExtension::class.java) - val providers = MyProjectProviders(project, downloadNodeJsTask) + val providers = MyProjectProviders(project) project.tasks.register("downloadNodeJsBinaryDistributionArchive") { group = TASK_GROUP diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/providers/providers.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/providers/providers.kt index a414c1f5aa..46942e86d0 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/providers/providers.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/providers/providers.kt @@ -18,8 +18,9 @@ package com.google.firebase.example.dataconnect.gradle.providers import com.android.build.api.variant.ApplicationVariant import com.google.firebase.example.dataconnect.gradle.DataConnectExtension -import com.google.firebase.example.dataconnect.gradle.tasks.DownloadNodeJsTask +import com.google.firebase.example.dataconnect.gradle.tasks.NodeJsPaths import com.google.firebase.example.dataconnect.gradle.tasks.SetupFirebaseToolsTask +import com.google.firebase.example.dataconnect.gradle.tasks.nodeJsPaths import org.gradle.api.GradleException import org.gradle.api.Project import org.gradle.api.file.Directory @@ -38,22 +39,17 @@ internal open class MyProjectProviders( val providerFactory: ProviderFactory, val objectFactory: ObjectFactory, val projectLayout: ProjectLayout, - val nodeExecutable: Provider, - val npmExecutable: Provider, ext: DataConnectExtension, logger: Logger ) { constructor( project: Project, - downloadNodeJsTask: TaskProvider ) : this( projectBuildDirectory = project.layout.buildDirectory, providerFactory = project.providers, objectFactory = project.objects, projectLayout = project.layout, - nodeExecutable = downloadNodeJsTask.map { it.nodeExecutable }, - npmExecutable = downloadNodeJsTask.map { it.npmExecutable }, ext = project.extensions.getByType(), project.logger ) @@ -97,6 +93,8 @@ internal open class MyProjectProviders( } providerFactory.provider { lazyDataConnectConfigDir.value } } + + val nodeJsPaths: Provider = providerFactory.nodeJsPaths(nodeJsVersion, operatingSystem) } internal open class MyVariantProviders( diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/ExtractArchiveTask.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/ExtractArchiveTask.kt index 365f808642..6550a75803 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/ExtractArchiveTask.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/ExtractArchiveTask.kt @@ -16,16 +16,25 @@ package com.google.firebase.example.dataconnect.gradle.tasks -import java.io.File -import javax.inject.Inject +import com.google.firebase.example.dataconnect.gradle.util.ArchiveSetFileMetadataType +import com.google.firebase.example.dataconnect.gradle.util.ArchiveType +import com.google.firebase.example.dataconnect.gradle.util.DataConnectGradleLogger +import com.google.firebase.example.dataconnect.gradle.util.DataConnectGradleLoggerProvider +import com.google.firebase.example.dataconnect.gradle.util.ExtractArchiveWithDetectionCallbacks +import com.google.firebase.example.dataconnect.gradle.util.createDirectory +import com.google.firebase.example.dataconnect.gradle.util.deleteDirectory +import com.google.firebase.example.dataconnect.gradle.util.extractArchive +import com.google.firebase.example.dataconnect.gradle.util.toFormattedString import org.gradle.api.file.DirectoryProperty import org.gradle.api.file.FileSystemOperations import org.gradle.api.file.RegularFileProperty -import org.gradle.api.logging.Logger import org.gradle.api.provider.Property import org.gradle.api.tasks.InputFile import org.gradle.api.tasks.OutputDirectory import org.gradle.work.DisableCachingByDefault +import java.io.File +import java.util.concurrent.atomic.AtomicLong +import javax.inject.Inject @DisableCachingByDefault(because = "extracting an archive is a quick operation not worth caching") public abstract class ExtractArchiveTask : DataConnectTaskBase(LOGGER_ID_PREFIX) { @@ -58,7 +67,7 @@ public abstract class ExtractArchiveTask : DataConnectTaskBase(LOGGER_ID_PREFIX) archiveFile = archiveFile.get().asFile, outputDirectory = outputDirectory.get().asFile, fileSystemOperations = fileSystemOperations, - logger = logger + logger = dataConnectLogger ) archiveExtractor.run() } @@ -73,9 +82,61 @@ private class ArchiveExtractor( val archiveFile: File, val outputDirectory: File, val fileSystemOperations: FileSystemOperations, - val logger: Logger -) + override val logger: DataConnectGradleLogger +) : DataConnectGradleLoggerProvider private fun ArchiveExtractor.run() { + logger.info { "archiveFile: ${archiveFile.absolutePath}" } + logger.info { "outputDirectory: ${outputDirectory.absolutePath}" } + + deleteDirectory(outputDirectory, fileSystemOperations) + createDirectory(outputDirectory) + logger.info { "Extracting ${archiveFile.absolutePath} to ${outputDirectory.absolutePath}" } + val extractCallbacks = ExtractArchiveCallbacksImpl(archiveFile, logger) + archiveFile.extractArchive(outputDirectory, extractCallbacks) + logger.info { + "Extracted ${extractCallbacks.extractedFileCount.toFormattedString()} files " + + "(${extractCallbacks.extractedByteCount.toFormattedString()} bytes) " + + "from ${archiveFile.absolutePath} to ${outputDirectory.absolutePath}" + } } + +private class ExtractArchiveCallbacksImpl(private val file: File, private val logger: DataConnectGradleLogger) : + ExtractArchiveWithDetectionCallbacks { + + private val _extractedFileCount = AtomicLong(0) + val extractedFileCount: Long get() = _extractedFileCount.get() + + private val _extractedByteCount = AtomicLong(0) + val extractedByteCount: Long get() = _extractedByteCount.get() + + override fun onArchiveTypeDetected(archiveType: ArchiveType) { + logger.info { "Detected archive type $archiveType for file: ${file.absolutePath}" } + } + + override fun onExtractFileStarting(srcPath: String, destFile: File) { + _extractedFileCount.incrementAndGet() + logger.debug { "Extracting $srcPath to ${destFile.absolutePath}" } + } + + override fun onExtractFileDone(srcPath: String, destFile: File, extractedByteCount: Long) { + _extractedByteCount.addAndGet(extractedByteCount) + logger.debug { + "Extracted ${extractedByteCount.toFormattedString()} bytes " + + "from $srcPath to ${destFile.absolutePath}" + } + } + + override fun onExtractSymlink(linkPath: String, destFile: File) { + logger.debug { "Creating symlink ${destFile.absolutePath} to $linkPath" } + } + + override fun onSetFileMetadataFailed(file: File, metadataType: ArchiveSetFileMetadataType, exception: Exception) { + logger.debug { + "Ignoring failure to set ${metadataType.name} " + + "on extracted file: ${file.absolutePath}" + } + } + +} \ No newline at end of file diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/GenerateDataConnectSourcesTask.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/GenerateDataConnectSourcesTask.kt index a5f5ba4017..21b60913c3 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/GenerateDataConnectSourcesTask.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/GenerateDataConnectSourcesTask.kt @@ -114,7 +114,8 @@ public abstract class GenerateDataConnectSourcesTask : DefaultTask() { internal fun GenerateDataConnectSourcesTask.configureFrom(providers: MyVariantProviders) { dataConnectConfigDir.set(providers.projectProviders.dataConnectConfigDir) firebaseExecutable.set(providers.firebaseExecutable) - nodeExecutable.set(providers.projectProviders.nodeExecutable) + TODO("uncomment line below nw8m8k5hjx") + //nodeExecutable.set(providers.projectProviders.nodeExecutable) tweakedDataConnectConfigDir.set(providers.buildDirectory.map { it.dir("config") }) } diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/SetupFirebaseToolsTask.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/SetupFirebaseToolsTask.kt index 648e024fbc..5036e22fcd 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/SetupFirebaseToolsTask.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/SetupFirebaseToolsTask.kt @@ -88,7 +88,8 @@ public abstract class SetupFirebaseToolsTask : DefaultTask() { internal fun SetupFirebaseToolsTask.configureFrom(providers: MyProjectProviders) { firebaseCliVersion.set(providers.firebaseCliVersion) - npmExecutable.set(providers.npmExecutable) - nodeExecutable.set(providers.nodeExecutable) + TODO("uncomment lines below yfcm22wmxt") + //npmExecutable.set(providers.npmExecutable) + //nodeExecutable.set(providers.nodeExecutable) outputDirectory.set(providers.buildDirectory.map { it.dir("firebase-tools") }) } diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/util/ArchiveExtraction.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/util/ArchiveExtraction.kt new file mode 100644 index 0000000000..0c37439b26 --- /dev/null +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/util/ArchiveExtraction.kt @@ -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.example.dataconnect.gradle.util + +import org.apache.commons.compress.archivers.ArchiveEntry +import org.apache.commons.compress.archivers.ArchiveInputStream +import org.apache.commons.compress.archivers.tar.TarArchiveEntry +import org.apache.commons.compress.archivers.tar.TarArchiveInputStream +import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream +import org.apache.commons.compress.compressors.xz.XZCompressorInputStream +import org.apache.commons.compress.compressors.xz.XZUtils +import java.io.File +import java.io.IOException +import java.io.InputStream +import java.nio.file.Files +import java.nio.file.Paths +import java.nio.file.attribute.FileTime +import java.nio.file.attribute.PosixFilePermission + +internal fun File.extractArchive(destDir: File, callbacks: ExtractArchiveWithDetectionCallbacks?) { + if (XZUtils.isCompressedFileName(name)) { + callbacks?.onArchiveTypeDetected(ArchiveType.TarXz) + extractTarXzArchive(destDir, callbacks) + } else if (name.endsWith(".7z")) { + callbacks?.onArchiveTypeDetected(ArchiveType.SevenZip) + extract7zArchive(destDir, callbacks) + } else { + throw UnsupportedArchiveException( + "don't know how to extract $name; " + + "supported archive formats are .7z and .tar.xz " + + "(error code vm9w6kmaby)" + ) + } +} + +@JvmName("extractTarXzArchiveFileExt") +private fun File.extractTarXzArchive(destDir: File, callbacks: ExtractArchiveCallbacks?) { + extractTarXzArchive(file = this, destDir = destDir, callbacks) +} + +private fun extractTarXzArchive( + inputStream: InputStream, + destDir: File, + callbacks: ExtractArchiveCallbacks? +) { + XZCompressorInputStream(inputStream).use { xzInputStream -> + extractTarArchive(xzInputStream, destDir, callbacks) + } +} + +private fun extractTarArchive( + inputStream: InputStream, + destDir: File, + callbacks: ExtractArchiveCallbacks? +) { + // Use `ArchiveInputStream` as the static type of `tarInputStream`, rather than the more + // natural `TarArchiveInputStream` because this enables compatibility with older versions of + // the Apache Commons Compress library. This is because at one point `ArchiveInputStream` was + // changed to be a _generic_ class which caused the signature of + // `TarArchiveInputStream.getNextEntry()` to change, a breaking ABI change which could cause + // NoSuchMethodErrors at runtime. By using `ArchiveInputStream` directly it uses the + // `getNextEntry()` method from `ArchiveInputStream`, whose ABI did _not_ change, and, + // therefore, links correctly at runtime with both the old and the new Commons Compress library. + val archiveInputStream: ArchiveInputStream<*> = TarArchiveInputStream(inputStream) + + archiveInputStream.use { tarInputStream -> + while (true) { + val tarEntry: ArchiveEntry = tarInputStream.nextEntry ?: break + if (tarEntry !is TarArchiveEntry) { + continue + } + val outputFile = File(destDir, tarEntry.name).absoluteFile + if (tarEntry.isSymbolicLink) { + callbacks?.onExtractSymlink(tarEntry.linkName, outputFile) + Files.createSymbolicLink(outputFile.toPath(), Paths.get(tarEntry.linkName)) + } else if (tarEntry.isFile) { + callbacks?.onExtractFileStarting(tarEntry.name, outputFile) + outputFile.parentFile.mkdirs() + val extractedByteCount = outputFile.outputStream().use { fileOutputStream -> + tarInputStream.copyTo(fileOutputStream) + } + callbacks?.onExtractFileDone(tarEntry.name, outputFile, extractedByteCount) + + val lastModifiedTime = FileTime.from(tarEntry.lastModifiedDate.toInstant()) + try { + Files.setLastModifiedTime(outputFile.toPath(), lastModifiedTime) + } catch (e: IOException) { + callbacks?.onSetFileMetadataFailed(outputFile, ArchiveSetFileMetadataType.LastModifiedTime, e) + } + + val newPermissions = buildSet { + add(PosixFilePermission.OWNER_READ) + add(PosixFilePermission.OWNER_WRITE) + + add(PosixFilePermission.GROUP_READ) + add(PosixFilePermission.OTHERS_READ) + + val mode = tarEntry.mode + if ((mode and 0x100) == 0x100) { + add(PosixFilePermission.OWNER_EXECUTE) + add(PosixFilePermission.GROUP_EXECUTE) + add(PosixFilePermission.OTHERS_EXECUTE) + } + } + try { + Files.setPosixFilePermissions(outputFile.toPath(), newPermissions) + } catch (e: UnsupportedOperationException) { + callbacks?.onSetFileMetadataFailed(outputFile, ArchiveSetFileMetadataType.PosixFilePermissions, e) + } + } else { + continue + } + } + + } +} + +private fun extractTarXzArchive(file: File, destDir: File, callbacks: ExtractArchiveCallbacks?) { + file.inputStream().use { fileInputStream -> + extractTarXzArchive(fileInputStream, destDir, callbacks) + } +} + +@JvmName("extract7zArchiveFileExt") +private fun File.extract7zArchive(destDir: File, callbacks: ExtractArchiveCallbacks?) { + extract7zArchive(file = this, destDir = destDir, callbacks) +} + +private fun extract7zArchive(file: File, destDir: File, callbacks: ExtractArchiveCallbacks?) { + TODO("extract7zArchive() not yet implemented") +} + +private class UnsupportedArchiveException(message: String) : Exception(message) + +internal interface ExtractArchiveCallbacks { + + fun onExtractFileStarting(srcPath: String, destFile: File) + + fun onExtractFileDone(srcPath: String, destFile: File, extractedByteCount: Long) + + fun onExtractSymlink(linkPath: String, destFile: File) + + fun onSetFileMetadataFailed(file: File, metadataType: ArchiveSetFileMetadataType, exception: Exception) +} + +internal interface ExtractArchiveWithDetectionCallbacks : ExtractArchiveCallbacks { + + fun onArchiveTypeDetected(archiveType: ArchiveType) + +} + +internal enum class ArchiveType { + TarXz, + SevenZip, +} + +internal enum class ArchiveSetFileMetadataType { + LastModifiedTime, + PosixFilePermissions, +} \ No newline at end of file diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/util/LongKtx.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/util/LongKtx.kt new file mode 100644 index 0000000000..0b2c6cf015 --- /dev/null +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/util/LongKtx.kt @@ -0,0 +1,21 @@ +/* + * 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.example.dataconnect.gradle.util + +import java.text.NumberFormat + +internal fun Long.toFormattedString(): String = NumberFormat.getNumberInstance().format(this) From 1ef9852a1fb0d2ca7cdaec8eeb89a389e6586f54 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Fri, 29 Nov 2024 04:23:05 +0000 Subject: [PATCH 72/79] refactor DataConnectTaskBase --- .../gradle/tasks/DataConnectTaskBase.kt | 36 ++++++++++- ...loadNodeJsBinaryDistributionArchiveTask.kt | 60 +++++++++++-------- .../gradle/tasks/ExtractArchiveTask.kt | 47 +++++++++------ .../util/DataConnectGradleLoggerProvider.kt | 46 -------------- 4 files changed, 96 insertions(+), 93 deletions(-) delete mode 100644 dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/util/DataConnectGradleLoggerProvider.kt diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DataConnectTaskBase.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DataConnectTaskBase.kt index 4e42da27d0..79a8ca97ee 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DataConnectTaskBase.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DataConnectTaskBase.kt @@ -16,11 +16,15 @@ package com.google.firebase.example.dataconnect.gradle.tasks +import com.google.firebase.example.dataconnect.gradle.DataConnectGradleException +import com.google.firebase.example.dataconnect.gradle.tasks.DataConnectTaskBase.Worker import com.google.firebase.example.dataconnect.gradle.util.DataConnectGradleLogger import kotlin.reflect.full.allSupertypes import org.gradle.api.DefaultTask +import org.gradle.api.file.FileSystemOperations import org.gradle.api.tasks.Internal import org.gradle.api.tasks.TaskAction +import java.io.File public abstract class DataConnectTaskBase(loggerIdPrefix: String) : DefaultTask() { @@ -40,7 +44,7 @@ public abstract class DataConnectTaskBase(loggerIdPrefix: String) : DefaultTask( @TaskAction public fun run() { dataConnectLogger.info { "Task $path starting execution" } - runCatching { doRun() }.fold( + runCatching { newWorker().invoke() }.fold( onSuccess = { dataConnectLogger.info { "Task $path execution completed successfully" } }, @@ -52,7 +56,33 @@ public abstract class DataConnectTaskBase(loggerIdPrefix: String) : DefaultTask( } /** - * Called by [run] to actually do the work of this task. + * Creates and returns a new [Worker] object, which will be called by + * [run] to actually perform this task's work. */ - protected abstract fun doRun() + protected abstract fun newWorker(): Worker + + public interface Worker : () -> Unit { + public val logger: DataConnectGradleLogger + } +} + +internal fun Worker.deleteDirectory(dir: File, fileSystemOperations: FileSystemOperations) { + logger.info { "Deleting directory: $dir" } + fileSystemOperations.runCatching { delete { delete(dir) } }.onFailure { + throw DataConnectGradleException( + "unable to delete directory: ${dir.absolutePath}: $it " + + "(error code 6trngh6x47)", + it + ) + } +} + +internal fun Worker.createDirectory(dir: File) { + logger.info { "Creating directory: $dir" } + if (!dir.mkdirs()) { + throw DataConnectGradleException( + "unable to create directory: ${dir.absolutePath} " + + "(error code j7x4sw7w95)" + ) + } } diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsBinaryDistributionArchiveTask.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsBinaryDistributionArchiveTask.kt index 7409cf33a9..584460a657 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsBinaryDistributionArchiveTask.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsBinaryDistributionArchiveTask.kt @@ -20,14 +20,12 @@ import com.google.firebase.example.dataconnect.gradle.DataConnectGradleException import com.google.firebase.example.dataconnect.gradle.providers.MyProjectProviders import com.google.firebase.example.dataconnect.gradle.providers.OperatingSystem import com.google.firebase.example.dataconnect.gradle.tasks.DownloadNodeJsBinaryDistributionArchiveTask.Inputs +import com.google.firebase.example.dataconnect.gradle.tasks.DownloadNodeJsBinaryDistributionArchiveTask.Worker import com.google.firebase.example.dataconnect.gradle.util.DataConnectGradleLogger -import com.google.firebase.example.dataconnect.gradle.util.DataConnectGradleLoggerProvider import com.google.firebase.example.dataconnect.gradle.util.FileDownloader import com.google.firebase.example.dataconnect.gradle.util.Sha256SignatureVerifier import com.google.firebase.example.dataconnect.gradle.util.addCertificatesFromKeyListResource import com.google.firebase.example.dataconnect.gradle.util.addHashesFromShasumsFile -import com.google.firebase.example.dataconnect.gradle.util.createDirectory -import com.google.firebase.example.dataconnect.gradle.util.deleteDirectory import java.io.File import javax.inject.Inject import kotlinx.coroutines.runBlocking @@ -88,10 +86,9 @@ public abstract class DownloadNodeJsBinaryDistributionArchiveTask : DataConnectT @get:Inject internal abstract val fileSystemOperations: FileSystemOperations - override fun doRun() { + override fun newWorker(): DataConnectTaskBase.Worker { val (operatingSystem: OperatingSystem, nodeJsVersion: String) = inputData.get() - - val nodeJsTarballDownloader = NodeJsTarballDownloader( + return DownloadNodeJsBinaryDistributionArchiveTaskWorkerImpl( operatingSystemType = operatingSystem.type, operatingSystemArchitecture = operatingSystem.architecture, nodeJsVersion = nodeJsVersion, @@ -99,10 +96,6 @@ public abstract class DownloadNodeJsBinaryDistributionArchiveTask : DataConnectT fileSystemOperations = fileSystemOperations, logger = dataConnectLogger ) - - nodeJsTarballDownloader.use { - it.run() - } } /** @@ -118,6 +111,21 @@ public abstract class DownloadNodeJsBinaryDistributionArchiveTask : DataConnectT @get:Input val nodeJsVersion: String ) + internal interface Worker : DataConnectTaskBase.Worker, AutoCloseable { + val operatingSystemType: OperatingSystem.Type + val operatingSystemArchitecture: OperatingSystem.Architecture + val nodeJsVersion: String + val outputDirectory: File + val fileSystemOperations: FileSystemOperations + val nodeJsPaths: NodeJsPaths + val fileDownloader: FileDownloader + + override fun close() { + fileDownloader.close() + } + } + + private companion object { const val LOGGER_ID_PREFIX = "dnb" } @@ -151,25 +159,25 @@ private fun Inputs.calculateNodeJsPaths(): NodeJsPaths = NodeJsPaths.from( operatingSystem.architecture ) -private class NodeJsTarballDownloader( - val operatingSystemType: OperatingSystem.Type, - val operatingSystemArchitecture: OperatingSystem.Architecture, - val nodeJsVersion: String, - val outputDirectory: File, - val fileSystemOperations: FileSystemOperations, +private class DownloadNodeJsBinaryDistributionArchiveTaskWorkerImpl( + override val operatingSystemType: OperatingSystem.Type, + override val operatingSystemArchitecture: OperatingSystem.Architecture, + override val nodeJsVersion: String, + override val outputDirectory: File, + override val fileSystemOperations: FileSystemOperations, override val logger: DataConnectGradleLogger -) : DataConnectGradleLoggerProvider, AutoCloseable { - val nodeJsPaths: NodeJsPaths = - NodeJsPaths.from(nodeJsVersion, operatingSystemType, operatingSystemArchitecture) +) : Worker { + override val fileDownloader = FileDownloader(logger) - val fileDownloader: FileDownloader = FileDownloader(logger) + override val nodeJsPaths = + NodeJsPaths.from(nodeJsVersion, operatingSystemType, operatingSystemArchitecture) - override fun close() { - fileDownloader.close() + override fun invoke() { + run() } } -private fun NodeJsTarballDownloader.run() { +private fun Worker.run() { logger.info { "operatingSystemType: $operatingSystemType" } logger.info { "operatingSystemArchitecture: $operatingSystemArchitecture" } logger.info { "nodeJsVersion: $nodeJsVersion" } @@ -183,21 +191,21 @@ private fun NodeJsTarballDownloader.run() { verifyNodeJsReleaseSignature(destFile) } -private fun NodeJsTarballDownloader.downloadNodeJsBinaryArchive(destFile: File) { +private fun Worker.downloadNodeJsBinaryArchive(destFile: File) { val url = nodeJsPaths.downloadUrl runBlocking { fileDownloader.download(url, destFile, maxNumDownloadBytes = 200_000_000) } } -private fun NodeJsTarballDownloader.downloadShasumsFile(destFile: File) { +private fun Worker.downloadShasumsFile(destFile: File) { val url = nodeJsPaths.shasumsUrl runBlocking { fileDownloader.download(url, destFile, maxNumDownloadBytes = 100_000) } } -private fun NodeJsTarballDownloader.verifyNodeJsReleaseSignature(file: File) { +private fun Worker.verifyNodeJsReleaseSignature(file: File) { val shasumsFile = File(outputDirectory, nodeJsPaths.shasumsFileName) downloadShasumsFile(shasumsFile) diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/ExtractArchiveTask.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/ExtractArchiveTask.kt index 6550a75803..cc0baed12d 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/ExtractArchiveTask.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/ExtractArchiveTask.kt @@ -16,13 +16,11 @@ package com.google.firebase.example.dataconnect.gradle.tasks +import com.google.firebase.example.dataconnect.gradle.tasks.ExtractArchiveTask.Worker import com.google.firebase.example.dataconnect.gradle.util.ArchiveSetFileMetadataType import com.google.firebase.example.dataconnect.gradle.util.ArchiveType import com.google.firebase.example.dataconnect.gradle.util.DataConnectGradleLogger -import com.google.firebase.example.dataconnect.gradle.util.DataConnectGradleLoggerProvider import com.google.firebase.example.dataconnect.gradle.util.ExtractArchiveWithDetectionCallbacks -import com.google.firebase.example.dataconnect.gradle.util.createDirectory -import com.google.firebase.example.dataconnect.gradle.util.deleteDirectory import com.google.firebase.example.dataconnect.gradle.util.extractArchive import com.google.firebase.example.dataconnect.gradle.util.toFormattedString import org.gradle.api.file.DirectoryProperty @@ -62,14 +60,17 @@ public abstract class ExtractArchiveTask : DataConnectTaskBase(LOGGER_ID_PREFIX) @get:Inject internal abstract val fileSystemOperations: FileSystemOperations - override fun doRun() { - val archiveExtractor = ArchiveExtractor( + override fun newWorker(): DataConnectTaskBase.Worker = ExtractArchiveTaskWorkerImpl( archiveFile = archiveFile.get().asFile, outputDirectory = outputDirectory.get().asFile, fileSystemOperations = fileSystemOperations, logger = dataConnectLogger ) - archiveExtractor.run() + + internal interface Worker: DataConnectTaskBase.Worker { + val archiveFile: File + val outputDirectory: File + val fileSystemOperations: FileSystemOperations } private companion object { @@ -78,14 +79,18 @@ public abstract class ExtractArchiveTask : DataConnectTaskBase(LOGGER_ID_PREFIX) } -private class ArchiveExtractor( - val archiveFile: File, - val outputDirectory: File, - val fileSystemOperations: FileSystemOperations, +private class ExtractArchiveTaskWorkerImpl( + override val archiveFile: File, + override val outputDirectory: File, + override val fileSystemOperations: FileSystemOperations, override val logger: DataConnectGradleLogger -) : DataConnectGradleLoggerProvider +) : Worker { + override fun invoke() { + run() + } +} -private fun ArchiveExtractor.run() { +private fun Worker.run() { logger.info { "archiveFile: ${archiveFile.absolutePath}" } logger.info { "outputDirectory: ${outputDirectory.absolutePath}" } @@ -95,9 +100,13 @@ private fun ArchiveExtractor.run() { logger.info { "Extracting ${archiveFile.absolutePath} to ${outputDirectory.absolutePath}" } val extractCallbacks = ExtractArchiveCallbacksImpl(archiveFile, logger) archiveFile.extractArchive(outputDirectory, extractCallbacks) + logger.info { - "Extracted ${extractCallbacks.extractedFileCount.toFormattedString()} files " + - "(${extractCallbacks.extractedByteCount.toFormattedString()} bytes) " + + val fileCountStr = extractCallbacks.extractedFileCount.toFormattedString() + val symlinkCountStr = extractCallbacks.extractedSymlinkCount.toFormattedString() + val byteCountStr = extractCallbacks.extractedByteCount.toFormattedString() + "Extracted $fileCountStr files ($byteCountStr bytes) " + + "and $symlinkCountStr symlinks " + "from ${archiveFile.absolutePath} to ${outputDirectory.absolutePath}" } } @@ -108,6 +117,9 @@ private class ExtractArchiveCallbacksImpl(private val file: File, private val lo private val _extractedFileCount = AtomicLong(0) val extractedFileCount: Long get() = _extractedFileCount.get() + private val _extractedSymlinkCount = AtomicLong(0) + val extractedSymlinkCount: Long get() = _extractedSymlinkCount.get() + private val _extractedByteCount = AtomicLong(0) val extractedByteCount: Long get() = _extractedByteCount.get() @@ -123,19 +135,18 @@ private class ExtractArchiveCallbacksImpl(private val file: File, private val lo override fun onExtractFileDone(srcPath: String, destFile: File, extractedByteCount: Long) { _extractedByteCount.addAndGet(extractedByteCount) logger.debug { - "Extracted ${extractedByteCount.toFormattedString()} bytes " + - "from $srcPath to ${destFile.absolutePath}" + "Extracted ${extractedByteCount.toFormattedString()} bytes " + "from $srcPath to ${destFile.absolutePath}" } } override fun onExtractSymlink(linkPath: String, destFile: File) { + _extractedFileCount.incrementAndGet() logger.debug { "Creating symlink ${destFile.absolutePath} to $linkPath" } } override fun onSetFileMetadataFailed(file: File, metadataType: ArchiveSetFileMetadataType, exception: Exception) { logger.debug { - "Ignoring failure to set ${metadataType.name} " + - "on extracted file: ${file.absolutePath}" + "Ignoring failure to set ${metadataType.name} " + "on extracted file: ${file.absolutePath}" } } diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/util/DataConnectGradleLoggerProvider.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/util/DataConnectGradleLoggerProvider.kt deleted file mode 100644 index 9b4051c2ad..0000000000 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/util/DataConnectGradleLoggerProvider.kt +++ /dev/null @@ -1,46 +0,0 @@ -/* - * 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.example.dataconnect.gradle.util - -import com.google.firebase.example.dataconnect.gradle.DataConnectGradleException -import java.io.File -import org.gradle.api.file.FileSystemOperations - -internal interface DataConnectGradleLoggerProvider { - val logger: DataConnectGradleLogger -} - -internal fun DataConnectGradleLoggerProvider.deleteDirectory(dir: File, fileSystemOperations: FileSystemOperations) { - logger.info { "Deleting directory: $dir" } - fileSystemOperations.runCatching { delete { delete(dir) } }.onFailure { - throw DataConnectGradleException( - "unable to delete directory: ${dir.absolutePath}: $it " + - "(error code 6trngh6x47)", - it - ) - } -} - -internal fun DataConnectGradleLoggerProvider.createDirectory(dir: File) { - logger.info { "Creating directory: $dir" } - if (!dir.mkdirs()) { - throw DataConnectGradleException( - "unable to create directory: ${dir.absolutePath} " + - "(error code j7x4sw7w95)" - ) - } -} From d843aeb560f1841e1ab0d2b52235fc3a43f2c714 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Fri, 29 Nov 2024 04:35:36 +0000 Subject: [PATCH 73/79] pathPrefixComponentStripCount impl started --- .../gradle/tasks/ExtractArchiveTask.kt | 37 ++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/ExtractArchiveTask.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/ExtractArchiveTask.kt index cc0baed12d..5b427b3fb7 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/ExtractArchiveTask.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/ExtractArchiveTask.kt @@ -27,7 +27,9 @@ import org.gradle.api.file.DirectoryProperty import org.gradle.api.file.FileSystemOperations import org.gradle.api.file.RegularFileProperty import org.gradle.api.provider.Property +import org.gradle.api.tasks.Input import org.gradle.api.tasks.InputFile +import org.gradle.api.tasks.Optional import org.gradle.api.tasks.OutputDirectory import org.gradle.work.DisableCachingByDefault import java.io.File @@ -46,6 +48,28 @@ public abstract class ExtractArchiveTask : DataConnectTaskBase(LOGGER_ID_PREFIX) @get:InputFile public abstract val archiveFile: RegularFileProperty + /** + * The number of path components to strip from the paths of files extracted from the archive. + * + * If this value is greater than zero, then the paths of the extracted files are written into + * the destination directory without that number of path prefixes stripped. For example, if this + * property's value is 2 and a file named `aaa/bbb/ccc/ddd/file.txt` was being extracted from + * the archive into directory `/home/foo` then it would be written to + * `/home/foo/ccc/ddd/file.txt` (with the 2 path prefix components `aaa/bbb/` removed) instead + * of `/home/foo/aaa/bbb/ccc/ddd/file.txt`. + * + * Setting this property to a positive value can be useful in the cases where the archive is + * known to contain exactly one top-level directory that is superfluous in the extracted + * directory structure. + * + * This property is _optional_; if this property is not set, that is, [Property.isPresent] + * returns `false`, then a value of `0` (zero) will be used. + * + * This property's value _must_ be greater than or equal to zero. + */ + @get:Input @get:Optional + public abstract val pathPrefixComponentStripCount: Property + /** * The directory into which to extract the archive. * @@ -63,6 +87,7 @@ public abstract class ExtractArchiveTask : DataConnectTaskBase(LOGGER_ID_PREFIX) override fun newWorker(): DataConnectTaskBase.Worker = ExtractArchiveTaskWorkerImpl( archiveFile = archiveFile.get().asFile, outputDirectory = outputDirectory.get().asFile, + prefixStripCount = pathPrefixComponentStripCount.orNull ?: 0, fileSystemOperations = fileSystemOperations, logger = dataConnectLogger ) @@ -70,6 +95,7 @@ public abstract class ExtractArchiveTask : DataConnectTaskBase(LOGGER_ID_PREFIX) internal interface Worker: DataConnectTaskBase.Worker { val archiveFile: File val outputDirectory: File + val prefixStripCount: Int val fileSystemOperations: FileSystemOperations } @@ -82,6 +108,7 @@ public abstract class ExtractArchiveTask : DataConnectTaskBase(LOGGER_ID_PREFIX) private class ExtractArchiveTaskWorkerImpl( override val archiveFile: File, override val outputDirectory: File, + override val prefixStripCount: Int, override val fileSystemOperations: FileSystemOperations, override val logger: DataConnectGradleLogger ) : Worker { @@ -93,11 +120,19 @@ private class ExtractArchiveTaskWorkerImpl( private fun Worker.run() { logger.info { "archiveFile: ${archiveFile.absolutePath}" } logger.info { "outputDirectory: ${outputDirectory.absolutePath}" } + logger.info { "prefixStripCount: $prefixStripCount" } + logger.info { "Extracting ${archiveFile.absolutePath} to ${outputDirectory.absolutePath}" } + + if (prefixStripCount < 0) { + throw IllegalArgumentException("invalid prefixStripCount: $prefixStripCount " + + "(must be greater than or equal to zero) (error code mn8pp2b7mc)") + } + + zzyzx("TODO: implement prefixStripCount 72mt5e9nt7") deleteDirectory(outputDirectory, fileSystemOperations) createDirectory(outputDirectory) - logger.info { "Extracting ${archiveFile.absolutePath} to ${outputDirectory.absolutePath}" } val extractCallbacks = ExtractArchiveCallbacksImpl(archiveFile, logger) archiveFile.extractArchive(outputDirectory, extractCallbacks) From fdb816cd15bd014ce2c400c3d7e02e524d0493d9 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Fri, 29 Nov 2024 07:55:18 +0000 Subject: [PATCH 74/79] strip prefix --- dataconnect/buildSrc/build.gradle.kts | 1 + .../gradle/DataConnectGradlePlugin.kt | 1 + .../gradle/tasks/ExtractArchiveTask.kt | 13 +- .../gradle/util/ArchiveExtraction.kt | 135 ++++++++++++++---- 4 files changed, 114 insertions(+), 36 deletions(-) diff --git a/dataconnect/buildSrc/build.gradle.kts b/dataconnect/buildSrc/build.gradle.kts index e745a5251f..102422d923 100644 --- a/dataconnect/buildSrc/build.gradle.kts +++ b/dataconnect/buildSrc/build.gradle.kts @@ -42,6 +42,7 @@ dependencies { implementation("org.bouncycastle:bcprov-jdk18on:1.78.1") implementation("org.apache.commons:commons-compress:1.27.1") + // Needed for org.apache.commons.compress.compressors.xz.XZCompressorInputStream runtimeOnly("org.tukaani:xz:1.10") } diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectGradlePlugin.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectGradlePlugin.kt index 6d7ee7caf4..2d0800c50b 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectGradlePlugin.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectGradlePlugin.kt @@ -57,6 +57,7 @@ public abstract class DataConnectGradlePlugin : Plugin { ) outputDirectory.set(providers.buildDirectory.map { it.dir("extracted") }) + pathPrefixComponentStripCount.set(1) } } diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/ExtractArchiveTask.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/ExtractArchiveTask.kt index 5b427b3fb7..1ceecc9008 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/ExtractArchiveTask.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/ExtractArchiveTask.kt @@ -20,7 +20,7 @@ import com.google.firebase.example.dataconnect.gradle.tasks.ExtractArchiveTask.W import com.google.firebase.example.dataconnect.gradle.util.ArchiveSetFileMetadataType import com.google.firebase.example.dataconnect.gradle.util.ArchiveType import com.google.firebase.example.dataconnect.gradle.util.DataConnectGradleLogger -import com.google.firebase.example.dataconnect.gradle.util.ExtractArchiveWithDetectionCallbacks +import com.google.firebase.example.dataconnect.gradle.util.ExtractArchiveCallbacks import com.google.firebase.example.dataconnect.gradle.util.extractArchive import com.google.firebase.example.dataconnect.gradle.util.toFormattedString import org.gradle.api.file.DirectoryProperty @@ -128,13 +128,14 @@ private fun Worker.run() { "(must be greater than or equal to zero) (error code mn8pp2b7mc)") } - zzyzx("TODO: implement prefixStripCount 72mt5e9nt7") - deleteDirectory(outputDirectory, fileSystemOperations) createDirectory(outputDirectory) val extractCallbacks = ExtractArchiveCallbacksImpl(archiveFile, logger) - archiveFile.extractArchive(outputDirectory, extractCallbacks) + archiveFile.extractArchive(outputDirectory) { + callbacks = extractCallbacks + prefixStripCount = this@run.prefixStripCount + } logger.info { val fileCountStr = extractCallbacks.extractedFileCount.toFormattedString() @@ -147,7 +148,7 @@ private fun Worker.run() { } private class ExtractArchiveCallbacksImpl(private val file: File, private val logger: DataConnectGradleLogger) : - ExtractArchiveWithDetectionCallbacks { + ExtractArchiveCallbacks { private val _extractedFileCount = AtomicLong(0) val extractedFileCount: Long get() = _extractedFileCount.get() @@ -158,7 +159,7 @@ private class ExtractArchiveCallbacksImpl(private val file: File, private val lo private val _extractedByteCount = AtomicLong(0) val extractedByteCount: Long get() = _extractedByteCount.get() - override fun onArchiveTypeDetected(archiveType: ArchiveType) { + override fun onExtractArchiveStarting(archiveType: ArchiveType) { logger.info { "Detected archive type $archiveType for file: ${file.absolutePath}" } } diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/util/ArchiveExtraction.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/util/ArchiveExtraction.kt index 0c37439b26..f209205606 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/util/ArchiveExtraction.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/util/ArchiveExtraction.kt @@ -20,7 +20,6 @@ import org.apache.commons.compress.archivers.ArchiveEntry import org.apache.commons.compress.archivers.ArchiveInputStream import org.apache.commons.compress.archivers.tar.TarArchiveEntry import org.apache.commons.compress.archivers.tar.TarArchiveInputStream -import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream import org.apache.commons.compress.compressors.xz.XZCompressorInputStream import org.apache.commons.compress.compressors.xz.XZUtils import java.io.File @@ -31,13 +30,11 @@ import java.nio.file.Paths import java.nio.file.attribute.FileTime import java.nio.file.attribute.PosixFilePermission -internal fun File.extractArchive(destDir: File, callbacks: ExtractArchiveWithDetectionCallbacks?) { +internal fun File.extractArchive(destDir: File, configure: ExtractArchiveConfigBuilder.() -> Unit = {}) { if (XZUtils.isCompressedFileName(name)) { - callbacks?.onArchiveTypeDetected(ArchiveType.TarXz) - extractTarXzArchive(destDir, callbacks) + extractTarXzArchive(destDir, configure) } else if (name.endsWith(".7z")) { - callbacks?.onArchiveTypeDetected(ArchiveType.SevenZip) - extract7zArchive(destDir, callbacks) + extract7zArchive(destDir, configure) } else { throw UnsupportedArchiveException( "don't know how to extract $name; " + @@ -48,25 +45,22 @@ internal fun File.extractArchive(destDir: File, callbacks: ExtractArchiveWithDet } @JvmName("extractTarXzArchiveFileExt") -private fun File.extractTarXzArchive(destDir: File, callbacks: ExtractArchiveCallbacks?) { - extractTarXzArchive(file = this, destDir = destDir, callbacks) +private fun File.extractTarXzArchive(destDir: File, configure: ExtractArchiveConfigBuilder.() -> Unit) { + val config = ExtractArchiveConfigBuilder().apply(configure).build(destDir) + config.callbacks.onExtractArchiveStarting(ArchiveType.TarXz) + extractTarXzArchive(this, config) } private fun extractTarXzArchive( inputStream: InputStream, - destDir: File, - callbacks: ExtractArchiveCallbacks? + config: ExtractArchiveConfig ) { XZCompressorInputStream(inputStream).use { xzInputStream -> - extractTarArchive(xzInputStream, destDir, callbacks) + extractTarArchive(xzInputStream, config) } } -private fun extractTarArchive( - inputStream: InputStream, - destDir: File, - callbacks: ExtractArchiveCallbacks? -) { +private fun extractTarArchive(inputStream: InputStream, config: ExtractArchiveConfig) { // Use `ArchiveInputStream` as the static type of `tarInputStream`, rather than the more // natural `TarArchiveInputStream` because this enables compatibility with older versions of // the Apache Commons Compress library. This is because at one point `ArchiveInputStream` was @@ -83,23 +77,27 @@ private fun extractTarArchive( if (tarEntry !is TarArchiveEntry) { continue } - val outputFile = File(destDir, tarEntry.name).absoluteFile + + val outputFile = config.destDir + .childWithPathPrefixComponentsStripped(tarEntry.name, config.prefixStripCount) + .absoluteFile + if (tarEntry.isSymbolicLink) { - callbacks?.onExtractSymlink(tarEntry.linkName, outputFile) + config.callbacks.onExtractSymlink(tarEntry.linkName, outputFile) Files.createSymbolicLink(outputFile.toPath(), Paths.get(tarEntry.linkName)) } else if (tarEntry.isFile) { - callbacks?.onExtractFileStarting(tarEntry.name, outputFile) + config.callbacks.onExtractFileStarting(tarEntry.name, outputFile) outputFile.parentFile.mkdirs() val extractedByteCount = outputFile.outputStream().use { fileOutputStream -> tarInputStream.copyTo(fileOutputStream) } - callbacks?.onExtractFileDone(tarEntry.name, outputFile, extractedByteCount) + config.callbacks.onExtractFileDone(tarEntry.name, outputFile, extractedByteCount) val lastModifiedTime = FileTime.from(tarEntry.lastModifiedDate.toInstant()) try { Files.setLastModifiedTime(outputFile.toPath(), lastModifiedTime) } catch (e: IOException) { - callbacks?.onSetFileMetadataFailed(outputFile, ArchiveSetFileMetadataType.LastModifiedTime, e) + config.callbacks.onSetFileMetadataFailed(outputFile, ArchiveSetFileMetadataType.LastModifiedTime, e) } val newPermissions = buildSet { @@ -119,7 +117,11 @@ private fun extractTarArchive( try { Files.setPosixFilePermissions(outputFile.toPath(), newPermissions) } catch (e: UnsupportedOperationException) { - callbacks?.onSetFileMetadataFailed(outputFile, ArchiveSetFileMetadataType.PosixFilePermissions, e) + config.callbacks.onSetFileMetadataFailed( + outputFile, + ArchiveSetFileMetadataType.PosixFilePermissions, + e + ) } } else { continue @@ -129,25 +131,45 @@ private fun extractTarArchive( } } -private fun extractTarXzArchive(file: File, destDir: File, callbacks: ExtractArchiveCallbacks?) { +private fun extractTarXzArchive(file: File, config: ExtractArchiveConfig) { file.inputStream().use { fileInputStream -> - extractTarXzArchive(fileInputStream, destDir, callbacks) + extractTarXzArchive(fileInputStream, config) } } @JvmName("extract7zArchiveFileExt") -private fun File.extract7zArchive(destDir: File, callbacks: ExtractArchiveCallbacks?) { - extract7zArchive(file = this, destDir = destDir, callbacks) +private fun File.extract7zArchive(destDir: File, configure: ExtractArchiveConfigBuilder.() -> Unit) { + val config = ExtractArchiveConfigBuilder().apply(configure).build(destDir) + extract7zArchive(this, config) } -private fun extract7zArchive(file: File, destDir: File, callbacks: ExtractArchiveCallbacks?) { +private fun extract7zArchive(file: File, config: ExtractArchiveConfig) { TODO("extract7zArchive() not yet implemented") } private class UnsupportedArchiveException(message: String) : Exception(message) +internal class ExtractArchiveConfigBuilder { + var callbacks: ExtractArchiveCallbacks? = null + var prefixStripCount: Int? = null +} + +private class ExtractArchiveConfig( + val destDir: File, + val callbacks: ExtractArchiveCallbacks, + val prefixStripCount: Int +) + +private fun ExtractArchiveConfigBuilder.build(destDir: File) = ExtractArchiveConfig( + destDir = destDir, + callbacks = callbacks ?: ExtractArchiveCallbacksStubImpl, + prefixStripCount = prefixStripCount ?: 0 +) + internal interface ExtractArchiveCallbacks { + fun onExtractArchiveStarting(archiveType: ArchiveType) + fun onExtractFileStarting(srcPath: String, destFile: File) fun onExtractFileDone(srcPath: String, destFile: File, extractedByteCount: Long) @@ -157,10 +179,21 @@ internal interface ExtractArchiveCallbacks { fun onSetFileMetadataFailed(file: File, metadataType: ArchiveSetFileMetadataType, exception: Exception) } -internal interface ExtractArchiveWithDetectionCallbacks : ExtractArchiveCallbacks { +private object ExtractArchiveCallbacksStubImpl : ExtractArchiveCallbacks { + override fun onExtractArchiveStarting(archiveType: ArchiveType) { + } + + override fun onExtractFileStarting(srcPath: String, destFile: File) { + } + + override fun onExtractFileDone(srcPath: String, destFile: File, extractedByteCount: Long) { + } - fun onArchiveTypeDetected(archiveType: ArchiveType) + override fun onExtractSymlink(linkPath: String, destFile: File) { + } + override fun onSetFileMetadataFailed(file: File, metadataType: ArchiveSetFileMetadataType, exception: Exception) { + } } internal enum class ArchiveType { @@ -171,4 +204,46 @@ internal enum class ArchiveType { internal enum class ArchiveSetFileMetadataType { LastModifiedTime, PosixFilePermissions, -} \ No newline at end of file +} + +@JvmName("childWithPathPrefixComponentsStrippedFileExt") +private fun File.childWithPathPrefixComponentsStripped(childPath: String, pathPrefixStripCount: Int): File = + childWithPathPrefixComponentsStripped(this, childPath, pathPrefixStripCount) + +private fun childWithPathPrefixComponentsStripped(file: File, childPath: String, pathPrefixStripCount: Int): File { + require(pathPrefixStripCount >= 0) { "invalid pathPrefixStripCount: $pathPrefixStripCount" } + val pathSeparators = charArrayOf('\\', '/') + + if (pathSeparators.any { childPath.startsWith(it) }) { + throw InvalidArchivePathException( + "unable to extract file: $childPath " + + "(must not be an absolute path)" + + "(error code t827vf36ac)" + ) + } + + val newChildPath = buildString { + append(childPath) + repeat(pathPrefixStripCount) { prefixComponentIndex -> + val index = indexOfAny(pathSeparators) + if (index < 0) { + throw MissingPathPrefixException( + "the given childPath was expected to have at least " + + "$pathPrefixStripCount leading path components, " + + "but there were only $prefixComponentIndex " + + "(error code radpfa8nc9)" + ) + } + deleteRange(0, index + 1) + while (isNotEmpty() && first() in pathSeparators) { + deleteAt(0) + } + } + } + + return File(file, newChildPath) +} + +private class InvalidArchivePathException(message: String) : Exception(message) + +private class MissingPathPrefixException(message: String) : Exception(message) \ No newline at end of file From e830d638392804fadf0b78824e288fe9b4e0306c Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Fri, 29 Nov 2024 08:46:59 +0000 Subject: [PATCH 75/79] more work --- .../gradle/DataConnectGradlePlugin.kt | 16 +-- .../gradle/tasks/DataConnectTaskBase.kt | 26 +++- ...loadNodeJsBinaryDistributionArchiveTask.kt | 116 +++++++++--------- .../gradle/tasks/ExtractArchiveTask.kt | 4 +- .../dataconnect/gradle/tasks/NodeJsPaths.kt | 62 +++++----- 5 files changed, 116 insertions(+), 108 deletions(-) diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectGradlePlugin.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectGradlePlugin.kt index 2d0800c50b..33c1dae94f 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectGradlePlugin.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectGradlePlugin.kt @@ -47,17 +47,13 @@ public abstract class DataConnectGradlePlugin : Plugin { project.tasks.register("ex") { group = TASK_GROUP - archiveFile.set( - dlTask.flatMap { task -> - task.outputDirectory.map { outputDirectory -> - val downloadFileName = providers.nodeJsPaths.get().downloadFileName - outputDirectory.file(downloadFileName) - } - } - ) - - outputDirectory.set(providers.buildDirectory.map { it.dir("extracted") }) pathPrefixComponentStripCount.set(1) + archiveFile.set(dlTask.flatMap { it.archiveFile }) + + outputDirectory.set(providers.buildDirectory.map { + val dirName = providers.nodeJsPaths.get().archiveBaseFileName + it.dir(dirName) + }) } } diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DataConnectTaskBase.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DataConnectTaskBase.kt index 79a8ca97ee..ff4d01c322 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DataConnectTaskBase.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DataConnectTaskBase.kt @@ -25,6 +25,7 @@ import org.gradle.api.file.FileSystemOperations import org.gradle.api.tasks.Internal import org.gradle.api.tasks.TaskAction import java.io.File +import java.nio.file.Files public abstract class DataConnectTaskBase(loggerIdPrefix: String) : DefaultTask() { @@ -68,7 +69,9 @@ public abstract class DataConnectTaskBase(loggerIdPrefix: String) : DefaultTask( internal fun Worker.deleteDirectory(dir: File, fileSystemOperations: FileSystemOperations) { logger.info { "Deleting directory: $dir" } - fileSystemOperations.runCatching { delete { delete(dir) } }.onFailure { + val result = fileSystemOperations.runCatching { delete { delete(dir) } } + + result.onFailure { throw DataConnectGradleException( "unable to delete directory: ${dir.absolutePath}: $it " + "(error code 6trngh6x47)", @@ -77,12 +80,27 @@ internal fun Worker.deleteDirectory(dir: File, fileSystemOperations: FileSystemO } } +internal fun Worker.deleteFile(file: File) { + logger.info { "Deleting file: ${file.absolutePath}" } + val result = kotlin.runCatching { Files.deleteIfExists(file.toPath()) } + + result.onFailure { + throw DataConnectGradleException( + "unable to delete file: ${file.absolutePath}: $it " + + "(error code rprr987jqk)", + it + ) + } +} + internal fun Worker.createDirectory(dir: File) { logger.info { "Creating directory: $dir" } - if (!dir.mkdirs()) { + + val result = runCatching { Files.createDirectories(dir.toPath()) } + result.onFailure { throw DataConnectGradleException( - "unable to create directory: ${dir.absolutePath} " + - "(error code j7x4sw7w95)" + "unable to create directory: ${dir.absolutePath}: $it " + + "(error code j7x4sw7w95)", it ) } } diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsBinaryDistributionArchiveTask.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsBinaryDistributionArchiveTask.kt index 584460a657..160cd968e5 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsBinaryDistributionArchiveTask.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsBinaryDistributionArchiveTask.kt @@ -30,16 +30,15 @@ import java.io.File import javax.inject.Inject import kotlinx.coroutines.runBlocking import kotlinx.serialization.Serializable -import org.gradle.api.file.DirectoryProperty import org.gradle.api.file.FileSystemOperations -import org.gradle.api.file.RegularFile +import org.gradle.api.file.RegularFileProperty import org.gradle.api.provider.Property import org.gradle.api.provider.ProviderFactory import org.gradle.api.tasks.CacheableTask import org.gradle.api.tasks.Input -import org.gradle.api.tasks.Internal import org.gradle.api.tasks.Nested -import org.gradle.api.tasks.OutputDirectory +import org.gradle.api.tasks.OutputFile +import java.nio.file.Files @CacheableTask public abstract class DownloadNodeJsBinaryDistributionArchiveTask : DataConnectTaskBase(LOGGER_ID_PREFIX) { @@ -54,31 +53,22 @@ public abstract class DownloadNodeJsBinaryDistributionArchiveTask : DataConnectT public abstract val inputData: Property /** - * The directory into which to place the downloaded artifact(s). + * The file to which to download the Node.js binary distribution archive. * * This property is _required_, meaning that it must be set; that is, [Property.isPresent] must * return `true`. - * - * This directory will be deleted and re-created when this task is executed. */ - @get:OutputDirectory - public abstract val outputDirectory: DirectoryProperty + @get:OutputFile + public abstract val archiveFile: RegularFileProperty /** - * The path of the downloaded Node.js binary distribution archive. - * - * This property's value is computed from [inputData] and [outputDirectory]. + * The file to which to download the "SHASUMS256.txt.asc" file. * - * This property must not be accessed until after the task executes. + * This property is _required_, meaning that it must be set; that is, [Property.isPresent] must + * return `true`. */ - @get:Internal - public val downloadedFile: RegularFile - get() { - val inputs = inputData.get() - val outputDirectory = outputDirectory.get() - val downloadedFileName = inputs.calculateNodeJsPaths().downloadFileName - return outputDirectory.file(downloadedFileName) - } + @get:OutputFile + public abstract val shasumsFile: RegularFileProperty @get:Inject internal abstract val providerFactory: ProviderFactory @@ -87,12 +77,17 @@ public abstract class DownloadNodeJsBinaryDistributionArchiveTask : DataConnectT internal abstract val fileSystemOperations: FileSystemOperations override fun newWorker(): DataConnectTaskBase.Worker { - val (operatingSystem: OperatingSystem, nodeJsVersion: String) = inputData.get() + val inputData = inputData.get() + val nodeJsPaths = inputData.calculateNodeJsPaths() + return DownloadNodeJsBinaryDistributionArchiveTaskWorkerImpl( - operatingSystemType = operatingSystem.type, - operatingSystemArchitecture = operatingSystem.architecture, - nodeJsVersion = nodeJsVersion, - outputDirectory = outputDirectory.get().asFile, + operatingSystemType = inputData.operatingSystem.type, + operatingSystemArchitecture = inputData.operatingSystem.architecture, + nodeJsVersion = inputData.nodeJsVersion, + archiveFile = archiveFile.get().asFile, + shasumsFile = shasumsFile.get().asFile, + archiveUrl = nodeJsPaths.archiveUrl, + shasumsUrl = nodeJsPaths.shasumsUrl, fileSystemOperations = fileSystemOperations, logger = dataConnectLogger ) @@ -115,9 +110,11 @@ public abstract class DownloadNodeJsBinaryDistributionArchiveTask : DataConnectT val operatingSystemType: OperatingSystem.Type val operatingSystemArchitecture: OperatingSystem.Architecture val nodeJsVersion: String - val outputDirectory: File + val archiveFile: File + val shasumsFile: File + val archiveUrl: String + val shasumsUrl: String val fileSystemOperations: FileSystemOperations - val nodeJsPaths: NodeJsPaths val fileDownloader: FileDownloader override fun close() { @@ -143,9 +140,20 @@ internal fun DownloadNodeJsBinaryDistributionArchiveTask.configureFrom(myProvide ) } - outputDirectory.run { + archiveFile.run { + finalizeValueOnRead() + set(myProviders.buildDirectory.map { + val downloadFileName = inputData.get().calculateNodeJsPaths().archiveFileName + it.file(downloadFileName) + }) + } + + shasumsFile.run { finalizeValueOnRead() - set(myProviders.buildDirectory.map { it.dir("DownloadNodeJsBinaryDistributionArchive") }) + set(myProviders.buildDirectory.map { + val shasumsFileName = inputData.get().calculateNodeJsPaths().shasumsFileName + it.file(shasumsFileName) + }) } setOnlyIf("inputData was specified", { @@ -163,15 +171,15 @@ private class DownloadNodeJsBinaryDistributionArchiveTaskWorkerImpl( override val operatingSystemType: OperatingSystem.Type, override val operatingSystemArchitecture: OperatingSystem.Architecture, override val nodeJsVersion: String, - override val outputDirectory: File, + override val archiveFile: File, + override val shasumsFile: File, + override val archiveUrl: String, + override val shasumsUrl: String, override val fileSystemOperations: FileSystemOperations, override val logger: DataConnectGradleLogger ) : Worker { override val fileDownloader = FileDownloader(logger) - override val nodeJsPaths = - NodeJsPaths.from(nodeJsVersion, operatingSystemType, operatingSystemArchitecture) - override fun invoke() { run() } @@ -181,41 +189,30 @@ private fun Worker.run() { logger.info { "operatingSystemType: $operatingSystemType" } logger.info { "operatingSystemArchitecture: $operatingSystemArchitecture" } logger.info { "nodeJsVersion: $nodeJsVersion" } - logger.info { "outputDirectory: $outputDirectory" } - - deleteDirectory(outputDirectory, fileSystemOperations) - createDirectory(outputDirectory) + logger.info { "archiveFile: ${archiveFile.absolutePath}" } + logger.info { "shasumsFile: ${shasumsFile.absolutePath}" } - val destFile = File(outputDirectory, nodeJsPaths.downloadFileName) - downloadNodeJsBinaryArchive(destFile) - verifyNodeJsReleaseSignature(destFile) -} + val files = listOf(archiveFile, shasumsFile).sorted() + files.forEach { deleteFile(it) } + val directories = files.map { it.absoluteFile.parentFile }.distinct().sorted() + directories.forEach { createDirectory(it) } -private fun Worker.downloadNodeJsBinaryArchive(destFile: File) { - val url = nodeJsPaths.downloadUrl runBlocking { - fileDownloader.download(url, destFile, maxNumDownloadBytes = 200_000_000) + fileDownloader.download(archiveUrl, archiveFile, maxNumDownloadBytes = 200_000_000) + fileDownloader.download(shasumsUrl, shasumsFile, maxNumDownloadBytes = 100_000) } -} -private fun Worker.downloadShasumsFile(destFile: File) { - val url = nodeJsPaths.shasumsUrl - runBlocking { - fileDownloader.download(url, destFile, maxNumDownloadBytes = 100_000) - } + verifyNodeJsReleaseSignature(file =archiveFile, shasumsFile =shasumsFile, keyListResourcePath="com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/keys.list", logger) } -private fun Worker.verifyNodeJsReleaseSignature(file: File) { - val shasumsFile = File(outputDirectory, nodeJsPaths.shasumsFileName) - downloadShasumsFile(shasumsFile) - +private fun verifyNodeJsReleaseSignature(file: File, shasumsFile: File, keyListResourcePath: String, logger: DataConnectGradleLogger) { val signatureVerifier = Sha256SignatureVerifier() logger.info { "Loading Node.js release signing certificates " + - "from resource: $KEY_LIST_RESOURCE_PATH" + "from resource: $keyListResourcePath" } - val numCertificatesAdded = signatureVerifier.addCertificatesFromKeyListResource(KEY_LIST_RESOURCE_PATH) - logger.info { "Loaded $numCertificatesAdded certificates from $KEY_LIST_RESOURCE_PATH" } + val numCertificatesAdded = signatureVerifier.addCertificatesFromKeyListResource(keyListResourcePath) + logger.info { "Loaded $numCertificatesAdded certificates from $keyListResourcePath" } logger.info { "Loading SHA256 hashes from file: ${shasumsFile.absolutePath}" } val fileNamesWithLoadedHash = signatureVerifier.addHashesFromShasumsFile(shasumsFile) @@ -237,6 +234,3 @@ private fun Worker.verifyNodeJsReleaseSignature(file: File) { signatureVerifier.verifyHash(inputStream, file.name) } } - -private const val KEY_LIST_RESOURCE_PATH = - "com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/keys.list" diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/ExtractArchiveTask.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/ExtractArchiveTask.kt index 1ceecc9008..0ded06826d 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/ExtractArchiveTask.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/ExtractArchiveTask.kt @@ -121,7 +121,6 @@ private fun Worker.run() { logger.info { "archiveFile: ${archiveFile.absolutePath}" } logger.info { "outputDirectory: ${outputDirectory.absolutePath}" } logger.info { "prefixStripCount: $prefixStripCount" } - logger.info { "Extracting ${archiveFile.absolutePath} to ${outputDirectory.absolutePath}" } if (prefixStripCount < 0) { throw IllegalArgumentException("invalid prefixStripCount: $prefixStripCount " + @@ -131,6 +130,7 @@ private fun Worker.run() { deleteDirectory(outputDirectory, fileSystemOperations) createDirectory(outputDirectory) + logger.info { "Extracting ${archiveFile.absolutePath} to ${outputDirectory.absolutePath}" } val extractCallbacks = ExtractArchiveCallbacksImpl(archiveFile, logger) archiveFile.extractArchive(outputDirectory) { callbacks = extractCallbacks @@ -176,7 +176,7 @@ private class ExtractArchiveCallbacksImpl(private val file: File, private val lo } override fun onExtractSymlink(linkPath: String, destFile: File) { - _extractedFileCount.incrementAndGet() + _extractedSymlinkCount.incrementAndGet() logger.debug { "Creating symlink ${destFile.absolutePath} to $linkPath" } } diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/NodeJsPaths.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/NodeJsPaths.kt index 4b91276481..25d255c9e9 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/NodeJsPaths.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/NodeJsPaths.kt @@ -22,8 +22,9 @@ import org.gradle.api.provider.Provider import org.gradle.api.provider.ProviderFactory public class NodeJsPaths( - public val downloadUrl: String, - public val downloadFileName: String, + public val archiveUrl: String, + public val archiveBaseFileName: String, + public val archiveFileName: String, public val shasumsUrl: String, public val shasumsFileName: String ) { @@ -46,10 +47,11 @@ public fun NodeJsPaths.Companion.from( ): NodeJsPaths { val calculator = NodeJsPathsCalculator(nodeJsVersion, operatingSystemType, operatingSystemArchitecture) return NodeJsPaths( - downloadUrl = calculator.downloadUrl(), - downloadFileName = calculator.downloadFileName(), - shasumsUrl = calculator.shasumsDownloadUrl(), - shasumsFileName = calculator.shasumsDownloadFileName() + archiveUrl = calculator.archiveUrl(), + archiveBaseFileName = calculator.archiveBaseFileName(), + archiveFileName = calculator.archiveFileName(), + shasumsUrl = calculator.shasumsUrl(), + shasumsFileName = calculator.shasumsFileName() ) } @@ -59,7 +61,7 @@ private class NodeJsPathsCalculator( val operatingSystemArchitecture: OperatingSystem.Architecture ) -private fun NodeJsPathsCalculator.downloadUrlPrefix(): String = "https://nodejs.org/dist/v$nodeJsVersion" +private fun NodeJsPathsCalculator.urlForFileWithName(fileName: String): String = "https://nodejs.org/dist/v$nodeJsVersion/$fileName" /** * The URL to download the Node.js binary distribution. @@ -74,20 +76,13 @@ private fun NodeJsPathsCalculator.downloadUrlPrefix(): String = "https://nodejs. * * https://nodejs.org/dist/v20.9.0/node-v20.9.0-win-x64.7z * * https://nodejs.org/dist/v20.9.0/node-v20.9.0-win-x86.7z */ -private fun NodeJsPathsCalculator.downloadUrl(): String { - val downloadUrlPrefix = downloadUrlPrefix() - val downloadFileName = downloadFileName() - return "$downloadUrlPrefix/$downloadFileName" -} +private fun NodeJsPathsCalculator.archiveUrl(): String = urlForFileWithName(archiveFileName()) + @Suppress("UnusedReceiverParameter") -private fun NodeJsPathsCalculator.shasumsDownloadFileName(): String = "SHASUMS256.txt.asc" +private fun NodeJsPathsCalculator.shasumsFileName(): String = "SHASUMS256.txt.asc" -private fun NodeJsPathsCalculator.shasumsDownloadUrl(): String { - val downloadUrlPrefix = downloadUrlPrefix() - val downloadFileName = shasumsDownloadFileName() - return "$downloadUrlPrefix/$downloadFileName" -} +private fun NodeJsPathsCalculator.shasumsUrl(): String = urlForFileWithName(shasumsFileName()) /** * The file name of the download for the Node.js binary distribution. @@ -102,22 +97,27 @@ private fun NodeJsPathsCalculator.shasumsDownloadUrl(): String { * * node-v20.9.0-win-x64.7z * * node-v20.9.0-win-x86.7z */ -private fun NodeJsPathsCalculator.downloadFileName(): String { - val fileExtension: String = when (operatingSystemType) { - OperatingSystem.Type.Windows -> "7z" - OperatingSystem.Type.MacOS -> "tar.xz" - OperatingSystem.Type.Linux -> "tar.xz" - else -> throw DataConnectGradleException( - "unable to determine Node.js download file extension " + +private fun NodeJsPathsCalculator.archiveFileName(): String = "${archiveBaseFileName()}${archiveFileNameSuffix()}" + +/** + * The suffix of the file name download for the Node.js binary distribution. + * + * Here are some examples: + * * .tar.xz + * * .7z + */ +private fun NodeJsPathsCalculator.archiveFileNameSuffix(): String = when (operatingSystemType) { + OperatingSystem.Type.Windows -> ".7z" + OperatingSystem.Type.MacOS, + OperatingSystem.Type.Linux -> ".tar.xz" + else -> throw DataConnectGradleException( + "unable to determine Node.js download file name suffix " + "for operating system type: $operatingSystemType " + "(error code ead53smf45)" - ) - } - - val downloadFileNameBase = downloadFileNameBase() - return "$downloadFileNameBase.$fileExtension" + ) } + /** * The base file name of the download for the Node.js binary distribution; * that is, the file name without the ".7z" or ".tar.xz" extension. @@ -132,7 +132,7 @@ private fun NodeJsPathsCalculator.downloadFileName(): String { * * node-v20.9.0-win-x64 * * node-v20.9.0-win-x86 */ -private fun NodeJsPathsCalculator.downloadFileNameBase(): String { +private fun NodeJsPathsCalculator.archiveBaseFileName(): String { val osType: String = when (operatingSystemType) { OperatingSystem.Type.Windows -> "win" OperatingSystem.Type.MacOS -> "darwin" From a9f9cebbfe78e6aa883a06a844e26c0765ff4914 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Fri, 29 Nov 2024 09:04:09 +0000 Subject: [PATCH 76/79] show elapsed time --- .../gradle/tasks/DataConnectTaskBase.kt | 27 ++++++++++++------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DataConnectTaskBase.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DataConnectTaskBase.kt index ff4d01c322..f7948bf7a8 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DataConnectTaskBase.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DataConnectTaskBase.kt @@ -26,6 +26,9 @@ import org.gradle.api.tasks.Internal import org.gradle.api.tasks.TaskAction import java.io.File import java.nio.file.Files +import java.util.Date +import kotlin.time.DurationUnit +import kotlin.time.toDuration public abstract class DataConnectTaskBase(loggerIdPrefix: String) : DefaultTask() { @@ -44,16 +47,20 @@ public abstract class DataConnectTaskBase(loggerIdPrefix: String) : DefaultTask( @TaskAction public fun run() { - dataConnectLogger.info { "Task $path starting execution" } - runCatching { newWorker().invoke() }.fold( - onSuccess = { - dataConnectLogger.info { "Task $path execution completed successfully" } - }, - onFailure = { - dataConnectLogger.warn("Task $path execution failed: $it") - throw it - } - ) + dataConnectLogger.info { "Task $path starting execution at ${Date()}" } + val startTime = System.nanoTime().toDuration(DurationUnit.NANOSECONDS) + + val result = runCatching { newWorker().invoke() } + + val endTime = System.nanoTime().toDuration(DurationUnit.NANOSECONDS) + val elapsedTime = endTime - startTime + dataConnectLogger.info { "Task $path completed execution at ${Date()} " + + "(${elapsedTime.inWholeSeconds} seconds)" } + + result.onFailure { + dataConnectLogger.warn("Task $path execution failed: $it") + throw it + } } /** From 1e1af6c12f279431d217547f3b6443c0f7047179 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Fri, 29 Nov 2024 09:07:22 +0000 Subject: [PATCH 77/79] tweak elapsed time message --- .../dataconnect/gradle/tasks/DataConnectTaskBase.kt | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DataConnectTaskBase.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DataConnectTaskBase.kt index f7948bf7a8..7b1fec50a0 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DataConnectTaskBase.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DataConnectTaskBase.kt @@ -54,8 +54,11 @@ public abstract class DataConnectTaskBase(loggerIdPrefix: String) : DefaultTask( val endTime = System.nanoTime().toDuration(DurationUnit.NANOSECONDS) val elapsedTime = endTime - startTime - dataConnectLogger.info { "Task $path completed execution at ${Date()} " + - "(${elapsedTime.inWholeSeconds} seconds)" } + dataConnectLogger.info { + "Task $path completed execution in " + + elapsedTime.toString(DurationUnit.SECONDS, 2) + + " at ${Date()}" + } result.onFailure { dataConnectLogger.warn("Task $path execution failed: $it") From 68bffd3f90c8c2539fdf4be1f91d025928bfe6d7 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Fri, 29 Nov 2024 11:00:36 +0000 Subject: [PATCH 78/79] big refactor --- .../gradle/DataConnectGradlePlugin.kt | 104 +-- .../gradle/DataConnectTasksConfigurer.kt | 139 ++++ .../dataconnect/gradle/providers/providers.kt | 118 ---- ...loadNodeJsBinaryDistributionArchiveTask.kt | 130 +--- .../gradle/tasks/DownloadNodeJsTask.kt | 630 ------------------ .../gradle/tasks/ExtractArchiveTask.kt | 4 + .../tasks/GenerateDataConnectSourcesTask.kt | 15 +- .../gradle/tasks/SetupFirebaseToolsTask.kt | 13 +- .../gradle/{tasks => util}/NodeJsPaths.kt | 36 +- .../{providers => util}/OperatingSystem.kt | 11 +- 10 files changed, 241 insertions(+), 959 deletions(-) create mode 100644 dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectTasksConfigurer.kt delete mode 100644 dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/providers/providers.kt delete mode 100644 dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsTask.kt rename dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/{tasks => util}/NodeJsPaths.kt (80%) rename dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/{providers => util}/OperatingSystem.kt (89%) diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectGradlePlugin.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectGradlePlugin.kt index 33c1dae94f..aa13c0d349 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectGradlePlugin.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectGradlePlugin.kt @@ -17,102 +17,50 @@ package com.google.firebase.example.dataconnect.gradle import com.android.build.api.variant.ApplicationAndroidComponentsExtension -import com.android.build.api.variant.ApplicationVariant -import com.google.firebase.example.dataconnect.gradle.providers.MyProjectProviders -import com.google.firebase.example.dataconnect.gradle.providers.MyVariantProviders import com.google.firebase.example.dataconnect.gradle.tasks.DownloadNodeJsBinaryDistributionArchiveTask -import com.google.firebase.example.dataconnect.gradle.tasks.DownloadNodeJsTask import com.google.firebase.example.dataconnect.gradle.tasks.ExtractArchiveTask import com.google.firebase.example.dataconnect.gradle.tasks.GenerateDataConnectSourcesTask import com.google.firebase.example.dataconnect.gradle.tasks.SetupFirebaseToolsTask -import com.google.firebase.example.dataconnect.gradle.tasks.configureFrom -import java.util.Locale import org.gradle.api.Plugin import org.gradle.api.Project import org.gradle.kotlin.dsl.getByType import org.gradle.kotlin.dsl.register +import java.util.Locale @Suppress("unused") public abstract class DataConnectGradlePlugin : Plugin { override fun apply(project: Project) { - project.extensions.create("dataconnect", DataConnectExtension::class.java) - val providers = MyProjectProviders(project) - - val dlTask = project.tasks.register("dl") { - group = TASK_GROUP - configureFrom(providers) - } - - project.tasks.register("ex") { - group = TASK_GROUP - - pathPrefixComponentStripCount.set(1) - archiveFile.set(dlTask.flatMap { it.archiveFile }) - - outputDirectory.set(providers.buildDirectory.map { - val dirName = providers.nodeJsPaths.get().archiveBaseFileName - it.dir(dirName) - }) - } - } - - private fun apply2(project: Project) { - val downloadNodeJsTask = project.tasks.register("downloadNodeJs") - val setupFirebaseToolsTask = project.tasks.register("setupFirebaseToolsForDataConnect") - - project.extensions.create("dataconnect", DataConnectExtension::class.java) - val providers = MyProjectProviders(project) + val downloadNodeJsArchiveTask = project.tasks.register("dataConnectDownloadNodeJs") + val extractNodeJsArchiveTask = project.tasks.register("dataConnectExtractNodeJs") + val setupFirebaseToolsTask = project.tasks.register("dataConnectSetupFirebaseTools") + + val dataConnectExtension = project.extensions.create("dataconnect", DataConnectExtension::class.java) + val configurer = DataConnectTasksConfigurer( + dataConnectExtension = dataConnectExtension, + downloadNodeJsArchiveTask = downloadNodeJsArchiveTask, + extractNodeJsArchiveTask = extractNodeJsArchiveTask, + setupFirebaseToolsTask = setupFirebaseToolsTask, + buildDirectory = project.layout.buildDirectory.dir("dataConnect"), + projectLayout = project.layout, + providerFactory = project.providers, + logger = project.logger + ) - project.tasks.register("downloadNodeJsBinaryDistributionArchive") { - group = TASK_GROUP - configureFrom(providers) - } - downloadNodeJsTask.configure { - group = TASK_GROUP - configureFrom(providers) - } - setupFirebaseToolsTask.configure { - group = TASK_GROUP - configureFrom(providers) - } + configurer.invoke() val androidComponents = project.extensions.getByType() androidComponents.onVariants { variant -> - val variantProviders = MyVariantProviders(variant, setupFirebaseToolsTask, providers) - registerVariantTasks(project, variant, variantProviders) + val variantNameTitleCase = variant.name.replaceFirstChar { it.titlecase(Locale.US) } + val generateCodeTaskName = "dataConnectGenerate${variantNameTitleCase}Sources" + val generateCodeTask = project.tasks.register(generateCodeTaskName) + configurer.configureGenerateDataConnectSourcesTask(generateCodeTask, variantNameTitleCase) + + variant.sources.java!!.addGeneratedSourceDirectory( + generateCodeTask, + GenerateDataConnectSourcesTask::outputDirectory + ) } } - private fun registerVariantTasks( - project: Project, - variant: ApplicationVariant, - providers: MyVariantProviders - ) { - val variantNameTitleCase = variant.name.replaceFirstChar { it.titlecase(Locale.US) } - - val generateCodeTaskName = "generate${variantNameTitleCase}DataConnectSources" - val generateCodeTask = project.tasks.register(generateCodeTaskName) { - configureFrom(providers) - @Suppress("ktlint:standard:max-line-length") - setOnlyIf( - "dataconnect.dataConnectConfigDir is null; to enable the \"$name\" task, " + - "set dataconnect.dataConnectConfigDir in build.gradle or build.gradle.kts to " + - "the directory that defines the Data Connect schema and " + - "connectors whose Kotlin code to generate code. That is, the directory " + - "containing the dataconnect.yaml file. For details, see " + - "https://firebase.google.com/docs/data-connect/configuration-reference#dataconnect.yaml-configuration " + - "(e.g. file(\"../dataconnect\")) (message code a3ch245mbd)" - ) { dataConnectConfigDir.isPresent } - } - - variant.sources.java!!.addGeneratedSourceDirectory( - generateCodeTask, - GenerateDataConnectSourcesTask::outputDirectory - ) - } - - private companion object { - private const val TASK_GROUP = "Firebase Data Connect" - } } diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectTasksConfigurer.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectTasksConfigurer.kt new file mode 100644 index 0000000000..d3d9dc0313 --- /dev/null +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectTasksConfigurer.kt @@ -0,0 +1,139 @@ +package com.google.firebase.example.dataconnect.gradle + +import com.google.firebase.example.dataconnect.gradle.tasks.DownloadNodeJsBinaryDistributionArchiveTask +import com.google.firebase.example.dataconnect.gradle.tasks.ExtractArchiveTask +import com.google.firebase.example.dataconnect.gradle.tasks.GenerateDataConnectSourcesTask +import com.google.firebase.example.dataconnect.gradle.tasks.SetupFirebaseToolsTask +import com.google.firebase.example.dataconnect.gradle.util.NodeJsPaths +import com.google.firebase.example.dataconnect.gradle.util.OperatingSystem +import com.google.firebase.example.dataconnect.gradle.util.nodeJsPaths +import com.google.firebase.example.dataconnect.gradle.util.operatingSystem +import org.gradle.api.GradleException +import org.gradle.api.file.Directory +import org.gradle.api.file.ProjectLayout +import org.gradle.api.file.RegularFile +import org.gradle.api.logging.Logger +import org.gradle.api.provider.Provider +import org.gradle.api.provider.ProviderFactory +import org.gradle.api.tasks.TaskProvider + +internal class DataConnectTasksConfigurer( + dataConnectExtension: DataConnectExtension, + private val downloadNodeJsArchiveTask: TaskProvider, + private val extractNodeJsArchiveTask: TaskProvider, + private val setupFirebaseToolsTask: TaskProvider, + private val buildDirectory: Provider, + projectLayout: ProjectLayout, + providerFactory: ProviderFactory, +) : () -> Unit { + + override fun invoke() { + configureDownloadNodeJsArchiveTask() + configureExtractNodeJsArchiveTask() + configureSetupFirebaseToolsTask() + } + + private val nodeJsVersion: Provider = + providerFactory.provider { + dataConnectExtension.nodeJsVersion + ?: throw GradleException( + "dataconnect.nodeJsVersion must be set in " + + "build.gradle or build.gradle.kts to " + + "specify the version of Node.js (https://nodejs.org) " + + "to install (e.g. \"20.9.0\") (error code 3acj27az2c)" + ) + } + + private val operatingSystem: Provider = providerFactory.operatingSystem() + + private val nodeJsPaths: Provider = providerFactory.nodeJsPaths(nodeJsVersion, operatingSystem) + + private val firebaseCliVersion: Provider = providerFactory.provider { + dataConnectExtension.firebaseCliVersion + ?: throw GradleException( + "dataconnect.firebaseCliVersion must be set in " + + "build.gradle or build.gradle.kts to " + + "specify the version of the Firebase CLI npm package " + + "(https://www.npmjs.com/package/firebase-tools) to use " + + "(e.g. \"13.25.0\") (error code xbmvkc3mtr)" + ) + } + + private val nodeExecutable: Provider = extractNodeJsArchiveTask.flatMap { + providerFactory.zip(it.outputDirectory, nodeJsPaths) { outputDirectory, nodeJsPaths -> + outputDirectory.file(nodeJsPaths.nodeExecutableRelativePath) + } + } + + private val npmExecutable: Provider = extractNodeJsArchiveTask.flatMap { + providerFactory.zip(it.outputDirectory, nodeJsPaths) { outputDirectory, nodeJsPaths -> + outputDirectory.file(nodeJsPaths.npmExecutableRelativePath) + } + } + + private val dataConnectConfigDir: Provider = providerFactory.provider { + dataConnectExtension.dataConnectConfigDir?.let { + projectLayout.projectDirectory.dir(it.path) + } + } + + private fun configureDownloadNodeJsArchiveTask() = downloadNodeJsArchiveTask.configure { + group = TASK_GROUP + + archiveUrl.set(nodeJsPaths.map { it.archiveUrl }) + shasumsUrl.set(nodeJsPaths.map { it.shasumsUrl }) + + archiveFile.set(providerFactory.zip(buildDirectory, nodeJsPaths) { buildDirectory, nodeJsPaths -> + buildDirectory.file(nodeJsPaths.archiveFileName) + }) + + shasumsFile.set(providerFactory.zip(buildDirectory, nodeJsPaths) { buildDirectory, nodeJsPaths -> + buildDirectory.file(nodeJsPaths.shasumsFileName) + }) + } + + private fun configureExtractNodeJsArchiveTask() = extractNodeJsArchiveTask.configure { + group = TASK_GROUP + + pathPrefixComponentStripCount.set(1) + archiveFile.set(downloadNodeJsArchiveTask.flatMap { it.archiveFile }) + + outputDirectory.set(providerFactory.zip(buildDirectory, nodeJsPaths) { buildDirectory, nodeJsPaths -> + buildDirectory.dir(nodeJsPaths.archiveBaseFileName) + }) + } + + private fun configureSetupFirebaseToolsTask() = setupFirebaseToolsTask.configure { + group = TASK_GROUP + + firebaseCliVersion.set(this@DataConnectTasksConfigurer.firebaseCliVersion) + outputDirectory.set(buildDirectory.map { it.dir("firebase-tools") }) + + nodeExecutable.set(this@DataConnectTasksConfigurer.nodeExecutable) + npmExecutable.set(this@DataConnectTasksConfigurer.npmExecutable) + } + + fun configureGenerateDataConnectSourcesTask(task: TaskProvider, variantName: String) = task.configure { + group = TASK_GROUP + + @Suppress("ktlint:standard:max-line-length") + setOnlyIf( + "dataconnect.dataConnectConfigDir is null; to enable the \"$name\" task, " + + "set dataconnect.dataConnectConfigDir in build.gradle or build.gradle.kts to " + + "the directory that defines the Data Connect schema and " + + "connectors whose Kotlin code to generate code. That is, the directory " + + "containing the dataconnect.yaml file. For details, see " + + "https://firebase.google.com/docs/data-connect/configuration-reference#dataconnect.yaml-configuration " + + "(e.g. file(\"../dataconnect\")) (message code a3ch245mbd)" + ) { dataConnectConfigDir.isPresent } + + dataConnectConfigDir.set(this@DataConnectTasksConfigurer.dataConnectConfigDir) + firebaseExecutable.set(setupFirebaseToolsTask.map { it.firebaseExecutable }) + nodeExecutable.set(this@DataConnectTasksConfigurer.nodeExecutable) + tweakedDataConnectConfigDir.set(buildDirectory.map { it.dir("variants/$variantName/config") }) + } + +} + +private const val TASK_GROUP = "Firebase Data Connect" + diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/providers/providers.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/providers/providers.kt deleted file mode 100644 index 46942e86d0..0000000000 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/providers/providers.kt +++ /dev/null @@ -1,118 +0,0 @@ -/* - * 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.example.dataconnect.gradle.providers - -import com.android.build.api.variant.ApplicationVariant -import com.google.firebase.example.dataconnect.gradle.DataConnectExtension -import com.google.firebase.example.dataconnect.gradle.tasks.NodeJsPaths -import com.google.firebase.example.dataconnect.gradle.tasks.SetupFirebaseToolsTask -import com.google.firebase.example.dataconnect.gradle.tasks.nodeJsPaths -import org.gradle.api.GradleException -import org.gradle.api.Project -import org.gradle.api.file.Directory -import org.gradle.api.file.DirectoryProperty -import org.gradle.api.file.ProjectLayout -import org.gradle.api.file.RegularFile -import org.gradle.api.logging.Logger -import org.gradle.api.model.ObjectFactory -import org.gradle.api.provider.Provider -import org.gradle.api.provider.ProviderFactory -import org.gradle.api.tasks.TaskProvider -import org.gradle.kotlin.dsl.getByType - -internal open class MyProjectProviders( - projectBuildDirectory: DirectoryProperty, - val providerFactory: ProviderFactory, - val objectFactory: ObjectFactory, - val projectLayout: ProjectLayout, - ext: DataConnectExtension, - logger: Logger -) { - - constructor( - project: Project, - ) : this( - projectBuildDirectory = project.layout.buildDirectory, - providerFactory = project.providers, - objectFactory = project.objects, - projectLayout = project.layout, - ext = project.extensions.getByType(), - project.logger - ) - - val operatingSystem: Provider = providerFactory.operatingSystem(logger) - - val buildDirectory: Provider = projectBuildDirectory.map { it.dir("dataconnect") } - - val firebaseCliVersion: Provider = run { - val lazyFirebaseCliVersion: Lazy = lazy { - ext.firebaseCliVersion - ?: throw GradleException( - "dataconnect.firebaseCliVersion must be set in " + - "build.gradle or build.gradle.kts to " + - "specify the version of the Firebase CLI npm package " + - "(https://www.npmjs.com/package/firebase-tools) to use " + - "(e.g. \"13.25.0\") (error code xbmvkc3mtr)" - ) - } - providerFactory.provider { lazyFirebaseCliVersion.value } - } - - val nodeJsVersion: Provider = run { - val lazyNodeVersion: Lazy = lazy { - ext.nodeJsVersion - ?: throw GradleException( - "dataconnect.nodeJsVersion must be set in " + - "build.gradle or build.gradle.kts to " + - "specify the version of Node.js (https://nodejs.org) " + - "to install (e.g. \"20.9.0\") (error code 3acj27az2c)" - ) - } - providerFactory.provider { lazyNodeVersion.value } - } - - val dataConnectConfigDir: Provider = run { - val lazyDataConnectConfigDir: Lazy = lazy { - ext.dataConnectConfigDir?.let { - projectLayout.projectDirectory.dir(it.path) - } - } - providerFactory.provider { lazyDataConnectConfigDir.value } - } - - val nodeJsPaths: Provider = providerFactory.nodeJsPaths(nodeJsVersion, operatingSystem) -} - -internal open class MyVariantProviders( - variant: ApplicationVariant, - val projectProviders: MyProjectProviders, - val firebaseExecutable: Provider -) { - - constructor( - variant: ApplicationVariant, - setupFirebaseToolsTask: TaskProvider, - projectProviders: MyProjectProviders - ) : this( - variant = variant, - projectProviders = projectProviders, - firebaseExecutable = setupFirebaseToolsTask.map { it.firebaseExecutable } - ) - - val buildDirectory: Provider = - projectProviders.buildDirectory.map { it.dir("variants/${variant.name}") } -} diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsBinaryDistributionArchiveTask.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsBinaryDistributionArchiveTask.kt index 160cd968e5..220253671b 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsBinaryDistributionArchiveTask.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsBinaryDistributionArchiveTask.kt @@ -17,46 +17,49 @@ package com.google.firebase.example.dataconnect.gradle.tasks import com.google.firebase.example.dataconnect.gradle.DataConnectGradleException -import com.google.firebase.example.dataconnect.gradle.providers.MyProjectProviders -import com.google.firebase.example.dataconnect.gradle.providers.OperatingSystem -import com.google.firebase.example.dataconnect.gradle.tasks.DownloadNodeJsBinaryDistributionArchiveTask.Inputs import com.google.firebase.example.dataconnect.gradle.tasks.DownloadNodeJsBinaryDistributionArchiveTask.Worker import com.google.firebase.example.dataconnect.gradle.util.DataConnectGradleLogger import com.google.firebase.example.dataconnect.gradle.util.FileDownloader import com.google.firebase.example.dataconnect.gradle.util.Sha256SignatureVerifier import com.google.firebase.example.dataconnect.gradle.util.addCertificatesFromKeyListResource import com.google.firebase.example.dataconnect.gradle.util.addHashesFromShasumsFile -import java.io.File -import javax.inject.Inject import kotlinx.coroutines.runBlocking -import kotlinx.serialization.Serializable import org.gradle.api.file.FileSystemOperations import org.gradle.api.file.RegularFileProperty import org.gradle.api.provider.Property import org.gradle.api.provider.ProviderFactory import org.gradle.api.tasks.CacheableTask import org.gradle.api.tasks.Input -import org.gradle.api.tasks.Nested import org.gradle.api.tasks.OutputFile -import java.nio.file.Files +import java.io.File +import javax.inject.Inject @CacheableTask public abstract class DownloadNodeJsBinaryDistributionArchiveTask : DataConnectTaskBase(LOGGER_ID_PREFIX) { /** - * The inputs required to execute this task. + * The URL of the Node.js binary distribution archive file to download. * - * This property is _required_, meaning that it must be set; that is, - * [Property.isPresent] must return `true`. + * This property is _required_; that is, it must be set by the time that + * this task is executed. */ - @get:Nested - public abstract val inputData: Property + @get:Input + public abstract val archiveUrl: Property /** - * The file to which to download the Node.js binary distribution archive. + * The URL of the archive file to download. * - * This property is _required_, meaning that it must be set; that is, [Property.isPresent] must - * return `true`. + * This property is _required_; that is, it must be set by the time that + * this task is executed. + */ + @get:Input + public abstract val shasumsUrl: Property + + /** + * The URL of the "SHASUMS256.txt.asc" file. + * + * This property is _required_; that is, it must be set by the time that + * this task is executed. */ @get:OutputFile public abstract val archiveFile: RegularFileProperty @@ -64,8 +67,8 @@ public abstract class DownloadNodeJsBinaryDistributionArchiveTask : DataConnectT /** * The file to which to download the "SHASUMS256.txt.asc" file. * - * This property is _required_, meaning that it must be set; that is, [Property.isPresent] must - * return `true`. + * This property is _required_; that is, it must be set by the time that + * this task is executed. */ @get:OutputFile public abstract val shasumsFile: RegularFileProperty @@ -77,49 +80,23 @@ public abstract class DownloadNodeJsBinaryDistributionArchiveTask : DataConnectT internal abstract val fileSystemOperations: FileSystemOperations override fun newWorker(): DataConnectTaskBase.Worker { - val inputData = inputData.get() - val nodeJsPaths = inputData.calculateNodeJsPaths() - return DownloadNodeJsBinaryDistributionArchiveTaskWorkerImpl( - operatingSystemType = inputData.operatingSystem.type, - operatingSystemArchitecture = inputData.operatingSystem.architecture, - nodeJsVersion = inputData.nodeJsVersion, + archiveUrl = archiveUrl.get(), + shasumsUrl = shasumsUrl.get(), archiveFile = archiveFile.get().asFile, shasumsFile = shasumsFile.get().asFile, - archiveUrl = nodeJsPaths.archiveUrl, - shasumsUrl = nodeJsPaths.shasumsUrl, fileSystemOperations = fileSystemOperations, logger = dataConnectLogger ) } - /** - * The inputs for [DownloadNodeJsBinaryDistributionArchiveTask]. - * - * @property operatingSystem The operating system whose Node.js binary distribution archive to - * download. - * @property nodeJsVersion The version of Node.js whose binary distribution archive to download. - */ - @Serializable - public data class Inputs( - @get:Nested val operatingSystem: OperatingSystem, - @get:Input val nodeJsVersion: String - ) - internal interface Worker : DataConnectTaskBase.Worker, AutoCloseable { - val operatingSystemType: OperatingSystem.Type - val operatingSystemArchitecture: OperatingSystem.Architecture - val nodeJsVersion: String - val archiveFile: File - val shasumsFile: File val archiveUrl: String val shasumsUrl: String + val archiveFile: File + val shasumsFile: File val fileSystemOperations: FileSystemOperations val fileDownloader: FileDownloader - - override fun close() { - fileDownloader.close() - } } @@ -128,67 +105,26 @@ public abstract class DownloadNodeJsBinaryDistributionArchiveTask : DataConnectT } } -internal fun DownloadNodeJsBinaryDistributionArchiveTask.configureFrom(myProviders: MyProjectProviders) { - inputData.run { - finalizeValueOnRead() - set( - providerFactory.provider { - val operatingSystem = myProviders.operatingSystem.get() - val nodeJsVersion = myProviders.nodeJsVersion.get() - Inputs(operatingSystem, nodeJsVersion) - } - ) - } - - archiveFile.run { - finalizeValueOnRead() - set(myProviders.buildDirectory.map { - val downloadFileName = inputData.get().calculateNodeJsPaths().archiveFileName - it.file(downloadFileName) - }) - } - - shasumsFile.run { - finalizeValueOnRead() - set(myProviders.buildDirectory.map { - val shasumsFileName = inputData.get().calculateNodeJsPaths().shasumsFileName - it.file(shasumsFileName) - }) - } - - setOnlyIf("inputData was specified", { - inputData.isPresent - }) -} - -private fun Inputs.calculateNodeJsPaths(): NodeJsPaths = NodeJsPaths.from( - nodeJsVersion, - operatingSystem.type, - operatingSystem.architecture -) - private class DownloadNodeJsBinaryDistributionArchiveTaskWorkerImpl( - override val operatingSystemType: OperatingSystem.Type, - override val operatingSystemArchitecture: OperatingSystem.Architecture, - override val nodeJsVersion: String, - override val archiveFile: File, - override val shasumsFile: File, override val archiveUrl: String, override val shasumsUrl: String, + override val archiveFile: File, + override val shasumsFile: File, override val fileSystemOperations: FileSystemOperations, override val logger: DataConnectGradleLogger ) : Worker { override val fileDownloader = FileDownloader(logger) - override fun invoke() { - run() + override fun invoke() = run() + + override fun close() { + fileDownloader.close() } } private fun Worker.run() { - logger.info { "operatingSystemType: $operatingSystemType" } - logger.info { "operatingSystemArchitecture: $operatingSystemArchitecture" } - logger.info { "nodeJsVersion: $nodeJsVersion" } + logger.info { "archiveUrl: $archiveUrl" } + logger.info { "shasumsUrl: $shasumsUrl" } logger.info { "archiveFile: ${archiveFile.absolutePath}" } logger.info { "shasumsFile: ${shasumsFile.absolutePath}" } diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsTask.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsTask.kt deleted file mode 100644 index ba2aba5ad0..0000000000 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsTask.kt +++ /dev/null @@ -1,630 +0,0 @@ -/* - * 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.example.dataconnect.gradle.tasks - -import com.google.firebase.example.dataconnect.gradle.providers.MyProjectProviders -import com.google.firebase.example.dataconnect.gradle.providers.OperatingSystem -import com.google.firebase.example.dataconnect.gradle.tasks.DownloadNodeJsTask.DownloadOfficialVersion -import com.google.firebase.example.dataconnect.gradle.tasks.DownloadNodeJsTask.Source -import io.ktor.client.HttpClient -import io.ktor.client.call.body -import io.ktor.client.engine.cio.CIO -import io.ktor.client.plugins.logging.LogLevel -import io.ktor.client.plugins.logging.Logger -import io.ktor.client.plugins.logging.Logging -import io.ktor.client.request.prepareGet -import io.ktor.utils.io.ByteReadChannel -import io.ktor.utils.io.jvm.javaio.copyTo -import java.io.File -import java.io.IOException -import java.nio.file.Files -import java.nio.file.Path -import java.nio.file.Paths -import java.nio.file.attribute.FileTime -import java.nio.file.attribute.PosixFilePermission -import java.security.MessageDigest -import java.text.NumberFormat -import kotlinx.coroutines.runBlocking -import kotlinx.serialization.encodeToString -import kotlinx.serialization.json.Json -import org.apache.commons.compress.archivers.ArchiveEntry -import org.apache.commons.compress.archivers.ArchiveInputStream -import org.apache.commons.compress.archivers.tar.TarArchiveEntry -import org.apache.commons.compress.archivers.tar.TarArchiveInputStream -import org.apache.commons.compress.archivers.zip.ZipArchiveEntry -import org.apache.commons.compress.archivers.zip.ZipArchiveInputStream -import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream -import org.bouncycastle.util.encoders.Hex -import org.gradle.api.DefaultTask -import org.gradle.api.GradleException -import org.gradle.api.Task -import org.gradle.api.file.Directory -import org.gradle.api.file.DirectoryProperty -import org.gradle.api.file.RegularFile -import org.gradle.api.provider.Property -import org.gradle.api.provider.Provider -import org.gradle.api.tasks.CacheableTask -import org.gradle.api.tasks.Input -import org.gradle.api.tasks.Internal -import org.gradle.api.tasks.Nested -import org.gradle.api.tasks.OutputDirectory -import org.gradle.api.tasks.TaskAction -import org.gradle.kotlin.dsl.newInstance -import org.pgpainless.sop.SOPImpl - -@CacheableTask -public abstract class DownloadNodeJsTask : DefaultTask() { - - @get:Nested - public abstract val source: Property - - @get:OutputDirectory - public abstract val outputDirectory: DirectoryProperty - - @get:Internal - public val nodeExecutable: RegularFile get() { - val source = source.get() - val outputDirectory = outputDirectory.get() - return outputDirectory.file(source.nodeExecutable) - } - - @get:Internal - public val npmExecutable: RegularFile get() { - val source = source.get() - val outputDirectory = outputDirectory.get() - return outputDirectory.file(source.npmExecutable) - } - - @TaskAction - public fun run() { - val source: Source = source.get() - val outputDirectoryRegularFile: Directory = outputDirectory.get() - val outputDirectory: File = outputDirectoryRegularFile.asFile - - logger.info("source: {}", Source.describe(source)) - logger.info("outputDirectory: {}", outputDirectory.absolutePath) - - project.delete(outputDirectory) - - when (source) { - is DownloadOfficialVersion -> downloadOfficialVersion(source, outputDirectory) - } - } - - public sealed interface Source : java.io.Serializable { - public companion object - - @get:Internal - public val nodeExecutable: String - - @get:Internal - public val npmExecutable: String - - @get:Internal - public val cacheKey: String - } - - public abstract class DownloadOfficialVersion : Source { - public companion object - - @get:Input - public abstract val version: Property - - @get:Nested - public abstract val operatingSystem: Property - - override val nodeExecutable: String get() = nodePathOf { it.nodeExecutable } - - override val npmExecutable: String get() = nodePathOf { it.npmExecutable } - - override val cacheKey: String - get() { - val os: OperatingSystem = operatingSystem.get() - return buildString { - append("DownloadOfficialVersion{") - append("nodeVersion=") - append(Json.encodeToString(version.get())) - append(", os=") - append(Json.encodeToString(os.type.name.lowercase())) - append(", architecture=") - append(Json.encodeToString(os.architecture.name.lowercase())) - append("}") - } - } - - private fun nodePathOf(block: (OperatingSystem.Type) -> Path): String { - val osType: OperatingSystem.Type = operatingSystem.get().type - val relativePath: Path = block(osType) - val path: Path = Path.of(downloadFileNameBase).resolve(relativePath) - return path.toString() - } - } -} - -private val OperatingSystem.Type.nodeExecutable: Path - get() = - if (this == OperatingSystem.Type.Windows) { - Path.of("node.exe") - } else { - Path.of("bin", "node") - } - -private val OperatingSystem.Type.npmExecutable: Path - get() = - if (this == OperatingSystem.Type.Windows) { - Path.of("npm.cmd") - } else { - Path.of("bin", "npm") - } - -internal fun DownloadNodeJsTask.configureFrom(providers: MyProjectProviders) { - source.run { - set(providers.source) - finalizeValueOnRead() - } - - outputDirectory.run { - set(providers.buildDirectory.map { it.dir("node") }) - finalizeValueOnRead() - } -} - -internal val MyProjectProviders.source: Provider - get() { - val lazySource: Lazy = lazy { - objectFactory.newInstance().also { - it.updateFrom(this@source) - } - } - return providerFactory.provider { lazySource.value } - } - -internal fun DownloadOfficialVersion.updateFrom(providers: MyProjectProviders) { - version.set(providers.nodeJsVersion) - operatingSystem.set(providers.operatingSystem) -} - -internal fun DownloadOfficialVersion.Companion.describe(source: DownloadOfficialVersion?): String = - if (source === null) { - "null" - } else source.run { - "DownloadNodeJsTask.Source.DownloadOfficialVersion(" + - "version=${version.orNull}, operatingSystem=${operatingSystem.orNull})" - } - -internal fun Source.Companion.describe(source: Source?): String = when (source) { - null -> "null" - is DownloadOfficialVersion -> DownloadOfficialVersion.describe(source) -} - -internal val DownloadOfficialVersion.downloadUrlPrefix: String get() = "https://nodejs.org/dist/v${version.get()}" - -private const val shasumsFileName = "SHASUMS256.txt.asc" - -internal val DownloadOfficialVersion.shasumsDownloadUrl: String get() = "$downloadUrlPrefix/$shasumsFileName" - -/** - * The URL to download the Node.js binary distribution. - * - * Here are some examples: - * * https://nodejs.org/dist/v20.9.0/node-v20.9.0-darwin-arm64.tar.gz - * * https://nodejs.org/dist/v20.9.0/node-v20.9.0-darwin-x64.tar.gz - * * https://nodejs.org/dist/v20.9.0/node-v20.9.0-linux-arm64.tar.gz - * * https://nodejs.org/dist/v20.9.0/node-v20.9.0-linux-armv7l.tar.gz - * * https://nodejs.org/dist/v20.9.0/node-v20.9.0-linux-x64.tar.gz - * * https://nodejs.org/dist/v20.9.0/node-v20.9.0-win-arm64.zip - * * https://nodejs.org/dist/v20.9.0/node-v20.9.0-win-x64.zip - * * https://nodejs.org/dist/v20.9.0/node-v20.9.0-win-x86.zip - */ -internal val DownloadOfficialVersion.downloadUrl: String get() = "$downloadUrlPrefix/$downloadFileName" - -/** - * The file name of the download for the Node.js binary distribution. - * - * Here are some examples: - * * node-v20.9.0-darwin-arm64.tar.gz - * * node-v20.9.0-darwin-x64.tar.gz - * * node-v20.9.0-linux-arm64.tar.gz - * * node-v20.9.0-linux-armv7l.tar.gz - * * node-v20.9.0-linux-x64.tar.gz - * * node-v20.9.0-win-arm64.zip - * * node-v20.9.0-win-x64.zip - * * node-v20.9.0-win-x86.zip - */ -internal val DownloadOfficialVersion.downloadFileName: String - get() { - val os = operatingSystem.get() - val fileExtension: String = when (val type = os.type) { - OperatingSystem.Type.Windows -> "zip" - OperatingSystem.Type.MacOS -> "tar.gz" - OperatingSystem.Type.Linux -> "tar.gz" - else -> throw GradleException( - "unable to determine node.js download file extension for operating system type: $type " + - "(operatingSystem=$os) (error code ead53smf45)" - ) - } - return "$downloadFileNameBase.$fileExtension" - } - -/** - * The base file name of the download for the Node.js binary distribution; - * that is, the file name without the ".zip" or ".tar.gz" extension. - * - * Here are some examples: - * * node-v20.9.0-darwin-arm64 - * * node-v20.9.0-darwin-x64 - * * node-v20.9.0-linux-arm64 - * * node-v20.9.0-linux-armv7l - * * node-v20.9.0-linux-x64 - * * node-v20.9.0-win-arm64 - * * node-v20.9.0-win-x64 - * * node-v20.9.0-win-x86 - */ -internal val DownloadOfficialVersion.downloadFileNameBase: String - get() { - val os: OperatingSystem = operatingSystem.get() - val osType: String = when (val type = os.type) { - OperatingSystem.Type.Windows -> "win" - OperatingSystem.Type.MacOS -> "darwin" - OperatingSystem.Type.Linux -> "linux" - else -> throw GradleException( - "unable to determine node.js download base file name for operating system type: $type " + - "(operatingSystem=$os) (error code m2grw3h7xz)" - ) - } - - val osArch: String = when (os.architecture) { - OperatingSystem.Architecture.Arm64 -> "arm64" - OperatingSystem.Architecture.ArmV7 -> "armv7l" - OperatingSystem.Architecture.X86 -> "x86" - OperatingSystem.Architecture.X86_64 -> "x64" - } - - val nodeVersion = version.get() - return "node-v$nodeVersion-$osType-$osArch" - } - -private data class DownloadedNodeJsFiles( - val binaryDistribution: File, - val shasums: File -) - -private fun Task.downloadOfficialVersion(source: DownloadOfficialVersion, outputDirectory: File) { - val downloadedFiles = downloadNodeJsBinaryDistribution(source, outputDirectory) - val shasums = verifyNodeJsShaSumsSignature(downloadedFiles.shasums) - val expectedSha256Digest = - getExpectedSha256DigestFromShasumsFile(downloadedFiles.shasums.absolutePath, shasums, source.downloadFileName) - verifySha256Digest(downloadedFiles.binaryDistribution, expectedSha256Digest) - - if (downloadedFiles.binaryDistribution.name.endsWith(".tar.gz")) { - untar(downloadedFiles.binaryDistribution, outputDirectory) - } else if (downloadedFiles.binaryDistribution.name.endsWith(".zip")) { - unzip(downloadedFiles.binaryDistribution, outputDirectory) - } else { - throw GradleException( - "Unsupported archive: ${downloadedFiles.binaryDistribution.absolutePath} " + - "(only .tar.gz and .zip extensions are supported) (error code pvrvw8sk9t)" - ) - } -} - -private fun Task.untar(file: File, destDir: File) { - logger.info("Extracting {} to {}", file.absolutePath, destDir.absolutePath) - var extractedFileCount = 0 - var extractedByteCount = 0L - file.inputStream().use { fileInputStream -> - GzipCompressorInputStream(fileInputStream).use { gzipInputStream -> - val archiveInputStream: ArchiveInputStream<*> = TarArchiveInputStream(gzipInputStream) - archiveInputStream.use { tarInputStream -> - while (true) { - val tarEntry: ArchiveEntry = tarInputStream.nextEntry ?: break - if (tarEntry !is TarArchiveEntry) { - continue - } - val outputFile = File(destDir, tarEntry.name).absoluteFile - if (tarEntry.isSymbolicLink) { - logger.debug("Creating symlink {} -> {}", outputFile.absolutePath, tarEntry.linkName) - Files.createSymbolicLink(outputFile.toPath(), Paths.get(tarEntry.linkName)) - extractedFileCount++ - } else if (tarEntry.isFile) { - logger.debug("Extracting {}", outputFile.absolutePath) - outputFile.parentFile.mkdirs() - outputFile.outputStream().use { fileOutputStream -> - extractedByteCount += tarInputStream.copyTo(fileOutputStream) - } - extractedFileCount++ - - val lastModifiedTime = FileTime.from(tarEntry.lastModifiedDate.toInstant()) - try { - Files.setLastModifiedTime(outputFile.toPath(), lastModifiedTime) - } catch (e: IOException) { - logger.debug( - "Ignoring error from Files.setLastModifiedTime({}, {}): {}", - outputFile.absolutePath, - lastModifiedTime, - e.toString() - ) - } - - val newPermissions = buildSet { - add(PosixFilePermission.OWNER_READ) - add(PosixFilePermission.OWNER_WRITE) - - add(PosixFilePermission.GROUP_READ) - add(PosixFilePermission.OTHERS_READ) - - val mode = tarEntry.mode - if ((mode and 0x100) == 0x100) { - add(PosixFilePermission.OWNER_EXECUTE) - add(PosixFilePermission.GROUP_EXECUTE) - add(PosixFilePermission.OTHERS_EXECUTE) - } - } - try { - Files.setPosixFilePermissions(outputFile.toPath(), newPermissions) - } catch (e: UnsupportedOperationException) { - logger.debug( - "Ignoring error from Files.setPosixFilePermissions({}, {}}): {}", - outputFile.absolutePath, - newPermissions, - e.toString() - ) - } - } else { - continue - } - } - } - } - } - val extractedByteCountStr = NumberFormat.getNumberInstance().format(extractedByteCount) - logger.info( - "Extracted {} files ({} bytes) from {} to {}", - extractedFileCount, - extractedByteCountStr, - file.absolutePath, - destDir.absolutePath - ) -} - -private fun Task.unzip(file: File, destDir: File) { - logger.info("Extracting {} to {}", file.absolutePath, destDir.absolutePath) - var extractedFileCount = 0 - var extractedByteCount = 0L - file.inputStream().use { fileInputStream -> - val archiveInputStream: ArchiveInputStream<*> = ZipArchiveInputStream(fileInputStream) - archiveInputStream.use { zipInputStream -> - while (true) { - val zipEntry: ArchiveEntry = zipInputStream.nextEntry ?: break - if (zipEntry.isDirectory || zipEntry !is ZipArchiveEntry) { - continue - } - val outputFile = File(destDir, zipEntry.name).absoluteFile - logger.debug("Extracting {}", outputFile.absolutePath) - outputFile.parentFile.mkdirs() - outputFile.outputStream().use { fileOutputStream -> - extractedByteCount += zipInputStream.copyTo(fileOutputStream) - } - extractedFileCount++ - - val lastModifiedTime = FileTime.from(zipEntry.lastModifiedDate.toInstant()) - try { - Files.setLastModifiedTime(outputFile.toPath(), lastModifiedTime) - } catch (e: IOException) { - logger.debug( - "Ignoring error from Files.setLastModifiedTime({}, {}): {}", - outputFile.absolutePath, - lastModifiedTime, - e.toString() - ) - } - - val newPermissions = buildSet { - add(PosixFilePermission.OWNER_READ) - add(PosixFilePermission.OWNER_WRITE) - - add(PosixFilePermission.GROUP_READ) - add(PosixFilePermission.OTHERS_READ) - - val mode = zipEntry.unixMode - if ((mode and 0x100) == 0x100) { - add(PosixFilePermission.OWNER_EXECUTE) - add(PosixFilePermission.GROUP_EXECUTE) - add(PosixFilePermission.OTHERS_EXECUTE) - } - } - try { - Files.setPosixFilePermissions(outputFile.toPath(), newPermissions) - } catch (e: UnsupportedOperationException) { - logger.debug( - "Ignoring error from Files.setPosixFilePermissions({}, {}}): {}", - outputFile.absolutePath, - newPermissions, - e.toString() - ) - } - } - } - } - val extractedByteCountStr = NumberFormat.getNumberInstance().format(extractedByteCount) - logger.info( - "Extracted {} files ({} bytes) from {} to {}", - extractedFileCount, - extractedByteCountStr, - file.absolutePath, - destDir.absolutePath - ) -} - -private fun Task.verifySha256Digest(file: File, expectedSha256Digest: String) { - val actualSha256Digest = file.inputStream().use { inputStream -> - val messageDigest = MessageDigest.getInstance("SHA-256") - val buffer = ByteArray(8192) - while (true) { - val readCount = inputStream.read(buffer) - if (readCount < 0) { - break - } - messageDigest.update(buffer, 0, readCount) - } - val digest = messageDigest.digest() - Hex.toHexString(digest) - } - - if (expectedSha256Digest == actualSha256Digest) { - logger.info("{} had the expected SHA256 digest: {}", file.absolutePath, expectedSha256Digest) - } else { - throw GradleException( - "Incorrect SHA256 digest of ${file.absolutePath}: " + - "$actualSha256Digest (expected $expectedSha256Digest)" - ) - } -} - -private fun Task.getExpectedSha256DigestFromShasumsFile( - shasumsFilePath: String, - shasumsFileBytes: ByteArray, - desiredFileName: String -): String { - logger.info("Looking for SHA256 sum of {} in {}", desiredFileName, shasumsFilePath) - val lines = String(shasumsFileBytes).lines() - val regex = Regex("""\s*(\w+)\s+(.*)\s*""") - val shas = lines.mapNotNull { line -> - regex.matchEntire(line)?.let { matchResult -> - if (matchResult.groupValues[2] == desiredFileName) { - matchResult.groupValues[1] - } else { - null - } - } - }.distinct() - - val sha = shas.singleOrNull() ?: throw GradleException( - "$shasumsFilePath defines ${shas.size} SHA256 hashes for " + - "$desiredFileName, but expected exactly 1" - ) - - logger.info("Found SHA256 sum of {} in {}: {}", desiredFileName, shasumsFilePath, sha) - return sha -} - -private fun Task.downloadNodeJsBinaryDistribution( - source: DownloadOfficialVersion, - outputDirectory: File -): DownloadedNodeJsFiles { - val shasumsFile = File(outputDirectory, shasumsFileName) - val binaryDistributionFile = File(outputDirectory, source.downloadFileName) - - val httpClient = HttpClient(CIO) { - expectSuccess = true - install(Logging) { - val gradleLogger = this@downloadNodeJsBinaryDistribution.logger - - level = if (gradleLogger.isDebugEnabled) { - LogLevel.HEADERS - } else if (gradleLogger.isInfoEnabled) { - LogLevel.INFO - } else { - LogLevel.NONE - } - - logger = object : Logger { - override fun log(message: String) { - message.lines().forEach { line -> - gradleLogger.info("ktor: {}", line.trimEnd()) - } - } - } - } - } - - httpClient.use { - runBlocking { - downloadFile(httpClient, source.shasumsDownloadUrl, shasumsFile, maxNumDownloadBytes = 100_000L) - downloadFile(httpClient, source.downloadUrl, binaryDistributionFile, maxNumDownloadBytes = 200_000_000L) - } - } - - return DownloadedNodeJsFiles( - binaryDistribution = binaryDistributionFile, - shasums = shasumsFile - ) -} - -private suspend fun Task.downloadFile(httpClient: HttpClient, url: String, destFile: File, maxNumDownloadBytes: Long) { - logger.info("Downloading {} to {}", url, destFile.absolutePath) - - val actualNumBytesDownloaded = httpClient.prepareGet(url).execute { httpResponse -> - val downloadChannel: ByteReadChannel = httpResponse.body() - destFile.parentFile.mkdirs() - destFile.outputStream().use { destFileOutputStream -> - downloadChannel.copyTo(destFileOutputStream, limit = maxNumDownloadBytes) - } - } - - val numberFormat = NumberFormat.getNumberInstance() - val actualNumBytesDownloadedStr = numberFormat.format(actualNumBytesDownloaded) - if (actualNumBytesDownloaded >= maxNumDownloadBytes) { - val maxNumDownloadBytesStr = numberFormat.format(maxNumDownloadBytes) - throw GradleException( - "Downloading $url failed: maximum file size $maxNumDownloadBytesStr bytes exceeded; " + - "cancelled after downloading $actualNumBytesDownloadedStr bytes " + - "(error code hvmhysn5vy)" - ) - } - - logger.info("Successfully downloaded {} to {} ({} bytes)", url, destFile.absolutePath, actualNumBytesDownloadedStr) -} - -private fun Task.verifyNodeJsShaSumsSignature(file: File): ByteArray { - logger.info( - "Verifying that ${file.absolutePath} has a valid signature " + - "from the node.js release signing keys" - ) - - val keysListPath = "com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/keys.list" - val keyNames: List = String(loadResource(keysListPath)).lines().map { it.trim() }.filter { it.isNotBlank() } - logger.info("Loaded the names of ${keyNames.size} keys from resource: $keysListPath") - - val sop = SOPImpl() - val inlineVerify = sop.inlineVerify() - keyNames.forEach { keyName -> - val certificateBytes = - loadResource("com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/$keyName.asc") - inlineVerify.cert(certificateBytes) - } - logger.info("Loading {} to verify its signature", file.absolutePath) - val verificationResult = file.inputStream().use { inputStream -> - inlineVerify.data(inputStream).toByteArrayAndResult() - } - logger.info("Signature of {} successfully verified", file.absolutePath) - - return verificationResult.bytes -} - -private fun Task.loadResource(path: String): ByteArray { - val classLoader = this::class.java.classLoader - logger.info("Loading resource: {}", path) - return classLoader.getResourceAsStream(path).let { unmanagedInputStream -> - unmanagedInputStream.use { inputStream -> - if (inputStream === null) { - throw GradleException("resource not found: $path (error code 6ygyz2dj2n)") - } - inputStream.readAllBytes() - } - } -} diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/ExtractArchiveTask.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/ExtractArchiveTask.kt index 0ded06826d..6f8fb7bfaf 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/ExtractArchiveTask.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/ExtractArchiveTask.kt @@ -27,6 +27,7 @@ import org.gradle.api.file.DirectoryProperty import org.gradle.api.file.FileSystemOperations import org.gradle.api.file.RegularFileProperty import org.gradle.api.provider.Property +import org.gradle.api.provider.ProviderFactory import org.gradle.api.tasks.Input import org.gradle.api.tasks.InputFile import org.gradle.api.tasks.Optional @@ -84,6 +85,9 @@ public abstract class ExtractArchiveTask : DataConnectTaskBase(LOGGER_ID_PREFIX) @get:Inject internal abstract val fileSystemOperations: FileSystemOperations + @get:Inject + internal abstract val providerFactory: ProviderFactory + override fun newWorker(): DataConnectTaskBase.Worker = ExtractArchiveTaskWorkerImpl( archiveFile = archiveFile.get().asFile, outputDirectory = outputDirectory.get().asFile, diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/GenerateDataConnectSourcesTask.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/GenerateDataConnectSourcesTask.kt index 21b60913c3..88a6ee08e3 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/GenerateDataConnectSourcesTask.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/GenerateDataConnectSourcesTask.kt @@ -16,10 +16,7 @@ package com.google.firebase.example.dataconnect.gradle.tasks -import com.google.firebase.example.dataconnect.gradle.providers.MyVariantProviders import com.google.firebase.example.dataconnect.gradle.util.runCommand -import java.io.File -import javax.inject.Inject import org.gradle.api.DefaultTask import org.gradle.api.GradleException import org.gradle.api.file.DirectoryProperty @@ -35,6 +32,8 @@ import org.gradle.api.tasks.OutputDirectory import org.gradle.api.tasks.TaskAction import org.gradle.process.ExecOperations import org.yaml.snakeyaml.Yaml +import java.io.File +import javax.inject.Inject @CacheableTask public abstract class GenerateDataConnectSourcesTask : DefaultTask() { @@ -45,7 +44,7 @@ public abstract class GenerateDataConnectSourcesTask : DefaultTask() { @get:OutputDirectory public abstract val outputDirectory: DirectoryProperty - @get:Internal + @get:InputFile public abstract val nodeExecutable: RegularFileProperty @get:Internal @@ -111,14 +110,6 @@ public abstract class GenerateDataConnectSourcesTask : DefaultTask() { } } -internal fun GenerateDataConnectSourcesTask.configureFrom(providers: MyVariantProviders) { - dataConnectConfigDir.set(providers.projectProviders.dataConnectConfigDir) - firebaseExecutable.set(providers.firebaseExecutable) - TODO("uncomment line below nw8m8k5hjx") - //nodeExecutable.set(providers.projectProviders.nodeExecutable) - tweakedDataConnectConfigDir.set(providers.buildDirectory.map { it.dir("config") }) -} - private fun tweakConnectorYamlFiles(dir: File, newOutputDir: String, logger: Logger) { logger.info("Tweaking connector.yaml files in {}", dir.absolutePath) dir.walk().forEach { file -> diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/SetupFirebaseToolsTask.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/SetupFirebaseToolsTask.kt index 5036e22fcd..3584b659ad 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/SetupFirebaseToolsTask.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/SetupFirebaseToolsTask.kt @@ -16,10 +16,7 @@ package com.google.firebase.example.dataconnect.gradle.tasks -import com.google.firebase.example.dataconnect.gradle.providers.MyProjectProviders import com.google.firebase.example.dataconnect.gradle.util.runCommand -import java.io.File -import javax.inject.Inject import org.gradle.api.DefaultTask import org.gradle.api.file.DirectoryProperty import org.gradle.api.file.RegularFile @@ -32,6 +29,8 @@ import org.gradle.api.tasks.Internal import org.gradle.api.tasks.OutputDirectory import org.gradle.api.tasks.TaskAction import org.gradle.process.ExecOperations +import java.io.File +import javax.inject.Inject @CacheableTask public abstract class SetupFirebaseToolsTask : DefaultTask() { @@ -85,11 +84,3 @@ public abstract class SetupFirebaseToolsTask : DefaultTask() { } } } - -internal fun SetupFirebaseToolsTask.configureFrom(providers: MyProjectProviders) { - firebaseCliVersion.set(providers.firebaseCliVersion) - TODO("uncomment lines below yfcm22wmxt") - //npmExecutable.set(providers.npmExecutable) - //nodeExecutable.set(providers.nodeExecutable) - outputDirectory.set(providers.buildDirectory.map { it.dir("firebase-tools") }) -} diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/NodeJsPaths.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/util/NodeJsPaths.kt similarity index 80% rename from dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/NodeJsPaths.kt rename to dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/util/NodeJsPaths.kt index 25d255c9e9..278431e100 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/NodeJsPaths.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/util/NodeJsPaths.kt @@ -14,10 +14,9 @@ * limitations under the License. */ -package com.google.firebase.example.dataconnect.gradle.tasks +package com.google.firebase.example.dataconnect.gradle.util import com.google.firebase.example.dataconnect.gradle.DataConnectGradleException -import com.google.firebase.example.dataconnect.gradle.providers.OperatingSystem import org.gradle.api.provider.Provider import org.gradle.api.provider.ProviderFactory @@ -26,7 +25,9 @@ public class NodeJsPaths( public val archiveBaseFileName: String, public val archiveFileName: String, public val shasumsUrl: String, - public val shasumsFileName: String + public val shasumsFileName: String, + public val nodeExecutableRelativePath: String, + public val npmExecutableRelativePath: String, ) { public companion object } @@ -51,7 +52,9 @@ public fun NodeJsPaths.Companion.from( archiveBaseFileName = calculator.archiveBaseFileName(), archiveFileName = calculator.archiveFileName(), shasumsUrl = calculator.shasumsUrl(), - shasumsFileName = calculator.shasumsFileName() + shasumsFileName = calculator.shasumsFileName(), + nodeExecutableRelativePath = calculator.nodeExecutableRelativePath(), + npmExecutableRelativePath = calculator.npmExecutableRelativePath() ) } @@ -153,3 +156,28 @@ private fun NodeJsPathsCalculator.archiveBaseFileName(): String { return "node-v$nodeJsVersion-$osType-$osArch" } + +private fun NodeJsPathsCalculator.nodeExecutableRelativePath(): String = + when (operatingSystemType) { + OperatingSystem.Type.Windows -> "node.exe" + OperatingSystem.Type.MacOS, + OperatingSystem.Type.Linux -> "bin/node" + else -> throw DataConnectGradleException( + "unable to determine nodeexecutable path " + + "for operating system type: $operatingSystemType " + + "(error code bjs8et7w6a)" + ) + } + +private fun NodeJsPathsCalculator.npmExecutableRelativePath(): String = + when (operatingSystemType) { + OperatingSystem.Type.Windows -> "npm.cmd" + OperatingSystem.Type.MacOS, + OperatingSystem.Type.Linux -> "bin/node" + else -> throw DataConnectGradleException( + "unable to determine npm executable path " + + "for operating system type: $operatingSystemType " + + "(error code zrrsk9g5n4)" + ) + } + diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/providers/OperatingSystem.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/util/OperatingSystem.kt similarity index 89% rename from dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/providers/OperatingSystem.kt rename to dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/util/OperatingSystem.kt index e31115c806..cdd5314784 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/providers/OperatingSystem.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/util/OperatingSystem.kt @@ -14,12 +14,10 @@ * limitations under the License. */ -package com.google.firebase.example.dataconnect.gradle.providers +package com.google.firebase.example.dataconnect.gradle.util -import com.google.firebase.example.dataconnect.gradle.util.DataConnectGradleLogger import kotlinx.serialization.Serializable import org.gradle.api.GradleException -import org.gradle.api.logging.Logger import org.gradle.api.provider.Provider import org.gradle.api.provider.ProviderFactory import org.gradle.api.tasks.Input @@ -52,10 +50,7 @@ public data class OperatingSystem( public companion object } -internal fun ProviderFactory.operatingSystem( - logger: Logger -): Provider { - @Suppress("NAME_SHADOWING") val logger = DataConnectGradleLogger(loggerIdPrefix="os", logger) +internal fun ProviderFactory.operatingSystem(): Provider { val osNameSystemPropertyName = "os.name" val osArchitectureSystemPropertyName = "os.arch" val osNameProvider = systemProperty(osNameSystemPropertyName) @@ -64,8 +59,6 @@ internal fun ProviderFactory.operatingSystem( return provider { val osName = osNameProvider.orNull val osArchitecture = osArchitectureProvider.orNull - logger.info { "System property \"$osNameSystemPropertyName\": $osName" } - logger.info { "System property \"$osArchitectureSystemPropertyName\": $osArchitecture" } val type = osName?.let { OperatingSystem.Type.forName(it) } val architecture = osArchitecture?.let { OperatingSystem.Architecture.forName(it) } From ba411ed11c7ed718e9e3215aef82e806648b344b Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Fri, 29 Nov 2024 11:07:46 +0000 Subject: [PATCH 79/79] ktlint --- .../gradle/DataConnectGradlePlugin.kt | 12 ++-- .../gradle/DataConnectTasksConfigurer.kt | 70 ++++++++++--------- .../gradle/tasks/DataConnectTaskBase.kt | 21 +++--- ...loadNodeJsBinaryDistributionArchiveTask.kt | 19 +++-- .../gradle/tasks/ExtractArchiveTask.kt | 36 +++++----- .../tasks/GenerateDataConnectSourcesTask.kt | 4 +- .../gradle/tasks/SetupFirebaseToolsTask.kt | 4 +- .../gradle/util/ArchiveExtraction.kt | 33 +++++---- .../dataconnect/gradle/util/NodeJsPaths.kt | 28 +++++--- .../gradle/util/OperatingSystem.kt | 12 ++-- 10 files changed, 132 insertions(+), 107 deletions(-) diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectGradlePlugin.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectGradlePlugin.kt index aa13c0d349..548a1acd70 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectGradlePlugin.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectGradlePlugin.kt @@ -21,17 +21,19 @@ import com.google.firebase.example.dataconnect.gradle.tasks.DownloadNodeJsBinary import com.google.firebase.example.dataconnect.gradle.tasks.ExtractArchiveTask import com.google.firebase.example.dataconnect.gradle.tasks.GenerateDataConnectSourcesTask import com.google.firebase.example.dataconnect.gradle.tasks.SetupFirebaseToolsTask +import java.util.Locale import org.gradle.api.Plugin import org.gradle.api.Project import org.gradle.kotlin.dsl.getByType import org.gradle.kotlin.dsl.register -import java.util.Locale @Suppress("unused") public abstract class DataConnectGradlePlugin : Plugin { override fun apply(project: Project) { - val downloadNodeJsArchiveTask = project.tasks.register("dataConnectDownloadNodeJs") + val downloadNodeJsArchiveTask = project.tasks.register( + "dataConnectDownloadNodeJs" + ) val extractNodeJsArchiveTask = project.tasks.register("dataConnectExtractNodeJs") val setupFirebaseToolsTask = project.tasks.register("dataConnectSetupFirebaseTools") @@ -42,9 +44,8 @@ public abstract class DataConnectGradlePlugin : Plugin { extractNodeJsArchiveTask = extractNodeJsArchiveTask, setupFirebaseToolsTask = setupFirebaseToolsTask, buildDirectory = project.layout.buildDirectory.dir("dataConnect"), - projectLayout = project.layout, - providerFactory = project.providers, - logger = project.logger + projectDirectory = project.layout.projectDirectory, + providerFactory = project.providers ) configurer.invoke() @@ -62,5 +63,4 @@ public abstract class DataConnectGradlePlugin : Plugin { ) } } - } diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectTasksConfigurer.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectTasksConfigurer.kt index d3d9dc0313..23c22b5c6a 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectTasksConfigurer.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/DataConnectTasksConfigurer.kt @@ -10,9 +10,7 @@ import com.google.firebase.example.dataconnect.gradle.util.nodeJsPaths import com.google.firebase.example.dataconnect.gradle.util.operatingSystem import org.gradle.api.GradleException import org.gradle.api.file.Directory -import org.gradle.api.file.ProjectLayout import org.gradle.api.file.RegularFile -import org.gradle.api.logging.Logger import org.gradle.api.provider.Provider import org.gradle.api.provider.ProviderFactory import org.gradle.api.tasks.TaskProvider @@ -23,8 +21,8 @@ internal class DataConnectTasksConfigurer( private val extractNodeJsArchiveTask: TaskProvider, private val setupFirebaseToolsTask: TaskProvider, private val buildDirectory: Provider, - projectLayout: ProjectLayout, - providerFactory: ProviderFactory, + projectDirectory: Directory, + providerFactory: ProviderFactory ) : () -> Unit { override fun invoke() { @@ -38,24 +36,25 @@ internal class DataConnectTasksConfigurer( dataConnectExtension.nodeJsVersion ?: throw GradleException( "dataconnect.nodeJsVersion must be set in " + - "build.gradle or build.gradle.kts to " + - "specify the version of Node.js (https://nodejs.org) " + - "to install (e.g. \"20.9.0\") (error code 3acj27az2c)" + "build.gradle or build.gradle.kts to " + + "specify the version of Node.js (https://nodejs.org) " + + "to install (e.g. \"20.9.0\") (error code 3acj27az2c)" ) } private val operatingSystem: Provider = providerFactory.operatingSystem() - private val nodeJsPaths: Provider = providerFactory.nodeJsPaths(nodeJsVersion, operatingSystem) + private val nodeJsPaths: Provider = + providerFactory.nodeJsPaths(nodeJsVersion, operatingSystem) private val firebaseCliVersion: Provider = providerFactory.provider { dataConnectExtension.firebaseCliVersion ?: throw GradleException( "dataconnect.firebaseCliVersion must be set in " + - "build.gradle or build.gradle.kts to " + - "specify the version of the Firebase CLI npm package " + - "(https://www.npmjs.com/package/firebase-tools) to use " + - "(e.g. \"13.25.0\") (error code xbmvkc3mtr)" + "build.gradle or build.gradle.kts to " + + "specify the version of the Firebase CLI npm package " + + "(https://www.npmjs.com/package/firebase-tools) to use " + + "(e.g. \"13.25.0\") (error code xbmvkc3mtr)" ) } @@ -73,7 +72,7 @@ internal class DataConnectTasksConfigurer( private val dataConnectConfigDir: Provider = providerFactory.provider { dataConnectExtension.dataConnectConfigDir?.let { - projectLayout.projectDirectory.dir(it.path) + projectDirectory.dir(it.path) } } @@ -83,13 +82,17 @@ internal class DataConnectTasksConfigurer( archiveUrl.set(nodeJsPaths.map { it.archiveUrl }) shasumsUrl.set(nodeJsPaths.map { it.shasumsUrl }) - archiveFile.set(providerFactory.zip(buildDirectory, nodeJsPaths) { buildDirectory, nodeJsPaths -> - buildDirectory.file(nodeJsPaths.archiveFileName) - }) - - shasumsFile.set(providerFactory.zip(buildDirectory, nodeJsPaths) { buildDirectory, nodeJsPaths -> - buildDirectory.file(nodeJsPaths.shasumsFileName) - }) + archiveFile.set( + providerFactory.zip(buildDirectory, nodeJsPaths) { buildDirectory, nodeJsPaths -> + buildDirectory.file(nodeJsPaths.archiveFileName) + } + ) + + shasumsFile.set( + providerFactory.zip(buildDirectory, nodeJsPaths) { buildDirectory, nodeJsPaths -> + buildDirectory.file(nodeJsPaths.shasumsFileName) + } + ) } private fun configureExtractNodeJsArchiveTask() = extractNodeJsArchiveTask.configure { @@ -98,9 +101,11 @@ internal class DataConnectTasksConfigurer( pathPrefixComponentStripCount.set(1) archiveFile.set(downloadNodeJsArchiveTask.flatMap { it.archiveFile }) - outputDirectory.set(providerFactory.zip(buildDirectory, nodeJsPaths) { buildDirectory, nodeJsPaths -> - buildDirectory.dir(nodeJsPaths.archiveBaseFileName) - }) + outputDirectory.set( + providerFactory.zip(buildDirectory, nodeJsPaths) { buildDirectory, nodeJsPaths -> + buildDirectory.dir(nodeJsPaths.archiveBaseFileName) + } + ) } private fun configureSetupFirebaseToolsTask() = setupFirebaseToolsTask.configure { @@ -113,18 +118,21 @@ internal class DataConnectTasksConfigurer( npmExecutable.set(this@DataConnectTasksConfigurer.npmExecutable) } - fun configureGenerateDataConnectSourcesTask(task: TaskProvider, variantName: String) = task.configure { + fun configureGenerateDataConnectSourcesTask( + task: TaskProvider, + variantName: String + ) = task.configure { group = TASK_GROUP @Suppress("ktlint:standard:max-line-length") setOnlyIf( "dataconnect.dataConnectConfigDir is null; to enable the \"$name\" task, " + - "set dataconnect.dataConnectConfigDir in build.gradle or build.gradle.kts to " + - "the directory that defines the Data Connect schema and " + - "connectors whose Kotlin code to generate code. That is, the directory " + - "containing the dataconnect.yaml file. For details, see " + - "https://firebase.google.com/docs/data-connect/configuration-reference#dataconnect.yaml-configuration " + - "(e.g. file(\"../dataconnect\")) (message code a3ch245mbd)" + "set dataconnect.dataConnectConfigDir in build.gradle or build.gradle.kts to " + + "the directory that defines the Data Connect schema and " + + "connectors whose Kotlin code to generate code. That is, the directory " + + "containing the dataconnect.yaml file. For details, see " + + "https://firebase.google.com/docs/data-connect/configuration-reference#dataconnect.yaml-configuration " + + "(e.g. file(\"../dataconnect\")) (message code a3ch245mbd)" ) { dataConnectConfigDir.isPresent } dataConnectConfigDir.set(this@DataConnectTasksConfigurer.dataConnectConfigDir) @@ -132,8 +140,6 @@ internal class DataConnectTasksConfigurer( nodeExecutable.set(this@DataConnectTasksConfigurer.nodeExecutable) tweakedDataConnectConfigDir.set(buildDirectory.map { it.dir("variants/$variantName/config") }) } - } private const val TASK_GROUP = "Firebase Data Connect" - diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DataConnectTaskBase.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DataConnectTaskBase.kt index 7b1fec50a0..6d322158f1 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DataConnectTaskBase.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DataConnectTaskBase.kt @@ -19,16 +19,16 @@ package com.google.firebase.example.dataconnect.gradle.tasks import com.google.firebase.example.dataconnect.gradle.DataConnectGradleException import com.google.firebase.example.dataconnect.gradle.tasks.DataConnectTaskBase.Worker import com.google.firebase.example.dataconnect.gradle.util.DataConnectGradleLogger -import kotlin.reflect.full.allSupertypes -import org.gradle.api.DefaultTask -import org.gradle.api.file.FileSystemOperations -import org.gradle.api.tasks.Internal -import org.gradle.api.tasks.TaskAction import java.io.File import java.nio.file.Files import java.util.Date +import kotlin.reflect.full.allSupertypes import kotlin.time.DurationUnit import kotlin.time.toDuration +import org.gradle.api.DefaultTask +import org.gradle.api.file.FileSystemOperations +import org.gradle.api.tasks.Internal +import org.gradle.api.tasks.TaskAction public abstract class DataConnectTaskBase(loggerIdPrefix: String) : DefaultTask() { @@ -56,8 +56,8 @@ public abstract class DataConnectTaskBase(loggerIdPrefix: String) : DefaultTask( val elapsedTime = endTime - startTime dataConnectLogger.info { "Task $path completed execution in " + - elapsedTime.toString(DurationUnit.SECONDS, 2) + - " at ${Date()}" + elapsedTime.toString(DurationUnit.SECONDS, 2) + + " at ${Date()}" } result.onFailure { @@ -84,7 +84,7 @@ internal fun Worker.deleteDirectory(dir: File, fileSystemOperations: FileSystemO result.onFailure { throw DataConnectGradleException( "unable to delete directory: ${dir.absolutePath}: $it " + - "(error code 6trngh6x47)", + "(error code 6trngh6x47)", it ) } @@ -97,7 +97,7 @@ internal fun Worker.deleteFile(file: File) { result.onFailure { throw DataConnectGradleException( "unable to delete file: ${file.absolutePath}: $it " + - "(error code rprr987jqk)", + "(error code rprr987jqk)", it ) } @@ -110,7 +110,8 @@ internal fun Worker.createDirectory(dir: File) { result.onFailure { throw DataConnectGradleException( "unable to create directory: ${dir.absolutePath}: $it " + - "(error code j7x4sw7w95)", it + "(error code j7x4sw7w95)", + it ) } } diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsBinaryDistributionArchiveTask.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsBinaryDistributionArchiveTask.kt index 220253671b..5215c78cc2 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsBinaryDistributionArchiveTask.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/DownloadNodeJsBinaryDistributionArchiveTask.kt @@ -23,6 +23,8 @@ import com.google.firebase.example.dataconnect.gradle.util.FileDownloader import com.google.firebase.example.dataconnect.gradle.util.Sha256SignatureVerifier import com.google.firebase.example.dataconnect.gradle.util.addCertificatesFromKeyListResource import com.google.firebase.example.dataconnect.gradle.util.addHashesFromShasumsFile +import java.io.File +import javax.inject.Inject import kotlinx.coroutines.runBlocking import org.gradle.api.file.FileSystemOperations import org.gradle.api.file.RegularFileProperty @@ -31,8 +33,6 @@ import org.gradle.api.provider.ProviderFactory import org.gradle.api.tasks.CacheableTask import org.gradle.api.tasks.Input import org.gradle.api.tasks.OutputFile -import java.io.File -import javax.inject.Inject @CacheableTask public abstract class DownloadNodeJsBinaryDistributionArchiveTask : DataConnectTaskBase(LOGGER_ID_PREFIX) { @@ -99,7 +99,6 @@ public abstract class DownloadNodeJsBinaryDistributionArchiveTask : DataConnectT val fileDownloader: FileDownloader } - private companion object { const val LOGGER_ID_PREFIX = "dnb" } @@ -138,10 +137,20 @@ private fun Worker.run() { fileDownloader.download(shasumsUrl, shasumsFile, maxNumDownloadBytes = 100_000) } - verifyNodeJsReleaseSignature(file =archiveFile, shasumsFile =shasumsFile, keyListResourcePath="com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/keys.list", logger) + verifyNodeJsReleaseSignature( + file = archiveFile, + shasumsFile = shasumsFile, + keyListResourcePath = "com/google/firebase/example/dataconnect/gradle/nodejs_release_signing_keys/keys.list", + logger + ) } -private fun verifyNodeJsReleaseSignature(file: File, shasumsFile: File, keyListResourcePath: String, logger: DataConnectGradleLogger) { +private fun verifyNodeJsReleaseSignature( + file: File, + shasumsFile: File, + keyListResourcePath: String, + logger: DataConnectGradleLogger +) { val signatureVerifier = Sha256SignatureVerifier() logger.info { "Loading Node.js release signing certificates " + diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/ExtractArchiveTask.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/ExtractArchiveTask.kt index 6f8fb7bfaf..d49048e10d 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/ExtractArchiveTask.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/ExtractArchiveTask.kt @@ -23,6 +23,9 @@ import com.google.firebase.example.dataconnect.gradle.util.DataConnectGradleLogg import com.google.firebase.example.dataconnect.gradle.util.ExtractArchiveCallbacks import com.google.firebase.example.dataconnect.gradle.util.extractArchive import com.google.firebase.example.dataconnect.gradle.util.toFormattedString +import java.io.File +import java.util.concurrent.atomic.AtomicLong +import javax.inject.Inject import org.gradle.api.file.DirectoryProperty import org.gradle.api.file.FileSystemOperations import org.gradle.api.file.RegularFileProperty @@ -33,9 +36,6 @@ import org.gradle.api.tasks.InputFile import org.gradle.api.tasks.Optional import org.gradle.api.tasks.OutputDirectory import org.gradle.work.DisableCachingByDefault -import java.io.File -import java.util.concurrent.atomic.AtomicLong -import javax.inject.Inject @DisableCachingByDefault(because = "extracting an archive is a quick operation not worth caching") public abstract class ExtractArchiveTask : DataConnectTaskBase(LOGGER_ID_PREFIX) { @@ -89,14 +89,14 @@ public abstract class ExtractArchiveTask : DataConnectTaskBase(LOGGER_ID_PREFIX) internal abstract val providerFactory: ProviderFactory override fun newWorker(): DataConnectTaskBase.Worker = ExtractArchiveTaskWorkerImpl( - archiveFile = archiveFile.get().asFile, - outputDirectory = outputDirectory.get().asFile, - prefixStripCount = pathPrefixComponentStripCount.orNull ?: 0, - fileSystemOperations = fileSystemOperations, - logger = dataConnectLogger - ) - - internal interface Worker: DataConnectTaskBase.Worker { + archiveFile = archiveFile.get().asFile, + outputDirectory = outputDirectory.get().asFile, + prefixStripCount = pathPrefixComponentStripCount.orNull ?: 0, + fileSystemOperations = fileSystemOperations, + logger = dataConnectLogger + ) + + internal interface Worker : DataConnectTaskBase.Worker { val archiveFile: File val outputDirectory: File val prefixStripCount: Int @@ -106,7 +106,6 @@ public abstract class ExtractArchiveTask : DataConnectTaskBase(LOGGER_ID_PREFIX) private companion object { const val LOGGER_ID_PREFIX = "ear" } - } private class ExtractArchiveTaskWorkerImpl( @@ -127,8 +126,10 @@ private fun Worker.run() { logger.info { "prefixStripCount: $prefixStripCount" } if (prefixStripCount < 0) { - throw IllegalArgumentException("invalid prefixStripCount: $prefixStripCount " + - "(must be greater than or equal to zero) (error code mn8pp2b7mc)") + throw IllegalArgumentException( + "invalid prefixStripCount: $prefixStripCount " + + "(must be greater than or equal to zero) (error code mn8pp2b7mc)" + ) } deleteDirectory(outputDirectory, fileSystemOperations) @@ -146,8 +147,8 @@ private fun Worker.run() { val symlinkCountStr = extractCallbacks.extractedSymlinkCount.toFormattedString() val byteCountStr = extractCallbacks.extractedByteCount.toFormattedString() "Extracted $fileCountStr files ($byteCountStr bytes) " + - "and $symlinkCountStr symlinks " + - "from ${archiveFile.absolutePath} to ${outputDirectory.absolutePath}" + "and $symlinkCountStr symlinks " + + "from ${archiveFile.absolutePath} to ${outputDirectory.absolutePath}" } } @@ -189,5 +190,4 @@ private class ExtractArchiveCallbacksImpl(private val file: File, private val lo "Ignoring failure to set ${metadataType.name} " + "on extracted file: ${file.absolutePath}" } } - -} \ No newline at end of file +} diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/GenerateDataConnectSourcesTask.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/GenerateDataConnectSourcesTask.kt index 88a6ee08e3..a5bc01b04b 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/GenerateDataConnectSourcesTask.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/GenerateDataConnectSourcesTask.kt @@ -17,6 +17,8 @@ package com.google.firebase.example.dataconnect.gradle.tasks import com.google.firebase.example.dataconnect.gradle.util.runCommand +import java.io.File +import javax.inject.Inject import org.gradle.api.DefaultTask import org.gradle.api.GradleException import org.gradle.api.file.DirectoryProperty @@ -32,8 +34,6 @@ import org.gradle.api.tasks.OutputDirectory import org.gradle.api.tasks.TaskAction import org.gradle.process.ExecOperations import org.yaml.snakeyaml.Yaml -import java.io.File -import javax.inject.Inject @CacheableTask public abstract class GenerateDataConnectSourcesTask : DefaultTask() { diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/SetupFirebaseToolsTask.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/SetupFirebaseToolsTask.kt index 3584b659ad..c03e83ae7c 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/SetupFirebaseToolsTask.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/tasks/SetupFirebaseToolsTask.kt @@ -17,6 +17,8 @@ package com.google.firebase.example.dataconnect.gradle.tasks import com.google.firebase.example.dataconnect.gradle.util.runCommand +import java.io.File +import javax.inject.Inject import org.gradle.api.DefaultTask import org.gradle.api.file.DirectoryProperty import org.gradle.api.file.RegularFile @@ -29,8 +31,6 @@ import org.gradle.api.tasks.Internal import org.gradle.api.tasks.OutputDirectory import org.gradle.api.tasks.TaskAction import org.gradle.process.ExecOperations -import java.io.File -import javax.inject.Inject @CacheableTask public abstract class SetupFirebaseToolsTask : DefaultTask() { diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/util/ArchiveExtraction.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/util/ArchiveExtraction.kt index f209205606..dc7a37ee14 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/util/ArchiveExtraction.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/util/ArchiveExtraction.kt @@ -16,12 +16,6 @@ package com.google.firebase.example.dataconnect.gradle.util -import org.apache.commons.compress.archivers.ArchiveEntry -import org.apache.commons.compress.archivers.ArchiveInputStream -import org.apache.commons.compress.archivers.tar.TarArchiveEntry -import org.apache.commons.compress.archivers.tar.TarArchiveInputStream -import org.apache.commons.compress.compressors.xz.XZCompressorInputStream -import org.apache.commons.compress.compressors.xz.XZUtils import java.io.File import java.io.IOException import java.io.InputStream @@ -29,6 +23,12 @@ import java.nio.file.Files import java.nio.file.Paths import java.nio.file.attribute.FileTime import java.nio.file.attribute.PosixFilePermission +import org.apache.commons.compress.archivers.ArchiveEntry +import org.apache.commons.compress.archivers.ArchiveInputStream +import org.apache.commons.compress.archivers.tar.TarArchiveEntry +import org.apache.commons.compress.archivers.tar.TarArchiveInputStream +import org.apache.commons.compress.compressors.xz.XZCompressorInputStream +import org.apache.commons.compress.compressors.xz.XZUtils internal fun File.extractArchive(destDir: File, configure: ExtractArchiveConfigBuilder.() -> Unit = {}) { if (XZUtils.isCompressedFileName(name)) { @@ -38,8 +38,8 @@ internal fun File.extractArchive(destDir: File, configure: ExtractArchiveConfigB } else { throw UnsupportedArchiveException( "don't know how to extract $name; " + - "supported archive formats are .7z and .tar.xz " + - "(error code vm9w6kmaby)" + "supported archive formats are .7z and .tar.xz " + + "(error code vm9w6kmaby)" ) } } @@ -127,7 +127,6 @@ private fun extractTarArchive(inputStream: InputStream, config: ExtractArchiveCo continue } } - } } @@ -198,12 +197,12 @@ private object ExtractArchiveCallbacksStubImpl : ExtractArchiveCallbacks { internal enum class ArchiveType { TarXz, - SevenZip, + SevenZip } internal enum class ArchiveSetFileMetadataType { LastModifiedTime, - PosixFilePermissions, + PosixFilePermissions } @JvmName("childWithPathPrefixComponentsStrippedFileExt") @@ -217,8 +216,8 @@ private fun childWithPathPrefixComponentsStripped(file: File, childPath: String, if (pathSeparators.any { childPath.startsWith(it) }) { throw InvalidArchivePathException( "unable to extract file: $childPath " + - "(must not be an absolute path)" + - "(error code t827vf36ac)" + "(must not be an absolute path)" + + "(error code t827vf36ac)" ) } @@ -229,9 +228,9 @@ private fun childWithPathPrefixComponentsStripped(file: File, childPath: String, if (index < 0) { throw MissingPathPrefixException( "the given childPath was expected to have at least " + - "$pathPrefixStripCount leading path components, " + - "but there were only $prefixComponentIndex " + - "(error code radpfa8nc9)" + "$pathPrefixStripCount leading path components, " + + "but there were only $prefixComponentIndex " + + "(error code radpfa8nc9)" ) } deleteRange(0, index + 1) @@ -246,4 +245,4 @@ private fun childWithPathPrefixComponentsStripped(file: File, childPath: String, private class InvalidArchivePathException(message: String) : Exception(message) -private class MissingPathPrefixException(message: String) : Exception(message) \ No newline at end of file +private class MissingPathPrefixException(message: String) : Exception(message) diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/util/NodeJsPaths.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/util/NodeJsPaths.kt index 278431e100..0da15e1db8 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/util/NodeJsPaths.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/util/NodeJsPaths.kt @@ -27,7 +27,7 @@ public class NodeJsPaths( public val shasumsUrl: String, public val shasumsFileName: String, public val nodeExecutableRelativePath: String, - public val npmExecutableRelativePath: String, + public val npmExecutableRelativePath: String ) { public companion object } @@ -36,8 +36,11 @@ internal fun ProviderFactory.nodeJsPaths( nodeJsVersion: Provider, operatingSystem: Provider ): Provider = provider { - @Suppress("NAME_SHADOWING") val nodeJsVersion: String = nodeJsVersion.get() - @Suppress("NAME_SHADOWING") val operatingSystem: OperatingSystem = operatingSystem.get() + @Suppress("NAME_SHADOWING") + val nodeJsVersion: String = nodeJsVersion.get() + + @Suppress("NAME_SHADOWING") + val operatingSystem: OperatingSystem = operatingSystem.get() NodeJsPaths.from(nodeJsVersion, operatingSystem.type, operatingSystem.architecture) } @@ -46,7 +49,11 @@ public fun NodeJsPaths.Companion.from( operatingSystemType: OperatingSystem.Type, operatingSystemArchitecture: OperatingSystem.Architecture ): NodeJsPaths { - val calculator = NodeJsPathsCalculator(nodeJsVersion, operatingSystemType, operatingSystemArchitecture) + val calculator = NodeJsPathsCalculator( + nodeJsVersion, + operatingSystemType, + operatingSystemArchitecture + ) return NodeJsPaths( archiveUrl = calculator.archiveUrl(), archiveBaseFileName = calculator.archiveBaseFileName(), @@ -64,7 +71,8 @@ private class NodeJsPathsCalculator( val operatingSystemArchitecture: OperatingSystem.Architecture ) -private fun NodeJsPathsCalculator.urlForFileWithName(fileName: String): String = "https://nodejs.org/dist/v$nodeJsVersion/$fileName" +private fun NodeJsPathsCalculator.urlForFileWithName(fileName: String): String = + "https://nodejs.org/dist/v$nodeJsVersion/$fileName" /** * The URL to download the Node.js binary distribution. @@ -81,7 +89,6 @@ private fun NodeJsPathsCalculator.urlForFileWithName(fileName: String): String = */ private fun NodeJsPathsCalculator.archiveUrl(): String = urlForFileWithName(archiveFileName()) - @Suppress("UnusedReceiverParameter") private fun NodeJsPathsCalculator.shasumsFileName(): String = "SHASUMS256.txt.asc" @@ -100,7 +107,8 @@ private fun NodeJsPathsCalculator.shasumsUrl(): String = urlForFileWithName(shas * * node-v20.9.0-win-x64.7z * * node-v20.9.0-win-x86.7z */ -private fun NodeJsPathsCalculator.archiveFileName(): String = "${archiveBaseFileName()}${archiveFileNameSuffix()}" +private fun NodeJsPathsCalculator.archiveFileName(): String = + "${archiveBaseFileName()}${archiveFileNameSuffix()}" /** * The suffix of the file name download for the Node.js binary distribution. @@ -115,12 +123,11 @@ private fun NodeJsPathsCalculator.archiveFileNameSuffix(): String = when (operat OperatingSystem.Type.Linux -> ".tar.xz" else -> throw DataConnectGradleException( "unable to determine Node.js download file name suffix " + - "for operating system type: $operatingSystemType " + - "(error code ead53smf45)" + "for operating system type: $operatingSystemType " + + "(error code ead53smf45)" ) } - /** * The base file name of the download for the Node.js binary distribution; * that is, the file name without the ".7z" or ".tar.xz" extension. @@ -180,4 +187,3 @@ private fun NodeJsPathsCalculator.npmExecutableRelativePath(): String = "(error code zrrsk9g5n4)" ) } - diff --git a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/util/OperatingSystem.kt b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/util/OperatingSystem.kt index cdd5314784..64838062c4 100644 --- a/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/util/OperatingSystem.kt +++ b/dataconnect/buildSrc/src/main/kotlin/com/google/firebase/example/dataconnect/gradle/util/OperatingSystem.kt @@ -83,7 +83,10 @@ internal fun ProviderFactory.operatingSystem(): Provider { * @param osName the name of the operating system whose value to return; this value should be one that was * returned from [System.getProperty] called with `"os.name"`. */ -public fun OperatingSystem.Type.Companion.forName(osName: String): OperatingSystem.Type? = forLowerCaseName(osName.lowercase()) +public fun OperatingSystem.Type.Companion.forName(osName: String): OperatingSystem.Type? = + forLowerCaseName( + osName.lowercase() + ) // NOTE: This logic was adapted from // https://github.com/gradle/gradle/blob/99d83f56d6/platforms/core-runtime/base-services/src/main/java/org/gradle/internal/os/OperatingSystem.java#L63-L79 @@ -105,10 +108,11 @@ private fun OperatingSystem.Type.Companion.forLowerCaseName(osName: String): Ope /** * Returns the [OperatingSystem.Type] for the given operating system name. * - * @param osArch the name of the operating system whose value to return; this value should be one that was - * returned from [System.getProperty] called with `"os.name"`. + * @param osArch the name of the operating system whose value to return; this value should be one + * that was returned from [System.getProperty] called with `"os.name"`. */ -public fun OperatingSystem.Architecture.Companion.forName(osArch: String): OperatingSystem.Architecture? = forLowerCaseName(osArch.lowercase()) +public fun OperatingSystem.Architecture.Companion.forName(osArch: String): OperatingSystem.Architecture? = + forLowerCaseName(osArch.lowercase()) // NOTE: This logic was adapted from // https://github.com/gradle/gradle/blob/e745e6d369/platforms/native/platform-native/src/main/java/org/gradle/nativeplatform/platform/internal/Architectures.java#L26-L42