Skip to content

Commit f40d4a9

Browse files
authored
Upgrade to Gradle 9.3.1 with JDK 21 build requirement (#365)
1 parent 16e60e1 commit f40d4a9

24 files changed

Lines changed: 885 additions & 200 deletions

File tree

.github/actions/setup_cached_java/action.yml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,12 @@ runs:
1818
shell: bash
1919
id: infer_build_jdk
2020
run: |
21-
echo "Infering JDK 11 [${{ inputs.arch }}]"
21+
# Gradle 9 requires JDK 17+ to run; using JDK 21 (LTS)
22+
echo "Inferring JDK 21 [${{ inputs.arch }}]"
2223
if [[ ${{ inputs.arch }} =~ "-musl" ]]; then
23-
echo "build_jdk=jdk11-librca" >> $GITHUB_OUTPUT
24+
echo "build_jdk=jdk21-librca" >> $GITHUB_OUTPUT
2425
else
25-
echo "build_jdk=jdk11" >> $GITHUB_OUTPUT
26+
echo "build_jdk=jdk21" >> $GITHUB_OUTPUT
2627
fi
2728
- name: Cache Build JDK [${{ inputs.arch }}]
2829
id: cache_build_jdk

.github/dependabot.yml

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
version: 2
2+
updates:
3+
# Gradle dependencies (root project)
4+
- package-ecosystem: "gradle"
5+
directory: "/"
6+
schedule:
7+
interval: "weekly"
8+
day: "monday"
9+
open-pull-requests-limit: 5
10+
groups:
11+
gradle-minor:
12+
update-types:
13+
- "minor"
14+
- "patch"
15+
16+
# Gradle dependencies (build-logic composite build)
17+
- package-ecosystem: "gradle"
18+
directory: "/build-logic"
19+
schedule:
20+
interval: "weekly"
21+
day: "monday"
22+
open-pull-requests-limit: 5
23+
groups:
24+
gradle-minor:
25+
update-types:
26+
- "minor"
27+
- "patch"
28+
29+
# GitHub Actions
30+
- package-ecosystem: "github-actions"
31+
directory: "/"
32+
schedule:
33+
interval: "weekly"
34+
day: "monday"
35+
open-pull-requests-limit: 5

.github/workflows/ci.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,13 @@ jobs:
4747
if: needs.check-for-pr.outputs.skip != 'true'
4848
steps:
4949
- uses: actions/checkout@v3
50+
51+
- name: Setup Java
52+
uses: actions/setup-java@v3
53+
with:
54+
distribution: 'zulu'
55+
java-version: '21'
56+
5057
- name: Setup OS
5158
run: |
5259
sudo apt-get update

.github/workflows/test_workflow.yml

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,22 @@ jobs:
1515
cache-jdks:
1616
# This job is used to cache the JDKs for the test jobs
1717
uses: ./.github/workflows/cache_java.yml
18+
filter-musl-configs:
19+
# Sanitizers (asan/tsan) are not supported on musl - filter them out
20+
runs-on: ubuntu-latest
21+
outputs:
22+
configs: ${{ steps.filter.outputs.configs }}
23+
has_configs: ${{ steps.filter.outputs.has_configs }}
24+
steps:
25+
- id: filter
26+
run: |
27+
configs=$(echo '${{ inputs.configuration }}' | jq -c '[.[] | select(. != "asan" and . != "tsan")]')
28+
if [ "$configs" = "[]" ]; then
29+
echo "has_configs=false" >> $GITHUB_OUTPUT
30+
else
31+
echo "has_configs=true" >> $GITHUB_OUTPUT
32+
fi
33+
echo "configs=$configs" >> $GITHUB_OUTPUT
1834
test-linux-glibc-amd64:
1935
needs: cache-jdks
2036
strategy:
@@ -132,12 +148,13 @@ jobs:
132148
path: test-reports
133149

134150
test-linux-musl-amd64:
135-
needs: cache-jdks
151+
needs: [cache-jdks, filter-musl-configs]
152+
if: needs.filter-musl-configs.outputs.has_configs == 'true'
136153
strategy:
137154
fail-fast: false
138155
matrix:
139156
java_version: [ "8-librca", "11-librca", "17-librca", "21-librca", "25-librca" ]
140-
config: ${{ fromJson(inputs.configuration) }}
157+
config: ${{ fromJson(needs.filter-musl-configs.outputs.configs) }}
141158
runs-on: ubuntu-latest
142159
container:
143160
image: "alpine:3.21"
@@ -368,12 +385,13 @@ jobs:
368385
path: test-reports
369386

370387
test-linux-musl-aarch64:
371-
needs: cache-jdks
388+
needs: [cache-jdks, filter-musl-configs]
389+
if: needs.filter-musl-configs.outputs.has_configs == 'true'
372390
strategy:
373391
fail-fast: false
374392
matrix:
375393
java_version: [ "8-librca", "11-librca", "17-librca", "21-librca", "25-librca" ]
376-
config: ${{ fromJson(inputs.configuration) }}
394+
config: ${{ fromJson(needs.filter-musl-configs.outputs.configs) }}
377395
runs-on:
378396
group: ARM LINUX SHARED
379397
labels: arm-4core-linux-ubuntu24.04

AGENTS.md

Lines changed: 65 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -274,11 +274,22 @@ Release builds automatically extract debug symbols:
274274
## Development Workflow
275275

276276
### Running Single Tests
277-
Use standard Gradle syntax:
277+
Use the `-Ptests` property across all platforms:
278278
```bash
279-
./gradlew :ddprof-test:test --tests "ClassName.methodName"
279+
./gradlew :ddprof-test:testDebug -Ptests=ClassName.methodName # Single method
280+
./gradlew :ddprof-test:testDebug -Ptests=ClassName # Entire class
281+
./gradlew :ddprof-test:testDebug -Ptests="*.ClassName" # Pattern matching
280282
```
281283

284+
**Platform Implementation Details:**
285+
- **glibc/macOS**: Test tasks use Gradle's native Test task type with JUnit Platform integration
286+
- **musl (Alpine)**: Exec tasks with custom ProfilerTestRunner (bypasses Gradle 9 toolchain probe issues)
287+
- **Custom Test Runner**: Uses JUnit Platform Launcher API directly (same API used by IDEs and Gradle internally)
288+
- **Result**: Unified `-Ptests` property works identically across all platforms, no platform-specific syntax required
289+
290+
**Why `-Ptests` instead of `--tests`?**
291+
The `-Ptests` property works consistently across both Test and Exec task types, while `--tests` only works with Test tasks. This ensures a truly unified interface across all platforms.
292+
282293
### Working with Native Code
283294
Native compilation is automatic during build. C++ code changes require:
284295
1. Full rebuild: `/build-and-summarize clean build`
@@ -604,8 +615,56 @@ See `gradle.properties.template` for all options. Key ones:
604615

605616
- Exclude ddprof-lib/build/async-profiler from searches of active usage
606617

607-
- Run tests with 'testdebug' gradle task
608-
- Use at most Java 21 to build and run tests
618+
- Run tests with 'testDebug' gradle task
619+
620+
## Build JDK Configuration
621+
622+
The project uses a **two-JDK pattern**:
623+
- **Build JDK** (`JAVA_HOME`): Used to run Gradle itself. Must be JDK 17+ for Gradle 9.
624+
- **Test JDK** (`JAVA_TEST_HOME`): Used to run tests against different Java versions.
625+
626+
**Current requirement:** JDK 21 (LTS) for building, targeting Java 8 bytecode via `--release 8`.
627+
628+
### Files to Modify When Changing Build JDK Version
629+
630+
When upgrading the build JDK (e.g., from JDK 21 to JDK 25), update these files:
631+
632+
| File | What to Change |
633+
|------|----------------|
634+
| `README.md` | Update "Prerequisites" section with new JDK version |
635+
| `.github/actions/setup_cached_java/action.yml` | Change `build_jdk=jdk21` to new version (line ~25) |
636+
| `.github/workflows/ci.yml` | Update `java-version` in `check-formatting` job's Setup Java step |
637+
| `utils/run-docker-tests.sh` | Update `BUILD_JDK_VERSION="21"` constant |
638+
| `build-logic/.../JavaConventionsPlugin.kt` | Update documentation comment if minimum changes |
639+
640+
### Files to Modify When Changing Target JDK Version
641+
642+
When changing the target bytecode version (e.g., from Java 8 to Java 11):
643+
644+
| File | What to Change |
645+
|------|----------------|
646+
| `build-logic/.../JavaConventionsPlugin.kt` | Change `--release 8` to new version |
647+
| `ddprof-lib/build.gradle.kts` | Change `sourceCompatibility`/`targetCompatibility` |
648+
| `README.md` | Update minimum Java runtime version |
649+
650+
### Gradle 9 API Changes Reference
651+
652+
When upgrading Gradle major versions, watch for these breaking changes:
653+
654+
| Old API | New API (Gradle 9+) | Affected Files |
655+
|---------|---------------------|----------------|
656+
| `project.exec { }` in task actions | `ProcessBuilder` directly | `GtestPlugin.kt` |
657+
| `String.capitalize()` | `replaceFirstChar { it.uppercaseChar() }` | Kotlin plugins |
658+
| `createTempFile()` | `kotlin.io.path.createTempFile()` | `PlatformUtils.kt` |
659+
| Spotless `userData()` | `editorConfigOverride()` | `SpotlessConventionPlugin.kt` |
660+
| Spotless `indentWithSpaces()` | `leadingTabsToSpaces()` | `SpotlessConventionPlugin.kt` |
661+
662+
### CI JDK Caching
663+
664+
The CI caches JDKs via `.github/workflows/cache_java.yml`. When adding a new JDK version:
665+
1. Add version URLs to `cache_java.yml` environment variables
666+
2. Add to the `java_variant` matrix in cache jobs
667+
3. Run the `cache_java.yml` workflow manually to populate caches
609668

610669
## Agentic Work
611670

@@ -623,11 +682,11 @@ See `gradle.properties.template` for all options. Key ones:
623682
```
624683
- Instead of:
625684
```bash
626-
./gradlew :prof-utils:test --tests "UpscaledMethodSampleEventSinkTest"
685+
./gradlew :ddprof-test:testDebug -Ptests=MuslDetectionTest
627686
```
628687
use:
629688
```bash
630-
./.claude/commands/build-and-summarize :prof-utils:test --tests "UpscaledMethodSampleEventSinkTest"
689+
./.claude/commands/build-and-summarize :ddprof-test:testdebug -Ptests=MuslDetectionTest
631690
```
632691

633692
- This ensures the full build log is captured to a file and only a summary is shown in the main session.

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ If you need a full-fledged Java profiler head back to [async-profiler](https://g
99
## Build
1010

1111
### Prerequisites
12-
1. JDK 8 or later (required for building)
13-
2. Gradle (included in wrapper)
12+
1. JDK 21 or later (required for building - Gradle 9 requirement)
13+
2. Gradle 9.3.1 (included in wrapper)
1414
3. C++ compiler (clang++ preferred, g++ supported)
1515
- Build system auto-detects clang++ or g++
1616
- Override with: `./gradlew build -Pnative.forceCompiler=g++`

build-logic/conventions/build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ repositories {
99

1010
dependencies {
1111
implementation("org.jetbrains.kotlin:kotlin-stdlib")
12-
implementation("com.diffplug.spotless:spotless-plugin-gradle:6.11.0")
12+
implementation("com.diffplug.spotless:spotless-plugin-gradle:7.0.2")
1313
}
1414

1515
gradlePlugin {

build-logic/conventions/src/main/kotlin/com/datadoghq/native/gtest/GtestPlugin.kt

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -142,13 +142,18 @@ class GtestPlugin : Plugin<Project> {
142142
val libDir = File("$targetDir/$libName")
143143
val libSrcDir = File("$srcDir/$libName")
144144

145-
project.exec {
146-
commandLine("sh", "-c", """
147-
echo "Processing library: $libName @ $libSrcDir"
148-
mkdir -p $libDir
149-
cd $libSrcDir
150-
make TARGET_DIR=$libDir
151-
""".trimIndent())
145+
// Use ProcessBuilder directly (Gradle 9 removed project.exec in task actions)
146+
val process = ProcessBuilder("sh", "-c", """
147+
echo "Processing library: $libName @ $libSrcDir"
148+
mkdir -p $libDir
149+
cd $libSrcDir
150+
make TARGET_DIR=$libDir
151+
""".trimIndent())
152+
.inheritIO()
153+
.start()
154+
val exitCode = process.waitFor()
155+
if (exitCode != 0) {
156+
throw org.gradle.api.GradleException("Failed to build native lib: $libName (exit code: $exitCode)")
152157
}
153158
}
154159
}

build-logic/conventions/src/main/kotlin/com/datadoghq/native/util/PlatformUtils.kt

Lines changed: 36 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ import com.datadoghq.native.model.Platform
66
import org.gradle.api.GradleException
77
import org.gradle.api.Project
88
import java.io.File
9+
import kotlin.io.path.createTempFile
10+
import kotlin.io.path.deleteIfExists
11+
import kotlin.io.path.writeText
912
import java.util.concurrent.TimeUnit
1013

1114
object PlatformUtils {
@@ -83,31 +86,43 @@ object PlatformUtils {
8386
return null
8487
}
8588

86-
return try {
87-
// Try the specified compiler first, fall back to gcc
88-
val compilerToUse = if (isCompilerAvailable(compiler)) {
89-
compiler
90-
} else if (compiler != "gcc" && isCompilerAvailable("gcc")) {
91-
"gcc"
92-
} else {
93-
return null
89+
// Try the specified compiler first
90+
if (isCompilerAvailable(compiler)) {
91+
try {
92+
val process = ProcessBuilder(compiler, "-print-file-name=$libName.so")
93+
.redirectErrorStream(true)
94+
.start()
95+
96+
val output = process.inputStream.bufferedReader().readText().trim()
97+
process.waitFor()
98+
99+
if (process.exitValue() == 0 && output != "$libName.so") {
100+
return output
101+
}
102+
} catch (e: Exception) {
103+
// Fall through to try gcc
94104
}
105+
}
95106

96-
val process = ProcessBuilder(compilerToUse, "-print-file-name=$libName.so")
97-
.redirectErrorStream(true)
98-
.start()
107+
// If the specified compiler didn't find it, try gcc as fallback
108+
if (compiler != "gcc" && isCompilerAvailable("gcc")) {
109+
try {
110+
val process = ProcessBuilder("gcc", "-print-file-name=$libName.so")
111+
.redirectErrorStream(true)
112+
.start()
99113

100-
val output = process.inputStream.bufferedReader().readText().trim()
101-
process.waitFor()
114+
val output = process.inputStream.bufferedReader().readText().trim()
115+
process.waitFor()
102116

103-
if (process.exitValue() == 0 && !output.endsWith("$libName.so")) {
104-
output
105-
} else {
106-
null
117+
if (process.exitValue() == 0 && output != "$libName.so") {
118+
return output
119+
}
120+
} catch (e: Exception) {
121+
// Fall through to return null
107122
}
108-
} catch (e: Exception) {
109-
null
110123
}
124+
125+
return null
111126
}
112127

113128
fun locateLibasan(compiler: String = "gcc"): String? = locateLibrary("libasan", compiler)
@@ -124,15 +139,15 @@ object PlatformUtils {
124139
"clang++",
125140
"-fsanitize=fuzzer",
126141
"-c",
127-
testFile.absolutePath,
142+
testFile.toAbsolutePath().toString(),
128143
"-o",
129144
"/dev/null"
130145
).redirectErrorStream(true).start()
131146

132147
process.waitFor()
133148
process.exitValue() == 0
134149
} finally {
135-
testFile.delete()
150+
testFile.deleteIfExists()
136151
}
137152
} catch (e: Exception) {
138153
false

build-logic/conventions/src/main/kotlin/com/datadoghq/profiler/JavaConventionsPlugin.kt

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@ import org.gradle.api.tasks.compile.JavaCompile
1010
*
1111
* Applies standard Java compilation options across all subprojects:
1212
* - Java 8 release target for broad JVM compatibility
13+
* - Suppresses JDK 21+ deprecation warnings for --release 8
1314
*
14-
* Requires JDK 9+ for building (uses --release flag).
15+
* Requires JDK 21+ for building (Gradle 9 requirement).
1516
* The compiled bytecode targets Java 8 runtime.
1617
*
1718
* Usage:
@@ -23,18 +24,10 @@ import org.gradle.api.tasks.compile.JavaCompile
2324
*/
2425
class JavaConventionsPlugin : Plugin<Project> {
2526
override fun apply(project: Project) {
26-
val javaVersion = System.getProperty("java.specification.version")?.toDoubleOrNull() ?: 0.0
27-
2827
project.tasks.withType(JavaCompile::class.java).configureEach {
29-
if (javaVersion >= 9) {
30-
// JDK 9+ supports --release flag which handles source, target, and boot classpath
31-
options.compilerArgs.addAll(listOf("--release", "8"))
32-
} else {
33-
// Fallback for JDK 8 (not recommended for building)
34-
sourceCompatibility = "8"
35-
targetCompatibility = "8"
36-
project.logger.warn("Building with JDK 8 is not recommended. Use JDK 11+ with --release 8 for better compatibility.")
37-
}
28+
// JDK 21+ deprecated --release 8 with warnings; suppress with -Xlint:-options
29+
// The deprecation is informational - Java 8 targeting still works
30+
options.compilerArgs.addAll(listOf("--release", "8", "-Xlint:-options"))
3831
}
3932
}
4033
}

0 commit comments

Comments
 (0)