From 0c7f596360e35ac8a2c1187cf279c94424c3ad4b Mon Sep 17 00:00:00 2001 From: Daniel Piet Date: Thu, 8 Oct 2026 19:25:31 +0000 Subject: [PATCH 1/6] ci: reuse version-only test compilation and parallelize core test builds --- .github/workflows/ci.yml | 34 ++++++++++++++++++- .../src/main/kotlin/openai.kotlin.gradle.kts | 9 +++++ .../com/openai/gradle/MergeQueuePolicyTest.kt | 14 +++++++- openai-java-core/build.gradle.kts | 26 ++++++++++++++ 4 files changed, 81 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b48206245..bcdec26d5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -87,6 +87,33 @@ jobs: - name: Run lints run: ./scripts/lint + build_logic: + name: CI / build logic + runs-on: ${{ vars.SDK_GHA_RUNNER || 'ubuntu-24.04' }} + timeout-minutes: 15 + + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v6 + with: + persist-credentials: false + + - name: Set up Java + uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6.0.1 + with: + distribution: temurin + java-version: | + 8 + 21 + + - name: Set up Gradle + uses: gradle/actions/setup-gradle@9c971963bec38e04b3d30dcc455b5382be2fdbfb # v6.3.0 + with: + cache-provider: basic + cache-read-only: true + + - name: Test build logic + run: ./scripts/gradle :buildSrc:test + build: name: CI / build runs-on: ${{ vars.SDK_GHA_RUNNER || 'ubuntu-24.04' }} @@ -128,7 +155,9 @@ jobs: env: GRADLE_OPTS: -Dkotlin.compiler.execution.strategy=in-process # Release compiled outputs before running the older-Jackson suite in its own job. - run: ./scripts/build -x :openai-java-core:testJacksonCompatibility + run: >- + ./scripts/gradle build testClasses verifyVersionSupportPolicy + -x test -x :openai-java-core:testJacksonCompatibility - name: Collect exact-run Gradle build cache env: @@ -398,6 +427,7 @@ jobs: needs: - lint - build + - build_logic - test - jackson_compatibility - api_compatibility @@ -411,6 +441,7 @@ jobs: EVENT_NAME: ${{ github.event_name }} LINT_RESULT: ${{ needs.lint.result }} BUILD_RESULT: ${{ needs.build.result }} + BUILD_LOGIC_RESULT: ${{ needs.build_logic.result }} TEST_RESULT: ${{ needs.test.result }} JACKSON_COMPATIBILITY_RESULT: ${{ needs.jackson_compatibility.result }} API_COMPATIBILITY_RESULT: ${{ needs.api_compatibility.result }} @@ -421,6 +452,7 @@ jobs: failed_jobs=() [[ "$LINT_RESULT" == "success" ]] || failed_jobs+=("lint: $LINT_RESULT") [[ "$BUILD_RESULT" == "success" ]] || failed_jobs+=("build: $BUILD_RESULT") + [[ "$BUILD_LOGIC_RESULT" == "success" ]] || failed_jobs+=("build logic: $BUILD_LOGIC_RESULT") [[ "$TEST_RESULT" == "success" ]] || failed_jobs+=("test: $TEST_RESULT") [[ "$JACKSON_COMPATIBILITY_RESULT" == "success" ]] || failed_jobs+=("Jackson compatibility: $JACKSON_COMPATIBILITY_RESULT") diff --git a/buildSrc/src/main/kotlin/openai.kotlin.gradle.kts b/buildSrc/src/main/kotlin/openai.kotlin.gradle.kts index fb96bcba4..f4cf4dd89 100644 --- a/buildSrc/src/main/kotlin/openai.kotlin.gradle.kts +++ b/buildSrc/src/main/kotlin/openai.kotlin.gradle.kts @@ -2,6 +2,7 @@ import com.openai.gradle.CoreCompilationShards import com.openai.gradle.VersionSupportPolicy import org.jetbrains.kotlin.gradle.dsl.JvmTarget import org.jetbrains.kotlin.gradle.dsl.KotlinVersion +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile plugins { id("openai.java") @@ -38,6 +39,14 @@ kotlin { } } +// Kotlin fingerprints friend paths by name, not by class content. Its default includes the +// versioned main JAR, so a release alone invalidates every test compilation. The compiler already +// fingerprints the test compile classpath; use the stable main classes for internal visibility. +// In core this is the canonical aggregate, including its internal compilation shards. +tasks.named("compileTestKotlin") { + friendPaths.setFrom(sourceSets.main.map { it.output.classesDirs }) +} + tasks.withType().configureEach { systemProperty("junit.jupiter.execution.parallel.enabled", true) systemProperty("junit.jupiter.execution.parallel.mode.default", "concurrent") diff --git a/buildSrc/src/test/kotlin/com/openai/gradle/MergeQueuePolicyTest.kt b/buildSrc/src/test/kotlin/com/openai/gradle/MergeQueuePolicyTest.kt index dc76e4e3b..3e7b25dcd 100644 --- a/buildSrc/src/test/kotlin/com/openai/gradle/MergeQueuePolicyTest.kt +++ b/buildSrc/src/test/kotlin/com/openai/gradle/MergeQueuePolicyTest.kt @@ -54,6 +54,7 @@ class MergeQueuePolicyTest { setOf( "lint", "build", + "build_logic", "test", "jackson_compatibility", "api_compatibility", @@ -67,6 +68,7 @@ class MergeQueuePolicyTest { mapOf( "LINT_RESULT" to "lint", "BUILD_RESULT" to "build", + "BUILD_LOGIC_RESULT" to "build_logic", "TEST_RESULT" to "test", "JACKSON_COMPATIBILITY_RESULT" to "jackson_compatibility", "API_COMPATIBILITY_RESULT" to "api_compatibility", @@ -100,7 +102,17 @@ class MergeQueuePolicyTest { val workflow = workflow("ci") val build = job(workflow, "build") val buildCommand = steps(build).single { it["name"] == "Build SDK" }["run"] - assertEquals("./scripts/build -x :openai-java-core:testJacksonCompatibility", buildCommand) + assertEquals( + "./scripts/gradle build testClasses verifyVersionSupportPolicy " + + "-x test -x :openai-java-core:testJacksonCompatibility", + (buildCommand as String).trim(), + ) + val buildLogic = job(workflow, "build_logic") + assertFalse(buildLogic.containsKey("needs"), "Build logic must not wait for SDK compilation") + assertEquals( + "./scripts/gradle :buildSrc:test", + steps(buildLogic).single { it["name"] == "Test build logic" }["run"], + ) val jackson = job(workflow, "jackson_compatibility") assertEquals("build", jackson["needs"]) diff --git a/openai-java-core/build.gradle.kts b/openai-java-core/build.gradle.kts index 0bd64d65b..a24538b9d 100644 --- a/openai-java-core/build.gradle.kts +++ b/openai-java-core/build.gradle.kts @@ -78,6 +78,32 @@ mainClassesDirectories.setFrom(combinedClassesDirectory) mainClassesDirectories.builtBy(assembleCoreClasses) tasks.named("classes") { dependsOn(assembleCoreClasses) } +// These generated model tests do not use the shared fixtures needed by services and core tests. +// Compile them independently, without moving generated sources or changing test discovery. +val independentModelTests = + listOf( + "betaModelTest" to "com/openai/models/beta/**", + "adminModelTest" to "com/openai/models/admin/**", + ) +val coreTestSourceSet = sourceSets.test.get() +val coreTestClassesDirectories = coreTestSourceSet.output.classesDirs as ConfigurableFileCollection +independentModelTests.forEach { (name, pattern) -> + val modelTests = sourceSets.create(name) + // Resolve exactly the dependencies/Java 17 attributes already used to compile core tests. + modelTests.compileClasspath = coreTestSourceSet.compileClasspath + kotlin.sourceSets.named(name) { + kotlin.setSrcDirs(listOf(layout.projectDirectory.dir("src/test/kotlin"))) + kotlin.include(pattern) + } + kotlin.sourceSets.named("test") { kotlin.exclude(pattern) } + + val compileModelTests = tasks.named(modelTests.getCompileTaskName("kotlin")) { + friendPaths.setFrom(sourceSets.main.map { it.output.classesDirs }) + } + coreTestClassesDirectories.from(compileModelTests.flatMap { it.destinationDirectory }) + tasks.named("testClasses") { dependsOn(modelTests.classesTaskName) } +} + val coreJar = tasks.named("jar") { // The Kotlin plugin captures compileKotlin's destination before it is relocated above. Publish // those client-layer classes through the canonical aggregate instead of the staging directory. From fc4bf8b6258bf67367b50a3ea18352bdc9b7c9e2 Mon Sep 17 00:00:00 2001 From: Daniel Piet Date: Thu, 8 Oct 2026 19:36:53 +0000 Subject: [PATCH 2/6] style: format CI build logic policy assertion --- .../test/kotlin/com/openai/gradle/MergeQueuePolicyTest.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/buildSrc/src/test/kotlin/com/openai/gradle/MergeQueuePolicyTest.kt b/buildSrc/src/test/kotlin/com/openai/gradle/MergeQueuePolicyTest.kt index 3e7b25dcd..597cc0860 100644 --- a/buildSrc/src/test/kotlin/com/openai/gradle/MergeQueuePolicyTest.kt +++ b/buildSrc/src/test/kotlin/com/openai/gradle/MergeQueuePolicyTest.kt @@ -108,7 +108,10 @@ class MergeQueuePolicyTest { (buildCommand as String).trim(), ) val buildLogic = job(workflow, "build_logic") - assertFalse(buildLogic.containsKey("needs"), "Build logic must not wait for SDK compilation") + assertFalse( + buildLogic.containsKey("needs"), + "Build logic must not wait for SDK compilation", + ) assertEquals( "./scripts/gradle :buildSrc:test", steps(buildLogic).single { it["name"] == "Test build logic" }["run"], From 7b922540d0dc8aac70e57b48111389cc1e9eebba Mon Sep 17 00:00:00 2001 From: Daniel Piet Date: Thu, 8 Oct 2026 20:24:44 +0000 Subject: [PATCH 3/6] ci: preserve Graal release precompilation across SDK source versions --- .github/workflows/create-releases.yml | 23 +++-- .../gradle/GradleCacheTrustPolicyTest.kt | 88 +++++++++++++++++++ 2 files changed, 103 insertions(+), 8 deletions(-) diff --git a/.github/workflows/create-releases.yml b/.github/workflows/create-releases.yml index 8c2a03196..9d5c61daa 100644 --- a/.github/workflows/create-releases.yml +++ b/.github/workflows/create-releases.yml @@ -296,7 +296,7 @@ jobs: cache-disabled: true - name: Compile the openai-java-core project - run: ./gradlew :openai-java-core:compileJava :openai-java-core:compileTestJava -x test + run: ./gradlew :openai-java-core:classes :openai-java-core:testClasses -x test - name: Stop pre-GraalVM Gradle daemon run: ./gradlew --stop @@ -311,13 +311,20 @@ jobs: distribution: graalvm-community - name: Run GraalVM native-image agent tests - run: >- - ./gradlew :openai-java-core:test - -x compileJava - -x compileTestJava - -x compileKotlin - -x compileTestKotlin - -PgraalvmAgent + run: | + set -euo pipefail + + # A retry can check out a release from before core test compilation was split. + # Query the verified release source, not the workflow's current main branch. + task_list="${RUNNER_TEMP}/openai-java-core-gradle-tasks.txt" + ./gradlew :openai-java-core:tasks --all --console=plain > "$task_list" + excluded_tasks=(-x compileJava -x compileTestJava -x compileKotlin -x compileTestKotlin) + for task in compileBetaModelTestKotlin compileAdminModelTestKotlin; do + if grep -Eq "^${task}([[:space:]]|$)" "$task_list"; then + excluded_tasks+=(-x ":openai-java-core:$task") + fi + done + ./gradlew :openai-java-core:test "${excluded_tasks[@]}" -PgraalvmAgent - name: Check generated GraalVM files run: | diff --git a/buildSrc/src/test/kotlin/com/openai/gradle/GradleCacheTrustPolicyTest.kt b/buildSrc/src/test/kotlin/com/openai/gradle/GradleCacheTrustPolicyTest.kt index 18144a2f2..9dfcdcd35 100644 --- a/buildSrc/src/test/kotlin/com/openai/gradle/GradleCacheTrustPolicyTest.kt +++ b/buildSrc/src/test/kotlin/com/openai/gradle/GradleCacheTrustPolicyTest.kt @@ -19,6 +19,94 @@ import org.yaml.snakeyaml.constructor.SafeConstructor class GradleCacheTrustPolicyTest { @TempDir lateinit var temporaryDirectory: Path + @Test + fun `Graal release test step supports current and older source tasks and stops on query failure`() { + val workflow = Path.of("../.github/workflows/create-releases.yml").readText() + val script = + requireNotNull( + parseWorkflow(workflow) + .job("publish") + .steps + .single { it.name == "Run GraalVM native-image agent tests" } + .run + ) + val wrapper = temporaryDirectory.resolve("gradlew") + wrapper.writeText( + """ + |#!/usr/bin/env bash + |set -euo pipefail + |case "${'$'}1" in + | :openai-java-core:tasks) + | printf '%s\n' "${'$'}AVAILABLE_TASKS" + | exit "${'$'}QUERY_STATUS" + | ;; + | :openai-java-core:test) + | printf '%s\n' "${'$'}@" > "${'$'}RUNNER_TEMP/test-arguments" + | ;; + | *) exit 93 ;; + |esac + """ + .trimMargin() + "\n" + ) + assertTrue(wrapper.toFile().setExecutable(true)) + val testArguments = temporaryDirectory.resolve("test-arguments") + val baseArguments = + listOf( + ":openai-java-core:test", + "-x", + "compileJava", + "-x", + "compileTestJava", + "-x", + "compileKotlin", + "-x", + "compileTestKotlin", + ) + val shards = listOf("compileBetaModelTestKotlin", "compileAdminModelTestKotlin") + val currentListing = shards.joinToString("\n") { "$it - Compiles the test sources." } + // An old source or unrelated task prefix must not produce an unknown exclusion. + val oldListing = + "compileTestKotlin - Compiles tests.\ncompileBetaModelTestKotlinOther - Unrelated." + for ((listing, status, expectedShards) in + listOf( + Triple(currentListing, "0", shards), + Triple(oldListing, "0", emptyList()), + Triple(currentListing, "19", emptyList()), + )) { + Files.deleteIfExists(testArguments) + val builder = + ProcessBuilder("bash", "-euo", "pipefail", "-c", script) + .directory(temporaryDirectory.toFile()) + .redirectErrorStream(true) + builder + .environment() + .putAll( + mapOf( + "RUNNER_TEMP" to temporaryDirectory.toString(), + "AVAILABLE_TASKS" to listing, + "QUERY_STATUS" to status, + ) + ) + val process = builder.start() + val output = process.inputStream.bufferedReader().use { it.readText() } + assertEquals(status.toInt(), process.waitFor(), output) + if (status == "0") { + assertEquals( + baseArguments + + expectedShards.flatMap { listOf("-x", ":openai-java-core:$it") } + + "-PgraalvmAgent", + testArguments.readText().lines().filter { it.isNotEmpty() }, + output, + ) + } else { + assertFalse( + Files.exists(testArguments), + "Failed task query must stop before tests.", + ) + } + } + } + @Test fun `all pull request Gradle jobs keep cross-run caches read-only`() { val workflow = Path.of("../.github/workflows/ci.yml").readText() From b43f3edfc29e34bfa721a1b1a9d26dba83a17596 Mon Sep 17 00:00:00 2001 From: Daniel Piet Date: Thu, 8 Oct 2026 21:13:43 +0000 Subject: [PATCH 4/6] ci: make GraalVM release compilation skips easier to read --- .github/workflows/create-releases.yml | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/.github/workflows/create-releases.yml b/.github/workflows/create-releases.yml index 9d5c61daa..cfe124307 100644 --- a/.github/workflows/create-releases.yml +++ b/.github/workflows/create-releases.yml @@ -314,17 +314,19 @@ jobs: run: | set -euo pipefail - # A retry can check out a release from before core test compilation was split. - # Query the verified release source, not the workflow's current main branch. - task_list="${RUNNER_TEMP}/openai-java-core-gradle-tasks.txt" - ./gradlew :openai-java-core:tasks --all --console=plain > "$task_list" - excluded_tasks=(-x compileJava -x compileTestJava -x compileKotlin -x compileTestKotlin) + # Skip the new test compilers only if this release has them. Older retries don't. + available_tasks="$(./gradlew :openai-java-core:tasks --all --console=plain)" + skip_optional_compilers=() for task in compileBetaModelTestKotlin compileAdminModelTestKotlin; do - if grep -Eq "^${task}([[:space:]]|$)" "$task_list"; then - excluded_tasks+=(-x ":openai-java-core:$task") + if grep -Eq "^${task}([[:space:]]|$)" <<< "$available_tasks"; then + skip_optional_compilers+=(-x ":openai-java-core:$task") fi done - ./gradlew :openai-java-core:test "${excluded_tasks[@]}" -PgraalvmAgent + + # All classes were compiled before switching to GraalVM; only run the tracing tests. + ./gradlew :openai-java-core:test \ + -x compileJava -x compileTestJava -x compileKotlin -x compileTestKotlin \ + "${skip_optional_compilers[@]}" -PgraalvmAgent - name: Check generated GraalVM files run: | From be0c0b6cb2b717530f743e5cf53a5d86ca05a3bb Mon Sep 17 00:00:00 2001 From: Daniel Piet Date: Thu, 8 Oct 2026 22:55:05 +0000 Subject: [PATCH 5/6] ci: extract GraalVM release tests to a pinned workflow script --- .github/scripts/run-graalvm-tests.sh | 16 ++++++++ .github/workflows/create-releases.yml | 26 +++++-------- .../gradle/GradleCacheTrustPolicyTest.kt | 38 +++++++++++++++++++ 3 files changed, 64 insertions(+), 16 deletions(-) create mode 100644 .github/scripts/run-graalvm-tests.sh diff --git a/.github/scripts/run-graalvm-tests.sh b/.github/scripts/run-graalvm-tests.sh new file mode 100644 index 000000000..700f2dd55 --- /dev/null +++ b/.github/scripts/run-graalvm-tests.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Run from the verified release checkout. It may predate the split test compilers. +available_tasks="$(./gradlew :openai-java-core:tasks --all --console=plain)" +skip_optional_compilers=() +for task in compileBetaModelTestKotlin compileAdminModelTestKotlin; do + if grep -Eq "^${task}([[:space:]]|$)" <<< "$available_tasks"; then + skip_optional_compilers+=(-x ":openai-java-core:$task") + fi +done + +# Classes were compiled before switching to GraalVM. Only run the tracing tests. +./gradlew :openai-java-core:test \ + -x compileJava -x compileTestJava -x compileKotlin -x compileTestKotlin \ + "${skip_optional_compilers[@]}" -PgraalvmAgent diff --git a/.github/workflows/create-releases.yml b/.github/workflows/create-releases.yml index cfe124307..ca32e54d2 100644 --- a/.github/workflows/create-releases.yml +++ b/.github/workflows/create-releases.yml @@ -262,6 +262,15 @@ jobs: environment: publish steps: + - name: Check out workflow scripts + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v6 + with: + persist-credentials: false + ref: ${{ github.workflow_sha }} + + - name: Preserve GraalVM test script for older release retries + run: install -m 700 .github/scripts/run-graalvm-tests.sh "$RUNNER_TEMP/run-graalvm-tests.sh" + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v6 with: persist-credentials: false @@ -311,22 +320,7 @@ jobs: distribution: graalvm-community - name: Run GraalVM native-image agent tests - run: | - set -euo pipefail - - # Skip the new test compilers only if this release has them. Older retries don't. - available_tasks="$(./gradlew :openai-java-core:tasks --all --console=plain)" - skip_optional_compilers=() - for task in compileBetaModelTestKotlin compileAdminModelTestKotlin; do - if grep -Eq "^${task}([[:space:]]|$)" <<< "$available_tasks"; then - skip_optional_compilers+=(-x ":openai-java-core:$task") - fi - done - - # All classes were compiled before switching to GraalVM; only run the tracing tests. - ./gradlew :openai-java-core:test \ - -x compileJava -x compileTestJava -x compileKotlin -x compileTestKotlin \ - "${skip_optional_compilers[@]}" -PgraalvmAgent + run: bash "$RUNNER_TEMP/run-graalvm-tests.sh" - name: Check generated GraalVM files run: | diff --git a/buildSrc/src/test/kotlin/com/openai/gradle/GradleCacheTrustPolicyTest.kt b/buildSrc/src/test/kotlin/com/openai/gradle/GradleCacheTrustPolicyTest.kt index 9dfcdcd35..209b62d16 100644 --- a/buildSrc/src/test/kotlin/com/openai/gradle/GradleCacheTrustPolicyTest.kt +++ b/buildSrc/src/test/kotlin/com/openai/gradle/GradleCacheTrustPolicyTest.kt @@ -30,6 +30,10 @@ class GradleCacheTrustPolicyTest { .single { it.name == "Run GraalVM native-image agent tests" } .run ) + Files.copy( + Path.of("../.github/scripts/run-graalvm-tests.sh"), + temporaryDirectory.resolve("run-graalvm-tests.sh"), + ) val wrapper = temporaryDirectory.resolve("gradlew") wrapper.writeText( """ @@ -1072,6 +1076,40 @@ class GradleCacheTrustPolicyTest { ) } + val workflowCheckout = + publishJob.steps.indexOfFirst { it.name == "Check out workflow scripts" } + val preserveHelper = + publishJob.steps.indexOfFirst { + it.name == "Preserve GraalVM test script for older release retries" + } + val releaseCheckout = + publishJob.steps.indexOfFirst { + it.action?.repository == "actions/checkout" && + it.action.inputs["ref"] == "\${{ needs.release.outputs.source_sha }}" + } + assertTrue( + workflowCheckout >= 0 && + preserveHelper > workflowCheckout && + releaseCheckout > preserveHelper, + "Copy the helper from the pinned workflow before checking out the verified release.", + ) + assertEquals( + mapOf("persist-credentials" to "false", "ref" to "\${{ github.workflow_sha }}"), + requireNotNull(publishJob.steps[workflowCheckout].action).inputs, + ) + assertEquals( + mapOf( + "persist-credentials" to "false", + "ref" to "\${{ needs.release.outputs.source_sha }}", + ), + requireNotNull(publishJob.steps[releaseCheckout].action).inputs, + ) + assertEquals( + "install -m 700 .github/scripts/run-graalvm-tests.sh " + + "\"\$RUNNER_TEMP/run-graalvm-tests.sh\"", + publishJob.steps[preserveHelper].run, + ) + val isolation = publishJob.steps.indexOfFirst { it.name == "Create isolated release Gradle User Home" } val initializers = From 41be46e364c7cbd9fcda0aef23d545e4cd90f5dc Mon Sep 17 00:00:00 2001 From: Codex Date: Thu, 8 Oct 2026 23:29:29 +0000 Subject: [PATCH 6/6] fix: track GraalVM helper as a build-logic test input --- buildSrc/build.gradle.kts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index f024f3635..a7b750739 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -55,6 +55,9 @@ tasks.test { inputs .file(layout.projectDirectory.file("../.github/workflows/create-releases.yml")) .withPathSensitivity(PathSensitivity.RELATIVE) + inputs + .file(layout.projectDirectory.file("../.github/scripts/run-graalvm-tests.sh")) + .withPathSensitivity(PathSensitivity.RELATIVE) inputs .file(layout.projectDirectory.file("../SECURITY.md")) .withPathSensitivity(PathSensitivity.RELATIVE)