import argparse
import itertools
import json
import os
import pathlib
import subprocess
import sys
import tempfile
REPOSITORY_ROOT = pathlib.Path(__file__).resolve().parent.parent.parent
sys.path.append(
os.path.join(os.path.dirname(os.path.abspath(__file__)), 'gni_impl'))
from rustc_wrapper import (ConvertPathsToAbsolute, LoadRustEnvAndFlags)
class Highlight:
def __init__(self, text):
self.text = text["text"]
self.start = text["highlight_start"]
self.end = text["highlight_end"]
class Fix:
def __init__(self, span):
self.file_name = span["file_name"]
self.byte_start = span["byte_start"]
self.byte_end = span["byte_end"]
self.line_start = span["line_start"]
self.line_end = span["line_end"]
self.suggestion_applicability = span["suggestion_applicability"]
self.suggested_replacement = span["suggested_replacement"]
self.highlights = [Highlight(h) for h in span["text"]]
def _GetMachineApplicableFixes(errors):
result = []
for e in errors:
result += _GetMachineApplicableFixes(e["children"])
for fix in e["spans"]:
fix = Fix(fix)
if fix.suggestion_applicability == "MachineApplicable":
result.append(fix)
return result
def _IsOldTextStillPresent(content, fix):
assert fix.byte_start <= len(content)
end = min(len(content), fix.byte_end)
for h in fix.highlights:
start = fix.byte_start - h.start + 1
current_text = content[start:end]
old_text = h.text[:h.end - 1]
if old_text not in current_text:
print(f" Fix on line {fix.line_start} doesn't apply!?")
return False
return True
def _ApplyFixes(content, fixes):
fixes = list(fixes)
assert len(set([fix.file_name for fix in fixes])) == 1
fixes = sorted(fixes,
reverse=True,
key=lambda fix: (fix.byte_start, fix.byte_end))
for fix in fixes:
if _IsOldTextStillPresent(content, fix):
print(f" Applying a fix on line {fix.line_start}...")
orig_prefix = content[:fix.byte_start]
orig_suffix = content[fix.byte_end:]
content = orig_prefix + fix.suggested_replacement + orig_suffix
return content
def main():
parser = argparse.ArgumentParser()
parser.add_argument('chromium_build_dir', type=pathlib.Path)
parser.add_argument('tool', type=pathlib.Path)
parser.add_argument('rustc_env_and_flags', type=pathlib.Path)
args = parser.parse_args()
if not args.chromium_build_dir.is_dir():
args.chromium_build_dir = REPOSITORY_ROOT / args.chromium_build_dir
args.chromium_build_dir = args.chromium_build_dir.resolve(strict=True)
os.chdir(args.chromium_build_dir)
if not args.tool.is_file():
args.tool = \
REPOSITORY_ROOT / 'third_party' / 'rust-toolchain' / 'bin' / args.tool
args.tool = args.tool.resolve(strict=True)
(rustenv, rustflags) = LoadRustEnvAndFlags(args.rustc_env_and_flags)
ConvertPathsToAbsolute(rustenv)
assert not [x for x in rustflags if x.startswith("--emit")]
assert not [x for x in rustflags if x.startswith("-o")]
assert not [x for x in rustflags if x.startswith("--out-dir")]
temp_dir = tempfile.TemporaryDirectory()
rustflags += ["--out-dir", temp_dir.name]
rustflags += ["--emit=metadata"]
rustflags += ["--error-format=json"]
print(f"Invoking {os.path.basename(args.tool)} to repro the build problem...")
r = subprocess.run([args.tool, *rustflags],
env=rustenv,
check=False,
capture_output=True)
if r.returncode == 0:
print("Unexpectedly didn't repro any build problem.")
return r.returncode
errors = [json.loads(line) for line in r.stderr.splitlines()]
fixes = _GetMachineApplicableFixes(errors)
if not fixes:
print("No machine-applicable fixes found...")
return 0
fixes = sorted(fixes, key=lambda f: f.file_name)
fixes = itertools.groupby(fixes, key=lambda f: f.file_name)
for file_name, fixes in fixes:
print(f"Applying fixes to `{file_name}`...")
with open(file_name) as f:
content = f.read()
content = _ApplyFixes(content, fixes)
with open(file_name, 'w') as f:
f.write(content)
return 0
if __name__ == '__main__':
sys.exit(main())