1*8975f5c5SAndroid Build Coastguard Worker#!/usr/bin/env python3 2*8975f5c5SAndroid Build Coastguard Worker# 3*8975f5c5SAndroid Build Coastguard Worker# Copyright 2013 The Chromium Authors 4*8975f5c5SAndroid Build Coastguard Worker# Use of this source code is governed by a BSD-style license that can be 5*8975f5c5SAndroid Build Coastguard Worker# found in the LICENSE file. 6*8975f5c5SAndroid Build Coastguard Worker"""Instruments classes and jar files. 7*8975f5c5SAndroid Build Coastguard Worker 8*8975f5c5SAndroid Build Coastguard WorkerThis script corresponds to the 'jacoco_instr' action in the Java build process. 9*8975f5c5SAndroid Build Coastguard WorkerDepending on whether jacoco_instrument is set, the 'jacoco_instr' action will 10*8975f5c5SAndroid Build Coastguard Workercall the instrument command which accepts a jar and instruments it using 11*8975f5c5SAndroid Build Coastguard Workerjacococli.jar. 12*8975f5c5SAndroid Build Coastguard Worker 13*8975f5c5SAndroid Build Coastguard Worker""" 14*8975f5c5SAndroid Build Coastguard Worker 15*8975f5c5SAndroid Build Coastguard Workerimport argparse 16*8975f5c5SAndroid Build Coastguard Workerimport json 17*8975f5c5SAndroid Build Coastguard Workerimport os 18*8975f5c5SAndroid Build Coastguard Workerimport shutil 19*8975f5c5SAndroid Build Coastguard Workerimport sys 20*8975f5c5SAndroid Build Coastguard Workerimport zipfile 21*8975f5c5SAndroid Build Coastguard Worker 22*8975f5c5SAndroid Build Coastguard Workerfrom util import build_utils 23*8975f5c5SAndroid Build Coastguard Workerimport action_helpers 24*8975f5c5SAndroid Build Coastguard Workerimport zip_helpers 25*8975f5c5SAndroid Build Coastguard Worker 26*8975f5c5SAndroid Build Coastguard Worker 27*8975f5c5SAndroid Build Coastguard Worker# This should be same as recipe side token. See bit.ly/3STSPcE. 28*8975f5c5SAndroid Build Coastguard WorkerINSTRUMENT_ALL_JACOCO_OVERRIDE_TOKEN = 'INSTRUMENT_ALL_JACOCO' 29*8975f5c5SAndroid Build Coastguard Worker 30*8975f5c5SAndroid Build Coastguard Worker 31*8975f5c5SAndroid Build Coastguard Workerdef _AddArguments(parser): 32*8975f5c5SAndroid Build Coastguard Worker """Adds arguments related to instrumentation to parser. 33*8975f5c5SAndroid Build Coastguard Worker 34*8975f5c5SAndroid Build Coastguard Worker Args: 35*8975f5c5SAndroid Build Coastguard Worker parser: ArgumentParser object. 36*8975f5c5SAndroid Build Coastguard Worker """ 37*8975f5c5SAndroid Build Coastguard Worker parser.add_argument( 38*8975f5c5SAndroid Build Coastguard Worker '--root-build-dir', 39*8975f5c5SAndroid Build Coastguard Worker required=True, 40*8975f5c5SAndroid Build Coastguard Worker help='Path to build directory rooted at checkout dir e.g. //out/Release') 41*8975f5c5SAndroid Build Coastguard Worker parser.add_argument('--input-path', 42*8975f5c5SAndroid Build Coastguard Worker required=True, 43*8975f5c5SAndroid Build Coastguard Worker help='Path to input file(s). Either the classes ' 44*8975f5c5SAndroid Build Coastguard Worker 'directory, or the path to a jar.') 45*8975f5c5SAndroid Build Coastguard Worker parser.add_argument('--output-path', 46*8975f5c5SAndroid Build Coastguard Worker required=True, 47*8975f5c5SAndroid Build Coastguard Worker help='Path to output final file(s) to. Either the ' 48*8975f5c5SAndroid Build Coastguard Worker 'final classes directory, or the directory in ' 49*8975f5c5SAndroid Build Coastguard Worker 'which to place the instrumented/copied jar.') 50*8975f5c5SAndroid Build Coastguard Worker parser.add_argument('--sources-json-file', 51*8975f5c5SAndroid Build Coastguard Worker required=True, 52*8975f5c5SAndroid Build Coastguard Worker help='File to create with the list of source directories ' 53*8975f5c5SAndroid Build Coastguard Worker 'and input path.') 54*8975f5c5SAndroid Build Coastguard Worker parser.add_argument( 55*8975f5c5SAndroid Build Coastguard Worker '--target-sources-file', 56*8975f5c5SAndroid Build Coastguard Worker required=True, 57*8975f5c5SAndroid Build Coastguard Worker help='File containing newline-separated .java and .kt paths') 58*8975f5c5SAndroid Build Coastguard Worker parser.add_argument( 59*8975f5c5SAndroid Build Coastguard Worker '--jacococli-jar', required=True, help='Path to jacococli.jar.') 60*8975f5c5SAndroid Build Coastguard Worker parser.add_argument( 61*8975f5c5SAndroid Build Coastguard Worker '--files-to-instrument', 62*8975f5c5SAndroid Build Coastguard Worker help='Path to a file containing which source files are affected.') 63*8975f5c5SAndroid Build Coastguard Worker 64*8975f5c5SAndroid Build Coastguard Worker 65*8975f5c5SAndroid Build Coastguard Workerdef _GetSourceDirsFromSourceFiles(source_files): 66*8975f5c5SAndroid Build Coastguard Worker """Returns list of directories for the files in |source_files|. 67*8975f5c5SAndroid Build Coastguard Worker 68*8975f5c5SAndroid Build Coastguard Worker Args: 69*8975f5c5SAndroid Build Coastguard Worker source_files: List of source files. 70*8975f5c5SAndroid Build Coastguard Worker 71*8975f5c5SAndroid Build Coastguard Worker Returns: 72*8975f5c5SAndroid Build Coastguard Worker List of source directories. 73*8975f5c5SAndroid Build Coastguard Worker """ 74*8975f5c5SAndroid Build Coastguard Worker return list(set(os.path.dirname(source_file) for source_file in source_files)) 75*8975f5c5SAndroid Build Coastguard Worker 76*8975f5c5SAndroid Build Coastguard Worker 77*8975f5c5SAndroid Build Coastguard Workerdef _CreateSourcesJsonFile(source_dirs, input_path, sources_json_file, src_root, 78*8975f5c5SAndroid Build Coastguard Worker root_build_dir): 79*8975f5c5SAndroid Build Coastguard Worker """Adds all normalized source directories and input path to 80*8975f5c5SAndroid Build Coastguard Worker |sources_json_file|. 81*8975f5c5SAndroid Build Coastguard Worker 82*8975f5c5SAndroid Build Coastguard Worker Args: 83*8975f5c5SAndroid Build Coastguard Worker source_dirs: List of source directories. 84*8975f5c5SAndroid Build Coastguard Worker input_path: The input path to non-instrumented class files. 85*8975f5c5SAndroid Build Coastguard Worker sources_json_file: File into which to write the list of source directories 86*8975f5c5SAndroid Build Coastguard Worker and input path. 87*8975f5c5SAndroid Build Coastguard Worker src_root: Root which sources added to the file should be relative to. 88*8975f5c5SAndroid Build Coastguard Worker root_build_dir: Build directory path rooted at checkout where 89*8975f5c5SAndroid Build Coastguard Worker sources_json_file is generated e.g. //out/Release 90*8975f5c5SAndroid Build Coastguard Worker 91*8975f5c5SAndroid Build Coastguard Worker Returns: 92*8975f5c5SAndroid Build Coastguard Worker An exit code. 93*8975f5c5SAndroid Build Coastguard Worker """ 94*8975f5c5SAndroid Build Coastguard Worker src_root = os.path.abspath(src_root) 95*8975f5c5SAndroid Build Coastguard Worker relative_sources = [] 96*8975f5c5SAndroid Build Coastguard Worker for s in source_dirs: 97*8975f5c5SAndroid Build Coastguard Worker abs_source = os.path.abspath(s) 98*8975f5c5SAndroid Build Coastguard Worker if abs_source[:len(src_root)] != src_root: 99*8975f5c5SAndroid Build Coastguard Worker print('Error: found source directory not under repository root: %s %s' % 100*8975f5c5SAndroid Build Coastguard Worker (abs_source, src_root)) 101*8975f5c5SAndroid Build Coastguard Worker return 1 102*8975f5c5SAndroid Build Coastguard Worker rel_source = os.path.relpath(abs_source, src_root) 103*8975f5c5SAndroid Build Coastguard Worker 104*8975f5c5SAndroid Build Coastguard Worker relative_sources.append(rel_source) 105*8975f5c5SAndroid Build Coastguard Worker 106*8975f5c5SAndroid Build Coastguard Worker data = {} 107*8975f5c5SAndroid Build Coastguard Worker data['source_dirs'] = relative_sources 108*8975f5c5SAndroid Build Coastguard Worker data['input_path'] = [] 109*8975f5c5SAndroid Build Coastguard Worker build_dir = os.path.join(src_root, root_build_dir[2:]) 110*8975f5c5SAndroid Build Coastguard Worker data['output_dir'] = build_dir 111*8975f5c5SAndroid Build Coastguard Worker if input_path: 112*8975f5c5SAndroid Build Coastguard Worker data['input_path'].append(os.path.abspath(input_path)) 113*8975f5c5SAndroid Build Coastguard Worker with open(sources_json_file, 'w') as f: 114*8975f5c5SAndroid Build Coastguard Worker json.dump(data, f) 115*8975f5c5SAndroid Build Coastguard Worker return 0 116*8975f5c5SAndroid Build Coastguard Worker 117*8975f5c5SAndroid Build Coastguard Worker 118*8975f5c5SAndroid Build Coastguard Workerdef _GetAffectedClasses(jar_file, source_files): 119*8975f5c5SAndroid Build Coastguard Worker """Gets affected classes by affected source files to a jar. 120*8975f5c5SAndroid Build Coastguard Worker 121*8975f5c5SAndroid Build Coastguard Worker Args: 122*8975f5c5SAndroid Build Coastguard Worker jar_file: The jar file to get all members. 123*8975f5c5SAndroid Build Coastguard Worker source_files: The list of affected source files. 124*8975f5c5SAndroid Build Coastguard Worker 125*8975f5c5SAndroid Build Coastguard Worker Returns: 126*8975f5c5SAndroid Build Coastguard Worker A tuple of affected classes and unaffected members. 127*8975f5c5SAndroid Build Coastguard Worker """ 128*8975f5c5SAndroid Build Coastguard Worker with zipfile.ZipFile(jar_file) as f: 129*8975f5c5SAndroid Build Coastguard Worker members = f.namelist() 130*8975f5c5SAndroid Build Coastguard Worker 131*8975f5c5SAndroid Build Coastguard Worker affected_classes = [] 132*8975f5c5SAndroid Build Coastguard Worker unaffected_members = [] 133*8975f5c5SAndroid Build Coastguard Worker 134*8975f5c5SAndroid Build Coastguard Worker for member in members: 135*8975f5c5SAndroid Build Coastguard Worker if not member.endswith('.class'): 136*8975f5c5SAndroid Build Coastguard Worker unaffected_members.append(member) 137*8975f5c5SAndroid Build Coastguard Worker continue 138*8975f5c5SAndroid Build Coastguard Worker 139*8975f5c5SAndroid Build Coastguard Worker is_affected = False 140*8975f5c5SAndroid Build Coastguard Worker index = member.find('$') 141*8975f5c5SAndroid Build Coastguard Worker if index == -1: 142*8975f5c5SAndroid Build Coastguard Worker index = member.find('.class') 143*8975f5c5SAndroid Build Coastguard Worker for source_file in source_files: 144*8975f5c5SAndroid Build Coastguard Worker if source_file.endswith( 145*8975f5c5SAndroid Build Coastguard Worker (member[:index] + '.java', member[:index] + '.kt')): 146*8975f5c5SAndroid Build Coastguard Worker affected_classes.append(member) 147*8975f5c5SAndroid Build Coastguard Worker is_affected = True 148*8975f5c5SAndroid Build Coastguard Worker break 149*8975f5c5SAndroid Build Coastguard Worker if not is_affected: 150*8975f5c5SAndroid Build Coastguard Worker unaffected_members.append(member) 151*8975f5c5SAndroid Build Coastguard Worker 152*8975f5c5SAndroid Build Coastguard Worker return affected_classes, unaffected_members 153*8975f5c5SAndroid Build Coastguard Worker 154*8975f5c5SAndroid Build Coastguard Worker 155*8975f5c5SAndroid Build Coastguard Workerdef _InstrumentClassFiles(instrument_cmd, 156*8975f5c5SAndroid Build Coastguard Worker input_path, 157*8975f5c5SAndroid Build Coastguard Worker output_path, 158*8975f5c5SAndroid Build Coastguard Worker temp_dir, 159*8975f5c5SAndroid Build Coastguard Worker affected_source_files=None): 160*8975f5c5SAndroid Build Coastguard Worker """Instruments class files from input jar. 161*8975f5c5SAndroid Build Coastguard Worker 162*8975f5c5SAndroid Build Coastguard Worker Args: 163*8975f5c5SAndroid Build Coastguard Worker instrument_cmd: JaCoCo instrument command. 164*8975f5c5SAndroid Build Coastguard Worker input_path: The input path to non-instrumented jar. 165*8975f5c5SAndroid Build Coastguard Worker output_path: The output path to instrumented jar. 166*8975f5c5SAndroid Build Coastguard Worker temp_dir: The temporary directory. 167*8975f5c5SAndroid Build Coastguard Worker affected_source_files: The affected source file paths to input jar. 168*8975f5c5SAndroid Build Coastguard Worker Default is None, which means instrumenting all class files in jar. 169*8975f5c5SAndroid Build Coastguard Worker """ 170*8975f5c5SAndroid Build Coastguard Worker affected_classes = None 171*8975f5c5SAndroid Build Coastguard Worker unaffected_members = None 172*8975f5c5SAndroid Build Coastguard Worker if affected_source_files: 173*8975f5c5SAndroid Build Coastguard Worker affected_classes, unaffected_members = _GetAffectedClasses( 174*8975f5c5SAndroid Build Coastguard Worker input_path, affected_source_files) 175*8975f5c5SAndroid Build Coastguard Worker 176*8975f5c5SAndroid Build Coastguard Worker # Extract affected class files. 177*8975f5c5SAndroid Build Coastguard Worker with zipfile.ZipFile(input_path) as f: 178*8975f5c5SAndroid Build Coastguard Worker f.extractall(temp_dir, affected_classes) 179*8975f5c5SAndroid Build Coastguard Worker 180*8975f5c5SAndroid Build Coastguard Worker instrumented_dir = os.path.join(temp_dir, 'instrumented') 181*8975f5c5SAndroid Build Coastguard Worker 182*8975f5c5SAndroid Build Coastguard Worker # Instrument extracted class files. 183*8975f5c5SAndroid Build Coastguard Worker instrument_cmd.extend([temp_dir, '--dest', instrumented_dir]) 184*8975f5c5SAndroid Build Coastguard Worker build_utils.CheckOutput(instrument_cmd) 185*8975f5c5SAndroid Build Coastguard Worker 186*8975f5c5SAndroid Build Coastguard Worker if affected_source_files and unaffected_members: 187*8975f5c5SAndroid Build Coastguard Worker # Extract unaffected members to instrumented_dir. 188*8975f5c5SAndroid Build Coastguard Worker with zipfile.ZipFile(input_path) as f: 189*8975f5c5SAndroid Build Coastguard Worker f.extractall(instrumented_dir, unaffected_members) 190*8975f5c5SAndroid Build Coastguard Worker 191*8975f5c5SAndroid Build Coastguard Worker # Zip all files to output_path 192*8975f5c5SAndroid Build Coastguard Worker with action_helpers.atomic_output(output_path) as f: 193*8975f5c5SAndroid Build Coastguard Worker zip_helpers.zip_directory(f, instrumented_dir) 194*8975f5c5SAndroid Build Coastguard Worker 195*8975f5c5SAndroid Build Coastguard Worker 196*8975f5c5SAndroid Build Coastguard Workerdef _RunInstrumentCommand(parser): 197*8975f5c5SAndroid Build Coastguard Worker """Instruments class or Jar files using JaCoCo. 198*8975f5c5SAndroid Build Coastguard Worker 199*8975f5c5SAndroid Build Coastguard Worker Args: 200*8975f5c5SAndroid Build Coastguard Worker parser: ArgumentParser object. 201*8975f5c5SAndroid Build Coastguard Worker 202*8975f5c5SAndroid Build Coastguard Worker Returns: 203*8975f5c5SAndroid Build Coastguard Worker An exit code. 204*8975f5c5SAndroid Build Coastguard Worker """ 205*8975f5c5SAndroid Build Coastguard Worker args = parser.parse_args() 206*8975f5c5SAndroid Build Coastguard Worker 207*8975f5c5SAndroid Build Coastguard Worker source_files = [] 208*8975f5c5SAndroid Build Coastguard Worker if args.target_sources_file: 209*8975f5c5SAndroid Build Coastguard Worker source_files.extend(build_utils.ReadSourcesList(args.target_sources_file)) 210*8975f5c5SAndroid Build Coastguard Worker 211*8975f5c5SAndroid Build Coastguard Worker with build_utils.TempDir() as temp_dir: 212*8975f5c5SAndroid Build Coastguard Worker instrument_cmd = build_utils.JavaCmd() + [ 213*8975f5c5SAndroid Build Coastguard Worker '-jar', args.jacococli_jar, 'instrument' 214*8975f5c5SAndroid Build Coastguard Worker ] 215*8975f5c5SAndroid Build Coastguard Worker 216*8975f5c5SAndroid Build Coastguard Worker if not args.files_to_instrument: 217*8975f5c5SAndroid Build Coastguard Worker affected_source_files = None 218*8975f5c5SAndroid Build Coastguard Worker else: 219*8975f5c5SAndroid Build Coastguard Worker affected_files = build_utils.ReadSourcesList(args.files_to_instrument) 220*8975f5c5SAndroid Build Coastguard Worker # Check if coverage recipe decided to instrument everything by overriding 221*8975f5c5SAndroid Build Coastguard Worker # the try builder default setting(selective instrumentation). This can 222*8975f5c5SAndroid Build Coastguard Worker # happen in cases like a DEPS roll of jacoco library 223*8975f5c5SAndroid Build Coastguard Worker 224*8975f5c5SAndroid Build Coastguard Worker # Note: This token is preceded by ../../ because the paths to be 225*8975f5c5SAndroid Build Coastguard Worker # instrumented are expected to be relative to the build directory. 226*8975f5c5SAndroid Build Coastguard Worker # See _rebase_paths() at https://bit.ly/40oiixX 227*8975f5c5SAndroid Build Coastguard Worker token = '../../' + INSTRUMENT_ALL_JACOCO_OVERRIDE_TOKEN 228*8975f5c5SAndroid Build Coastguard Worker if token in affected_files: 229*8975f5c5SAndroid Build Coastguard Worker affected_source_files = None 230*8975f5c5SAndroid Build Coastguard Worker else: 231*8975f5c5SAndroid Build Coastguard Worker source_set = set(source_files) 232*8975f5c5SAndroid Build Coastguard Worker affected_source_files = [f for f in affected_files if f in source_set] 233*8975f5c5SAndroid Build Coastguard Worker 234*8975f5c5SAndroid Build Coastguard Worker # Copy input_path to output_path and return if no source file affected. 235*8975f5c5SAndroid Build Coastguard Worker if not affected_source_files: 236*8975f5c5SAndroid Build Coastguard Worker shutil.copyfile(args.input_path, args.output_path) 237*8975f5c5SAndroid Build Coastguard Worker # Create a dummy sources_json_file. 238*8975f5c5SAndroid Build Coastguard Worker _CreateSourcesJsonFile([], None, args.sources_json_file, 239*8975f5c5SAndroid Build Coastguard Worker build_utils.DIR_SOURCE_ROOT, 240*8975f5c5SAndroid Build Coastguard Worker args.root_build_dir) 241*8975f5c5SAndroid Build Coastguard Worker return 0 242*8975f5c5SAndroid Build Coastguard Worker _InstrumentClassFiles(instrument_cmd, args.input_path, args.output_path, 243*8975f5c5SAndroid Build Coastguard Worker temp_dir, affected_source_files) 244*8975f5c5SAndroid Build Coastguard Worker 245*8975f5c5SAndroid Build Coastguard Worker source_dirs = _GetSourceDirsFromSourceFiles(source_files) 246*8975f5c5SAndroid Build Coastguard Worker # TODO(GYP): In GN, we are passed the list of sources, detecting source 247*8975f5c5SAndroid Build Coastguard Worker # directories, then walking them to re-establish the list of sources. 248*8975f5c5SAndroid Build Coastguard Worker # This can obviously be simplified! 249*8975f5c5SAndroid Build Coastguard Worker _CreateSourcesJsonFile(source_dirs, args.input_path, args.sources_json_file, 250*8975f5c5SAndroid Build Coastguard Worker build_utils.DIR_SOURCE_ROOT, args.root_build_dir) 251*8975f5c5SAndroid Build Coastguard Worker 252*8975f5c5SAndroid Build Coastguard Worker return 0 253*8975f5c5SAndroid Build Coastguard Worker 254*8975f5c5SAndroid Build Coastguard Worker 255*8975f5c5SAndroid Build Coastguard Workerdef main(): 256*8975f5c5SAndroid Build Coastguard Worker parser = argparse.ArgumentParser() 257*8975f5c5SAndroid Build Coastguard Worker _AddArguments(parser) 258*8975f5c5SAndroid Build Coastguard Worker _RunInstrumentCommand(parser) 259*8975f5c5SAndroid Build Coastguard Worker 260*8975f5c5SAndroid Build Coastguard Worker 261*8975f5c5SAndroid Build Coastguard Workerif __name__ == '__main__': 262*8975f5c5SAndroid Build Coastguard Worker sys.exit(main()) 263