Add protobuf Gradle setup and gRPC sync client skeleton
- Protobuf Gradle plugin with protoc, grpc-java, grpc-kotlin generators - Dependencies: protobuf-kotlin-lite, grpc-kotlin-stub, grpc-okhttp, grpc-protobuf-lite - Proto file at app/src/main/proto/engpad/v1/sync.proto - SyncClient.kt: gRPC channel with TLS + credential metadata interceptor - SyncManager.kt: notebook serialization to proto, sync/delete/list RPCs Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
import javax.inject.Inject
|
||||
|
||||
plugins {
|
||||
alias(libs.plugins.android.application)
|
||||
alias(libs.plugins.kotlin.compose)
|
||||
@@ -48,6 +50,179 @@ kotlin {
|
||||
jvmToolchain(17)
|
||||
}
|
||||
|
||||
// --- Protobuf / gRPC code generation via protoc ---
|
||||
//
|
||||
// The protobuf-gradle-plugin (0.9.6) does not yet support AGP 9, so we drive
|
||||
// protoc directly. A custom task exposes DirectoryProperty outputs that the
|
||||
// AGP Variant API can consume.
|
||||
|
||||
abstract class GenerateProtoTask : DefaultTask() {
|
||||
@get:Inject
|
||||
abstract val execOps: ExecOperations
|
||||
@get:InputDirectory
|
||||
abstract val protoSourceDir: DirectoryProperty
|
||||
|
||||
@get:InputFiles
|
||||
abstract val protocFile: ConfigurableFileCollection
|
||||
|
||||
@get:InputFiles
|
||||
abstract val grpcJavaPluginFile: ConfigurableFileCollection
|
||||
|
||||
@get:InputFiles
|
||||
abstract val grpcKotlinPluginFile: ConfigurableFileCollection
|
||||
|
||||
/** Protobuf JAR containing well-known type .proto files (google/protobuf/*.proto). */
|
||||
@get:InputFiles
|
||||
abstract val protobufIncludesJar: ConfigurableFileCollection
|
||||
|
||||
/** Temp directory for extracting well-known proto includes. */
|
||||
@get:OutputDirectory
|
||||
abstract val includesExtractDir: DirectoryProperty
|
||||
|
||||
/** Java sources generated by protoc (messages). */
|
||||
@get:OutputDirectory
|
||||
abstract val javaOutputDir: DirectoryProperty
|
||||
|
||||
/** Kotlin sources generated by protoc (Kotlin DSL builders). */
|
||||
@get:OutputDirectory
|
||||
abstract val kotlinOutputDir: DirectoryProperty
|
||||
|
||||
/** Java sources generated by protoc-gen-grpc-java (service stubs). */
|
||||
@get:OutputDirectory
|
||||
abstract val grpcOutputDir: DirectoryProperty
|
||||
|
||||
/** Kotlin sources generated by protoc-gen-grpc-kotlin (coroutine stubs). */
|
||||
@get:OutputDirectory
|
||||
abstract val grpcKtOutputDir: DirectoryProperty
|
||||
|
||||
@TaskAction
|
||||
fun generate() {
|
||||
val javaOut = javaOutputDir.get().asFile
|
||||
val kotlinOut = kotlinOutputDir.get().asFile
|
||||
val grpcOut = grpcOutputDir.get().asFile
|
||||
val grpcKtOut = grpcKtOutputDir.get().asFile
|
||||
|
||||
listOf(javaOut, kotlinOut, grpcOut, grpcKtOut).forEach { it.mkdirs() }
|
||||
|
||||
// Extract well-known .proto files from the protobuf JAR.
|
||||
val extractDir = includesExtractDir.get().asFile
|
||||
extractDir.mkdirs()
|
||||
val jar = protobufIncludesJar.singleFile
|
||||
java.util.zip.ZipFile(jar).use { zip ->
|
||||
zip.entries().asSequence()
|
||||
.filter { it.name.endsWith(".proto") }
|
||||
.forEach { entry ->
|
||||
val outFile = File(extractDir, entry.name)
|
||||
outFile.parentFile.mkdirs()
|
||||
zip.getInputStream(entry).use { input ->
|
||||
outFile.outputStream().use { output -> input.copyTo(output) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val protoc = protocFile.singleFile.also { it.setExecutable(true) }
|
||||
val grpcJava = grpcJavaPluginFile.singleFile.also { it.setExecutable(true) }
|
||||
val grpcKt = grpcKotlinPluginFile.singleFile.also { it.setExecutable(true) }
|
||||
|
||||
val protoDir = protoSourceDir.get().asFile
|
||||
val protoFiles = protoDir.walkTopDown().filter { it.extension == "proto" }.toList()
|
||||
if (protoFiles.isEmpty()) return
|
||||
|
||||
execOps.exec {
|
||||
commandLine(
|
||||
protoc.absolutePath,
|
||||
"--proto_path=${protoDir.absolutePath}",
|
||||
"--proto_path=${extractDir.absolutePath}",
|
||||
"--java_out=lite:${javaOut.absolutePath}",
|
||||
"--kotlin_out=lite:${kotlinOut.absolutePath}",
|
||||
"--plugin=protoc-gen-grpc=${grpcJava.absolutePath}",
|
||||
"--grpc_out=lite:${grpcOut.absolutePath}",
|
||||
"--plugin=protoc-gen-grpckt=${grpcKt.absolutePath}",
|
||||
"--grpckt_out=lite:${grpcKtOut.absolutePath}",
|
||||
*protoFiles.map { it.absolutePath }.toTypedArray(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val protobufIncludes: Configuration by configurations.creating {
|
||||
isTransitive = false
|
||||
}
|
||||
val protocArtifact: Configuration by configurations.creating {
|
||||
isTransitive = false
|
||||
}
|
||||
val grpcJavaPlugin: Configuration by configurations.creating {
|
||||
isTransitive = false
|
||||
}
|
||||
val grpcKotlinPlugin: Configuration by configurations.creating {
|
||||
isTransitive = false
|
||||
}
|
||||
|
||||
// Determine the OS/arch classifier for native protoc binaries.
|
||||
val protocClassifier: String = run {
|
||||
val os = System.getProperty("os.name").lowercase()
|
||||
val arch = System.getProperty("os.arch").lowercase()
|
||||
val osName = when {
|
||||
os.contains("mac") || os.contains("darwin") -> "osx"
|
||||
os.contains("linux") -> "linux"
|
||||
os.contains("windows") -> "windows"
|
||||
else -> error("Unsupported OS: $os")
|
||||
}
|
||||
val archName = when {
|
||||
arch.contains("aarch64") || arch.contains("arm64") -> "aarch_64"
|
||||
arch.contains("amd64") || arch.contains("x86_64") -> "x86_64"
|
||||
else -> error("Unsupported architecture: $arch")
|
||||
}
|
||||
"$osName-$archName"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
protobufIncludes("com.google.protobuf:protobuf-java:${libs.versions.protobuf.get()}")
|
||||
protocArtifact("com.google.protobuf:protoc:${libs.versions.protobuf.get()}:$protocClassifier@exe")
|
||||
grpcJavaPlugin("io.grpc:protoc-gen-grpc-java:${libs.versions.grpc.get()}:$protocClassifier@exe")
|
||||
grpcKotlinPlugin("io.grpc:protoc-gen-grpc-kotlin:${libs.versions.grpcKotlin.get()}:jdk8@jar")
|
||||
}
|
||||
|
||||
val protoGenBase = layout.buildDirectory.dir("generated/source/proto/main")
|
||||
|
||||
val generateProto by tasks.registering(GenerateProtoTask::class) {
|
||||
description = "Generate Kotlin/gRPC sources from .proto files"
|
||||
group = "build"
|
||||
|
||||
protoSourceDir.set(layout.projectDirectory.dir("src/main/proto"))
|
||||
protocFile.from(protocArtifact)
|
||||
grpcJavaPluginFile.from(grpcJavaPlugin)
|
||||
grpcKotlinPluginFile.from(grpcKotlinPlugin)
|
||||
|
||||
protobufIncludesJar.from(protobufIncludes)
|
||||
includesExtractDir.set(protoGenBase.map { it.dir("includes") })
|
||||
|
||||
javaOutputDir.set(protoGenBase.map { it.dir("java") })
|
||||
kotlinOutputDir.set(protoGenBase.map { it.dir("kotlin") })
|
||||
grpcOutputDir.set(protoGenBase.map { it.dir("grpc") })
|
||||
grpcKtOutputDir.set(protoGenBase.map { it.dir("grpckt") })
|
||||
}
|
||||
|
||||
// Register generated directories with AGP via the Variant API (AGP 9+).
|
||||
androidComponents {
|
||||
onVariants { variant ->
|
||||
variant.sources.java?.addGeneratedSourceDirectory(
|
||||
generateProto, GenerateProtoTask::javaOutputDir,
|
||||
)
|
||||
variant.sources.java?.addGeneratedSourceDirectory(
|
||||
generateProto, GenerateProtoTask::grpcOutputDir,
|
||||
)
|
||||
variant.sources.kotlin?.addGeneratedSourceDirectory(
|
||||
generateProto, GenerateProtoTask::kotlinOutputDir,
|
||||
)
|
||||
variant.sources.kotlin?.addGeneratedSourceDirectory(
|
||||
generateProto, GenerateProtoTask::grpcKtOutputDir,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// --- end protobuf generation ---
|
||||
|
||||
dependencies {
|
||||
implementation(libs.androidx.core.ktx)
|
||||
implementation(libs.androidx.activity.compose)
|
||||
@@ -70,6 +245,12 @@ dependencies {
|
||||
implementation(libs.coroutines.android)
|
||||
implementation(libs.reorderable)
|
||||
|
||||
implementation(libs.protobuf.kotlin.lite)
|
||||
implementation(libs.grpc.okhttp)
|
||||
implementation(libs.grpc.protobuf.lite)
|
||||
implementation(libs.grpc.stub)
|
||||
implementation(libs.grpc.kotlin.stub)
|
||||
|
||||
testImplementation(libs.junit)
|
||||
testImplementation(libs.coroutines.test)
|
||||
testImplementation(libs.room.testing)
|
||||
|
||||
Reference in New Issue
Block a user