1*90c8c64dSAndroid Build Coastguard Worker#!/usr/bin/env python3 2*90c8c64dSAndroid Build Coastguard Worker# 3*90c8c64dSAndroid Build Coastguard Worker# Copyright (C) 2020 The Android Open Source Project 4*90c8c64dSAndroid Build Coastguard Worker# 5*90c8c64dSAndroid Build Coastguard Worker# Licensed under the Apache License, Version 2.0 (the "License"); 6*90c8c64dSAndroid Build Coastguard Worker# you may not use this file except in compliance with the License. 7*90c8c64dSAndroid Build Coastguard Worker# You may obtain a copy of the License at 8*90c8c64dSAndroid Build Coastguard Worker# 9*90c8c64dSAndroid Build Coastguard Worker# http://www.apache.org/licenses/LICENSE-2.0 10*90c8c64dSAndroid Build Coastguard Worker# 11*90c8c64dSAndroid Build Coastguard Worker# Unless required by applicable law or agreed to in writing, software 12*90c8c64dSAndroid Build Coastguard Worker# distributed under the License is distributed on an "AS IS" BASIS, 13*90c8c64dSAndroid Build Coastguard Worker# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14*90c8c64dSAndroid Build Coastguard Worker# See the License for the specific language governing permissions and 15*90c8c64dSAndroid Build Coastguard Worker# limitations under the License. 16*90c8c64dSAndroid Build Coastguard Worker"""Fetch a Rust package from crates.io. 17*90c8c64dSAndroid Build Coastguard Worker 18*90c8c64dSAndroid Build Coastguard WorkerUsage: get_rust_pkg.py -v syn-1.0.7 19*90c8c64dSAndroid Build Coastguard WorkerGet the package syn-1.0.7 from crates.io and untar it into ./syn-1.0.7. 20*90c8c64dSAndroid Build Coastguard Worker 21*90c8c64dSAndroid Build Coastguard WorkerUsage: get_rust_pkg.py -v -o tmp syn 22*90c8c64dSAndroid Build Coastguard WorkerGet the latest version of package syn, say 1.0.17, 23*90c8c64dSAndroid Build Coastguard Workerand untar it into tmp/syn-1.0.17. 24*90c8c64dSAndroid Build Coastguard Worker 25*90c8c64dSAndroid Build Coastguard WorkerUsage: get_rust_pkg.py -show bindgen cxx 26*90c8c64dSAndroid Build Coastguard WorkerCount dependent packages of bindgen and cxx. 27*90c8c64dSAndroid Build Coastguard Worker 28*90c8c64dSAndroid Build Coastguard WorkerWhen downloading a package, its target directory should not exist, 29*90c8c64dSAndroid Build Coastguard Workeror the download will be skipped. 30*90c8c64dSAndroid Build Coastguard Worker""" 31*90c8c64dSAndroid Build Coastguard Worker 32*90c8c64dSAndroid Build Coastguard Workerimport argparse 33*90c8c64dSAndroid Build Coastguard Workerimport functools 34*90c8c64dSAndroid Build Coastguard Workerimport json 35*90c8c64dSAndroid Build Coastguard Workerimport os 36*90c8c64dSAndroid Build Coastguard Workerimport re 37*90c8c64dSAndroid Build Coastguard Workerimport shutil 38*90c8c64dSAndroid Build Coastguard Workerimport sys 39*90c8c64dSAndroid Build Coastguard Workerimport tarfile 40*90c8c64dSAndroid Build Coastguard Workerimport tempfile 41*90c8c64dSAndroid Build Coastguard Workerimport urllib.request 42*90c8c64dSAndroid Build Coastguard Worker 43*90c8c64dSAndroid Build Coastguard WorkerPKG_VERSION_PATTERN = r"(.*)-([0-9]+\.[0-9]+\.[0-9]+.*)" 44*90c8c64dSAndroid Build Coastguard Worker 45*90c8c64dSAndroid Build Coastguard WorkerPKG_VERSION_MATCHER = re.compile(PKG_VERSION_PATTERN) 46*90c8c64dSAndroid Build Coastguard Worker 47*90c8c64dSAndroid Build Coastguard WorkerVERSION_PATTERN = r"([0-9]+)\.([0-9]+)\.([0-9]+)" 48*90c8c64dSAndroid Build Coastguard Worker 49*90c8c64dSAndroid Build Coastguard WorkerVERSION_MATCHER = re.compile(VERSION_PATTERN) 50*90c8c64dSAndroid Build Coastguard Worker 51*90c8c64dSAndroid Build Coastguard Worker 52*90c8c64dSAndroid Build Coastguard Workerdef parse_args(): 53*90c8c64dSAndroid Build Coastguard Worker """Parse main arguments.""" 54*90c8c64dSAndroid Build Coastguard Worker parser = argparse.ArgumentParser("get_rust_pkg") 55*90c8c64dSAndroid Build Coastguard Worker parser.add_argument( 56*90c8c64dSAndroid Build Coastguard Worker "-v", action="store_true", default=False, help="echo executed commands") 57*90c8c64dSAndroid Build Coastguard Worker parser.add_argument( 58*90c8c64dSAndroid Build Coastguard Worker "-o", metavar="out_dir", default=".", help="output directory") 59*90c8c64dSAndroid Build Coastguard Worker parser.add_argument( 60*90c8c64dSAndroid Build Coastguard Worker "-add3prf", "--add3prf", 61*90c8c64dSAndroid Build Coastguard Worker action="store_true", 62*90c8c64dSAndroid Build Coastguard Worker default=False, 63*90c8c64dSAndroid Build Coastguard Worker help="call add3prf.py to add third party review files") 64*90c8c64dSAndroid Build Coastguard Worker parser.add_argument( 65*90c8c64dSAndroid Build Coastguard Worker "-show", "--show", 66*90c8c64dSAndroid Build Coastguard Worker action="store_true", 67*90c8c64dSAndroid Build Coastguard Worker default=False, 68*90c8c64dSAndroid Build Coastguard Worker help="show all default dependent packages, using crates.io api") 69*90c8c64dSAndroid Build Coastguard Worker parser.add_argument( 70*90c8c64dSAndroid Build Coastguard Worker dest="pkgs", 71*90c8c64dSAndroid Build Coastguard Worker metavar="pkg_name", 72*90c8c64dSAndroid Build Coastguard Worker nargs="+", 73*90c8c64dSAndroid Build Coastguard Worker help="name of Rust package to be fetched from crates.io") 74*90c8c64dSAndroid Build Coastguard Worker return parser.parse_args() 75*90c8c64dSAndroid Build Coastguard Worker 76*90c8c64dSAndroid Build Coastguard Worker 77*90c8c64dSAndroid Build Coastguard Workerdef set2list(a_set): 78*90c8c64dSAndroid Build Coastguard Worker return (" " + " ".join(sorted(a_set))) if a_set else "" 79*90c8c64dSAndroid Build Coastguard Worker 80*90c8c64dSAndroid Build Coastguard Worker 81*90c8c64dSAndroid Build Coastguard Workerdef echo(args, msg): 82*90c8c64dSAndroid Build Coastguard Worker if args.v: 83*90c8c64dSAndroid Build Coastguard Worker print("INFO: {}".format(msg), flush=True) 84*90c8c64dSAndroid Build Coastguard Worker 85*90c8c64dSAndroid Build Coastguard Worker 86*90c8c64dSAndroid Build Coastguard Workerdef echo_all_deps(args, kind, deps): 87*90c8c64dSAndroid Build Coastguard Worker if args.v and deps: 88*90c8c64dSAndroid Build Coastguard Worker print("INFO: now {} in {}:{}".format(len(deps), kind, set2list(deps))) 89*90c8c64dSAndroid Build Coastguard Worker 90*90c8c64dSAndroid Build Coastguard Worker 91*90c8c64dSAndroid Build Coastguard Workerdef pkg_base_name(pkg): 92*90c8c64dSAndroid Build Coastguard Worker match = PKG_VERSION_MATCHER.match(pkg) 93*90c8c64dSAndroid Build Coastguard Worker if match is not None: 94*90c8c64dSAndroid Build Coastguard Worker return (match.group(1), match.group(2)) 95*90c8c64dSAndroid Build Coastguard Worker else: 96*90c8c64dSAndroid Build Coastguard Worker return (pkg, "") 97*90c8c64dSAndroid Build Coastguard Worker 98*90c8c64dSAndroid Build Coastguard Worker 99*90c8c64dSAndroid Build Coastguard Workerdef get_version_numbers(version): 100*90c8c64dSAndroid Build Coastguard Worker match = VERSION_MATCHER.match(version) 101*90c8c64dSAndroid Build Coastguard Worker if match is not None: 102*90c8c64dSAndroid Build Coastguard Worker return tuple(int(match.group(i)) for i in range(1, 4)) 103*90c8c64dSAndroid Build Coastguard Worker return (0, 0, 0) 104*90c8c64dSAndroid Build Coastguard Worker 105*90c8c64dSAndroid Build Coastguard Worker 106*90c8c64dSAndroid Build Coastguard Workerdef is_newer_version(args, prev_version, prev_id, check_version, check_id): 107*90c8c64dSAndroid Build Coastguard Worker """Return true if check_version+id is newer than prev_version+id.""" 108*90c8c64dSAndroid Build Coastguard Worker echo(args, "checking version={} id={}".format(check_version, check_id)) 109*90c8c64dSAndroid Build Coastguard Worker return ((get_version_numbers(check_version), check_id) > 110*90c8c64dSAndroid Build Coastguard Worker (get_version_numbers(prev_version), prev_id)) 111*90c8c64dSAndroid Build Coastguard Worker 112*90c8c64dSAndroid Build Coastguard Worker 113*90c8c64dSAndroid Build Coastguard Workerdef get_max_version(pkg): 114*90c8c64dSAndroid Build Coastguard Worker """Ask crates.io for a pkg's latest stable version.""" 115*90c8c64dSAndroid Build Coastguard Worker url = "https://crates.io/api/v1/crates/" + pkg 116*90c8c64dSAndroid Build Coastguard Worker with urllib.request.urlopen(url) as request: 117*90c8c64dSAndroid Build Coastguard Worker data = json.loads(request.read().decode()) 118*90c8c64dSAndroid Build Coastguard Worker return data["crate"]["max_stable_version"] 119*90c8c64dSAndroid Build Coastguard Worker 120*90c8c64dSAndroid Build Coastguard Worker 121*90c8c64dSAndroid Build Coastguard Workerdef find_dl_path(args, name): 122*90c8c64dSAndroid Build Coastguard Worker """Ask crates.io for the latest stable version download path.""" 123*90c8c64dSAndroid Build Coastguard Worker base_name, version = pkg_base_name(name) 124*90c8c64dSAndroid Build Coastguard Worker if not version: 125*90c8c64dSAndroid Build Coastguard Worker version = get_max_version(name) 126*90c8c64dSAndroid Build Coastguard Worker url = "https://crates.io/api/v1/crates/{}/{}".format(base_name, version) 127*90c8c64dSAndroid Build Coastguard Worker echo(args, "try to get dl_path from {}".format(url)) 128*90c8c64dSAndroid Build Coastguard Worker with urllib.request.urlopen(url) as request: 129*90c8c64dSAndroid Build Coastguard Worker data = json.loads(request.read().decode()) 130*90c8c64dSAndroid Build Coastguard Worker if "version" not in data or "dl_path" not in data["version"]: 131*90c8c64dSAndroid Build Coastguard Worker print("ERROR: cannot find version {} of package {}".format( 132*90c8c64dSAndroid Build Coastguard Worker version, base_name)) 133*90c8c64dSAndroid Build Coastguard Worker return None 134*90c8c64dSAndroid Build Coastguard Worker echo(args, "found download path for version {}".format(version)) 135*90c8c64dSAndroid Build Coastguard Worker return data["version"]["dl_path"] 136*90c8c64dSAndroid Build Coastguard Worker 137*90c8c64dSAndroid Build Coastguard Worker 138*90c8c64dSAndroid Build Coastguard Workerdef fetch_pkg(args, pkg, dl_path): 139*90c8c64dSAndroid Build Coastguard Worker """Fetch package from crates.io and untar it into a subdirectory.""" 140*90c8c64dSAndroid Build Coastguard Worker if not dl_path: 141*90c8c64dSAndroid Build Coastguard Worker print("ERROR: cannot find download path for '{}'".format(pkg)) 142*90c8c64dSAndroid Build Coastguard Worker return False 143*90c8c64dSAndroid Build Coastguard Worker url = "https://crates.io" + dl_path 144*90c8c64dSAndroid Build Coastguard Worker tmp_dir = tempfile.mkdtemp() 145*90c8c64dSAndroid Build Coastguard Worker echo(args, "fetch tar file from {}".format(url)) 146*90c8c64dSAndroid Build Coastguard Worker tar_file, _ = urllib.request.urlretrieve(url) 147*90c8c64dSAndroid Build Coastguard Worker with tarfile.open(tar_file, mode="r") as tfile: 148*90c8c64dSAndroid Build Coastguard Worker echo(args, "extract tar file {} into {}".format(tar_file, tmp_dir)) 149*90c8c64dSAndroid Build Coastguard Worker tfile.extractall(tmp_dir) 150*90c8c64dSAndroid Build Coastguard Worker files = os.listdir(tmp_dir) 151*90c8c64dSAndroid Build Coastguard Worker # There should be only one directory in the tar file, 152*90c8c64dSAndroid Build Coastguard Worker # but it might not be (name + "-" + version) 153*90c8c64dSAndroid Build Coastguard Worker pkg_tmp_dir = os.path.join(tmp_dir, files[0]) 154*90c8c64dSAndroid Build Coastguard Worker echo(args, "untared package in {}".format(pkg_tmp_dir)) 155*90c8c64dSAndroid Build Coastguard Worker dest_dir = os.path.join(args.o, files[0]) 156*90c8c64dSAndroid Build Coastguard Worker if os.path.exists(dest_dir): 157*90c8c64dSAndroid Build Coastguard Worker print("ERROR: do not overwrite existing {}".format(dest_dir)) 158*90c8c64dSAndroid Build Coastguard Worker return False # leave tar_file and tmp_dir 159*90c8c64dSAndroid Build Coastguard Worker else: 160*90c8c64dSAndroid Build Coastguard Worker echo(args, "move {} to {}".format(pkg_tmp_dir, dest_dir)) 161*90c8c64dSAndroid Build Coastguard Worker shutil.move(pkg_tmp_dir, dest_dir) 162*90c8c64dSAndroid Build Coastguard Worker echo(args, "delete downloaded tar file {}".format(tar_file)) 163*90c8c64dSAndroid Build Coastguard Worker os.remove(tar_file) 164*90c8c64dSAndroid Build Coastguard Worker echo(args, "delete temp directory {}".format(tmp_dir)) 165*90c8c64dSAndroid Build Coastguard Worker shutil.rmtree(tmp_dir) 166*90c8c64dSAndroid Build Coastguard Worker print("SUCCESS: downloaded package to '{}'".format(dest_dir)) 167*90c8c64dSAndroid Build Coastguard Worker if args.add3prf: 168*90c8c64dSAndroid Build Coastguard Worker echo(args, "Calling add3prf.py in {}".format(dest_dir)) 169*90c8c64dSAndroid Build Coastguard Worker cwd = os.getcwd() 170*90c8c64dSAndroid Build Coastguard Worker os.chdir(dest_dir) 171*90c8c64dSAndroid Build Coastguard Worker # add3prf.py is in the same directory as this python script 172*90c8c64dSAndroid Build Coastguard Worker os.system(os.path.dirname(__file__) + "/add3prf.py") 173*90c8c64dSAndroid Build Coastguard Worker os.chdir(cwd) 174*90c8c64dSAndroid Build Coastguard Worker return True 175*90c8c64dSAndroid Build Coastguard Worker 176*90c8c64dSAndroid Build Coastguard Workerdef get_crate_dependencies(args, pkg): 177*90c8c64dSAndroid Build Coastguard Worker """Ask crates.io for pkg's dependencies.""" 178*90c8c64dSAndroid Build Coastguard Worker echo(args, "Ask crates.io for {} ...".format(pkg)) 179*90c8c64dSAndroid Build Coastguard Worker try: 180*90c8c64dSAndroid Build Coastguard Worker url = "https://crates.io/api/v1/crates/{}/{}/dependencies".format( 181*90c8c64dSAndroid Build Coastguard Worker pkg, get_max_version(pkg)) 182*90c8c64dSAndroid Build Coastguard Worker with urllib.request.urlopen(url) as request: 183*90c8c64dSAndroid Build Coastguard Worker data = json.loads(request.read().decode()) 184*90c8c64dSAndroid Build Coastguard Worker except urllib.error.HTTPError: 185*90c8c64dSAndroid Build Coastguard Worker print("ERROR: failed to find {}".format(pkg)) 186*90c8c64dSAndroid Build Coastguard Worker return False, None, None 187*90c8c64dSAndroid Build Coastguard Worker build_deps = set() 188*90c8c64dSAndroid Build Coastguard Worker dev_deps = set() 189*90c8c64dSAndroid Build Coastguard Worker for crate in data["dependencies"]: 190*90c8c64dSAndroid Build Coastguard Worker if not crate["optional"]: # some package has a lot of optional features 191*90c8c64dSAndroid Build Coastguard Worker # dev_deps is a super set of build_deps 192*90c8c64dSAndroid Build Coastguard Worker dev_deps.add(crate["crate_id"]) 193*90c8c64dSAndroid Build Coastguard Worker if crate["kind"] != "dev": 194*90c8c64dSAndroid Build Coastguard Worker build_deps.add(crate["crate_id"]) 195*90c8c64dSAndroid Build Coastguard Worker return True, build_deps, dev_deps 196*90c8c64dSAndroid Build Coastguard Worker 197*90c8c64dSAndroid Build Coastguard Worker 198*90c8c64dSAndroid Build Coastguard Workerdef compare_pkg_deps(pkg1, pkg2): 199*90c8c64dSAndroid Build Coastguard Worker """Compare dependency order of pkg1 and pkg2.""" 200*90c8c64dSAndroid Build Coastguard Worker base1, build_deps1, dev_deps1 = pkg1 201*90c8c64dSAndroid Build Coastguard Worker base2, build_deps2, dev_deps2 = pkg2 202*90c8c64dSAndroid Build Coastguard Worker # Some pkg1 can be build-dependent (non-dev-dependent) on pkg2, 203*90c8c64dSAndroid Build Coastguard Worker # when pkg2 is only test-dependent (dev-dependent) on pkg1. 204*90c8c64dSAndroid Build Coastguard Worker # This is not really a build dependency cycle, because pkg2 205*90c8c64dSAndroid Build Coastguard Worker # can be built before pkg1, but tested after pkg1. 206*90c8c64dSAndroid Build Coastguard Worker # So the dependency order is based on build_deps first, and then dev_deps. 207*90c8c64dSAndroid Build Coastguard Worker if base1 in build_deps2: 208*90c8c64dSAndroid Build Coastguard Worker return -1 # pkg2 needs base1 209*90c8c64dSAndroid Build Coastguard Worker if base2 in build_deps1: 210*90c8c64dSAndroid Build Coastguard Worker return 1 # pkg1 needs base2 211*90c8c64dSAndroid Build Coastguard Worker if base1 in dev_deps2: 212*90c8c64dSAndroid Build Coastguard Worker return -1 # pkg2 needs base1 213*90c8c64dSAndroid Build Coastguard Worker if base2 in dev_deps1: 214*90c8c64dSAndroid Build Coastguard Worker return 1 # pkg1 needs base2 215*90c8c64dSAndroid Build Coastguard Worker # If there is no dependency between pkg1 and pkg2, 216*90c8c64dSAndroid Build Coastguard Worker # order them by the size of build_deps or dev_deps, or the name. 217*90c8c64dSAndroid Build Coastguard Worker count1 = (len(build_deps1), len(dev_deps1), base1) 218*90c8c64dSAndroid Build Coastguard Worker count2 = (len(build_deps2), len(dev_deps2), base2) 219*90c8c64dSAndroid Build Coastguard Worker if count1 != count2: 220*90c8c64dSAndroid Build Coastguard Worker return -1 if count1 < count2 else 1 221*90c8c64dSAndroid Build Coastguard Worker return 0 222*90c8c64dSAndroid Build Coastguard Worker 223*90c8c64dSAndroid Build Coastguard Worker 224*90c8c64dSAndroid Build Coastguard Workerdef sort_found_pkgs(tuples): 225*90c8c64dSAndroid Build Coastguard Worker """A topological sort of tuples based on build_deps.""" 226*90c8c64dSAndroid Build Coastguard Worker # tuples is a list of (base_name, build_deps, dev_deps) 227*90c8c64dSAndroid Build Coastguard Worker 228*90c8c64dSAndroid Build Coastguard Worker # Use build_deps as the dependency relation in a topological sort. 229*90c8c64dSAndroid Build Coastguard Worker # The new_tuples list is used in topological sort. It is the input tuples 230*90c8c64dSAndroid Build Coastguard Worker # prefixed with a changing build_deps during the sorting process. 231*90c8c64dSAndroid Build Coastguard Worker # Collect all package base names. 232*90c8c64dSAndroid Build Coastguard Worker # Dependent packages not found in all_base_names will be treated as 233*90c8c64dSAndroid Build Coastguard Worker # "external" and ignored in topological sort. 234*90c8c64dSAndroid Build Coastguard Worker all_base_names = set(map(lambda t: t[0], tuples)) 235*90c8c64dSAndroid Build Coastguard Worker new_tuples = [] 236*90c8c64dSAndroid Build Coastguard Worker all_names = set() 237*90c8c64dSAndroid Build Coastguard Worker for (base_name, build_deps, dev_deps) in tuples: 238*90c8c64dSAndroid Build Coastguard Worker new_tuples.append((build_deps, (base_name, build_deps, dev_deps))) 239*90c8c64dSAndroid Build Coastguard Worker all_names = all_names.union(build_deps) 240*90c8c64dSAndroid Build Coastguard Worker external_names = all_names.difference(all_base_names) 241*90c8c64dSAndroid Build Coastguard Worker new_tuples = list( 242*90c8c64dSAndroid Build Coastguard Worker map(lambda t: (t[0].difference(external_names), t[1]), new_tuples)) 243*90c8c64dSAndroid Build Coastguard Worker 244*90c8c64dSAndroid Build Coastguard Worker sorted_tuples = [] 245*90c8c64dSAndroid Build Coastguard Worker # A brute force topological sort; 246*90c8c64dSAndroid Build Coastguard Worker # tuples with empty build_deps are put before the others. 247*90c8c64dSAndroid Build Coastguard Worker while new_tuples: 248*90c8c64dSAndroid Build Coastguard Worker first_group = list(filter(lambda t: not t[0], new_tuples)) 249*90c8c64dSAndroid Build Coastguard Worker other_group = list(filter(lambda t: t[0], new_tuples)) 250*90c8c64dSAndroid Build Coastguard Worker new_tuples = [] 251*90c8c64dSAndroid Build Coastguard Worker if first_group: 252*90c8c64dSAndroid Build Coastguard Worker # Remove the extra build_deps in first_group, 253*90c8c64dSAndroid Build Coastguard Worker # then sort it, and add its tuples to the sorted_tuples list. 254*90c8c64dSAndroid Build Coastguard Worker first_group = list(map(lambda t: t[1], first_group)) 255*90c8c64dSAndroid Build Coastguard Worker first_group.sort(key=functools.cmp_to_key(compare_pkg_deps)) 256*90c8c64dSAndroid Build Coastguard Worker sorted_tuples.extend(first_group) 257*90c8c64dSAndroid Build Coastguard Worker # Copy other_group to new_tuples but remove names in the first_group. 258*90c8c64dSAndroid Build Coastguard Worker base_names = set(map(lambda t: t[0], first_group)) 259*90c8c64dSAndroid Build Coastguard Worker new_tuples = list( 260*90c8c64dSAndroid Build Coastguard Worker map(lambda t: (t[0].difference(base_names), t[1]), other_group)) 261*90c8c64dSAndroid Build Coastguard Worker else: 262*90c8c64dSAndroid Build Coastguard Worker # There is a bug, or a cycle in the build_deps. 263*90c8c64dSAndroid Build Coastguard Worker # If we include all optional dependent packages into build_deps, 264*90c8c64dSAndroid Build Coastguard Worker # we will see one cycle: futures-util has an optional dependent 265*90c8c64dSAndroid Build Coastguard Worker # on futures, which has a normal dependent on futures-util. 266*90c8c64dSAndroid Build Coastguard Worker print("ERROR: leftover in topological sort: {}".format( 267*90c8c64dSAndroid Build Coastguard Worker list(map(lambda t: t[1][1], other_group)))) 268*90c8c64dSAndroid Build Coastguard Worker # Anyway, sort the other_group to include them into final report. 269*90c8c64dSAndroid Build Coastguard Worker other_group = list(map(lambda t: t[1], other_group)) 270*90c8c64dSAndroid Build Coastguard Worker other_group.sort(key=functools.cmp_to_key(compare_pkg_deps)) 271*90c8c64dSAndroid Build Coastguard Worker sorted_tuples.extend(other_group) 272*90c8c64dSAndroid Build Coastguard Worker return sorted_tuples 273*90c8c64dSAndroid Build Coastguard Worker 274*90c8c64dSAndroid Build Coastguard Worker 275*90c8c64dSAndroid Build Coastguard Workerdef show_all_dependencies(args, found_pkgs): 276*90c8c64dSAndroid Build Coastguard Worker """Topological sort found_pkgs and report number of dependent packages.""" 277*90c8c64dSAndroid Build Coastguard Worker found_pkgs = sort_found_pkgs(found_pkgs) 278*90c8c64dSAndroid Build Coastguard Worker max_length = functools.reduce(lambda a, t: max(a, len(t[0])), found_pkgs, 1) 279*90c8c64dSAndroid Build Coastguard Worker name_format = "{:" + str(max_length) + "s}" 280*90c8c64dSAndroid Build Coastguard Worker print("\n##### Summary of all dependent package counts #####") 281*90c8c64dSAndroid Build Coastguard Worker pattern = "{:>9s}_deps[k] = # of {}dependent packages of {}" 282*90c8c64dSAndroid Build Coastguard Worker for (prefix, suffix) in [(" ", "pkg[k]"), ("all_", "pkg[1] to pkg[k]")]: 283*90c8c64dSAndroid Build Coastguard Worker for (name, kind) in [("build", "non-dev-"), ("dev", "all ")]: 284*90c8c64dSAndroid Build Coastguard Worker print(pattern.format(prefix + name, kind, suffix)) 285*90c8c64dSAndroid Build Coastguard Worker print(("{:>4s} " + name_format + " {:>10s} {:>10s} {:>14s} {:>14s}").format( 286*90c8c64dSAndroid Build Coastguard Worker "k", "pkg", "build_deps", "dev_deps", "all_build_deps", "all_dev_deps")) 287*90c8c64dSAndroid Build Coastguard Worker all_pkgs = set() 288*90c8c64dSAndroid Build Coastguard Worker all_build_deps = set() 289*90c8c64dSAndroid Build Coastguard Worker all_dev_deps = set() 290*90c8c64dSAndroid Build Coastguard Worker k = 0 291*90c8c64dSAndroid Build Coastguard Worker prev_all_build_deps = set() 292*90c8c64dSAndroid Build Coastguard Worker prev_all_dev_deps = set() 293*90c8c64dSAndroid Build Coastguard Worker for (pkg, build_deps, dev_deps) in found_pkgs: 294*90c8c64dSAndroid Build Coastguard Worker all_pkgs.add(pkg) 295*90c8c64dSAndroid Build Coastguard Worker all_build_deps = all_build_deps.union(build_deps).difference(all_pkgs) 296*90c8c64dSAndroid Build Coastguard Worker all_dev_deps = all_dev_deps.union(dev_deps).difference(all_pkgs) 297*90c8c64dSAndroid Build Coastguard Worker k += 1 298*90c8c64dSAndroid Build Coastguard Worker print(("{:4d} " + name_format + " {:10d} {:10d} {:14d} {:14d}").format( 299*90c8c64dSAndroid Build Coastguard Worker k, pkg, len(build_deps), len(dev_deps), len(all_build_deps), 300*90c8c64dSAndroid Build Coastguard Worker len(all_dev_deps))) 301*90c8c64dSAndroid Build Coastguard Worker if prev_all_build_deps != all_build_deps: 302*90c8c64dSAndroid Build Coastguard Worker echo_all_deps(args, "all_build_deps", all_build_deps) 303*90c8c64dSAndroid Build Coastguard Worker prev_all_build_deps = all_build_deps 304*90c8c64dSAndroid Build Coastguard Worker if prev_all_dev_deps != all_dev_deps: 305*90c8c64dSAndroid Build Coastguard Worker echo_all_deps(args, "all_dev_deps", all_dev_deps) 306*90c8c64dSAndroid Build Coastguard Worker prev_all_dev_deps = all_dev_deps 307*90c8c64dSAndroid Build Coastguard Worker print("\nNOTE: from all {} package(s):{}".format( 308*90c8c64dSAndroid Build Coastguard Worker len(all_pkgs), set2list(all_pkgs))) 309*90c8c64dSAndroid Build Coastguard Worker for (kind, deps) in [("non-dev-", all_build_deps), ("", all_dev_deps)]: 310*90c8c64dSAndroid Build Coastguard Worker if deps: 311*90c8c64dSAndroid Build Coastguard Worker print("NOTE: found {:3d} other {}dependent package(s):{}".format( 312*90c8c64dSAndroid Build Coastguard Worker len(deps), kind, set2list(deps))) 313*90c8c64dSAndroid Build Coastguard Worker 314*90c8c64dSAndroid Build Coastguard Worker 315*90c8c64dSAndroid Build Coastguard Workerdef crates_io_find_pkgs(args, pkgs, found_pkgs): 316*90c8c64dSAndroid Build Coastguard Worker """Call crates.io api to find direct dependent packages.""" 317*90c8c64dSAndroid Build Coastguard Worker success = True 318*90c8c64dSAndroid Build Coastguard Worker for pkg in sorted(pkgs): 319*90c8c64dSAndroid Build Coastguard Worker ok, build_deps, dev_deps = get_crate_dependencies(args, pkg) 320*90c8c64dSAndroid Build Coastguard Worker if not ok: 321*90c8c64dSAndroid Build Coastguard Worker success = False 322*90c8c64dSAndroid Build Coastguard Worker else: 323*90c8c64dSAndroid Build Coastguard Worker found_pkgs.append((pkg, build_deps, dev_deps)) 324*90c8c64dSAndroid Build Coastguard Worker return success 325*90c8c64dSAndroid Build Coastguard Worker 326*90c8c64dSAndroid Build Coastguard Worker 327*90c8c64dSAndroid Build Coastguard Workerdef add_non_dev_dependencies(args, all_deps, core_pkgs, visited, pkg): 328*90c8c64dSAndroid Build Coastguard Worker """Add reachable non-dev dependencies to all_deps[pkg]'s dependencies.""" 329*90c8c64dSAndroid Build Coastguard Worker if pkg not in all_deps: 330*90c8c64dSAndroid Build Coastguard Worker ok, build_deps, dev_deps = get_crate_dependencies(args, pkg) 331*90c8c64dSAndroid Build Coastguard Worker if not ok: 332*90c8c64dSAndroid Build Coastguard Worker return set() 333*90c8c64dSAndroid Build Coastguard Worker all_deps[pkg] = (pkg, build_deps, dev_deps) 334*90c8c64dSAndroid Build Coastguard Worker else: 335*90c8c64dSAndroid Build Coastguard Worker (_, build_deps, dev_deps) = all_deps[pkg] 336*90c8c64dSAndroid Build Coastguard Worker if pkg in visited: 337*90c8c64dSAndroid Build Coastguard Worker return build_deps 338*90c8c64dSAndroid Build Coastguard Worker visited.add(pkg) 339*90c8c64dSAndroid Build Coastguard Worker 340*90c8c64dSAndroid Build Coastguard Worker for p in sorted(build_deps): 341*90c8c64dSAndroid Build Coastguard Worker if p not in core_pkgs and pkg in core_pkgs: 342*90c8c64dSAndroid Build Coastguard Worker core_pkgs.add(p) 343*90c8c64dSAndroid Build Coastguard Worker deps = add_non_dev_dependencies(args, all_deps, core_pkgs, visited, p) 344*90c8c64dSAndroid Build Coastguard Worker build_deps = build_deps.union(deps) 345*90c8c64dSAndroid Build Coastguard Worker if pkg in core_pkgs: 346*90c8c64dSAndroid Build Coastguard Worker for p in sorted(dev_deps): 347*90c8c64dSAndroid Build Coastguard Worker deps = add_non_dev_dependencies(args, all_deps, core_pkgs, visited, p) 348*90c8c64dSAndroid Build Coastguard Worker dev_deps = dev_deps.union(deps) 349*90c8c64dSAndroid Build Coastguard Worker all_deps[pkg] = (pkg, build_deps, dev_deps) 350*90c8c64dSAndroid Build Coastguard Worker return build_deps 351*90c8c64dSAndroid Build Coastguard Worker 352*90c8c64dSAndroid Build Coastguard Worker 353*90c8c64dSAndroid Build Coastguard Workerdef add_indirect_build_deps(args, found_pkgs): 354*90c8c64dSAndroid Build Coastguard Worker """Add all indirect dependencies and return a new found_pkgs.""" 355*90c8c64dSAndroid Build Coastguard Worker all_deps = dict(map(lambda t: (t[0], t), found_pkgs)) 356*90c8c64dSAndroid Build Coastguard Worker core_pkgs = set(map(lambda t: t[0], found_pkgs)) 357*90c8c64dSAndroid Build Coastguard Worker dev_pkgs = set() 358*90c8c64dSAndroid Build Coastguard Worker dump_pkgs(args, "BEFORE", all_deps, core_pkgs, dev_pkgs) 359*90c8c64dSAndroid Build Coastguard Worker visited = set() 360*90c8c64dSAndroid Build Coastguard Worker for pkg in sorted(core_pkgs): 361*90c8c64dSAndroid Build Coastguard Worker add_non_dev_dependencies(args, all_deps, core_pkgs, visited, pkg) 362*90c8c64dSAndroid Build Coastguard Worker # Revisit core packages to add build_deps into core_pkgs and other deps. 363*90c8c64dSAndroid Build Coastguard Worker check_again = True 364*90c8c64dSAndroid Build Coastguard Worker while check_again: 365*90c8c64dSAndroid Build Coastguard Worker pattern = "Revisiting {} core packages:{}" 366*90c8c64dSAndroid Build Coastguard Worker echo(args, pattern.format(len(core_pkgs), set2list(core_pkgs))) 367*90c8c64dSAndroid Build Coastguard Worker check_again = False 368*90c8c64dSAndroid Build Coastguard Worker visited = set() 369*90c8c64dSAndroid Build Coastguard Worker num_core_pkgs = len(core_pkgs) 370*90c8c64dSAndroid Build Coastguard Worker for pkg in sorted(core_pkgs): 371*90c8c64dSAndroid Build Coastguard Worker (_, build_deps, dev_deps) = all_deps[pkg] 372*90c8c64dSAndroid Build Coastguard Worker (num_build_deps, num_dev_deps) = (len(build_deps), len(dev_deps)) 373*90c8c64dSAndroid Build Coastguard Worker add_non_dev_dependencies(args, all_deps, core_pkgs, visited, pkg) 374*90c8c64dSAndroid Build Coastguard Worker (_, build_deps, dev_deps) = all_deps[pkg] 375*90c8c64dSAndroid Build Coastguard Worker (new_num_build_deps, new_num_dev_deps) = (len(build_deps), len(dev_deps)) 376*90c8c64dSAndroid Build Coastguard Worker # If build_deps, dev_deps, or core_pkgs was changed, check again. 377*90c8c64dSAndroid Build Coastguard Worker if (num_build_deps != new_num_build_deps or 378*90c8c64dSAndroid Build Coastguard Worker num_dev_deps != new_num_dev_deps or num_core_pkgs != len(core_pkgs)): 379*90c8c64dSAndroid Build Coastguard Worker check_again = True 380*90c8c64dSAndroid Build Coastguard Worker dev_pkgs = visited.difference(core_pkgs) 381*90c8c64dSAndroid Build Coastguard Worker dump_pkgs(args, "AFTER", all_deps, core_pkgs, dev_pkgs) 382*90c8c64dSAndroid Build Coastguard Worker found_pkgs = list(map(lambda p: all_deps[p], core_pkgs)) 383*90c8c64dSAndroid Build Coastguard Worker found_pkgs.sort(key=lambda t: t[0]) 384*90c8c64dSAndroid Build Coastguard Worker return found_pkgs 385*90c8c64dSAndroid Build Coastguard Worker 386*90c8c64dSAndroid Build Coastguard Worker 387*90c8c64dSAndroid Build Coastguard Workerdef echo_dump_found_pkgs(args, msg, all_deps, pkgs): 388*90c8c64dSAndroid Build Coastguard Worker if not args.v or not pkgs: 389*90c8c64dSAndroid Build Coastguard Worker return 390*90c8c64dSAndroid Build Coastguard Worker echo(args, msg) 391*90c8c64dSAndroid Build Coastguard Worker for pkg in sorted(pkgs): 392*90c8c64dSAndroid Build Coastguard Worker (_, build_deps, dev_deps) = all_deps[pkg] 393*90c8c64dSAndroid Build Coastguard Worker for (name, deps) in [("normal", build_deps), ("dev", dev_deps)]: 394*90c8c64dSAndroid Build Coastguard Worker pattern = " {} has {} " + name + " deps:{}" 395*90c8c64dSAndroid Build Coastguard Worker echo(args, pattern.format(pkg, len(deps), set2list(deps))) 396*90c8c64dSAndroid Build Coastguard Worker 397*90c8c64dSAndroid Build Coastguard Worker 398*90c8c64dSAndroid Build Coastguard Workerdef dump_pkgs(args, msg, all_deps, core_pkgs, dev_pkgs): 399*90c8c64dSAndroid Build Coastguard Worker echo_dump_found_pkgs(args, msg + " core_pkgs:", all_deps, core_pkgs) 400*90c8c64dSAndroid Build Coastguard Worker echo_dump_found_pkgs(args, msg + " other_dev_pkgs:", all_deps, dev_pkgs) 401*90c8c64dSAndroid Build Coastguard Worker 402*90c8c64dSAndroid Build Coastguard Worker 403*90c8c64dSAndroid Build Coastguard Workerdef show_dependencies(args): 404*90c8c64dSAndroid Build Coastguard Worker """Calls crates.io api to find dependent packages; returns True on success.""" 405*90c8c64dSAndroid Build Coastguard Worker all_pkgs = set(map(lambda p: pkg_base_name(p)[0], args.pkgs)) 406*90c8c64dSAndroid Build Coastguard Worker if "" in all_pkgs: 407*90c8c64dSAndroid Build Coastguard Worker # TODO(chh): detect and report ill formed names in args.pkgs 408*90c8c64dSAndroid Build Coastguard Worker print("WARNING: skip some ill formatted pkg arguments.") 409*90c8c64dSAndroid Build Coastguard Worker all_pkgs = all_pkgs.remove("") 410*90c8c64dSAndroid Build Coastguard Worker if not all_pkgs: 411*90c8c64dSAndroid Build Coastguard Worker print("ERROR: no valid pkg names to show dependencies.") 412*90c8c64dSAndroid Build Coastguard Worker return False 413*90c8c64dSAndroid Build Coastguard Worker pkgs = sorted(all_pkgs) 414*90c8c64dSAndroid Build Coastguard Worker found_pkgs = [] 415*90c8c64dSAndroid Build Coastguard Worker success = crates_io_find_pkgs(args, pkgs, found_pkgs) 416*90c8c64dSAndroid Build Coastguard Worker if not found_pkgs: 417*90c8c64dSAndroid Build Coastguard Worker return False 418*90c8c64dSAndroid Build Coastguard Worker # All normal (non-dev) dependent packages will be added into found_pkgs. 419*90c8c64dSAndroid Build Coastguard Worker found_pkgs = add_indirect_build_deps(args, found_pkgs) 420*90c8c64dSAndroid Build Coastguard Worker show_all_dependencies(args, found_pkgs) 421*90c8c64dSAndroid Build Coastguard Worker return success 422*90c8c64dSAndroid Build Coastguard Worker 423*90c8c64dSAndroid Build Coastguard Worker 424*90c8c64dSAndroid Build Coastguard Workerdef main(): 425*90c8c64dSAndroid Build Coastguard Worker args = parse_args() 426*90c8c64dSAndroid Build Coastguard Worker if args.show: 427*90c8c64dSAndroid Build Coastguard Worker # only show dependencies, not to fetch any package 428*90c8c64dSAndroid Build Coastguard Worker if not show_dependencies(args): 429*90c8c64dSAndroid Build Coastguard Worker sys.exit(2) 430*90c8c64dSAndroid Build Coastguard Worker return 431*90c8c64dSAndroid Build Coastguard Worker echo(args, "to fetch packages = {}".format(args.pkgs)) 432*90c8c64dSAndroid Build Coastguard Worker success = True 433*90c8c64dSAndroid Build Coastguard Worker for pkg in args.pkgs: 434*90c8c64dSAndroid Build Coastguard Worker echo(args, "trying to fetch package '{}'".format(pkg)) 435*90c8c64dSAndroid Build Coastguard Worker try: 436*90c8c64dSAndroid Build Coastguard Worker if not fetch_pkg(args, pkg, find_dl_path(args, pkg)): 437*90c8c64dSAndroid Build Coastguard Worker success = False 438*90c8c64dSAndroid Build Coastguard Worker except urllib.error.HTTPError: 439*90c8c64dSAndroid Build Coastguard Worker print("ERROR: unknown package '{}'".format(pkg)) 440*90c8c64dSAndroid Build Coastguard Worker success = False 441*90c8c64dSAndroid Build Coastguard Worker if not success: 442*90c8c64dSAndroid Build Coastguard Worker sys.exit(1) 443*90c8c64dSAndroid Build Coastguard Worker 444*90c8c64dSAndroid Build Coastguard Worker 445*90c8c64dSAndroid Build Coastguard Workerif __name__ == "__main__": 446*90c8c64dSAndroid Build Coastguard Worker main() 447