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