85 lines
3 KiB
Python
85 lines
3 KiB
Python
from importlib.metadata import PathDistribution
|
|
from pathlib import Path
|
|
import collections
|
|
import sys
|
|
import os
|
|
from typing import Dict, List, Tuple
|
|
do_abort: bool = False
|
|
packages: Dict[str, Dict[str, List[Dict[str, List[str]]]]] = collections.defaultdict(list)
|
|
out_path: Path = Path(os.getenv("out"))
|
|
version: Tuple[int, int] = sys.version_info
|
|
site_packages_path: str = f'lib/python{version[0]}.{version[1]}/site-packages'
|
|
|
|
|
|
def get_name(dist: PathDistribution) -> str:
|
|
return dist.metadata['name'].lower().replace('-', '_')
|
|
|
|
|
|
# pretty print a package
|
|
def describe_package(dist: PathDistribution) -> str:
|
|
return f"{get_name(dist)} {dist.version} ({dist._path})"
|
|
|
|
|
|
# pretty print a list of parents (dependency chain)
|
|
def describe_parents(parents: List[str]) -> str:
|
|
if not parents:
|
|
return ""
|
|
return \
|
|
f" dependency chain:\n " \
|
|
+ str(f"\n ...depending on: ".join(parents))
|
|
|
|
|
|
# inserts an entry into 'packages'
|
|
def add_entry(name: str, version: str, store_path: str, parents: List[str]) -> None:
|
|
if name not in packages:
|
|
packages[name] = {}
|
|
if store_path not in packages[name]:
|
|
packages[name][store_path] = []
|
|
packages[name][store_path].append(dict(
|
|
version=version,
|
|
parents=parents,
|
|
))
|
|
|
|
|
|
# transitively discover python dependencies and store them in 'packages'
|
|
def find_packages(store_path: Path, site_packages_path: str, parents: List[str]) -> None:
|
|
site_packages: Path = (store_path / site_packages_path)
|
|
propagated_build_inputs: Path = (store_path / "nix-support/propagated-build-inputs")
|
|
|
|
# add the current package to the list
|
|
if site_packages.exists():
|
|
for dist_info in site_packages.glob("*.dist-info"):
|
|
dist: PathDistribution = PathDistribution(dist_info)
|
|
add_entry(get_name(dist), dist.version, store_path, parents)
|
|
|
|
# recursively add dependencies
|
|
if propagated_build_inputs.exists():
|
|
with open(propagated_build_inputs, "r") as f:
|
|
build_inputs: List[str] = f.read().strip().split(" ")
|
|
for build_input in build_inputs:
|
|
if build_input not in parents:
|
|
find_packages(Path(build_input), site_packages_path, parents + [build_input])
|
|
|
|
|
|
find_packages(out_path, site_packages_path, [f"this derivation: {out_path}"])
|
|
|
|
# print all duplicates
|
|
for name, store_paths in packages.items():
|
|
if len(store_paths) > 1:
|
|
do_abort = True
|
|
print("Found duplicated packages in closure for dependency '{}': ".format(name))
|
|
for store_path, candidates in store_paths.items():
|
|
for candidate in candidates:
|
|
print(f" {name} {candidate['version']} ({store_path})")
|
|
print(describe_parents(candidate['parents']))
|
|
|
|
# fail if duplicates were found
|
|
if do_abort:
|
|
print("")
|
|
print(
|
|
"Package duplicates found in closure, see above. Usually this "
|
|
"happens if two packages depend on different version "
|
|
"of the same dependency."
|
|
)
|
|
sys.exit(1)
|