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

sxnan pushed a commit to branch release-0.2
in repository https://gitbox.apache.org/repos/asf/flink-agents.git


The following commit(s) were added to refs/heads/release-0.2 by this push:
     new 71b49bce [infra] Download JARs from Maven Central during pip install 
(#567)
71b49bce is described below

commit 71b49bce474092008cff92c2179450e6463d7967
Author: Xuannan <[email protected]>
AuthorDate: Wed Mar 18 16:16:28 2026 +0800

    [infra] Download JARs from Maven Central during pip install (#567)
---
 .github/workflows/ci.yml                      |  21 ++
 .gitignore                                    |  16 +-
 dist/common/pom.xml                           |   9 -
 dist/flink-1.20/pom.xml                       |   4 +-
 dist/flink-2.0/pom.xml                        |   4 +-
 dist/flink-2.1/pom.xml                        |   5 +-
 dist/flink-2.2/pom.xml                        |   5 +-
 docs/content/docs/get-started/installation.md |   9 +
 python/MANIFEST.in                            |  22 ++
 python/_build_backend/backend.py              | 183 +++++++++++++
 python/_build_backend/tests/__init__.py       |  17 ++
 python/_build_backend/tests/test_backend.py   | 359 ++++++++++++++++++++++++++
 python/jar_manifest.json                      |  36 +++
 python/pyproject.toml                         |   6 +-
 tools/releasing/create_release_branch.sh      |   2 +
 tools/releasing/update_branch_version.sh      |   2 +
 16 files changed, 675 insertions(+), 25 deletions(-)

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index e555fd43..96cd3edf 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -48,6 +48,27 @@ jobs:
       - name: Check code style
         run: ./tools/lint.sh -c
 
+  build_backend_tests:
+    name: ut-build-backend
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v4
+      - name: Install python
+        uses: actions/setup-python@v4
+        with:
+          python-version: '3.12'
+      - name: Install uv
+        uses: astral-sh/setup-uv@v4
+        with:
+          version: "latest"
+      - name: Run build-backend tests
+        working-directory: python
+        run: |
+          uv sync --extra test
+          PYTHONPATH="${PWD}" uv run --no-sync pytest _build_backend/tests \
+            -o log_cli=true \
+            -o log_cli_level=INFO
+
   java_tests:
     name: ut-java [${{ matrix.os }}] [java-${{ matrix.java-version}}]
     runs-on: ${{ matrix.os }}
diff --git a/.gitignore b/.gitignore
index 345b3c23..54c83287 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,15 +1,23 @@
+# IDE
 .idea/*
-target/
 .vscode
 *.iml
+.qwen
+.qoder
+
+# OS
 .DS_Store
+python/.DS_Store
+
+# Build
+target/
 python/.idea/
-python/flink_agents.egg-info/
-python/flink_agents/flink_agents.egg-info/
 python/build/
 python/dist
+python/flink_agents.egg-info/
+python/flink_agents/flink_agents.egg-info/
+python/flink_agents/lib/
 python/uv.lock
-python/.DS_Store
 __pycache__
 **/dependency-reduced-pom.xml
 /tools/releasing/release/
diff --git a/dist/common/pom.xml b/dist/common/pom.xml
index 2be34afc..2d6b6dbc 100644
--- a/dist/common/pom.xml
+++ b/dist/common/pom.xml
@@ -38,15 +38,6 @@ under the License.
 
     <build>
         <plugins>
-            <!-- Skip deployment - this module is for Python wheel 
optimization only -->
-            <plugin>
-                <groupId>org.apache.maven.plugins</groupId>
-                <artifactId>maven-deploy-plugin</artifactId>
-                <configuration>
-                    <skip>true</skip>
-                </configuration>
-            </plugin>
-
             <!-- Copy the shared LICENSE/NOTICE files from parent -->
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
diff --git a/dist/flink-1.20/pom.xml b/dist/flink-1.20/pom.xml
index 19102cb9..e6a28489 100644
--- a/dist/flink-1.20/pom.xml
+++ b/dist/flink-1.20/pom.xml
@@ -158,8 +158,8 @@ under the License.
                         </goals>
                         <configuration>
                             <shadeTestJar>false</shadeTestJar>
-                            
<shadedArtifactAttached>false</shadedArtifactAttached>
-                            
<finalName>${project.artifactId}-${project.version}-thin</finalName>
+                            
<shadedArtifactAttached>true</shadedArtifactAttached>
+                            <shadedClassifierName>thin</shadedClassifierName>
                             <!-- Only include flink-agents artifacts -->
                             <artifactSet>
                                 <includes>
diff --git a/dist/flink-2.0/pom.xml b/dist/flink-2.0/pom.xml
index 3e72c2dd..a8c13273 100644
--- a/dist/flink-2.0/pom.xml
+++ b/dist/flink-2.0/pom.xml
@@ -97,8 +97,8 @@
                         </goals>
                         <configuration>
                             <shadeTestJar>false</shadeTestJar>
-                            
<shadedArtifactAttached>false</shadedArtifactAttached>
-                            
<finalName>${project.artifactId}-${project.version}-thin</finalName>
+                            
<shadedArtifactAttached>true</shadedArtifactAttached>
+                            <shadedClassifierName>thin</shadedClassifierName>
                             <!-- Only include flink-agents artifacts -->
                             <artifactSet>
                                 <includes>
diff --git a/dist/flink-2.1/pom.xml b/dist/flink-2.1/pom.xml
index b5c6b54d..0367c8ae 100644
--- a/dist/flink-2.1/pom.xml
+++ b/dist/flink-2.1/pom.xml
@@ -85,7 +85,6 @@
             </plugin>
 
             <!-- Build thin jar for Python wheel (only flink-agents code) -->
-            <!-- Not attached as Maven artifact to avoid deployment -->
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-shade-plugin</artifactId>
@@ -98,8 +97,8 @@
                         </goals>
                         <configuration>
                             <shadeTestJar>false</shadeTestJar>
-                            
<shadedArtifactAttached>false</shadedArtifactAttached>
-                            
<finalName>${project.artifactId}-${project.version}-thin</finalName>
+                            
<shadedArtifactAttached>true</shadedArtifactAttached>
+                            <shadedClassifierName>thin</shadedClassifierName>
                             <!-- Only include flink-agents artifacts -->
                             <artifactSet>
                                 <includes>
diff --git a/dist/flink-2.2/pom.xml b/dist/flink-2.2/pom.xml
index 8cff9757..2d0b3f7a 100644
--- a/dist/flink-2.2/pom.xml
+++ b/dist/flink-2.2/pom.xml
@@ -56,7 +56,6 @@ under the License.
             </plugin>
 
             <!-- Build thin jar for Python wheel (only flink-agents code) -->
-            <!-- Not attached as Maven artifact to avoid deployment -->
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-shade-plugin</artifactId>
@@ -69,8 +68,8 @@ under the License.
                         </goals>
                         <configuration>
                             <shadeTestJar>false</shadeTestJar>
-                            
<shadedArtifactAttached>false</shadedArtifactAttached>
-                            
<finalName>${project.artifactId}-${project.version}-thin</finalName>
+                            
<shadedArtifactAttached>true</shadedArtifactAttached>
+                            <shadedClassifierName>thin</shadedClassifierName>
                             <!-- Only include flink-agents artifacts -->
                             <artifactSet>
                                 <includes>
diff --git a/docs/content/docs/get-started/installation.md 
b/docs/content/docs/get-started/installation.md
index 5bf8ce6c..16a2ea06 100644
--- a/docs/content/docs/get-started/installation.md
+++ b/docs/content/docs/get-started/installation.md
@@ -166,6 +166,15 @@ After building:
 - The Python package is installed and ready to use
 - The distribution JAR is located at: 
`dist/flink-${FLINK_VERSION%.*}/target/flink-agents-dist-*.jar`
 
+### Build Environment Variables
+
+The following environment variables can be used to control how JARs are 
resolved during `pip install flink-agents` (from sdist) or `python -m build`:
+
+| Variable | Description |
+|----------|-------------|
+| `FLINK_AGENTS_SKIP_JAR_DOWNLOAD` | Set to `1`, `true`, `yes`, or `on` 
(case-insensitive) to skip downloading JARs from Maven Central. Useful when 
building from source with `tools/build.sh`, which copies JARs from the local 
Maven build. |
+| `FLINK_AGENTS_MAVEN_MIRROR` | Override the Maven repository base URL for JAR 
downloads. Defaults to `https://repo1.maven.org/maven2`. Useful for 
environments with restricted network access or internal mirrors. |
+
 ## Maven Dependencies (For Java)
 
 For developing Flink Agents applications in Java, add the following 
dependencies to your `pom.xml`:
diff --git a/python/MANIFEST.in b/python/MANIFEST.in
new file mode 100644
index 00000000..39ac82a6
--- /dev/null
+++ b/python/MANIFEST.in
@@ -0,0 +1,22 @@
+#    Licensed to the Apache Software Foundation (ASF) under one
+#   or more contributor license agreements.  See the NOTICE file
+#   distributed with this work for additional information
+#   regarding copyright ownership.  The ASF licenses this file
+#   to you under the Apache License, Version 2.0 (the
+#   "License"); you may not use this file except in compliance
+#   with the License.  You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#  limitations under the License.
+
+# Include the custom build backend in source distributions
+include _build_backend/*.py
+include _build_backend/tests/*.py
+
+# Include jar_manifest.json if it exists
+include jar_manifest.json
diff --git a/python/_build_backend/backend.py b/python/_build_backend/backend.py
new file mode 100644
index 00000000..30811300
--- /dev/null
+++ b/python/_build_backend/backend.py
@@ -0,0 +1,183 @@
+################################################################################
+#  Licensed to the Apache Software Foundation (ASF) under one
+#  or more contributor license agreements.  See the NOTICE file
+#  distributed with this work for additional information
+#  regarding copyright ownership.  The ASF licenses this file
+#  to you under the Apache License, Version 2.0 (the
+#  "License"); you may not use this file except in compliance
+#  with the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+# limitations under the License.
+#################################################################################
+
+"""Custom PEP 517 build backend that downloads JARs from Maven Central.
+
+Wraps ``setuptools.build_meta`` and overrides ``build_wheel()`` to download
+JAR files before the standard setuptools build runs.  This ensures that
+``pip install flink-agents`` (from sdist) produces a wheel that already
+contains the required JARs.
+"""
+
+from __future__ import annotations
+
+import hashlib
+import json
+import logging
+import os
+import urllib.request
+from pathlib import Path
+
+from setuptools.build_meta import (
+    build_sdist,
+    get_requires_for_build_sdist,
+    get_requires_for_build_wheel,
+    prepare_metadata_for_build_wheel,
+)
+from setuptools.build_meta import (
+    build_wheel as _setuptools_build_wheel,
+)
+
+__all__ = [
+    "build_sdist",
+    "build_wheel",
+    "get_requires_for_build_sdist",
+    "get_requires_for_build_wheel",
+    "prepare_metadata_for_build_wheel",
+]
+
+# PEP 660 editable install hooks (setuptools >= 64)
+try:
+    from setuptools.build_meta import (
+        build_editable,
+        get_requires_for_build_editable,
+        prepare_metadata_for_build_editable,
+    )
+
+    __all__ += [
+        "build_editable",
+        "get_requires_for_build_editable",
+        "prepare_metadata_for_build_editable",
+    ]
+except ImportError:
+    pass
+
+logger = logging.getLogger(__name__)
+
+_MANIFEST_FILE = "jar_manifest.json"
+_SKIP_ENV_VAR = "FLINK_AGENTS_SKIP_JAR_DOWNLOAD"
+_MIRROR_ENV_VAR = "FLINK_AGENTS_MAVEN_MIRROR"
+_TRUTHY_VALUES = frozenset({"1", "true", "yes", "on"})
+
+
+# ---------------------------------------------------------------------------
+# Public PEP 517 hook
+# ---------------------------------------------------------------------------
+
+
+def build_wheel(
+    wheel_directory: str,
+    config_settings: dict | None = None,
+    metadata_directory: str | None = None,
+) -> str:
+    """Build wheel after downloading JARs from Maven Central."""
+    _ensure_jars()
+    return _setuptools_build_wheel(
+        wheel_directory,
+        config_settings=config_settings,
+        metadata_directory=metadata_directory,
+    )
+
+
+# ---------------------------------------------------------------------------
+# Internal helpers
+# ---------------------------------------------------------------------------
+
+
+def _ensure_jars() -> None:
+    """Download JARs listed in *jar_manifest.json* if they are not present."""
+    manifest_path = Path(_MANIFEST_FILE)
+    if not manifest_path.exists():
+        logger.info("No %s found - skipping JAR download.", _MANIFEST_FILE)
+        return
+
+    if os.environ.get(_SKIP_ENV_VAR, "").lower() in _TRUTHY_VALUES:
+        logger.info(
+            "%s is set - skipping JAR download.",
+            _SKIP_ENV_VAR,
+        )
+        return
+
+    manifest = _load_manifest(manifest_path)
+    maven_base_url = os.environ.get(_MIRROR_ENV_VAR) or 
manifest["maven_base_url"]
+    maven_base_url = maven_base_url.rstrip("/")
+    group_path = manifest["group_id"].replace(".", "/")
+    version = manifest["version"]
+
+    for jar_entry in manifest["jars"]:
+        _download_jar(jar_entry, maven_base_url, group_path, version)
+
+
+def _load_manifest(path: Path) -> dict:
+    """Read and parse *jar_manifest.json*."""
+    with path.open() as f:
+        return json.load(f)
+
+
+def _jar_filename(jar_entry: dict, version: str) -> str:
+    """Construct the JAR filename from manifest entry fields."""
+    artifact_id = jar_entry["artifact_id"]
+    classifier = jar_entry.get("classifier")
+    if classifier:
+        return f"{artifact_id}-{version}-{classifier}.jar"
+    return f"{artifact_id}-{version}.jar"
+
+
+def _download_jar(
+    jar_entry: dict,
+    maven_base_url: str,
+    group_path: str,
+    version: str,
+) -> None:
+    """Download a single JAR if it does not already exist locally."""
+    filename = _jar_filename(jar_entry, version)
+    dest_dir = Path(jar_entry["dest"])
+    dest_path = dest_dir / filename
+
+    if dest_path.exists():
+        logger.info("JAR already exists: %s - skipping download.", dest_path)
+        return
+
+    artifact_id = jar_entry["artifact_id"]
+    url = f"{maven_base_url}/{group_path}/{artifact_id}/{version}/{filename}"
+
+    logger.info("Downloading %s ...", url)
+    dest_dir.mkdir(parents=True, exist_ok=True)
+
+    try:
+        urllib.request.urlretrieve(url, dest_path)
+    except Exception:
+        # Clean up partial download
+        dest_path.unlink(missing_ok=True)
+        raise
+
+    _verify_checksum(dest_path, jar_entry["sha256"])
+    logger.info("Downloaded and verified: %s", dest_path)
+
+
+def _verify_checksum(path: Path, expected_sha256: str) -> None:
+    """Verify the SHA-256 checksum of a downloaded file."""
+    sha256 = hashlib.sha256()
+    with path.open("rb") as f:
+        for chunk in iter(lambda: f.read(8192), b""):
+            sha256.update(chunk)
+    actual = sha256.hexdigest()
+    if actual != expected_sha256:
+        path.unlink(missing_ok=True)
+        msg = f"SHA-256 mismatch for {path}: expected {expected_sha256}, got 
{actual}"
+        raise ValueError(msg)
diff --git a/python/_build_backend/tests/__init__.py 
b/python/_build_backend/tests/__init__.py
new file mode 100644
index 00000000..e154fadd
--- /dev/null
+++ b/python/_build_backend/tests/__init__.py
@@ -0,0 +1,17 @@
+################################################################################
+#  Licensed to the Apache Software Foundation (ASF) under one
+#  or more contributor license agreements.  See the NOTICE file
+#  distributed with this work for additional information
+#  regarding copyright ownership.  The ASF licenses this file
+#  to you under the Apache License, Version 2.0 (the
+#  "License"); you may not use this file except in compliance
+#  with the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+# limitations under the License.
+#################################################################################
diff --git a/python/_build_backend/tests/test_backend.py 
b/python/_build_backend/tests/test_backend.py
new file mode 100644
index 00000000..3883dc70
--- /dev/null
+++ b/python/_build_backend/tests/test_backend.py
@@ -0,0 +1,359 @@
+################################################################################
+#  Licensed to the Apache Software Foundation (ASF) under one
+#  or more contributor license agreements.  See the NOTICE file
+#  distributed with this work for additional information
+#  regarding copyright ownership.  The ASF licenses this file
+#  to you under the Apache License, Version 2.0 (the
+#  "License"); you may not use this file except in compliance
+#  with the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+# limitations under the License.
+#################################################################################
+from __future__ import annotations
+
+import hashlib
+import http.server
+import json
+import threading
+import zipfile
+from typing import TYPE_CHECKING
+
+import pytest
+
+if TYPE_CHECKING:
+    from pathlib import Path
+from _build_backend.backend import (
+    _ensure_jars,
+    _jar_filename,
+    _load_manifest,
+    _verify_checksum,
+)
+
+# ---------------------------------------------------------------------------
+# Helpers
+# ---------------------------------------------------------------------------
+
+
+def _sha256(data: bytes) -> str:
+    return hashlib.sha256(data).hexdigest()
+
+
+def _write_manifest(path: Path, manifest: dict) -> None:
+    with path.open("w") as f:
+        json.dump(manifest, f)
+
+
+# ---------------------------------------------------------------------------
+# Unit tests
+# ---------------------------------------------------------------------------
+
+
+class TestJarFilename:  # noqa: D101
+    def test_without_classifier(self) -> None:
+        entry = {
+            "artifact_id": "flink-agents-dist-common",
+            "dest": "lib/common/",
+            "sha256": "abc",
+        }
+        assert _jar_filename(entry, "0.3.0") == 
"flink-agents-dist-common-0.3.0.jar"
+
+    def test_with_classifier(self) -> None:
+        entry = {
+            "artifact_id": "flink-agents-dist-flink-2.2",
+            "classifier": "thin",
+            "dest": "lib/flink-2.2/",
+            "sha256": "abc",
+        }
+        assert (
+            _jar_filename(entry, "0.3.0")
+            == "flink-agents-dist-flink-2.2-0.3.0-thin.jar"
+        )
+
+
+class TestLoadManifest:  # noqa: D101
+    def test_load(self, tmp_path) -> None:
+        manifest = {
+            "maven_base_url": "https://repo1.maven.org/maven2";,
+            "group_id": "org.apache.flink",
+            "version": "0.3.0",
+            "jars": [],
+        }
+        _write_manifest(tmp_path / "jar_manifest.json", manifest)
+        loaded = _load_manifest(tmp_path / "jar_manifest.json")
+        assert loaded == manifest
+
+
+class TestVerifyChecksum:  # noqa: D101
+    def test_valid_checksum(self, tmp_path) -> None:
+        content = b"fake jar content"
+        jar = tmp_path / "test.jar"
+        jar.write_bytes(content)
+        _verify_checksum(jar, _sha256(content))
+
+    def test_invalid_checksum(self, tmp_path) -> None:
+        jar = tmp_path / "test.jar"
+        jar.write_bytes(b"fake jar content")
+        with pytest.raises(ValueError, match="SHA-256 mismatch"):
+            _verify_checksum(
+                jar, 
"0000000000000000000000000000000000000000000000000000000000000000"
+            )
+        assert not jar.exists()
+
+
+class TestEnsureJars:  # noqa: D101
+    def test_skip_when_no_manifest(self, tmp_path, monkeypatch) -> None:
+        monkeypatch.chdir(tmp_path)
+        _ensure_jars()
+
+    def test_skip_when_env_var_set(self, tmp_path, monkeypatch) -> None:
+        monkeypatch.chdir(tmp_path)
+        _write_manifest(
+            tmp_path / "jar_manifest.json",
+            {
+                "maven_base_url": "http://example.com";,
+                "group_id": "org.apache.flink",
+                "version": "0.3.0",
+                "jars": [{"artifact_id": "a", "dest": "lib/", "sha256": 
"abc"}],
+            },
+        )
+        monkeypatch.setenv("FLINK_AGENTS_SKIP_JAR_DOWNLOAD", "true")
+        _ensure_jars()
+        assert not (tmp_path / "lib").exists()
+
+    def test_skip_when_jar_already_exists(self, tmp_path, monkeypatch) -> None:
+        monkeypatch.chdir(tmp_path)
+        dest = tmp_path / "lib" / "common"
+        dest.mkdir(parents=True)
+        jar = dest / "flink-agents-dist-common-0.3.0.jar"
+        jar.write_bytes(b"existing")
+
+        _write_manifest(
+            tmp_path / "jar_manifest.json",
+            {
+                "maven_base_url": "http://localhost:1";,
+                "group_id": "org.apache.flink",
+                "version": "0.3.0",
+                "jars": [
+                    {
+                        "artifact_id": "flink-agents-dist-common",
+                        "dest": "lib/common/",
+                        "sha256": "abc",
+                    }
+                ],
+            },
+        )
+        _ensure_jars()
+        assert jar.read_bytes() == b"existing"
+
+
+# ---------------------------------------------------------------------------
+# Integration test — local HTTP server
+# ---------------------------------------------------------------------------
+
+
+class _JarHandler(http.server.SimpleHTTPRequestHandler):
+    """Serve fake JARs from a directory."""
+
+    def log_message(self, format, *args: object) -> None:
+        pass
+
+
[email protected](scope="module")
+def maven_server(tmp_path_factory) -> tuple[str, Path]:
+    """Start a local HTTP server that serves fake Maven artifacts."""
+    serve_dir = tmp_path_factory.mktemp("maven_repo")
+
+    fake_jar_content = b"PK\x03\x04fake-jar-bytes-for-testing"
+    sha = _sha256(fake_jar_content)
+
+    group_path = "org/apache/flink"
+    version = "0.3.0"
+    artifacts = [
+        ("flink-agents-dist-common", None),
+        ("flink-agents-dist-flink-1.20", "thin"),
+        ("flink-agents-dist-flink-2.0", "thin"),
+        ("flink-agents-dist-flink-2.1", "thin"),
+        ("flink-agents-dist-flink-2.2", "thin"),
+    ]
+
+    for artifact_id, classifier in artifacts:
+        if classifier:
+            filename = f"{artifact_id}-{version}-{classifier}.jar"
+        else:
+            filename = f"{artifact_id}-{version}.jar"
+        artifact_dir = serve_dir / group_path / artifact_id / version
+        artifact_dir.mkdir(parents=True, exist_ok=True)
+        (artifact_dir / filename).write_bytes(fake_jar_content)
+
+    def handler(*args: object, **kwargs: object) -> _JarHandler:
+        return _JarHandler(*args, directory=str(serve_dir), **kwargs)
+
+    server = http.server.HTTPServer(("127.0.0.1", 0), handler)
+    port = server.server_address[1]
+    thread = threading.Thread(target=server.serve_forever, daemon=True)
+    thread.start()
+
+    yield f"http://127.0.0.1:{port}";, sha
+
+    server.shutdown()
+
+
+def test_download_jars_from_local_server(tmp_path, monkeypatch, maven_server) 
-> None:
+    base_url, expected_sha = maven_server
+    monkeypatch.chdir(tmp_path)
+    monkeypatch.setenv("FLINK_AGENTS_MAVEN_MIRROR", base_url)
+
+    manifest = {
+        "maven_base_url": "https://should-not-be-used.example.com";,
+        "group_id": "org.apache.flink",
+        "version": "0.3.0",
+        "jars": [
+            {
+                "artifact_id": "flink-agents-dist-common",
+                "dest": "flink_agents/lib/common/",
+                "sha256": expected_sha,
+            },
+            {
+                "artifact_id": "flink-agents-dist-flink-1.20",
+                "classifier": "thin",
+                "dest": "flink_agents/lib/flink-1.20/",
+                "sha256": expected_sha,
+            },
+            {
+                "artifact_id": "flink-agents-dist-flink-2.0",
+                "classifier": "thin",
+                "dest": "flink_agents/lib/flink-2.0/",
+                "sha256": expected_sha,
+            },
+            {
+                "artifact_id": "flink-agents-dist-flink-2.1",
+                "classifier": "thin",
+                "dest": "flink_agents/lib/flink-2.1/",
+                "sha256": expected_sha,
+            },
+            {
+                "artifact_id": "flink-agents-dist-flink-2.2",
+                "classifier": "thin",
+                "dest": "flink_agents/lib/flink-2.2/",
+                "sha256": expected_sha,
+            },
+        ],
+    }
+    _write_manifest(tmp_path / "jar_manifest.json", manifest)
+
+    _ensure_jars()
+
+    assert (
+        tmp_path / "flink_agents/lib/common/flink-agents-dist-common-0.3.0.jar"
+    ).exists()
+    assert (
+        tmp_path
+        / 
"flink_agents/lib/flink-1.20/flink-agents-dist-flink-1.20-0.3.0-thin.jar"
+    ).exists()
+    assert (
+        tmp_path
+        / 
"flink_agents/lib/flink-2.0/flink-agents-dist-flink-2.0-0.3.0-thin.jar"
+    ).exists()
+    assert (
+        tmp_path
+        / 
"flink_agents/lib/flink-2.1/flink-agents-dist-flink-2.1-0.3.0-thin.jar"
+    ).exists()
+    assert (
+        tmp_path
+        / 
"flink_agents/lib/flink-2.2/flink-agents-dist-flink-2.2-0.3.0-thin.jar"
+    ).exists()
+
+
+def test_download_fails_on_checksum_mismatch(
+    tmp_path, monkeypatch, maven_server
+) -> None:
+    base_url, _ = maven_server
+    monkeypatch.chdir(tmp_path)
+    monkeypatch.setenv("FLINK_AGENTS_MAVEN_MIRROR", base_url)
+
+    manifest = {
+        "maven_base_url": "https://should-not-be-used.example.com";,
+        "group_id": "org.apache.flink",
+        "version": "0.3.0",
+        "jars": [
+            {
+                "artifact_id": "flink-agents-dist-common",
+                "dest": "flink_agents/lib/common/",
+                "sha256": "bad_checksum",
+            },
+        ],
+    }
+    _write_manifest(tmp_path / "jar_manifest.json", manifest)
+
+    with pytest.raises(ValueError, match="SHA-256 mismatch"):
+        _ensure_jars()
+
+    assert not (
+        tmp_path / "flink_agents/lib/common/flink-agents-dist-common-0.3.0.jar"
+    ).exists()
+
+
+def test_build_wheel_produces_wheel_with_jars(
+    tmp_path, monkeypatch, maven_server
+) -> None:
+    """Integration test: build a wheel and verify it contains downloaded 
JARs."""
+    base_url, expected_sha = maven_server
+
+    # Use a subdirectory as the "project root" to isolate from wheel output dir
+    project_dir = tmp_path / "project"
+    project_dir.mkdir()
+    monkeypatch.chdir(project_dir)
+    monkeypatch.setenv("FLINK_AGENTS_MAVEN_MIRROR", base_url)
+
+    # Minimal Python package structure (with __init__.py in lib dirs so
+    # setuptools discovers them as packages for package-data inclusion)
+    lib_common = project_dir / "flink_agents" / "lib" / "common"
+    lib_common.mkdir(parents=True)
+    (project_dir / "flink_agents" / "__init__.py").write_text("")
+    (project_dir / "flink_agents" / "lib" / "__init__.py").write_text("")
+    (project_dir / "flink_agents" / "lib" / "common" / 
"__init__.py").write_text("")
+
+    manifest = {
+        "maven_base_url": "https://should-not-be-used.example.com";,
+        "group_id": "org.apache.flink",
+        "version": "0.3.0",
+        "jars": [
+            {
+                "artifact_id": "flink-agents-dist-common",
+                "dest": "flink_agents/lib/common/",
+                "sha256": expected_sha,
+            },
+        ],
+    }
+    _write_manifest(project_dir / "jar_manifest.json", manifest)
+
+    (project_dir / "setup.cfg").write_text(
+        "[metadata]\n"
+        "name = test-pkg\n"
+        "version = 0.0.1\n\n"
+        "[options]\n"
+        "packages = find:\n\n"
+        "[options.package_data]\n"
+        "flink_agents.lib = **/*.jar\n"
+    )
+
+    from _build_backend.backend import build_wheel
+
+    wheel_dir = tmp_path / "wheel_out"
+    wheel_dir.mkdir()
+    wheel_name = build_wheel(str(wheel_dir))
+
+    wheel_path = wheel_dir / wheel_name
+    assert wheel_path.exists()
+
+    with zipfile.ZipFile(wheel_path) as zf:
+        names = zf.namelist()
+        jar_entries = [n for n in names if n.endswith(".jar")]
+        assert len(jar_entries) == 1
+        assert any("flink-agents-dist-common-0.3.0.jar" in n for n in 
jar_entries)
diff --git a/python/jar_manifest.json b/python/jar_manifest.json
new file mode 100644
index 00000000..5077c4d1
--- /dev/null
+++ b/python/jar_manifest.json
@@ -0,0 +1,36 @@
+{
+  "maven_base_url": "https://repo1.maven.org/maven2";,
+  "group_id": "org.apache.flink",
+  "version": "0.3-SNAPSHOT",
+  "jars": [
+    {
+      "artifact_id": "flink-agents-dist-common",
+      "dest": "flink_agents/lib/common/",
+      "sha256": "PLACEHOLDER"
+    },
+    {
+      "artifact_id": "flink-agents-dist-flink-1.20",
+      "classifier": "thin",
+      "dest": "flink_agents/lib/flink-1.20/",
+      "sha256": "PLACEHOLDER"
+    },
+    {
+      "artifact_id": "flink-agents-dist-flink-2.0",
+      "classifier": "thin",
+      "dest": "flink_agents/lib/flink-2.0/",
+      "sha256": "PLACEHOLDER"
+    },
+    {
+      "artifact_id": "flink-agents-dist-flink-2.1",
+      "classifier": "thin",
+      "dest": "flink_agents/lib/flink-2.1/",
+      "sha256": "PLACEHOLDER"
+    },
+    {
+      "artifact_id": "flink-agents-dist-flink-2.2",
+      "classifier": "thin",
+      "dest": "flink_agents/lib/flink-2.2/",
+      "sha256": "PLACEHOLDER"
+    }
+  ]
+}
diff --git a/python/pyproject.toml b/python/pyproject.toml
index fe27c9ca..d297f905 100644
--- a/python/pyproject.toml
+++ b/python/pyproject.toml
@@ -19,9 +19,9 @@
 # Minimum requirements for the build system to execute.
 requires = [
     "setuptools>=75.3,<82",
-    "wheel",
 ]
-build-backend = "setuptools.build_meta"
+build-backend = "backend"
+backend-path = ["_build_backend"]
 
 [project]
 name = "flink-agents"
@@ -62,6 +62,7 @@ include-package-data = true
 
 [tool.setuptools.packages.find]
 where = [""]
+exclude = ["_build_backend*"]
 
 [tool.setuptools.package-data]
 "flink_agents.lib" = ["**/*.jar"]
@@ -179,6 +180,7 @@ ignore = [
 [tool.ruff.lint.per-file-ignores]
 "dependencies.py" = ["ICN001"]
 "tests/**/*.py" = ["ANN001", "ANN201", "D100", "D102", "D103", "B018", 
"FBT001"]
+"_build_backend/tests/**/*.py" = ["ANN001", "ANN201", "D100", "D102", "D103", 
"B018", "FBT001"]
 
 [tool.ruff.lint.pycodestyle]
 max-doc-length = 88
diff --git a/tools/releasing/create_release_branch.sh 
b/tools/releasing/create_release_branch.sh
index ebe76832..fd9184e3 100755
--- a/tools/releasing/create_release_branch.sh
+++ b/tools/releasing/create_release_branch.sh
@@ -66,6 +66,8 @@ cd ..
 #change version of flink-agents
 cd python
 perl -pi -e "s#^version = \".*\"#version = \"${NEW_VERSION}\"#" pyproject.toml
+# Update jar_manifest.json version to match the Maven release version
+perl -pi -e "s#\"version\": \".*\"#\"version\": \"${NEW_VERSION}\"#" 
jar_manifest.json
 cd ..
 
 git commit -am "Commit for release $NEW_VERSION"
diff --git a/tools/releasing/update_branch_version.sh 
b/tools/releasing/update_branch_version.sh
index 7fc9c389..92152845 100755
--- a/tools/releasing/update_branch_version.sh
+++ b/tools/releasing/update_branch_version.sh
@@ -56,6 +56,8 @@ cd ..
 cd python
 perl -pi -e "s#^version = \".*\"#version = \"${NEW_VERSION}\"#" pyproject.toml
 perl -pi -e "s#-SNAPSHOT#\\.dev0#" pyproject.toml
+# Update jar_manifest.json version to match project version
+perl -pi -e "s#\"version\": \".*\"#\"version\": \"${NEW_VERSION}\"#" 
jar_manifest.json
 cd ..
 
 git commit -am "Update version to $NEW_VERSION"

Reply via email to