From c3b8608230651d9923f2973b12eb54a09e1538fe Mon Sep 17 00:00:00 2001 From: Tom Blake Date: Thu, 13 Aug 2026 15:26:25 +0100 Subject: [PATCH 1/3] refactor(ci): extract SDK post-processing into a shared script --- .github/workflows/check.yaml | 26 +++-------------------- scripts/postprocess.sh | 40 ++++++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 23 deletions(-) create mode 100755 scripts/postprocess.sh diff --git a/.github/workflows/check.yaml b/.github/workflows/check.yaml index da16385..43cbccc 100644 --- a/.github/workflows/check.yaml +++ b/.github/workflows/check.yaml @@ -125,30 +125,10 @@ jobs: if: steps.version_check.outputs.should_generate == 'true' id: check_changes env: - OPENAPI_VERSION: ${{ steps.version_check.outputs.openapi_version }} + SDK_VERSION: ${{ steps.version_check.outputs.openapi_version }} run: | - # Move generated files to the correct locations and clean up - rm -Rf docs && mv java-client/docs . - rm -Rf gradle && mv java-client/gradle . - rm -Rf src/main && mv java-client/src/main src/ - rm -Rf build.gradle && mv java-client/build.gradle . - rm -Rf build.sbt && mv java-client/build.sbt . - rm -Rf gradle.properties && mv java-client/gradle.properties . - rm -Rf gradlew && mv java-client/gradlew . - rm -Rf gradlew.bat && mv java-client/gradlew.bat . - rm -Rf pom.xml && mv java-client/pom.xml . - rm -Rf settings.gradle && mv java-client/settings.gradle . - rm -Rf README.md && mv java-client/README.md . - rm -Rf java-client - - # Ensure gradlew is executable - chmod +x gradlew - - # Move custom models - cp models/* src/main/java/ai/reveng/model/ - - # Store the SDK version - echo "$OPENAPI_VERSION" > .sdk-version + # Shared with scripts/generate-local.sh so the two cannot drift. + bash scripts/postprocess.sh # Configure git git config user.name "github-actions[bot]" diff --git a/scripts/postprocess.sh b/scripts/postprocess.sh new file mode 100755 index 0000000..cb53f47 --- /dev/null +++ b/scripts/postprocess.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash +# +# Post-processing for a fresh openapi-generator run. +# +# This is shared by two callers and MUST stay identical for both: +# - CI: .github/workflows/check.yaml ("Check for changes" step) +# - local: scripts/generate-local.sh +# +# Precondition: openapi-generator has just written its output into ./java-client +# Inputs (env): +# SDK_VERSION - value written to .sdk-version +# +set -euo pipefail + +cd "$(dirname "$0")/.." + +: "${SDK_VERSION:?SDK_VERSION must be set}" + +# Move generated files to the correct locations and clean up +rm -Rf docs && mv java-client/docs . +rm -Rf gradle && mv java-client/gradle . +rm -Rf src/main && mv java-client/src/main src/ +rm -Rf build.gradle && mv java-client/build.gradle . +rm -Rf build.sbt && mv java-client/build.sbt . +rm -Rf gradle.properties && mv java-client/gradle.properties . +rm -Rf gradlew && mv java-client/gradlew . +rm -Rf gradlew.bat && mv java-client/gradlew.bat . +rm -Rf pom.xml && mv java-client/pom.xml . +rm -Rf settings.gradle && mv java-client/settings.gradle . +rm -Rf README.md && mv java-client/README.md . +rm -Rf java-client + +# Ensure gradlew is executable +chmod +x gradlew + +# Move custom models +cp models/* src/main/java/ai/reveng/model/ + +# Store the SDK version +echo "$SDK_VERSION" > .sdk-version From dd8c4b2646c2677be70ab9bc7d8c97fb3ec82177 Mon Sep 17 00:00:00 2001 From: Tom Blake Date: Thu, 13 Aug 2026 15:30:36 +0100 Subject: [PATCH 2/3] feat: add local SDK generation from an arbitrary spec --- .gitignore | 5 +- Makefile | 46 +++++++++++++++ scripts/generate-local.sh | 120 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 170 insertions(+), 1 deletion(-) create mode 100644 Makefile create mode 100755 scripts/generate-local.sh diff --git a/.gitignore b/.gitignore index b6b2081..32b49c4 100644 --- a/.gitignore +++ b/.gitignore @@ -23,4 +23,7 @@ build .idea .openapi-generator out/ -*.iml \ No newline at end of file +*.iml +# Local generation scratch +.openapi-spec.json +java-client/ diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..0914946 --- /dev/null +++ b/Makefile @@ -0,0 +1,46 @@ +# Local generation and installation of the Java SDK. +# +# The released SDK is generated in CI (.github/workflows/check.yaml) and +# published by .github/workflows/publish.yaml. These targets exist to build an +# SDK from an arbitrary spec and install it into the local Maven repository, +# so downstream projects can be built against unreleased API changes. Nothing +# here publishes to a remote registry. + +# Spec to generate from: a URL or a path to a local .json file. +SPEC ?= https://docs.reveng.ai/openapi.json + +# Artifact version. Leave empty to derive it from the spec's info.version. +VERSION ?= + +export SPEC +export VERSION + +help: ## Show this help message + @printf "\nUsage: make [target]\n\n" + @printf "Targets:\n" + @grep -E '^[a-zA-Z_-]+:.*##' $(MAKEFILE_LIST) | awk -F ':.*## ' '{printf " %-15s %s\n", $$1, $$2}' + @printf "\nVariables:\n" + @printf " %-15s %s\n" "SPEC" "OpenAPI spec URL or file path" + @printf " %-15s %s\n" "VERSION" "Artifact version override (default: derived from the spec)" + @printf "\n" + +.DEFAULT_GOAL := help + +generate: ## Regenerate the SDK source tree from SPEC + ./scripts/generate-local.sh + +build: ## Compile the current source tree + ./gradlew build + +install: ## Install the current source tree into ~/.m2 (publishToMavenLocal) + ./gradlew publishToMavenLocal + @printf "\nInstalled:\n" + @ls -1 "$$HOME/.m2/repository/ai/reveng/sdk/$$(cat .sdk-version)/" + +sdk: generate install ## Regenerate from SPEC and install into ~/.m2 + +clean: ## Remove build output and any leftover generator output + ./gradlew clean + rm -rf java-client .openapi-spec.json + +.PHONY: help generate build install sdk clean diff --git a/scripts/generate-local.sh b/scripts/generate-local.sh new file mode 100755 index 0000000..51c3573 --- /dev/null +++ b/scripts/generate-local.sh @@ -0,0 +1,120 @@ +#!/usr/bin/env bash +# +# Generate the Java SDK locally from an OpenAPI spec. +# +# This is the local-developer equivalent of .github/workflows/check.yaml. It +# uses the same generator version, the same config.yml, the same templates and +# the same post-processing (scripts/postprocess.sh), so local output does not +# drift from CI output. The only deliberate difference is that the spec source +# is configurable, so any spec URL or a saved .json file can be generated from +# as easily as the default. +# +# Usage: +# scripts/generate-local.sh [--spec ] [--version ] +# [--version-prefix ] [--generator ] +# +# Env equivalents: SPEC, VERSION, VERSION_PREFIX, GENERATOR_VERSION +# +set -euo pipefail + +REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)" + +# Spec to generate from: a URL or a path to a local .json file. +SPEC="${SPEC:-https://docs.reveng.ai/openapi.json}" + +# Explicit artifact version. If empty it is derived from the spec (see below). +VERSION="${VERSION:-}" + +# Used only when the spec's info.version is not semver, which is the case for +# unreleased builds. Maven needs a semver-shaped version, and consumers select +# an installed SDK by parsing a numeric X.Y.Z out of the artifact filename, so +# a locally built SDK must still start with one. The default sorts above any +# released version, so a local build is never mistaken for a release. +VERSION_PREFIX="${VERSION_PREFIX:-3.999.0}" + +# Keep in lockstep with `generator-tag` in .github/workflows/check.yaml. +GENERATOR_VERSION="${GENERATOR_VERSION:-7.23.0}" + +while [ $# -gt 0 ]; do + case "$1" in + --spec) SPEC="$2"; shift 2 ;; + --version) VERSION="$2"; shift 2 ;; + --version-prefix) VERSION_PREFIX="$2"; shift 2 ;; + --generator) GENERATOR_VERSION="$2"; shift 2 ;; + -h|--help) sed -n '2,17p' "$0"; exit 0 ;; + *) echo "unknown argument: $1" >&2; exit 2 ;; + esac +done + +cd "$REPO_ROOT" + +# --- 1. Resolve the spec to a file inside the repo ------------------------- +# It has to live under REPO_ROOT because that is the only path bind-mounted +# into the generator container. +SPEC_FILE="$REPO_ROOT/.openapi-spec.json" +trap 'rm -f "$SPEC_FILE"' EXIT + +case "$SPEC" in + http://*|https://*) + echo "==> Fetching spec from $SPEC" + curl -fsSL "$SPEC" -o "$SPEC_FILE" + ;; + *) + echo "==> Using local spec $SPEC" + [ -f "$SPEC" ] || { echo "spec file not found: $SPEC" >&2; exit 1; } + cp "$SPEC" "$SPEC_FILE" + ;; +esac + +SPEC_VERSION="$(jq -r '.info.version // empty' "$SPEC_FILE")" +[ -n "$SPEC_VERSION" ] || { echo "spec has no info.version" >&2; exit 1; } +echo "==> Spec info.version: $SPEC_VERSION" + +# --- 2. Resolve the artifact version -------------------------------------- +if [ -z "$VERSION" ]; then + # Drop the `v` prefix, as CI does: it is not usually used in Maven versions. + BASE="${SPEC_VERSION#v}" + if printf '%s' "$BASE" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+$'; then + # Released spec: use its version verbatim, identical to CI. + VERSION="$BASE" + else + # Unreleased spec: prefix a synthetic semver and keep the spec's own + # version as a suffix, so the artifact stays traceable to its source. + VERSION="${VERSION_PREFIX}-${BASE}" + fi +fi + +# Fail here rather than at consumer build time. +if ! printf '%s' "$VERSION" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+'; then + echo "version must start with a numeric X.Y.Z: got '$VERSION'" >&2 + exit 1 +fi +echo "==> Artifact version: $VERSION" + +# --- 3. Generate ---------------------------------------------------------- +# Pinned to the same image tag CI uses, so no local generator install is +# needed. Running as the invoking user keeps the generated tree owned by the +# developer rather than by root. +rm -rf "$REPO_ROOT/java-client" + +echo "==> Running openapi-generator v${GENERATOR_VERSION}" +docker run --rm \ + --user "$(id -u):$(id -g)" \ + -v "$REPO_ROOT:/local" \ + -w /local \ + "openapitools/openapi-generator-cli:v${GENERATOR_VERSION}" \ + generate \ + -i /local/.openapi-spec.json \ + -g java \ + -o /local/java-client \ + -c /local/config.yml \ + -t /local/templates \ + --additional-properties="artifactVersion=${VERSION}" + +# --- 4. Post-process (shared with CI) ------------------------------------- +echo "==> Post-processing" +SDK_VERSION="$VERSION" "$REPO_ROOT/scripts/postprocess.sh" + +echo +echo "==> Done. Generated SDK version $(cat "$REPO_ROOT/.sdk-version")" +grep -n "^version = \|coordinates(" "$REPO_ROOT/build.gradle" From fbc0dc1b2b33cab4e7453f06001da5c22ade87ed Mon Sep 17 00:00:00 2001 From: Tom Blake Date: Thu, 13 Aug 2026 15:31:15 +0100 Subject: [PATCH 3/3] fix(publish): sign only when a signing key is configured --- Makefile | 2 +- build.gradle | 5 ++++- scripts/generate-local.sh | 4 +++- templates/libraries/okhttp-gson/build.gradle.mustache | 5 ++++- 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index 0914946..1673e26 100644 --- a/Makefile +++ b/Makefile @@ -35,7 +35,7 @@ build: ## Compile the current source tree install: ## Install the current source tree into ~/.m2 (publishToMavenLocal) ./gradlew publishToMavenLocal @printf "\nInstalled:\n" - @ls -1 "$$HOME/.m2/repository/ai/reveng/sdk/$$(cat .sdk-version)/" + @ls -1 "$$HOME/.m2/repository/ai/reveng/sdk/$$(sed 's/^v//' .sdk-version)/" sdk: generate install ## Regenerate from SPEC and install into ~/.m2 diff --git a/build.gradle b/build.gradle index 1f85130..c0f99d0 100644 --- a/build.gradle +++ b/build.gradle @@ -169,7 +169,10 @@ test { mavenPublishing { publishToMavenCentral(true) - signAllPublications() + + // CI sets ORG_GRADLE_PROJECT_signingInMemoryKey; publishToMavenLocal has no + // key, and signing without one fails the build. + if (project.hasProperty("signingInMemoryKey")) signAllPublications() coordinates("ai.reveng", "sdk", "3.131.1") diff --git a/scripts/generate-local.sh b/scripts/generate-local.sh index 51c3573..fa7e82e 100755 --- a/scripts/generate-local.sh +++ b/scripts/generate-local.sh @@ -72,7 +72,9 @@ echo "==> Spec info.version: $SPEC_VERSION" # --- 2. Resolve the artifact version -------------------------------------- if [ -z "$VERSION" ]; then - # Drop the `v` prefix, as CI does: it is not usually used in Maven versions. + # Drop the `v` prefix: Maven versions do not use it. Note this makes + # .sdk-version differ from CI, which stores the tag-shaped version there. + # Local consumers read the file as the Maven version, so keep it stripped. BASE="${SPEC_VERSION#v}" if printf '%s' "$BASE" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+$'; then # Released spec: use its version verbatim, identical to CI. diff --git a/templates/libraries/okhttp-gson/build.gradle.mustache b/templates/libraries/okhttp-gson/build.gradle.mustache index f972c02..01ebbf7 100644 --- a/templates/libraries/okhttp-gson/build.gradle.mustache +++ b/templates/libraries/okhttp-gson/build.gradle.mustache @@ -205,7 +205,10 @@ test { mavenPublishing { publishToMavenCentral(true) - signAllPublications() + + // CI sets ORG_GRADLE_PROJECT_signingInMemoryKey; publishToMavenLocal has no + // key, and signing without one fails the build. + if (project.hasProperty("signingInMemoryKey")) signAllPublications() coordinates("{{groupId}}", "{{artifactId}}", "{{artifactVersion}}")