158 lines
5.8 KiB
Python
158 lines
5.8 KiB
Python
|
|
#!/usr/bin/env python3
|
||
|
|
|
||
|
|
# Copyright 2021 The Chromium Authors
|
||
|
|
# Use of this source code is governed by a BSD-style license that can be
|
||
|
|
# found in the LICENSE file.
|
||
|
|
|
||
|
|
import argparse
|
||
|
|
import pathlib
|
||
|
|
import subprocess
|
||
|
|
import os
|
||
|
|
import sys
|
||
|
|
import re
|
||
|
|
|
||
|
|
# Set up path to be able to import action_helpers.
|
||
|
|
sys.path.append(
|
||
|
|
os.path.join(os.path.dirname(os.path.abspath(__file__)), os.pardir,
|
||
|
|
os.pardir, 'build'))
|
||
|
|
import action_helpers
|
||
|
|
|
||
|
|
# This script wraps rustc for (currently) these reasons:
|
||
|
|
# * To work around some ldflags escaping performed by ninja/gn
|
||
|
|
# * To remove dependencies on some environment variables from the .d file.
|
||
|
|
# * To enable use of .rsp files.
|
||
|
|
# * To work around two gn bugs on Windows
|
||
|
|
#
|
||
|
|
# LDFLAGS ESCAPING
|
||
|
|
#
|
||
|
|
# This script performs a simple function to work around some of the
|
||
|
|
# parameter escaping performed by ninja/gn.
|
||
|
|
#
|
||
|
|
# rustc invocations are given access to {{rustflags}} and {{ldflags}}.
|
||
|
|
# We want to pass {{ldflags}} into rustc, using -Clink-args="{{ldflags}}".
|
||
|
|
# Unfortunately, ninja assumes that each item in {{ldflags}} is an
|
||
|
|
# independent command-line argument and will have escaped them appropriately
|
||
|
|
# for use on a bare command line, instead of in a string.
|
||
|
|
#
|
||
|
|
# This script converts such {{ldflags}} into individual -Clink-arg=X
|
||
|
|
# arguments to rustc.
|
||
|
|
#
|
||
|
|
# RUSTENV dependency stripping
|
||
|
|
#
|
||
|
|
# When Rust code depends on an environment variable at build-time
|
||
|
|
# (using the env! macro), rustc spots that and adds it to the .d file.
|
||
|
|
# Ninja then parses that .d file and determines that the environment
|
||
|
|
# dependency means that the target always needs to be rebuilt.
|
||
|
|
#
|
||
|
|
# That's all correct, but _we_ know that some of these environment
|
||
|
|
# variables (typically, all of them) are set by .gn files which ninja
|
||
|
|
# tracks independently. So we remove them from the .d file.
|
||
|
|
#
|
||
|
|
# RSP files:
|
||
|
|
#
|
||
|
|
# We want to put the ninja/gn variables {{rustdeps}} and {{externs}}
|
||
|
|
# in an RSP file. Unfortunately, they are space-separated variables
|
||
|
|
# but Rust requires a newline-separated input. This script duly makes
|
||
|
|
# the adjustment. This works around a gn issue:
|
||
|
|
# TODO(https://bugs.chromium.org/p/gn/issues/detail?id=249): fix this
|
||
|
|
#
|
||
|
|
# WORKAROUND WINDOWS BUGS:
|
||
|
|
#
|
||
|
|
# On Windows platforms, this temporarily works around some issues in gn.
|
||
|
|
# See comments inline, linking to the relevant gn fixes.
|
||
|
|
#
|
||
|
|
# Usage:
|
||
|
|
# rustc_wrapper.py --rustc <path to rustc> --depfile <path to .d file>
|
||
|
|
# -- <normal rustc args> LDFLAGS {{ldflags}} RUSTENV {{rustenv}}
|
||
|
|
# The LDFLAGS token is discarded, and everything after that is converted
|
||
|
|
# to being a series of -Clink-arg=X arguments, until or unless RUSTENV
|
||
|
|
# is encountered, after which those are interpreted as environment
|
||
|
|
# variables to pass to rustc (and which will be removed from the .d file).
|
||
|
|
#
|
||
|
|
# Both LDFLAGS and RUSTENV **MUST** be specified, in that order, even if
|
||
|
|
# the list following them is empty.
|
||
|
|
#
|
||
|
|
# TODO(https://github.com/rust-lang/rust/issues/73632): avoid using rustc
|
||
|
|
# for linking in the first place. Most of our binaries are linked using
|
||
|
|
# clang directly, but there are some types of Rust build product which
|
||
|
|
# must currently be created by rustc (e.g. unit test executables). As
|
||
|
|
# part of support for using non-rustc linkers, we should arrange to extract
|
||
|
|
# such functionality from rustc so that we can make all types of binary
|
||
|
|
# using our clang toolchain. That will remove the need for most of this
|
||
|
|
# script.
|
||
|
|
|
||
|
|
|
||
|
|
# Equivalent of python3.9 built-in
|
||
|
|
def remove_lib_suffix_from_l_args(text):
|
||
|
|
if text.startswith("-l") and text.endswith(".lib"):
|
||
|
|
return text[:-len(".lib")]
|
||
|
|
return text
|
||
|
|
|
||
|
|
|
||
|
|
def main():
|
||
|
|
parser = argparse.ArgumentParser()
|
||
|
|
parser.add_argument('--rustc', required=True, type=pathlib.Path)
|
||
|
|
parser.add_argument('--depfile', type=pathlib.Path)
|
||
|
|
parser.add_argument('--rsp', type=pathlib.Path)
|
||
|
|
parser.add_argument('args', metavar='ARG', nargs='+')
|
||
|
|
|
||
|
|
args = parser.parse_args()
|
||
|
|
|
||
|
|
remaining_args = args.args
|
||
|
|
|
||
|
|
ldflags_separator = remaining_args.index("LDFLAGS")
|
||
|
|
rustenv_separator = remaining_args.index("RUSTENV", ldflags_separator)
|
||
|
|
rustc_args = remaining_args[:ldflags_separator]
|
||
|
|
ldflags = remaining_args[ldflags_separator + 1:rustenv_separator]
|
||
|
|
rustenv = remaining_args[rustenv_separator + 1:]
|
||
|
|
|
||
|
|
is_windows = os.name == 'nt'
|
||
|
|
|
||
|
|
rustc_args.extend(["-Clink-arg=%s" % arg for arg in ldflags])
|
||
|
|
|
||
|
|
# Workaround for https://bugs.chromium.org/p/gn/issues/detail?id=249
|
||
|
|
if args.rsp:
|
||
|
|
with open(args.rsp) as rspfile:
|
||
|
|
rsp_args = [l.rstrip() for l in rspfile.read().split(' ') if l.rstrip()]
|
||
|
|
if is_windows:
|
||
|
|
# Work around for hard-coded string in gn; full fix will come from
|
||
|
|
# https://gn-review.googlesource.com/c/gn/+/12460
|
||
|
|
rsp_args = [arg for arg in rsp_args if not arg.endswith("-Bdynamic")]
|
||
|
|
# Work around for "-l<foo>.lib", where ".lib" suffix is undesirable.
|
||
|
|
# Full fix will come from https://gn-review.googlesource.com/c/gn/+/12480
|
||
|
|
rsp_args = [remove_lib_suffix_from_l_args(arg) for arg in rsp_args]
|
||
|
|
with open(args.rsp, 'w') as rspfile:
|
||
|
|
rspfile.write("\n".join(rsp_args))
|
||
|
|
rustc_args.append(f'@{args.rsp}')
|
||
|
|
|
||
|
|
env = os.environ.copy()
|
||
|
|
fixed_env_vars = []
|
||
|
|
for item in rustenv:
|
||
|
|
(k, v) = item.split("=", 1)
|
||
|
|
env[k] = v
|
||
|
|
fixed_env_vars.append(k)
|
||
|
|
|
||
|
|
r = subprocess.run([args.rustc, *rustc_args], env=env, check=False)
|
||
|
|
if r.returncode != 0:
|
||
|
|
sys.exit(r.returncode)
|
||
|
|
|
||
|
|
# Now edit the depfile produced
|
||
|
|
if args.depfile is not None:
|
||
|
|
env_dep_re = re.compile("# env-dep:(.*)=.*")
|
||
|
|
replacement_lines = []
|
||
|
|
dirty = False
|
||
|
|
with open(args.depfile, encoding="utf-8") as d:
|
||
|
|
for line in d:
|
||
|
|
m = env_dep_re.match(line)
|
||
|
|
if m and m.group(1) in fixed_env_vars:
|
||
|
|
dirty = True # skip this line
|
||
|
|
else:
|
||
|
|
replacement_lines.append(line)
|
||
|
|
if dirty: # we made a change, let's write out the file
|
||
|
|
with action_helpers.atomic_output(args.depfile) as output:
|
||
|
|
output.write("\n".join(replacement_lines).encode("utf-8"))
|
||
|
|
|
||
|
|
|
||
|
|
if __name__ == '__main__':
|
||
|
|
sys.exit(main())
|