| #!/usr/bin/env python3 | |
| # Copyright 2019 The Fuchsia Authors. All rights reserved. | |
| # Use of this source code is governed by a BSD-style license that can be | |
| # found in the LICENSE file. | |
| """ | |
| Example usage: | |
| $ fx set ... | |
| $ scripts/gn/suppress_errors.py\ | |
| --error=-Wconversion | |
| --config="//build/config:Wno-conversion" | |
| """ | |
| import argparse | |
| import fileinput | |
| import multiprocessing | |
| import os | |
| import re | |
| import subprocess | |
| import sys | |
| def run_command(command): | |
| return subprocess.check_output( | |
| command, stderr=subprocess.STDOUT, encoding="utf8") | |
| def get_common_gn_args(is_zircon): | |
| """Retrieve common GN args shared by several commands in this script. | |
| Args: | |
| is_zircon: True iff dealing with the Zircon build output directory. | |
| Returns: | |
| A list of GN command-line arguments (to be used after "gn <command>"). | |
| """ | |
| if is_zircon: | |
| return [ | |
| "out/default.zircon", | |
| "--root=zircon", | |
| "--all-toolchains", | |
| "//:instrumented-ulib-redirect.asan(//public/gn/toolchain:user-arm64-clang)", | |
| "//:instrumented-ulib-redirect.asan(//public/gn/toolchain:user-x64-clang)", | |
| ] | |
| else: | |
| return ["out/default"] | |
| def can_have_config(params): | |
| """Returns whether the given target can have a config. | |
| If not sure, returns True. | |
| This function is module-level for reasons to do with how Python | |
| multiprocessing works. | |
| Args: | |
| params: tuple of target to examine, config to add, is target in Zircon. | |
| Packed in a single tuple for reasons to do with how Python | |
| multiprocessing works. | |
| """ | |
| target, config, is_zircon = params | |
| desc_command = ["fx", "gn", "desc"] | |
| desc_command += get_common_gn_args(is_zircon) | |
| try: | |
| desc_out = run_command(desc_command + [target, "configs"]) | |
| # Target has configs and they include the given config. | |
| # Can't add a duplicate config! | |
| return config not in desc_out | |
| except subprocess.CalledProcessError as e: | |
| if 'Don\'t know how to display "configs" for ' in e.output: | |
| # Target type cannot have configs, | |
| # or the actual target is in another toolchain. | |
| # Assume the latter. | |
| return True | |
| elif " matches no targets, configs or files" in e.output: | |
| # The target probably exists in a non-default toolchain. | |
| # Assume that it can have the config. | |
| return True | |
| else: | |
| raise e | |
| def main(): | |
| parser = argparse.ArgumentParser( | |
| description="Adds a given config to all compile targets with " | |
| "a given compiler error") | |
| parser.add_argument("--error", required=True, help="Compiler error marker") | |
| parser.add_argument("--config", required=True, help="Config to add") | |
| parser.add_argument("--comment", help="Comment to add before config") | |
| parser.add_argument( | |
| "--zircon", help="//zircon build (ZN)", action="store_true") | |
| args = parser.parse_args() | |
| error = args.error | |
| config = args.config | |
| comment = args.comment | |
| zircon = args.zircon | |
| # Harvest all compilation errors | |
| print("Building...") | |
| try: | |
| # On error continue building to discover all failures | |
| run_command(["fx", "build", "-k0"]) | |
| print("Build successful!") | |
| return 0 | |
| except subprocess.CalledProcessError as e: | |
| build_out = e.output | |
| error_regex = re.compile( | |
| "(?:../)*([^:]*):\d*:\d*: error: .*" + re.escape(error)) | |
| error_files = set() | |
| for line in build_out.splitlines(): | |
| match = error_regex.match(line) | |
| if match: | |
| path = os.path.normpath(match.group(1)) | |
| if zircon: | |
| path = "//" + os.path.relpath(path, "zircon") | |
| error_files.add(path) | |
| print("Sources with compilation errors:") | |
| print("\n".join(sorted(error_files))) | |
| print() | |
| if not error_files: | |
| return 0 | |
| # Collect all BUILD.gn files with targets referencing failing sources | |
| print("Resolving references...") | |
| outdir = "out/default.zircon" if zircon else "out/default" | |
| refs_command = ["fx", "gn", "refs"] | |
| refs_command += get_common_gn_args(zircon) | |
| refs_command += sorted(error_files) | |
| try: | |
| refs_out = run_command(refs_command) | |
| except subprocess.CalledProcessError as e: | |
| print("Failed to resolve references!") | |
| print(e.output) | |
| return 1 | |
| target_no_toolchain = re.compile("(\/\/[^:]*:[^(]*)(?:\(.*\))?") | |
| error_targets = { | |
| target_no_toolchain.match(ref).group(1) | |
| for ref in refs_out.splitlines() | |
| } | |
| # Remove targets that already have the given config | |
| # or can't have a config in the first place | |
| print("Removing irrelevant targets...") | |
| # `gn desc` is slow so parallelize | |
| with multiprocessing.Pool(multiprocessing.cpu_count()) as p: | |
| target_can_have = zip( | |
| error_targets, | |
| p.map( | |
| can_have_config, | |
| ((target, config, zircon) for target in error_targets)), | |
| ) | |
| error_targets = { | |
| target for target, can_have in target_can_have if can_have | |
| } | |
| print("Failing targets:") | |
| print("\n".join(sorted(error_targets))) | |
| print() | |
| # Fix failing targets | |
| ref_regex = re.compile("//([^:]*):([^.(]*).*") | |
| for target in sorted(error_targets): | |
| match = ref_regex.match(target) | |
| build_dir, target_name = match.groups() | |
| target_regex = re.compile( | |
| '^\s*\w*\("' + re.escape(target_name) + '"\) {') | |
| build_file = os.path.join( | |
| build_dir, "BUILD.gn" if not zircon else "BUILD.zircon.gn") | |
| # Format file before processing | |
| run_command(["fx", "format-code", "--files=" + build_file]) | |
| print("Fixing", target) | |
| in_target = False | |
| config_printed = False | |
| in_configs = False | |
| curly_brace_depth = 0 | |
| if zircon: | |
| build_file = os.path.join("zircon", build_file) | |
| secondary_build_file = os.path.join("build", "secondary", build_file) | |
| if os.path.exists(secondary_build_file): | |
| # Sometimes we put third_party BUILD.gn files in a shadow directory | |
| # to avoid conflicting with original BUILD.gn files. | |
| build_file = secondary_build_file | |
| start_configs_inline = re.compile('configs \+?= \[ "') | |
| start_configs = re.compile("configs \+?= \[") | |
| for line in fileinput.FileInput(build_file, inplace=True): | |
| curly_brace_depth += line.count("{") - line.count("}") | |
| assert curly_brace_depth >= 0 | |
| if config_printed: | |
| # We already printed the config, keep running until we exit the target | |
| if curly_brace_depth == target_end_depth: | |
| in_target = False | |
| elif not in_target: | |
| # Try to enter target | |
| in_target = bool(target_regex.match(line)) | |
| if in_target: | |
| target_end_depth = curly_brace_depth - 1 | |
| elif curly_brace_depth > target_end_depth + 1: | |
| # Ignore inner blocks such as inner definitions and conditionals | |
| pass | |
| elif curly_brace_depth == target_end_depth: | |
| # Last chance to print config before exiting | |
| if comment: | |
| print("#", comment) | |
| print('configs += [ "' + config + '"]') | |
| config_printed = True | |
| in_target = False | |
| elif start_configs_inline.match(line) and config in line: | |
| config_printed = True | |
| elif start_configs_inline.match(line): | |
| line = line[:-3] + ', "' + config + '" ]\n' | |
| config_printed = True | |
| elif start_configs.match(line): | |
| in_configs = True | |
| elif in_configs and line.strip() == '"' + config + '",': | |
| in_configs = False | |
| config_printed = True | |
| elif in_configs and line.strip() == "]": | |
| if comment: | |
| print("#", comment) | |
| print('"' + config + '",') | |
| in_configs = False | |
| config_printed = True | |
| print(line, end="") | |
| if config_printed and not in_target: | |
| # Reset for a possible redefinition of the same target | |
| # (e.g. within another conditional block) | |
| config_printed = False | |
| run_command(["fx", "format-code", "--files=" + build_file]) | |
| print("Fixed all of:") | |
| for error_target in sorted(error_targets): | |
| print(' "' + error_target + '",') | |
| return 0 | |
| if __name__ == "__main__": | |
| sys.exit(main()) |