diff --git a/build.gradle.kts b/build.gradle.kts index cbda3f76f82..b1a691f2a50 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -12,6 +12,7 @@ plugins { id("dd-trace-java.ci-jobs") id("com.diffplug.spotless") version "8.1.0" + id("me.champeau.gradle.japicmp") version "0.4.3" id("com.github.spotbugs") version "6.4.7" id("de.thetaphi.forbiddenapis") version "3.10" id("io.github.gradle-nexus.publish-plugin") version "2.0.0" @@ -156,3 +157,29 @@ testAggregate( ":dd-java-agent:agent-debugger" ) ) + +// JApiCmp configuration example +// Usage: ./gradlew japicmp -Partifact=groupId:artifactId -Pbaseline=1.0.0 -Ptarget=2.0.0 +tasks.register("japicmp") { + val artifact = providers.gradleProperty("artifact").orNull + val baseline = providers.gradleProperty("baseline").orNull + val target = providers.gradleProperty("target").orNull + + if (artifact != null && baseline != null && target != null) { + oldClasspath.from( + configurations.detachedConfiguration( + dependencies.create("$artifact:$baseline") + ) + ) + newClasspath.from( + configurations.detachedConfiguration( + dependencies.create("$artifact:$target") + ) + ) + onlyModified.set(true) + failOnModification.set(false) + ignoreMissingClasses.set(true) + txtOutputFile.set(layout.buildDirectory.file("reports/japicmp.txt")) + htmlOutputFile.set(layout.buildDirectory.file("reports/japicmp.html")) + } +} diff --git a/docs/how_instrumentations_work.md b/docs/how_instrumentations_work.md index ec641ccf928..21a729ed64b 100644 --- a/docs/how_instrumentations_work.md +++ b/docs/how_instrumentations_work.md @@ -91,14 +91,44 @@ To run muzzle on your instrumentation, run: > [!WARNING] > Muzzle does _not_ run tests. -> It checks that the types and methods used by the instrumentation are present in particular versions of libraries. -> It can be subverted with `MethodHandle` and reflection -- in other words, having the `muzzle` task passing is not enough +> It checks that the types and methods used by the instrumentation are present in particular versions of libraries. +> It can be subverted with `MethodHandle` and reflection -- in other words, having the `muzzle` task passing is not enough > to validate an instrumentation. By default, all the muzzle directives are checked against all the instrumentations included in a module. -However, there can be situations in which it’s only needed to check one specific directive on an instrumentation. +However, there can be situations in which it's only needed to check one specific directive on an instrumentation. At this point the instrumentation should override the method `muzzleDirective()` by returning the name of the directive to execute. +### Identifying Breaking Changes with JApiCmp + +Before defining muzzle version ranges, you can use the JApiCmp plugin to compare different versions of a library and +identify breaking API changes. This helps determine where to split version ranges in your muzzle directives. + +The `japicmp` task compares two versions of a Maven artifact and reports: +- Removed classes and methods (breaking changes) +- Added classes and methods (non-breaking changes) +- Modified methods with binary compatibility status + +#### Usage + +Compare two versions of any Maven artifact: + +```shell +./gradlew japicmp -Partifact=groupId:artifactId -Pbaseline=oldVersion -Ptarget=newVersion +``` + +For example, to compare MongoDB driver versions: + +```shell +./gradlew japicmp -Partifact=org.mongodb:mongodb-driver-sync -Pbaseline=3.11.0 -Ptarget=4.0.0 +``` + +#### Output + +The task generates two reports: + +- **Text report**: `build/reports/japicmp.txt` - Detailed line-by-line comparison +- **HTML report**: `build/reports/japicmp.html` - Browsable visual report ## Instrumentation classes