diff --git a/.github/workflows/build-artifacts.yml b/.github/workflows/build-artifacts.yml index d4be0f1..b25a86e 100644 --- a/.github/workflows/build-artifacts.yml +++ b/.github/workflows/build-artifacts.yml @@ -103,6 +103,7 @@ jobs: java-distribution: ${{ matrix.java-distribution || needs.get-configs.outputs.java-distribution }} kotlin-version: ${{ needs.get-configs.outputs.kotlin-version }} ktlint-version: ${{ needs.get-configs.outputs.ktlint-version }} + gradle-version: ${{ needs.get-configs.outputs.gradle-version }} - name: Log tool versions uses: ./.github/actions/print-build-versions diff --git a/.github/workflows/configs.yml b/.github/workflows/configs.yml index e119212..bbad421 100644 --- a/.github/workflows/configs.yml +++ b/.github/workflows/configs.yml @@ -24,6 +24,9 @@ on: kotlin-version: description: "Kotlin compiler (kotlinc) version" value: ${{ jobs.get-configs.outputs.kotlin-version }} + dokka-version: + description: "Dokka version for the JVM binding Javadoc jar" + value: ${{ jobs.get-configs.outputs.dokka-version }} ktlint-version: description: "ktlint version" value: ${{ jobs.get-configs.outputs.ktlint-version }} @@ -66,6 +69,7 @@ jobs: JAVA_VERSION: '21' JAVA_DISTRIBUTION: 'corretto' KOTLIN_VERSION: '2.4.0' + DOKKA_VERSION: '1.9.20' # keep in sync with bindings-jvm/build.gradle.kts KTLINT_VERSION: '1.8.0' GRADLE_VERSION: '8.14' JNA_VERSION: '5.19.1' # keep in sync with bindings-jvm/build.sh @@ -83,6 +87,7 @@ jobs: java-version: ${{ env.JAVA_VERSION }} java-distribution: ${{ env.JAVA_DISTRIBUTION }} kotlin-version: ${{ env.KOTLIN_VERSION }} + dokka-version: ${{ env.DOKKA_VERSION }} ktlint-version: ${{ env.KTLINT_VERSION }} gradle-version: ${{ env.GRADLE_VERSION }} jna-version: ${{ env.JNA_VERSION }} @@ -102,6 +107,7 @@ jobs: echo "java-version = ${{ env.JAVA_VERSION }}" echo "java-distribution = ${{ env.JAVA_DISTRIBUTION }}" echo "kotlin-version = ${{ env.KOTLIN_VERSION }}" + echo "dokka-version = ${{ env.DOKKA_VERSION }}" echo "ktlint-version = ${{ env.KTLINT_VERSION }}" echo "gradle-version = ${{ env.GRADLE_VERSION }}" echo "jna-version = ${{ env.JNA_VERSION }}" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 053e0a7..e328d29 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -196,6 +196,26 @@ jobs: printf '%s\n' "$fingerprint" > release/signing-key.pem.sha256 echo "public key SHA-256: $fingerprint" + - name: Configure AWS credentials for Maven signing key + if: ${{ !contains(inputs.tag, '-beta') }} + uses: aws-actions/configure-aws-credentials@v6.2.0 + with: + role-to-assume: ${{ secrets.MAVEN_SIGNING_ROLE_ARN }} + aws-region: ${{ secrets.AWS_REGION }} + + - name: Add Maven PGP public key to release assets + if: ${{ !contains(inputs.tag, '-beta') }} + env: + MAVEN_GPG_SECRET_ID: ${{ secrets.MAVEN_GPG_SECRET_ID }} + run: | + set -euo pipefail + secret_json="$(aws secretsmanager get-secret-value \ + --secret-id "$MAVEN_GPG_SECRET_ID" --query SecretString --output text)" + key="release/cloudformation-validate-maven-signing-key.asc" + printf '%s' "$secret_json" | jq -r '.publicKey' > "$key" + printf '%s' "$secret_json" | jq -r '.fingerprint' > "$key.fingerprint" + echo "Maven PGP public key fingerprint: $(cat "$key.fingerprint")" + - name: Create GitHub Release uses: softprops/action-gh-release@v3 with: @@ -205,6 +225,101 @@ jobs: files: release/* fail_on_unmatched_files: true + publish-maven: + needs: [ get-configs, version-and-tag, release ] + if: ${{ !contains(inputs.tag, '-beta') }} + runs-on: ubuntu-latest + environment: release-maven + permissions: + contents: read + id-token: write + env: + WORKING_DIR: ${{ needs.get-configs.outputs.working-dir }} + steps: + - uses: actions/checkout@v6 + with: + ref: ${{ inputs.tag }} + + - name: Setup JVM toolchain + uses: ./.github/actions/setup-jvm-toolchain + with: + java-version: ${{ needs.get-configs.outputs.java-version }} + java-distribution: ${{ needs.get-configs.outputs.java-distribution }} + kotlin-version: ${{ needs.get-configs.outputs.kotlin-version }} + ktlint-version: ${{ needs.get-configs.outputs.ktlint-version }} + gradle-version: ${{ needs.get-configs.outputs.gradle-version }} + + - name: Verify committed jar is present and multi-platform + working-directory: ${{ env.WORKING_DIR }}/bindings-jvm + run: | + set -euo pipefail + jar="generated/cloudformation-validate.jar" + if [ ! -f "$jar" ]; then + echo "::error::Missing $jar. Run the Build Artifacts workflow and merge its PR first." + exit 1 + fi + for prefix in linux-x86-64 darwin-aarch64 win32-x86-64; do + if ! unzip -Z1 "$jar" | grep -q "^$prefix/"; then + echo "::error::native library for $prefix missing from committed jar; the all-platform merge did not run" + exit 1 + fi + done + echo "Committed jar carries all three platform natives." + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v6.2.0 + with: + role-to-assume: ${{ secrets.MAVEN_SIGNING_ROLE_ARN }} + aws-region: ${{ secrets.AWS_REGION }} + + - name: Fetch PGP signing key + id: signing-secrets + env: + MAVEN_GPG_SECRET_ID: ${{ secrets.MAVEN_GPG_SECRET_ID }} + run: | + set -euo pipefail + + secret_json="$(aws secretsmanager get-secret-value \ + --secret-id "$MAVEN_GPG_SECRET_ID" --query SecretString --output text)" + { + echo "signing_key<<__EOF_KEY__" + printf '%s' "$secret_json" | jq -r '.privateKey' + echo "__EOF_KEY__" + echo "signing_password<<__EOF_PASS__" + printf '%s' "$secret_json" | jq -r '.passphrase' + echo "__EOF_PASS__" + } >> "$GITHUB_OUTPUT" + + - name: Build signed Central Portal bundle + working-directory: ${{ env.WORKING_DIR }}/bindings-jvm + env: + ORG_GRADLE_PROJECT_signingKey: ${{ steps.signing-secrets.outputs.signing_key }} + ORG_GRADLE_PROJECT_signingPassword: ${{ steps.signing-secrets.outputs.signing_password }} + run: | + set -euo pipefail + gradle --no-daemon --console=plain centralBundle + ls -lh build/central/ + + - name: Verify every artifact is signed + working-directory: ${{ env.WORKING_DIR }}/bindings-jvm + run: | + set -euo pipefail + bundle="$(ls build/central/*-bundle.zip)" + tmp="$(mktemp -d)" + unzip -qo "$bundle" -d "$tmp" + base="$tmp/software/amazon/cloudformation/cloudformation-validate/${{ inputs.tag }}" + missing=0 + for suffix in .jar -sources.jar -javadoc.jar .pom; do + f="cloudformation-validate-${{ inputs.tag }}${suffix}" + for companion in asc md5 sha1; do + if [ ! -f "$base/$f.$companion" ]; then + echo "::error::missing $f.$companion in bundle" + missing=1 + fi + done + done + [ "$missing" -eq 0 ] && echo "All artifacts carry .asc/.md5/.sha1." || exit 1 + publish-npm: needs: [ get-configs, version-and-tag, release ] runs-on: ubuntu-latest diff --git a/README.md b/README.md index 5d4fe6a..36ef44a 100644 --- a/README.md +++ b/README.md @@ -121,7 +121,7 @@ engine.free(); ### JVM (Kotlin) ```kotlin -import com.amazonaws.cloudformation.validation.* +import software.amazon.cloudformation.validate.* import java.io.File val engine = RegoEngine() diff --git a/src/bindings-jvm/README.md b/src/bindings-jvm/README.md index 2fca210..fd178a6 100644 --- a/src/bindings-jvm/README.md +++ b/src/bindings-jvm/README.md @@ -4,7 +4,7 @@ JVM bindings for [`cloudformation-validate`](https://github.com/aws-cloudformati via [UniFFI](https://mozilla.github.io/uniffi-rs/). Compiles the full validation pipeline into a native shared library with auto-generated Kotlin bindings, packaged as a JAR with bundled natives. -All types live in `com.amazonaws.cloudformation.validation`. +All types live in `software.amazon.cloudformation.validate`. For a complete, runnable example, see [examples](https://github.com/aws-cloudformation/cloudformation-validate/tree/main/src/bindings-jvm/examples). @@ -15,7 +15,7 @@ For a complete, runnable example, see diagnostics for the same template and config. ```kotlin -import com.amazonaws.cloudformation.validation.* +import software.amazon.cloudformation.validate.* import java.io.File val engine = RegoEngine() diff --git a/src/bindings-jvm/bench/settings.gradle.kts b/src/bindings-jvm/bench/settings.gradle.kts index 366bb02..f8cd227 100644 --- a/src/bindings-jvm/bench/settings.gradle.kts +++ b/src/bindings-jvm/bench/settings.gradle.kts @@ -1 +1 @@ -rootProject.name = "bindings-jvm-bench" +rootProject.name = "cloudformation-validate-bench" diff --git a/src/bindings-jvm/bench/src/main/kotlin/Benchmark.kt b/src/bindings-jvm/bench/src/main/kotlin/Benchmark.kt index 6e60edd..6f8950e 100644 --- a/src/bindings-jvm/bench/src/main/kotlin/Benchmark.kt +++ b/src/bindings-jvm/bench/src/main/kotlin/Benchmark.kt @@ -1,15 +1,15 @@ -import com.amazonaws.cloudformation.validation.JvmCelEngine -import com.amazonaws.cloudformation.validation.JvmRegoEngine -import com.amazonaws.cloudformation.validation.JvmSemanticModel -import com.amazonaws.cloudformation.validation.SchemaValidator -import com.amazonaws.cloudformation.validation.ValidateConfig -import com.amazonaws.cloudformation.validation.diagnostics.DetailedReport -import com.amazonaws.cloudformation.validation.engine.EngineConfig -import com.amazonaws.cloudformation.validation.gson.buildBindingsGson -import com.amazonaws.cloudformation.validation.rules.RuleFilterConfig -import com.amazonaws.cloudformation.validation.rules.Severity -import com.amazonaws.cloudformation.validation.templatemodel.PseudoParameterOverrides -import com.amazonaws.cloudformation.validation.version +import software.amazon.cloudformation.validate.JvmCelEngine +import software.amazon.cloudformation.validate.JvmRegoEngine +import software.amazon.cloudformation.validate.JvmSemanticModel +import software.amazon.cloudformation.validate.SchemaValidator +import software.amazon.cloudformation.validate.ValidateConfig +import software.amazon.cloudformation.validate.diagnostics.DetailedReport +import software.amazon.cloudformation.validate.engine.EngineConfig +import software.amazon.cloudformation.validate.gson.buildBindingsGson +import software.amazon.cloudformation.validate.rules.RuleFilterConfig +import software.amazon.cloudformation.validate.rules.Severity +import software.amazon.cloudformation.validate.templatemodel.PseudoParameterOverrides +import software.amazon.cloudformation.validate.version import com.google.gson.JsonArray import com.google.gson.JsonObject import java.io.File diff --git a/src/bindings-jvm/build.gradle.kts b/src/bindings-jvm/build.gradle.kts new file mode 100644 index 0000000..3691318 --- /dev/null +++ b/src/bindings-jvm/build.gradle.kts @@ -0,0 +1,256 @@ +import java.util.Properties +import org.jetbrains.dokka.gradle.DokkaTask + +plugins { + kotlin("jvm") version "2.4.0" // keep in sync with configs.yml kotlin-version + `maven-publish` + signing + id("org.jetbrains.dokka") version "1.9.20" // keep in sync with configs.yml dokka-version +} + +// ── Coordinates + bundled runtime dependency versions. +// Static publishing identity (groupId, artifactId) lives in gradle.properties. +// The version and bundled dependency versions come from version.properties, which +// build.sh generates from Cargo.toml (the single source of truth) so they cannot +// drift. Every value is required: the build fails loudly if one is missing rather +// than substituting a baked-in default. A -P override still wins over both files. +val generatedVersions = + Properties().apply { + val file = layout.projectDirectory.file("version.properties").asFile + if (file.exists()) file.inputStream().use { load(it) } + } + +fun requiredProperty(name: String): String = + (providers.gradleProperty(name).orNull ?: generatedVersions.getProperty(name)) + .takeIf { !it.isNullOrBlank() } + ?: error( + "Required property '$name' is not defined. Static coordinates live in gradle.properties; " + + "version/dependency versions are generated into version.properties by build.sh (run ./build.sh first) " + + "or passed with -P$name=.", + ) + +val publishGroupId = requiredProperty("publishGroupId") +val publishArtifactId = requiredProperty("publishArtifactId") +val publishVersion = requiredProperty("publishVersion") +val jnaVersion = requiredProperty("jnaVersion") +val gsonVersion = requiredProperty("gsonVersion") + +group = publishGroupId +version = publishVersion + +repositories { + mavenCentral() +} + +dependencies { + implementation("net.java.dev.jna:jna:$jnaVersion") + implementation("com.google.code.gson:gson:$gsonVersion") +} + +kotlin { + jvmToolchain(21) // keep in sync with configs.yml java-version +} + +// ── Source layout ─────────────────────────────────────────────────────────────── +// build.sh writes uniffi-generated + hand-maintained sources into generated/, and +// the host native library into generated/natives/-/. Point the main source +// set at generated/ so `gradle jar` compiles exactly what build.sh produced. +val generatedDir = layout.projectDirectory.dir("generated") +val nativesDir = generatedDir.dir("natives") +val mergedJar = generatedDir.file("cloudformation-validate.jar") +val repoLicense = layout.projectDirectory.file("../../LICENSE") +val repoNotice = layout.projectDirectory.file("../../NOTICE") +val thirdPartyLicenses = layout.projectDirectory.file("THIRD-PARTY-LICENSES.txt") +val bindingReadme = layout.projectDirectory.file("README.md") + +sourceSets { + main { + kotlin.setSrcDirs(listOf(generatedDir)) + // generated/ also holds the emitted jar and staged natives — only .kt is source. + kotlin.exclude("**/*.jar", "natives/**") + } +} + +// The main jar written to generated/cloudformation-validate.jar, matching the name and +// contents build.sh previously produced by hand: classes + .kt sources (IDE nav) + +// host native under the JNA -/ path + license/readme metadata. +tasks.named("jar") { + archiveFileName.set("cloudformation-validate.jar") + destinationDirectory.set(generatedDir) + includeEmptyDirs = false + + from(generatedDir) { + include("**/*.kt") // bundle sources for IDE navigation, mirroring the old build + } + from(nativesDir) // contents land at the jar root as -/lib*.{dylib,so,dll} + from(repoLicense) { into("META-INF") } + from(bindingReadme) { into("META-INF") } + from(thirdPartyLicenses) { into("META-INF") } + + manifest { + attributes( + "Implementation-Title" to "cloudformation-validate", + "Implementation-Version" to publishVersion, + "Implementation-Vendor" to "Amazon Web Services (AWS)", + "License" to "Apache-2.0", + "Requires" to "net.java.dev.jna:jna:$jnaVersion, com.google.code.gson:gson:$gsonVersion", + ) + } +} + +// ── Javadoc via Dokka ─────────────────────────────────────────────────────────── +// Dokka documents the Kotlin API. At a release checkout the generated sources are gone +// (only the merged jar remains), so re-extract the .kt from the merged jar into a +// scratch dir and point Dokka at it. When build.sh just ran, generated/ still holds the +// same sources; using the jar keeps a single, checkout-independent source of truth. +val dokkaSources = layout.buildDirectory.dir("dokka-sources") +val extractDokkaSources by tasks.registering(Sync::class) { + description = "Extracts the bundled Kotlin sources from the merged jar for Dokka." + onlyIf { + mergedJar.asFile.exists().also { + if (!it) logger.warn("Merged jar ${mergedJar.asFile} absent; Dokka has no sources to document.") + } + } + from({ zipTree(mergedJar) }) { include("**/*.kt") } + into(dokkaSources) +} + +tasks.withType().configureEach { + dependsOn(extractDokkaSources) + dokkaSourceSets.configureEach { + sourceRoots.setFrom(dokkaSources) + classpath.from(configurations.named("compileClasspath")) + jdkVersion.set(21) + reportUndocumented.set(false) + skipEmptyPackages.set(true) + } +} + +val javadocJar by tasks.registering(Jar::class) { + archiveClassifier.set("javadoc") + from(tasks.named("dokkaJavadoc").flatMap { it.outputDirectory }) + from(repoLicense) { into("META-INF") } + from(repoNotice) { into("META-INF") } +} + +// Sources jar: re-extract the .kt the merged jar bundles, so it works at a release +// checkout where the generated sources are absent from disk (only the jar is committed). +val sourcesJar by tasks.registering(Jar::class) { + archiveClassifier.set("sources") + includeEmptyDirs = false + onlyIf { mergedJar.asFile.exists() } + from({ zipTree(mergedJar) }) { include("**/*.kt") } + from(repoLicense) { into("META-INF") } + from(repoNotice) { into("META-INF") } +} + +// ── Publication ───────────────────────────────────────────────────────────────── +publishing { + publications { + create("maven") { + groupId = publishGroupId + artifactId = publishArtifactId + version = publishVersion + + artifact(mergedJar) { extension = "jar" } + artifact(sourcesJar) + artifact(javadocJar) + + pom { + name.set("CloudFormation Validate") + description.set( + "Fast, offline validator for AWS CloudFormation templates. JVM (Kotlin/Java) " + + "bindings over the shared Rust core, with bundled native libraries.", + ) + url.set("https://github.com/aws-cloudformation/cloudformation-validate") + licenses { + license { + name.set("Apache License, Version 2.0") + url.set("https://www.apache.org/licenses/LICENSE-2.0.txt") + distribution.set("repo") + } + } + developers { + developer { + id.set("aws-cloudformation") + name.set("AWS CloudFormation") + organization.set("Amazon Web Services") + organizationUrl.set("https://aws.amazon.com") + } + } + scm { + connection.set("scm:git:https://github.com/aws-cloudformation/cloudformation-validate.git") + developerConnection.set("scm:git:ssh://git@github.com/aws-cloudformation/cloudformation-validate.git") + url.set("https://github.com/aws-cloudformation/cloudformation-validate") + } + // No software component is attached (the published jar is the prebuilt + // merged jar, not this project's compiled output), so the consumer runtime + // dependencies are written into the POM explicitly. + withXml { + val dependencies = asNode().appendNode("dependencies") + fun runtimeDependency(group: String, name: String, dependencyVersion: String) { + dependencies.appendNode("dependency").apply { + appendNode("groupId", group) + appendNode("artifactId", name) + appendNode("version", dependencyVersion) + appendNode("scope", "runtime") + } + } + runtimeDependency("net.java.dev.jna", "jna", jnaVersion) + runtimeDependency("com.google.code.gson", "gson", gsonVersion) + } + } + } + } + + // Local Maven-layout staging directory. Publishing here produces the signed + // artifacts plus their .md5/.sha1 checksums under the standard groupId path, + // which the centralBundle task zips into the Portal upload archive. + repositories { + maven { + name = "staging" + url = uri(layout.buildDirectory.dir("staging-deploy")) + } + } +} + +// The published main artifact is the prebuilt merged jar, not this project's own jar +// task output — guard against publishing an accidentally host-only jar. +fun requireMergedJar() = + require(mergedJar.asFile.exists()) { + "Merged jar not found at ${mergedJar.asFile}. Run ./build.sh (and merge-jars.sh in CI) first." + } + +tasks.withType().configureEach { + doFirst { requireMergedJar() } +} + +// ── PGP signing — required by Maven Central, engaged only when a key is present ── +// signingKey / signingPassword resolve from -PsigningKey=... or the environment +// variables ORG_GRADLE_PROJECT_signingKey / ORG_GRADLE_PROJECT_signingPassword. +// signingKey must be the full ASCII-armored private key block. +val signingKey = findProperty("signingKey") as String? +val signingPassword = (findProperty("signingPassword") as String?) ?: "" +signing { + setRequired { signingKey != null } + if (signingKey != null) { + useInMemoryPgpKeys(signingKey, signingPassword) + sign(publishing.publications["maven"]) + } +} + +// ── Central Publisher Portal upload bundle ────────────────────────────────────── +// Zips the staged Maven layout into a single archive whose internal folder structure +// follows the Maven Repository Layout, as the Portal upload API requires. +// maven-metadata.* files are repository bookkeeping the Portal does not accept. +val centralBundle by tasks.registering(Zip::class) { + group = "publishing" + description = "Assembles the Central Publisher Portal upload bundle from the staged Maven layout." + dependsOn("publishMavenPublicationToStagingRepository") + from(layout.buildDirectory.dir("staging-deploy")) { exclude("**/maven-metadata.*") } + destinationDirectory.set(layout.buildDirectory.dir("central")) + archiveFileName.set("$publishArtifactId-$publishVersion-bundle.zip") + doLast { + logger.lifecycle("Central Portal bundle: ${archiveFile.get().asFile}") + } +} diff --git a/src/bindings-jvm/build.sh b/src/bindings-jvm/build.sh index ff04cab..16dc336 100755 --- a/src/bindings-jvm/build.sh +++ b/src/bindings-jvm/build.sh @@ -12,15 +12,11 @@ done SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" WORKSPACE="$(cd "$SCRIPT_DIR/.." && pwd)" GENERATED_DIR="$SCRIPT_DIR/generated" -BUILD_DIR="$SCRIPT_DIR/build" RELEASE_DIR="$WORKSPACE/target/release" KOTLIN_SRC="$SCRIPT_DIR/src/main/kotlin" JNA_VERSION="5.19.1" -JNA_MAVEN_URL="https://repo1.maven.org/maven2/net/java/dev/jna/jna/${JNA_VERSION}/jna-${JNA_VERSION}.jar" - GSON_VERSION="2.14.0" -GSON_MAVEN_URL="https://repo1.maven.org/maven2/com/google/code/gson/gson/${GSON_VERSION}/gson-${GSON_VERSION}.jar" ARCH="$(uname -m)" # Normalize to JNA's resource-prefix arch tokens (its canonical form) @@ -36,7 +32,7 @@ case "$(uname -s)" in *) echo "Unsupported platform: $(uname -s)" >&2; exit 1 ;; esac -NATIVES_DIR="$BUILD_DIR/classes/${OS}-${ARCH}" +NATIVES_DIR="$GENERATED_DIR/natives/${OS}-${ARCH}" JAR_FILE="$GENERATED_DIR/cloudformation-validate.jar" cat </dev/null || { echo "Error: ktlint not found on PATH" >&2; exit 1; } -command -v kotlinc &>/dev/null || { echo "Error: kotlinc not found on PATH" >&2; exit 1; } -command -v jar &>/dev/null || { echo "Error: jar not found on PATH" >&2; exit 1; } +command -v gradle &>/dev/null || { echo "Error: gradle not found on PATH" >&2; exit 1; } JAVA_VERSION=$(java -version 2>&1 | head -1 | sed -E 's/.*"([0-9]+).*/\1/') if [ "$JAVA_VERSION" -lt 21 ]; then @@ -62,8 +56,8 @@ fi # ── Clean ───────────────────────────────────────────────────────────────────── echo "Cleaning previous build..." -rm -rf "$GENERATED_DIR" "$BUILD_DIR" -mkdir -p "$GENERATED_DIR" "$BUILD_DIR/classes" +rm -rf "$GENERATED_DIR" +mkdir -p "$GENERATED_DIR" # ── Build native library ───────────────────────────────────────────────────── echo "Building native library..." @@ -90,51 +84,35 @@ echo "Formatting Kotlin sources..." cd "$SCRIPT_DIR" ktlint --format "generated/**/*.kt" -# ── Download JNA jar ───────────────────────────────────────────────────────── -echo "Downloading JNA ${JNA_VERSION}..." -JNA_JAR="$BUILD_DIR/jna-${JNA_VERSION}.jar" -curl -sfL "$JNA_MAVEN_URL" -o "$JNA_JAR" - -# ── Download Gson jar ──────────────────────────────────────────────────────── -echo "Downloading Gson ${GSON_VERSION}..." -GSON_JAR="$BUILD_DIR/gson-${GSON_VERSION}.jar" -curl -sfL "$GSON_MAVEN_URL" -o "$GSON_JAR" - -# ── Compile Kotlin sources ─────────────────────────────────────────────────── -echo "Compiling Kotlin bindings..." -find "$GENERATED_DIR" -name '*.kt' -type f -print0 \ - | xargs -0 kotlinc -classpath "$JNA_JAR:$GSON_JAR" -d "$BUILD_DIR/classes" -nowarn - -# ── Package JAR ────────────────────────────────────────────────────────────── -# Bundle native library at the JNA auto-extract path: -/ +# ── Stage native library ────────────────────────────────────────────────────── +# Place the host native at the JNA auto-extract path generated/natives/-/ +# so the Gradle jar task bundles it. In CI, merge-jars.sh later grafts the other +# platforms' natives into the committed all-platform jar. +echo "Staging native library..." +rm -rf "$GENERATED_DIR/natives" mkdir -p "$NATIVES_DIR" cp "$RELEASE_DIR/$LIB_NAME" "$NATIVES_DIR/" -# Bundle Kotlin sources for IDE navigation -find "$GENERATED_DIR" -name '*.kt' -type f | while read -r kt; do - REL="${kt#"$GENERATED_DIR/"}" - mkdir -p "$BUILD_DIR/classes/$(dirname "$REL")" - cp "$kt" "$BUILD_DIR/classes/$REL" -done - -mkdir -p "$BUILD_DIR/classes/META-INF" -cp "$WORKSPACE/../LICENSE" "$BUILD_DIR/classes/META-INF/LICENSE" - -# Create manifest with version and dependency info +# ── Generate version.properties ──────────────────────────────────────────────── +# Cargo.toml is the single source of truth for the version; JNA/Gson versions are +# owned here. The Gradle build reads these (build.gradle.kts) for the coordinates and +# POM dependency versions, so nothing is hardcoded and versions cannot drift. VERSION=$(grep '^version' "$WORKSPACE/Cargo.toml" | head -1 | sed 's/.*"\(.*\)".*/\1/') -cat > "$BUILD_DIR/MANIFEST.MF" < "$SCRIPT_DIR/version.properties" <) { diff --git a/src/bindings-jvm/gradle.properties b/src/bindings-jvm/gradle.properties new file mode 100644 index 0000000..c351915 --- /dev/null +++ b/src/bindings-jvm/gradle.properties @@ -0,0 +1,2 @@ +publishGroupId=software.amazon.cloudformation +publishArtifactId=cloudformation-validate diff --git a/src/bindings-jvm/settings.gradle.kts b/src/bindings-jvm/settings.gradle.kts new file mode 100644 index 0000000..1cab878 --- /dev/null +++ b/src/bindings-jvm/settings.gradle.kts @@ -0,0 +1 @@ +rootProject.name = "cloudformation-validate" diff --git a/src/bindings-jvm/src/main/kotlin/com/amazonaws/cloudformation/validation/Api.kt b/src/bindings-jvm/src/main/kotlin/software/amazon/cloudformation/validate/Api.kt similarity index 86% rename from src/bindings-jvm/src/main/kotlin/com/amazonaws/cloudformation/validation/Api.kt rename to src/bindings-jvm/src/main/kotlin/software/amazon/cloudformation/validate/Api.kt index d9c5d40..9ee3891 100644 --- a/src/bindings-jvm/src/main/kotlin/com/amazonaws/cloudformation/validation/Api.kt +++ b/src/bindings-jvm/src/main/kotlin/software/amazon/cloudformation/validate/Api.kt @@ -1,10 +1,10 @@ -package com.amazonaws.cloudformation.validation +package software.amazon.cloudformation.validate -import com.amazonaws.cloudformation.validation.diagnostics.DetailedReport -import com.amazonaws.cloudformation.validation.diagnostics.StandardDiagnostic -import com.amazonaws.cloudformation.validation.diagnostics.StandardReport -import com.amazonaws.cloudformation.validation.engine.EngineConfig -import com.amazonaws.cloudformation.validation.rules.RuleInfo +import software.amazon.cloudformation.validate.diagnostics.DetailedReport +import software.amazon.cloudformation.validate.diagnostics.StandardDiagnostic +import software.amazon.cloudformation.validate.diagnostics.StandardReport +import software.amazon.cloudformation.validate.engine.EngineConfig +import software.amazon.cloudformation.validate.rules.RuleInfo import java.io.File interface Engine { diff --git a/src/bindings-jvm/src/main/kotlin/com/amazonaws/cloudformation/validation/gson/GsonAdapters.kt b/src/bindings-jvm/src/main/kotlin/software/amazon/cloudformation/validate/gson/GsonAdapters.kt similarity index 93% rename from src/bindings-jvm/src/main/kotlin/com/amazonaws/cloudformation/validation/gson/GsonAdapters.kt rename to src/bindings-jvm/src/main/kotlin/software/amazon/cloudformation/validate/gson/GsonAdapters.kt index e2f0af1..1ac4892 100644 --- a/src/bindings-jvm/src/main/kotlin/com/amazonaws/cloudformation/validation/gson/GsonAdapters.kt +++ b/src/bindings-jvm/src/main/kotlin/software/amazon/cloudformation/validate/gson/GsonAdapters.kt @@ -1,9 +1,9 @@ @file:JvmName("BindingsGson") -package com.amazonaws.cloudformation.validation.gson +package software.amazon.cloudformation.validate.gson -import com.amazonaws.cloudformation.validation.diagnostics.JsonValueEnum -import com.amazonaws.cloudformation.validation.diagnostics.ViolationContext +import software.amazon.cloudformation.validate.diagnostics.JsonValueEnum +import software.amazon.cloudformation.validate.diagnostics.ViolationContext import com.google.gson.Gson import com.google.gson.GsonBuilder import com.google.gson.JsonArray diff --git a/src/bindings-jvm/tests/kotlin/build.gradle.kts b/src/bindings-jvm/tests/kotlin/build.gradle.kts index 6f7c22d..ef1395e 100644 --- a/src/bindings-jvm/tests/kotlin/build.gradle.kts +++ b/src/bindings-jvm/tests/kotlin/build.gradle.kts @@ -39,7 +39,7 @@ tasks.test { tasks.jacocoTestReport { classDirectories.setFrom( zipTree(bindingsJar).matching { - include("com/amazonaws/cloudformation/validation/**/*.class") + include("software/amazon/cloudformation/validate/**/*.class") exclude( "**/FfiConverter*", "**/ForeignBytes*", diff --git a/src/bindings-jvm/tests/kotlin/settings.gradle.kts b/src/bindings-jvm/tests/kotlin/settings.gradle.kts index f28109f..2e5336d 100644 --- a/src/bindings-jvm/tests/kotlin/settings.gradle.kts +++ b/src/bindings-jvm/tests/kotlin/settings.gradle.kts @@ -1 +1 @@ -rootProject.name = "bindings-jvm-tests" +rootProject.name = "cloudformation-validate-tests" diff --git a/src/bindings-jvm/tests/kotlin/src/test/kotlin/SmokeTest.kt b/src/bindings-jvm/tests/kotlin/src/test/kotlin/SmokeTest.kt index f6b7df6..c4db510 100644 --- a/src/bindings-jvm/tests/kotlin/src/test/kotlin/SmokeTest.kt +++ b/src/bindings-jvm/tests/kotlin/src/test/kotlin/SmokeTest.kt @@ -3,11 +3,11 @@ import org.junit.jupiter.api.DynamicTest import org.junit.jupiter.api.TestFactory import org.junit.jupiter.api.Assertions.* import org.junit.jupiter.api.assertThrows -import com.amazonaws.cloudformation.validation.* -import com.amazonaws.cloudformation.validation.diagnostics.* -import com.amazonaws.cloudformation.validation.engine.* -import com.amazonaws.cloudformation.validation.gson.buildBindingsGson -import com.amazonaws.cloudformation.validation.rules.* +import software.amazon.cloudformation.validate.* +import software.amazon.cloudformation.validate.diagnostics.* +import software.amazon.cloudformation.validate.engine.* +import software.amazon.cloudformation.validate.gson.buildBindingsGson +import software.amazon.cloudformation.validate.rules.* import java.io.File class SmokeTest { diff --git a/src/bindings-jvm/uniffi.toml b/src/bindings-jvm/uniffi.toml index cbc0949..5f336c4 100644 --- a/src/bindings-jvm/uniffi.toml +++ b/src/bindings-jvm/uniffi.toml @@ -1,9 +1,9 @@ [bindings.kotlin] -package_name = "com.amazonaws.cloudformation.validation" +package_name = "software.amazon.cloudformation.validate" generate_immutable_records = true [bindings.kotlin.external_packages] -diagnostics = "com.amazonaws.cloudformation.validation.diagnostics" -rules = "com.amazonaws.cloudformation.validation.rules" -template_model = "com.amazonaws.cloudformation.validation.templatemodel" -validation_engine = "com.amazonaws.cloudformation.validation.engine" +diagnostics = "software.amazon.cloudformation.validate.diagnostics" +rules = "software.amazon.cloudformation.validate.rules" +template_model = "software.amazon.cloudformation.validate.templatemodel" +validation_engine = "software.amazon.cloudformation.validate.engine" diff --git a/src/bindings-wasm/bench/package.json b/src/bindings-wasm/bench/package.json index d0b5d79..29a2da5 100644 --- a/src/bindings-wasm/bench/package.json +++ b/src/bindings-wasm/bench/package.json @@ -1,6 +1,4 @@ { - "name": "bindings-wasm-bench", - "version": "0.1.0", "private": true, "scripts": { "bench": "npx ts-node benchmark.ts" diff --git a/src/bindings-wasm/examples/package.json b/src/bindings-wasm/examples/package.json index 80f9e38..b5fc388 100644 --- a/src/bindings-wasm/examples/package.json +++ b/src/bindings-wasm/examples/package.json @@ -1,12 +1,12 @@ { - "name": "bindings-wasm-example", + "name": "@aws/cloudformation-validate/example", "version": "0.1.0", "private": true, "scripts": { "start": "ts-node validate.ts" }, "dependencies": { - "@aws/cloudformation-validate": "file:../dist" + "@aws/cloudformation-validate": "*" }, "devDependencies": { "@types/node": "26.1.0", diff --git a/src/diagnostics/uniffi.toml b/src/diagnostics/uniffi.toml index 350c59f..00a38af 100644 --- a/src/diagnostics/uniffi.toml +++ b/src/diagnostics/uniffi.toml @@ -1,6 +1,6 @@ [bindings.kotlin] -package_name = "com.amazonaws.cloudformation.validation.diagnostics" +package_name = "software.amazon.cloudformation.validate.diagnostics" generate_immutable_records = true [bindings.kotlin.external_packages] -rules = "com.amazonaws.cloudformation.validation.rules" +rules = "software.amazon.cloudformation.validate.rules" diff --git a/src/rules/uniffi.toml b/src/rules/uniffi.toml index e951945..e135754 100644 --- a/src/rules/uniffi.toml +++ b/src/rules/uniffi.toml @@ -1,3 +1,3 @@ [bindings.kotlin] -package_name = "com.amazonaws.cloudformation.validation.rules" +package_name = "software.amazon.cloudformation.validate.rules" generate_immutable_records = true diff --git a/src/template-model/uniffi.toml b/src/template-model/uniffi.toml index 47c3d2f..297e273 100644 --- a/src/template-model/uniffi.toml +++ b/src/template-model/uniffi.toml @@ -1,7 +1,7 @@ [bindings.kotlin] -package_name = "com.amazonaws.cloudformation.validation.templatemodel" +package_name = "software.amazon.cloudformation.validate.templatemodel" generate_immutable_records = true [bindings.kotlin.external_packages] -diagnostics = "com.amazonaws.cloudformation.validation.diagnostics" -rules = "com.amazonaws.cloudformation.validation.rules" +diagnostics = "software.amazon.cloudformation.validate.diagnostics" +rules = "software.amazon.cloudformation.validate.rules" diff --git a/src/validation-engine/uniffi.toml b/src/validation-engine/uniffi.toml index cd2a8f4..13c6bae 100644 --- a/src/validation-engine/uniffi.toml +++ b/src/validation-engine/uniffi.toml @@ -1,8 +1,8 @@ [bindings.kotlin] -package_name = "com.amazonaws.cloudformation.validation.engine" +package_name = "software.amazon.cloudformation.validate.engine" generate_immutable_records = true [bindings.kotlin.external_packages] -rules = "com.amazonaws.cloudformation.validation.rules" -diagnostics = "com.amazonaws.cloudformation.validation.diagnostics" -template_model = "com.amazonaws.cloudformation.validation.templatemodel" +rules = "software.amazon.cloudformation.validate.rules" +diagnostics = "software.amazon.cloudformation.validate.diagnostics" +template_model = "software.amazon.cloudformation.validate.templatemodel"