google / protobuf-gradle-plugin

Protobuf Plugin for Gradle
Other
1.73k stars 269 forks source link

Support Kotlin multiplatform builds #497

Open bubenheimer opened 3 years ago

bubenheimer commented 3 years ago

I'd like to combine separate Gradle projects for generation of Android client protobuf Java (lite) sources and server protobuf Java sources into a single project via a Kotlin multiplatform Gradle project. Clearly the intention of the protobuf Gradle plugin is to generate sources for different languages & flavors within a single project; it should support this use case. Conceptually it seems the most sensible way to set up Gradle-based protobuf code generation when Android is involved.

Some problems I encountered:

vitorhugods commented 1 year ago

If someone is looking for a hack around it, here's what I've done in Kalium's protobuf-codegen and protobuf modules:

Protobuf-Codegen

Is a Kotlin/JVM project that contains the .proto files and uses PBandK + this Gradle plugin to generate the Protobuf files.

Protobuf

Is a Kotlin Multiplatform (iOS, JS, JVM and Android), that:

It's a weird hack, but it works pretty well!

In the end, shared code can just add dependency to project(":protobuf") and use Protobuf (de)serialisation normally.

Maragues commented 1 month ago

@vitorhugods Thanks for that example! It worked for me.

A modification is needed if your KMP project targets Android. Instead of declaring the dependency using a forEach

compileTasks.forEach {

You must use whenTaskAdded

val generateProtoTask = this as GenerateProtoTask

compileTasks.whenTaskAdded {
  dependsOn(generateProtoTask)
}

Otherwise, Android tasks aren't present when GenerateProtoTask is configured and building Android doesn't generate protobuf code.

Maragues commented 1 month ago

This version fixes configuration cache

val copyTask = tasks.register<Copy>("CopyGeneratedProtobuf") {
    val generateProtoTasks = codegenProject.tasks
        .withType(GenerateProtoTask::class.java)
        .matching { !it.isTest }

    dependsOn(generateProtoTasks)

    from(generateProtoTasks)

    val outDirs = generateProtoTasks.flatMap { it.outputSourceDirectorySet.srcDirs }

    outDirs.forEach { generatedDirectory ->
        val targetDirectory = File(generatedFilesBaseDir.get().asFile, generatedDirectory.name)
        into(targetDirectory)
    }

    doLast {
        outDirs.forEach { generatedDirectory ->
            require(generatedDirectory.deleteRecursively()) {
                "Failed to remove contents of ${generatedDirectory.absolutePath}"
            }

        }
    }
}

tasks
    .matching { it is KotlinCompile || it is KotlinNativeCompile }
    .whenTaskAdded {
        dependsOn(copyTask)
    }

I'm sure it can be improved, but it's good enough for me.