• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env/python3
2
3# Copyright 2021 The Chromium Authors
4# Use of this source code is governed by a BSD-style license that can be
5# found in the LICENSE file.
6
7# See BUILD.gn in this directory for an explanation of what this script is for.
8
9import argparse
10import os
11import stat
12import sys
13import shutil
14import subprocess
15import re
16
17from collections import defaultdict
18
19EXPECTED_STDLIB_INPUT_REGEX = re.compile(r"([0-9a-z_]+)(?:-([0-9]+))?$")
20RLIB_NAME_REGEX = re.compile(r"lib([0-9a-z_]+)-([0-9a-f]+)\.rlib$")
21
22
23def main():
24  parser = argparse.ArgumentParser("find_std_rlibs.py")
25  parser.add_argument("--rust-bin-dir",
26                      help="Path to Rust binaries",
27                      required=True),
28  parser.add_argument("--target", help="Rust target triple", required=False),
29  parser.add_argument("--output",
30                      help="Path to rlibs without suffixes",
31                      required=True)
32  parser.add_argument("--depfile", help="Path to write depfile", required=True)
33  parser.add_argument("--depfile-target",
34                      help="Target to key depfile around",
35                      required=True)
36  parser.add_argument("--stdlibs",
37                      help="Expected list of standard library libraries")
38  parser.add_argument("--ignore-stdlibs",
39                      help="List of sysroot libraries to ignore")
40  parser.add_argument("--extra-libs",
41                      help="List of extra non-libstd sysroot libraries")
42  parser.add_argument("--rustc-revision",
43                      help="Not used, just passed from GN to add a dependency"
44                      " on the rustc version.")
45  args = parser.parse_args()
46
47  # Expected rlibs by concise name (the crate name, plus a disambiguating suffix
48  # e.g. "-2" when necessary).
49  if args.stdlibs:
50    rlibs_expected = set()
51    for lib in args.stdlibs.split(','):
52      # The version is only included if there's more than one of `name`, and
53      # even then is only included for the 2nd onward.
54      (name, version) = EXPECTED_STDLIB_INPUT_REGEX.match(lib).group(1, 2)
55      if version is None:
56        rlibs_expected.add(name)
57      else:
58        rlibs_expected.add(f"{name}-{version}")
59    ignore_rlibs = set()
60    if args.ignore_stdlibs is not None:
61      ignore_rlibs = set(args.ignore_stdlibs.split(','))
62  else:
63    rlibs_expected = None
64
65  extra_libs = set()
66  if args.extra_libs:
67    for lib in args.extra_libs.split(','):
68      extra_libs.add(lib)
69
70  # Ask rustc where to find the stdlib for this target.
71  rustc = os.path.join(args.rust_bin_dir, "rustc")
72  rustc_args = [rustc, "--print", "target-libdir"]
73  if args.target:
74    rustc_args.extend(["--target", args.target])
75  rustlib_dir = subprocess.check_output(rustc_args).rstrip().decode()
76
77  # Copy the rlibs to a predictable location. Whilst we're doing so,
78  # also write a .d file so that ninja knows it doesn't need to do this
79  # again unless the source rlibs change.
80  # Format:
81  # <output path to>/lib<lib name.rlib>: <path to each Rust stlib rlib>
82  with open(args.depfile, 'w') as depfile:
83    # Ninja isn't versatile at understanding depfiles. We have to say that a
84    # single output depends on all the inputs. We choose any one of the
85    # output rlibs for that purpose. If any of the input rlibs change, ninja
86    # will run this script again and we'll copy them all afresh.
87    depfile.write(
88        "%s:" % (os.path.join(args.output, "lib%s.rlib" % args.depfile_target)))
89
90    def copy_file(infile, outfile):
91      depfile.write(f" {infile}")
92      if (not os.path.exists(outfile)
93          or os.stat(infile).st_mtime != os.stat(outfile).st_mtime):
94        if os.path.exists(outfile):
95          st = os.stat(outfile)
96          os.chmod(outfile, st.st_mode | stat.S_IWUSR)
97        shutil.copy(infile, outfile)
98
99    # Each rlib is named "lib<crate_name>-<metadata>.rlib". The metadata
100    # disambiguates multiple crates of the same name. We want to throw away the
101    # metadata and use stable names. To do so, we replace the metadata bit with
102    # a simple number 1, 2, etc. It doesn't matter how we assign these numbers
103    # as long as it's consistent for a particular set of rlibs.
104
105    # The rlib names present in the Rust distribution, including metadata. We
106    # sort this list so crates of the same name are ordered by metadata. Also
107    # filter out names that aren't rlibs.
108    rlibs_present = [
109        name for name in os.listdir(rustlib_dir) if name.endswith('.rlib')
110    ]
111    rlibs_present.sort()
112
113    # Keep a count of the instances a crate name, so we can disambiguate the
114    # rlibs with an incrementing number at the end.
115    rlibs_seen = defaultdict(lambda: 0)
116
117    for f in rlibs_present:
118      # As standard Rust includes a hash on the end of each filename
119      # representing certain metadata, to ensure that clients will link
120      # against the correct version. As gn will be manually passing
121      # the correct file path to our linker invocations, we don't need
122      # that, and it would prevent us having the predictable filenames
123      # which we need for statically computable gn dependency rules.
124      (crate_name, metadata) = RLIB_NAME_REGEX.match(f).group(1, 2)
125
126      # Use the number of times we've seen this name to disambiguate the output
127      # filenames. Since we sort the input filenames including the metadata,
128      # this will be the same every time.
129      #
130      # Only append the times seen if it is greater than 1. This allows the
131      # BUILD.gn file to avoid adding '-1' to every name if there's only one
132      # version of a particular one.
133      rlibs_seen[crate_name] += 1
134      if rlibs_seen[crate_name] == 1:
135        concise_name = crate_name
136      else:
137        concise_name = "%s-%d" % (crate_name, rlibs_seen[crate_name])
138
139      output_filename = f"lib{concise_name}.rlib"
140
141      if rlibs_expected is not None:
142        if concise_name in ignore_rlibs:
143          continue
144        if concise_name not in rlibs_expected:
145          raise Exception("Found stdlib rlib that wasn't expected: %s" % f)
146        rlibs_expected.remove(concise_name)
147
148      infile = os.path.join(rustlib_dir, f)
149      outfile = os.path.join(args.output, output_filename)
150      copy_file(infile, outfile)
151
152    for f in extra_libs:
153      infile = os.path.join(rustlib_dir, f)
154      outfile = os.path.join(args.output, f)
155      copy_file(infile, outfile)
156
157    depfile.write("\n")
158    if rlibs_expected:
159      raise Exception("We failed to find all expected stdlib rlibs: %s" %
160                      ','.join(rlibs_expected))
161
162
163if __name__ == '__main__':
164  sys.exit(main())
165