Skip to content

Commit 479f5fa

Browse files
committed
first draft of plugin
1 parent e479f89 commit 479f5fa

File tree

11 files changed

+447
-105
lines changed

11 files changed

+447
-105
lines changed

buildSrc/src/main/kotlin/otel.japicmp-conventions.gradle.kts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,13 @@ val latestReleasedVersion: String by lazy {
3030

3131
class AllowNewAbstractMethodOnAutovalueClasses : AbstractRecordingSeenMembers() {
3232
override fun maybeAddViolation(member: JApiCompatibility): Violation? {
33-
val allowableAutovalueChanges = setOf(JApiCompatibilityChangeType.METHOD_ABSTRACT_ADDED_TO_CLASS,
34-
JApiCompatibilityChangeType.METHOD_ADDED_TO_PUBLIC_CLASS, JApiCompatibilityChangeType.ANNOTATION_ADDED)
33+
val allowableAutovalueChanges = setOf(
34+
JApiCompatibilityChangeType.METHOD_ABSTRACT_ADDED_TO_CLASS,
35+
JApiCompatibilityChangeType.METHOD_ADDED_TO_PUBLIC_CLASS,
36+
JApiCompatibilityChangeType.ANNOTATION_ADDED
37+
)
3538
if (member.compatibilityChanges.filter { !allowableAutovalueChanges.contains(it.type) }.isEmpty() &&
36-
member is JApiMethod && isAutoValueClass(member.getjApiClass()))
37-
{
39+
member is JApiMethod && isAutoValueClass(member.getjApiClass())) {
3840
return Violation.accept(member, "Autovalue will automatically add implementation")
3941
}
4042
if (member.compatibilityChanges.isEmpty() &&
@@ -44,10 +46,8 @@ class AllowNewAbstractMethodOnAutovalueClasses : AbstractRecordingSeenMembers()
4446
return null
4547
}
4648

47-
fun isAutoValueClass(japiClass: JApiClass): Boolean {
48-
return japiClass.newClass.get().getAnnotation(AutoValue::class.java) != null ||
49-
japiClass.newClass.get().getAnnotation(AutoValue.Builder::class.java) != null
50-
}
49+
fun isAutoValueClass(japiClass: JApiClass): Boolean = japiClass.newClass.get().getAnnotation(AutoValue::class.java) != null ||
50+
japiClass.newClass.get().getAnnotation(AutoValue.Builder::class.java) != null
5151
}
5252

5353
class SourceIncompatibleRule : AbstractRecordingSeenMembers() {

buildSrc/src/main/kotlin/otel.publish-conventions.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ publishing {
5858
connection.set("scm:git:[email protected]:open-telemetry/opentelemetry-java-contrib.git")
5959
developerConnection.set("scm:git:[email protected]:open-telemetry/opentelemetry-java-contrib.git")
6060
tag.set(tagVersion)
61-
url.set("https://github.com/open-telemetry/opentelemetry-java-contrib/tree/${tagVersion}")
61+
url.set("https://github.com/open-telemetry/opentelemetry-java-contrib/tree/$tagVersion")
6262
}
6363

6464
issueManagement {
Lines changed: 264 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,264 @@
1+
import io.opentelemetry.gradle.OtelJavaExtension
2+
import org.gradle.api.provider.Property
3+
import java.io.File
4+
5+
// Weaver code generation convention plugin for OpenTelemetry
6+
// Apply this plugin to modules that have a model/ directory with weaver model files
7+
// It will generate Java code, documentation, and YAML configs using the OpenTelemetry Weaver tool
8+
9+
/**
10+
* Extension for configuring Weaver code generation.
11+
*/
12+
interface OtelWeaverExtension {
13+
/**
14+
* The Java package path where generated code will be placed.
15+
* Path should use forward slashes (e.g., "io/opentelemetry/contrib/mymodule/metrics").
16+
*
17+
* Defaults to deriving the path from otelJava.moduleName by:
18+
* 1. Removing "io.opentelemetry.contrib." prefix
19+
* 2. Converting dots and hyphens to forward slashes
20+
* 3. Prepending "io/opentelemetry/"
21+
*
22+
* Example: "io.opentelemetry.contrib.ibm-mq-metrics" → "io/opentelemetry/ibm/mq/metrics"
23+
*/
24+
val javaOutputPackage: Property<String>
25+
}
26+
27+
/**
28+
* Derives a Java package path from the otelJava.moduleName.
29+
* Converts module name format to filesystem path format.
30+
*/
31+
fun derivePackagePathFromModuleName(): String {
32+
val otelJava = extensions.findByType(OtelJavaExtension::class.java)
33+
if (otelJava != null && otelJava.moduleName.isPresent) {
34+
val moduleName = otelJava.moduleName.get()
35+
36+
// Remove common prefix and convert to path
37+
return moduleName
38+
.removePrefix("io.opentelemetry.contrib.")
39+
.removePrefix("io.opentelemetry.")
40+
.replace('.', '/')
41+
.replace('-', '/')
42+
.let { "io/opentelemetry/$it" }
43+
}
44+
45+
// Fallback if otelJava extension not found
46+
return "io/opentelemetry/metrics"
47+
}
48+
49+
fun configureWeaverTask(task: Exec) {
50+
task.group = "weaver"
51+
task.standardOutput = System.out
52+
task.errorOutput = System.err
53+
task.executable = "docker"
54+
}
55+
56+
// Create the weaver extension for configuration
57+
val weaverExtension = extensions.create("otelWeaver", OtelWeaverExtension::class.java)
58+
59+
// Check if this module has a model directory
60+
val modelDir = File(project.projectDir, "model")
61+
val hasWeaverModel = modelDir.exists() && modelDir.isDirectory
62+
63+
if (hasWeaverModel) {
64+
val templatesDir = File(project.projectDir, "templates")
65+
val docsDir = File(project.projectDir, "docs")
66+
val weaverContainer = "otel/weaver:v0.18.0@sha256:5425ade81dc22ddd840902b0638b4b6a9186fb654c5b50c1d1ccd31299437390"
67+
68+
// Configure default after project is evaluated so otelJava.moduleName is available
69+
afterEvaluate {
70+
if (!weaverExtension.javaOutputPackage.isPresent) {
71+
weaverExtension.javaOutputPackage.set(derivePackagePathFromModuleName())
72+
}
73+
}
74+
75+
logger.lifecycle("Weaver model found in ${project.name}")
76+
logger.lifecycle(" Model directory: ${modelDir.absolutePath}")
77+
logger.lifecycle(" Templates directory: ${templatesDir.absolutePath}")
78+
logger.lifecycle(" Container: $weaverContainer")
79+
80+
81+
tasks.register<Exec>("weaverCheck") {
82+
configureWeaverTask(this)
83+
description = "Check the weaver model for errors"
84+
85+
val modelDirPath = modelDir.absolutePath
86+
val templatesDirPath = templatesDir.absolutePath
87+
val hasTemplates = templatesDir.exists() && templatesDir.isDirectory
88+
89+
inputs.dir(modelDir)
90+
if (hasTemplates) {
91+
inputs.dir(templatesDir)
92+
}
93+
94+
val tempDir = layout.buildDirectory.dir("weaver-check").get().asFile
95+
96+
doFirst {
97+
tempDir.mkdirs()
98+
99+
val mountArgs = mutableListOf(
100+
"--mount", "type=bind,source=$modelDirPath,target=/home/weaver/model,readonly"
101+
)
102+
if (hasTemplates) {
103+
mountArgs.addAll(listOf(
104+
"--mount", "type=bind,source=$templatesDirPath,target=/home/weaver/templates,readonly"
105+
))
106+
}
107+
mountArgs.addAll(listOf(
108+
"--mount", "type=bind,source=${tempDir.absolutePath},target=/home/weaver/target"
109+
))
110+
111+
args = listOf("run", "--rm", "--platform=linux/x86_64") +
112+
mountArgs +
113+
listOf(weaverContainer, "registry", "check", "--registry=/home/weaver/model")
114+
}
115+
}
116+
117+
tasks.register<Exec>("weaverGenerateDocs") {
118+
configureWeaverTask(this)
119+
description = "Generate markdown documentation from weaver model"
120+
121+
val modelDirPath = modelDir.absolutePath
122+
val templatesDirPath = templatesDir.absolutePath
123+
val docsDirPath = docsDir.absolutePath
124+
val hasTemplates = templatesDir.exists() && templatesDir.isDirectory
125+
126+
inputs.dir(modelDir)
127+
if (hasTemplates) {
128+
inputs.dir(templatesDir)
129+
}
130+
outputs.dir(docsDir)
131+
132+
doFirst {
133+
docsDir.mkdirs()
134+
135+
val mountArgs = mutableListOf(
136+
"--mount", "type=bind,source=$modelDirPath,target=/home/weaver/model,readonly"
137+
)
138+
if (hasTemplates) {
139+
mountArgs.addAll(listOf(
140+
"--mount", "type=bind,source=$templatesDirPath,target=/home/weaver/templates,readonly"
141+
))
142+
}
143+
mountArgs.addAll(listOf(
144+
"--mount", "type=bind,source=$docsDirPath,target=/home/weaver/target"
145+
))
146+
147+
args = listOf("run", "--rm", "--platform=linux/x86_64") +
148+
mountArgs +
149+
listOf(weaverContainer, "registry", "generate", "--registry=/home/weaver/model", "markdown", "--future", "/home/weaver/target")
150+
}
151+
}
152+
153+
val weaverGenerateJavaTask = tasks.register<Exec>("weaverGenerateJava") {
154+
configureWeaverTask(this)
155+
description = "Generate Java code from weaver model"
156+
157+
val modelDirPath = modelDir.absolutePath
158+
val templatesDirPath = templatesDir.absolutePath
159+
val hasTemplates = templatesDir.exists() && templatesDir.isDirectory
160+
161+
inputs.dir(modelDir)
162+
if (hasTemplates) {
163+
inputs.dir(templatesDir)
164+
}
165+
166+
// Register outputs lazily using provider
167+
val javaOutputDirProvider = weaverExtension.javaOutputPackage.map {
168+
File(project.projectDir, "src/main/java/$it")
169+
}
170+
outputs.dir(javaOutputDirProvider)
171+
172+
doFirst {
173+
val outputDir = javaOutputDirProvider.get()
174+
logger.lifecycle(" Java output: ${outputDir.absolutePath}")
175+
outputDir.mkdirs()
176+
177+
val mountArgs = mutableListOf(
178+
"--mount", "type=bind,source=$modelDirPath,target=/home/weaver/model,readonly"
179+
)
180+
if (hasTemplates) {
181+
mountArgs.addAll(listOf(
182+
"--mount", "type=bind,source=$templatesDirPath,target=/home/weaver/templates,readonly"
183+
))
184+
}
185+
mountArgs.addAll(listOf(
186+
"--mount", "type=bind,source=${outputDir.absolutePath},target=/home/weaver/target"
187+
))
188+
189+
// Build base docker run command
190+
val baseDockerArgs = mutableListOf("run", "--rm", "--platform=linux/x86_64")
191+
192+
// Add user mapping for Linux (not needed on macOS with Docker Desktop)
193+
val os = System.getProperty("os.name").lowercase()
194+
if (os.contains("linux")) {
195+
try {
196+
val userId = ProcessBuilder("id", "-u").start().inputStream.bufferedReader().readText().trim()
197+
val groupId = ProcessBuilder("id", "-g").start().inputStream.bufferedReader().readText().trim()
198+
baseDockerArgs.addAll(listOf("-u", "$userId:$groupId"))
199+
} catch (e: Exception) {
200+
logger.warn("Failed to get user/group ID, generated files may be owned by root: ${e.message}")
201+
}
202+
}
203+
204+
args = baseDockerArgs + mountArgs +
205+
listOf(weaverContainer, "registry", "generate", "--registry=/home/weaver/model", "java", "--future", "/home/weaver/target")
206+
}
207+
}
208+
209+
// Make spotless tasks depend on weaver generation
210+
tasks.matching { it.name == "spotlessJava" || it.name == "spotlessJavaApply" || it.name == "spotlessApply" }.configureEach {
211+
mustRunAfter(weaverGenerateJavaTask)
212+
}
213+
214+
// Make weaverGenerateJava automatically format generated code
215+
weaverGenerateJavaTask.configure {
216+
finalizedBy("spotlessJavaApply")
217+
}
218+
219+
tasks.register<Exec>("weaverGenerateYaml") {
220+
configureWeaverTask(this)
221+
description = "Generate YAML configuration from weaver model"
222+
223+
val modelDirPath = modelDir.absolutePath
224+
val templatesDirPath = templatesDir.absolutePath
225+
val projectDirPath = project.projectDir.absolutePath
226+
val hasTemplates = templatesDir.exists() && templatesDir.isDirectory
227+
228+
inputs.dir(modelDir)
229+
if (hasTemplates) {
230+
inputs.dir(templatesDir)
231+
}
232+
outputs.file(File(project.projectDir, "config.yml"))
233+
234+
doFirst {
235+
val mountArgs = mutableListOf(
236+
"--mount", "type=bind,source=$modelDirPath,target=/home/weaver/model,readonly"
237+
)
238+
if (hasTemplates) {
239+
mountArgs.addAll(listOf(
240+
"--mount", "type=bind,source=$templatesDirPath,target=/home/weaver/templates,readonly"
241+
))
242+
}
243+
mountArgs.addAll(listOf(
244+
"--mount", "type=bind,source=$projectDirPath,target=/home/weaver/target"
245+
))
246+
247+
args = listOf("run", "--rm", "--platform=linux/x86_64") +
248+
mountArgs +
249+
listOf(weaverContainer, "registry", "generate", "--registry=/home/weaver/model", "yaml", "--future", "/home/weaver/target")
250+
}
251+
}
252+
253+
tasks.register("weaverGenerate") {
254+
description = "Generate all outputs (Java, docs, YAML) from weaver model"
255+
group = "weaver"
256+
dependsOn("weaverGenerateJava", "weaverGenerateDocs", "weaverGenerateYaml")
257+
}
258+
259+
tasks.named("compileJava") {
260+
dependsOn("weaverGenerateJava")
261+
}
262+
} else {
263+
logger.debug("No weaver model directory found in ${project.name}, skipping weaver task registration")
264+
}

0 commit comments

Comments
 (0)