-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathcmp-observe-metadata.gradle.kts
More file actions
179 lines (163 loc) · 7.6 KB
/
Copy pathcmp-observe-metadata.gradle.kts
File metadata and controls
179 lines (163 loc) · 7.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
/*
* Copyright 2026 MobileByteLabs
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*/
// ============================================================================
// cmp-observe-metadata.gradle.kts — shared Gradle task generating CmpMetadata.kt
// from CMP_LIBRARY_METADATA.gradle's `extra["..."]` properties at compile time.
//
// Authored 2026-05-30 by library-runtime-observability epic Phase 02 T3 (AC #10).
//
// Each cmp-* module that consumes cmp-observe applies this script:
//
// // In cmp-share/build.gradle.kts (or any cmp-*):
// apply(from = "$rootDir/cmp-observe-metadata.gradle.kts")
//
// At every `:cmp-share:compileKotlinCommon`, generates:
//
// cmp-share/build/generated/observability/io/github/mobilebytelabs/kmptoolkit/share/CmpMetadata.kt
//
// containing compile-time constants:
//
// internal object CmpMetadata {
// const val NAME = "cmp-share"
// const val VERSION = "3.2.11"
// const val ARTIFACT = "io.github.mobilebytelabs:cmp-share"
// }
//
// Module init paths read these constants when calling
// `LibraryObservation.notifyInit(CmpMetadata(NAME, VERSION, ARTIFACT))`.
//
// The generated file is automatically added to commonMain.kotlin.srcDirs.
// ============================================================================
// Read CMP_LIBRARY_METADATA.gradle if it exists (top-level umbrella defaults).
val metadataFile = rootProject.file("CMP_LIBRARY_METADATA.gradle.kts")
if (metadataFile.exists()) {
apply(from = metadataFile)
}
// Module name = the project's directory name (cmp-share, cmp-network-monitor, etc.)
val moduleName: String = project.name
val moduleKey: String = moduleName.replace('-', '_')
// Resolve version + artifact from rootProject.extra; fall back to UNKNOWN.
val moduleVersion: String =
try {
rootProject.extra["${moduleKey}_version"] as String
} catch (_: Exception) {
"UNKNOWN"
}
val moduleArtifact: String =
try {
rootProject.extra["${moduleKey}_artifact"] as String
} catch (_: Exception) {
"io.github.mobilebytelabs:$moduleName"
}
// Derive Kotlin package: cmp-share → com.mobilebytelabs.kmptoolkit.share
val modulePackage: String =
run {
// Mirror the actual source package convention used across cmp-* modules:
// cmp-network-monitor → io.github.mobilebytelabs.kmptoolkit.networkmonitor
// cmp-deep-link → io.github.mobilebytelabs.kmptoolkit.deeplink
// cmp-share → io.github.mobilebytelabs.kmptoolkit.share
// (hyphens are DROPPED, not converted to dots — the existing source tree uses
// squashed concatenation, e.g. `package …kmptoolkit.networkmonitor`.)
val short = moduleName.removePrefix("cmp-").replace("-", "")
"io.github.mobilebytelabs.kmptoolkit.$short"
}
abstract class CmpMetadataGenTask : org.gradle.api.DefaultTask() {
@get:org.gradle.api.tasks.Input
abstract val moduleName: org.gradle.api.provider.Property<String>
@get:org.gradle.api.tasks.Input
abstract val moduleVersion: org.gradle.api.provider.Property<String>
@get:org.gradle.api.tasks.Input
abstract val moduleArtifact: org.gradle.api.provider.Property<String>
@get:org.gradle.api.tasks.Input
abstract val modulePackage: org.gradle.api.provider.Property<String>
@get:org.gradle.api.tasks.OutputDirectory
abstract val outputDir: org.gradle.api.file.DirectoryProperty
@org.gradle.api.tasks.TaskAction
fun generate() {
val pkgDir = outputDir.get().asFile.resolve(modulePackage.get().replace('.', '/'))
pkgDir.mkdirs()
pkgDir.resolve("CmpMetadata.kt").writeText(
"""
/*
* Auto-generated by cmp-observe-metadata.gradle.kts. DO NOT EDIT.
*
* Source: CMP_LIBRARY_METADATA.gradle (regenerated post-publish by mbl-actionhub).
* Per library-runtime-observability epic Phase 02 T4 (AC #10).
*/
package ${modulePackage.get()}
internal object CmpMetadata {
const val NAME: String = "${moduleName.get()}"
const val VERSION: String = "${moduleVersion.get()}"
const val ARTIFACT: String = "${moduleArtifact.get()}"
}
""".trimIndent(),
)
}
}
// Capture script-level vars before they are shadowed by the task's same-named abstract properties.
val capturedModuleName = moduleName
val capturedModuleVersion = moduleVersion
val capturedModuleArtifact = moduleArtifact
val capturedModulePackage = modulePackage
val genTask =
tasks.register<CmpMetadataGenTask>("generateCmpMetadata") {
moduleName.set(capturedModuleName)
moduleVersion.set(capturedModuleVersion)
moduleArtifact.set(capturedModuleArtifact)
modulePackage.set(capturedModulePackage)
outputDir.set(layout.buildDirectory.dir("generated/observability"))
}
// Wire generated file into commonMain so compileKotlinCommon picks it up.
// KotlinMultiplatformExtension is NOT on the buildscript compilation classpath of an applied
// script — only on the plugin classpath resolved at runtime. We use reflection so this script
// compiles with only Gradle-core types (always available), and defers the KGP type lookup to
// the configuration phase when the plugin is already applied.
afterEvaluate {
val ext = project.extensions.findByName("kotlin") ?: return@afterEvaluate
try {
@Suppress("UNCHECKED_CAST")
val sourceSets =
ext.javaClass.getMethod("getSourceSets").invoke(ext)
as? org.gradle.api.NamedDomainObjectContainer<Any>
?: return@afterEvaluate
val commonMain = sourceSets.findByName("commonMain") ?: return@afterEvaluate
val kotlinSrcSet =
commonMain.javaClass.getMethod("getKotlin").invoke(commonMain)
as? org.gradle.api.file.SourceDirectorySet
?: return@afterEvaluate
// Wire srcDir to the TaskProvider (not the static path) — Gradle then
// auto-derives the task dependency for EVERY consumer of this source
// set (compileKotlinX, androidSourcesJar, X64SourcesJar, etc.). Avoids
// the brittle task-name-pattern approach that previously missed
// *SourcesJar publish-time tasks and broke the publish workflow.
kotlinSrcSet.srcDir(genTask.map { it.outputDir })
} catch (e: Exception) {
logger.warn("cmp-observe-metadata: failed to register generated sources in commonMain: ${e.message}")
}
}
// Belt-and-suspenders: also wire an explicit dependsOn on every consumer task
// pattern we know about. The srcDir(taskProvider) registration above should be
// sufficient on its own, but this guard catches any task that reads the source
// dir via a path other than the kotlinSrcSet API.
//
// Pattern coverage:
// - compileKotlin{TargetName} (KMP standard targets)
// - compileCommonMainKotlinMetadata (KMP metadata)
// - compileAndroidMain (Android KMP Library plugin)
// - compile{Variant}KotlinAndroid (legacy android plugin paths)
// - {target}SourcesJar / androidSourcesJar / sourcesJar (publish-time)
tasks
.matching {
it.name.startsWith("compileKotlin") ||
it.name.startsWith("compileCommonMainKotlinMetadata") ||
it.name.startsWith("compileAndroid") ||
it.name.endsWith("SourcesJar") ||
it.name == "sourcesJar"
}.configureEach { dependsOn(genTask) }