blob: 6d9568fd5aa494d7d7ec8c62ef1cd5393767ebad [file] [log] [blame]
#!/usr/bin/env python3
#
# Copyright (C) 2022 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License."""
"""Helpers pertaining to clang compile actions."""
import collections
from commands import CommandInfo
from commands import flag_repr
from commands import is_flag_starts_with
from commands import parse_flag_groups
class ClangCompileInfo(CommandInfo):
"""Contains information about a clang compile action commandline."""
def __init__(self, tool, args):
CommandInfo.__init__(self, tool, args)
flag_groups = parse_flag_groups(args, _custom_flag_group)
misc = []
i_includes = []
iquote_includes = []
isystem_includes = []
defines = []
warnings = []
file_flags = []
for g in flag_groups:
if is_flag_starts_with("D", g) or is_flag_starts_with("U", g):
defines += [g]
elif is_flag_starts_with("I", g):
i_includes += [g]
elif is_flag_starts_with("isystem", g):
isystem_includes += [g]
elif is_flag_starts_with("iquote", g):
iquote_includes += [g]
elif is_flag_starts_with("W", g) or is_flag_starts_with("w", g):
warnings += [g]
elif (is_flag_starts_with("MF", g) or is_flag_starts_with("o", g) or
_is_src_group(g)):
file_flags += [g]
else:
misc += [g]
self.misc_flags = sorted(misc, key=flag_repr)
self.i_includes = _process_includes(i_includes)
self.iquote_includes = _process_includes(iquote_includes)
self.isystem_includes = _process_includes(isystem_includes)
self.defines = _process_defines(defines)
self.warnings = warnings
self.file_flags = file_flags
def _str_for_field(self, field_name, values):
s = " " + field_name + ":\n"
for x in values:
s += " " + flag_repr(x) + "\n"
return s
def __str__(self):
s = "ClangCompileInfo:\n"
s += self._str_for_field("Includes (-I)", self.i_includes)
s += self._str_for_field("Includes (-iquote)", self.iquote_includes)
s += self._str_for_field("Includes (-isystem)", self.isystem_includes)
s += self._str_for_field("Defines", self.defines)
s += self._str_for_field("Warnings", self.warnings)
s += self._str_for_field("Files", self.file_flags)
s += self._str_for_field("Misc", self.misc_flags)
return s
def _is_src_group(x):
"""Returns true if the given flag group describes a source file."""
return isinstance(x, str) and x.endswith(".cpp")
def _custom_flag_group(x):
"""Identifies single-arg flag groups for clang compiles.
Returns a flag group if the given argument corresponds to a single-argument
flag group for clang compile. (For example, `-c` is a single-arg flag for
clang compiles, but may not be for other tools.)
See commands.parse_flag_groups documentation for signature details."""
if x.startswith("-I") and len(x) > 2:
return ("I", x[2:])
if x.startswith("-W") and len(x) > 2:
return (x)
elif x == "-c":
return x
return None
def _process_defines(defs):
"""Processes and returns deduplicated define flags from all define args."""
# TODO(cparsons): Determine and return effective defines (returning the last
# set value).
defines_by_var = collections.defaultdict(list)
for x in defs:
if isinstance(x, tuple):
var_name = x[0][2:]
else:
var_name = x[2:]
defines_by_var[var_name].append(x)
result = []
for k in sorted(defines_by_var):
d = defines_by_var[k]
for x in d:
result += [x]
return result
def _process_includes(includes):
# Drop genfiles directories; makes diffing easier.
result = []
for x in includes:
if isinstance(x, tuple):
if not x[1].startswith("bazel-out"):
result += [x]
else:
result += [x]
return result