Files
vnidrop/shared/build.gradle.kts
Hammed Abass ead4e09a60 feat(shared): add opt-in diagnostics client with compile-time kill switch
Ship client-side telemetry, crash capture, and bug-report assembly behind a
user opt-in, with NoOp transport until Cloudflare is wired. Builds can set
vnidrop.diagnostics.included=false to omit the Share-diagnostics UI and
disable the telemetry/crash auto-upload stack while keeping bug reports.
2026-07-14 08:27:42 +02:00

234 lines
6.6 KiB
Kotlin

@file:OptIn(gobley.gradle.InternalGobleyGradleApi::class)
import gobley.gradle.cargo.dsl.appleMobile
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.RustAppleMobileTarget
import gobley.gradle.rust.targets.RustTarget
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.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)
}
// Compile-time switch (gradle.properties or -Pvnidrop.diagnostics.included=false).
// false: no Share-diagnostics toggle, no telemetry/crash auto-upload stack.
val diagnosticsIncluded: Boolean =
(findProperty("vnidrop.diagnostics.included") as String?)?.toBooleanStrictOrNull() ?: true
val diagnosticsBuildConfigDir = layout.buildDirectory.dir("generated/diagnostics/commonMain/kotlin")
val generateDiagnosticsBuildConfig by tasks.registering {
group = "build"
description = "Generates DiagnosticsBuildConfig from vnidrop.diagnostics.included"
val outputDir = diagnosticsBuildConfigDir
val included = diagnosticsIncluded
inputs.property("vnidrop.diagnostics.included", included)
outputs.dir(outputDir)
doLast {
val packageDir = outputDir.get().asFile.resolve("com/vnidrop/app/diagnostics")
packageDir.mkdirs()
packageDir.resolve("DiagnosticsBuildConfig.kt").writeText(
"""
|package com.vnidrop.app.diagnostics
|
|/**
| * Generated by shared/build.gradle.kts.
| * Override with `-Pvnidrop.diagnostics.included=false` or gradle.properties.
| */
|object DiagnosticsBuildConfig {
| const val INCLUDED: Boolean = $included
|}
|
""".trimMargin(),
)
}
}
kotlin {
if (GobleyHost.current.platform == GobleyHost.Platform.MacOS) {
listOf(
iosArm64(),
iosSimulatorArm64()
).forEach { iosTarget ->
iosTarget.binaries.framework {
baseName = "Shared"
isStatic = true
}
}
}
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)
}
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.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)
if (GobleyHost.current.platform == GobleyHost.Platform.MacOS) {
addAll(RustAppleMobileTarget.entries)
}
}
cargo {
packageDirectory = layout.projectDirectory.dir("../crates/vnidrop")
publishJvmArtifacts = true
androidTargetsToBuild.set(setOf(RustAndroidTarget.Arm64))
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.appleMobile {
variants {
buildTaskProvider.configure {
if (rustTarget.cinteropName == "ios") {
additionalEnvironment.put("IPHONEOS_DEPLOYMENT_TARGET", "16.0.0")
}
}
}
}
builds.configureEach {
val buildOnCurrentHost = rustTarget in hostCargoTargets
installTargetBeforeBuild.set(buildOnCurrentHost)
variants {
buildTaskProvider.configure {
enabled = buildOnCurrentHost
}
checkTaskProvider.configure {
enabled = buildOnCurrentHost
}
}
}
}
uniffi {
generateFromLibrary {
namespace = "vnidrop"
}
}
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)
}
}