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