blob: 2e02cae27428761d4de6fae3a919f7ae10ce7d2a [file] [log] [blame]
#!/usr/bin/env python2.7
# 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.
import argparse
import fileinput
import os
import re
import subprocess
import sys
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
FUCHSIA_ROOT = os.path.dirname( # $root
os.path.dirname( # scripts
SCRIPT_DIR)) # unification
if sys.platform.startswith('linux'):
PLATFORM = 'linux-x64'
elif sys.platform.startswith('darwin'):
PLATFORM = 'mac-x64'
GN = os.path.join(FUCHSIA_ROOT, 'prebuilt', 'third_party', 'gn', PLATFORM, 'gn')
FX = os.path.join(os.path.dirname(SCRIPT_DIR), 'fx')
GIT_BRANCH_FORBIDDEN_CHARS = '.~^:/\\'
def run_command(command):
return subprocess.check_output(
command, stderr=subprocess.STDOUT, cwd=FUCHSIA_ROOT)
def main():
parser = argparse.ArgumentParser(
description='Adds a given config to all compile targets with '
'a given compiler error')
parser.add_argument('--error', help='Compiler error marker')
parser.add_argument('--config', help='Config to add')
parser.add_argument(
'--zircon', help='//zircon build (ZN)', action='store_true')
args = parser.parse_args()
error = args.error
config = args.config
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: .*' + error)
error_files = set()
for line in build_out.split('\n'):
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
# Collect all BUILD.gn files with failing targets
print 'Resolving failing targets...'
# TODO support for zircon build
# --root=zircon refs out/default.zircon <target>
# --all-toolchains
# //:instrumented-ulib-redirect.asan(//public/gn/toolchain:user-arm64-clang)
# //:instrumented-ulib-redirect.asan(//public/gn/toolchain:user-x64-clang)
outdir = 'out/default.zircon' if zircon else 'out/default'
refs_command = [GN, 'refs']
if zircon:
refs_command += [
'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:
refs_command += ['out/default']
refs_command += sorted(error_files)
refs_out = run_command(refs_command)
error_targets = set(refs_out.strip().split('\n'))
print 'Failing BUILD.gn files:'
print '\n'.join(sorted(error_targets))
print
# Fix failing targets
ref_regex = re.compile('//([^:]*):([^.(]*).*')
for target in error_targets:
match = ref_regex.match(target)
build_dir, target_name = match.groups()
target_regex = re.compile('\w*\("' + target_name + '"\) {')
build_file = os.path.join(build_dir, 'BUILD.gn')
# Format file before processing
run_command([FX, 'format-code', '--files=' + build_file])
print 'Fixing', build_file
# Below is a duct-tape approach to editing GN files that is not
# robust to all possible inputs.
# Consider writing ANTLR grammar for GN and then generating
# Python lexer and parser code to properly process these files.
in_target = False
config_printed = False
in_public_configs = False
curly_brace_depth = 0
if zircon:
build_file = os.path.join('zircon', build_file)
build_file = os.path.join(FUCHSIA_ROOT, build_file)
for line in fileinput.FileInput(build_file, inplace=True):
# TODO: make this robust to escaping
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.findall(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
print 'public_configs = [ "' + config + '" ]'
config_printed = True
in_target = False
elif 'public_configs = [ "' in line and config in line:
config_printed = True
elif 'public_configs = [ "' in line:
line = line[:-3] + ', "' + config + '" ]\n'
config_printed = True
elif 'public_configs = [' in line:
in_public_configs = True
elif in_public_configs and line.strip() == '"' + config + '",':
sys.stderr.write('Case 1 ' + line)
in_public_configs = False
config_printed = True
elif in_public_configs and line.strip() == ']':
sys.stderr.write('Case 2 ' + line)
print '"' + config + '",'
in_public_configs = False
config_printed = True
print line,
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])
run_command(['git', 'add', build_file])
# Create a commit.
run_command(
[
'git', 'checkout', '-b',
'config-' + config.translate(None, GIT_BRANCH_FORBIDDEN_CHARS),
'JIRI_HEAD'
])
message = [
'[config] configs += "' + config + '"', '',
'Generated with: //scripts/unification/fix_compile_with_config.py ' +
'--error="' + error + '" --config="' + config + '"', ''
]
commit_command = ['git', 'commit', '-a']
for line in message:
commit_command += ['-m', line]
run_command(commit_command)
print 'Change is ready, use "jiri upload" to start the review process.'
return 0
if __name__ == '__main__':
sys.exit(main())