blob: e17e1a7a24d76d1184bdd964f4e8a05a6f69c928 [file]
#!/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.
import asyncio
from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter
from contextlib import contextmanager
from pathlib import Path
import subprocess
from typing import Iterator
from constants import PACKAGE_FEATURES, PACKAGES
def is_tracked(path: Path) -> bool:
result = subprocess.run(
["git", "ls-files", "--error-unmatch", "--", path.name],
cwd=path.parent,
check=False,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
)
return result.returncode == 0
def cargo_deny_command(root: Path, *args: str, locked: bool = False) -> list[str]:
command = ["cargo", "deny"]
if locked:
command.append("--locked")
features = PACKAGE_FEATURES.get(root)
if features:
command.extend(["--features", ",".join(features)])
command.extend(args)
return command
@contextmanager
def resolved_dependencies(root: Path) -> Iterator[bool]:
lockfile = root / "Cargo.lock"
if is_tracked(lockfile):
yield True
return
original_lockfile = lockfile.read_bytes() if lockfile.exists() else None
lockfile.unlink(missing_ok=True)
try:
yield False
finally:
lockfile.unlink(missing_ok=True)
if original_lockfile is not None:
lockfile.write_bytes(original_lockfile)
def check_single_package(root: Path) -> None:
if (root / "Cargo.toml").exists():
print(f"Checking dependencies of {root}")
with resolved_dependencies(root) as locked:
subprocess.run(
cargo_deny_command(root, "check", "licenses", locked=locked),
cwd=root,
check=True,
)
else:
print(f"Skipping {root} as Cargo.toml does not exist")
def check_deps() -> None:
failures: list[Path] = []
for root in PACKAGES:
# We disable dependencies checks since PHP binding release is not working.
if str(root) in ["bindings/php"]:
continue
try:
check_single_package(root)
except subprocess.CalledProcessError:
failures.append(root)
if failures:
roots = ", ".join(str(root) for root in failures)
raise RuntimeError(f"dependency checks failed for: {roots}")
async def generate_single_package(root: Path) -> None:
if (root / "Cargo.toml").exists():
print(f"Generating dependencies {root}")
with resolved_dependencies(root) as locked:
command = cargo_deny_command(
root, "list", "-f", "tsv", "-t", "0.6", locked=locked
)
process = await asyncio.create_subprocess_exec(
*command, cwd=root, stdout=asyncio.subprocess.PIPE
)
stdout, _ = await process.communicate()
if process.returncode != 0:
raise subprocess.CalledProcessError(process.returncode, command)
(root / "DEPENDENCIES.rust.tsv").write_bytes(stdout)
else:
print(f"Skipping {root} as Cargo.toml does not exist")
async def generate_all_deps() -> None:
results = await asyncio.gather(
*(generate_single_package(root) for root in PACKAGES),
return_exceptions=True,
)
for result in results:
if isinstance(result, BaseException):
raise result
def generate_deps() -> None:
asyncio.run(generate_all_deps())
if __name__ == "__main__":
parser = ArgumentParser(formatter_class=ArgumentDefaultsHelpFormatter)
parser.set_defaults(func=parser.print_help)
subparsers = parser.add_subparsers()
parser_check = subparsers.add_parser(
"check", description="Check dependencies", help="Check dependencies"
)
parser_check.set_defaults(func=check_deps)
parser_generate = subparsers.add_parser(
"generate", description="Generate dependencies", help="Generate dependencies"
)
parser_generate.set_defaults(func=generate_deps)
args = parser.parse_args()
arg_dict = dict(vars(args))
del arg_dict["func"]
args.func(**arg_dict)