xref: /aosp_15_r20/external/cronet/build/rust/std/find_std_rlibs.py (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
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("--extra-libs",
37                      help="List of extra non-libstd sysroot libraries")
38  parser.add_argument("--rustc-revision",
39                      help="Not used, just passed from GN to add a dependency"
40                      " on the rustc version.")
41  args = parser.parse_args()
42
43  extra_libs = set()
44  if args.extra_libs:
45    for lib in args.extra_libs.split(','):
46      extra_libs.add(lib)
47
48  # Ask rustc where to find the stdlib for this target.
49  rustc = os.path.join(args.rust_bin_dir, "rustc")
50  rustc_args = [rustc, "--print", "target-libdir"]
51  if args.target:
52    rustc_args.extend(["--target", args.target])
53  rustlib_dir = subprocess.check_output(rustc_args).rstrip().decode()
54
55  # Copy the rlibs to a predictable location. Whilst we're doing so,
56  # also write a .d file so that ninja knows it doesn't need to do this
57  # again unless the source rlibs change.
58  # Format:
59  # <output path to>/lib<lib name.rlib>: <path to each Rust stlib rlib>
60  with open(args.depfile, 'w') as depfile:
61    # Ninja isn't versatile at understanding depfiles. We have to say that a
62    # single output depends on all the inputs. We choose any one of the
63    # output rlibs for that purpose. If any of the input rlibs change, ninja
64    # will run this script again and we'll copy them all afresh.
65    depfile.write(
66        "%s:" % (os.path.join(args.output, "lib%s.rlib" % args.depfile_target)))
67
68    def copy_file(infile, outfile):
69      depfile.write(f" {infile}")
70      if (not os.path.exists(outfile)
71          or os.stat(infile).st_mtime != os.stat(outfile).st_mtime):
72        if os.path.exists(outfile):
73          st = os.stat(outfile)
74          os.chmod(outfile, st.st_mode | stat.S_IWUSR)
75        shutil.copy(infile, outfile)
76
77    # Each rlib is named "lib<crate_name>-<metadata>.rlib". The metadata
78    # disambiguates multiple crates of the same name. We want to throw away the
79    # metadata and use stable names. To do so, we replace the metadata bit with
80    # a simple number 1, 2, etc. It doesn't matter how we assign these numbers
81    # as long as it's consistent for a particular set of rlibs.
82
83    # The rlib names present in the Rust distribution, including metadata. We
84    # sort this list so crates of the same name are ordered by metadata. Also
85    # filter out names that aren't rlibs.
86    rlibs_present = [
87        name for name in os.listdir(rustlib_dir) if name.endswith('.rlib')
88    ]
89    rlibs_present.sort()
90
91    # Keep a count of the instances a crate name, so we can disambiguate the
92    # rlibs with an incrementing number at the end.
93    rlibs_seen = defaultdict(lambda: 0)
94
95    for f in rlibs_present:
96      # As standard Rust includes a hash on the end of each filename
97      # representing certain metadata, to ensure that clients will link
98      # against the correct version. As gn will be manually passing
99      # the correct file path to our linker invocations, we don't need
100      # that, and it would prevent us having the predictable filenames
101      # which we need for statically computable gn dependency rules.
102      (crate_name, metadata) = RLIB_NAME_REGEX.match(f).group(1, 2)
103
104      # Use the number of times we've seen this name to disambiguate the output
105      # filenames. Since we sort the input filenames including the metadata,
106      # this will be the same every time.
107      #
108      # Only append the times seen if it is greater than 1. This allows the
109      # BUILD.gn file to avoid adding '-1' to every name if there's only one
110      # version of a particular one.
111      rlibs_seen[crate_name] += 1
112      if rlibs_seen[crate_name] == 1:
113        concise_name = crate_name
114      else:
115        concise_name = "%s-%d" % (crate_name, rlibs_seen[crate_name])
116
117      output_filename = f"lib{concise_name}.rlib"
118
119      infile = os.path.join(rustlib_dir, f)
120      outfile = os.path.join(args.output, output_filename)
121      copy_file(infile, outfile)
122
123    for f in extra_libs:
124      infile = os.path.join(rustlib_dir, f)
125      outfile = os.path.join(args.output, f)
126      copy_file(infile, outfile)
127
128    depfile.write("\n")
129
130
131if __name__ == '__main__':
132  sys.exit(main())
133