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