- 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>
258 lines
8.5 KiB
Kotlin
258 lines
8.5 KiB
Kotlin
import javax.inject.Inject
|
|
|
|
plugins {
|
|
alias(libs.plugins.android.application)
|
|
alias(libs.plugins.kotlin.compose)
|
|
alias(libs.plugins.ksp)
|
|
}
|
|
|
|
android {
|
|
namespace = "net.metacircular.engpad"
|
|
compileSdk = 36
|
|
|
|
defaultConfig {
|
|
applicationId = "net.metacircular.engpad"
|
|
minSdk = 30
|
|
targetSdk = 36
|
|
versionCode = 1
|
|
versionName = "0.1.0"
|
|
|
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
|
}
|
|
|
|
buildTypes {
|
|
release {
|
|
isMinifyEnabled = true
|
|
proguardFiles(
|
|
getDefaultProguardFile("proguard-android-optimize.txt"),
|
|
"proguard-rules.pro"
|
|
)
|
|
}
|
|
}
|
|
|
|
compileOptions {
|
|
sourceCompatibility = JavaVersion.VERSION_17
|
|
targetCompatibility = JavaVersion.VERSION_17
|
|
}
|
|
|
|
buildFeatures {
|
|
compose = true
|
|
}
|
|
|
|
lint {
|
|
warningsAsErrors = true
|
|
abortOnError = true
|
|
checkDependencies = true
|
|
}
|
|
}
|
|
|
|
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)
|
|
|
|
implementation(platform(libs.compose.bom))
|
|
implementation(libs.compose.ui)
|
|
implementation(libs.compose.ui.tooling.preview)
|
|
implementation(libs.compose.material3)
|
|
debugImplementation(libs.compose.ui.tooling)
|
|
|
|
implementation(libs.navigation.compose)
|
|
|
|
implementation(libs.lifecycle.viewmodel.compose)
|
|
implementation(libs.lifecycle.runtime.compose)
|
|
|
|
implementation(libs.room.runtime)
|
|
implementation(libs.room.ktx)
|
|
ksp(libs.room.compiler)
|
|
|
|
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)
|
|
}
|