1*8975f5c5SAndroid Build Coastguard Worker#!/usr/bin/env python3 2*8975f5c5SAndroid Build Coastguard Worker# Copyright 2020 The Chromium Authors 3*8975f5c5SAndroid Build Coastguard Worker# Use of this source code is governed by a BSD-style license that can be 4*8975f5c5SAndroid Build Coastguard Worker# found in the LICENSE file. 5*8975f5c5SAndroid Build Coastguard Worker"""Checks that compiling targets in BUILD.gn file fails.""" 6*8975f5c5SAndroid Build Coastguard Worker 7*8975f5c5SAndroid Build Coastguard Workerimport argparse 8*8975f5c5SAndroid Build Coastguard Workerimport json 9*8975f5c5SAndroid Build Coastguard Workerimport os 10*8975f5c5SAndroid Build Coastguard Workerimport subprocess 11*8975f5c5SAndroid Build Coastguard Workerimport re 12*8975f5c5SAndroid Build Coastguard Workerimport sys 13*8975f5c5SAndroid Build Coastguard Workerfrom util import build_utils 14*8975f5c5SAndroid Build Coastguard Worker 15*8975f5c5SAndroid Build Coastguard Worker_CHROMIUM_SRC = os.path.normpath(os.path.join(__file__, '..', '..', '..', '..')) 16*8975f5c5SAndroid Build Coastguard Worker_NINJA_PATH = os.path.join(_CHROMIUM_SRC, 'third_party', 'ninja', 'ninja') 17*8975f5c5SAndroid Build Coastguard Worker 18*8975f5c5SAndroid Build Coastguard Worker# Relative to _CHROMIUM_SRC 19*8975f5c5SAndroid Build Coastguard Worker_GN_SRC_REL_PATH = os.path.join('buildtools', 'linux64', 'gn') 20*8975f5c5SAndroid Build Coastguard Worker 21*8975f5c5SAndroid Build Coastguard Worker# Regex for determining whether compile failed because 'gn gen' needs to be run. 22*8975f5c5SAndroid Build Coastguard Worker_GN_GEN_REGEX = re.compile(r'ninja: (error|fatal):') 23*8975f5c5SAndroid Build Coastguard Worker 24*8975f5c5SAndroid Build Coastguard Worker 25*8975f5c5SAndroid Build Coastguard Workerdef _raise_command_exception(args, returncode, output): 26*8975f5c5SAndroid Build Coastguard Worker """Raises an exception whose message describes a command failure. 27*8975f5c5SAndroid Build Coastguard Worker 28*8975f5c5SAndroid Build Coastguard Worker Args: 29*8975f5c5SAndroid Build Coastguard Worker args: shell command-line (as passed to subprocess.Popen()) 30*8975f5c5SAndroid Build Coastguard Worker returncode: status code. 31*8975f5c5SAndroid Build Coastguard Worker output: command output. 32*8975f5c5SAndroid Build Coastguard Worker Raises: 33*8975f5c5SAndroid Build Coastguard Worker a new Exception. 34*8975f5c5SAndroid Build Coastguard Worker """ 35*8975f5c5SAndroid Build Coastguard Worker message = 'Command failed with status {}: {}\n' \ 36*8975f5c5SAndroid Build Coastguard Worker 'Output:-----------------------------------------\n{}\n' \ 37*8975f5c5SAndroid Build Coastguard Worker '------------------------------------------------\n'.format( 38*8975f5c5SAndroid Build Coastguard Worker returncode, args, output) 39*8975f5c5SAndroid Build Coastguard Worker raise Exception(message) 40*8975f5c5SAndroid Build Coastguard Worker 41*8975f5c5SAndroid Build Coastguard Worker 42*8975f5c5SAndroid Build Coastguard Workerdef _run_command(args, cwd=None): 43*8975f5c5SAndroid Build Coastguard Worker """Runs shell command. Raises exception if command fails.""" 44*8975f5c5SAndroid Build Coastguard Worker p = subprocess.Popen(args, 45*8975f5c5SAndroid Build Coastguard Worker stdout=subprocess.PIPE, 46*8975f5c5SAndroid Build Coastguard Worker stderr=subprocess.STDOUT, 47*8975f5c5SAndroid Build Coastguard Worker cwd=cwd) 48*8975f5c5SAndroid Build Coastguard Worker pout, _ = p.communicate() 49*8975f5c5SAndroid Build Coastguard Worker if p.returncode != 0: 50*8975f5c5SAndroid Build Coastguard Worker _raise_command_exception(args, p.returncode, pout) 51*8975f5c5SAndroid Build Coastguard Worker 52*8975f5c5SAndroid Build Coastguard Worker 53*8975f5c5SAndroid Build Coastguard Workerdef _run_command_get_failure_output(args): 54*8975f5c5SAndroid Build Coastguard Worker """Runs shell command. 55*8975f5c5SAndroid Build Coastguard Worker 56*8975f5c5SAndroid Build Coastguard Worker Returns: 57*8975f5c5SAndroid Build Coastguard Worker Command output if command fails, None if command succeeds. 58*8975f5c5SAndroid Build Coastguard Worker """ 59*8975f5c5SAndroid Build Coastguard Worker p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) 60*8975f5c5SAndroid Build Coastguard Worker pout, _ = p.communicate() 61*8975f5c5SAndroid Build Coastguard Worker 62*8975f5c5SAndroid Build Coastguard Worker if p.returncode == 0: 63*8975f5c5SAndroid Build Coastguard Worker return None 64*8975f5c5SAndroid Build Coastguard Worker 65*8975f5c5SAndroid Build Coastguard Worker # For Python3 only: 66*8975f5c5SAndroid Build Coastguard Worker if isinstance(pout, bytes) and sys.version_info >= (3, ): 67*8975f5c5SAndroid Build Coastguard Worker pout = pout.decode('utf-8') 68*8975f5c5SAndroid Build Coastguard Worker return '' if pout is None else pout 69*8975f5c5SAndroid Build Coastguard Worker 70*8975f5c5SAndroid Build Coastguard Worker 71*8975f5c5SAndroid Build Coastguard Workerdef _copy_and_append_gn_args(src_args_path, dest_args_path, extra_args): 72*8975f5c5SAndroid Build Coastguard Worker """Copies args.gn. 73*8975f5c5SAndroid Build Coastguard Worker 74*8975f5c5SAndroid Build Coastguard Worker Args: 75*8975f5c5SAndroid Build Coastguard Worker src_args_path: args.gn file to copy. 76*8975f5c5SAndroid Build Coastguard Worker dest_args_path: Copy file destination. 77*8975f5c5SAndroid Build Coastguard Worker extra_args: Text to append to args.gn after copy. 78*8975f5c5SAndroid Build Coastguard Worker """ 79*8975f5c5SAndroid Build Coastguard Worker with open(src_args_path) as f_in, open(dest_args_path, 'w') as f_out: 80*8975f5c5SAndroid Build Coastguard Worker f_out.write(f_in.read()) 81*8975f5c5SAndroid Build Coastguard Worker f_out.write('\n') 82*8975f5c5SAndroid Build Coastguard Worker f_out.write('\n'.join(extra_args)) 83*8975f5c5SAndroid Build Coastguard Worker 84*8975f5c5SAndroid Build Coastguard Worker 85*8975f5c5SAndroid Build Coastguard Workerdef _find_regex_in_test_failure_output(test_output, regex): 86*8975f5c5SAndroid Build Coastguard Worker """Searches for regex in test output. 87*8975f5c5SAndroid Build Coastguard Worker 88*8975f5c5SAndroid Build Coastguard Worker Args: 89*8975f5c5SAndroid Build Coastguard Worker test_output: test output. 90*8975f5c5SAndroid Build Coastguard Worker regex: regular expression to search for. 91*8975f5c5SAndroid Build Coastguard Worker Returns: 92*8975f5c5SAndroid Build Coastguard Worker Whether the regular expression was found in the part of the test output 93*8975f5c5SAndroid Build Coastguard Worker after the 'FAILED' message. 94*8975f5c5SAndroid Build Coastguard Worker """ 95*8975f5c5SAndroid Build Coastguard Worker if test_output is None: 96*8975f5c5SAndroid Build Coastguard Worker return False 97*8975f5c5SAndroid Build Coastguard Worker 98*8975f5c5SAndroid Build Coastguard Worker failed_index = test_output.find('FAILED') 99*8975f5c5SAndroid Build Coastguard Worker if failed_index < 0: 100*8975f5c5SAndroid Build Coastguard Worker return False 101*8975f5c5SAndroid Build Coastguard Worker 102*8975f5c5SAndroid Build Coastguard Worker failure_message = test_output[failed_index:] 103*8975f5c5SAndroid Build Coastguard Worker if regex.find('\n') >= 0: 104*8975f5c5SAndroid Build Coastguard Worker return re.search(regex, failure_message) 105*8975f5c5SAndroid Build Coastguard Worker return _search_regex_in_list(failure_message.split('\n'), regex) 106*8975f5c5SAndroid Build Coastguard Worker 107*8975f5c5SAndroid Build Coastguard Worker 108*8975f5c5SAndroid Build Coastguard Workerdef _search_regex_in_list(value, regex): 109*8975f5c5SAndroid Build Coastguard Worker for line in value: 110*8975f5c5SAndroid Build Coastguard Worker if re.search(regex, line): 111*8975f5c5SAndroid Build Coastguard Worker return True 112*8975f5c5SAndroid Build Coastguard Worker return False 113*8975f5c5SAndroid Build Coastguard Worker 114*8975f5c5SAndroid Build Coastguard Worker 115*8975f5c5SAndroid Build Coastguard Workerdef _do_build_get_failure_output(gn_path, gn_cmd, options): 116*8975f5c5SAndroid Build Coastguard Worker # Extract directory from test target. As all of the test targets are declared 117*8975f5c5SAndroid Build Coastguard Worker # in the same BUILD.gn file, it does not matter which test target is used. 118*8975f5c5SAndroid Build Coastguard Worker target_dir = gn_path.rsplit(':', 1)[0] 119*8975f5c5SAndroid Build Coastguard Worker 120*8975f5c5SAndroid Build Coastguard Worker if gn_cmd is not None: 121*8975f5c5SAndroid Build Coastguard Worker gn_args = [ 122*8975f5c5SAndroid Build Coastguard Worker _GN_SRC_REL_PATH, '--root-target=' + target_dir, gn_cmd, 123*8975f5c5SAndroid Build Coastguard Worker os.path.relpath(options.out_dir, _CHROMIUM_SRC) 124*8975f5c5SAndroid Build Coastguard Worker ] 125*8975f5c5SAndroid Build Coastguard Worker _run_command(gn_args, cwd=_CHROMIUM_SRC) 126*8975f5c5SAndroid Build Coastguard Worker 127*8975f5c5SAndroid Build Coastguard Worker ninja_args = [_NINJA_PATH, '-C', options.out_dir, gn_path] 128*8975f5c5SAndroid Build Coastguard Worker return _run_command_get_failure_output(ninja_args) 129*8975f5c5SAndroid Build Coastguard Worker 130*8975f5c5SAndroid Build Coastguard Worker 131*8975f5c5SAndroid Build Coastguard Workerdef main(): 132*8975f5c5SAndroid Build Coastguard Worker parser = argparse.ArgumentParser() 133*8975f5c5SAndroid Build Coastguard Worker parser.add_argument('--gn-args-path', 134*8975f5c5SAndroid Build Coastguard Worker required=True, 135*8975f5c5SAndroid Build Coastguard Worker help='Path to args.gn file.') 136*8975f5c5SAndroid Build Coastguard Worker parser.add_argument('--test-configs-path', 137*8975f5c5SAndroid Build Coastguard Worker required=True, 138*8975f5c5SAndroid Build Coastguard Worker help='Path to file with test configurations') 139*8975f5c5SAndroid Build Coastguard Worker parser.add_argument('--out-dir', 140*8975f5c5SAndroid Build Coastguard Worker required=True, 141*8975f5c5SAndroid Build Coastguard Worker help='Path to output directory to use for compilation.') 142*8975f5c5SAndroid Build Coastguard Worker parser.add_argument('--stamp', help='Path to touch.') 143*8975f5c5SAndroid Build Coastguard Worker options = parser.parse_args() 144*8975f5c5SAndroid Build Coastguard Worker 145*8975f5c5SAndroid Build Coastguard Worker with open(options.test_configs_path) as f: 146*8975f5c5SAndroid Build Coastguard Worker # Escape '\' in '\.' now. This avoids having to do the escaping in the test 147*8975f5c5SAndroid Build Coastguard Worker # specification. 148*8975f5c5SAndroid Build Coastguard Worker config_text = f.read().replace(r'\.', r'\\.') 149*8975f5c5SAndroid Build Coastguard Worker test_configs = json.loads(config_text) 150*8975f5c5SAndroid Build Coastguard Worker 151*8975f5c5SAndroid Build Coastguard Worker if not os.path.exists(options.out_dir): 152*8975f5c5SAndroid Build Coastguard Worker os.makedirs(options.out_dir) 153*8975f5c5SAndroid Build Coastguard Worker 154*8975f5c5SAndroid Build Coastguard Worker out_gn_args_path = os.path.join(options.out_dir, 'args.gn') 155*8975f5c5SAndroid Build Coastguard Worker extra_gn_args = [ 156*8975f5c5SAndroid Build Coastguard Worker 'enable_android_nocompile_tests = true', 157*8975f5c5SAndroid Build Coastguard Worker 'treat_warnings_as_errors = true', 158*8975f5c5SAndroid Build Coastguard Worker # RBE does not work with non-standard output directories. 159*8975f5c5SAndroid Build Coastguard Worker 'use_remoteexec = false', 160*8975f5c5SAndroid Build Coastguard Worker ] 161*8975f5c5SAndroid Build Coastguard Worker _copy_and_append_gn_args(options.gn_args_path, out_gn_args_path, 162*8975f5c5SAndroid Build Coastguard Worker extra_gn_args) 163*8975f5c5SAndroid Build Coastguard Worker 164*8975f5c5SAndroid Build Coastguard Worker ran_gn_gen = False 165*8975f5c5SAndroid Build Coastguard Worker did_clean_build = False 166*8975f5c5SAndroid Build Coastguard Worker error_messages = [] 167*8975f5c5SAndroid Build Coastguard Worker for config in test_configs: 168*8975f5c5SAndroid Build Coastguard Worker # Strip leading '//' 169*8975f5c5SAndroid Build Coastguard Worker gn_path = config['target'][2:] 170*8975f5c5SAndroid Build Coastguard Worker expect_regex = config['expect_regex'] 171*8975f5c5SAndroid Build Coastguard Worker 172*8975f5c5SAndroid Build Coastguard Worker test_output = _do_build_get_failure_output(gn_path, None, options) 173*8975f5c5SAndroid Build Coastguard Worker 174*8975f5c5SAndroid Build Coastguard Worker # 'gn gen' takes > 1s to run. Only run 'gn gen' if it is needed for compile. 175*8975f5c5SAndroid Build Coastguard Worker if (test_output 176*8975f5c5SAndroid Build Coastguard Worker and _search_regex_in_list(test_output.split('\n'), _GN_GEN_REGEX)): 177*8975f5c5SAndroid Build Coastguard Worker assert not ran_gn_gen 178*8975f5c5SAndroid Build Coastguard Worker ran_gn_gen = True 179*8975f5c5SAndroid Build Coastguard Worker test_output = _do_build_get_failure_output(gn_path, 'gen', options) 180*8975f5c5SAndroid Build Coastguard Worker 181*8975f5c5SAndroid Build Coastguard Worker if (not _find_regex_in_test_failure_output(test_output, expect_regex) 182*8975f5c5SAndroid Build Coastguard Worker and not did_clean_build): 183*8975f5c5SAndroid Build Coastguard Worker # Ensure the failure is not due to incremental build. 184*8975f5c5SAndroid Build Coastguard Worker did_clean_build = True 185*8975f5c5SAndroid Build Coastguard Worker test_output = _do_build_get_failure_output(gn_path, 'clean', options) 186*8975f5c5SAndroid Build Coastguard Worker 187*8975f5c5SAndroid Build Coastguard Worker if not _find_regex_in_test_failure_output(test_output, expect_regex): 188*8975f5c5SAndroid Build Coastguard Worker if test_output is None: 189*8975f5c5SAndroid Build Coastguard Worker # Purpose of quotes at beginning of message is to make it clear that 190*8975f5c5SAndroid Build Coastguard Worker # "Compile successful." is not a compiler log message. 191*8975f5c5SAndroid Build Coastguard Worker test_output = '""\nCompile successful.' 192*8975f5c5SAndroid Build Coastguard Worker error_message = '//{} failed.\nExpected compile output pattern:\n'\ 193*8975f5c5SAndroid Build Coastguard Worker '{}\nActual compile output:\n{}'.format( 194*8975f5c5SAndroid Build Coastguard Worker gn_path, expect_regex, test_output) 195*8975f5c5SAndroid Build Coastguard Worker error_messages.append(error_message) 196*8975f5c5SAndroid Build Coastguard Worker 197*8975f5c5SAndroid Build Coastguard Worker if error_messages: 198*8975f5c5SAndroid Build Coastguard Worker raise Exception('\n'.join(error_messages)) 199*8975f5c5SAndroid Build Coastguard Worker 200*8975f5c5SAndroid Build Coastguard Worker if options.stamp: 201*8975f5c5SAndroid Build Coastguard Worker build_utils.Touch(options.stamp) 202*8975f5c5SAndroid Build Coastguard Worker 203*8975f5c5SAndroid Build Coastguard Worker 204*8975f5c5SAndroid Build Coastguard Workerif __name__ == '__main__': 205*8975f5c5SAndroid Build Coastguard Worker main() 206