Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Add Firebase Data Connect to CI #2540

Closed
wants to merge 18 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions .github/workflows/android.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
15 changes: 11 additions & 4 deletions dataconnect/app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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)
Expand All @@ -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")
}
45 changes: 45 additions & 0 deletions dataconnect/buildSrc/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -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()
}
}
15 changes: 15 additions & 0 deletions dataconnect/buildSrc/settings.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
pluginManagement {
repositories {
gradlePluginPortal()
google()
mavenCentral()
}
}

dependencyResolutionManagement {
repositories {
google()
mavenCentral()
}
versionCatalogs { create("libs") { from(files("../../gradle/libs.versions.toml")) } }
}
Original file line number Diff line number Diff line change
@@ -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") })
}
}
Original file line number Diff line number Diff line change
@@ -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
}
}
Original file line number Diff line number Diff line change
@@ -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<Any?>(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) }
}
Original file line number Diff line number Diff line change
@@ -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?
}
Loading
Loading