xref: /aosp_15_r20/external/cronet/build/android/gyp/jacoco_instr.py (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
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