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