You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@iceberg.apache.org by gi...@apache.org on 2026/10/09 22:37:56 UTC

(iceberg-rust) branch main updated: chore(release): port the dependency license check from shell to Python (#3379)

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

github-merge-queue[bot] pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/iceberg-rust.git


The following commit(s) were added to refs/heads/main by this push:
     new e8ec7f840 chore(release): port the dependency license check from shell to Python (#3379)
e8ec7f840 is described below

commit e8ec7f8403554496db976700eb993459e0ab7f9b
Author: Danny C Jones <dj...@amazon.com>
AuthorDate: Fri Oct 9 22:31:27 2026 +0000

    chore(release): port the dependency license check from shell to Python (#3379)
    
    This change migrates from the old shell script for cargo-deny to a new Python script.
    This new script will contain logic for dealing with dependencies and their licenses. This commit simply translates the existing logic.
    
    In later changes, this script will be extended with:
    - Clarifications for existing dependencies where licenses are misreported, with additional verifications added in the script itself
    - Logic for generating the attribution documentation for the pyiceberg-core package.
---
 .github/workflows/ci.yml    |   6 +-
 Makefile                    |   2 +-
 dev/release/README.md       |   4 +-
 dev/release/create_rc.sh    |   3 +-
 dev/release/dependencies.sh | 148 --------------------------------------------
 dev/release/licenses.py     | 101 ++++++++++++++++++++++++++++++
 website/src/release.md      |   2 +-
 7 files changed, 113 insertions(+), 153 deletions(-)

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 2c57b4799..9369a91e8 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -61,6 +61,10 @@ jobs:
         with:
           tool: taplo@0.9.3,cargo-machete@0.7.0,cargo-deny@0.19.9
 
+      - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
+        with:
+          python-version: 3.12
+
       - name: Check License Header
         uses: apache/skywalking-eyes/header@a196742f472feaffafea537ce5a2a4c3c53a8de4 # v0.9.0
 
@@ -80,7 +84,7 @@ jobs:
         run: make check-license-notice
 
       - name: Check dependency licenses
-        run: dev/release/dependencies.sh check
+        run: python3 ./dev/release/licenses.py dependencies check
 
       # Trusted publishing cannot create a crate (https://crates.io/docs/trusted-publishing),
       # so a new publishable crate must be reserved on crates.io before it is merged.
diff --git a/Makefile b/Makefile
index 8733abbab..c596fc4bf 100644
--- a/Makefile
+++ b/Makefile
@@ -94,7 +94,7 @@ install-cargo-deny:
 	cargo install --locked cargo-deny@0.19.9
 
 check-dependency-licenses: install-cargo-deny
-	bash ./dev/release/dependencies.sh check
+	python3 ./dev/release/licenses.py dependencies check
 
 check: check-fmt check-clippy check-toml cargo-machete check-license-notice check-dependency-licenses
 
diff --git a/dev/release/README.md b/dev/release/README.md
index cf10a3aa3..4a9cecd8f 100644
--- a/dev/release/README.md
+++ b/dev/release/README.md
@@ -137,5 +137,7 @@ git push origin "v0.9.1"
 Verify every dependency's license is one the project allows:
 
 ```shell
-dev/release/dependencies.sh check
+python3 dev/release/licenses.py dependencies check
 ```
+
+CI also runs this check on every pull request.
diff --git a/dev/release/create_rc.sh b/dev/release/create_rc.sh
index ae3a5207e..99dd37565 100755
--- a/dev/release/create_rc.sh
+++ b/dev/release/create_rc.sh
@@ -307,7 +307,8 @@ check_rc_tag_available() {
 }
 
 check_dependency_licenses() {
-  "${SCRIPT_DIR}/dependencies.sh" check
+  require_command python3
+  python3 "${SCRIPT_DIR}/licenses.py" dependencies check
 }
 
 # Packages and builds every crate as `cargo publish` would, without uploading,
diff --git a/dev/release/dependencies.sh b/dev/release/dependencies.sh
deleted file mode 100755
index 36246a1cd..000000000
--- a/dev/release/dependencies.sh
+++ /dev/null
@@ -1,148 +0,0 @@
-#!/usr/bin/env bash
-#
-# 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.
-
-set -Eeuo pipefail
-
-# bash 3.2 (macOS default) does not run the ERR trap for a failing subshell:
-# the script still exits on failure but does not report which step failed.
-if [ "${BASH_VERSINFO[0]}" -lt 4 ]; then
-  echo "Warning: bash ${BASH_VERSION} will not print which step failed. Use bash 4 or newer to see it." >&2
-fi
-
-CURRENT_STEP=""
-
-on_error() {
-  local status=$?
-  if [ -n "${CURRENT_STEP}" ]; then
-    echo "FAILED: ${CURRENT_STEP}" >&2
-  else
-    echo "FAILED" >&2
-  fi
-  exit "${status}"
-}
-
-trap on_error ERR
-
-usage() {
-  cat <<USAGE
-Usage:
-  $0 <check>
-
-Commands:
-  check
-      Run cargo-deny license validation once at the root workspace.
-      Default command: none; this argument is required.
-
-Options:
-  -h, --help
-      Show this help message.
-
-Examples:
-  $0 check
-USAGE
-}
-
-show_help_if_requested() {
-  while [ "$#" -gt 0 ]; do
-    case "$1" in
-      -h | --help)
-        usage
-        exit 0
-        ;;
-    esac
-    shift
-  done
-}
-
-start_step() {
-  CURRENT_STEP="$1"
-  echo "==> ${CURRENT_STEP}"
-}
-
-finish_step() {
-  echo "OK: ${CURRENT_STEP}"
-  CURRENT_STEP=""
-}
-
-run_step() {
-  local step="$1"
-  shift
-  start_step "${step}"
-  "$@"
-  finish_step
-}
-
-require_command() {
-  local command_name="$1"
-  if ! command -v "${command_name}" >/dev/null 2>&1; then
-    echo "This script requires '${command_name}', but it is not installed." >&2
-    return 1
-  fi
-}
-
-require_cargo_deny() {
-  require_command cargo
-  if ! cargo deny --version >/dev/null 2>&1; then
-    echo "This script requires 'cargo-deny' for dependency license checks." >&2
-    echo "Install it with: cargo install --locked cargo-deny" >&2
-    return 1
-  fi
-}
-
-validate_args() {
-  if [ "$#" -ne 1 ]; then
-    usage
-    return 1
-  fi
-
-  case "$1" in
-    check) ;;
-    *)
-      usage
-      return 1
-      ;;
-  esac
-}
-
-check_deps_for_dir() {
-  local cargo_dir="$1"
-  require_cargo_deny
-  (
-    trap - ERR
-    cd "${cargo_dir}"
-    cargo deny check license
-  )
-}
-
-check_deps() {
-  run_step "Check dependency licenses in ${REPO_ROOT}" check_deps_for_dir "${REPO_ROOT}"
-}
-
-SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
-REPO_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)"
-COMMAND="${1:-}"
-
-show_help_if_requested "$@"
-run_step "Validate dependency command arguments" validate_args "$@"
-
-case "${COMMAND}" in
-  check)
-    check_deps
-    ;;
-esac
diff --git a/dev/release/licenses.py b/dev/release/licenses.py
new file mode 100755
index 000000000..2fc744987
--- /dev/null
+++ b/dev/release/licenses.py
@@ -0,0 +1,101 @@
+#!/usr/bin/env python3
+# 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.
+"""
+CLI for assisting maintenance of the project's license checks.
+"""
+
+import argparse
+import subprocess
+import sys
+from pathlib import Path
+
+REPO_ROOT = Path(__file__).resolve().parent.parent.parent
+
+
+class Failure(Exception):
+    """A user-facing error."""
+
+
+def step(message: str) -> None:
+    print(f"==> {message}", flush=True)
+
+
+def ok(message: str) -> None:
+    print(f"OK: {message}", flush=True)
+
+
+def require_cargo_deny() -> None:
+    """Assert cargo-deny is installed."""
+    try:
+        subprocess.run(
+            ["cargo", "deny", "--version"],
+            capture_output=True,
+            check=True,
+        )
+    except FileNotFoundError:
+        raise Failure("This script requires 'cargo', but it is not installed.")
+    except subprocess.CalledProcessError:
+        raise Failure(
+            "This script requires 'cargo-deny' for dependency license checks.\n"
+            "Install it with: make install-cargo-deny"
+        )
+
+
+def command_check_dependencies(_args: argparse.Namespace) -> None:
+    step("Check dependency licenses")
+    require_cargo_deny()
+
+    result = subprocess.run(
+        ["cargo", "deny", "check", "license"],
+        cwd=REPO_ROOT,
+        check=False,
+    )
+    if result.returncode != 0:
+        raise Failure("cargo-deny rejected the dependency licenses.")
+
+    ok("Check dependency licenses")
+
+
+def main() -> int:
+    parser = argparse.ArgumentParser(description=__doc__)
+    cmd_groups = parser.add_subparsers(dest="group", required=True)
+
+    dependencies = cmd_groups.add_parser(
+        "dependencies",
+        help="license policy for the Rust dependency graph, enforced through deny.toml",
+    )
+    dependencies_commands = dependencies.add_subparsers(dest="command", required=True)
+
+    check = dependencies_commands.add_parser(
+        "check",
+        help="assert every dependency's license is one deny.toml allows",
+    )
+    check.set_defaults(func=command_check_dependencies)
+
+    args = parser.parse_args()
+    try:
+        args.func(args)
+    except Failure as failure:
+        print(str(failure), file=sys.stderr)
+        print(f"FAILED: {args.group} {args.command}", file=sys.stderr)
+        return 1
+    return 0
+
+
+if __name__ == "__main__":
+    sys.exit(main())
diff --git a/website/src/release.md b/website/src/release.md
index 03fb66551..a519fce56 100644
--- a/website/src/release.md
+++ b/website/src/release.md
@@ -175,7 +175,7 @@ The changelog should reflect a summary of each commit in the new release.
 Run the following command to verify the licenses meet the project's policy.
 
 ```shell
-dev/release/dependencies.sh check
+python3 dev/release/licenses.py dependencies check
 ```
 
 #### Open pull request