OLD | NEW |
| (Empty) |
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 | |
9 This script corresponds to the 'emma_instr' action in the java build process. | |
10 Depending on whether emma_instrument is set, the 'emma_instr' action will either | |
11 call one of the instrument commands, or the copy command. | |
12 | |
13 Possible commands are: | |
14 - instrument_jar: Accepts a jar and instruments it using emma.jar. | |
15 - instrument_classes: Accepts a directory containing java classes and | |
16 instruments it using emma.jar. | |
17 - copy: Called when EMMA coverage is not enabled. This allows us to make | |
18 this a required step without necessarily instrumenting on every build. | |
19 Also removes any stale coverage files. | |
20 """ | |
21 | |
22 import collections | |
23 import json | |
24 import os | |
25 import shutil | |
26 import sys | |
27 import tempfile | |
28 | |
29 sys.path.append(os.path.join(os.path.dirname(__file__), os.pardir)) | |
30 from pylib.utils import command_option_parser | |
31 | |
32 from util import build_utils | |
33 | |
34 | |
35 def _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 option_parser.add_option('--coverage-file', | |
46 help='File to create with coverage metadata.') | |
47 option_parser.add_option('--sources-file', | |
48 help='File to create with the list of sources.') | |
49 | |
50 | |
51 def _AddInstrumentOptions(option_parser): | |
52 """Adds options related to instrumentation to |option_parser|.""" | |
53 _AddCommonOptions(option_parser) | |
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 option_parser.add_option( | |
61 '--filter-string', default='', | |
62 help=('Filter string consisting of a list of inclusion/exclusion ' | |
63 'patterns separated with whitespace and/or comma.')) | |
64 | |
65 | |
66 def _RunCopyCommand(_command, options, _, option_parser): | |
67 """Copies the jar from input to output locations. | |
68 | |
69 Also removes any old coverage/sources file. | |
70 | |
71 Args: | |
72 command: String indicating the command that was received to trigger | |
73 this function. | |
74 options: optparse options dictionary. | |
75 args: List of extra args from optparse. | |
76 option_parser: optparse.OptionParser object. | |
77 | |
78 Returns: | |
79 An exit code. | |
80 """ | |
81 if not (options.input_path and options.output_path and | |
82 options.coverage_file and options.sources_file): | |
83 option_parser.error('All arguments are required.') | |
84 | |
85 coverage_file = os.path.join(os.path.dirname(options.output_path), | |
86 options.coverage_file) | |
87 sources_file = os.path.join(os.path.dirname(options.output_path), | |
88 options.sources_file) | |
89 if os.path.exists(coverage_file): | |
90 os.remove(coverage_file) | |
91 if os.path.exists(sources_file): | |
92 os.remove(sources_file) | |
93 | |
94 if os.path.isdir(options.input_path): | |
95 shutil.rmtree(options.output_path, ignore_errors=True) | |
96 shutil.copytree(options.input_path, options.output_path) | |
97 else: | |
98 shutil.copy(options.input_path, options.output_path) | |
99 | |
100 if options.stamp: | |
101 build_utils.Touch(options.stamp) | |
102 | |
103 | |
104 def _CreateSourcesFile(sources_string, sources_file, src_root): | |
105 """Adds all normalized source directories to |sources_file|. | |
106 | |
107 Args: | |
108 sources_string: String generated from gyp containing the list of sources. | |
109 sources_file: File into which to write the JSON list of sources. | |
110 src_root: Root which sources added to the file should be relative to. | |
111 | |
112 Returns: | |
113 An exit code. | |
114 """ | |
115 src_root = os.path.abspath(src_root) | |
116 sources = build_utils.ParseGypList(sources_string) | |
117 relative_sources = [] | |
118 for s in sources: | |
119 abs_source = os.path.abspath(s) | |
120 if abs_source[:len(src_root)] != src_root: | |
121 print ('Error: found source directory not under repository root: %s %s' | |
122 % (abs_source, src_root)) | |
123 return 1 | |
124 rel_source = os.path.relpath(abs_source, src_root) | |
125 | |
126 relative_sources.append(rel_source) | |
127 | |
128 with open(sources_file, 'w') as f: | |
129 json.dump(relative_sources, f) | |
130 | |
131 | |
132 def _RunInstrumentCommand(command, options, _, option_parser): | |
133 """Instruments the classes/jar files using EMMA. | |
134 | |
135 Args: | |
136 command: 'instrument_jar' or 'instrument_classes'. This distinguishes | |
137 whether we copy the output from the created lib/ directory, or classes/ | |
138 directory. | |
139 options: optparse options dictionary. | |
140 args: List of extra args from optparse. | |
141 option_parser: optparse.OptionParser object. | |
142 | |
143 Returns: | |
144 An exit code. | |
145 """ | |
146 if not (options.input_path and options.output_path and | |
147 options.coverage_file and options.sources_file and options.sources and | |
148 options.src_root and options.emma_jar): | |
149 option_parser.error('All arguments are required.') | |
150 | |
151 coverage_file = os.path.join(os.path.dirname(options.output_path), | |
152 options.coverage_file) | |
153 sources_file = os.path.join(os.path.dirname(options.output_path), | |
154 options.sources_file) | |
155 if os.path.exists(coverage_file): | |
156 os.remove(coverage_file) | |
157 temp_dir = tempfile.mkdtemp() | |
158 try: | |
159 cmd = ['java', '-cp', options.emma_jar, | |
160 'emma', 'instr', | |
161 '-ip', options.input_path, | |
162 '-ix', options.filter_string, | |
163 '-d', temp_dir, | |
164 '-out', coverage_file, | |
165 '-m', 'fullcopy'] | |
166 build_utils.CheckOutput(cmd) | |
167 | |
168 if command == 'instrument_jar': | |
169 for jar in os.listdir(os.path.join(temp_dir, 'lib')): | |
170 shutil.copy(os.path.join(temp_dir, 'lib', jar), | |
171 options.output_path) | |
172 else: # 'instrument_classes' | |
173 if os.path.isdir(options.output_path): | |
174 shutil.rmtree(options.output_path, ignore_errors=True) | |
175 shutil.copytree(os.path.join(temp_dir, 'classes'), | |
176 options.output_path) | |
177 finally: | |
178 shutil.rmtree(temp_dir) | |
179 | |
180 _CreateSourcesFile(options.sources, sources_file, options.src_root) | |
181 | |
182 if options.stamp: | |
183 build_utils.Touch(options.stamp) | |
184 | |
185 return 0 | |
186 | |
187 | |
188 CommandFunctionTuple = collections.namedtuple( | |
189 'CommandFunctionTuple', ['add_options_func', 'run_command_func']) | |
190 VALID_COMMANDS = { | |
191 'copy': CommandFunctionTuple(_AddCommonOptions, | |
192 _RunCopyCommand), | |
193 'instrument_jar': CommandFunctionTuple(_AddInstrumentOptions, | |
194 _RunInstrumentCommand), | |
195 'instrument_classes': CommandFunctionTuple(_AddInstrumentOptions, | |
196 _RunInstrumentCommand), | |
197 } | |
198 | |
199 | |
200 def main(): | |
201 option_parser = command_option_parser.CommandOptionParser( | |
202 commands_dict=VALID_COMMANDS) | |
203 command_option_parser.ParseAndExecute(option_parser) | |
204 | |
205 | |
206 if __name__ == '__main__': | |
207 sys.exit(main()) | |
OLD | NEW |