This is an automated email from the ASF dual-hosted git repository.

jason810496 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git


The following commit(s) were added to refs/heads/main by this push:
     new 162952b3d3d Fix Java SDK RC2 blockers: source packaging, jar 
LICENSE/NOTICE, BOM gap (#69582)
162952b3d3d is described below

commit 162952b3d3d5d3ec05492bf17c08f388256beb56
Author: Jason(Zhe-You) Liu <[email protected]>
AuthorDate: Wed Jul 8 21:54:45 2026 +0900

    Fix Java SDK RC2 blockers: source packaging, jar LICENSE/NOTICE, BOM gap 
(#69582)
    
    * Exclude the full Gradle wrapper from the Java SDK source release
    
    Only gradle-wrapper.jar was excluded from the sourceRelease tarball
    (LEGAL-570), but gradlew, gradlew.bat, and gradle-wrapper.properties
    still shipped, drawing a binding -1 in the 1.0.0-beta1 RC2 vote. The
    wrapper stays tracked in git; only the git-archive output used by
    sourceRelease now omits it. Document both ways to build an extracted
    source package without the wrapper, add a release-verification
    checklist, and add a vote-email template with the full 8-artifact
    staged list so future RCs don't repeat the RC2 feedback.
    
    * Add LICENSE/NOTICE and reproducible archives to Java SDK jars
    
    Staged convenience-binary jars (main, sources, javadoc, test-fixtures)
    were missing META-INF/LICENSE and META-INF/NOTICE, violating ASF
    release policy for distributed binaries. Builds were also not
    byte-reproducible: jar entries carried wall-clock timestamps, blocking
    GitHub-driven publishing. Centralizing both fixes in the
    airflow-jvm-conventions plugin, applied by every jar-producing module
    (sdk, jul, log4j2, slf4j, jpl, processor, plugin), covers all modules
    without per-module duplication.
    
    * Manage airflow-sdk-jpl version in the Java SDK BOM
    
    The BOM constrained sdk, jul, log4j2, processor, and slf4j but not
    jpl, even though jpl is published to Nexus and the bundled example
    module depends on it. Consumers pulling in jpl via the BOM alone had
    no version constraint to resolve against.
    
    * Manage airflow-sdk-gradle-plugin version in the Java SDK BOM
    
    airflow-sdk-gradle-plugin publishes under the same org.apache.airflow
    Maven coordinate convention as the other SDK artifacts, but was left
    out of the BOM's constraints when airflow-sdk-jpl was added — the same
    gap the jpl fix was closing. Consumers importing airflow-sdk-bom still
    got no version pin for the plugin artifact.
    
    * Use rc<N> placeholder in Java SDK release verification clone example
    
    * Keep wrapper properties in the archive
    
    * Add verification in BOM to not miss subprojects
    
    * Touch up on Java SDK verification process
    
    ---------
    
    Co-authored-by: TP <[email protected]>
---
 java-sdk/.gitattributes                            |   9 +-
 java-sdk/README.md                                 | 205 +++++++++++++++++----
 java-sdk/bom/build.gradle.kts                      |  47 +++++
 .../main/kotlin/airflow-jvm-conventions.gradle.kts |  14 ++
 4 files changed, 242 insertions(+), 33 deletions(-)

diff --git a/java-sdk/.gitattributes b/java-sdk/.gitattributes
index 428710f2ee4..6cd69336377 100644
--- a/java-sdk/.gitattributes
+++ b/java-sdk/.gitattributes
@@ -10,6 +10,11 @@
 # Binary files should be left untouched
 *.jar           binary
 
-# Keep the Gradle wrapper jar out of source releases.
+# Keep the Gradle wrapper jar and scripts out of source releases.
 # See: https://issues.apache.org/jira/browse/LEGAL-570
-gradle/wrapper/gradle-wrapper.jar export-ignore
+# This intentionally does not exclude the entire gradle/wrapper directory so
+# gradle-wrapper.properties is kept. This file carries the pinned Gradle 
version
+# and distribution checksum for a verifier to use when regenerating the 
wrapper.
+/gradlew export-ignore
+/gradlew.bat export-ignore
+/gradle/wrapper/gradle-wrapper.jar export-ignore
diff --git a/java-sdk/README.md b/java-sdk/README.md
index cd8874a9552..c9d54f942fe 100644
--- a/java-sdk/README.md
+++ b/java-sdk/README.md
@@ -117,22 +117,23 @@ Every release therefore requires a PMC vote before it is 
published; only
 Edit `gradle.properties` and set the version for this release:
 
 ```properties
-projectVersion=1.0.0
+projectVersion=<VERSION>
 ```
 
 Commit the change and push it to the release branch.
 
 Use a Maven-compatible version string, as defined by the
-[Maven version order 
specification](https://maven.apache.org/pom.html#Version_Order_Specification).
-For example, version 1 beta 1 is `1.0.0-beta1`, and the eventual
-general-availability release is `1.0.0`.
+[Maven version order specification]. For example, version 1 beta 1 is
+`1.0.0-beta1`, and the eventual version 1 release is `1.0.0`.
+
+[Maven version order specification]: 
https://maven.apache.org/pom.html#Version_Order_Specification
 
 *NOTE:* Editing `gradle.properties` as above is the standard procedure. You can
 alternatively override the version for a single command without editing the
-file, by passing `-PprojectVersion=1.0.0-beta1` to any Gradle invocation. This
+file, by passing `-PprojectVersion=<VERSION>` to any Gradle invocation. This
 is handy for one-off or pre-release builds. Either way, `main` should stay on a
-`-SNAPSHOT` version between releases: a snapshot sorts after `1.0.0-beta1` and
-before the `1.0.0` GA, so no extra bump is needed after a beta.
+`-SNAPSHOT` version between releases: a snapshot sorts after `<VERSION>` and
+before the GA, so no extra bump is needed after a beta.
 
 ### Tag the release candidate
 
@@ -142,8 +143,8 @@ suffix). Push the tag before sending the vote so reviewers 
can check out the
 exact source being voted on.
 
 ```bash
-git tag -s java-sdk/1.0.0-beta1-rc1 -m "Java SDK 1.0.0-beta1 RC 1"
-git push upstream java-sdk/1.0.0-beta1-rc1
+git tag -s java-sdk/<VERSION>-rc<N> -m "Java SDK <VERSION> RC <N>"
+git push upstream java-sdk/<VERSION>-rc<N>
 ```
 
 ### Verify the POM locally
@@ -246,22 +247,23 @@ signature and checksum in one step:
 
 ```bash
 # Signing uses your local gpg keyring, so have your key/passphrase ready.
-./gradlew sourceRelease -PgitRef=java-sdk/1.0.0-beta1-rc1
+./gradlew sourceRelease -PgitRef=java-sdk/<VERSION>-rc<N>
 ```
 
 This writes three files to `build/distributions/`:
 
 ```
-apache-airflow-java-sdk-1.0.0-beta1-src.tar.gz
-apache-airflow-java-sdk-1.0.0-beta1-src.tar.gz.asc
-apache-airflow-java-sdk-1.0.0-beta1-src.tar.gz.sha512
+apache-airflow-java-sdk-<VERSION>-src.tar.gz
+apache-airflow-java-sdk-<VERSION>-src.tar.gz.asc
+apache-airflow-java-sdk-<VERSION>-src.tar.gz.sha512
 ```
 
-*NOTE:* The source archive deliberately omits 
`gradle/wrapper/gradle-wrapper.jar`
-since ASF source releases must not contain compiled code (see [LEGAL-570]). To
-build an *extracted* source package, use a locally installed Gradle instead:
-either run `gradle build`, or use `gradle wrapper` once to regenerate the jar
-and then `./gradlew build`.
+**NOTE:** The source archive omits the Gradle wrapper scripts (`gradlew`,
+`gradlew.bat`) and `gradle/wrapper/gradle-wrapper.jar` since ASF source 
releases
+must not contain compiled code (see [LEGAL-570]), and the scripts are not 
useful
+without the jar. The `gradle/wrapper/gradle-wrapper.properties` file is kept so
+the archive pins the Gradle version and distribution checksum for verification
+when the wrapper is regenerated.
 
 [LEGAL-570]: https://issues.apache.org/jira/browse/LEGAL-570
 
@@ -275,23 +277,164 @@ project's location:
 svn checkout https://dist.apache.org/repos/dist/dev/airflow <dist-dev-checkout>
 
 cd <dist-dev-checkout>
-mkdir -p java-sdk/1.0.0-beta1-rc1
-cp 
<path-to>/java-sdk/build/distributions/apache-airflow-java-sdk-1.0.0-beta1-src.tar.gz*
 \
-   java-sdk/1.0.0-beta1-rc1/
+mkdir -p java-sdk/<VERSION>-rc<N>
+cp 
<path-to>/java-sdk/build/distributions/apache-airflow-java-sdk-<VERSION>-src.tar.gz*
 \
+   java-sdk/<VERSION>-rc<N>/
 
-svn add --parents java-sdk/1.0.0-beta1-rc1
-svn commit -m "Add Apache Airflow Java SDK 1.0.0-beta1-rc1 source release 
candidate"
+svn add --parents java-sdk/<VERSION>-rc<N>
+svn commit -m "Add Apache Airflow Java SDK <VERSION>-rc<N> source release 
candidate"
 ```
 
 The commit publishes them under
-`https://dist.apache.org/repos/dist/dev/airflow/java-sdk/1.0.0-beta1-rc1/`, 
which
+`https://dist.apache.org/repos/dist/dev/airflow/java-sdk/<VERSION>-rc<N>/`, 
which
 is the source-package URL you link in the vote.
 
 ### Call the vote
 
 Send a `[VOTE]` email to `[email protected]` linking the git tag and
 commit, the source package in `dist/dev`, the closed Nexus staging repository,
-and the `KEYS` file.
+and the `KEYS` file. See "Vote email template" below for the exact fields to
+fill in, and "Verifying a release" for what to ask reviewers to check.
+
+### Vote email template
+
+```text
+Subject: [VOTE] Release Apache Airflow Java SDK <VERSION> based on 
<VERSION>-rc<N>
+
+Hi,
+
+I would like to call a vote to release Apache Airflow Java SDK <VERSION>, based
+on release candidate <VERSION>-rc<N>.
+
+Changes since <the last release>: <a one-line description>
+
+The release candidate contains the following Maven artifacts, all under group 
id org.apache.airflow:
+
+- Main SDK API
+    - airflow-sdk
+    - airflow-sdk-processor
+- Logger helpers
+    - airflow-sdk-jpl
+    - airflow-sdk-jul
+    - airflow-sdk-log4j2
+    - airflow-sdk-slf4j
+- Gradle plugin and the marker artifact
+    - airflow-sdk-gradle-plugin
+    - org.apache.airflow.sdk.gradle.plugin
+- BOM
+    - airflow-sdk-bom
+
+Git information:
+
+- Tag: java-sdk/<VERSION>-rc<N>
+- Commit: <full-commit-sha>
+- https://github.com/apache/airflow/releases/tag/java-sdk%2F<VERSION>-rc<N>
+
+Source release, signatures and checksums:
+https://dist.apache.org/repos/dist/dev/airflow/java-sdk/<VERSION>-rc<N>/
+
+Convenience binaries (staged in the ASF Nexus repository):
+https://repository.apache.org/content/repositories/orgapacheairflow-<NNNN>/
+
+KEYS file (public keys used to sign the release):
+https://downloads.apache.org/airflow/KEYS
+
+Please review and vote. The vote will remain open for at least 72 hours, until 
<YYYY-MM-DD HH:MM UTC>, or until the necessary number of binding votes is 
reached.
+
+[ ] +1 Release this package as Apache Airflow Java SDK <VERSION>
+[ ] +0 No opinion
+[ ] -1 Do not release, because ...
+
+Only votes from Airflow PMC members are binding, but everyone is welcome and 
encouraged to test the release and vote.
+
+The verification process can be found in the main repository:
+https://github.com/apache/airflow/tree/java-sdk/<VERSION>-rc<N>/java-sdk#verifying-a-release
+
+For more details on ASF release verification, see: 
https://www.apache.org/info/verification.html
+
+Best,
+<your name>
+```
+
+Pre-send checklist:
+
+* Every staged artifact above resolves in the Nexus staging repository. Cross
+  check against the BOM, not just this list from memory.
+* `Changes since rc<N-1>` and the vote deadline are filled in.
+* Run `grep '<' email.txt` on the rendered email and confirm **no output** —
+  any match means a template placeholder (`<N>`, `<NNNN>`, `<YYYY-MM-DD ...>`,
+  etc.) was left unfilled.
+
+### Verifying a release
+
+Anyone on `[email protected]` can (and should) independently verify a
+candidate before voting. Below is the checklist a reviewer — or the release
+manager, before sending the vote — should run against the source package in
+`dist/dev`.
+
+1. **Checksum.** Confirm the published SHA-512 matches the downloaded tarball:
+
+   ```bash
+   sha512sum -c apache-airflow-java-sdk-<VERSION>-src.tar.gz.sha512
+   ```
+
+2. **Signature.** Import the `KEYS` file and verify the GPG signature:
+
+   ```bash
+   curl -O https://downloads.apache.org/airflow/KEYS
+   gpg --import KEYS
+   gpg --verify apache-airflow-java-sdk-<VERSION>-src.tar.gz.asc \
+       apache-airflow-java-sdk-<VERSION>-src.tar.gz
+   ```
+
+3. **Diff against the git tag.** Extract the tarball and compare it with a
+   clean checkout of the tag it claims to be built from. They should be
+   identical except `gradlew`, `gradlew.bat`, and `gradle-wrapper.jar`. The
+   extracted top-level directory should be `apache-airflow-java-sdk-<version>`
+   without the `-src` suffix that only appears in the tarball's own filename:
+
+   ```bash
+   tar xzf apache-airflow-java-sdk-<VERSION>-src.tar.gz
+   git clone --branch java-sdk/<VERSION>-rc<N> \
+     https://github.com/apache/airflow.git tag-checkout
+   diff -rq apache-airflow-java-sdk-<VERSION>/ tag-checkout/java-sdk/ \
+     | grep -vE ': (gradlew|gradlew\.bat|gradle-wrapper\.jar)$'
+   ```
+
+   Any remaining diff output is unexpected and should block the vote.
+
+4. **No binary files.** ASF source releases must not contain compiled code.
+   Scan for anything that isn't text:
+
+   ```bash
+   find apache-airflow-java-sdk-<VERSION>/ -type f \
+     -exec sh -c 'file "$1" | grep -qv text && echo "$1"' _ {} \;
+   ```
+
+   This should print nothing.
+
+5. **Build from source.** Regenerate the Gradle wrapper from a locally 
installed
+   Gradle (see the *Upload the source package* section above):
+
+   ```bash
+   cd apache-airflow-java-sdk-<VERSION>
+   gradle wrapper \
+       --gradle-version <GRADLE-VERSION> \
+       --gradle-distribution-sha256-sum <GRADLE-DISTRIBUTION-SHA>
+   ./gradlew build
+   ```
+
+   Use the values for `<GRADLE-VERSION>` and `<GRADLE-DISTRIBUTION-SHA>` from
+   `distributionUrl` and `distributionSha256Sum` in the bundled
+   `gradle/wrapper/gradle-wrapper.properties`.
+
+6. **Staged-binary smoke test.** Resolve the staged Nexus artifacts from a
+   throwaway project to confirm they're actually consumable, following the
+   same pattern as the "Dry-run against a local repository" step: point a
+   `repositories {}` block at the staging repository URL, declare a dependency
+   on `org.apache.airflow:airflow-sdk-bom:<VERSION>`, and confirm the
+   transitive artifacts (including `airflow-sdk-jpl`) resolve and the example
+   bundle builds against them.
 
 ### After a successful vote
 
@@ -309,16 +452,16 @@ Reply with a `[RESULT][VOTE]` tally, then:
 2. **Move** the source package from `dist/dev` to `dist/release`:
 
    ```bash
-   svn mv 
https://dist.apache.org/repos/dist/dev/airflow/java-sdk/1.0.0-beta1-rc1 \
-          
https://dist.apache.org/repos/dist/release/airflow/java-sdk/1.0.0-beta1 \
-          -m "Release Apache Airflow Java SDK 1.0.0-beta1"
+   svn mv 
https://dist.apache.org/repos/dist/dev/airflow/java-sdk/<VERSION>-rc<N> \
+          
https://dist.apache.org/repos/dist/release/airflow/java-sdk/<VERSION> \
+          -m "Release Apache Airflow Java SDK <VERSION>"
    ```
 
 3. **Tag** the final version on the same commit that was voted:
 
    ```bash
-   git tag -s java-sdk/1.0.0-beta1 <voted-commit-hash> -m "Apache Airflow Java 
SDK 1.0.0-beta1"
-   git push upstream java-sdk/1.0.0-beta1
+   git tag -s java-sdk/<VERSION> <voted-commit-hash> -m "Apache Airflow Java 
SDK <VERSION>"
+   git push upstream java-sdk/<VERSION>
    ```
 
    Keep the RC tag for traceability.
@@ -330,7 +473,7 @@ Reply with a `[RESULT][VOTE]` tally, then:
 
 Close the vote, **drop** the staging repository in Nexus, remove the `dist/dev`
 candidate, fix the issue, and cut the next RC (`...-rc2`). The released version
-stays the same (e.g. `1.0.0-beta1`); only the RC counter in the tag increments.
+stays the same (e.g. `<VERSION>`); only the RC counter in the tag increments.
 
 ## Contributing
 
diff --git a/java-sdk/bom/build.gradle.kts b/java-sdk/bom/build.gradle.kts
index 1656dc3ca97..8babd7c8876 100644
--- a/java-sdk/bom/build.gradle.kts
+++ b/java-sdk/bom/build.gradle.kts
@@ -17,7 +17,10 @@
  * under the License.
  */
 
+import org.gradle.api.publish.PublishingExtension
+
 plugins {
+    base
     `java-platform`
     id("airflow-publish")
 }
@@ -28,6 +31,8 @@ val airflowSupervisorSchemaVersion: String by project
 dependencies {
     constraints {
         api("org.apache.airflow:airflow-sdk:$projectVersion")
+        api("org.apache.airflow:airflow-sdk-gradle-plugin:$projectVersion")
+        api("org.apache.airflow:airflow-sdk-jpl:$projectVersion")
         api("org.apache.airflow:airflow-sdk-jul:$projectVersion")
         api("org.apache.airflow:airflow-sdk-log4j2:$projectVersion")
         api("org.apache.airflow:airflow-sdk-processor:${projectVersion}")
@@ -48,3 +53,45 @@ publishing {
         }
     }
 }
+
+// What the BOM currently constrains (group org.apache.airflow).
+val bomArtifacts =
+    configurations["api"].dependencyConstraints
+        .filter { it.group == "org.apache.airflow" }
+        .map { it.name }
+        .toSet()
+
+val publishedArtifacts =
+    rootProject.subprojects
+        .filter { it.path != project.path }
+        .flatMap { sub ->
+            evaluationDependsOn(sub.path)
+            sub.extensions.findByType(PublishingExtension::class.java)
+                ?.publications
+                ?.withType(MavenPublication::class.java)
+                ?.filter { it.groupId == "org.apache.airflow" }
+                ?.map { it.artifactId }
+                ?: emptyList()
+        }
+        .toSet()
+
+val verifyBomCoverage by tasks.registering {
+    group = "verification"
+    description = "Fail if airflow-sdk-bom does not constrain the same set of 
published Java SDK artifacts."
+    doLast {
+        val missing = (publishedArtifacts - bomArtifacts).sorted()
+        val stale = (bomArtifacts - publishedArtifacts).sorted()
+        if (missing.isNotEmpty() || stale.isNotEmpty()) {
+            throw GradleException(
+                buildString {
+                    appendLine("airflow-sdk-bom is out of sync with the 
published Java SDK artifacts:")
+                    if (missing.isNotEmpty()) appendLine("  published but 
missing from the BOM: $missing")
+                    if (stale.isNotEmpty()) appendLine("  listed in the BOM 
but not published: $stale")
+                    append("Update constraints in bom/build.gradle.kts to 
match.")
+                },
+            )
+        }
+    }
+}
+
+tasks.named("check") { dependsOn(verifyBomCoverage) }
diff --git 
a/java-sdk/buildSrc/src/main/kotlin/airflow-jvm-conventions.gradle.kts 
b/java-sdk/buildSrc/src/main/kotlin/airflow-jvm-conventions.gradle.kts
index 5a5c6b873aa..75fa4282f93 100644
--- a/java-sdk/buildSrc/src/main/kotlin/airflow-jvm-conventions.gradle.kts
+++ b/java-sdk/buildSrc/src/main/kotlin/airflow-jvm-conventions.gradle.kts
@@ -53,3 +53,17 @@ configure<SpotlessExtension> {
         endWithNewline()
     }
 }
+
+tasks.withType<Jar>().configureEach {
+    metaInf {
+        from(rootProject.layout.projectDirectory.file("LICENSE"))
+        from(rootProject.layout.projectDirectory.file("NOTICE"))
+    }
+}
+
+tasks.withType<AbstractArchiveTask>().configureEach {
+    isPreserveFileTimestamps = false
+    isReproducibleFileOrder = true
+    dirPermissions { unix("755") }
+    filePermissions { unix("644") }
+}

Reply via email to