1*337cbf3dSRuoqing He#!/bin/env python3 2*337cbf3dSRuoqing He# 3*337cbf3dSRuoqing He# Copyright © 2024 Institute of Software, CAS. All rights reserved. 4*337cbf3dSRuoqing He# 5*337cbf3dSRuoqing He# SPDX-License-Identifier: Apache-2.0 6*337cbf3dSRuoqing He# 7*337cbf3dSRuoqing He 8*337cbf3dSRuoqing Heimport subprocess 9*337cbf3dSRuoqing Heimport json 10*337cbf3dSRuoqing Hefrom argparse import ArgumentParser 11*337cbf3dSRuoqing Hefrom collections import defaultdict 12*337cbf3dSRuoqing He 13*337cbf3dSRuoqing Hedef get_cargo_metadata(): 14*337cbf3dSRuoqing He result = subprocess.run( 15*337cbf3dSRuoqing He ['cargo', 'metadata', '--format-version=1'], 16*337cbf3dSRuoqing He capture_output=True, 17*337cbf3dSRuoqing He text=True 18*337cbf3dSRuoqing He ) 19*337cbf3dSRuoqing He if result.returncode != 0: 20*337cbf3dSRuoqing He exit(1) 21*337cbf3dSRuoqing He 22*337cbf3dSRuoqing He metadata = json.loads(result.stdout) 23*337cbf3dSRuoqing He return metadata 24*337cbf3dSRuoqing He 25*337cbf3dSRuoqing Hedef find_dependents_of_package(metadata, package_source): 26*337cbf3dSRuoqing He """Find dependencies based on the provided source identifier and return related package info.""" 27*337cbf3dSRuoqing He packages = defaultdict(list) 28*337cbf3dSRuoqing He direct_dependents = defaultdict(list) 29*337cbf3dSRuoqing He 30*337cbf3dSRuoqing He # Identify packages from the given package source and record version 31*337cbf3dSRuoqing He for pkg in metadata['packages']: 32*337cbf3dSRuoqing He repository = pkg['repository'] or '' 33*337cbf3dSRuoqing He if package_source in repository: 34*337cbf3dSRuoqing He packages[pkg['name']].append(pkg['version']) 35*337cbf3dSRuoqing He 36*337cbf3dSRuoqing He # Find packages that immediately depend on the identified source packages 37*337cbf3dSRuoqing He for node in metadata['resolve']['nodes']: 38*337cbf3dSRuoqing He current_pkg = next(pkg for pkg in metadata['packages'] if pkg['id'] == node['id']) 39*337cbf3dSRuoqing He current_pkg_name = current_pkg['name'] 40*337cbf3dSRuoqing He current_pkg_version = current_pkg['version'] 41*337cbf3dSRuoqing He 42*337cbf3dSRuoqing He for dep_id in node['dependencies']: 43*337cbf3dSRuoqing He dep_pkg = next(pkg for pkg in metadata['packages'] if pkg['id'] == dep_id) 44*337cbf3dSRuoqing He dep_name = dep_pkg['name'] 45*337cbf3dSRuoqing He dep_version = dep_pkg['version'] 46*337cbf3dSRuoqing He 47*337cbf3dSRuoqing He if dep_name in packages: 48*337cbf3dSRuoqing He direct_dependents[(dep_name, dep_version)].append((current_pkg_name, current_pkg_version)) 49*337cbf3dSRuoqing He 50*337cbf3dSRuoqing He return packages, direct_dependents 51*337cbf3dSRuoqing He 52*337cbf3dSRuoqing Hedef check_for_version_conflicts(packages, direct_dependents): 53*337cbf3dSRuoqing He """Check if there are multiple versions of dependencies, and return True if conflicts are found.""" 54*337cbf3dSRuoqing He has_conflicts = False 55*337cbf3dSRuoqing He 56*337cbf3dSRuoqing He for pkg_name, versions in packages.items(): 57*337cbf3dSRuoqing He if len(set(versions)) > 1: 58*337cbf3dSRuoqing He has_conflicts = True 59*337cbf3dSRuoqing He print(f"Error: Multiple versions detected for {pkg_name}: {set(versions)}") 60*337cbf3dSRuoqing He for version in set(versions): 61*337cbf3dSRuoqing He print(f" Version {version} used by:") 62*337cbf3dSRuoqing He for dependent, dep_version in direct_dependents[(pkg_name, version)]: 63*337cbf3dSRuoqing He print(f" - {dependent} v{dep_version}") 64*337cbf3dSRuoqing He 65*337cbf3dSRuoqing He return has_conflicts 66*337cbf3dSRuoqing He 67*337cbf3dSRuoqing Heif __name__ == '__main__': 68*337cbf3dSRuoqing He parser = ArgumentParser(description='Cargo dependency conflict checker.') 69*337cbf3dSRuoqing He parser.add_argument('package_source', type=str, help='A keyword used to match the repository URL field') 70*337cbf3dSRuoqing He args = parser.parse_args() 71*337cbf3dSRuoqing He 72*337cbf3dSRuoqing He metadata = get_cargo_metadata() 73*337cbf3dSRuoqing He if metadata is None: 74*337cbf3dSRuoqing He print("Error: Metadata is empty") 75*337cbf3dSRuoqing He exit(1) 76*337cbf3dSRuoqing He 77*337cbf3dSRuoqing He packages, direct_dependents = find_dependents_of_package(metadata, args.package_source) 78*337cbf3dSRuoqing He 79*337cbf3dSRuoqing He has_conflicts = check_for_version_conflicts(packages, direct_dependents) 80*337cbf3dSRuoqing He 81*337cbf3dSRuoqing He if has_conflicts: 82*337cbf3dSRuoqing He exit(1) 83*337cbf3dSRuoqing He 84