xref: /aosp_15_r20/build/bazel/scripts/difftool/clangcompile.py (revision 7594170e27e0732bc44b93d1440d87a54b6ffe7c)
1*7594170eSAndroid Build Coastguard Worker#!/usr/bin/env python3
2*7594170eSAndroid Build Coastguard Worker#
3*7594170eSAndroid Build Coastguard Worker# Copyright (C) 2022 The Android Open Source Project
4*7594170eSAndroid Build Coastguard Worker#
5*7594170eSAndroid Build Coastguard Worker# Licensed under the Apache License, Version 2.0 (the "License");
6*7594170eSAndroid Build Coastguard Worker# you may not use this file except in compliance with the License.
7*7594170eSAndroid Build Coastguard Worker# You may obtain a copy of the License at
8*7594170eSAndroid Build Coastguard Worker#
9*7594170eSAndroid Build Coastguard Worker#   http://www.apache.org/licenses/LICENSE-2.0
10*7594170eSAndroid Build Coastguard Worker#
11*7594170eSAndroid Build Coastguard Worker# Unless required by applicable law or agreed to in writing, software
12*7594170eSAndroid Build Coastguard Worker# distributed under the License is distributed on an "AS IS" BASIS,
13*7594170eSAndroid Build Coastguard Worker# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14*7594170eSAndroid Build Coastguard Worker# See the License for the specific language governing permissions and
15*7594170eSAndroid Build Coastguard Worker# limitations under the License."""
16*7594170eSAndroid Build Coastguard Worker"""Helpers pertaining to clang compile actions."""
17*7594170eSAndroid Build Coastguard Worker
18*7594170eSAndroid Build Coastguard Workerimport collections
19*7594170eSAndroid Build Coastguard Workerimport pathlib
20*7594170eSAndroid Build Coastguard Workerimport subprocess
21*7594170eSAndroid Build Coastguard Workerfrom commands import CommandInfo
22*7594170eSAndroid Build Coastguard Workerfrom commands import flag_repr
23*7594170eSAndroid Build Coastguard Workerfrom commands import is_flag_starts_with
24*7594170eSAndroid Build Coastguard Workerfrom commands import parse_flag_groups
25*7594170eSAndroid Build Coastguard Workerfrom diffs.diff import ExtractInfo
26*7594170eSAndroid Build Coastguard Workerfrom diffs.context import ContextDiff
27*7594170eSAndroid Build Coastguard Workerfrom diffs.nm import NmSymbolDiff
28*7594170eSAndroid Build Coastguard Workerfrom diffs.bloaty import BloatyDiff
29*7594170eSAndroid Build Coastguard Worker
30*7594170eSAndroid Build Coastguard Worker
31*7594170eSAndroid Build Coastguard Workerclass ClangCompileInfo(CommandInfo):
32*7594170eSAndroid Build Coastguard Worker  """Contains information about a clang compile action commandline."""
33*7594170eSAndroid Build Coastguard Worker
34*7594170eSAndroid Build Coastguard Worker  def __init__(self, tool, args):
35*7594170eSAndroid Build Coastguard Worker    CommandInfo.__init__(self, tool, args)
36*7594170eSAndroid Build Coastguard Worker
37*7594170eSAndroid Build Coastguard Worker    flag_groups = parse_flag_groups(args, _custom_flag_group)
38*7594170eSAndroid Build Coastguard Worker
39*7594170eSAndroid Build Coastguard Worker    misc = []
40*7594170eSAndroid Build Coastguard Worker    i_includes = []
41*7594170eSAndroid Build Coastguard Worker    iquote_includes = []
42*7594170eSAndroid Build Coastguard Worker    isystem_includes = []
43*7594170eSAndroid Build Coastguard Worker    defines = []
44*7594170eSAndroid Build Coastguard Worker    warnings = []
45*7594170eSAndroid Build Coastguard Worker    features = []
46*7594170eSAndroid Build Coastguard Worker    libraries = []
47*7594170eSAndroid Build Coastguard Worker    linker_args = []
48*7594170eSAndroid Build Coastguard Worker    assembler_args = []
49*7594170eSAndroid Build Coastguard Worker    file_flags = []
50*7594170eSAndroid Build Coastguard Worker    for g in flag_groups:
51*7594170eSAndroid Build Coastguard Worker      if is_flag_starts_with("D", g) or is_flag_starts_with("U", g):
52*7594170eSAndroid Build Coastguard Worker        defines += [g]
53*7594170eSAndroid Build Coastguard Worker      elif is_flag_starts_with("f", g):
54*7594170eSAndroid Build Coastguard Worker        features += [g]
55*7594170eSAndroid Build Coastguard Worker      elif is_flag_starts_with("l", g):
56*7594170eSAndroid Build Coastguard Worker        libraries += [g]
57*7594170eSAndroid Build Coastguard Worker      elif is_flag_starts_with("Wl", g):
58*7594170eSAndroid Build Coastguard Worker        linker_args += [g]
59*7594170eSAndroid Build Coastguard Worker      elif is_flag_starts_with("Wa", g) and not is_flag_starts_with("Wall", g):
60*7594170eSAndroid Build Coastguard Worker        assembler_args += [g]
61*7594170eSAndroid Build Coastguard Worker      elif is_flag_starts_with("W", g) or is_flag_starts_with("w", g):
62*7594170eSAndroid Build Coastguard Worker        warnings += [g]
63*7594170eSAndroid Build Coastguard Worker      elif is_flag_starts_with("I", g):
64*7594170eSAndroid Build Coastguard Worker        i_includes += [g]
65*7594170eSAndroid Build Coastguard Worker      elif is_flag_starts_with("isystem", g):
66*7594170eSAndroid Build Coastguard Worker        isystem_includes += [g]
67*7594170eSAndroid Build Coastguard Worker      elif is_flag_starts_with("iquote", g):
68*7594170eSAndroid Build Coastguard Worker        iquote_includes += [g]
69*7594170eSAndroid Build Coastguard Worker      elif (
70*7594170eSAndroid Build Coastguard Worker          is_flag_starts_with("MF", g)
71*7594170eSAndroid Build Coastguard Worker          or is_flag_starts_with("o", g)
72*7594170eSAndroid Build Coastguard Worker          or _is_src_group(g)
73*7594170eSAndroid Build Coastguard Worker      ):
74*7594170eSAndroid Build Coastguard Worker        file_flags += [g]
75*7594170eSAndroid Build Coastguard Worker      else:
76*7594170eSAndroid Build Coastguard Worker        misc += [g]
77*7594170eSAndroid Build Coastguard Worker    self.features = features
78*7594170eSAndroid Build Coastguard Worker    self.defines = _process_defines(defines)
79*7594170eSAndroid Build Coastguard Worker    self.libraries = libraries
80*7594170eSAndroid Build Coastguard Worker    self.linker_args = linker_args
81*7594170eSAndroid Build Coastguard Worker    self.assembler_args = assembler_args
82*7594170eSAndroid Build Coastguard Worker    self.i_includes = _process_includes(i_includes)
83*7594170eSAndroid Build Coastguard Worker    self.iquote_includes = _process_includes(iquote_includes)
84*7594170eSAndroid Build Coastguard Worker    self.isystem_includes = _process_includes(isystem_includes)
85*7594170eSAndroid Build Coastguard Worker    self.file_flags = file_flags
86*7594170eSAndroid Build Coastguard Worker    self.warnings = warnings
87*7594170eSAndroid Build Coastguard Worker    self.misc_flags = sorted(misc, key=flag_repr)
88*7594170eSAndroid Build Coastguard Worker
89*7594170eSAndroid Build Coastguard Worker  def _str_for_field(self, field_name, values):
90*7594170eSAndroid Build Coastguard Worker    s = "  " + field_name + ":\n"
91*7594170eSAndroid Build Coastguard Worker    for x in values:
92*7594170eSAndroid Build Coastguard Worker      s += "    " + flag_repr(x) + "\n"
93*7594170eSAndroid Build Coastguard Worker    return s
94*7594170eSAndroid Build Coastguard Worker
95*7594170eSAndroid Build Coastguard Worker  def __str__(self):
96*7594170eSAndroid Build Coastguard Worker    s = "ClangCompileInfo:\n"
97*7594170eSAndroid Build Coastguard Worker
98*7594170eSAndroid Build Coastguard Worker    for label, fields in {
99*7594170eSAndroid Build Coastguard Worker        "Features": self.features,
100*7594170eSAndroid Build Coastguard Worker        "Defines": self.defines,
101*7594170eSAndroid Build Coastguard Worker        "Libraries": self.libraries,
102*7594170eSAndroid Build Coastguard Worker        "Linker args": self.linker_args,
103*7594170eSAndroid Build Coastguard Worker        "Assembler args": self.assembler_args,
104*7594170eSAndroid Build Coastguard Worker        "Includes (-I,": self.i_includes,
105*7594170eSAndroid Build Coastguard Worker        "Includes (-iquote,": self.iquote_includes,
106*7594170eSAndroid Build Coastguard Worker        "Includes (-isystem,": self.isystem_includes,
107*7594170eSAndroid Build Coastguard Worker        "Files": self.file_flags,
108*7594170eSAndroid Build Coastguard Worker        "Warnings": self.warnings,
109*7594170eSAndroid Build Coastguard Worker        "Misc": self.misc_flags,
110*7594170eSAndroid Build Coastguard Worker    }.items():
111*7594170eSAndroid Build Coastguard Worker      if len(fields) > 0:
112*7594170eSAndroid Build Coastguard Worker        s += self._str_for_field(label, list(set(fields)))
113*7594170eSAndroid Build Coastguard Worker
114*7594170eSAndroid Build Coastguard Worker    return s
115*7594170eSAndroid Build Coastguard Worker
116*7594170eSAndroid Build Coastguard Worker  def compare(self, other):
117*7594170eSAndroid Build Coastguard Worker    """computes difference in arguments from another ClangCompileInfo"""
118*7594170eSAndroid Build Coastguard Worker    diffs = ClangCompileInfo(self.tool, [])
119*7594170eSAndroid Build Coastguard Worker    diffs.defines = [i for i in self.defines if i not in other.defines]
120*7594170eSAndroid Build Coastguard Worker    diffs.warnings = [i for i in self.warnings if i not in other.warnings]
121*7594170eSAndroid Build Coastguard Worker    diffs.features = [i for i in self.features if i not in other.features]
122*7594170eSAndroid Build Coastguard Worker    diffs.libraries = [i for i in self.libraries if i not in other.libraries]
123*7594170eSAndroid Build Coastguard Worker    diffs.linker_args = [
124*7594170eSAndroid Build Coastguard Worker        i for i in self.linker_args if i not in other.linker_args
125*7594170eSAndroid Build Coastguard Worker    ]
126*7594170eSAndroid Build Coastguard Worker    diffs.assembler_args = [
127*7594170eSAndroid Build Coastguard Worker        i for i in self.assembler_args if i not in other.assembler_args
128*7594170eSAndroid Build Coastguard Worker    ]
129*7594170eSAndroid Build Coastguard Worker    diffs.i_includes = [i for i in self.i_includes if i not in other.i_includes]
130*7594170eSAndroid Build Coastguard Worker    diffs.iquote_includes = [
131*7594170eSAndroid Build Coastguard Worker        i for i in self.iquote_includes if i not in other.iquote_includes
132*7594170eSAndroid Build Coastguard Worker    ]
133*7594170eSAndroid Build Coastguard Worker    diffs.isystem_includes = [
134*7594170eSAndroid Build Coastguard Worker        i for i in self.isystem_includes if i not in other.isystem_includes
135*7594170eSAndroid Build Coastguard Worker    ]
136*7594170eSAndroid Build Coastguard Worker    diffs.file_flags = [i for i in self.file_flags if i not in other.file_flags]
137*7594170eSAndroid Build Coastguard Worker    diffs.misc_flags = [i for i in self.misc_flags if i not in other.misc_flags]
138*7594170eSAndroid Build Coastguard Worker    return diffs
139*7594170eSAndroid Build Coastguard Worker
140*7594170eSAndroid Build Coastguard Worker
141*7594170eSAndroid Build Coastguard Workerdef _is_src_group(x):
142*7594170eSAndroid Build Coastguard Worker  """Returns true if the given flag group describes a source file."""
143*7594170eSAndroid Build Coastguard Worker  return isinstance(x, str) and x.endswith(".cpp")
144*7594170eSAndroid Build Coastguard Worker
145*7594170eSAndroid Build Coastguard Worker
146*7594170eSAndroid Build Coastguard Workerdef _custom_flag_group(x):
147*7594170eSAndroid Build Coastguard Worker  """Identifies single-arg flag groups for clang compiles.
148*7594170eSAndroid Build Coastguard Worker
149*7594170eSAndroid Build Coastguard Worker  Returns a flag group if the given argument corresponds to a single-argument
150*7594170eSAndroid Build Coastguard Worker  flag group for clang compile. (For example, `-c` is a single-arg flag for
151*7594170eSAndroid Build Coastguard Worker  clang compiles, but may not be for other tools.)
152*7594170eSAndroid Build Coastguard Worker
153*7594170eSAndroid Build Coastguard Worker  See commands.parse_flag_groups documentation for signature details.
154*7594170eSAndroid Build Coastguard Worker  """
155*7594170eSAndroid Build Coastguard Worker  if x.startswith("-I") and len(x) > 2:
156*7594170eSAndroid Build Coastguard Worker    return ("I", x[2:])
157*7594170eSAndroid Build Coastguard Worker  if x.startswith("-W") and len(x) > 2:
158*7594170eSAndroid Build Coastguard Worker    return x
159*7594170eSAndroid Build Coastguard Worker  elif x == "-c":
160*7594170eSAndroid Build Coastguard Worker    return x
161*7594170eSAndroid Build Coastguard Worker  return None
162*7594170eSAndroid Build Coastguard Worker
163*7594170eSAndroid Build Coastguard Worker
164*7594170eSAndroid Build Coastguard Workerdef _process_defines(defs):
165*7594170eSAndroid Build Coastguard Worker  """Processes and returns deduplicated define flags from all define args."""
166*7594170eSAndroid Build Coastguard Worker  # TODO(cparsons): Determine and return effective defines (returning the last
167*7594170eSAndroid Build Coastguard Worker  # set value).
168*7594170eSAndroid Build Coastguard Worker  defines_by_var = collections.defaultdict(list)
169*7594170eSAndroid Build Coastguard Worker  for x in defs:
170*7594170eSAndroid Build Coastguard Worker    if isinstance(x, tuple):
171*7594170eSAndroid Build Coastguard Worker      var_name = x[0][2:]
172*7594170eSAndroid Build Coastguard Worker    else:
173*7594170eSAndroid Build Coastguard Worker      var_name = x[2:]
174*7594170eSAndroid Build Coastguard Worker    defines_by_var[var_name].append(x)
175*7594170eSAndroid Build Coastguard Worker  result = []
176*7594170eSAndroid Build Coastguard Worker  for k in sorted(defines_by_var):
177*7594170eSAndroid Build Coastguard Worker    d = defines_by_var[k]
178*7594170eSAndroid Build Coastguard Worker    for x in d:
179*7594170eSAndroid Build Coastguard Worker      result += [x]
180*7594170eSAndroid Build Coastguard Worker  return result
181*7594170eSAndroid Build Coastguard Worker
182*7594170eSAndroid Build Coastguard Worker
183*7594170eSAndroid Build Coastguard Workerdef _process_includes(includes):
184*7594170eSAndroid Build Coastguard Worker  # Drop genfiles directories; makes diffing easier.
185*7594170eSAndroid Build Coastguard Worker  result = []
186*7594170eSAndroid Build Coastguard Worker  for x in includes:
187*7594170eSAndroid Build Coastguard Worker    if isinstance(x, tuple):
188*7594170eSAndroid Build Coastguard Worker      if not x[1].startswith("bazel-out"):
189*7594170eSAndroid Build Coastguard Worker        result += [x]
190*7594170eSAndroid Build Coastguard Worker    else:
191*7594170eSAndroid Build Coastguard Worker      result += [x]
192*7594170eSAndroid Build Coastguard Worker  return result
193*7594170eSAndroid Build Coastguard Worker
194*7594170eSAndroid Build Coastguard Worker
195*7594170eSAndroid Build Coastguard Workerdef _external_tool(*args) -> ExtractInfo:
196*7594170eSAndroid Build Coastguard Worker  return lambda file: subprocess.run(
197*7594170eSAndroid Build Coastguard Worker      [*args, str(file)], check=True, capture_output=True, encoding="utf-8"
198*7594170eSAndroid Build Coastguard Worker  ).stdout.splitlines()
199*7594170eSAndroid Build Coastguard Worker
200*7594170eSAndroid Build Coastguard Worker
201*7594170eSAndroid Build Coastguard Worker# TODO(usta) use nm as a data dependency
202*7594170eSAndroid Build Coastguard Workerdef nm_differences(
203*7594170eSAndroid Build Coastguard Worker    left_path: pathlib.Path, right_path: pathlib.Path
204*7594170eSAndroid Build Coastguard Worker) -> list[str]:
205*7594170eSAndroid Build Coastguard Worker  """Returns differences in symbol tables.
206*7594170eSAndroid Build Coastguard Worker
207*7594170eSAndroid Build Coastguard Worker  Returns the empty list if these files are deemed "similar enough".
208*7594170eSAndroid Build Coastguard Worker  """
209*7594170eSAndroid Build Coastguard Worker  return NmSymbolDiff(_external_tool("nm"), "symbol tables").diff(
210*7594170eSAndroid Build Coastguard Worker      left_path, right_path
211*7594170eSAndroid Build Coastguard Worker  )
212*7594170eSAndroid Build Coastguard Worker
213*7594170eSAndroid Build Coastguard Worker
214*7594170eSAndroid Build Coastguard Worker# TODO(usta) use readelf as a data dependency
215*7594170eSAndroid Build Coastguard Workerdef elf_differences(
216*7594170eSAndroid Build Coastguard Worker    left_path: pathlib.Path, right_path: pathlib.Path
217*7594170eSAndroid Build Coastguard Worker) -> list[str]:
218*7594170eSAndroid Build Coastguard Worker  """Returns differences in elf headers.
219*7594170eSAndroid Build Coastguard Worker
220*7594170eSAndroid Build Coastguard Worker  Returns the empty list if these files are deemed "similar enough".
221*7594170eSAndroid Build Coastguard Worker
222*7594170eSAndroid Build Coastguard Worker  The given files must exist and must be object (.o) files.
223*7594170eSAndroid Build Coastguard Worker  """
224*7594170eSAndroid Build Coastguard Worker  return ContextDiff(_external_tool("readelf", "-h"), "elf headers").diff(
225*7594170eSAndroid Build Coastguard Worker      left_path, right_path
226*7594170eSAndroid Build Coastguard Worker  )
227*7594170eSAndroid Build Coastguard Worker
228*7594170eSAndroid Build Coastguard Worker
229*7594170eSAndroid Build Coastguard Worker# TODO(usta) use bloaty as a data dependency
230*7594170eSAndroid Build Coastguard Workerdef bloaty_differences(
231*7594170eSAndroid Build Coastguard Worker    left_path: pathlib.Path, right_path: pathlib.Path
232*7594170eSAndroid Build Coastguard Worker) -> list[str]:
233*7594170eSAndroid Build Coastguard Worker  """Returns differences in symbol and section tables.
234*7594170eSAndroid Build Coastguard Worker
235*7594170eSAndroid Build Coastguard Worker  Returns the empty list if these files are deemed "similar enough".
236*7594170eSAndroid Build Coastguard Worker
237*7594170eSAndroid Build Coastguard Worker  The given files must exist and must be object (.o) files.
238*7594170eSAndroid Build Coastguard Worker  """
239*7594170eSAndroid Build Coastguard Worker  return _bloaty_differences(left_path, right_path)
240*7594170eSAndroid Build Coastguard Worker
241*7594170eSAndroid Build Coastguard Worker
242*7594170eSAndroid Build Coastguard Worker# TODO(usta) use bloaty as a data dependency
243*7594170eSAndroid Build Coastguard Workerdef bloaty_differences_compileunits(
244*7594170eSAndroid Build Coastguard Worker    left_path: pathlib.Path, right_path: pathlib.Path
245*7594170eSAndroid Build Coastguard Worker) -> list[str]:
246*7594170eSAndroid Build Coastguard Worker  """Returns differences in symbol and section tables.
247*7594170eSAndroid Build Coastguard Worker
248*7594170eSAndroid Build Coastguard Worker  Returns the empty list if these files are deemed "similar enough".
249*7594170eSAndroid Build Coastguard Worker
250*7594170eSAndroid Build Coastguard Worker  The given files must exist and must be object (.o) files.
251*7594170eSAndroid Build Coastguard Worker  """
252*7594170eSAndroid Build Coastguard Worker  return _bloaty_differences(left_path, right_path, True)
253*7594170eSAndroid Build Coastguard Worker
254*7594170eSAndroid Build Coastguard Worker
255*7594170eSAndroid Build Coastguard Worker# TODO(usta) use bloaty as a data dependency
256*7594170eSAndroid Build Coastguard Workerdef _bloaty_differences(
257*7594170eSAndroid Build Coastguard Worker    left_path: pathlib.Path, right_path: pathlib.Path, debug=False
258*7594170eSAndroid Build Coastguard Worker) -> list[str]:
259*7594170eSAndroid Build Coastguard Worker  symbols = BloatyDiff(
260*7594170eSAndroid Build Coastguard Worker      "symbol tables", "symbols", has_debug_symbols=debug
261*7594170eSAndroid Build Coastguard Worker  ).diff(left_path, right_path)
262*7594170eSAndroid Build Coastguard Worker  sections = BloatyDiff(
263*7594170eSAndroid Build Coastguard Worker      "section tables", "sections", has_debug_symbols=debug
264*7594170eSAndroid Build Coastguard Worker  ).diff(left_path, right_path)
265*7594170eSAndroid Build Coastguard Worker  segments = BloatyDiff(
266*7594170eSAndroid Build Coastguard Worker      "segment tables", "segments", has_debug_symbols=debug
267*7594170eSAndroid Build Coastguard Worker  ).diff(left_path, right_path)
268*7594170eSAndroid Build Coastguard Worker  return symbols + sections + segments
269