1515 */
1616package com.google.firebase.dataconnect.gradle.plugin
1717
18+ import com.google.firebase.dataconnect.gradle.plugin.DataConnectGenerateCodeTask.CallingConvention
1819import java.io.File
20+ import javax.inject.Inject
1921import org.gradle.api.DefaultTask
20- import org.gradle.api.Task
2122import org.gradle.api.file.DirectoryProperty
2223import org.gradle.api.file.RegularFileProperty
2324import org.gradle.api.provider.Property
25+ import org.gradle.api.provider.Provider
2426import org.gradle.api.tasks.Input
2527import org.gradle.api.tasks.InputFile
2628import org.gradle.api.tasks.InputFiles
2729import org.gradle.api.tasks.Internal
2830import org.gradle.api.tasks.Optional
2931import org.gradle.api.tasks.OutputDirectory
3032import org.gradle.api.tasks.TaskAction
33+ import org.gradle.process.ExecOperations
34+ import org.slf4j.Logger
3135
3236abstract class DataConnectGenerateCodeTask : DefaultTask () {
3337
3438 @get:InputFile abstract val dataConnectExecutable: RegularFileProperty
3539
40+ @get:Input abstract val dataConnectExecutableCallingConvention: Property <CallingConvention >
41+
3642 @get:Optional @get:InputFiles abstract val configDirectory: DirectoryProperty
3743
3844 @get:Input abstract val connectors: Property <Collection <String >>
@@ -43,16 +49,32 @@ abstract class DataConnectGenerateCodeTask : DefaultTask() {
4349
4450 @get:Optional @get:InputFile abstract val ktfmtJarFile: RegularFileProperty
4551
52+ @get:Inject abstract val execOperations: ExecOperations
53+
54+ /* *
55+ * The subcommand of the Data Connect executable to use to perform code generation.
56+ *
57+ * In August 2025 the subcommand was changed by cl/795582011 from "gradle generate" to "sdk
58+ * generate -platform=kotlin". The "gradle generate" command was last supported in version 2.11.0
59+ * of the Data Connect executable.
60+ */
61+ enum class CallingConvention {
62+ GRADLE ,
63+ SDK_GENERATE ,
64+ }
65+
4666 @TaskAction
4767 fun run () {
4868 val dataConnectExecutable: File = dataConnectExecutable.get().asFile
69+ val dataConnectExecutableCallingConvention = dataConnectExecutableCallingConvention.get()
4970 val configDirectory: File ? = configDirectory.orNull?.asFile
5071 val connectors: Collection <String > = connectors.get().distinct().sorted()
5172 val buildDirectory: File = buildDirectory.get().asFile
5273 val outputDirectory: File = outputDirectory.get().asFile
5374 val ktfmtJarFile: File ? = ktfmtJarFile.orNull?.asFile
5475
5576 logger.info(" dataConnectExecutable={}" , dataConnectExecutable.absolutePath)
77+ logger.info(" dataConnectExecutableCallingConvention={}" , dataConnectExecutableCallingConvention)
5678 logger.info(" configDirectory={}" , configDirectory?.absolutePath)
5779 logger.info(" connectors={}" , connectors.joinToString(" , " ))
5880 logger.info(" buildDirectory={}" , buildDirectory.absolutePath)
@@ -69,14 +91,26 @@ abstract class DataConnectGenerateCodeTask : DefaultTask() {
6991 return
7092 }
7193
94+ val subCommand =
95+ when (dataConnectExecutableCallingConvention) {
96+ CallingConvention .GRADLE -> listOf (" gradle" , " generate" )
97+ CallingConvention .SDK_GENERATE -> listOf (" sdk" , " generate" )
98+ }
99+
72100 runDataConnectExecutable(
73101 dataConnectExecutable = dataConnectExecutable,
74- subCommand = listOf ( " gradle " , " generate " ) ,
102+ subCommand = subCommand ,
75103 configDirectory = configDirectory,
76104 ) {
77- this .connectors = connectors
105+ when (dataConnectExecutableCallingConvention) {
106+ CallingConvention .GRADLE -> this .connectors = connectors
107+ CallingConvention .SDK_GENERATE -> this .connectorId = connectors
108+ }
78109 this .outputDirectory = outputDirectory
79110 this .logFile = File (buildDirectory, " codegen.log.txt" )
111+ if (dataConnectExecutableCallingConvention == CallingConvention .SDK_GENERATE ) {
112+ this .platform = " kotlin"
113+ }
80114 }
81115
82116 if (ktfmtJarFile != = null ) {
@@ -92,7 +126,7 @@ abstract class DataConnectGenerateCodeTask : DefaultTask() {
92126 }
93127}
94128
95- private fun Task .runKtfmt (
129+ private fun DataConnectGenerateCodeTask .runKtfmt (
96130 ktfmtJarFile : File ,
97131 directory : File ,
98132 logFile : File ,
@@ -123,3 +157,80 @@ private fun Task.runKtfmt(
123157 }
124158 }
125159}
160+
161+ fun DataConnectGenerateCodeTask.detectedCallingConvention (
162+ dataConnectExecutable : RegularFileProperty = this.dataConnectExecutable,
163+ buildDirectory : DirectoryProperty = this.buildDirectory,
164+ execOperations : ExecOperations = this.execOperations,
165+ logger : Logger = this.logger
166+ ): Provider <CallingConvention > =
167+ dataConnectExecutable.map {
168+ determineCallingConvention(
169+ dataConnectExecutable = it.asFile,
170+ workDirectory = File (buildDirectory.get().asFile, " determineCallingConvention" ),
171+ execOperations = execOperations,
172+ logger = logger,
173+ )
174+ }
175+
176+ private fun determineCallingConvention (
177+ dataConnectExecutable : File ,
178+ workDirectory : File ,
179+ execOperations : ExecOperations ,
180+ logger : Logger ,
181+ ): CallingConvention {
182+ logger.info(
183+ " Determining calling convention of Data Connect executable: {}" ,
184+ dataConnectExecutable.absolutePath
185+ )
186+
187+ val callingConventionResults =
188+ CallingConvention .entries.map { callingConvention ->
189+ val logFile =
190+ File (workDirectory, " $callingConvention .log.txt" ).also { it.parentFile.mkdirs() }
191+ logger.info(
192+ " Testing {} for support of calling convention {} (log file: {})" ,
193+ dataConnectExecutable.absolutePath,
194+ callingConvention,
195+ logFile.absolutePath
196+ )
197+
198+ val exitCode: Int =
199+ logFile.outputStream().use { logFileStream ->
200+ execOperations
201+ .exec { execSpec ->
202+ execSpec.run {
203+ executable(dataConnectExecutable)
204+ isIgnoreExitValue = true
205+ standardOutput = logFileStream
206+ errorOutput = logFileStream
207+ when (callingConvention) {
208+ CallingConvention .GRADLE -> args(" gradle" , " help" , " generate" )
209+ CallingConvention .SDK_GENERATE -> args(" sdk" , " help" , " generate" )
210+ }
211+ }
212+ }
213+ .exitValue
214+ }
215+
216+ val callingConventionSupported = exitCode == 0
217+ logger.info(
218+ " Testing {} for support of calling convention {} completed: {} (exitCode={})" ,
219+ dataConnectExecutable.absolutePath,
220+ callingConvention,
221+ callingConventionSupported,
222+ exitCode
223+ )
224+ Pair (callingConvention, callingConventionSupported)
225+ }
226+
227+ val supportedCallingConventions: List <CallingConvention > =
228+ callingConventionResults.filter { it.second }.map { it.first }
229+ return supportedCallingConventions.singleOrNull()
230+ ? : throw DataConnectGradleException (
231+ " d24j9dm3r6" ,
232+ " could not detect calling convention of Data Connect executable ${dataConnectExecutable.absolutePath} : " +
233+ " found ${supportedCallingConventions.size} supported calling conventions, but expected exactly 1: " +
234+ supportedCallingConventions.joinToString(" , " )
235+ )
236+ }
0 commit comments