diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 072835527..55cef32ed 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -138,6 +138,71 @@ jobs: chmod +x "$BASE_DETECTOR" "$BASE_DETECTOR" "$BASE_COMMIT" + version_support_matrix: + name: CI / version support matrix + runs-on: ubuntu-24.04 + timeout-minutes: 5 + + outputs: + runtime-matrix: ${{ steps.matrix.outputs.runtime-matrix }} + + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + with: + persist-credentials: false + + - name: Set up Java + uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 + with: + distribution: temurin + java-version: 21 + + - name: Set up Gradle + uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5.0.2 + + - name: Read version support policy + id: matrix + run: | + ./scripts/gradle generateVersionSupportMatrix -PversionSupportMatrix=pull-request + echo "runtime-matrix=$(< build/version-support-matrix.json)" >> "$GITHUB_OUTPUT" + + runtime_compatibility: + name: CI / runtime compatibility / Java ${{ matrix.java }} + needs: version_support_matrix + runs-on: ubuntu-24.04 + timeout-minutes: 20 + + strategy: + fail-fast: false + matrix: + java: ${{ fromJSON(needs.version_support_matrix.outputs.runtime-matrix) }} + + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + with: + persist-credentials: false + + - name: Set up consumer Java + uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 + with: + distribution: temurin + java-version: ${{ matrix.java }} + + - name: Set up build Java + uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 + with: + distribution: temurin + java-version: 21 + + - name: Set up Gradle + uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5.0.2 + + - name: Run consumer compatibility smoke test + run: >- + ./scripts/gradle + :openai-java-runtime-compatibility:runRuntimeCompatibility + -PruntimeJavaVersion=${{ matrix.java }} + required: name: CI / required if: always() @@ -146,6 +211,7 @@ jobs: - build - test - api_compatibility + - runtime_compatibility runs-on: ubuntu-24.04 timeout-minutes: 5 @@ -159,6 +225,7 @@ jobs: BUILD_RESULT: ${{ needs.build.result }} TEST_RESULT: ${{ needs.test.result }} API_COMPATIBILITY_RESULT: ${{ needs.api_compatibility.result }} + RUNTIME_COMPATIBILITY_RESULT: ${{ needs.runtime_compatibility.result }} run: | set -euo pipefail @@ -178,6 +245,8 @@ jobs: if [[ "$EVENT_NAME" == "pull_request" && "$API_COMPATIBILITY_RESULT" != "success" ]]; then failed_jobs+=("API compatibility: $API_COMPATIBILITY_RESULT") fi + [[ "$RUNTIME_COMPATIBILITY_RESULT" == "success" ]] || + failed_jobs+=("runtime compatibility: $RUNTIME_COMPATIBILITY_RESULT") if (( ${#failed_jobs[@]} > 0 )); then printf 'Required CI job did not succeed: %s\n' "${failed_jobs[@]}" diff --git a/.github/workflows/publish-sonatype-reusable.yml b/.github/workflows/publish-sonatype-reusable.yml index 96b4137ab..cffff41b8 100644 --- a/.github/workflows/publish-sonatype-reusable.yml +++ b/.github/workflows/publish-sonatype-reusable.yml @@ -11,8 +11,13 @@ concurrency: cancel-in-progress: false jobs: + runtime_compatibility: + name: Publish / runtime compatibility + uses: ./.github/workflows/runtime-compatibility.yml + publish: name: Publish / Sonatype + needs: runtime_compatibility runs-on: ubuntu-24.04 timeout-minutes: 60 environment: publish diff --git a/.github/workflows/runtime-compatibility.yml b/.github/workflows/runtime-compatibility.yml new file mode 100644 index 000000000..5eca862c2 --- /dev/null +++ b/.github/workflows/runtime-compatibility.yml @@ -0,0 +1,82 @@ +# Exercises representative consumer entry points on every supported JVM. It runs nightly to catch +# ecosystem drift and is reusable so automated and manual publications are gated by the same matrix. +name: Runtime compatibility + +on: + workflow_call: + schedule: + - cron: '0 7 * * *' + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: runtime-compatibility-${{ github.ref }} + cancel-in-progress: false + +jobs: + version_support_matrix: + name: Runtime compatibility / version support matrix + runs-on: ubuntu-24.04 + timeout-minutes: 5 + + outputs: + runtime-matrix: ${{ steps.matrix.outputs.runtime-matrix }} + + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + with: + persist-credentials: false + + - name: Set up Java + uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 + with: + distribution: temurin + java-version: 21 + + - name: Set up Gradle + uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5.0.2 + + - name: Read version support policy + id: matrix + run: | + ./scripts/gradle generateVersionSupportMatrix -PversionSupportMatrix=full + echo "runtime-matrix=$(< build/version-support-matrix.json)" >> "$GITHUB_OUTPUT" + + runtime_compatibility: + name: Runtime compatibility / Java ${{ matrix.java }} + needs: version_support_matrix + runs-on: ubuntu-24.04 + timeout-minutes: 20 + + strategy: + fail-fast: false + matrix: + java: ${{ fromJSON(needs.version_support_matrix.outputs.runtime-matrix) }} + + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + with: + persist-credentials: false + + - name: Set up consumer Java + uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 + with: + distribution: temurin + java-version: ${{ matrix.java }} + + - name: Set up build Java + uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 + with: + distribution: temurin + java-version: 21 + + - name: Set up Gradle + uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5.0.2 + + - name: Run consumer compatibility smoke test + run: >- + ./scripts/gradle + :openai-java-runtime-compatibility:runRuntimeCompatibility + -PruntimeJavaVersion=${{ matrix.java }} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index edc52c816..860bef5da 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,11 +2,14 @@ ## Setting up the environment -This repository uses [Gradle](https://gradle.org/) with Kotlin DSL for building and dependency management. The SDK requires Java 8, but development requires JDK 21 for the Kotlin toolchain. +This repository uses [Gradle](https://gradle.org/) with Kotlin DSL for building and dependency +management. The framework-neutral SDK requires Java 8, while development requires JDK 21 for the +Kotlin toolchain. See the [Java version support policy](docs/version-support-policy.md) for +artifact-level runtime, framework, lifecycle, and release rules. ## Project structure -The SDK consists of three artifacts: +The SDK's primary artifacts are: - `openai-java-core` - Contains core SDK logic @@ -18,6 +21,15 @@ The SDK consists of three artifacts: - `openai-java` - Depends on and exposes the APIs of both `openai-java-core` and `openai-java-client-okhttp` - Does not have its own logic +- `openai-java-bedrock` + - Adds optional Amazon Bedrock authentication and credential-provider integration +- `openai-java-spring-boot-starter` + - Provides the legacy Spring Boot 2 integration + - Is retained to verify the final 4.45.0 release, but is OpenAI EOL and no longer published + +`openai-java-runtime-compatibility` is a non-published fixture that exercises representative +behavior for each supported artifact on an isolated runtime classpath. Its JVM matrix and each +artifact's eligibility on a given JVM come from `gradle/version-support.properties`. ## Modifying or adding code diff --git a/README.md b/README.md index 63157594d..75dc6621b 100644 --- a/README.md +++ b/README.md @@ -19,8 +19,6 @@ The REST API documentation can be found on [platform.openai.com](https://platfor -[_Try `openai-java-spring-boot-starter` if you're using Spring Boot!_](#spring-boot) - ### Gradle ```kotlin @@ -41,7 +39,8 @@ implementation("com.openai:openai-java:4.45.0") ## Requirements -This library requires Java 8 or later. +The framework-neutral SDK artifacts require Java 8 or later. Runtime floors and lifecycle states +are declared per artifact in the [Java version support policy](docs/version-support-policy.md). ## Usage @@ -1453,11 +1452,17 @@ GraalVM should automatically detect and use the published metadata, but [manual ## Spring Boot -If you're using Spring Boot, then you can use the SDK's [Spring Boot starter](https://docs.spring.io/spring-boot/docs/2.7.18/reference/htmlsingle/#using.build-systems.starters) to simplify configuration and get set up quickly. +> [!WARNING] +> `openai-java-spring-boot-starter` targets Spring Boot 2.7 and is OpenAI EOL as of 2026-07-27. +> Version 4.45.0 is the final supported, tested, and published release. The artifact remains +> downloadable but receives no fixes, testing, or compatibility support. See the +> [Spring Boot 2 EOL decision and migration path](docs/spring-boot-2-eol.md). New Spring +> applications should depend on `openai-java` directly and provide an `OpenAIClient` bean until a +> supported, generation-specific integration is available. -### Installation +Existing Spring Boot 2 applications can use the legacy starter to simplify configuration. - +### Installation #### Gradle @@ -1475,8 +1480,6 @@ implementation("com.openai:openai-java-spring-boot-starter:4.45.0") ``` - - ### Configuration The [client's environment variable options](#client-configuration) can be configured in [`application.properties` or `application.yml`](https://docs.spring.io/spring-boot/how-to/properties-and-configuration.html). @@ -1961,6 +1964,11 @@ This package generally follows [SemVer](https://semver.org/spec/v2.0.0.html) con 1. Changes to library internals which are technically public but not intended or documented for external use. _(Please open a GitHub issue to let us know if you are relying on such internals.)_ 2. Changes that we do not expect to impact the vast majority of users in practice. +Those exceptions do not apply to raising a supported JVM or JDK API floor, changing an integration's +framework generation, removing a published artifact, or removing a transitive dependency that +consumers may rely on; those changes require a major release. Announcing EOL without removing or +changing the last available artifact may be a minor release. + We take backwards-compatibility seriously and work hard to ensure you can rely on a smooth upgrade experience. We are keen for your feedback; please open an [issue](https://www.github.com/openai/openai-java/issues) with questions, bugs, or suggestions. diff --git a/build.gradle.kts b/build.gradle.kts index b2a975228..78d111df4 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,3 +1,11 @@ +import com.openai.gradle.GenerateVersionSupportMatrixTask +import com.openai.gradle.VersionSupportPolicy +import com.openai.gradle.VerifyVersionSupportPolicyTask +import org.gradle.api.plugins.JavaPluginExtension +import org.gradle.api.tasks.SourceSetContainer +import org.gradle.api.tasks.compile.JavaCompile +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + buildscript { dependencies { constraints { @@ -56,6 +64,76 @@ subprojects { apply(plugin = "org.jetbrains.dokka") } +val versionSupportFile = layout.projectDirectory.file("gradle/version-support.properties") +val versionSupport = VersionSupportPolicy.load(versionSupportFile.asFile) + +tasks.register("generateVersionSupportMatrix") { + group = "verification" + description = "Generates a CI runtime matrix from the version support policy." + + policyFile.set(versionSupportFile) + mode.set(providers.gradleProperty("versionSupportMatrix")) + outputFile.set(layout.buildDirectory.file("version-support-matrix.json")) +} + +tasks.register("verifyVersionSupportPolicy") { + group = "verification" + description = "Verifies published artifact runtime floors and lifecycle declarations." + + inputs.file(versionSupportFile) + dependsOn(versionSupport.artifacts.keys.map { ":$it:verifyVersionSupportPolicy" }) +} + +subprojects { + val artifactName = name + val artifactSupport = versionSupport.artifacts[artifactName] + + pluginManager.withPlugin("openai.publish") { + checkNotNull(artifactSupport) { + "$artifactName is published but missing from ${versionSupportFile.asFile}" + } + } + + if (artifactSupport != null) { + pluginManager.withPlugin("java") { + val java = extensions.getByType(JavaPluginExtension::class.java) + val mainSourceSet = extensions.getByType(SourceSetContainer::class.java).getByName("main") + val compileJava = tasks.named("compileJava") + + val verify = + tasks.register("verifyVersionSupportPolicy") { + group = "verification" + description = + "Verifies $artifactName against its declared version support policy." + + this.artifactName.set(artifactName) + runtimeFloor.set(artifactSupport.runtime) + lifecycle.set(artifactSupport.lifecycle.propertyValue) + requiredBuildJdk.set(versionSupport.buildJdk) + configuredBuildJdk.set(java.toolchain.languageVersion.map { it.asInt() }) + sourceCompatibility.set(java.sourceCompatibility.majorVersion.toInt()) + targetCompatibility.set(java.targetCompatibility.majorVersion.toInt()) + javaRelease.set(compileJava.flatMap { it.options.release }) + classFiles.from(mainSourceSet.output.classesDirs) + + dependsOn(tasks.named("classes")) + } + + pluginManager.withPlugin("org.jetbrains.kotlin.jvm") { + val compileKotlin = tasks.named("compileKotlin") + verify.configure { + kotlinJvmTarget.set( + compileKotlin.flatMap { it.compilerOptions.jvmTarget }.map { it.target } + ) + kotlinFreeCompilerArgs.set( + compileKotlin.flatMap { it.compilerOptions.freeCompilerArgs } + ) + } + } + } + } +} + // Avoid race conditions between `dokkaJavadocCollector` and `dokkaJavadocJar` tasks tasks.named("dokkaJavadocCollector").configure { subprojects.flatMap { it.tasks } diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index c6dc92ec5..b0b098c84 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -12,4 +12,11 @@ repositories { dependencies { implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.20") implementation("com.vanniktech:gradle-maven-publish-plugin:0.28.0") + + testImplementation(kotlin("test")) + testImplementation("org.junit.jupiter:junit-jupiter:5.9.3") +} + +tasks.test { + useJUnitPlatform() } diff --git a/buildSrc/src/main/kotlin/com/openai/gradle/GenerateVersionSupportMatrixTask.kt b/buildSrc/src/main/kotlin/com/openai/gradle/GenerateVersionSupportMatrixTask.kt new file mode 100644 index 000000000..8596c16e0 --- /dev/null +++ b/buildSrc/src/main/kotlin/com/openai/gradle/GenerateVersionSupportMatrixTask.kt @@ -0,0 +1,33 @@ +package com.openai.gradle + +import org.gradle.api.DefaultTask +import org.gradle.api.file.RegularFileProperty +import org.gradle.api.provider.Property +import org.gradle.api.tasks.CacheableTask +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.InputFile +import org.gradle.api.tasks.OutputFile +import org.gradle.api.tasks.PathSensitive +import org.gradle.api.tasks.PathSensitivity +import org.gradle.api.tasks.TaskAction + +@CacheableTask +abstract class GenerateVersionSupportMatrixTask : DefaultTask() { + @get:InputFile + @get:PathSensitive(PathSensitivity.RELATIVE) + abstract val policyFile: RegularFileProperty + + @get:Input abstract val mode: Property + + @get:OutputFile abstract val outputFile: RegularFileProperty + + @TaskAction + fun generate() { + val versions = VersionSupportPolicy.load(policyFile.get().asFile).runtimeMatrix(mode.get()) + val json = versions.joinToString(separator = "\",\"", prefix = "[\"", postfix = "\"]") + outputFile.get().asFile.apply { + parentFile.mkdirs() + writeText("$json\n") + } + } +} diff --git a/buildSrc/src/main/kotlin/com/openai/gradle/VerifyVersionSupportPolicyTask.kt b/buildSrc/src/main/kotlin/com/openai/gradle/VerifyVersionSupportPolicyTask.kt new file mode 100644 index 000000000..f02dbfd5b --- /dev/null +++ b/buildSrc/src/main/kotlin/com/openai/gradle/VerifyVersionSupportPolicyTask.kt @@ -0,0 +1,100 @@ +package com.openai.gradle + +import java.io.DataInputStream +import org.gradle.api.DefaultTask +import org.gradle.api.file.ConfigurableFileCollection +import org.gradle.api.provider.ListProperty +import org.gradle.api.provider.Property +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.InputFiles +import org.gradle.api.tasks.PathSensitive +import org.gradle.api.tasks.PathSensitivity +import org.gradle.api.tasks.TaskAction +import org.gradle.work.DisableCachingByDefault + +@DisableCachingByDefault(because = "This verification task has no outputs") +abstract class VerifyVersionSupportPolicyTask : DefaultTask() { + @get:Input abstract val artifactName: Property + + @get:Input abstract val runtimeFloor: Property + + @get:Input abstract val lifecycle: Property + + @get:Input abstract val requiredBuildJdk: Property + + @get:Input abstract val configuredBuildJdk: Property + + @get:Input abstract val sourceCompatibility: Property + + @get:Input abstract val targetCompatibility: Property + + @get:Input abstract val javaRelease: Property + + @get:Input abstract val kotlinJvmTarget: Property + + @get:Input abstract val kotlinFreeCompilerArgs: ListProperty + + @get:InputFiles + @get:PathSensitive(PathSensitivity.RELATIVE) + abstract val classFiles: ConfigurableFileCollection + + @TaskAction + fun verify() { + val name = artifactName.get() + val floor = runtimeFloor.get() + val allowedLifecycles = setOf("active", "maintenance", "deprecated", "eol") + + check(lifecycle.get() in allowedLifecycles) { + "$name must declare one of ${allowedLifecycles.sorted()}, but declared '${lifecycle.get()}'" + } + check(configuredBuildJdk.get() == requiredBuildJdk.get()) { + "$name must build with JDK ${requiredBuildJdk.get()}" + } + check(sourceCompatibility.get() == floor) { + "$name source compatibility must be Java $floor" + } + check(targetCompatibility.get() == floor) { + "$name target compatibility must be Java $floor" + } + check(javaRelease.get() == floor) { + "$name Java compiler must enforce --release $floor" + } + + val expectedKotlinTarget = if (floor == 8) "1.8" else floor.toString() + check(kotlinJvmTarget.get() == expectedKotlinTarget) { + "$name Kotlin JVM target must be $expectedKotlinTarget" + } + val jdkReleaseArguments = + kotlinFreeCompilerArgs.get().filter { it.startsWith("-Xjdk-release=") } + check(jdkReleaseArguments == listOf("-Xjdk-release=$expectedKotlinTarget")) { + "$name Kotlin compiler must enforce exactly -Xjdk-release=$expectedKotlinTarget" + } + + val maximumClassMajorVersion = 44 + floor + val bytecodeErrors = + classFiles.asFileTree + .matching { include("**/*.class") } + .mapNotNull { classFile -> + DataInputStream(classFile.inputStream().buffered()).use { input -> + check(input.readInt() == 0xCAFEBABE.toInt()) { + "$classFile is not a valid JVM class file" + } + input.readUnsignedShort() + val classMajorVersion = input.readUnsignedShort() + if (classMajorVersion > maximumClassMajorVersion) { + "$classFile uses class-file version $classMajorVersion; $name permits " + + "at most $maximumClassMajorVersion (Java $floor)" + } else { + null + } + } + } + + check(bytecodeErrors.isEmpty()) { + bytecodeErrors.joinToString( + separator = "\n- ", + prefix = "Published bytecode exceeds its declared runtime floor:\n- ", + ) + } + } +} diff --git a/buildSrc/src/main/kotlin/com/openai/gradle/VersionSupportPolicy.kt b/buildSrc/src/main/kotlin/com/openai/gradle/VersionSupportPolicy.kt new file mode 100644 index 000000000..af990380a --- /dev/null +++ b/buildSrc/src/main/kotlin/com/openai/gradle/VersionSupportPolicy.kt @@ -0,0 +1,166 @@ +package com.openai.gradle + +import java.io.File +import java.io.Serializable +import java.util.Properties + +enum class ArtifactLifecycle(val propertyValue: String) { + ACTIVE("active"), + MAINTENANCE("maintenance"), + DEPRECATED("deprecated"), + EOL("eol"); + + val isSupported: Boolean + get() = this != EOL + + companion object { + fun parse(value: String, property: String): ArtifactLifecycle = + values().find { it.propertyValue == value } + ?: error( + "$property must be one of ${values().map { it.propertyValue }}, but was '$value'" + ) + } +} + +data class ArtifactSupport( + val runtime: Int, + val lifecycle: ArtifactLifecycle, +) : Serializable + +data class VersionSupportPolicy( + val buildJdk: Int, + val currentLts: Int, + val supportedLts: List, + val currentNonLts: Int, + val artifacts: Map, +) : Serializable { + private val supportedArtifacts = artifacts.filterValues { it.lifecycle.isSupported } + val minimumSupportedRuntime: Int = supportedArtifacts.values.minOf { it.runtime } + + fun runtimeFloor(projectName: String): Int = + artifacts[projectName]?.runtime ?: minimumSupportedRuntime + + fun runtimeMatrix(mode: String): List { + val supportedArtifactFloors = + supportedArtifacts.values.map(ArtifactSupport::runtime) + return when (mode) { + "pull-request" -> supportedArtifactFloors + currentLts + "full" -> supportedLts + currentNonLts + else -> + error( + "Unknown version support matrix '$mode'; expected 'pull-request' or 'full'" + ) + } + .distinct() + .sorted() + } + + companion object { + private val requiredKeys = + setOf( + "build.jdk", + "test.current-lts", + "test.supported-lts", + "test.current-non-lts", + ) + private val artifactKey = Regex("""artifact\.(.+)\.(runtime|lifecycle)""") + + fun load(file: File): VersionSupportPolicy { + check(file.isFile) { "Version support policy does not exist: $file" } + + val properties = Properties().apply { file.inputStream().use(::load) } + val unknownKeys = + properties.stringPropertyNames().filterNot { key -> + key in requiredKeys || artifactKey.matches(key) + } + check(unknownKeys.isEmpty()) { + "Unknown version support properties in $file: ${unknownKeys.sorted()}" + } + + val missingKeys = requiredKeys.filterNot(properties::containsKey) + check(missingKeys.isEmpty()) { + "Missing version support properties in $file: ${missingKeys.sorted()}" + } + + val artifactNames = + properties + .stringPropertyNames() + .mapNotNull { artifactKey.matchEntire(it)?.groupValues?.get(1) } + .toSortedSet() + check(artifactNames.isNotEmpty()) { "No artifact policies declared in $file" } + + val artifacts = + artifactNames.associateWith { name -> + val runtime = properties.requiredInt("artifact.$name.runtime", file) + val lifecycleProperty = "artifact.$name.lifecycle" + val lifecycle = + ArtifactLifecycle.parse( + properties.required(lifecycleProperty, file), + lifecycleProperty, + ) + ArtifactSupport(runtime = runtime, lifecycle = lifecycle) + } + check(artifacts.values.any { it.lifecycle.isSupported }) { + "At least one artifact in $file must have a supported lifecycle" + } + + val buildJdk = properties.requiredInt("build.jdk", file) + val currentLts = properties.requiredInt("test.current-lts", file) + val supportedLts = + properties + .required("test.supported-lts", file) + .split(',') + .map { value -> + value.trim().toIntOrNull() + ?: error("test.supported-lts contains invalid JVM '$value' in $file") + } + val currentNonLts = properties.requiredInt("test.current-non-lts", file) + + check(buildJdk > 0) { "build.jdk must be positive" } + check(supportedLts.isNotEmpty() && supportedLts.all { it > 0 }) { + "test.supported-lts must contain positive JVM versions" + } + check(supportedLts == supportedLts.distinct().sorted()) { + "test.supported-lts must contain unique JVMs in ascending order" + } + check(currentLts in supportedLts) { + "test.current-lts ($currentLts) must appear in test.supported-lts" + } + check(currentNonLts > currentLts) { + "test.current-non-lts ($currentNonLts) must be newer than current LTS $currentLts" + } + artifacts.forEach { (name, support) -> + check(support.runtime > 0) { "$name must declare a positive runtime floor" } + check(support.runtime <= buildJdk) { + "$name requires Java ${support.runtime}, newer than build JDK $buildJdk" + } + check(!support.lifecycle.isSupported || support.runtime in supportedLts) { + "$name has supported runtime floor ${support.runtime}, which is absent from " + + "test.supported-lts" + } + } + + return VersionSupportPolicy( + buildJdk = buildJdk, + currentLts = currentLts, + supportedLts = supportedLts, + currentNonLts = currentNonLts, + artifacts = artifacts, + ) + } + + private fun Properties.required( + key: String, + file: File, + ): String = + getProperty(key)?.trim()?.takeIf(String::isNotEmpty) + ?: error("Required version support property '$key' is missing or empty in $file") + + private fun Properties.requiredInt( + key: String, + file: File, + ): Int = + required(key, file).toIntOrNull() + ?: error("Version support property '$key' must be an integer in $file") + } +} diff --git a/buildSrc/src/main/kotlin/openai.java.gradle.kts b/buildSrc/src/main/kotlin/openai.java.gradle.kts index e64ff271c..38e72eab6 100644 --- a/buildSrc/src/main/kotlin/openai.java.gradle.kts +++ b/buildSrc/src/main/kotlin/openai.java.gradle.kts @@ -1,3 +1,4 @@ +import com.openai.gradle.VersionSupportPolicy import org.gradle.api.tasks.testing.logging.TestExceptionFormat plugins { @@ -8,18 +9,22 @@ repositories { mavenCentral() } +val versionSupportPolicy = + VersionSupportPolicy.load(rootProject.file("gradle/version-support.properties")) +val runtimeFloor = versionSupportPolicy.runtimeFloor(project.name) + java { toolchain { - languageVersion.set(JavaLanguageVersion.of(21)) + languageVersion.set(JavaLanguageVersion.of(versionSupportPolicy.buildJdk)) } - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 + sourceCompatibility = JavaVersion.toVersion(runtimeFloor) + targetCompatibility = JavaVersion.toVersion(runtimeFloor) } tasks.withType().configureEach { options.compilerArgs.add("-Werror") - options.release.set(8) + options.release.set(runtimeFloor) } tasks.named("jar") { diff --git a/buildSrc/src/main/kotlin/openai.kotlin.gradle.kts b/buildSrc/src/main/kotlin/openai.kotlin.gradle.kts index 37850353a..33c864220 100644 --- a/buildSrc/src/main/kotlin/openai.kotlin.gradle.kts +++ b/buildSrc/src/main/kotlin/openai.kotlin.gradle.kts @@ -1,3 +1,4 @@ +import com.openai.gradle.VersionSupportPolicy import org.jetbrains.kotlin.gradle.dsl.JvmTarget import org.jetbrains.kotlin.gradle.dsl.KotlinVersion @@ -10,20 +11,25 @@ repositories { mavenCentral() } +val versionSupportPolicy = + VersionSupportPolicy.load(rootProject.file("gradle/version-support.properties")) +val runtimeFloor = versionSupportPolicy.runtimeFloor(project.name) +val kotlinJvmTarget = if (runtimeFloor == 8) "1.8" else runtimeFloor.toString() + kotlin { jvmToolchain { - languageVersion.set(JavaLanguageVersion.of(21)) + languageVersion.set(JavaLanguageVersion.of(versionSupportPolicy.buildJdk)) } compilerOptions { freeCompilerArgs = listOf( "-Xjvm-default=all", - "-Xjdk-release=1.8", + "-Xjdk-release=$kotlinJvmTarget", // Suppress deprecation warnings because we may still reference and test deprecated members. // TODO: Replace with `-Xsuppress-warning=DEPRECATION` once we use Kotlin compiler 2.1.0+. "-nowarn", ) - jvmTarget.set(JvmTarget.JVM_1_8) + jvmTarget.set(JvmTarget.fromTarget(kotlinJvmTarget)) languageVersion.set(KotlinVersion.KOTLIN_1_8) apiVersion.set(KotlinVersion.KOTLIN_1_8) coreLibrariesVersion = "1.8.0" diff --git a/buildSrc/src/test/kotlin/com/openai/gradle/VersionSupportPolicyTest.kt b/buildSrc/src/test/kotlin/com/openai/gradle/VersionSupportPolicyTest.kt new file mode 100644 index 000000000..500d7ed3e --- /dev/null +++ b/buildSrc/src/test/kotlin/com/openai/gradle/VersionSupportPolicyTest.kt @@ -0,0 +1,79 @@ +package com.openai.gradle + +import java.nio.file.Path +import kotlin.io.path.writeText +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith +import kotlin.test.assertTrue +import org.junit.jupiter.api.io.TempDir + +class VersionSupportPolicyTest { + @TempDir lateinit var temporaryDirectory: Path + + @Test + fun `matrices include supported artifact floors and exclude EOL floors`() { + val policy = + loadPolicy( + """ + build.jdk=21 + test.current-lts=25 + test.supported-lts=8,11,17,21,25 + test.current-non-lts=26 + artifact.java-8.runtime=8 + artifact.java-8.lifecycle=active + artifact.java-17.runtime=17 + artifact.java-17.lifecycle=maintenance + artifact.eol.runtime=11 + artifact.eol.lifecycle=eol + """ + ) + + assertEquals(listOf(8, 17, 25), policy.runtimeMatrix("pull-request")) + assertEquals(listOf(8, 11, 17, 21, 25, 26), policy.runtimeMatrix("full")) + } + + @Test + fun `Java properties syntax has one validated interpretation`() { + val policy = + loadPolicy( + """ + build.jdk : 21 + test.current-lts = 25 + test.supported-lts = 8,11,17,21,\ + 25 + test.current-non-lts = 26 + artifact.core.runtime = 8 + artifact.core.lifecycle = active + """ + ) + + assertEquals(listOf(8, 11, 17, 21, 25), policy.supportedLts) + } + + @Test + fun `unknown properties fail closed`() { + val error = + assertFailsWith { + loadPolicy( + """ + build.jdk=21 + test.current-lts=25 + test.supported-lts=8,11,17,21,25 + test.current-non-lts=26 + test.minimum=8 + artifact.core.runtime=8 + artifact.core.lifecycle=active + """ + ) + } + + assertTrue(error.message.orEmpty().contains("Unknown version support properties")) + } + + private fun loadPolicy(contents: String): VersionSupportPolicy { + val policyFile = temporaryDirectory.resolve("version-support.properties") + policyFile.writeText(contents.trimIndent()) + return VersionSupportPolicy.load(policyFile.toFile()) + } +} diff --git a/docs/spring-boot-2-eol.md b/docs/spring-boot-2-eol.md new file mode 100644 index 000000000..6b3325a6b --- /dev/null +++ b/docs/spring-boot-2-eol.md @@ -0,0 +1,42 @@ +# Spring Boot 2 starter end-of-life + +**Status:** Accepted 2026-07-27 + +## Decision + +`com.openai:openai-java-spring-boot-starter` targets Spring Boot 2.7 and is OpenAI EOL effective +2026-07-27. Version 4.45.0 is its final supported, tested, and published OpenAI SDK release. That +release remains downloadable, but no later starter versions will be published and it receives no +fixes, testing, or compatibility support. + +Spring ended public OSS support for Boot 2 with +[2.7.18 on 2023-11-23](https://spring.io/blog/2023/11/23/spring-boot-2-7-18-available-now/). +The policy's maximum six-month grace period therefore ended 2024-05-23. Commercial maintenance does +not satisfy the repository's OSS-support admission rule, and the upstream gap is already much +longer than the permitted grace period. + +Declaring the unchanged starter EOL is a minor release. Removing the artifact, raising its Java +floor, or changing the same coordinate to a newer Spring generation would require a SemVer major. +A future supported Spring integration must use generation-specific coordinates and independently +pass the policy's 12-month admission gate. + +## Consumer migration + +Applications that cannot leave Boot 2 may pin the starter to 4.45.0, but do so without OpenAI or +upstream OSS security support. The preferred path is: + +1. Follow Spring's [Boot 3 migration guide](https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-3.0-Migration-Guide): + upgrade to the latest 2.7 release first, move to Java 17 and Spring Framework 6, and migrate + affected Java EE imports to Jakarta EE. +2. Remove `openai-java-spring-boot-starter`. +3. Depend on `com.openai:openai-java` and provide a normal application-owned `OpenAIClient` bean. + +```java +@Bean +OpenAIClient openAIClient() { + return OpenAIOkHttpClient.fromEnv(); +} +``` + +This keeps Spring's dependency platform authoritative and avoids pretending that a Boot 2 +auto-configuration artifact supports Boot 3 or later. diff --git a/docs/version-support-policy.md b/docs/version-support-policy.md new file mode 100644 index 000000000..fc17644f1 --- /dev/null +++ b/docs/version-support-policy.md @@ -0,0 +1,88 @@ +# Java version support policy + +This policy governs the OpenAI Java SDK's supported JVMs, framework integrations, and +material runtime dependencies. Compatibility is declared per published artifact, not inferred +from the JDK used to build the repository. Dependency-specific decisions belong in short ADRs +that apply this policy. + +## Compatibility contract + +- Every artifact declares its runtime and bytecode/API floor and lifecycle state in + [`gradle/version-support.properties`](../gradle/version-support.properties). Framework + generations and tested ranges belong in the relevant artifact documentation or ADR. +- A newer build JDK must not silently raise the consumer runtime floor. +- The framework-neutral core evolves independently from optional integrations. An integration may + require a newer JVM only when it uses a separate, generation-specific artifact. + +## Admission and retirement + +- Do not add support for a runtime, framework, or major dependency generation with fewer than + **12 months of expected public OSS security maintenance** remaining. Commercial-only maintenance + does not count. +- An already-supported generation enters maintenance during its final 12 months: critical fixes and + migration help only, with no feature expansion. +- At upstream EOL, retire support or grant a documented grace period of **up to six months**. State + the exact end date and the limits of supporting software that no longer receives upstream fixes. +- After the grace period, the generation is OpenAI EOL. Old artifacts may remain downloadable, but + availability is not support. +- Security risk may shorten timelines. It does not turn a breaking change into a compatible one. + +## Framework and dependency ownership + +- Use separate coordinates for materially incompatible framework generations. Do not span them + with reflection or hidden runtime branching. +- The consuming application's framework platform or BOM is authoritative. Do not force isolated + framework components outside its supported dependency graph. +- OpenAI owns core implementation dependencies. Consumers own their application platform. + Optional integration dependencies are shared: OpenAI declares and tests the range; consumers + choose the version through their platform. + +## Release treatment + +| Change | Default release | +| --- | --- | +| Fix or dependency update within the supported contract | Patch | +| Add a new, explicit integration artifact | Minor | +| Move an existing generation to maintenance, deprecate it, or declare it EOL while leaving its last release available | Minor | +| Raise an artifact's JVM/API floor; change its framework generation; remove an artifact; or remove a transitive dependency consumers may rely on | Major | +| Urgent security-driven incompatible change | Major by default; security changes timing, not compatibility | + +## Required proof + +- **Every PR:** verify every published artifact's declared JVM floor, compiler API floor, and + emitted bytecode; run each supported artifact's isolated consumer smoke test on its runtime floor + and the current LTS; check public API compatibility; and run the normal build, test, and + dependency-compatibility suites. +- **Nightly and before release:** run every supported artifact's isolated consumer smoke test on + every compatible supported LTS and use the current non-LTS JDK as a forward-compatibility signal. +- **Quarterly and before GA:** review upstream lifecycles and revalidate the 12-month admission gate, + generated metadata, dependency ownership, and migration plan. +- Any material runtime, framework, or dependency-generation change requires an ADR covering + lifecycle evidence, affected artifacts, consumer resolution, migration, SemVer treatment, final + compatible release, and verification. + +## Current decisions + +| Surface | Decision | Next action | +| --- | --- | --- | +| Framework-neutral artifacts | Keep Java 8 through SDK v4. | Raise the floor only in an SDK major; first prefer replacing or isolating a constraining dependency. | +| Existing Spring Boot 2 starter | OpenAI EOL as of 2026-07-27; 4.45.0 is the final supported and published release. | Follow the [Spring Boot 2 EOL decision](spring-boot-2-eol.md); publish no later versions. | +| Potential Spring Boot 4 integration | Candidate Java 17 integration using a new generation-specific artifact. | Pass the admission gate and Spring ADR before implementation. | + +Do not publish an OpenAI Java BOM until independently versioned artifacts or demonstrated consumer +resolution failures create a real alignment need. Add an optional integration only when it passes +the lifecycle gate, solves substantial repeated work for SDK users, and has a named owner and funded +compatibility matrix. + +## Supporting rationale + +- Direct AI SDK peers such as [Anthropic Java](https://github.com/anthropics/anthropic-sdk-java#requirements) + and [Google Gen AI Java](https://github.com/googleapis/java-genai/blob/main/pom.xml) retain a Java 8 + core floor. +- [Google Cloud Java](https://github.com/googleapis/java-datastore#supported-java-versions) treats + raising the JVM floor as a SemVer-major boundary. +- The [AWS SDK maintenance policy](https://docs.aws.amazon.com/sdkref/latest/guide/maint-policy.html) + demonstrates explicit, time-bounded support beyond an upstream dependency's EOL. +- [Jackson](https://github.com/FasterXML/jackson-databind#jdk) and + [Spring Boot](https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-4.0-Migration-Guide) + use major generations for JVM, package, namespace, and framework compatibility boundaries. diff --git a/gradle/version-support.properties b/gradle/version-support.properties new file mode 100644 index 000000000..4c55d7516 --- /dev/null +++ b/gradle/version-support.properties @@ -0,0 +1,19 @@ +# Repository toolchain. This may be newer than any published artifact's runtime floor. +build.jdk=21 + +# JVMs exercised by consumer-facing compatibility smoke tests. +test.current-lts=25 +test.supported-lts=8,11,17,21,25 +test.current-non-lts=26 + +# Published artifacts must declare a runtime floor and lifecycle. +artifact.openai-java.runtime=8 +artifact.openai-java.lifecycle=active +artifact.openai-java-bedrock.runtime=8 +artifact.openai-java-bedrock.lifecycle=active +artifact.openai-java-client-okhttp.runtime=8 +artifact.openai-java-client-okhttp.lifecycle=active +artifact.openai-java-core.runtime=8 +artifact.openai-java-core.lifecycle=active +artifact.openai-java-spring-boot-starter.runtime=8 +artifact.openai-java-spring-boot-starter.lifecycle=eol diff --git a/openai-java-core/build.gradle.kts b/openai-java-core/build.gradle.kts index f36399698..a1ee2daa7 100644 --- a/openai-java-core/build.gradle.kts +++ b/openai-java-core/build.gradle.kts @@ -95,6 +95,9 @@ dependencies { testImplementation("org.mockito:mockito-core:$mockitoVersion") testImplementation("org.mockito:mockito-junit-jupiter:$mockitoVersion") testImplementation("org.mockito.kotlin:mockito-kotlin:4.1.0") + testRuntimeOnly("org.slf4j:slf4j-nop:2.0.17") { + because("prevent SLF4J provider diagnostics from racing stderr assertions") + } mockitoAgent("org.mockito:mockito-core:$mockitoVersion") { isTransitive = false } } diff --git a/openai-java-runtime-compatibility/build.gradle.kts b/openai-java-runtime-compatibility/build.gradle.kts new file mode 100644 index 000000000..60adf7099 --- /dev/null +++ b/openai-java-runtime-compatibility/build.gradle.kts @@ -0,0 +1,100 @@ +import com.openai.gradle.VersionSupportPolicy +import org.gradle.api.attributes.Bundling +import org.gradle.api.attributes.Category +import org.gradle.api.attributes.LibraryElements +import org.gradle.api.attributes.Usage +import org.gradle.api.attributes.java.TargetJvmVersion + +plugins { + id("openai.java") +} + +val versionSupportPolicy = + VersionSupportPolicy.load(rootProject.file("gradle/version-support.properties")) +val runtimeProbes = + mapOf( + "openai-java-core" to "com.openai.compatibility.CoreRuntimeProbe", + "openai-java-client-okhttp" to "com.openai.compatibility.OkHttpRuntimeProbe", + "openai-java" to "com.openai.compatibility.OkHttpRuntimeProbe", + "openai-java-bedrock" to "com.openai.compatibility.BedrockRuntimeProbe", + ) +val supportedArtifacts = + versionSupportPolicy.artifacts.filterValues { it.lifecycle.isSupported }.keys + +check(runtimeProbes.keys == supportedArtifacts) { + "Runtime probes must exactly cover supported artifacts. " + + "Missing: ${supportedArtifacts - runtimeProbes.keys}; " + + "unexpected: ${runtimeProbes.keys - supportedArtifacts}" +} + +dependencies { + runtimeProbes.keys.forEach { compileOnly(project(":$it")) } +} + +tasks.withType().configureEach { + // This fixture intentionally compiles at the SDK's minimum consumer level. + options.compilerArgs.add("-Xlint:-options") +} + +val runtimeJavaVersion = + providers + .gradleProperty("runtimeJavaVersion") + .map(String::toInt) + .orElse(versionSupportPolicy.minimumSupportedRuntime) +val runtimeLauncher = + javaToolchains.launcherFor { + languageVersion.set(runtimeJavaVersion.map(JavaLanguageVersion::of)) + } + +fun String.toTaskSegment(): String = + split('-').joinToString("") { segment -> + segment.replaceFirstChar { firstCharacter -> firstCharacter.uppercase() } + } + +val artifactProbeTasks = + runtimeProbes.mapValues { (artifactName, probeMainClass) -> + val artifactSupport = versionSupportPolicy.artifacts.getValue(artifactName) + val runtimeClasspath = + configurations.create("${artifactName.toTaskSegment()}RuntimeCompatibilityClasspath") { + isCanBeConsumed = false + isCanBeResolved = true + attributes { + attribute(Bundling.BUNDLING_ATTRIBUTE, objects.named(Bundling.EXTERNAL)) + attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category.LIBRARY)) + attribute( + LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, + objects.named(LibraryElements.JAR), + ) + attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage.JAVA_RUNTIME)) + attribute( + TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, + artifactSupport.runtime, + ) + } + } + dependencies.add(runtimeClasspath.name, project(":$artifactName")) + + tasks.register("run${artifactName.toTaskSegment()}RuntimeCompatibility") { + group = "verification" + description = + "Exercises $artifactName on its isolated consumer runtime classpath." + + classpath = sourceSets.main.get().output + runtimeClasspath + mainClass.set(probeMainClass) + javaLauncher.set(runtimeLauncher) + } + } + +tasks.register("runRuntimeCompatibility") { + group = "verification" + description = "Exercises every supported artifact compatible with the requested consumer JVM." + + dependsOn( + artifactProbeTasks + .filterKeys { artifactName -> + versionSupportPolicy.artifacts.getValue(artifactName).runtime <= + runtimeJavaVersion.get() + } + .values + ) +} diff --git a/openai-java-runtime-compatibility/src/main/java/com/openai/compatibility/BedrockRuntimeProbe.java b/openai-java-runtime-compatibility/src/main/java/com/openai/compatibility/BedrockRuntimeProbe.java new file mode 100644 index 000000000..18428f799 --- /dev/null +++ b/openai-java-runtime-compatibility/src/main/java/com/openai/compatibility/BedrockRuntimeProbe.java @@ -0,0 +1,39 @@ +package com.openai.compatibility; + +import com.openai.client.OpenAIClient; +import com.openai.client.okhttp.BedrockOpenAIOkHttpClient; + +public final class BedrockRuntimeProbe { + private static final String[] RUNTIME_PROVIDER_CLASSES = { + "software.amazon.awssdk.services.sts.StsClient", + "software.amazon.awssdk.services.sso.SsoClient", + "software.amazon.awssdk.services.ssooidc.SsoOidcClient", + "software.amazon.awssdk.services.signin.SigninClient", + "software.amazon.awssdk.http.urlconnection.UrlConnectionHttpClient", + }; + + private BedrockRuntimeProbe() {} + + public static void main(String[] args) throws ClassNotFoundException { + for (String providerClass : RUNTIME_PROVIDER_CLASSES) { + Class.forName(providerClass); + } + + OpenAIClient client = BedrockOpenAIOkHttpClient.builder() + .awsRegion("us-east-1") + .baseUrl("https://example.com/openai/v1") + .skipAuth(true) + .build(); + try { + if (client.models() == null) { + throw new IllegalStateException("Bedrock client did not create its model service"); + } + } finally { + client.close(); + } + + System.out.printf( + "Exercised openai-java-bedrock and %d runtime providers on Java %s.%n", + RUNTIME_PROVIDER_CLASSES.length, System.getProperty("java.version")); + } +} diff --git a/openai-java-runtime-compatibility/src/main/java/com/openai/compatibility/CoreRuntimeProbe.java b/openai-java-runtime-compatibility/src/main/java/com/openai/compatibility/CoreRuntimeProbe.java new file mode 100644 index 000000000..99c74d920 --- /dev/null +++ b/openai-java-runtime-compatibility/src/main/java/com/openai/compatibility/CoreRuntimeProbe.java @@ -0,0 +1,35 @@ +package com.openai.compatibility; + +import com.openai.core.JsonValue; +import com.openai.models.chat.completions.ChatCompletionCreateParams; +import com.openai.models.chat.completions.StructuredChatCompletionCreateParams; +import java.util.Collections; +import java.util.Map; + +public final class CoreRuntimeProbe { + private CoreRuntimeProbe() {} + + public static void main(String[] args) { + Map input = Collections.singletonMap("probe", "runtime"); + Map converted = JsonValue.from(input).convert(Map.class); + + if (converted == null || !"runtime".equals(converted.get("probe"))) { + throw new IllegalStateException("Core JSON round trip failed"); + } + + StructuredChatCompletionCreateParams params = ChatCompletionCreateParams.builder() + .addUserMessage("runtime probe") + .model("runtime-probe") + .responseFormat(RuntimeResponse.class) + .build(); + if (!params.rawParams().responseFormat().isPresent()) { + throw new IllegalStateException("Core structured-output schema generation failed"); + } + + System.out.printf("Exercised openai-java-core on Java %s.%n", System.getProperty("java.version")); + } + + public static final class RuntimeResponse { + public String value; + } +} diff --git a/openai-java-runtime-compatibility/src/main/java/com/openai/compatibility/OkHttpRuntimeProbe.java b/openai-java-runtime-compatibility/src/main/java/com/openai/compatibility/OkHttpRuntimeProbe.java new file mode 100644 index 000000000..a586a6347 --- /dev/null +++ b/openai-java-runtime-compatibility/src/main/java/com/openai/compatibility/OkHttpRuntimeProbe.java @@ -0,0 +1,22 @@ +package com.openai.compatibility; + +import com.openai.client.OpenAIClient; +import com.openai.client.okhttp.OpenAIOkHttpClient; + +public final class OkHttpRuntimeProbe { + private OkHttpRuntimeProbe() {} + + public static void main(String[] args) { + OpenAIClient client = + OpenAIOkHttpClient.builder().apiKey("runtime-probe").build(); + try { + if (client.models() == null) { + throw new IllegalStateException("OkHttp client did not create its model service"); + } + } finally { + client.close(); + } + + System.out.printf("Exercised an OkHttp SDK client on Java %s.%n", System.getProperty("java.version")); + } +} diff --git a/openai-java-spring-boot-starter/build.gradle.kts b/openai-java-spring-boot-starter/build.gradle.kts index c49a36abe..6b68e7ecb 100644 --- a/openai-java-spring-boot-starter/build.gradle.kts +++ b/openai-java-spring-boot-starter/build.gradle.kts @@ -1,6 +1,5 @@ plugins { id("openai.kotlin") - id("openai.publish") } repositories { diff --git a/scripts/build b/scripts/build index 23d80780f..edb4c1b68 100755 --- a/scripts/build +++ b/scripts/build @@ -4,5 +4,8 @@ set -e cd "$(dirname "$0")/.." +echo "==> Testing build logic" +./scripts/gradle :buildSrc:test "$@" + echo "==> Building classes" -./scripts/gradle build testClasses "$@" -x test +./scripts/gradle build testClasses verifyVersionSupportPolicy "$@" -x test