diff --git a/.bazelrc b/.bazelrc index b7c2c81..557e848 100644 --- a/.bazelrc +++ b/.bazelrc @@ -3,5 +3,6 @@ build --java_language_version=21 build --tool_java_language_version=21 build --java_runtime_version=remotejdk_21 build --tool_java_runtime_version=remotejdk_21 +build --define=maven_version=0.0.0-SNAPSHOT test --test_output=errors diff --git a/.github/workflows/publish-maven.yml b/.github/workflows/publish-maven.yml new file mode 100644 index 0000000..d0bef0b --- /dev/null +++ b/.github/workflows/publish-maven.yml @@ -0,0 +1,157 @@ +name: Publish Maven artifact + +on: + workflow_dispatch: + inputs: + tag: + description: Version tag to publish (for example, v1.0.3) + required: true + type: string + +permissions: + contents: read + +concurrency: + group: publish-maven-${{ inputs.tag }} + cancel-in-progress: false + +jobs: + validate: + runs-on: ubuntu-latest + outputs: + tag: ${{ steps.validate.outputs.tag }} + version: ${{ steps.validate.outputs.version }} + steps: + - name: Validate tag + id: validate + env: + RELEASE_TAG: ${{ inputs.tag }} + shell: bash + run: | + set -euo pipefail + + if [[ ! "${RELEASE_TAG}" =~ ^v([0-9]+\.[0-9]+\.[0-9]+)$ ]]; then + echo "Tag must match vMAJOR.MINOR.PATCH (for example, v1.0.3)." >&2 + exit 1 + fi + + echo "tag=${RELEASE_TAG}" >> "${GITHUB_OUTPUT}" + echo "version=${BASH_REMATCH[1]}" >> "${GITHUB_OUTPUT}" + + publish: + needs: validate + runs-on: ubuntu-latest + environment: maven-publishing + steps: + - name: Check out release tag + uses: actions/checkout@v6 + with: + ref: refs/tags/${{ needs.validate.outputs.tag }} + persist-credentials: false + + - name: Build Maven artifact + env: + RELEASE_VERSION: ${{ needs.validate.outputs.version }} + run: | + bazel build --define=maven_version="${RELEASE_VERSION}" \ + //java/session:session_export \ + //java/session:session_export-pom + + - name: Verify tag matches Maven version + env: + RELEASE_VERSION: ${{ needs.validate.outputs.version }} + shell: bash + run: | + python3 - <<'PY' + import os + import xml.etree.ElementTree as ET + + pom = ET.parse("bazel-bin/java/session/session_export-pom.xml").getroot() + namespace = {"m": "http://maven.apache.org/POM/4.0.0"} + published_version = pom.findtext("m:version", namespaces=namespace) + release_version = os.environ["RELEASE_VERSION"] + if published_version != release_version: + raise SystemExit( + f"Tag version {release_version} does not match Maven version {published_version}" + ) + PY + + - name: Stage signed Maven artifact + env: + GPG_SIGNING_KEY: ${{ secrets.GPG_SIGNING_KEY }} + GPG_SIGNING_KEY_PASSWORD: ${{ secrets.GPG_SIGNING_KEY_PASSWORD }} + RELEASE_VERSION: ${{ needs.validate.outputs.version }} + USE_IN_MEMORY_PGP_KEYS: "true" + shell: bash + run: | + set -euo pipefail + + if [[ -z "${GPG_SIGNING_KEY}" ]]; then + echo "Environment secret GPG_SIGNING_KEY is not configured." >&2 + exit 1 + fi + + staging_repo="${RUNNER_TEMP}/maven-central-staging" + mkdir -p "${staging_repo}" + env \ + MAVEN_REPO="file://${staging_repo}" \ + PGP_SIGNING_KEY="${GPG_SIGNING_KEY}" \ + PGP_SIGNING_PWD="${GPG_SIGNING_KEY_PASSWORD}" \ + bazel run --define=maven_version="${RELEASE_VERSION}" \ + //java/session:session_export.publish + find "${staging_repo}" -type f -name 'maven-metadata.xml*' -delete + + - name: Create Maven Central bundle + shell: bash + run: | + set -euo pipefail + + staging_repo="${RUNNER_TEMP}/maven-central-staging" + bundle="${RUNNER_TEMP}/maven-central-bundle.zip" + if [[ -z "$(find "${staging_repo}" -type f -print -quit)" ]]; then + echo "The staged Maven repository is empty." >&2 + exit 1 + fi + while IFS= read -r artifact; do + if [[ ! -f "${artifact}.asc" ]]; then + echo "Missing signature for ${artifact}" >&2 + exit 1 + fi + done < <(find "${staging_repo}" -type f \( -name '*.jar' -o -name '*.pom' \)) + ( + cd "${staging_repo}" + zip -q -r "${bundle}" . + ) + + - name: Upload deployment to Central Portal + env: + MAVEN_CENTRAL_PASSWORD: ${{ secrets.MAVEN_CENTRAL_PASSWORD }} + MAVEN_CENTRAL_USERNAME: ${{ secrets.MAVEN_CENTRAL_USERNAME }} + RELEASE_VERSION: ${{ needs.validate.outputs.version }} + shell: bash + run: | + set -euo pipefail + + if [[ -z "${MAVEN_CENTRAL_USERNAME}" || -z "${MAVEN_CENTRAL_PASSWORD}" ]]; then + echo "Environment secrets MAVEN_CENTRAL_USERNAME and MAVEN_CENTRAL_PASSWORD are not configured." >&2 + exit 1 + fi + + repository_name="${GITHUB_REPOSITORY#*/}" + deployment_name="${repository_name}-${RELEASE_VERSION}" + bundle="${RUNNER_TEMP}/maven-central-bundle.zip" + authorization="$(printf '%s:%s' "${MAVEN_CENTRAL_USERNAME}" "${MAVEN_CENTRAL_PASSWORD}" | base64 -w 0)" + deployment_id="$(curl --fail-with-body --silent --show-error \ + --request POST \ + --header "Authorization: Bearer ${authorization}" \ + --url-query "name=${deployment_name}" \ + --url-query "publishingType=USER_MANAGED" \ + --form "bundle=@${bundle};type=application/octet-stream" \ + "https://central.sonatype.com/api/v1/publisher/upload")" + + { + echo "### Maven Central deployment" + echo + echo "- Name: ${deployment_name}" + echo "- ID: ${deployment_id}" + } >> "${GITHUB_STEP_SUMMARY}" diff --git a/MODULE.bazel b/MODULE.bazel index fd86851..bdc7d82 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -9,7 +9,6 @@ go_sdk = use_extension("@rules_go//go:extensions.bzl", "go_sdk") go_sdk.from_file(go_mod = "//:go.mod") go_deps = use_extension("@gazelle//:extensions.bzl", "go_deps") - go_deps.from_file(go_mod = "//:go.mod") use_repo( go_deps, @@ -24,16 +23,18 @@ use_repo( inject_repo( go_deps, "rules_go", - "rules_java" + "rules_java", ) -bazel_dep(name = "contrib_rules_jvm", version = "0.32.0") +bazel_dep(name = "contrib_rules_jvm", version = "0.33.0") bazel_dep(name = "rules_java", version = "9.3.0") -bazel_dep(name = "rules_jvm_external", version = "6.9") +bazel_dep(name = "rules_jvm_external", version = "6.10") maven = use_extension("@rules_jvm_external//:extensions.bzl", "maven") maven.install( artifacts = [ + "co.atoms.lib.net.location:location:1.0.3", + "com.google.protobuf:protobuf-java:4.34.1", "com.google.code.findbugs:jsr305:3.0.2", "com.google.guava:guava:33.3.1-jre", "io.grpc:grpc-api:1.72.0", @@ -45,10 +46,10 @@ maven.install( "org.slf4j:slf4j-api:2.0.17", ], known_contributing_modules = ["protobuf"], + lock_file = "//:maven_install.json", repositories = [ "https://repo1.maven.org/maven2", ], - lock_file = "//:maven_install.json", ) use_repo(maven, "maven") diff --git a/README.md b/README.md index 235e614..b5b079c 100644 --- a/README.md +++ b/README.md @@ -2,3 +2,40 @@ Session library provides data types for communication between parties using sessions. + +## Publishing the Java artifact + +Build the Maven artifact and its POM: + +```shell +bazel build //java/session:session_export //java/session:session_export-pom +``` + +Ordinary builds use version `0.0.0-SNAPSHOT`. Supply a release version with a +Bazel definition: + +```shell +bazel build --define=maven_version=1.0.3 \ + //java/session:session_export \ + //java/session:session_export-pom +``` + +Publish to a local Maven repository for testing: + +```shell +MAVEN_REPO="file://${HOME}/.m2/repository" \ + bazel run --define=maven_version=1.0.3 \ + //java/session:session_export.publish +``` + +For a remote Maven repository, set `MAVEN_REPO`, `MAVEN_USER`, and +`MAVEN_PASSWORD` in the environment before running the same publish target. + +### Publishing from GitHub Actions + +The `Publish Maven artifact` workflow publishes an existing `vMAJOR.MINOR.PATCH` +tag when started manually from the Actions page. It passes the version from the +tag to Bazel when building the artifact. The workflow creates a signed Maven +repository bundle and uploads it to the Central Publisher Portal as a +user-managed deployment. An authorized person must then review it in the Portal +and either publish it to Maven Central or drop it. diff --git a/java/session/BUILD.bazel b/java/session/BUILD.bazel index 88ad4a5..86aa899 100644 --- a/java/session/BUILD.bazel +++ b/java/session/BUILD.bazel @@ -1,5 +1,8 @@ load("@contrib_rules_jvm//java:defs.bzl", "java_junit5_test") load("@rules_java//java:defs.bzl", "java_library") +load("@rules_jvm_external//:defs.bzl", "maven_export") + +MAVEN_COORDINATES = "co.atoms.lib.net.session:session:$(maven_version)" java_library( name = "session", @@ -7,22 +10,35 @@ java_library( ["*.java"], exclude = ["*Test.java"], ), - visibility = ["//:__subpackages__"], + tags = ["maven_coordinates=%s" % MAVEN_COORDINATES], + visibility = ["//visibility:public"], exports = [ "//proto/atoms/lib/net/session:session_java_proto", - "@com_google_protobuf//java/core", + "@maven//:co_atoms_lib_net_location_location", # keep: published API dependency + "@maven//:com_google_protobuf_protobuf_java", + "@maven//:io_grpc_grpc_stub", # keep: published API dependency ], deps = [ "//proto/atoms/lib/net/session:session_java_proto", - "@co_atoms_go_lib_net_location//proto/atoms/lib/net/location:location_java_proto", - "@com_google_protobuf//java/core", + "@maven//:co_atoms_lib_net_location_location", "@maven//:com_google_code_findbugs_jsr305", "@maven//:com_google_guava_guava", + "@maven//:com_google_protobuf_protobuf_java", "@maven//:io_grpc_grpc_stub", "@maven//:org_slf4j_slf4j_api", ], ) +maven_export( + name = "session_export", + excluded_workspaces = { + "gazelle": "@maven//:co_atoms_lib_net_location_location", + }, + lib_name = "session", + maven_coordinates = MAVEN_COORDINATES, + pom_template = "pom.xml.tpl", +) + java_junit5_test( name = "session_test", srcs = glob([ diff --git a/java/session/pom.xml.tpl b/java/session/pom.xml.tpl new file mode 100644 index 0000000..cf570b1 --- /dev/null +++ b/java/session/pom.xml.tpl @@ -0,0 +1,42 @@ + + + 4.0.0 + + {groupId} + {artifactId} + {version} + {type} + + Session Library + Data types for communication between parties using sessions. + https://github.com/atoms-co/session-lib + + + + Apache License, Version 2.0 + https://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + + + + Sergey Tyurin + sergey.tyurin@atoms.co + Atoms + https://atoms.co + + + + + scm:git:https://github.com/atoms-co/session-lib.git + scm:git:ssh://git@github.com/atoms-co/session-lib.git + https://github.com/atoms-co/session-lib + + + +{dependencies} + + diff --git a/maven_install.json b/maven_install.json index 2879283..74ec128 100644 --- a/maven_install.json +++ b/maven_install.json @@ -1,13 +1,57 @@ { "__AUTOGENERATED_FILE_DO_NOT_MODIFY_THIS_FILE_MANUALLY": "THERE_IS_NO_DATA_ONLY_ZUUL", - "__INPUT_ARTIFACTS_HASH": 888306343, - "__RESOLVED_ARTIFACTS_HASH": -782520073, + "__INPUT_ARTIFACTS_HASH": { + "co.atoms.lib.net.location:location": -1959753936, + "com.google.code.findbugs:jsr305": 495355163, + "com.google.code.gson:gson": 597770368, + "com.google.errorprone:error_prone_annotations": -1035138750, + "com.google.guava:guava": -770160850, + "com.google.j2objc:j2objc-annotations": 2003271689, + "com.google.protobuf:protobuf-java": 1237026331, + "io.grpc:grpc-api": -1557010041, + "io.grpc:grpc-stub": -952065045, + "org.junit.jupiter:junit-jupiter-api": 1679408083, + "org.junit.jupiter:junit-jupiter-engine": -647767569, + "org.junit.platform:junit-platform-launcher": 1694939609, + "org.junit.platform:junit-platform-reporting": -2023832725, + "org.slf4j:slf4j-api": -197708565, + "repositories": -1949687017 + }, + "__RESOLVED_ARTIFACTS_HASH": { + "co.atoms.lib.net.location:location": -1665590081, + "com.google.code.findbugs:jsr305": 870839855, + "com.google.code.gson:gson": -1575757252, + "com.google.errorprone:error_prone_annotations": -1097264484, + "com.google.guava:failureaccess": -718864417, + "com.google.guava:guava": -678705198, + "com.google.guava:listenablefuture": 1079558157, + "com.google.j2objc:j2objc-annotations": -404209759, + "com.google.protobuf:protobuf-java": 1198356280, + "io.grpc:grpc-api": -1995667424, + "io.grpc:grpc-stub": 356713335, + "org.apiguardian:apiguardian-api": 1279798469, + "org.checkerframework:checker-qual": -1158206121, + "org.codehaus.mojo:animal-sniffer-annotations": -1619058809, + "org.junit.jupiter:junit-jupiter-api": -1452921533, + "org.junit.jupiter:junit-jupiter-engine": 551847014, + "org.junit.platform:junit-platform-commons": 1576947314, + "org.junit.platform:junit-platform-engine": -1014764526, + "org.junit.platform:junit-platform-launcher": -1998194815, + "org.junit.platform:junit-platform-reporting": -412738989, + "org.opentest4j:opentest4j": -1584531193, + "org.slf4j:slf4j-api": -1521593912 + }, "conflict_resolution": { "com.google.errorprone:error_prone_annotations:2.5.1": "com.google.errorprone:error_prone_annotations:2.30.0", - "com.google.guava:guava:32.0.1-jre": "com.google.guava:guava:33.3.1-jre", "com.google.j2objc:j2objc-annotations:2.8": "com.google.j2objc:j2objc-annotations:3.0.0" }, "artifacts": { + "co.atoms.lib.net.location:location": { + "shasums": { + "jar": "3b57b516011a4941fe58548c221f9bd6e4ec403c1a40a2f89958f9232ff1c4f7" + }, + "version": "1.0.3" + }, "com.google.code.findbugs:jsr305": { "shasums": { "jar": "766ad2a0783f2687962c8ad74ceecc38a28b9f72a2d085ee438b7813e928d0c7" @@ -50,6 +94,12 @@ }, "version": "3.0.0" }, + "com.google.protobuf:protobuf-java": { + "shasums": { + "jar": "ae0459030b54a6f3058ad349aa01fc2edcbb2d4a84b73699675bbd125df2e96d" + }, + "version": "4.34.1" + }, "io.grpc:grpc-api": { "shasums": { "jar": "f7ca643e2a8cab338b3c3c37305da4084d81d75b66a2016018c1c0ab97b655d4" @@ -130,6 +180,9 @@ } }, "dependencies": { + "co.atoms.lib.net.location:location": [ + "com.google.protobuf:protobuf-java" + ], "com.google.guava:guava": [ "com.google.code.findbugs:jsr305", "com.google.errorprone:error_prone_annotations", @@ -177,6 +230,10 @@ ] }, "packages": { + "co.atoms.lib.net.location:location": [ + "co.atoms.lib.net.location", + "co.atoms.lib.net.location.proto" + ], "com.google.code.findbugs:jsr305": [ "javax.annotation", "javax.annotation.concurrent", @@ -223,6 +280,10 @@ "com.google.j2objc:j2objc-annotations": [ "com.google.j2objc.annotations" ], + "com.google.protobuf:protobuf-java": [ + "com.google.protobuf", + "com.google.protobuf.compiler" + ], "io.grpc:grpc-api": [ "io.grpc" ], @@ -335,6 +396,7 @@ }, "repositories": { "https://repo1.maven.org/maven2/": [ + "co.atoms.lib.net.location:location", "com.google.code.findbugs:jsr305", "com.google.code.gson:gson", "com.google.errorprone:error_prone_annotations", @@ -342,6 +404,7 @@ "com.google.guava:guava", "com.google.guava:listenablefuture", "com.google.j2objc:j2objc-annotations", + "com.google.protobuf:protobuf-java", "io.grpc:grpc-api", "io.grpc:grpc-stub", "org.apiguardian:apiguardian-api", @@ -374,5 +437,5 @@ ] } }, - "version": "2" + "version": "3" }