diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..3c1fa61 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,46 @@ +name: release + +# Publishes the library to Maven Central (Sonatype Central Portal) when a version tag is pushed, +# then creates the matching GitHub Release. Credentials and the signing key are read from repo +# secrets at publish time only -- see RELEASING.md for the one-time setup and how to cut a release. + +on: + push: + tags: + - 'v*' + +permissions: + contents: write + +jobs: + publish: + runs-on: ubuntu-latest + steps: + - name: Check out source + uses: actions/checkout@v4 + + - name: Set up JDK 17 + uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: '17' + + - name: Set up Gradle + uses: gradle/actions/setup-gradle@v4 + + - name: Derive release version from tag + id: version + run: echo "version=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT" + + - name: Publish to Maven Central + env: + ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.MAVEN_CENTRAL_USERNAME }} + ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.MAVEN_CENTRAL_PASSWORD }} + ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.SIGNING_KEY }} + ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.SIGNING_KEY_PASSWORD }} + run: ./gradlew publishToMavenCentral -PreleaseVersion=${{ steps.version.outputs.version }} --no-daemon --no-configuration-cache + + - name: Create GitHub Release + uses: softprops/action-gh-release@v2 + with: + generate_release_notes: true diff --git a/RELEASING.md b/RELEASING.md new file mode 100644 index 0000000..043cfa5 --- /dev/null +++ b/RELEASING.md @@ -0,0 +1,69 @@ +# Releasing JavaSake + +JavaSake is published to **Maven Central** (via the Sonatype Central Portal) so downstream projects +can depend on it directly: + +```kotlin +dependencies { + implementation("org.openminimed:javasake:") +} +``` + +Publishing is automated: pushing a `vX.Y.Z` tag builds, signs, and publishes the release, then +creates a GitHub Release (`.github/workflows/release.yml`). Local builds and PRs always use a +`-SNAPSHOT` version and never publish. + +## One-time setup (repo admin) + +Publishing requires four repository secrets. None are needed to build or test; they are read only by +the `release` workflow. + +### 1. Maven Central namespace + +The published coordinate uses the `org.openminimed` group, so the Central Portal namespace +`org.openminimed` must be verified once. Two options: + +- **`org.openminimed`** (current group) — requires verifying ownership of the `openminimed.org` + domain via a DNS TXT record in the Central Portal. Preferred if the org owns that domain. +- **`io.github.openminimed`** — verified automatically against the GitHub org, no domain needed. + If you prefer this, change `group` in `lib/build.gradle.kts` accordingly before the first release. + +### 2. Central Portal user token + +In the Central Portal (https://central.sonatype.com) → *Account* → *Generate User Token*. Store the +two halves as: + +- `MAVEN_CENTRAL_USERNAME` +- `MAVEN_CENTRAL_PASSWORD` + +### 3. GPG signing key + +Central requires signed artifacts. Generate a key, publish its public half to a keyserver, then +export the private key in-memory (ASCII-armored): + +```bash +gpg --armor --export-secret-keys +``` + +Store as: + +- `SIGNING_KEY` — the full ASCII-armored private key block +- `SIGNING_KEY_PASSWORD` — its passphrase + +Add all four under *Settings → Secrets and variables → Actions*. + +## Cutting a release + +1. Decide the version (SemVer, e.g. `0.2.0`). +2. Tag and push: + + ```bash + git tag v0.2.0 + git push origin v0.2.0 + ``` + +3. The `release` workflow publishes to Central (auto-released once validation passes) and opens a + GitHub Release. The artifact typically appears on Central within ~15-30 minutes. + +Do **not** set the version in `lib/build.gradle.kts`; it is derived from the tag +(`-PreleaseVersion`), so the tag is the single source of truth. diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 75ed71f..fb869c2 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -4,6 +4,7 @@ junitPlatform = "1.11.3" bouncycastle = "1.79" spotless = "6.25.0" googleJavaFormat = "1.22.0" +mavenPublish = "0.30.0" [libraries] junit-jupiter-api = { group = "org.junit.jupiter", name = "junit-jupiter-api", version.ref = "junit" } @@ -13,3 +14,4 @@ bouncycastle = { group = "org.bouncycastle", name = "bcprov-jdk18on", version.re [plugins] spotless = { id = "com.diffplug.spotless", version.ref = "spotless" } +mavenPublish = { id = "com.vanniktech.maven.publish", version.ref = "mavenPublish" } diff --git a/lib/build.gradle.kts b/lib/build.gradle.kts index 92818a2..8f7fb37 100644 --- a/lib/build.gradle.kts +++ b/lib/build.gradle.kts @@ -1,15 +1,22 @@ +import com.vanniktech.maven.publish.JavaLibrary +import com.vanniktech.maven.publish.JavadocJar +import com.vanniktech.maven.publish.SonatypeHost + plugins { `java-library` alias(libs.plugins.spotless) + alias(libs.plugins.mavenPublish) } group = "org.openminimed" -version = "0.1.0-SNAPSHOT" + +// Release version is supplied by CI from the git tag (-PreleaseVersion=x.y.z); local builds and +// PRs fall back to a -SNAPSHOT so nothing is ever accidentally published with a release version. +version = (findProperty("releaseVersion") as String?) ?: "0.1.0-SNAPSHOT" java { sourceCompatibility = JavaVersion.VERSION_11 targetCompatibility = JavaVersion.VERSION_11 - withSourcesJar() } repositories { @@ -40,3 +47,47 @@ spotless { endWithNewline() } } + +// Publishing to Maven Central via the Sonatype Central Portal. Credentials and the signing key are +// injected from CI secrets at publish time only (see RELEASING.md); build and test need neither. +mavenPublishing { + configure(JavaLibrary(javadocJar = JavadocJar.Javadoc(), sourcesJar = true)) + publishToMavenCentral(SonatypeHost.CENTRAL_PORTAL, automaticRelease = true) + signAllPublications() + + coordinates(group.toString(), "javasake", version.toString()) + + pom { + name.set("JavaSake") + description.set( + "SAKE handshake and sequenced AES-CTR/CMAC session cipher for the JVM and Android.", + ) + url.set("https://github.com/OpenMinimed/JavaSake") + inceptionYear.set("2026") + + licenses { + license { + name.set("GNU General Public License v3.0") + url.set("https://www.gnu.org/licenses/gpl-3.0.txt") + distribution.set("repo") + } + } + developers { + developer { + id.set("palmarci") + name.set("Pal Marci") + url.set("https://github.com/palmarci") + } + developer { + id.set("openminimed") + name.set("OpenMinimed contributors") + url.set("https://github.com/OpenMinimed") + } + } + scm { + url.set("https://github.com/OpenMinimed/JavaSake") + connection.set("scm:git:https://github.com/OpenMinimed/JavaSake.git") + developerConnection.set("scm:git:ssh://git@github.com/OpenMinimed/JavaSake.git") + } + } +}