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