Skip to content

Extract Spring Boot 2.x app source into nested-build subprojects#11408

Open
bric3 wants to merge 5 commits into
masterfrom
bdu/smoke-test-pattern-d-extract-applications
Open

Extract Spring Boot 2.x app source into nested-build subprojects#11408
bric3 wants to merge 5 commits into
masterfrom
bdu/smoke-test-pattern-d-extract-applications

Conversation

@bric3
Copy link
Copy Markdown
Contributor

@bric3 bric3 commented May 18, 2026

What Does This Do

For 12 smoke-test modules whose Spring Boot Gradle plugin is incompatible with Gradle 9, this PR extracts the application source into a self-contained application/ Gradle subproject and switches the outer module to the smokeTestApp { application { … } } DSL added in #11405.

Modules converted (bootJar):

  • springboot-thymeleaf (Spring Boot 2.7.15, Java 8)
  • springboot-freemarker (Spring Boot 2.7.15 plugin / 1.5.18 starter, Java 8)
  • springboot-velocity (Spring Boot 2.7.15 plugin / 1.5.18 starter, Java 8)
  • springboot-java-11 (Spring Boot 2.7.15, Java 11; passes iast-util-11 jar)
  • springboot-java-17 (Spring Boot 2.7.15, Java 17; passes iast-util-17 jar)
  • openfeature (Spring Boot 2.7.15, Java 11; passes feature-flagging-api jar)
  • kafka-2 (Spring Boot 2.7.15, Java 8; passes iast-util jar)
  • apm-tracing-disabled (Spring Boot 2.7.15, Java 8; passes dd-trace-api jar)

Modules converted (bootWar):

  • springboot-jpa (Spring Boot 2.6.0, Java 8; Lombok)
  • springboot-tomcat-jsp (Spring Boot 2.7.15, Java 8; JSP webapp)
  • springboot-jetty-jsp (Spring Boot 2.7.15, Java 8; JSP webapp)
  • springboot-tomcat (Spring Boot 2.5.12, Java 8; Ivy Tomcat download + unzip)

Tip

For each module the change is uniform:

  1. git mv src/main/ application/src/main/
  2. New application/settings.gradle + application/build.gradle carrying the Spring Boot plugin + the module's existing dependencies.
  3. Outer build.gradle rewritten to apply dd-trace-java.smoke-test-app and configure smokeTestApp { application { … } } (plus projectJar(name, project) for the 5 modules that forward a sibling jar).

Note

smokeTestApp has a convention of running Gradle 8.14.5, with a daemon running on Java 21, so it's not explicitly set. It's configurable if needed.

Motivation

The Spring Boot Gradle plugin pre-3.5.0 calls Configuration.getUploadTaskName(), removed in Gradle 9. These 12 modules block the root Gradle 9 migration.

Mirrors the goal of PR #11379 "Pattern C" (12 modules, source extraction), but instead of committing 22 per-application gradlew wrappers it uses the Gradle Tooling API via the dd-trace-java.smoke-test-app plugin (#11405). Net diff per module is smaller; nothing extra is committed to source control beyond the inner application/ Gradle scripts.

Additional Notes

  • Stacked on #11405 (smoke-test plugin infrastructure). Merge target is the infrastructure branch; rebase to master after Add smoke-test plugin for nested Gradle builds #11405 lands.
  • Per-module quirks:
    • kafka-2: spring-kafka-test is pinned to 2.8.11 in the outer test module (was previously resolved from the Spring Boot BOM, which no longer applies to the outer module).
    • springboot-freemarker / springboot-velocity: their XssController loads templates from resources/main/templates relative to the test JVM working directory. After the source move, the processed resources land under build/application/resources/main/. Each affected outer build adds a copyAppResources Copy task that mirrors them to build/resources/main/ so the runtime path resolves. jar dependsOn this Copy task to avoid an implicit-output conflict.
    • springboot-tomcat: the Ivy-downloaded Tomcat server (apache-tomcat-9.0.117) stays inside the nested build; the outer build forwards the unpack directory as a second system property via additionalSystemProperties.
    • apm-tracing-disabled: ApmTracingDisabledSamplingSmoke{V04,V1}Test are already @Flaky and continue to be skipped in CI.
  • Verified locally with ./gradlew :dd-smoke-tests:<module>:test -PskipFlakyTests=true for each module.

Contributor Checklist

🤖 Generated with Claude Code

Adds a new included build `build-logic/` hosting a single subproject
`smoke-test` that exposes the `dd-trace-java.smoke-test-app` plugin.

The plugin contributes:
- `NestedGradleBuild` task type that runs a nested Gradle build via the
  Gradle Tooling API. It pins the nested Gradle version (no committed
  per-application wrappers), uses the configured Java toolchain for the
  nested daemon, forwards artifact paths from the root build as
  `-P<name>=<path>`, and redirects the nested `buildDir` via
  `-PappBuildDir=<path>` so outputs land under the outer project's build
  directory.
- `smokeTestApp` project extension with an `application { ... }` block
  that registers the `NestedGradleBuild` task, wires it into every `Test`
  task via `dependsOn` + a `jvmArgumentProvider` for the produced
  artifact's system property. Consumers can also register
  `NestedGradleBuild` directly when they need more control; the plugin
  is a no-op until `application` or a manual registration is done.
- `projectJar(name, project)` helper that forwards a sibling project's
  jar to the nested build through a resolvable `Configuration` (avoids
  `evaluationDependsOn` and the cross-project access ordering issues).

The plugin is verified with JUnit 5 unit tests (`ProjectBuilder`) and
end-to-end tests that drive the Tooling API path through the Gradle Test
Kit with a temporary Kotlin-DSL test project.

`build-logic/settings.gradle.kts` references the existing
`gradle/libs.versions.toml` catalog (mirroring `buildSrc/`) so the
plugin can use the same library coordinates as the rest of the repo.
The Gradle libs Maven repository (`https://repo.gradle.org/gradle/libs-releases`,
scoped to `org.gradle:`) is added to the root build's `pluginManagement`
and to `gradle/repositories.gradle` so the Tooling API jar resolves.

Smoke-test modules with Spring Boot plugin versions incompatible with
Gradle 9 will use this plugin in follow-up PRs instead of a committed
Gradle 8 wrapper.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@bric3 bric3 added type: enhancement Enhancements and improvements tag: no release notes Changes to exclude from release notes comp: tooling Build & Tooling tag: ai generated Largely based on code generated by an AI or LLM labels May 18, 2026
@bric3 bric3 changed the title chore(smoke-tests): extract Spring Boot 2.x app source into nested-build subprojects Extract Spring Boot 2.x app source into nested-build subprojects May 19, 2026
@bric3 bric3 force-pushed the bdu/smoke-test-pattern-d-extract-applications branch from b924cb8 to 149a0ec Compare May 19, 2026 14:06
…14.5

Set conventions on `smokeTestApp`:
- `gradleVersion` defaults to `"8.14.5"` (Gradle 8 last release; pinned because
  Spring Boot plugin pre-3.5 calls `Configuration.getUploadTaskName()`, removed
  in Gradle 9).
- `javaLauncher` defaults to a JDK 21 toolchain (the version the root build
  requires for its own Gradle 9 migration; standardising the nested daemon on
  the same JDK avoids requiring an extra toolchain on dev machines and CI
  runners).

Consumers that need a different JDK or Gradle version still override
explicitly. The inner build script is responsible for pinning the produced
bytecode level (`java { sourceCompatibility = JavaVersion.VERSION_1_8 }` or
similar) — Gradle adds `--release N` automatically when source/target differs
from the daemon JVM.

`JavaToolchainService` is now injected into the extension; this works in any
project where a `java*` (or related) plugin is applied. Smoke-test modules
already apply `gradle/java.gradle`, which applies `java`, so the convention
resolves on first read.

Public defaults exposed as `DEFAULT_NESTED_GRADLE_VERSION` and
`DEFAULT_NESTED_JAVA_VERSION` constants so the values are discoverable.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@bric3 bric3 force-pushed the bdu/smoke-test-plugin-infrastructure branch from c6d9744 to 1562192 Compare May 19, 2026 14:08
@datadog-prod-us1-4

This comment has been minimized.

@bric3 bric3 force-pushed the bdu/smoke-test-pattern-d-extract-applications branch 2 times, most recently from 9e60b33 to 6364e58 Compare May 19, 2026 16:32
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

note: settings.gradle in nested projects have somewhat the same duplicated configuration around repo proxies. And ci cache. I have no proper solution yet, but this should come as a follow-up work.

@bric3 bric3 marked this pull request as ready for review May 19, 2026 16:41
@bric3 bric3 requested review from a team as code owners May 19, 2026 16:41
@bric3 bric3 requested review from claponcet, dd-oleksii, jandro996, leoromanovsky and ygree and removed request for a team May 19, 2026 16:41
…ugin

Switch the plugin sources and unit tests over to the typed
`org.gradle.kotlin.dsl` extension functions where they replace
`::class.java` boilerplate:

- `tasks.register(name, Type::class.java) { … }` → `tasks.register<Type>(name) { … }`
- `tasks.withType(Type::class.java).configureEach { … }` → `tasks.withType<Type>().configureEach { … }`
- `extensions.create("name", Type::class.java)` → `extensions.create<Type>("name")`
- `extensions.getByType(Type::class.java)` → `extensions.getByType<Type>()`
- `extensions.findByName("name")` (followed by `isInstanceOf`) → `extensions.findByType<Type>()`
- `project.plugins.apply(Plugin::class.java)` → `project.apply<Plugin>()` (PluginAware)
- `objects.newInstance(Type::class.java)` → `objects.newInstance<Type>()`

Also drop the six `captured*` local variables in `SmokeTestAppExtension.application` —
inside `tasks.register<NestedGradleBuild>(taskName) { … }` the outer extension's
properties are now reached via `this@SmokeTestAppExtension.<prop>` directly.

No behavioural change; the 9 plugin tests still pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ild subprojects

The Spring Boot Gradle plugin is incompatible with Gradle 9 for all
versions before 3.5.0 because it calls `Configuration.getUploadTaskName()`,
a method removed in Gradle 9.

Twelve smoke-test modules were direct Gradle subprojects that applied the
Spring Boot plugin to build their bootJar / bootWar artefact. They
cannot stay as subprojects of the Gradle 9 root build.

For each of these modules, the application source is extracted into a
new `application/` subdirectory that is a fully self-contained Gradle
project (`settings.gradle` + `build.gradle`). The outer module keeps the
test source and delegates the application build to the
`dd-trace-java.smoke-test-app` plugin from `build-logic/` (added in the
parent infrastructure PR), which runs the nested build via the Gradle
Tooling API pinned to Gradle 8.14.5 — no committed `gradlew` wrapper.

Modules converted (bootJar):
- springboot-thymeleaf   (Spring Boot 2.7.15, Java 8)
- springboot-freemarker  (Spring Boot 2.7.15 plugin / 1.5.18 starter, Java 8)
- springboot-velocity    (Spring Boot 2.7.15 plugin / 1.5.18 starter, Java 8)
- springboot-java-11     (Spring Boot 2.7.15, Java 11; passes iast-util-11 jar)
- springboot-java-17     (Spring Boot 2.7.15, Java 17; passes iast-util-17 jar)
- openfeature            (Spring Boot 2.7.15, Java 11; passes feature-flagging-api jar)
- kafka-2                (Spring Boot 2.7.15, Java 8; passes iast-util jar)
- apm-tracing-disabled   (Spring Boot 2.7.15, Java 8; passes dd-trace-api jar)

Modules converted (bootWar):
- springboot-jpa         (Spring Boot 2.6.0, Java 8; Lombok)
- springboot-tomcat-jsp  (Spring Boot 2.7.15, Java 8; JSP webapp)
- springboot-jetty-jsp   (Spring Boot 2.7.15, Java 8; JSP webapp)
- springboot-tomcat      (Spring Boot 2.5.12, Java 8; Ivy Tomcat download + unzip)

Where an application module depends on a project artifact from the main
build (e.g. iast-util-11, dd-trace-api), the jar is forwarded via the
plugin's `projectJar(name, project)` helper — a resolvable
`Configuration` that establishes the upstream task dependency
automatically. The inner build picks it up via `findProperty(name)` and
adds it as `implementation files(...)`.

The `spring-kafka-test` test dependency in kafka-2 is pinned to 2.8.11
(the version previously resolved from the Spring Boot BOM) since the
outer test module no longer applies that BOM.

For springboot-freemarker and springboot-velocity, the `XssController`
loads templates from `resources/main/templates` relative to the test
JVM's working directory. After moving sources into `application/`, those
files land at `build/application/resources/main/templates/` instead of
`build/resources/main/templates/`. Each affected outer build registers a
`copyAppResources` Copy task that mirrors the inner build's processed
resources into the outer build dir so the runtime path still resolves.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@bric3 bric3 force-pushed the bdu/smoke-test-pattern-d-extract-applications branch from 6364e58 to 6b7e87c Compare May 19, 2026 17:00
Base automatically changed from bdu/smoke-test-plugin-infrastructure to master May 19, 2026 20:48
@gh-worker-dd-mergequeue-cf854d gh-worker-dd-mergequeue-cf854d Bot requested a review from a team as a code owner May 19, 2026 20:48
@gh-worker-ownership-write-b05516 gh-worker-ownership-write-b05516 Bot removed the request for review from a team May 19, 2026 23:48
Copy link
Copy Markdown
Contributor

@AlexeyKuznetsov-DD AlexeyKuznetsov-DD left a comment

Choose a reason for hiding this comment

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

Overall LGTM, left minor comments and questions.
I feel that all build files should have more comments around why things done in that way, also maybe there is a way to minimize config by using reasonable defaults here and there.

Comment on lines +3 to +4
id 'org.springframework.boot' version '2.7.15'
id 'io.spring.dependency-management' version '1.0.15.RELEASE'
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.

nit: I would put some comments for future devs about this legacy libs.

Comment thread dd-smoke-tests/apm-tracing-disabled/application/settings.gradle
smokeTestApp {
application {
taskName = 'bootJar'
artifactPath = 'libs/apm-tracing-disabled-smoketest.jar'
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.

Just as idea: how about to calculate default name of artifactPath from module name by template?
Like (pseudocode): libs/$moduleName-smoke-test.jar? With all needed .toLowerCase() and replace if needed. WDYT?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

good point, I'll take a look in a follow up pr

Comment on lines +3 to +4
id 'org.springframework.boot' version '2.7.15'
id 'io.spring.dependency-management' version '1.0.15.RELEASE'
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.

Same here and other similar places, let's have some comments.

sourceCompatibility = JavaVersion.VERSION_1_8
}

if (hasProperty('iastUtilJar')) {
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.

Same here it is better to comment what is the meaning of iastUtilJar.

Comment on lines +21 to +23
def isCI = providers.environmentVariable("CI").isPresent()

if (isCI) {
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.

Not an issue, but this isCI code is all over the project... Is there any way to move it as common utility?

Copy link
Copy Markdown
Contributor Author

@bric3 bric3 May 20, 2026

Choose a reason for hiding this comment

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

I haven't looked into that yet.

It's what I meant with this #11408 (comment)

Comment on lines +25 to +29
buildCache {
local {
directory = "$sharedRootDir/workspace/build-cache"
}
}
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.

Let's comment the knowledge behind such non-trivial-Gradle configs.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Note existing smoke tests already had this for quite some time, it just follows what's been there, it seems to have been introduced by b34ccbc, and has been applied to other smoke tests, prior my changes.

The buildcache dir config referes to the root level cache that was introduced in f6ec1f5 #982

I'll add the comments in a follow-up pr.

Comment on lines +19 to +22
java {
sourceCompatibility = 11
targetCompatibility = 11
}
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.

Just curious why Java 11 with old spring boot 2.7.15?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The app was compiled with Java 11 before

tasks.named("compileJava", JavaCompile) {
configureCompiler(it, 11, JavaVersion.VERSION_11)
}

Comment on lines +24 to +27
def sharedRootDir = "$rootDir/../../../"
buildCache {
local {
directory = "$sharedRootDir/workspace/build-cache"
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.

Just curious why some project hardcode sharedRootDir, and some under isCI?

@bric3 bric3 added this pull request to the merge queue May 20, 2026
@dd-octo-sts
Copy link
Copy Markdown
Contributor

dd-octo-sts Bot commented May 20, 2026

/merge

@gh-worker-devflow-routing-ef8351
Copy link
Copy Markdown

gh-worker-devflow-routing-ef8351 Bot commented May 20, 2026

View all feedbacks in Devflow UI.

2026-05-20 07:05:46 UTC ℹ️ Start processing command /merge


2026-05-20 07:05:51 UTC ℹ️ MergeQueue: pull request added to the queue

The expected merge time in master is approximately 1h (p90).


2026-05-20 09:06:17 UTCMergeQueue: The build pipeline has timeout

The merge request has been interrupted because the build 0 took longer than expected. The current limit for the base branch 'master' is 120 minutes.

@github-merge-queue github-merge-queue Bot removed this pull request from the merge queue due to failed status checks May 20, 2026
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: enhancement Enhancements and improvements

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants