emma_instr.py revision 3551c9c881056c480085172ff9840cab31610854
1#!/usr/bin/env python 2# 3# Copyright 2013 The Chromium Authors. All rights reserved. 4# Use of this source code is governed by a BSD-style license that can be 5# found in the LICENSE file. 6 7"""Instruments classes and jar files. 8 9This script corresponds to the 'emma_instr' action in the java build process. 10Depending on whether emma_instrument is set, the 'emma_instr' action will either 11call one of the instrument commands, or the copy command. 12 13Possible commands are: 14- instrument_jar: Accepts a jar and instruments it using emma.jar. 15- instrument_classes: Accepts a directory contains java classes and instruments 16 it using emma.jar. 17- copy: Triggered instead of an instrumentation command when we don't have EMMA 18 coverage enabled. This allows us to make this a required step without 19 necessarily instrumenting on every build. 20""" 21 22import collections 23import json 24import os 25import shutil 26import sys 27import tempfile 28 29sys.path.append(os.path.join(os.path.dirname(__file__), os.pardir)) 30from pylib.utils import command_option_parser 31 32from util import build_utils 33 34 35def _AddCommonOptions(option_parser): 36 """Adds common options to |option_parser|.""" 37 option_parser.add_option('--input-path', 38 help=('Path to input file(s). Either the classes ' 39 'directory, or the path to a jar.')) 40 option_parser.add_option('--output-path', 41 help=('Path to output final file(s) to. Either the ' 42 'final classes directory, or the directory in ' 43 'which to place the instrumented/copied jar.')) 44 option_parser.add_option('--stamp', help='Path to touch when done.') 45 46 47def _AddInstrumentOptions(option_parser): 48 """Adds options related to instrumentation to |option_parser|.""" 49 _AddCommonOptions(option_parser) 50 option_parser.add_option('--coverage-file', 51 help='File to create with coverage metadata.') 52 option_parser.add_option('--sources-file', 53 help='File to create with the list of sources.') 54 option_parser.add_option('--sources', 55 help='Space separated list of sources.') 56 option_parser.add_option('--src-root', 57 help='Root of the src repository.') 58 option_parser.add_option('--emma-jar', 59 help='Path to emma.jar.') 60 61 62def _RunCopyCommand(command, options, args, option_parser): 63 """Just copies the jar from input to output locations. 64 65 Args: 66 command: String indicating the command that was received to trigger 67 this function. 68 options: optparse options dictionary. 69 args: List of extra args from optparse. 70 option_parser: optparse.OptionParser object. 71 72 Returns: 73 An exit code. 74 """ 75 if not (options.input_path and options.output_path): 76 option_parser.error('All arguments are required.') 77 78 if os.path.isdir(options.input_path): 79 shutil.rmtree(options.output_path, ignore_errors=True) 80 shutil.copytree(options.input_path, options.output_path) 81 else: 82 shutil.copy(options.input_path, options.output_path) 83 84 if options.stamp: 85 build_utils.Touch(options.stamp) 86 87 88def _CreateSourcesFile(sources_string, sources_file, src_root): 89 """Adds all normalized source directories to |sources_file|. 90 91 Args: 92 sources_string: String generated from gyp containing the list of sources. 93 sources_file: File into which to write the JSON list of sources. 94 src_root: Root which sources added to the file should be relative to. 95 96 Returns: 97 An exit code. 98 """ 99 src_root = os.path.abspath(src_root) 100 sources = build_utils.ParseGypList(sources_string) 101 relative_sources = [] 102 for s in sources: 103 abs_source = os.path.abspath(s) 104 if abs_source[:len(src_root)] != src_root: 105 print ('Error: found source directory not under repository root: %s %s' 106 % (abs_source, src_root)) 107 return 1 108 rel_source = os.path.relpath(abs_source, src_root) 109 110 relative_sources.append(rel_source) 111 112 with open(sources_file, 'w') as f: 113 json.dump(relative_sources, f) 114 115 116def _RunInstrumentCommand(command, options, args, option_parser): 117 """Instruments the classes/jar files using EMMA. 118 119 Args: 120 command: 'instrument_jar' or 'instrument_classes'. This distinguishes 121 whether we copy the output from the created lib/ directory, or classes/ 122 directory. 123 options: optparse options dictionary. 124 args: List of extra args from optparse. 125 option_parser: optparse.OptionParser object. 126 127 Returns: 128 An exit code. 129 """ 130 if not (options.input_path and options.output_path and 131 options.coverage_file and options.sources_file and options.sources and 132 options.src_root and options.emma_jar): 133 option_parser.error('All arguments are required.') 134 135 coverage_file = os.path.join(os.path.dirname(options.output_path), 136 options.coverage_file) 137 sources_file = os.path.join(os.path.dirname(options.output_path), 138 options.sources_file) 139 temp_dir = tempfile.mkdtemp() 140 try: 141 # TODO(gkanwar): Add '-ix' option to filter out useless classes. 142 build_utils.CheckCallDie(['java', '-cp', options.emma_jar, 143 'emma', 'instr', 144 '-ip', options.input_path, 145 '-d', temp_dir, 146 '-out', coverage_file, 147 '-m', 'fullcopy'], suppress_output=True) 148 149 if command == 'instrument_jar': 150 for jar in os.listdir(os.path.join(temp_dir, 'lib')): 151 shutil.copy(os.path.join(temp_dir, 'lib', jar), 152 options.output_path) 153 else: # 'instrument_classes' 154 if os.path.isdir(options.output_path): 155 shutil.rmtree(options.output_path, ignore_errors=True) 156 shutil.copytree(os.path.join(temp_dir, 'classes'), 157 options.output_path) 158 finally: 159 shutil.rmtree(temp_dir) 160 161 _CreateSourcesFile(options.sources, sources_file, options.src_root) 162 163 if options.stamp: 164 build_utils.Touch(options.stamp) 165 166 return 0 167 168 169CommandFunctionTuple = collections.namedtuple( 170 'CommandFunctionTuple', ['add_options_func', 'run_command_func']) 171VALID_COMMANDS = { 172 'copy': CommandFunctionTuple(_AddCommonOptions, 173 _RunCopyCommand), 174 'instrument_jar': CommandFunctionTuple(_AddInstrumentOptions, 175 _RunInstrumentCommand), 176 'instrument_classes': CommandFunctionTuple(_AddInstrumentOptions, 177 _RunInstrumentCommand), 178} 179 180 181def main(argv): 182 option_parser = command_option_parser.CommandOptionParser( 183 commands_dict=VALID_COMMANDS) 184 command_option_parser.ParseAndExecute(option_parser) 185 186 187if __name__ == '__main__': 188 sys.exit(main(sys.argv)) 189