Files
vnidrop/shared/build.gradle.kts

261 lines
7.9 KiB
Kotlin

@file:OptIn(gobley.gradle.InternalGobleyGradleApi::class)
import gobley.gradle.cargo.dsl.jvm
import gobley.gradle.cargo.tasks.CargoBuildTask
import gobley.gradle.cargo.tasks.CargoCheckTask
import gobley.gradle.GobleyHost
import gobley.gradle.rust.targets.RustAndroidTarget
import gobley.gradle.rust.targets.RustTarget
import gobley.gradle.Variant
import org.gradle.api.DefaultTask
import org.gradle.api.provider.ListProperty
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.PathSensitivity
import org.gradle.api.tasks.TaskAction
import org.gradle.jvm.tasks.Jar
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
abstract class VerifyHostCargoTaskSelection : DefaultTask() {
@get:Input
abstract val mismatches: ListProperty<String>
@TaskAction
fun verify() {
check(mismatches.get().isEmpty()) {
"Cargo tasks do not match the current host: ${mismatches.get().joinToString()}"
}
}
}
plugins {
alias(libs.plugins.kotlinMultiplatform)
alias(libs.plugins.androidLibrary)
alias(libs.plugins.composeMultiplatform)
alias(libs.plugins.composeCompiler)
alias(libs.plugins.gobleyCargo)
alias(libs.plugins.gobleyUniffi)
alias(libs.plugins.kotlinAtomicfu)
}
val appVersion = providers.gradleProperty("vnidrop.version").get()
val desktopRustVariant = providers.gradleProperty("vnidrop.desktop.rustVariant")
.map { value ->
when (value.trim().lowercase()) {
"debug" -> Variant.Debug
"release" -> Variant.Release
else -> error("vnidrop.desktop.rustVariant must be either debug or release")
}
}
.orElse(Variant.Debug)
// Compile-time switches (gradle.properties or -P…).
// included=false: no Share-diagnostics toggle, no telemetry/crash auto-upload stack.
// endpoint/key both empty: transport is NoOp (safe default until Cloudflare is deployed).
val diagnosticsIncluded: Boolean =
(findProperty("vnidrop.diagnostics.included") as String?)?.toBooleanStrictOrNull() ?: false
val diagnosticsEndpoint: String =
(findProperty("vnidrop.diagnostics.endpoint") as String?)?.trim().orEmpty()
val diagnosticsIngestKey: String =
(findProperty("vnidrop.diagnostics.ingestKey") as String?)?.trim().orEmpty()
check(!diagnosticsIncluded || diagnosticsEndpoint.isEmpty() == diagnosticsIngestKey.isEmpty()) {
"vnidrop.diagnostics.endpoint and vnidrop.diagnostics.ingestKey must be configured together"
}
val diagnosticsBuildConfigDir = layout.buildDirectory.dir("generated/diagnostics/commonMain/kotlin")
val generateDiagnosticsBuildConfig by tasks.registering {
group = "build"
description = "Generates DiagnosticsBuildConfig from vnidrop.diagnostics.* properties"
val outputDir = diagnosticsBuildConfigDir
val included = diagnosticsIncluded
val endpoint = if (included) diagnosticsEndpoint else ""
val ingestKey = if (included) diagnosticsIngestKey else ""
inputs.property("vnidrop.diagnostics.included", included)
inputs.property("vnidrop.diagnostics.endpoint", endpoint)
inputs.property("vnidrop.diagnostics.ingestKey", ingestKey)
outputs.dir(outputDir)
doLast {
val packageDir = outputDir.get().asFile.resolve("com/vnidrop/app/diagnostics")
packageDir.mkdirs()
fun esc(value: String): String = buildString {
for (ch in value) {
when (ch) {
'\\' -> append("\\\\")
'"' -> append("\\\"")
'\n' -> append("\\n")
'\r' -> append("\\r")
'\t' -> append("\\t")
'$' -> append("\\$")
else -> append(ch)
}
}
}
packageDir.resolve("DiagnosticsBuildConfig.kt").writeText(
"""
|package com.vnidrop.app.diagnostics
|
|/**
| * Generated by shared/build.gradle.kts from vnidrop.diagnostics.* properties.
| *
| * - included=false → no opt-in UI / telemetry / crash hooks
| * - endpoint/key both empty → [NoOpDiagnosticsTransport] (no network)
| */
|object DiagnosticsBuildConfig {
| const val INCLUDED: Boolean = $included
| const val ENDPOINT: String = "${esc(endpoint)}"
| const val INGEST_KEY: String = "${esc(ingestKey)}"
|}
|
""".trimMargin(),
)
}
}
kotlin {
androidTarget {
compilerOptions {
jvmTarget = JvmTarget.JVM_11
}
}
jvm()
sourceSets {
commonMain {
kotlin.srcDir(files(diagnosticsBuildConfigDir).builtBy(generateDiagnosticsBuildConfig))
}
androidMain.dependencies {
implementation(libs.androidx.activity.compose)
implementation(libs.androidx.core.ktx)
implementation(libs.google.code.scanner)
implementation(libs.compose.uiToolingPreview)
}
jvmMain.dependencies {
implementation(libs.filekit.dialogs)
}
commonMain.dependencies {
implementation(libs.compose.runtime)
implementation(libs.compose.foundation)
implementation(libs.compose.material3)
implementation(libs.compose.ui)
implementation(libs.compose.components.resources)
implementation(libs.compose.uiToolingPreview)
implementation(libs.compottie.lite)
implementation(libs.androidx.lifecycle.viewmodelCompose)
implementation(libs.androidx.lifecycle.runtimeCompose)
implementation(libs.androidx.datastore)
implementation(libs.androidx.datastore.preferences)
implementation(libs.kotlinx.coroutinesCore)
implementation(libs.qrcode.kotlin)
}
commonTest.dependencies {
implementation(libs.kotlin.test)
implementation(libs.kotlinx.coroutinesTest)
}
jvmTest.dependencies {
implementation(compose.desktop.currentOs)
implementation(libs.compose.uiTest)
}
}
}
dependencies {
debugImplementation(libs.compose.uiTooling)
}
android {
namespace = "com.vnidrop.app.shared"
compileSdk = libs.versions.android.compileSdk.get().toInt()
defaultConfig {
minSdk = libs.versions.android.minSdk.get().toInt()
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
}
val hostCargoTargets = buildSet<RustTarget> {
add(GobleyHost.current.rustTarget)
addAll(RustAndroidTarget.entries)
}
cargo {
packageDirectory = layout.projectDirectory.dir("../crates/vnidrop")
publishJvmArtifacts = true
jvmVariant.set(desktopRustVariant)
androidTargetsToBuild.set(setOf(RustAndroidTarget.Arm64, RustAndroidTarget.X64))
builds.jvm {
variants {
// Desktop distributions are built per host. Do not publish disabled
// cross-platform native jars into the app runtime classpath.
embedRustLibrary.set(rustTarget == GobleyHost.current.rustTarget)
}
}
builds.configureEach {
val buildOnCurrentHost = rustTarget in hostCargoTargets
installTargetBeforeBuild.set(buildOnCurrentHost)
variants {
buildTaskProvider.configure {
enabled = buildOnCurrentHost
}
checkTaskProvider.configure {
enabled = buildOnCurrentHost
}
}
}
}
uniffi {
generateFromLibrary {
namespace = "vnidrop"
build.set(GobleyHost.current.rustTarget)
variant.set(desktopRustVariant)
}
}
tasks.named<Jar>("jvmJar") {
manifest.attributes["Implementation-Version"] = appVersion
}
val verifyHostCargoTaskSelection = tasks.register<VerifyHostCargoTaskSelection>(
"verifyHostCargoTaskSelection"
) {
group = "verification"
description = "Verifies that Cargo tasks are enabled only for targets supported by this host."
mismatches.convention(emptyList())
}
afterEvaluate {
verifyHostCargoTaskSelection.configure {
mismatches.set(buildList {
tasks.withType<CargoBuildTask>().forEach {
if (it.enabled != (it.target.get() in hostCargoTargets)) add(it.path)
}
tasks.withType<CargoCheckTask>().forEach {
if (it.enabled != (it.target.get() in hostCargoTargets)) add(it.path)
}
})
}
}
tasks.named("check") {
dependsOn(verifyHostCargoTaskSelection)
}
tasks.configureEach {
// Gobley does not currently treat every Rust source/API change as an input of
// all platform cargo tasks. Without these inputs an incremental Android build
// can package an older .so next to freshly generated UniFFI Kotlin bindings.
if (name.startsWith("cargoBuild")) {
inputs.files(
fileTree(layout.projectDirectory.dir("../crates/vnidrop")) {
include("Cargo.toml", "build.rs", "src/**/*.rs")
},
layout.projectDirectory.file("../Cargo.toml"),
layout.projectDirectory.file("../Cargo.lock"),
).withPathSensitivity(PathSensitivity.RELATIVE)
}
}