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"