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 7import argparse 8import pathlib 9import subprocess 10import os 11import sys 12import re 13 14# Set up path to be able to import action_helpers. 15sys.path.append( 16 os.path.join(os.path.dirname(os.path.abspath(__file__)), os.pardir, 17 os.pardir, 'build')) 18import action_helpers 19 20# This script wraps rustc for (currently) these reasons: 21# * To work around some ldflags escaping performed by ninja/gn 22# * To remove dependencies on some environment variables from the .d file. 23# * To enable use of .rsp files. 24# * To work around two gn bugs on Windows 25# 26# LDFLAGS ESCAPING 27# 28# This script performs a simple function to work around some of the 29# parameter escaping performed by ninja/gn. 30# 31# rustc invocations are given access to {{rustflags}} and {{ldflags}}. 32# We want to pass {{ldflags}} into rustc, using -Clink-args="{{ldflags}}". 33# Unfortunately, ninja assumes that each item in {{ldflags}} is an 34# independent command-line argument and will have escaped them appropriately 35# for use on a bare command line, instead of in a string. 36# 37# This script converts such {{ldflags}} into individual -Clink-arg=X 38# arguments to rustc. 39# 40# RUSTENV dependency stripping 41# 42# When Rust code depends on an environment variable at build-time 43# (using the env! macro), rustc spots that and adds it to the .d file. 44# Ninja then parses that .d file and determines that the environment 45# dependency means that the target always needs to be rebuilt. 46# 47# That's all correct, but _we_ know that some of these environment 48# variables (typically, all of them) are set by .gn files which ninja 49# tracks independently. So we remove them from the .d file. 50# 51# RSP files: 52# 53# We want to put the ninja/gn variables {{rustdeps}} and {{externs}} 54# in an RSP file. Unfortunately, they are space-separated variables 55# but Rust requires a newline-separated input. This script duly makes 56# the adjustment. This works around a gn issue: 57# TODO(https://bugs.chromium.org/p/gn/issues/detail?id=249): fix this 58# 59# WORKAROUND WINDOWS BUGS: 60# 61# On Windows platforms, this temporarily works around some issues in gn. 62# See comments inline, linking to the relevant gn fixes. 63# 64# Usage: 65# rustc_wrapper.py --rustc <path to rustc> --depfile <path to .d file> 66# -- <normal rustc args> LDFLAGS {{ldflags}} RUSTENV {{rustenv}} 67# The LDFLAGS token is discarded, and everything after that is converted 68# to being a series of -Clink-arg=X arguments, until or unless RUSTENV 69# is encountered, after which those are interpreted as environment 70# variables to pass to rustc (and which will be removed from the .d file). 71# 72# Both LDFLAGS and RUSTENV **MUST** be specified, in that order, even if 73# the list following them is empty. 74# 75# TODO(https://github.com/rust-lang/rust/issues/73632): avoid using rustc 76# for linking in the first place. Most of our binaries are linked using 77# clang directly, but there are some types of Rust build product which 78# must currently be created by rustc (e.g. unit test executables). As 79# part of support for using non-rustc linkers, we should arrange to extract 80# such functionality from rustc so that we can make all types of binary 81# using our clang toolchain. That will remove the need for most of this 82# script. 83 84 85# Equivalent of python3.9 built-in 86def remove_lib_suffix_from_l_args(text): 87 if text.startswith("-l") and text.endswith(".lib"): 88 return text[:-len(".lib")] 89 return text 90 91 92def main(): 93 parser = argparse.ArgumentParser() 94 parser.add_argument('--rustc', required=True, type=pathlib.Path) 95 parser.add_argument('--depfile', type=pathlib.Path) 96 parser.add_argument('--rsp', type=pathlib.Path) 97 parser.add_argument('args', metavar='ARG', nargs='+') 98 99 args = parser.parse_args() 100 101 remaining_args = args.args 102 103 ldflags_separator = remaining_args.index("LDFLAGS") 104 rustenv_separator = remaining_args.index("RUSTENV", ldflags_separator) 105 rustc_args = remaining_args[:ldflags_separator] 106 ldflags = remaining_args[ldflags_separator + 1:rustenv_separator] 107 rustenv = remaining_args[rustenv_separator + 1:] 108 109 is_windows = os.name == 'nt' 110 111 rustc_args.extend(["-Clink-arg=%s" % arg for arg in ldflags]) 112 113 # Workaround for https://bugs.chromium.org/p/gn/issues/detail?id=249 114 if args.rsp: 115 with open(args.rsp) as rspfile: 116 rsp_args = [l.rstrip() for l in rspfile.read().split(' ') if l.rstrip()] 117 if is_windows: 118 # Work around for hard-coded string in gn; full fix will come from 119 # https://gn-review.googlesource.com/c/gn/+/12460 120 rsp_args = [arg for arg in rsp_args if not arg.endswith("-Bdynamic")] 121 # Work around for "-l<foo>.lib", where ".lib" suffix is undesirable. 122 # Full fix will come from https://gn-review.googlesource.com/c/gn/+/12480 123 rsp_args = [remove_lib_suffix_from_l_args(arg) for arg in rsp_args] 124 with open(args.rsp, 'w') as rspfile: 125 rspfile.write("\n".join(rsp_args)) 126 rustc_args.append(f'@{args.rsp}') 127 128 env = os.environ.copy() 129 fixed_env_vars = [] 130 for item in rustenv: 131 (k, v) = item.split("=", 1) 132 env[k] = v 133 fixed_env_vars.append(k) 134 135 r = subprocess.run([args.rustc, *rustc_args], env=env, check=False) 136 if r.returncode != 0: 137 sys.exit(r.returncode) 138 139 # Now edit the depfile produced 140 if args.depfile is not None: 141 env_dep_re = re.compile("# env-dep:(.*)=.*") 142 replacement_lines = [] 143 dirty = False 144 with open(args.depfile, encoding="utf-8") as d: 145 for line in d: 146 m = env_dep_re.match(line) 147 if m and m.group(1) in fixed_env_vars: 148 dirty = True # skip this line 149 else: 150 replacement_lines.append(line) 151 if dirty: # we made a change, let's write out the file 152 with action_helpers.atomic_output(args.depfile) as output: 153 output.write("\n".join(replacement_lines).encode("utf-8")) 154 155 156if __name__ == '__main__': 157 sys.exit(main()) 158