From a58db96df537f32904b416eea19c1f4b6f829f91 Mon Sep 17 00:00:00 2001 From: Shrey Bana Date: Thu, 31 Jul 2025 16:50:13 +0530 Subject: [PATCH] feat(java): Publishing to central-sonatype. --- .github/workflows/java-cicd.yaml | 103 ++++++++ clients/java/bindings/build.gradle.kts | 22 +- clients/java/build.gradle.kts | 57 +++- clients/java/buildSrc/build.gradle.kts | 9 + .../java-library-conventions.gradle.kts | 12 + .../kotlin/publishing-conventions.gradle.kts | 68 +++++ .../open-feature-provider/build.gradle.kts | 26 +- clients/java/sdk/build.gradle.kts | 19 +- .../superposition/model/OptionalBucket.java | 244 ------------------ 9 files changed, 253 insertions(+), 307 deletions(-) create mode 100644 .github/workflows/java-cicd.yaml create mode 100644 clients/java/buildSrc/build.gradle.kts create mode 100644 clients/java/buildSrc/src/main/kotlin/java-library-conventions.gradle.kts create mode 100644 clients/java/buildSrc/src/main/kotlin/publishing-conventions.gradle.kts delete mode 100644 clients/java/sdk/src/main/java/io/juspay/superposition/model/OptionalBucket.java diff --git a/.github/workflows/java-cicd.yaml b/.github/workflows/java-cicd.yaml new file mode 100644 index 000000000..801fe1b57 --- /dev/null +++ b/.github/workflows/java-cicd.yaml @@ -0,0 +1,103 @@ +name: Java CI/CD + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + workflow_dispatch: + +jobs: + publish: + runs-on: ubuntu-latest + # needs: build-and-test + # if: github.event_name == 'workflow_dispatch' + env: + JRELEASER_GPG_SECRET_KEY: ${{ secrets.SONATYPE_MAVEN_SIGNING_KEY }} + JRELEASER_GPG_PASSPHRASE: ${{ secrets.SONATYPE_MAVEN_SIGNING_KEY_PASSWORD }} + JRELEASER_MAVENCENTRAL_USERNAME: ${{ secrets.SONATYPE_MAVEN_USERNAME }} + JRELEASER_MAVENCENTRAL_TOKEN: ${{ secrets.SONATYPE_MAVEN_PASSWORD }} + defaults: + run: + working-directory: clients/java + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up JDK 17 + uses: actions/setup-java@v4 + with: + java-version: '17' + distribution: 'temurin' + + - name: Make gradlew executable + run: chmod +x gradlew + + - name: Publish + run: ./gradlew clean publish + + - name: Check required secrets + run: | + if [[ -z "$JRELEASER_GPG_SECRET_KEY" ]]; then + echo "❌ JRELEASER_GPG_SECRET_KEY is not set" + exit 1 + fi + if [[ -z "$JRELEASER_GPG_PASSPHRASE" ]]; then + echo "❌ JRELEASER_GPG_PASSPHRASE is not set" + exit 1 + fi + if [[ -z "$JRELEASER_MAVENCENTRAL_USERNAME" ]]; then + echo "❌ JRELEASER_MAVENCENTRAL_USERNAME is not set" + exit 1 + fi + if [[ -z "$JRELEASER_MAVENCENTRAL_TOKEN" ]]; then + echo "❌ JRELEASER_MAVENCENTRAL_TOKEN is not set" + exit 1 + fi + echo "✅ All required secrets are set" + + - name: Deploy to Sonatype + run: ./gradlew jreleaserDeploy + # GRADLE_OPTS: >- + # -DsonatypeUsername=${{ secrets.SONATYPE_MAVEN_USERNAME }} + # -DsonatypePassword=${{ secrets.SONATYPE_MAVEN_PASSWORD }} + # -DsigningKey=${{ secrets.SONATYPE_MAVEN_SIGNING_KEY }} + # -DsigningPassword=${{ secrets.SONATYPE_MAVEN_SIGNING_KEY_PASSWORD }} + + # build-and-test: + # runs-on: ubuntu-latest + # defaults: + # run: + # working-directory: clients/java + + # steps: + # - name: Checkout code + # uses: actions/checkout@v4 + + # - name: Set up JDK 17 + # uses: actions/setup-java@v4 + # with: + # java-version: '17' + # distribution: 'temurin' + + # - name: Make gradlew executable + # run: chmod +x gradlew + + # - name: Run Gradle assemble + # run: ./gradlew assemble + + # ## This requires more setup. Specifically, we need the server to be up w/ + # ## some dummy data. + # # - name: Run tests + # # run: ./gradlew test + + # - name: Upload build artifacts + # if: failure() + # uses: actions/upload-artifact@v4 + # with: + # name: build-reports + # path: | + # build/reports/ + # build/test-results/ + # retention-days: 7 diff --git a/clients/java/bindings/build.gradle.kts b/clients/java/bindings/build.gradle.kts index 9cfecdc3d..bf4881712 100644 --- a/clients/java/bindings/build.gradle.kts +++ b/clients/java/bindings/build.gradle.kts @@ -1,27 +1,13 @@ -import java.net.URL - plugins { - `maven-publish` - `java-library` + `java-library-conventions` + `publishing-conventions` kotlin("jvm") version "1.9.10" } -java { - toolchain { - languageVersion = JavaLanguageVersion.of(17) - } -} +extra["displayName"] = "Superposition Foreign Function Interface" +description = "Bindings for some of superpositions core functions." dependencies { implementation("org.jetbrains.kotlin:kotlin-stdlib") implementation("net.java.dev.jna:jna:5.13.0") } - - -publishing { - publications { - create("maven") { - from(components["kotlin"]) - } - } -} \ No newline at end of file diff --git a/clients/java/build.gradle.kts b/clients/java/build.gradle.kts index b6247bee0..1de565241 100644 --- a/clients/java/build.gradle.kts +++ b/clients/java/build.gradle.kts @@ -1,3 +1,10 @@ +import org.jreleaser.model.Active + +plugins { + id("org.jreleaser") version "1.19.0" + id("base") +} + allprojects { group = "io.juspay.superposition" version = System.getenv("VERSION") ?: "0.0.1-SNAPSHOT" @@ -6,17 +13,49 @@ allprojects { google() gradlePluginPortal() } +} + +/* + * Jreleaser (https://jreleaser.org) config. + */ +jreleaser { + dryrun = false - apply(plugin = "maven-publish") + // Used for creating a tagged release, uploading files and generating changelog. + // In the future we can set this up to push release tags to GitHub, but for now it's + // set up to do nothing. + // https://jreleaser.org/guide/latest/reference/release/index.html + release { + generic { + enabled = true + skipRelease = true + } + } + + // Used to announce a release to configured announcers. + // https://jreleaser.org/guide/latest/reference/announce/index.html + announce { + active = Active.NEVER + } + + // Signing configuration. + // https://jreleaser.org/guide/latest/reference/signing.html + signing { + active = Active.ALWAYS + armored = true + verify = false + } - configure { - repositories { - maven { - name = "CodeArtifact" - url = uri(System.getenv("CODEARTIFACT_REPOSITORY_ENDPOINT") ?: "https://non.existent.site.here") - credentials { - username = "aws" - password = System.getenv("CODEARTIFACT_AUTH_TOKEN") + // Configuration for deploying to Maven Central. + // https://jreleaser.org/guide/latest/examples/maven/maven-central.html#_gradle + deploy { + maven { + mavenCentral { + create("maven-central") { + active = Active.ALWAYS + url = "https://central.sonatype.com/api/v1/publisher" + snapshotSupported = true + stagingRepository(rootProject.layout.buildDirectory.dir("staging-deploy").get().asFile.path) } } } diff --git a/clients/java/buildSrc/build.gradle.kts b/clients/java/buildSrc/build.gradle.kts new file mode 100644 index 000000000..5a56eac7c --- /dev/null +++ b/clients/java/buildSrc/build.gradle.kts @@ -0,0 +1,9 @@ +plugins { + `kotlin-dsl` +} + +repositories { + gradlePluginPortal() + mavenCentral() + google() +} diff --git a/clients/java/buildSrc/src/main/kotlin/java-library-conventions.gradle.kts b/clients/java/buildSrc/src/main/kotlin/java-library-conventions.gradle.kts new file mode 100644 index 000000000..8729ada75 --- /dev/null +++ b/clients/java/buildSrc/src/main/kotlin/java-library-conventions.gradle.kts @@ -0,0 +1,12 @@ +plugins { + `java-library` +} + +java { + // Can't use this as provider is a mixed project. + // withJavadocJar() + withSourcesJar() + toolchain { + languageVersion.set(JavaLanguageVersion.of(17)) + } +} diff --git a/clients/java/buildSrc/src/main/kotlin/publishing-conventions.gradle.kts b/clients/java/buildSrc/src/main/kotlin/publishing-conventions.gradle.kts new file mode 100644 index 000000000..bd7fc4f9c --- /dev/null +++ b/clients/java/buildSrc/src/main/kotlin/publishing-conventions.gradle.kts @@ -0,0 +1,68 @@ +import org.gradle.api.publish.maven.MavenPublication +import org.gradle.kotlin.dsl.extra +import org.gradle.kotlin.dsl.provideDelegate + +plugins { + `maven-publish` + signing +} + +publishing { + // Add license spec to all maven publications + publications { + afterEvaluate { + create("maven") { + from(components["java"]) + val displayName: String by extra + pom { + name.set(displayName) + description.set(project.description) + url.set("https://github.com/juspay/superposition") + licenses { + license { + name.set("Apache License 2.0") + url.set("http://www.apache.org/licenses/LICENSE-2.0.txt") + } + } + developers { + developer { + id.set("superposition") + name.set("superposition") + email.set("superposition@juspay.in") + organization.set("Juspay") + organizationUrl.set("https://juspay.io") + roles.add("developer") + } + } + scm { + connection.set("https://github.com/juspay/superposition.git") + developerConnection.set("https://github.com/juspay/superposition.git") + url.set("https://github.com/juspay/superposition.git") + } + } + } + } + } + repositories { + maven { + url = uri(layout.buildDirectory.dir("staging-deploy")) + } + } +} + +signing { + setRequired { + // signing is required only if the artifacts are to be published to a maven repository + gradle.taskGraph.allTasks.any { it is PublishToMavenRepository } + } + + // Don't sign the artifacts if we didn't get a key and password to use. + if (project.hasProperty("signingKey") && project.hasProperty("signingPassword")) { + signing { + useInMemoryPgpKeys( + project.properties["signingKey"].toString(), + project.properties["signingPassword"].toString()) + sign(publishing.publications["maven"]) + } + } +} diff --git a/clients/java/open-feature-provider/build.gradle.kts b/clients/java/open-feature-provider/build.gradle.kts index f38fc7a28..adf97e194 100644 --- a/clients/java/open-feature-provider/build.gradle.kts +++ b/clients/java/open-feature-provider/build.gradle.kts @@ -1,19 +1,13 @@ plugins { - `java-library` - `maven-publish` + `java-library-conventions` + `publishing-conventions` kotlin("jvm") version "1.9.10" id("io.freefair.lombok") version "8.6" } -java { - toolchain { - languageVersion = JavaLanguageVersion.of(17) - } -} - -kotlin { - jvmToolchain(17) -} +group = "${rootProject.group}.openfeature" +extra["displayName"] = "Superposition Openfeature Provider" +description = "Openfeature provider implementation for Superposition." dependencies { implementation(project(":bindings")) @@ -43,13 +37,3 @@ tasks.test { showStandardStreams = true } } - -publishing { - publications { - create("maven") { - groupId = "${rootProject.group}.openfeature" - artifactId = "superposition-provider" - from(components["java"]) - } - } -} diff --git a/clients/java/sdk/build.gradle.kts b/clients/java/sdk/build.gradle.kts index 2465d041f..aa0f9e608 100644 --- a/clients/java/sdk/build.gradle.kts +++ b/clients/java/sdk/build.gradle.kts @@ -1,24 +1,13 @@ plugins { - `java-library` - `maven-publish` + `java-library-conventions` + `publishing-conventions` } -java { - toolchain { - languageVersion = JavaLanguageVersion.of(17) - } -} +extra["displayName"] = "Superposition SDK" +description = "Java SDK for Superposition." dependencies { implementation("software.amazon.smithy.java:client-core:0.0.1") implementation("software.amazon.smithy:smithy-aws-traits:1.55.0") implementation("software.amazon.smithy.java:aws-client-restjson:0.0.1") } - -publishing { - publications { - create("maven") { - from(components["java"]) - } - } -} diff --git a/clients/java/sdk/src/main/java/io/juspay/superposition/model/OptionalBucket.java b/clients/java/sdk/src/main/java/io/juspay/superposition/model/OptionalBucket.java deleted file mode 100644 index 0b7263105..000000000 --- a/clients/java/sdk/src/main/java/io/juspay/superposition/model/OptionalBucket.java +++ /dev/null @@ -1,244 +0,0 @@ - -package io.juspay.superposition.model; - -import java.util.Objects; -import software.amazon.smithy.java.core.schema.Schema; -import software.amazon.smithy.java.core.schema.SchemaUtils; -import software.amazon.smithy.java.core.schema.SerializableStruct; -import software.amazon.smithy.java.core.schema.ShapeBuilder; -import software.amazon.smithy.java.core.schema.Unit; -import software.amazon.smithy.java.core.serde.ShapeDeserializer; -import software.amazon.smithy.java.core.serde.ShapeSerializer; -import software.amazon.smithy.java.core.serde.ToStringSerializer; -import software.amazon.smithy.model.shapes.ShapeId; -import software.amazon.smithy.utils.SmithyGenerated; - -@SmithyGenerated -public abstract class OptionalBucket implements SerializableStruct { - public static final ShapeId $ID = ShapeId.from("io.superposition#OptionalBucket"); - - public static final Schema $SCHEMA = Schema.unionBuilder($ID) - .putMember("bucket", Bucket.$SCHEMA) - .putMember("null", Unit.SCHEMA) - .build(); - - private static final Schema $SCHEMA_BUCKET = $SCHEMA.member("bucket"); - private static final Schema $SCHEMA_NULL = $SCHEMA.member("null"); - - private final Type type; - - private OptionalBucket(Type type) { - this.type = type; - } - - public Type type() { - return type; - } - - /** - * Enum representing the possible variants of {@link OptionalBucket}. - */ - public enum Type { - $UNKNOWN, - bucket, - null - } - - @Override - public String toString() { - return ToStringSerializer.serialize(this); - } - - @Override - public Schema schema() { - return $SCHEMA; - } - - @Override - public T getMemberValue(Schema member) { - return SchemaUtils.validateMemberInSchema($SCHEMA, member, getValue()); - } - - public abstract T getValue(); - - @SmithyGenerated - public static final class BucketMember extends OptionalBucket { - private final transient Bucket value; - - public BucketMember(Bucket value) { - super(Type.bucket); - this.value = Objects.requireNonNull(value, "Union value cannot be null"); - } - - @Override - public void serializeMembers(ShapeSerializer serializer) { - serializer.writeStruct($SCHEMA_BUCKET, value); - } - - public Bucket bucket() { - return value; - } - - @Override - @SuppressWarnings("unchecked") - public T getValue() { - return (T) value; - } - } - - @SmithyGenerated - public static final class NullMember extends OptionalBucket { - - public NullMember() { - super(Type.null); - } - - @Override - public void serializeMembers(ShapeSerializer serializer) { - serializer.writeStruct($SCHEMA_NULL, Unit.getInstance()); - } - - @Override - @SuppressWarnings("unchecked") - public T getValue() { - return (T) null; - } - } - - public static final class $UnknownMember extends OptionalBucket { - private final String memberName; - - public $UnknownMember(String memberName) { - super(Type.$UNKNOWN); - this.memberName = memberName; - } - - public String memberName() { - return memberName; - } - - @Override - public void serialize(ShapeSerializer serializer) { - throw new UnsupportedOperationException("Cannot serialize union with unknown member " + this.memberName); - } - - @Override - public void serializeMembers(ShapeSerializer serializer) {} - - @Override - @SuppressWarnings("unchecked") - public T getValue() { - return (T) memberName; - } - } - - @Override - public int hashCode() { - return Objects.hash(type, getValue()); - } - - @Override - public boolean equals(Object other) { - if (other == this) { - return true; - } - if (other == null || getClass() != other.getClass()) { - return false; - } - return Objects.equals(getValue(), ((OptionalBucket) other).getValue()); - } - - public interface BuildStage { - OptionalBucket build(); - } - - /** - * @return returns a new Builder. - */ - public static Builder builder() { - return new Builder(); - } - - /** - * Builder for {@link OptionalBucket}. - */ - public static final class Builder implements ShapeBuilder, BuildStage { - private OptionalBucket value; - - private Builder() {} - - @Override - public Schema schema() { - return $SCHEMA; - } - - public BuildStage bucket(Bucket value) { - return setValue(new BucketMember(value)); - } - - public BuildStage null(Unit value) { - return setValue(new NullMember()); - } - - public BuildStage $unknownMember(String memberName) { - return setValue(new $UnknownMember(memberName)); - } - - private BuildStage setValue(OptionalBucket value) { - if (this.value != null) { - if (this.value.type() == Type.$UNKNOWN) { - throw new IllegalArgumentException("Cannot change union from unknown to known variant"); - } - throw new IllegalArgumentException("Only one value may be set for unions"); - } - this.value = value; - return this; - } - - @Override - public OptionalBucket build() { - return Objects.requireNonNull(value, "no union value set"); - } - - @Override - @SuppressWarnings("unchecked") - public void setMemberValue(Schema member, Object value) { - switch (member.memberIndex()) { - case 0 -> bucket((Bucket) SchemaUtils.validateSameMember($SCHEMA_BUCKET, member, value)); - case 1 -> null((Unit) SchemaUtils.validateSameMember($SCHEMA_NULL, member, value)); - default -> ShapeBuilder.super.setMemberValue(member, value); - } - } - - @Override - public Builder deserialize(ShapeDeserializer decoder) { - decoder.readStruct($SCHEMA, this, $InnerDeserializer.INSTANCE); - return this; - } - - @Override - public Builder deserializeMember(ShapeDeserializer decoder, Schema schema) { - decoder.readStruct(schema.assertMemberTargetIs($SCHEMA), this, $InnerDeserializer.INSTANCE); - return this; - } - - private static final class $InnerDeserializer implements ShapeDeserializer.StructMemberConsumer { - private static final $InnerDeserializer INSTANCE = new $InnerDeserializer(); - - @Override - public void accept(Builder builder, Schema member, ShapeDeserializer de) { - switch (member.memberIndex()) { - case 0 -> builder.bucket(Bucket.builder().deserializeMember(de, member).build()); - case 1 -> builder.null(Unit.builder().deserializeMember(de, member).build()); - default -> throw new IllegalArgumentException("Unexpected member: " + member.memberName()); - } - } - - @Override - public void unknownMember(Builder builder, String memberName) { - builder.$unknownMember(memberName); - } - } - } -} -