xref: /aosp_15_r20/external/angle/build/android/gyp/jacoco_instr.py (revision 8975f5c5ed3d1c378011245431ada316dfb6f244)
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