import argparse
import json
import pathlib
import subprocess
import shlex
import os
import sys
import re
sys.path.append(
os.path.join(os.path.dirname(os.path.abspath(__file__)), os.pardir,
os.pardir, os.pardir, 'build'))
import action_helpers
FILE_RE = re.compile("^([^# ][^ ]*):( .+)?$")
def remove_lib_suffix_from_l_args(text):
if text.startswith("-l") and text.endswith(".lib"):
return text[:-len(".lib")]
return text
def remove_gn_escaping_from_rsp_args(arg):
arg = arg.replace(r'\"', '"')
if arg.startswith('"') and arg.endswith('"'):
arg = arg[1:len(arg) - 1]
return arg
def normalize_path(path, abs_build_root):
def remove_prefix(text, prefix):
if text.startswith(prefix):
return text[len(prefix):]
return text
return os.path.relpath(os.path.normpath(remove_prefix(
path, abs_build_root))).replace('\\', '/')
def normalize_depline(depline, abs_build_root):
m = FILE_RE.match(depline)
if not m:
return depline
lhs_file = m.group(1)
rhs_group = m.group(2)
if rhs_group:
rhs_files = rhs_group.split()
else:
rhs_files = []
lhs_file = normalize_path(lhs_file, abs_build_root)
rhs_files = [normalize_path(f, abs_build_root) for f in rhs_files]
if rhs_files:
rhs_files = " ".join(rhs_files)
return f"{lhs_file}: {rhs_files}"
else:
return f"{lhs_file}:"
def verify_inputs(depline, sources, abs_build_root):
found_files = {}
m = FILE_RE.match(depline)
if m and m.group(2):
files = m.group(2)
found_files = {normalize_path(f, abs_build_root): f for f in files.split()}
missing_files = found_files.keys() - sources
if not missing_files:
return True
for file_files_key in missing_files:
gn_type = "sources" if file_files_key.endswith(".rs") else "inputs"
print(f'ERROR: Rust source file or input not in GN {gn_type}: ' +
f'{found_files[file_files_key]}',
file=sys.stderr)
print('NOTE: See `//docs/rust/build_errors_guide.md` for more information.',
file=sys.stderr)
return False
def ConvertPathsToAbsolute(env):
for (k, v) in env.items():
if v and os.path.exists(v):
env[k] = os.path.abspath(v)
def _SaveRustEnvAndFlags(path, rustenv, rustflags):
os.makedirs(os.path.dirname(path), exist_ok=True)
data = {'rustenv': rustenv, 'rustflags': rustflags}
with action_helpers.atomic_output(path,
'w',
encoding='utf-8',
only_if_changed=False) as json_file:
json.dump(data, json_file, indent=4)
def LoadRustEnvAndFlags(path):
with open(path, "r", encoding='utf-8') as json_file:
data = json.load(json_file)
rustenv = data["rustenv"]
rustflags = data["rustflags"]
return rustenv, rustflags
def RecommendApplyFixesScript(tool, rustc_env_and_flags):
source_root = os.path.join(os.path.dirname(os.path.abspath(__file__)),
os.pardir, os.pardir, os.pardir)
rel_build_dir = os.path.relpath(os.getcwd(), source_root)
print(f"NOTE: To apply machine-applicable fix suggestions (if any), run:",
file=sys.stderr,
end='')
print(f" build/rust/apply_fixes.py", file=sys.stderr, end='')
print(f" {rel_build_dir}", file=sys.stderr, end='')
print(f" {os.path.basename(tool)} {rustc_env_and_flags}", file=sys.stderr)
def _ExpandNestedRustStyleRspFiles(rsp_args):
new_rsp_args = []
for arg in rsp_args:
if arg.startswith('@'):
with open(arg[1:], 'r') as nested_rsp_file:
new_rsp_args += [line.rstrip() for line in nested_rsp_file]
else:
new_rsp_args.append(arg)
return new_rsp_args
def main():
parser = argparse.ArgumentParser()
parser.add_argument('--rustc', required=True, type=pathlib.Path)
parser.add_argument('--depfile', required=True, type=pathlib.Path)
parser.add_argument('--rsp', type=pathlib.Path, required=True)
parser.add_argument('--target-windows', action='store_true')
parser.add_argument('--dump-rustc-env-and-flags',
type=pathlib.Path,
required=True)
parser.add_argument('-v', action='store_true')
parser.add_argument('-o', type=pathlib.Path, required=True)
parser.add_argument('--emit', type=str, required=True)
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)
try:
sources_separator = remaining_args.index("SOURCES", rustenv_separator)
except:
sources_separator = None
rustc_args = remaining_args[:ldflags_separator]
ldflags = remaining_args[ldflags_separator + 1:rustenv_separator]
rustenv = remaining_args[rustenv_separator + 1:sources_separator]
abs_build_root = os.getcwd().replace('\\', '/') + '/'
is_windows = sys.platform == 'win32' or args.target_windows
rustc_args.extend(["-Clink-arg=%s" % arg for arg in ldflags])
with open(args.rsp) as rspfile:
rsp_args = [l.rstrip() for l in rspfile.read().split(' ') if l.rstrip()]
sources_separator = rsp_args.index("SOURCES")
sources = set(rsp_args[sources_separator + 1:])
rsp_args = rsp_args[:sources_separator]
if is_windows:
rsp_args = [remove_lib_suffix_from_l_args(arg) for arg in rsp_args]
rustc_args = [remove_lib_suffix_from_l_args(arg) for arg in rustc_args]
rsp_args = [remove_gn_escaping_from_rsp_args(arg) for arg in rsp_args]
rsp_args = _ExpandNestedRustStyleRspFiles(rsp_args)
out_rsp = str(args.rsp) + ".rust"
with open(out_rsp, 'w') as rspfile:
rspfile.write("\n".join(rsp_args))
rustc_args.append(f'@{out_rsp}')
rustenv = dict([item.split("=", 1) for item in rustenv]) _SaveRustEnvAndFlags(args.dump_rustc_env_and_flags, rustenv, rustc_args)
ConvertPathsToAbsolute(rustenv)
env = os.environ.copy() | rustenv
rustc_args += ["--emit", args.emit, "-o", args.o]
if args.v:
print(' '.join(f'{k}={shlex.quote(v)}' for k, v in env.items()), args.rustc,
shlex.join(rustc_args))
r = subprocess.run([args.rustc, *rustc_args], env=env, check=False)
if r.returncode != 0:
RecommendApplyFixesScript(args.rustc, args.dump_rustc_env_and_flags)
sys.exit(r.returncode)
final_depfile_lines = []
dirty = False
with open(args.depfile, encoding="utf-8") as d:
env_dep_re = re.compile("# env-dep:(.*)=.*")
for line in d:
m = env_dep_re.match(line)
if m and m.group(1) in rustenv.keys():
dirty = True else:
new_line = normalize_depline(line, abs_build_root)
dirty = dirty or (new_line != line)
final_depfile_lines.append(new_line)
for line in final_depfile_lines:
if not verify_inputs(line, sources, abs_build_root):
return 1
if dirty: with action_helpers.atomic_output(args.depfile,
only_if_changed=False) as output:
output.write("\n".join(final_depfile_lines).encode("utf-8"))
if __name__ == '__main__':
sys.exit(main())