@@ -23,7 +23,86 @@ repositories {
2323 mavenCentral()
2424}
2525
26- def langchainVersion = ' 1.9.1'
26+ // Shared config for instrumentation subprojects
27+ subprojects {
28+ apply plugin : ' java'
29+ apply plugin : ' dev.braintrust.muzzle'
30+
31+ java {
32+ toolchain {
33+ languageVersion = JavaLanguageVersion . of(17 )
34+ vendor = JvmVendorSpec . ADOPTIUM
35+ }
36+ }
37+
38+ tasks. withType(JavaCompile ). configureEach {
39+ options. release = 17
40+ }
41+
42+ repositories {
43+ mavenCentral()
44+ }
45+ }
46+
47+ // Add muzzle generation task and muzzle check plugin to all instrumentation subprojects.
48+ subprojects { subproject ->
49+ subproject. afterEvaluate {
50+ if (subproject. path. startsWith(' :braintrust-sdk:instrumentation:' )) {
51+ def instrumentationApi = project(' :braintrust-java-agent:instrumenter' )
52+
53+ // Bootstrap classes (e.g. BraintrustBridge) are on the bootstrap classpath at runtime.
54+ // Make them available at compile time so instrumentation modules can reference them.
55+ dependencies. add(' compileOnly' , project(' :braintrust-java-agent:bootstrap' ))
56+
57+ // --- $Muzzle side-class generation (compile-time) ---
58+
59+ // Configuration for the muzzle generator classpath
60+ configurations. maybeCreate(' muzzleGenerator' )
61+ dependencies. add(' muzzleGenerator' , instrumentationApi)
62+ dependencies. add(' muzzleGenerator' , ' net.bytebuddy:byte-buddy:1.17.5' )
63+
64+ task generateMuzzle(type : JavaExec ) {
65+ dependsOn compileJava, instrumentationApi. tasks. named(' compileJava' )
66+ description = ' Generates $Muzzle side-classes for InstrumentationModule subclasses'
67+ group = ' build'
68+
69+ // Classpath: compiled instrumentation classes + instrumenter + bytebuddy + deps
70+ classpath = files(
71+ sourceSets. main. output. classesDirs,
72+ configurations. muzzleGenerator,
73+ configurations. compileClasspath
74+ )
75+ mainClass = ' dev.braintrust.instrumentation.muzzle.MuzzleGenerator'
76+ args = [sourceSets. main. java. classesDirectory. get(). asFile. absolutePath]
77+ }
78+
79+ // Run muzzle generation after compileJava, before jar
80+ tasks. named(' classes' ). configure { dependsOn generateMuzzle }
81+
82+ // Run muzzle checks as part of the check lifecycle
83+ tasks. named(' check' ). configure { dependsOn ' muzzle' }
84+ }
85+ }
86+ }
87+
88+ // Configuration for embedding instrumentation subproject classes into the SDK JAR
89+ configurations {
90+ // Non-transitive: we only want the instrumentation subproject JARs themselves,
91+ // not their transitive dependencies (which include braintrust-sdk itself).
92+ embed {
93+ transitive = false
94+ }
95+ }
96+
97+ // Auto-discover instrumentation subprojects and add them to the embed configuration.
98+ // This must be done after project evaluation so the subprojects are resolved.
99+ afterEvaluate {
100+ project. subprojects. each { sub ->
101+ if (sub. path. startsWith(' :braintrust-sdk:instrumentation:' )) {
102+ dependencies. add(' embed' , sub)
103+ }
104+ }
105+ }
27106
28107dependencies {
29108 api " io.opentelemetry:opentelemetry-api:${ otelVersion} "
@@ -52,27 +131,44 @@ dependencies {
52131 testImplementation " org.junit.jupiter:junit-jupiter-params:${ junitVersion} "
53132 testImplementation ' org.wiremock:wiremock:3.13.1'
54133
55- // OAI instrumentation
56- compileOnly ' com.openai:openai-java:2.8.1'
57- testImplementation ' com.openai:openai-java:2.8.1'
58-
59- // Anthropic Instrumentation
60- compileOnly " com.anthropic:anthropic-java:2.8.1"
61- testImplementation " com.anthropic:anthropic-java:2.8.1"
62-
63- // Google GenAI Instrumentation
64- compileOnly " com.google.genai:google-genai:1.20.0"
65- testImplementation " com.google.genai:google-genai:1.20.0"
66-
67- // LangChain4j Instrumentation
68- compileOnly " dev.langchain4j:langchain4j:${ langchainVersion} "
69- compileOnly " dev.langchain4j:langchain4j-http-client:${ langchainVersion} "
70- compileOnly " dev.langchain4j:langchain4j-open-ai:${ langchainVersion} "
71- testImplementation " dev.langchain4j:langchain4j:${ langchainVersion} "
72- testImplementation " dev.langchain4j:langchain4j-http-client:${ langchainVersion} "
73- testImplementation " dev.langchain4j:langchain4j-open-ai:${ langchainVersion} "
134+
74135}
75136
137+ // Merge META-INF/services files from embedded instrumentation JARs.
138+ // This ensures ServiceLoader can discover InstrumentationModule implementations.
139+ task mergeEmbedServices {
140+ dependsOn configurations. embed
141+ def mergedDir = layout. buildDirectory. dir(" merged-services" )
142+ outputs. dir mergedDir
143+
144+ doLast {
145+ def servicesMap = [:]
146+ configurations. embed. each { file ->
147+ if (file. isFile()) {
148+ zipTree(file). matching { include ' META-INF/services/**' }. visit { details ->
149+ if (! details. isDirectory()) {
150+ def serviceName = details. relativePath. toString()
151+ if (! servicesMap. containsKey(serviceName)) {
152+ servicesMap[serviceName] = new LinkedHashSet<String > ()
153+ }
154+ servicesMap[serviceName]. addAll(details. file. readLines(). findAll { it. trim() })
155+ }
156+ }
157+ }
158+ }
159+ def outDir = mergedDir. get(). asFile
160+ outDir. deleteDir()
161+ servicesMap. each { path , lines ->
162+ def outFile = new File (outDir, path)
163+ outFile. parentFile. mkdirs()
164+ outFile. text = lines. join(' \n ' ) + ' \n '
165+ }
166+ }
167+ }
168+
169+ jar. dependsOn mergeEmbedServices
170+ jar. from(layout. buildDirectory. dir(" merged-services" ))
171+
76172// Generate braintrust.properties at build time with smart versioning
77173task generateBraintrustProperties {
78174 description = ' Generate braintrust.properties with smart git-based versioning'
@@ -128,6 +224,19 @@ jar {
128224 ' Main-Class' : ' dev.braintrust.SDKMain'
129225 )
130226 }
227+
228+ // Embed instrumentation subproject classes into the SDK JAR.
229+ // Use dependsOn to ensure instrumentation jars are built first.
230+ dependsOn configurations. embed
231+
232+ from({
233+ configurations. embed. collect { it. isDirectory() ? it : zipTree(it) }
234+ }) {
235+ // Exclude META-INF from embedded jars (we handle service files separately below)
236+ exclude ' META-INF/**'
237+ }
238+
239+ duplicatesStrategy = DuplicatesStrategy . EXCLUDE
131240}
132241
133242import com.vanniktech.maven.publish.JavadocJar
0 commit comments