diff --git a/firebase-dataconnect/gradleplugin/plugin/src/main/kotlin/com/google/firebase/dataconnect/gradle/plugin/DataConnectExecutableLauncher.kt b/firebase-dataconnect/gradleplugin/plugin/src/main/kotlin/com/google/firebase/dataconnect/gradle/plugin/DataConnectExecutableLauncher.kt index 0e4fb24822b..5d77007c830 100644 --- a/firebase-dataconnect/gradleplugin/plugin/src/main/kotlin/com/google/firebase/dataconnect/gradle/plugin/DataConnectExecutableLauncher.kt +++ b/firebase-dataconnect/gradleplugin/plugin/src/main/kotlin/com/google/firebase/dataconnect/gradle/plugin/DataConnectExecutableLauncher.kt @@ -21,10 +21,12 @@ import org.gradle.api.Task interface DataConnectExecutableConfig { var outputDirectory: File? var connectors: Collection + var connectorId: Collection var listen: String? var localConnectionString: String? var logFile: File? var schemaExtensionsOutputEnabled: Boolean? + var platform: String? } fun Task.runDataConnectExecutable( @@ -37,10 +39,12 @@ fun Task.runDataConnectExecutable( object : DataConnectExecutableConfig { override var outputDirectory: File? = null override var connectors: Collection = emptyList() + override var connectorId: Collection = emptyList() override var listen: String? = null override var localConnectionString: String? = null override var logFile: File? = null override var schemaExtensionsOutputEnabled: Boolean? = null + override var platform: String? = null } .apply(configure) @@ -76,7 +80,13 @@ fun Task.runDataConnectExecutable( args("-connectors=${it.joinToString(",")}") } } + config.connectorId.let { + if (it.isNotEmpty()) { + args("-connector_id=${it.joinToString(",")}") + } + } config.listen?.let { args("-listen=${it}") } + config.platform?.let { args("-platform=${it}") } config.localConnectionString?.let { args("-local_connection_string=${it}") } config.schemaExtensionsOutputEnabled?.let { args("-enable_output_schema_extensions=${it}") } } diff --git a/firebase-dataconnect/gradleplugin/plugin/src/main/kotlin/com/google/firebase/dataconnect/gradle/plugin/DataConnectGenerateCodeTask.kt b/firebase-dataconnect/gradleplugin/plugin/src/main/kotlin/com/google/firebase/dataconnect/gradle/plugin/DataConnectGenerateCodeTask.kt index d30d24cb6e1..c25ae04bd0c 100644 --- a/firebase-dataconnect/gradleplugin/plugin/src/main/kotlin/com/google/firebase/dataconnect/gradle/plugin/DataConnectGenerateCodeTask.kt +++ b/firebase-dataconnect/gradleplugin/plugin/src/main/kotlin/com/google/firebase/dataconnect/gradle/plugin/DataConnectGenerateCodeTask.kt @@ -15,12 +15,14 @@ */ package com.google.firebase.dataconnect.gradle.plugin +import com.google.firebase.dataconnect.gradle.plugin.DataConnectGenerateCodeTask.CallingConvention import java.io.File +import javax.inject.Inject import org.gradle.api.DefaultTask -import org.gradle.api.Task import org.gradle.api.file.DirectoryProperty 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.InputFiles @@ -28,11 +30,15 @@ import org.gradle.api.tasks.Internal import org.gradle.api.tasks.Optional import org.gradle.api.tasks.OutputDirectory import org.gradle.api.tasks.TaskAction +import org.gradle.process.ExecOperations +import org.slf4j.Logger abstract class DataConnectGenerateCodeTask : DefaultTask() { @get:InputFile abstract val dataConnectExecutable: RegularFileProperty + @get:Input abstract val dataConnectExecutableCallingConvention: Property + @get:Optional @get:InputFiles abstract val configDirectory: DirectoryProperty @get:Input abstract val connectors: Property> @@ -43,9 +49,24 @@ abstract class DataConnectGenerateCodeTask : DefaultTask() { @get:Optional @get:InputFile abstract val ktfmtJarFile: RegularFileProperty + @get:Inject abstract val execOperations: ExecOperations + + /** + * The subcommand of the Data Connect executable to use to perform code generation. + * + * In August 2025 the subcommand was changed by cl/795582011 from "gradle generate" to "sdk + * generate -platform=kotlin". The "gradle generate" command was last supported in version 2.11.0 + * of the Data Connect executable. + */ + enum class CallingConvention { + GRADLE, + SDK_GENERATE, + } + @TaskAction fun run() { val dataConnectExecutable: File = dataConnectExecutable.get().asFile + val dataConnectExecutableCallingConvention = dataConnectExecutableCallingConvention.get() val configDirectory: File? = configDirectory.orNull?.asFile val connectors: Collection = connectors.get().distinct().sorted() val buildDirectory: File = buildDirectory.get().asFile @@ -53,6 +74,7 @@ abstract class DataConnectGenerateCodeTask : DefaultTask() { val ktfmtJarFile: File? = ktfmtJarFile.orNull?.asFile logger.info("dataConnectExecutable={}", dataConnectExecutable.absolutePath) + logger.info("dataConnectExecutableCallingConvention={}", dataConnectExecutableCallingConvention) logger.info("configDirectory={}", configDirectory?.absolutePath) logger.info("connectors={}", connectors.joinToString(", ")) logger.info("buildDirectory={}", buildDirectory.absolutePath) @@ -69,14 +91,26 @@ abstract class DataConnectGenerateCodeTask : DefaultTask() { return } + val subCommand = + when (dataConnectExecutableCallingConvention) { + CallingConvention.GRADLE -> listOf("gradle", "generate") + CallingConvention.SDK_GENERATE -> listOf("sdk", "generate") + } + runDataConnectExecutable( dataConnectExecutable = dataConnectExecutable, - subCommand = listOf("gradle", "generate"), + subCommand = subCommand, configDirectory = configDirectory, ) { - this.connectors = connectors + when (dataConnectExecutableCallingConvention) { + CallingConvention.GRADLE -> this.connectors = connectors + CallingConvention.SDK_GENERATE -> this.connectorId = connectors + } this.outputDirectory = outputDirectory this.logFile = File(buildDirectory, "codegen.log.txt") + if (dataConnectExecutableCallingConvention == CallingConvention.SDK_GENERATE) { + this.platform = "kotlin" + } } if (ktfmtJarFile !== null) { @@ -92,7 +126,7 @@ abstract class DataConnectGenerateCodeTask : DefaultTask() { } } -private fun Task.runKtfmt( +private fun DataConnectGenerateCodeTask.runKtfmt( ktfmtJarFile: File, directory: File, logFile: File, @@ -123,3 +157,80 @@ private fun Task.runKtfmt( } } } + +fun DataConnectGenerateCodeTask.detectedCallingConvention( + dataConnectExecutable: RegularFileProperty = this.dataConnectExecutable, + buildDirectory: DirectoryProperty = this.buildDirectory, + execOperations: ExecOperations = this.execOperations, + logger: Logger = this.logger +): Provider = + dataConnectExecutable.map { + determineCallingConvention( + dataConnectExecutable = it.asFile, + workDirectory = File(buildDirectory.get().asFile, "determineCallingConvention"), + execOperations = execOperations, + logger = logger, + ) + } + +private fun determineCallingConvention( + dataConnectExecutable: File, + workDirectory: File, + execOperations: ExecOperations, + logger: Logger, +): CallingConvention { + logger.info( + "Determining calling convention of Data Connect executable: {}", + dataConnectExecutable.absolutePath + ) + + val callingConventionResults = + CallingConvention.entries.map { callingConvention -> + val logFile = + File(workDirectory, "$callingConvention.log.txt").also { it.parentFile.mkdirs() } + logger.info( + "Testing {} for support of calling convention {} (log file: {})", + dataConnectExecutable.absolutePath, + callingConvention, + logFile.absolutePath + ) + + val exitCode: Int = + logFile.outputStream().use { logFileStream -> + execOperations + .exec { execSpec -> + execSpec.run { + executable(dataConnectExecutable) + isIgnoreExitValue = true + standardOutput = logFileStream + errorOutput = logFileStream + when (callingConvention) { + CallingConvention.GRADLE -> args("gradle", "help", "generate") + CallingConvention.SDK_GENERATE -> args("sdk", "help", "generate") + } + } + } + .exitValue + } + + val callingConventionSupported = exitCode == 0 + logger.info( + "Testing {} for support of calling convention {} completed: {} (exitCode={})", + dataConnectExecutable.absolutePath, + callingConvention, + callingConventionSupported, + exitCode + ) + Pair(callingConvention, callingConventionSupported) + } + + val supportedCallingConventions: List = + callingConventionResults.filter { it.second }.map { it.first } + return supportedCallingConventions.singleOrNull() + ?: throw DataConnectGradleException( + "d24j9dm3r6", + "could not detect calling convention of Data Connect executable ${dataConnectExecutable.absolutePath}: " + + "found ${supportedCallingConventions.size} supported calling conventions, but expected exactly 1: " + + supportedCallingConventions.joinToString(", ") + ) +} diff --git a/firebase-dataconnect/gradleplugin/plugin/src/main/kotlin/com/google/firebase/dataconnect/gradle/plugin/DataConnectGradlePlugin.kt b/firebase-dataconnect/gradleplugin/plugin/src/main/kotlin/com/google/firebase/dataconnect/gradle/plugin/DataConnectGradlePlugin.kt index aa39c6cc104..bbe06bc5ae0 100644 --- a/firebase-dataconnect/gradleplugin/plugin/src/main/kotlin/com/google/firebase/dataconnect/gradle/plugin/DataConnectGradlePlugin.kt +++ b/firebase-dataconnect/gradleplugin/plugin/src/main/kotlin/com/google/firebase/dataconnect/gradle/plugin/DataConnectGradlePlugin.kt @@ -186,6 +186,7 @@ abstract class DataConnectGradlePlugin : Plugin { connectors.set(dataConnectProviders.connectors) buildDirectory.set(baseBuildDirectory.map { it.dir("generateCode") }) ktfmtJarFile.set(dataConnectProviders.ktfmtJarFile) + dataConnectExecutableCallingConvention.set(detectedCallingConvention()) } variant.sources.java!!.addGeneratedSourceDirectory(