Skip to content

Introduce module-kind convention plugins#11620

Open
bric3 wants to merge 13 commits into
masterfrom
bdu/introduce-first-module-conventions
Open

Introduce module-kind convention plugins#11620
bric3 wants to merge 13 commits into
masterfrom
bdu/introduce-first-module-conventions

Conversation

@bric3

@bric3 bric3 commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

What Does This Do

Introduces module-kind convention plugins for Java-backed Gradle modules and applies them from plugins {} while keeping the existing script plugins as the behavior source.

The basic change is usually removing the apply from of the java script plugin, and use the modern recommended plugins block with the identified module kind :

- apply from: "$rootDir/gradle/java.gradle"
+ plugins {
+   id 'dd-trace-java.module.instrumentation'
+ }

In particular these are the identified module kind, in this PR, it's likely some kind are not represented in this PR, and that's not the goal to be exhaustive at this stage, it can be refined later (for example for the shared modules in the agent jar).

  • dd-trace-java.module.agent-product

  • dd-trace-java.module.annotation-processor

  • dd-trace-java.module.bootstrap-component

    It is for code whose artifact is intentionally part of the agent's bootstrap surface. It is expected to be included at the root of the final agent jar, currently directly through shadowInclude.

    // Includes for the top level shadow jar
    shadowInclude project(path: ':components:environment', configuration: 'shadow')
    shadowInclude project(path: ':dd-java-agent:agent-bootstrap')
    shadowInclude project(path: ':dd-java-agent:agent-debugger:debugger-bootstrap')
    shadowInclude project(path: ':dd-java-agent:agent-otel:otel-bootstrap', configuration: 'shadow')
    // embed an extra copy of our otel-shim for drop-in/extension support
    shadowInclude project(path: ':dd-java-agent:agent-otel:otel-shim')
    shadowInclude project(path: ':products:feature-flagging:feature-flagging-bootstrap')

    The classes in these modules (and their dependencies) are visible through the JVM bootstrap classloader once the agent jar is appended to the bootstrap search path. This the strictest category because the code can run very early and can be referenced from instrumented application classes.

    Note, the dependencies of these modules do not use this convention.

    The bootstrap modules and their dependencies happens to be excluded from the shared/ prefix

    it.dependencies {
    exclude(project(':dd-java-agent:agent-bootstrap'))
    exclude(project(':dd-java-agent:agent-logging'))
    exclude(project(':dd-trace-api'))
    exclude(project(':internal-api'))
    exclude(project(':components:context'))
    exclude(project(':utils:config-utils'))
    exclude(project(':utils:logging-utils'))
    exclude(project(':utils:time-utils'))
    exclude(project(':products:metrics:metrics-api'))
    exclude(project(':products:metrics:metrics-agent'))
    exclude(dependency('org.slf4j::'))
    // use dd-instrument-java's embedded copy of asm
    exclude(dependency('org.ow2.asm:asm:'))
    }
    }

  • dd-trace-java.module.distributable.api (here the dot is intended, as the idea is to have a distributable.agent)

  • dd-trace-java.module.instrumentation

  • dd-trace-java.module.internal-library

    This is for internal implementation libraries. They are regular shared implementation code, generally intended to be consumed by other agent modules and often packaged inside shared/, trace/, or a product-specific directory in the final jar. Some of them can still land at the jar root (bootstrap) transitively when a bootstrap component depends on them, for example internal-api or metrics-agent; that does not necessarily make them bootstrap components. It just means a bootstrap component needs them.

  • dd-trace-java.module.internal-platform-component

    It is for low-level platform building blocks shared by several parts of the agent: context, json, annotations, HTTP primitives, etc. These modules are not agent
    products and are not feature implementations. They are reusable infrastructure. They may end up bootstrap-visible when a bootstrap module depends on them, but that is a consequence of who consumes them.

  • dd-trace-java.module.smoke-test

  • dd-trace-java.module.testing-support

I chose module prefix as a way to convey the "higher level" module kind. While there might be other lower level technical conventions.

Small point of attention to the :dd-java-agent:instrumentation module, this PR replaces this check

if (subProj.path != ':dd-java-agent:instrumentation:vertx:vertx-redis-client:vertx-redis-client-stubs') {
  // don't include the redis RequestImpl stubs
  parent_project.dependencies {
    addProvider("implementation", providers.provider { project(subProj.path) })
  }
}

By checking if the sub project has the dd-trace-java.module.instrumentation convention, if it does it applies the necessary conventions for the instrumentation, so it just wraps existing code within the following closure:

subProj.pluginManager.withPlugin("dd-trace-java.module.instrumentation") {
  // ...
  parent_project.dependencies {
     addProvider("implementation", providers.provider { project(subProj.path) })
  }
}

Motivation

Prepare a gradual migration from lower-level script plugin application to higher-level conventions that describe what each module is.

Gradle references used for this migration direction:

Additional Details

The current forbidden-apis wiring for instrumentation modules is preserved behaviorally, but the investigation found it is not the intended rule set. On master, instrumentation tasks effectively read only instrumentation.txt; the expected rule set should be main.txt plus instrumentation.txt. The old += ordering with forbidden-apis 3.10 convention mapping let APIs covered only by main.txt leak into instrumentation modules. Restoring the intended combined signature set should be a follow-up PR.

:dd-java-agent:instrumentation itself is intentionally not migrated further here. Its aggregator-specific wiring, especially around muzzle reports, needs preparatory work before moving more of that project into convention-plugin logic.

jardiff was used to compare the agent jar from master and this branch. The agent jar contains both .class and .classdata entries, so the stricter check includes both and coalesces .classdata as class-like bytecode:

$ mise exec java@corretto-25.0.3.9.1 -- jardiff --stat -c classdata -i '**/*.class,**/*.classdata' /private/tmp/dd-trace-java-jardiff-master-640b1156e1/dd-java-agent/build/libs/dd-java-agent-1.64.0-SNAPSHOT.jar ./build/libs/dd-java-agent-1.64.0-SNAPSHOT.jar | tail -n 1

0 files changed, 0 insertions(+), 0 deletions(-)

Contributor Checklist

  • Format the title according to the contribution guidelines
  • Assign the type: and (comp: or inst:) labels in addition to any other useful labels
  • Avoid using close, fix, or any linking keywords when referencing an issue
    Use solves instead, and assign the PR milestone to the issue
  • Update the CODEOWNERS file on source file addition, migration, or deletion
  • Update public documentation with any new configuration flags or behaviors
  • Add your completed PR to the merge queue by commenting /merge. You can also:
    • Customize the commit message associated with the merge with /merge --commit-message "..."
    • Remove your PR from the merge queue with /merge -c
    • Skip all merge queue checks with /merge -f --reason "reason"; please use this judiciously, as some checks do not run at the PR-level (note: the PR still needs to be mergeable, this will only skip the pre-merge build)
    • Get more information in this doc

Jira ticket: [N/A]

@bric3 bric3 added comp: tooling Build & Tooling tag: ai generated Largely based on code generated by an AI or LLM tag: no release notes Changes to exclude from release notes type: refactoring labels Jun 10, 2026
@datadog-datadog-prod-us1

This comment has been minimized.

@bric3 bric3 marked this pull request as ready for review June 10, 2026 15:53
@bric3 bric3 requested review from a team as code owners June 10, 2026 15:53
@bric3 bric3 requested review from claponcet, dd-oleksii, dudikeleti, jandro996, leoromanovsky and ygree and removed request for a team June 10, 2026 15:53
Comment thread dd-java-agent/instrumentation/build.gradle Outdated
@pr-commenter

pr-commenter Bot commented Jun 11, 2026

Copy link
Copy Markdown

Kafka / producer-benchmark

Parameters

Baseline Candidate
baseline_or_candidate baseline candidate
git_branch master bdu/introduce-first-module-conventions
git_commit_date 1782136018 1782146130
git_commit_sha 11c9d48 0a0226c
See matching parameters
Baseline Candidate
ci_job_date 1782147390 1782147390
ci_job_id 1792466116 1792466116
ci_pipeline_id 120272581 120272581
cpu_model Intel(R) Xeon(R) Platinum 8259CL CPU @ 2.50GHz Intel(R) Xeon(R) Platinum 8259CL CPU @ 2.50GHz
jdkVersion 11.0.25 11.0.25
jmhVersion 1.36 1.36
jvm /usr/lib/jvm/java-11-openjdk-amd64/bin/java /usr/lib/jvm/java-11-openjdk-amd64/bin/java
jvmArgs -Dfile.encoding=UTF-8 -Djava.io.tmpdir=/go/src/github.com/DataDog/apm-reliability/dd-trace-java/platform/src/producer-benchmark/build/tmp/jmh -Duser.country=US -Duser.language=en -Duser.variant -Dfile.encoding=UTF-8 -Djava.io.tmpdir=/go/src/github.com/DataDog/apm-reliability/dd-trace-java/platform/src/producer-benchmark/build/tmp/jmh -Duser.country=US -Duser.language=en -Duser.variant
vmName OpenJDK 64-Bit Server VM OpenJDK 64-Bit Server VM
vmVersion 11.0.25+9-post-Ubuntu-1ubuntu122.04 11.0.25+9-post-Ubuntu-1ubuntu122.04

Summary

Found 0 performance improvements and 0 performance regressions! Performance is the same for 3 metrics, 0 unstable metrics.

See unchanged results
scenario Δ mean throughput
scenario:not-instrumented/KafkaProduceBenchmark.benchProduce same
scenario:only-tracing-dsm-disabled-benchmarks/KafkaProduceBenchmark.benchProduce same
scenario:only-tracing-dsm-enabled-benchmarks/KafkaProduceBenchmark.benchProduce same

@pr-commenter

pr-commenter Bot commented Jun 11, 2026

Copy link
Copy Markdown

Kafka / consumer-benchmark

Parameters

Baseline Candidate
baseline_or_candidate baseline candidate
git_branch master bdu/introduce-first-module-conventions
git_commit_date 1782136018 1782146130
git_commit_sha 11c9d48 0a0226c
See matching parameters
Baseline Candidate
ci_job_date 1782147438 1782147438
ci_job_id 1792466120 1792466120
ci_pipeline_id 120272581 120272581
cpu_model Intel(R) Xeon(R) Platinum 8259CL CPU @ 2.50GHz Intel(R) Xeon(R) Platinum 8259CL CPU @ 2.50GHz
jdkVersion 11.0.25 11.0.25
jmhVersion 1.36 1.36
jvm /usr/lib/jvm/java-11-openjdk-amd64/bin/java /usr/lib/jvm/java-11-openjdk-amd64/bin/java
jvmArgs -Dfile.encoding=UTF-8 -Djava.io.tmpdir=/go/src/github.com/DataDog/apm-reliability/dd-trace-java/platform/src/consumer-benchmark/build/tmp/jmh -Duser.country=US -Duser.language=en -Duser.variant -Dfile.encoding=UTF-8 -Djava.io.tmpdir=/go/src/github.com/DataDog/apm-reliability/dd-trace-java/platform/src/consumer-benchmark/build/tmp/jmh -Duser.country=US -Duser.language=en -Duser.variant
vmName OpenJDK 64-Bit Server VM OpenJDK 64-Bit Server VM
vmVersion 11.0.25+9-post-Ubuntu-1ubuntu122.04 11.0.25+9-post-Ubuntu-1ubuntu122.04

Summary

Found 0 performance improvements and 0 performance regressions! Performance is the same for 3 metrics, 0 unstable metrics.

See unchanged results
scenario Δ mean throughput
scenario:not-instrumented/KafkaConsumerBenchmark.benchConsume same
scenario:only-tracing-dsm-disabled-benchmarks/KafkaConsumerBenchmark.benchConsume same
scenario:only-tracing-dsm-enabled-benchmarks/KafkaConsumerBenchmark.benchConsume same

@amarziali amarziali left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bric3 it looks OK to me . I remember we have different documentations (wrt insturmentation and how to deal with gradle). Can we have that documented there please?

@bric3

bric3 commented Jun 15, 2026

Copy link
Copy Markdown
Contributor Author

@amarziali Good spot, I'll update the doc.

@PerfectSlayer

Copy link
Copy Markdown
Contributor

Sorry for the late feedback, I was struggling to find some time to get back at it.
About the module proposal, here are the ones I can think of (again, for high level modules):

  • agent - product module that gets added to the agent / bootstrap
  • api - product public api - expect addition API compatibility plugin here
  • lib - not a great name put internal product implementation
  • component - platform modules (currently under :components)
  • instrumentation - instrumentation modules (currently under :java-agent:instrumentations)
  • smoke-tests - all the smoke tests project (currently under :dd-smoke-tests)

Most of the existing modules will fit under lib, or api if they have a dedicated one.
Temeletry, remote config, utils, etc... can all fit to the lib one.
I would try with a limit set of convention plugins to check if it fits the current project module.
WDYT?

@bric3

bric3 commented Jun 19, 2026

Copy link
Copy Markdown
Contributor Author

No problem @PerfectSlayer, thank you very much for the review.

  • agent - product module that gets added to the agent / bootstrap

I propose dd-trace-java.module.agent-product, because I believe agent itself can be reserved to the final produced agent jar, and might be related to packaging in particular. Also, given bootstrap module is a tad particular as it's not really a library I chose to identify it with dd-trace-java.module.bootstrap-component

  • api - product public api - expect addition API compatibility plugin here
  • lib - not a great name put internal product implementation
  • component - platform modules (currently under :components)

That's a good point, at that time they are all under dd-trace-java.module.internal-component.

  • instrumentation - instrumentation modules (currently under :java-agent:instrumentations)
  • smoke-tests - all the smoke tests project (currently under :dd-smoke-tests)

Indeed those are covered.

I would try with a limit set of convention plugins to check if it fits the current project module.

Yes and no, while I was aiming for few conventions, it appeared to me that modules were sufficiently "different" in their purpose to warrant specific conventions. Also, e.g. those that apply these conventions:

  • dd-trace-java.module.annotation-processor
  • dd-trace-java.module.testing-support - focus on tests, and not shipped in the distribution
  • dd-trace-java.module.distributable.api - the jar that get pushed to central

Moreover, the end game it to remove script plugins, and by experience it's not a only have lower level (technical) convention plugins. THat's why identifying the purpose of a module is sensible. The project is large, and that is reflected in various purpose this project has.

@PerfectSlayer

Copy link
Copy Markdown
Contributor

because I believe agent itself can be reserved to the final produced agent jar

But it won't use the dd-trace-java.module prefix? More something like dd-trace-java.build or dd-trace-java.artefact

at that time they are all under dd-trace-java.module.internal-component.

Make sure to have a separate plugin for the ones under :components and modules like :communication.
The platform components have strict constraints for testing, dependencies, etc... that :communications or :telemetry break.

THat's why identifying the purpose of a module is sensible.

I wonder if we should focus on setting modules up for where we want to go, or what we have right now?
Or we should named the one we want to get rid of as "legacy" / "deprecated" for example?

@bric3

bric3 commented Jun 22, 2026

Copy link
Copy Markdown
Contributor Author

But it won't use the dd-trace-java.module prefix? More something like dd-trace-java.build or dd-trace-java.artefact

Yes, I was thinking like dd-trace-java.distributable.agent.

I wonder if we should focus on setting modules up for where we want to go, or what we have right now?

I asked the same question, but I think it's gonna be easier to refactor if we have convention modules that identify modules for what they are today. Since they convey some expectation like you mentioned for components, we can easily spot modules for what they or what they aren't.

@PerfectSlayer

Copy link
Copy Markdown
Contributor

if we have convention modules that identify modules for what they are today

Alright, just keep in mind we will have duplicate as :components and :products are where we want to go to (and some already are in place) while we still have :communications, :telemetry, dd-trace-core, etc... 😬

bric3 added 7 commits June 22, 2026 16:09
Introduce module-kind convention plugins that describe what a Gradle
project is, rather than only how it is wired.

These conventions keep the existing script plugins as the source of
truth for now, but let modules apply them through plugins {}.
This starts a gradual migration path away from direct apply from usage
without changing existing lower level plugin scripts at this time.

This only changes the application path for `gradle/java.gradle`.
It introduces module conventions for instrumentation, internal
components, smoke tests, sub-agents, annotation processors, and
distributable API modules.

The change also normalizes plugin declarations in touched build files so
dd-trace-java.* and core Gradle plugins are declared in plugins {} where
possible.

Gradle documents convention plugins from `buildSrc` or `build-logic` as
the recommended way to share build logic, while script plugin
application via `apply()` is listed as legacy/not recommended:
* https://docs.gradle.org/9.5.1/userguide/sharing_build_logic_between_subprojects.html#sec:sharing_logic_via_convention_plugins
* https://docs.gradle.org/9.5.1/userguide/plugins_intermediate.html#sec:script_plugins
Disable Spotless' Groovy Java exclusion after the local targets are declared.

This preserves the previous scoped formatting behavior now that scala is applied from the plugins block.
Keep the instrumentation aggregator non-Java while restoring archive conventions and the root muzzle report task used by CI.
Keep the module-kind hook, but leave the Java-based configurations and muzzle report behavior in place for now.
Keep instrumentation modules on the same forbidden API signature set that the
script-plugin ordering used before module conventions. Applying `java.gradle`
from `plugins {}` otherwise adds the global `main` filter to these tasks.

Checked with
* `:dd-java-agent:instrumentation:liberty:liberty-20.0:forbiddenApisMain --info`
* `:dd-java-agent:instrumentation:java:java-lang:java-lang-1.8:forbiddenApisMain --info`
* `:components:context:forbiddenApisMain --info`
* ...
on `master` and this branch. On `master` the tasks only reads
`gradle/forbiddenApiFilters/instrumentation.txt` on instrumantation
modules, while the modification in this branch, before this commit,
read `main.txt` + `instrumentation.txt`.

The ordering issue is subtle: the old append on `master` happened before
`forbiddenapis.gradle` installed `main.txt` on the extension.
The `forbidden-apis` 3.10 plugin uses convention mapping rather than a
live Provider-backed task property, so that early task actually read
captured the "then-empty" default when adding `instruentation.txt`
and the later extension assignment did not appended `main.txt` into the
task value.

The purpose of this branch is to keep the convention-plugin migration
behavior-neutral. A follow-up PR should restore the intended rule set,
i.e. `main.txt` + `instrumentation.txt`, because the current mechanism
is broken and allowed some forbidden APIs only covered by `main.txt`
to leak into instrumentation modules.
Apply the shadow plugin before the distributable API convention so `publish.gradle` sees the same plugin state it saw on `master`. This keeps `dd-trace-ot` on the shadow publication path and avoids publishing metadata that points at internal components.
Rename the `sub-agent` module kind to `agent-product` and reserve it for
shipped agent products.
Add `bootstrap-component` and `testing-support` module kinds.
@bric3 bric3 force-pushed the bdu/introduce-first-module-conventions branch from 2ea7e84 to 8beb2c7 Compare June 22, 2026 14:16
bric3 added 3 commits June 22, 2026 17:40
Replace the `module.internal-component` module plugin with explicit
internal module kinds.

* Use `module.internal-api` for product and remote-config API surfaces.
* Use `module.internal-library` for product implementation modules and
  shared internal libraries such as `:communication`, `:telemetry`,
  `:utils`, and agent helper modules.
* Use `module.internal-platform-component` only for modules under
  `:components` so their stricter platform dependency and testing
  constraints can evolve separately.

Currently, they still delegate to the shared Java convention; in essence
this commit expand the module taxonomy precision, not the underlying
build behavior.
@bric3 bric3 force-pushed the bdu/introduce-first-module-conventions branch from 8beb2c7 to 0a0226c Compare June 22, 2026 16:35
bric3 added 2 commits June 23, 2026 17:36
…-module-conventions

# Conflicts:
#	dd-smoke-tests/springboot-openliberty-20/build.gradle
#	dd-smoke-tests/springboot-openliberty-23/build.gradle

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot wasn't able to review this pull request because it exceeds the maximum number of files (300). Try reducing the number of changed files and requesting a review from Copilot again.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp: tooling Build & Tooling tag: ai generated Largely based on code generated by an AI or LLM tag: no release notes Changes to exclude from release notes type: refactoring

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants