xref: /aosp_15_r20/build/bazel/scripts/difftool/difftool.py (revision 7594170e27e0732bc44b93d1440d87a54b6ffe7c)
1*7594170eSAndroid Build Coastguard Worker#!/usr/bin/env python3
2*7594170eSAndroid Build Coastguard Worker#
3*7594170eSAndroid Build Coastguard Worker# Copyright (C) 2021 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"""Provides useful diff information for build artifacts.
17*7594170eSAndroid Build Coastguard Worker
18*7594170eSAndroid Build Coastguard WorkerUses collected build artifacts from two separate build invocations to
19*7594170eSAndroid Build Coastguard Workercompare output artifacts of these builds and/or the commands executed
20*7594170eSAndroid Build Coastguard Workerto generate them.
21*7594170eSAndroid Build Coastguard Worker
22*7594170eSAndroid Build Coastguard WorkerSee the directory-level README for information about full usage, including
23*7594170eSAndroid Build Coastguard Workerthe collection step: a preparatory step required before invocation of this
24*7594170eSAndroid Build Coastguard Workertool.
25*7594170eSAndroid Build Coastguard Worker
26*7594170eSAndroid Build Coastguard WorkerUse `difftool.py --help` for full usage information of this tool.
27*7594170eSAndroid Build Coastguard Worker
28*7594170eSAndroid Build Coastguard WorkerExample Usage:
29*7594170eSAndroid Build Coastguard Worker  ./difftool.py [left_dir] [left_output_file] [right_dir] [right_output_file]
30*7594170eSAndroid Build Coastguard Worker
31*7594170eSAndroid Build Coastguard WorkerDifftool will compare [left_dir]/[left_output_file] and
32*7594170eSAndroid Build Coastguard Worker[right_dir]/[right_output_file] and provide its best insightful analysis on the
33*7594170eSAndroid Build Coastguard Workerdifferences between these files. The content and depth of this analysis depends
34*7594170eSAndroid Build Coastguard Workeron the types of these files, and also on Difftool"s verbosity mode. Difftool
35*7594170eSAndroid Build Coastguard Workermay also use command data present in the left and right directories as part of
36*7594170eSAndroid Build Coastguard Workerits analysis.
37*7594170eSAndroid Build Coastguard Worker"""
38*7594170eSAndroid Build Coastguard Worker
39*7594170eSAndroid Build Coastguard Workerimport argparse
40*7594170eSAndroid Build Coastguard Workerimport enum
41*7594170eSAndroid Build Coastguard Workerimport functools
42*7594170eSAndroid Build Coastguard Workerimport json
43*7594170eSAndroid Build Coastguard Workerimport os
44*7594170eSAndroid Build Coastguard Workerimport pathlib
45*7594170eSAndroid Build Coastguard Workerimport re
46*7594170eSAndroid Build Coastguard Workerimport subprocess
47*7594170eSAndroid Build Coastguard Workerimport sys
48*7594170eSAndroid Build Coastguard Workerfrom typing import Callable
49*7594170eSAndroid Build Coastguard Worker
50*7594170eSAndroid Build Coastguard Workerimport clangcompile
51*7594170eSAndroid Build Coastguard Workerimport commands
52*7594170eSAndroid Build Coastguard Workerfrom collect import COLLECTION_INFO_FILENAME
53*7594170eSAndroid Build Coastguard Worker
54*7594170eSAndroid Build Coastguard WorkerDiffFunction = Callable[[pathlib.Path, pathlib.Path], list[str]]
55*7594170eSAndroid Build Coastguard Worker"""Given two files, produces a list of differences."""
56*7594170eSAndroid Build Coastguard Worker
57*7594170eSAndroid Build Coastguard Worker
58*7594170eSAndroid Build Coastguard Worker@functools.total_ordering
59*7594170eSAndroid Build Coastguard Workerclass DiffLevel(enum.Enum):
60*7594170eSAndroid Build Coastguard Worker  """Defines the level of differences that should trigger a failure.
61*7594170eSAndroid Build Coastguard Worker
62*7594170eSAndroid Build Coastguard Worker  E.g. when set to WARNING, differences deemed WARNING or SEVERE are taken into
63*7594170eSAndroid Build Coastguard Worker  account while other differences (INFO, FINE etc.) will be ignored.
64*7594170eSAndroid Build Coastguard Worker  """
65*7594170eSAndroid Build Coastguard Worker  SEVERE = 1
66*7594170eSAndroid Build Coastguard Worker  WARNING = 2
67*7594170eSAndroid Build Coastguard Worker  INFO = 3
68*7594170eSAndroid Build Coastguard Worker  FINE = 4
69*7594170eSAndroid Build Coastguard Worker
70*7594170eSAndroid Build Coastguard Worker  def __lt__(self, other):
71*7594170eSAndroid Build Coastguard Worker    if self.__class__ is other.__class__:
72*7594170eSAndroid Build Coastguard Worker      return self.value < other.value
73*7594170eSAndroid Build Coastguard Worker    return NotImplemented
74*7594170eSAndroid Build Coastguard Worker
75*7594170eSAndroid Build Coastguard Worker
76*7594170eSAndroid Build Coastguard Workerclass EnumAction(argparse.Action):
77*7594170eSAndroid Build Coastguard Worker  """Parses command line options into Enum types."""
78*7594170eSAndroid Build Coastguard Worker
79*7594170eSAndroid Build Coastguard Worker  def __init__(self, **kwargs):
80*7594170eSAndroid Build Coastguard Worker    enum_type = kwargs.pop("type", None)
81*7594170eSAndroid Build Coastguard Worker    kwargs.setdefault("choices", list(e.name for e in enum_type))
82*7594170eSAndroid Build Coastguard Worker    super(EnumAction, self).__init__(**kwargs)
83*7594170eSAndroid Build Coastguard Worker    self._enum = enum_type
84*7594170eSAndroid Build Coastguard Worker
85*7594170eSAndroid Build Coastguard Worker  def __call__(self, parser, namespace, values, option_string=None):
86*7594170eSAndroid Build Coastguard Worker    value = self._enum[values]
87*7594170eSAndroid Build Coastguard Worker    setattr(namespace, self.dest, value)
88*7594170eSAndroid Build Coastguard Worker
89*7594170eSAndroid Build Coastguard Worker
90*7594170eSAndroid Build Coastguard Workerclass ArtifactType(enum.Enum):
91*7594170eSAndroid Build Coastguard Worker  AUTO_INFER_FROM_SUFFIX = 0
92*7594170eSAndroid Build Coastguard Worker  CC_OBJECT = 1
93*7594170eSAndroid Build Coastguard Worker  CC_SHARED_LIBRARY = 2
94*7594170eSAndroid Build Coastguard Worker  CC_OBJECT_WITH_DEBUG_SYMBOLS = 3
95*7594170eSAndroid Build Coastguard Worker  OTHER = 99
96*7594170eSAndroid Build Coastguard Worker
97*7594170eSAndroid Build Coastguard Worker
98*7594170eSAndroid Build Coastguard WorkerFILE_TYPE_CHOICES = {
99*7594170eSAndroid Build Coastguard Worker    "auto": ArtifactType.AUTO_INFER_FROM_SUFFIX,
100*7594170eSAndroid Build Coastguard Worker    "object": ArtifactType.CC_OBJECT,
101*7594170eSAndroid Build Coastguard Worker    "object_with_debug_symbols": ArtifactType.CC_OBJECT_WITH_DEBUG_SYMBOLS,
102*7594170eSAndroid Build Coastguard Worker    "shared_library": ArtifactType.CC_SHARED_LIBRARY,
103*7594170eSAndroid Build Coastguard Worker}
104*7594170eSAndroid Build Coastguard Worker
105*7594170eSAndroid Build Coastguard Worker
106*7594170eSAndroid Build Coastguard Workerdef _artifact_type(file_path):
107*7594170eSAndroid Build Coastguard Worker  ext = file_path.suffix
108*7594170eSAndroid Build Coastguard Worker  if ext in [".o", ".a"]:
109*7594170eSAndroid Build Coastguard Worker    return ArtifactType.CC_OBJECT
110*7594170eSAndroid Build Coastguard Worker  elif ext == ".so":
111*7594170eSAndroid Build Coastguard Worker    return ArtifactType.CC_SHARED_LIBRARY
112*7594170eSAndroid Build Coastguard Worker  else:
113*7594170eSAndroid Build Coastguard Worker    return ArtifactType.OTHER
114*7594170eSAndroid Build Coastguard Worker
115*7594170eSAndroid Build Coastguard Worker
116*7594170eSAndroid Build Coastguard Worker# TODO(usta) use libdiff
117*7594170eSAndroid Build Coastguard Workerdef literal_diff(left_path: pathlib.Path,
118*7594170eSAndroid Build Coastguard Worker                 right_path: pathlib.Path) -> list[str]:
119*7594170eSAndroid Build Coastguard Worker  return subprocess.run(
120*7594170eSAndroid Build Coastguard Worker      ["diff", str(left_path), str(right_path)],
121*7594170eSAndroid Build Coastguard Worker      check=False,
122*7594170eSAndroid Build Coastguard Worker      capture_output=True,
123*7594170eSAndroid Build Coastguard Worker      encoding="utf-8").stdout.splitlines()
124*7594170eSAndroid Build Coastguard Worker
125*7594170eSAndroid Build Coastguard Worker
126*7594170eSAndroid Build Coastguard Worker@functools.cache
127*7594170eSAndroid Build Coastguard Workerdef _diff_fns(artifact_type: ArtifactType,
128*7594170eSAndroid Build Coastguard Worker              level: DiffLevel) -> list[DiffFunction]:
129*7594170eSAndroid Build Coastguard Worker  fns = []
130*7594170eSAndroid Build Coastguard Worker
131*7594170eSAndroid Build Coastguard Worker  if artifact_type in [
132*7594170eSAndroid Build Coastguard Worker      ArtifactType.CC_OBJECT, ArtifactType.CC_OBJECT_WITH_DEBUG_SYMBOLS
133*7594170eSAndroid Build Coastguard Worker  ]:
134*7594170eSAndroid Build Coastguard Worker    fns.append(clangcompile.nm_differences)
135*7594170eSAndroid Build Coastguard Worker    if level >= DiffLevel.WARNING:
136*7594170eSAndroid Build Coastguard Worker      fns.append(clangcompile.elf_differences)
137*7594170eSAndroid Build Coastguard Worker      if artifact_type == ArtifactType.CC_OBJECT_WITH_DEBUG_SYMBOLS:
138*7594170eSAndroid Build Coastguard Worker        fns.append(clangcompile.bloaty_differences_compileunits)
139*7594170eSAndroid Build Coastguard Worker      else:
140*7594170eSAndroid Build Coastguard Worker        fns.append(clangcompile.bloaty_differences)
141*7594170eSAndroid Build Coastguard Worker  else:
142*7594170eSAndroid Build Coastguard Worker    fns.append(literal_diff)
143*7594170eSAndroid Build Coastguard Worker
144*7594170eSAndroid Build Coastguard Worker  return fns
145*7594170eSAndroid Build Coastguard Worker
146*7594170eSAndroid Build Coastguard Worker
147*7594170eSAndroid Build Coastguard Workerdef collect_commands_bazel(expr: str, config: str, mnemonic: str, *args):
148*7594170eSAndroid Build Coastguard Worker  bazel_tool_path = pathlib.Path("build/bazel/bin/bazel").resolve().absolute()
149*7594170eSAndroid Build Coastguard Worker  bazel_proc = subprocess.run(
150*7594170eSAndroid Build Coastguard Worker      [
151*7594170eSAndroid Build Coastguard Worker          bazel_tool_path,
152*7594170eSAndroid Build Coastguard Worker          "aquery",
153*7594170eSAndroid Build Coastguard Worker          "--curses=no",
154*7594170eSAndroid Build Coastguard Worker          "--config=bp2build",
155*7594170eSAndroid Build Coastguard Worker          "--output=jsonproto",
156*7594170eSAndroid Build Coastguard Worker          f"--config={config}",
157*7594170eSAndroid Build Coastguard Worker          *args,
158*7594170eSAndroid Build Coastguard Worker          f"{expr}",
159*7594170eSAndroid Build Coastguard Worker      ],
160*7594170eSAndroid Build Coastguard Worker      capture_output=True,
161*7594170eSAndroid Build Coastguard Worker      encoding="utf-8",
162*7594170eSAndroid Build Coastguard Worker  )
163*7594170eSAndroid Build Coastguard Worker  print(bazel_proc.stderr)
164*7594170eSAndroid Build Coastguard Worker  actions_json = json.loads(bazel_proc.stdout)
165*7594170eSAndroid Build Coastguard Worker  return [a for a in actions_json["actions"] if a["mnemonic"] == mnemonic]
166*7594170eSAndroid Build Coastguard Worker
167*7594170eSAndroid Build Coastguard Worker
168*7594170eSAndroid Build Coastguard Workerdef collect_commands_ninja(ninja_file_path: pathlib.Path,
169*7594170eSAndroid Build Coastguard Worker                           output_file_path: pathlib.Path,
170*7594170eSAndroid Build Coastguard Worker                           ninja_tool_path: pathlib.Path) -> list[str]:
171*7594170eSAndroid Build Coastguard Worker  """Returns a list of all command lines required to build the file at given
172*7594170eSAndroid Build Coastguard Worker
173*7594170eSAndroid Build Coastguard Worker  output_file_path_string, as described by the ninja file present at
174*7594170eSAndroid Build Coastguard Worker  ninja_file_path_string.
175*7594170eSAndroid Build Coastguard Worker  """
176*7594170eSAndroid Build Coastguard Worker
177*7594170eSAndroid Build Coastguard Worker  result = subprocess.check_output([
178*7594170eSAndroid Build Coastguard Worker      str(ninja_tool_path), "-f", ninja_file_path, "-t", "commands",
179*7594170eSAndroid Build Coastguard Worker      str(output_file_path)
180*7594170eSAndroid Build Coastguard Worker  ]).decode("utf-8")
181*7594170eSAndroid Build Coastguard Worker  return result.splitlines()
182*7594170eSAndroid Build Coastguard Worker
183*7594170eSAndroid Build Coastguard Worker
184*7594170eSAndroid Build Coastguard Workerdef collect_commands(ninja_file_path: pathlib.Path,
185*7594170eSAndroid Build Coastguard Worker                     output_file_path: pathlib.Path) -> list[str]:
186*7594170eSAndroid Build Coastguard Worker  ninja_tool_path = pathlib.Path(
187*7594170eSAndroid Build Coastguard Worker      "prebuilts/build-tools/linux-x86/bin/ninja").resolve()
188*7594170eSAndroid Build Coastguard Worker  wd = os.getcwd()
189*7594170eSAndroid Build Coastguard Worker  try:
190*7594170eSAndroid Build Coastguard Worker    os.chdir(ninja_file_path.parent.absolute())
191*7594170eSAndroid Build Coastguard Worker    return collect_commands_ninja(
192*7594170eSAndroid Build Coastguard Worker        ninja_file_path.name,
193*7594170eSAndroid Build Coastguard Worker        output_file_path,
194*7594170eSAndroid Build Coastguard Worker        ninja_tool_path,
195*7594170eSAndroid Build Coastguard Worker    )
196*7594170eSAndroid Build Coastguard Worker  except Exception as e:
197*7594170eSAndroid Build Coastguard Worker    raise e
198*7594170eSAndroid Build Coastguard Worker  finally:
199*7594170eSAndroid Build Coastguard Worker    os.chdir(wd)
200*7594170eSAndroid Build Coastguard Worker
201*7594170eSAndroid Build Coastguard Worker
202*7594170eSAndroid Build Coastguard Workerdef file_differences(
203*7594170eSAndroid Build Coastguard Worker    left_path: pathlib.Path,
204*7594170eSAndroid Build Coastguard Worker    right_path: pathlib.Path,
205*7594170eSAndroid Build Coastguard Worker    level=DiffLevel.SEVERE,
206*7594170eSAndroid Build Coastguard Worker    file_type=ArtifactType.AUTO_INFER_FROM_SUFFIX) -> list[str]:
207*7594170eSAndroid Build Coastguard Worker  """Returns differences between the two given files.
208*7594170eSAndroid Build Coastguard Worker
209*7594170eSAndroid Build Coastguard Worker  Returns the empty list if these files are deemed "similar enough".
210*7594170eSAndroid Build Coastguard Worker  """
211*7594170eSAndroid Build Coastguard Worker
212*7594170eSAndroid Build Coastguard Worker  errors = []
213*7594170eSAndroid Build Coastguard Worker  if not left_path.is_file():
214*7594170eSAndroid Build Coastguard Worker    errors += ["%s does not exist" % left_path]
215*7594170eSAndroid Build Coastguard Worker  if not right_path.is_file():
216*7594170eSAndroid Build Coastguard Worker    errors += ["%s does not exist" % right_path]
217*7594170eSAndroid Build Coastguard Worker  if errors:
218*7594170eSAndroid Build Coastguard Worker    return errors
219*7594170eSAndroid Build Coastguard Worker
220*7594170eSAndroid Build Coastguard Worker  if file_type is ArtifactType.AUTO_INFER_FROM_SUFFIX:
221*7594170eSAndroid Build Coastguard Worker    file_type = _artifact_type(left_path)
222*7594170eSAndroid Build Coastguard Worker    right_type = _artifact_type(right_path)
223*7594170eSAndroid Build Coastguard Worker    if file_type != right_type:
224*7594170eSAndroid Build Coastguard Worker      errors += ["file types differ: %s and %s" % (file_type, right_type)]
225*7594170eSAndroid Build Coastguard Worker      return errors
226*7594170eSAndroid Build Coastguard Worker
227*7594170eSAndroid Build Coastguard Worker  for fn in _diff_fns(file_type, level):
228*7594170eSAndroid Build Coastguard Worker    errors += fn(left_path, right_path)
229*7594170eSAndroid Build Coastguard Worker
230*7594170eSAndroid Build Coastguard Worker  return errors
231*7594170eSAndroid Build Coastguard Worker
232*7594170eSAndroid Build Coastguard Worker
233*7594170eSAndroid Build Coastguard Workerdef parse_collection_info(info_file_path: pathlib.Path):
234*7594170eSAndroid Build Coastguard Worker  """Parses the collection info file at the given path and returns details."""
235*7594170eSAndroid Build Coastguard Worker  if not info_file_path.is_file():
236*7594170eSAndroid Build Coastguard Worker    raise Exception("Expected file %s was not found. " % info_file_path +
237*7594170eSAndroid Build Coastguard Worker                    "Did you run collect.py for this directory?")
238*7594170eSAndroid Build Coastguard Worker
239*7594170eSAndroid Build Coastguard Worker  info_contents = info_file_path.read_text().splitlines()
240*7594170eSAndroid Build Coastguard Worker  ninja_path = pathlib.Path(info_contents[0])
241*7594170eSAndroid Build Coastguard Worker  target_file = None
242*7594170eSAndroid Build Coastguard Worker
243*7594170eSAndroid Build Coastguard Worker  if len(info_contents) > 1 and info_contents[1]:
244*7594170eSAndroid Build Coastguard Worker    target_file = info_contents[1]
245*7594170eSAndroid Build Coastguard Worker
246*7594170eSAndroid Build Coastguard Worker  return ninja_path, target_file
247*7594170eSAndroid Build Coastguard Worker
248*7594170eSAndroid Build Coastguard Worker
249*7594170eSAndroid Build Coastguard Worker# Pattern to parse out env-setting command prefix, for example:
250*7594170eSAndroid Build Coastguard Worker#
251*7594170eSAndroid Build Coastguard Worker# FOO=BAR KEY=VALUE {main_command_args}
252*7594170eSAndroid Build Coastguard Workerenv_set_prefix_pattern = re.compile("^(( )*([^ =]+=[^ =]+)( )*)+(.*)$")
253*7594170eSAndroid Build Coastguard Worker
254*7594170eSAndroid Build Coastguard Worker# Pattern to parse out command prefixes which cd into the execroot and
255*7594170eSAndroid Build Coastguard Worker# then remove the old output. For example:
256*7594170eSAndroid Build Coastguard Worker#
257*7594170eSAndroid Build Coastguard Worker# cd path/to/execroot && rm old_output && {main_command}
258*7594170eSAndroid Build Coastguard Workercd_rm_prefix_pattern = re.compile("^cd [^&]* &&( )+rm [^&]* && (.*)$")
259*7594170eSAndroid Build Coastguard Worker
260*7594170eSAndroid Build Coastguard Worker# Pattern to parse out any trailing comment suffix. For example:
261*7594170eSAndroid Build Coastguard Worker#
262*7594170eSAndroid Build Coastguard Worker# {main_command} # This comment should be removed.
263*7594170eSAndroid Build Coastguard Workercomment_suffix_pattern = re.compile("(.*) # .*")
264*7594170eSAndroid Build Coastguard Worker
265*7594170eSAndroid Build Coastguard Worker
266*7594170eSAndroid Build Coastguard Workerdef _remove_rbe_tokens(tokens, tool_endings):
267*7594170eSAndroid Build Coastguard Worker  for i in range(len(tokens)):
268*7594170eSAndroid Build Coastguard Worker    for ending in tool_endings:
269*7594170eSAndroid Build Coastguard Worker      if tokens[i].endswith(ending):
270*7594170eSAndroid Build Coastguard Worker        return tokens[i:]
271*7594170eSAndroid Build Coastguard Worker  return None
272*7594170eSAndroid Build Coastguard Worker
273*7594170eSAndroid Build Coastguard Worker
274*7594170eSAndroid Build Coastguard Workerdef rich_command_info(raw_command):
275*7594170eSAndroid Build Coastguard Worker  """Returns a command info object describing the raw command string."""
276*7594170eSAndroid Build Coastguard Worker  cmd = raw_command.strip()
277*7594170eSAndroid Build Coastguard Worker  # Remove things unrelated to the core command.
278*7594170eSAndroid Build Coastguard Worker  m = env_set_prefix_pattern.fullmatch(cmd)
279*7594170eSAndroid Build Coastguard Worker  if m is not None:
280*7594170eSAndroid Build Coastguard Worker    cmd = m.group(5)
281*7594170eSAndroid Build Coastguard Worker  m = cd_rm_prefix_pattern.fullmatch(cmd)
282*7594170eSAndroid Build Coastguard Worker  if m is not None:
283*7594170eSAndroid Build Coastguard Worker    cmd = m.group(2)
284*7594170eSAndroid Build Coastguard Worker  m = comment_suffix_pattern.fullmatch(cmd)
285*7594170eSAndroid Build Coastguard Worker  if m is not None:
286*7594170eSAndroid Build Coastguard Worker    cmd = m.group(1)
287*7594170eSAndroid Build Coastguard Worker  tokens = cmd.split()
288*7594170eSAndroid Build Coastguard Worker  tokens_without_rbe = _remove_rbe_tokens(tokens, ["clang", "clang++"])
289*7594170eSAndroid Build Coastguard Worker  if tokens_without_rbe:
290*7594170eSAndroid Build Coastguard Worker    tokens = tokens_without_rbe
291*7594170eSAndroid Build Coastguard Worker  tool = tokens[0]
292*7594170eSAndroid Build Coastguard Worker  args = tokens[1:]
293*7594170eSAndroid Build Coastguard Worker
294*7594170eSAndroid Build Coastguard Worker  if tool.endswith("clang") or tool.endswith("clang++"):
295*7594170eSAndroid Build Coastguard Worker    # TODO(cparsons): Disambiguate between clang compile and other clang
296*7594170eSAndroid Build Coastguard Worker    # commands.
297*7594170eSAndroid Build Coastguard Worker    return clangcompile.ClangCompileInfo(tool=tool, args=args)
298*7594170eSAndroid Build Coastguard Worker  else:
299*7594170eSAndroid Build Coastguard Worker    return commands.CommandInfo(tool=tool, args=args)
300*7594170eSAndroid Build Coastguard Worker
301*7594170eSAndroid Build Coastguard Worker
302*7594170eSAndroid Build Coastguard Workerdef main():
303*7594170eSAndroid Build Coastguard Worker  parser = argparse.ArgumentParser(description="")
304*7594170eSAndroid Build Coastguard Worker  parser.add_argument(
305*7594170eSAndroid Build Coastguard Worker      "--level",
306*7594170eSAndroid Build Coastguard Worker      action=EnumAction,
307*7594170eSAndroid Build Coastguard Worker      default=DiffLevel.SEVERE,
308*7594170eSAndroid Build Coastguard Worker      type=DiffLevel,
309*7594170eSAndroid Build Coastguard Worker      help="the level of differences to be considered." +
310*7594170eSAndroid Build Coastguard Worker      "Diffs below the specified level are ignored.")
311*7594170eSAndroid Build Coastguard Worker  parser.add_argument(
312*7594170eSAndroid Build Coastguard Worker      "--verbose",
313*7594170eSAndroid Build Coastguard Worker      "-v",
314*7594170eSAndroid Build Coastguard Worker      action=argparse.BooleanOptionalAction,
315*7594170eSAndroid Build Coastguard Worker      default=False,
316*7594170eSAndroid Build Coastguard Worker      help="log verbosely.")
317*7594170eSAndroid Build Coastguard Worker  parser.add_argument(
318*7594170eSAndroid Build Coastguard Worker      "left_dir",
319*7594170eSAndroid Build Coastguard Worker      help="the 'left' directory to compare build outputs " +
320*7594170eSAndroid Build Coastguard Worker      "from. This must be the target of an invocation of collect.py.")
321*7594170eSAndroid Build Coastguard Worker  parser.add_argument(
322*7594170eSAndroid Build Coastguard Worker      "--left_file",
323*7594170eSAndroid Build Coastguard Worker      "-l",
324*7594170eSAndroid Build Coastguard Worker      dest="left_file",
325*7594170eSAndroid Build Coastguard Worker      default=None,
326*7594170eSAndroid Build Coastguard Worker      help="the output file (relative to execution root) for " +
327*7594170eSAndroid Build Coastguard Worker      "the 'left' build invocation.")
328*7594170eSAndroid Build Coastguard Worker  parser.add_argument(
329*7594170eSAndroid Build Coastguard Worker      "right_dir",
330*7594170eSAndroid Build Coastguard Worker      help="the 'right' directory to compare build outputs " +
331*7594170eSAndroid Build Coastguard Worker      "from. This must be the target of an invocation of collect.py.")
332*7594170eSAndroid Build Coastguard Worker  parser.add_argument(
333*7594170eSAndroid Build Coastguard Worker      "--right_file",
334*7594170eSAndroid Build Coastguard Worker      "-r",
335*7594170eSAndroid Build Coastguard Worker      dest="right_file",
336*7594170eSAndroid Build Coastguard Worker      default=None,
337*7594170eSAndroid Build Coastguard Worker      help="the output file (relative to execution root) " +
338*7594170eSAndroid Build Coastguard Worker      "for the 'right' build invocation.")
339*7594170eSAndroid Build Coastguard Worker  parser.add_argument(
340*7594170eSAndroid Build Coastguard Worker      "--file_type",
341*7594170eSAndroid Build Coastguard Worker      dest="file_type",
342*7594170eSAndroid Build Coastguard Worker      default="auto",
343*7594170eSAndroid Build Coastguard Worker      choices=FILE_TYPE_CHOICES.keys(),
344*7594170eSAndroid Build Coastguard Worker      help="the type of file being diffed (overrides automatic " +
345*7594170eSAndroid Build Coastguard Worker      "filetype resolution)")
346*7594170eSAndroid Build Coastguard Worker  parser.add_argument(
347*7594170eSAndroid Build Coastguard Worker      "--allow_missing_file",
348*7594170eSAndroid Build Coastguard Worker      action=argparse.BooleanOptionalAction,
349*7594170eSAndroid Build Coastguard Worker      default=False,
350*7594170eSAndroid Build Coastguard Worker      help="allow a missing output file; this is useful to " +
351*7594170eSAndroid Build Coastguard Worker      "compare actions even in the absence of an output file.")
352*7594170eSAndroid Build Coastguard Worker  args = parser.parse_args()
353*7594170eSAndroid Build Coastguard Worker
354*7594170eSAndroid Build Coastguard Worker  level = args.level
355*7594170eSAndroid Build Coastguard Worker  left_diffinfo = pathlib.Path(args.left_dir).joinpath(COLLECTION_INFO_FILENAME)
356*7594170eSAndroid Build Coastguard Worker  right_diffinfo = pathlib.Path(
357*7594170eSAndroid Build Coastguard Worker      args.right_dir).joinpath(COLLECTION_INFO_FILENAME)
358*7594170eSAndroid Build Coastguard Worker
359*7594170eSAndroid Build Coastguard Worker  left_ninja_name, left_file = parse_collection_info(left_diffinfo)
360*7594170eSAndroid Build Coastguard Worker  right_ninja_name, right_file = parse_collection_info(right_diffinfo)
361*7594170eSAndroid Build Coastguard Worker  if args.left_file:
362*7594170eSAndroid Build Coastguard Worker    left_file = pathlib.Path(args.left_file)
363*7594170eSAndroid Build Coastguard Worker  if args.right_file:
364*7594170eSAndroid Build Coastguard Worker    right_file = pathlib.Path(args.right_file)
365*7594170eSAndroid Build Coastguard Worker
366*7594170eSAndroid Build Coastguard Worker  if left_file is None:
367*7594170eSAndroid Build Coastguard Worker    raise Exception("No left file specified. Either run collect.py with a " +
368*7594170eSAndroid Build Coastguard Worker                    "target file, or specify --left_file.")
369*7594170eSAndroid Build Coastguard Worker  if right_file is None:
370*7594170eSAndroid Build Coastguard Worker    raise Exception("No right file specified. Either run collect.py with a " +
371*7594170eSAndroid Build Coastguard Worker                    "target file, or specify --right_file.")
372*7594170eSAndroid Build Coastguard Worker
373*7594170eSAndroid Build Coastguard Worker  left_path = pathlib.Path(args.left_dir).joinpath(left_file)
374*7594170eSAndroid Build Coastguard Worker  right_path = pathlib.Path(args.right_dir).joinpath(right_file)
375*7594170eSAndroid Build Coastguard Worker  if not args.allow_missing_file:
376*7594170eSAndroid Build Coastguard Worker    if not left_path.is_file():
377*7594170eSAndroid Build Coastguard Worker      raise RuntimeError("Expected file %s was not found. " % left_path)
378*7594170eSAndroid Build Coastguard Worker    if not right_path.is_file():
379*7594170eSAndroid Build Coastguard Worker      raise RuntimeError("Expected file %s was not found. " % right_path)
380*7594170eSAndroid Build Coastguard Worker
381*7594170eSAndroid Build Coastguard Worker  file_diff_errors = file_differences(left_path, right_path, level,
382*7594170eSAndroid Build Coastguard Worker                                      FILE_TYPE_CHOICES[args.file_type])
383*7594170eSAndroid Build Coastguard Worker
384*7594170eSAndroid Build Coastguard Worker  if file_diff_errors:
385*7594170eSAndroid Build Coastguard Worker    for err in file_diff_errors:
386*7594170eSAndroid Build Coastguard Worker      print(err)
387*7594170eSAndroid Build Coastguard Worker    if args.verbose:
388*7594170eSAndroid Build Coastguard Worker      left_ninja_path = pathlib.Path(args.left_dir).joinpath(left_ninja_name)
389*7594170eSAndroid Build Coastguard Worker      left_commands = collect_commands(left_ninja_path, left_file)
390*7594170eSAndroid Build Coastguard Worker      left_command_info = rich_command_info(left_commands[-1])
391*7594170eSAndroid Build Coastguard Worker      right_ninja_path = pathlib.Path(args.right_dir).joinpath(right_ninja_name)
392*7594170eSAndroid Build Coastguard Worker      right_commands = collect_commands(right_ninja_path, right_file)
393*7594170eSAndroid Build Coastguard Worker      right_command_info = rich_command_info(right_commands[-1])
394*7594170eSAndroid Build Coastguard Worker      print("======== ACTION COMPARISON: ========")
395*7594170eSAndroid Build Coastguard Worker      print("=== LEFT ONLY:\n")
396*7594170eSAndroid Build Coastguard Worker      print(left_command_info.compare(right_command_info))
397*7594170eSAndroid Build Coastguard Worker      print()
398*7594170eSAndroid Build Coastguard Worker      print("=== RIGHT ONLY:\n")
399*7594170eSAndroid Build Coastguard Worker      print(right_command_info.compare(left_command_info))
400*7594170eSAndroid Build Coastguard Worker      print()
401*7594170eSAndroid Build Coastguard Worker    sys.exit(1)
402*7594170eSAndroid Build Coastguard Worker  else:
403*7594170eSAndroid Build Coastguard Worker    print(f"{left_file} matches\n{right_file}")
404*7594170eSAndroid Build Coastguard Worker  sys.exit(0)
405*7594170eSAndroid Build Coastguard Worker
406*7594170eSAndroid Build Coastguard Worker
407*7594170eSAndroid Build Coastguard Workerif __name__ == "__main__":
408*7594170eSAndroid Build Coastguard Worker  main()
409