xref: /aosp_15_r20/development/scripts/get_rust_pkg.py (revision 90c8c64db3049935a07c6143d7fd006e26f8ecca)
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