Extract Spring Boot 2.x app source into nested-build subprojects#11408
Extract Spring Boot 2.x app source into nested-build subprojects#11408bric3 wants to merge 5 commits into
Conversation
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>
b924cb8 to
149a0ec
Compare
…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>
c6d9744 to
1562192
Compare
This comment has been minimized.
This comment has been minimized.
9e60b33 to
6364e58
Compare
There was a problem hiding this comment.
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.
…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>
6364e58 to
6b7e87c
Compare
AlexeyKuznetsov-DD
left a comment
There was a problem hiding this comment.
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.
| id 'org.springframework.boot' version '2.7.15' | ||
| id 'io.spring.dependency-management' version '1.0.15.RELEASE' |
There was a problem hiding this comment.
nit: I would put some comments for future devs about this legacy libs.
| smokeTestApp { | ||
| application { | ||
| taskName = 'bootJar' | ||
| artifactPath = 'libs/apm-tracing-disabled-smoketest.jar' |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
good point, I'll take a look in a follow up pr
| id 'org.springframework.boot' version '2.7.15' | ||
| id 'io.spring.dependency-management' version '1.0.15.RELEASE' |
There was a problem hiding this comment.
Same here and other similar places, let's have some comments.
| sourceCompatibility = JavaVersion.VERSION_1_8 | ||
| } | ||
|
|
||
| if (hasProperty('iastUtilJar')) { |
There was a problem hiding this comment.
Same here it is better to comment what is the meaning of iastUtilJar.
| def isCI = providers.environmentVariable("CI").isPresent() | ||
|
|
||
| if (isCI) { |
There was a problem hiding this comment.
Not an issue, but this isCI code is all over the project... Is there any way to move it as common utility?
There was a problem hiding this comment.
I haven't looked into that yet.
It's what I meant with this #11408 (comment)
| buildCache { | ||
| local { | ||
| directory = "$sharedRootDir/workspace/build-cache" | ||
| } | ||
| } |
There was a problem hiding this comment.
Let's comment the knowledge behind such non-trivial-Gradle configs.
There was a problem hiding this comment.
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.
| java { | ||
| sourceCompatibility = 11 | ||
| targetCompatibility = 11 | ||
| } |
There was a problem hiding this comment.
Just curious why Java 11 with old spring boot 2.7.15?
There was a problem hiding this comment.
The app was compiled with Java 11 before
dd-trace-java/dd-smoke-tests/openfeature/build.gradle
Lines 17 to 20 in 5181a21
| def sharedRootDir = "$rootDir/../../../" | ||
| buildCache { | ||
| local { | ||
| directory = "$sharedRootDir/workspace/build-cache" |
There was a problem hiding this comment.
Just curious why some project hardcode sharedRootDir, and some under isCI?
|
/merge |
|
View all feedbacks in Devflow UI.
The expected merge time in
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. |
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 thesmokeTestApp { 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; passesiast-util-11jar)springboot-java-17(Spring Boot 2.7.15, Java 17; passesiast-util-17jar)openfeature(Spring Boot 2.7.15, Java 11; passesfeature-flagging-apijar)kafka-2(Spring Boot 2.7.15, Java 8; passesiast-utiljar)apm-tracing-disabled(Spring Boot 2.7.15, Java 8; passesdd-trace-apijar)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:
git mv src/main/ application/src/main/application/settings.gradle+application/build.gradlecarrying the Spring Boot plugin + the module's existing dependencies.build.gradlerewritten to applydd-trace-java.smoke-test-appand configuresmokeTestApp { application { … } }(plusprojectJar(name, project)for the 5 modules that forward a sibling jar).Note
smokeTestApphas 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
gradlewwrappers it uses the Gradle Tooling API via thedd-trace-java.smoke-test-appplugin (#11405). Net diff per module is smaller; nothing extra is committed to source control beyond the innerapplication/Gradle scripts.Additional Notes
kafka-2:spring-kafka-testis 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: theirXssControllerloads templates fromresources/main/templatesrelative to the test JVM working directory. After the source move, the processed resources land underbuild/application/resources/main/. Each affected outer build adds acopyAppResourcesCopy task that mirrors them tobuild/resources/main/so the runtime path resolves.jardependsOn 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 viaadditionalSystemProperties.apm-tracing-disabled:ApmTracingDisabledSamplingSmoke{V04,V1}Testare already@Flakyand continue to be skipped in CI../gradlew :dd-smoke-tests:<module>:test -PskipFlakyTests=truefor each module.Contributor Checklist
type:and (comp:orinst:) labels in addition to any other useful labels🤖 Generated with Claude Code