| #!/usr/bin/env fuchsia-vendored-python |
| # Copyright 2022 The Fuchsia Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| """C++ compilation commands and attributes. |
| """ |
| |
| import argparse |
| import dataclasses |
| import enum |
| |
| import cl_utils |
| |
| from pathlib import Path |
| from typing import AbstractSet, Any, Iterable, Optional, Sequence, Tuple |
| |
| |
| def _remove_suffix(text: str, suffix: str) -> str: |
| """string.removesuffix is in Python 3.9+""" |
| if text.endswith(suffix): |
| return text[: -len(suffix)] |
| return text |
| |
| |
| def _cxx_command_scanner() -> Tuple[argparse.ArgumentParser, Sequence[str]]: |
| parser = argparse.ArgumentParser( |
| description="Detects C++ compilation attributes (clang, gcc)", |
| argument_default=[], |
| allow_abbrev=False, |
| add_help=False, |
| ) |
| |
| single_dash_longopts = [] |
| |
| def _add_single_dash_longopt_argument( |
| name: str, *args: Any, **kwargs: Any |
| ) -> None: |
| """Add a single-dash option that will be internally treated as a |
| double-dash long-option to avoid unwanted prefix-matching behavior. |
| See _interpret_single_dash_longopts(). |
| |
| This is suitable for non-fused single-dash options like '-o' |
| (by preventing '-other' from matching '-o'), and suitable for |
| '-shared' (by preventing '-s' from prefix-matching '-shared'), |
| but not suitable for fused options like '-Lpath/to/dir' |
| and '-Wl...' which require special handling. |
| """ |
| parser.add_argument("-" + name, *args, **kwargs) |
| single_dash_longopts.append(name) |
| |
| _add_single_dash_longopt_argument( # do not match -other |
| "-o", |
| type=Path, |
| dest="output", |
| default=None, |
| metavar="FILE", |
| help="compiler output", |
| required=True, |
| ) |
| parser.add_argument( |
| "-L", |
| type=Path, |
| dest="libdirs", |
| action="append", |
| default=[], |
| metavar="DIR", |
| help="linking search paths", |
| ) |
| parser.add_argument( |
| "--sysroot", |
| type=Path, |
| default=None, |
| help="compiler sysroot", |
| ) |
| parser.add_argument( |
| "--target", |
| type=str, |
| default="", |
| help="target platform", |
| ) |
| _add_single_dash_longopt_argument( |
| "-shared", |
| default=False, |
| action="store_true", |
| help="output is linked shared", |
| ) |
| parser.add_argument( |
| "-fprofile-generate", |
| dest="profile_generate", |
| type=Path, |
| default=None, |
| help="generate profiling instrumented code (named directory)", |
| metavar="DIR", |
| ) |
| parser.add_argument( |
| "-fprofile-instr-generate", |
| dest="profile_instr_generate", |
| type=Path, |
| default=None, |
| help="generate profiling instrumented code (named file)", |
| metavar="FILE", |
| ) |
| parser.add_argument( |
| "-fprofile-list", |
| type=Path, |
| dest="profile_list", |
| default=None, |
| metavar="FILE", |
| help="profile list", |
| ) |
| parser.add_argument( |
| "-fcrash-diagnostics-dir", |
| type=Path, |
| dest="crash_diagnostics_dir", |
| default=None, |
| metavar="DIR", |
| help="additional directory where clang produces crash reports", |
| ) |
| parser.add_argument( |
| "-fsanitize", |
| type=str, |
| action=cl_utils.StringSetAdd, |
| dest="sanitize", |
| default=set(), |
| help="Compiler/linker sanitize options (cumulative)", |
| ) |
| parser.add_argument( |
| "-fno-sanitize", |
| type=str, |
| action=cl_utils.StringSetRemove, |
| dest="sanitize", |
| default=set(), |
| help="Disable compiler/linker sanitize options", |
| ) |
| parser.add_argument( |
| "-flto", |
| type=str, |
| dest="lto", |
| default=None, |
| nargs="?", # default: full LTO |
| help="Link time optimization", |
| ) |
| parser.add_argument( |
| "-fuse-ld", |
| type=str, |
| dest="use_ld", |
| default=None, |
| metavar="LD", |
| help="underyling linker tool", |
| ) |
| parser.add_argument( |
| "--save-temps", |
| action="store_true", |
| default=False, |
| help="Compiler saves intermediate files.", |
| ) |
| _add_single_dash_longopt_argument( |
| "-unwindlib", |
| type=str, |
| default=None, |
| metavar="LIB", |
| help="unwind library (e.g. libunwind)", |
| ) |
| _add_single_dash_longopt_argument( |
| "-rtlib", |
| type=str, |
| default=None, |
| metavar="LIB", |
| help="run-time library (e.g. compiler-rt)", |
| ) |
| _add_single_dash_longopt_argument( |
| "-static-libstdc++", |
| dest="static_libstdcxx", |
| default=False, |
| action="store_true", |
| help="Link with static C++ standard library", |
| ) |
| parser.add_argument( |
| "-W", |
| dest="driver_flags", |
| type=str, |
| default=[], |
| action="append", |
| metavar="FLAG", |
| help="Forwarded driver flags like -Wl,... -Wa,... -Wp,...", |
| ) |
| return parser, single_dash_longopts |
| |
| |
| _CXX_COMMAND_SCANNER, _SINGLE_DASH_LONGOPTS = _cxx_command_scanner() |
| |
| |
| class Compiler(enum.Enum): |
| OTHER = 0 |
| CLANG = 1 |
| GCC = 2 |
| |
| |
| class SourceLanguage(enum.Enum): |
| UNKNOWN = 0 |
| C = 1 |
| CXX = 2 # C++ |
| OBJC = 3 # Objective-C |
| ASM = 4 |
| |
| |
| @dataclasses.dataclass |
| class Source(object): |
| file: Path |
| dialect: SourceLanguage |
| |
| |
| def _compile_action_sources(command: Iterable[str]) -> Iterable[Source]: |
| for tok in command: |
| if tok.endswith(".c"): |
| yield Source(file=Path(tok), dialect=SourceLanguage.C) |
| if tok.endswith(".cc") or tok.endswith(".cxx") or tok.endswith(".cpp"): |
| yield Source(file=Path(tok), dialect=SourceLanguage.CXX) |
| if tok.endswith(".s") or tok.endswith(".S"): |
| yield Source(file=Path(tok), dialect=SourceLanguage.ASM) |
| if tok.endswith(".mm"): |
| yield Source(file=Path(tok), dialect=SourceLanguage.OBJC) |
| |
| |
| def _link_direct_inputs(command: Iterable[str]) -> Iterable[Path]: |
| for tok in command: |
| if any( |
| tok.endswith(ext) for ext in (".o", ".obj", ".so", ".a", ".dylib") |
| ): |
| yield Path(tok) |
| |
| |
| def _infer_dialect_from_sources(sources: Iterable[Source]) -> SourceLanguage: |
| # check by source file extension first |
| for s in sources: |
| return s.dialect |
| |
| return SourceLanguage.UNKNOWN |
| |
| |
| @dataclasses.dataclass |
| class CompilerTool(object): |
| tool: Path |
| type: Compiler |
| |
| |
| def _find_compiler_from_command(command: Iterable[str]) -> CompilerTool: |
| for tok in command: |
| if "clang" in tok: # matches clang++ |
| return CompilerTool(tool=Path(tok), type=Compiler.CLANG) |
| # 'g++' matches 'clang++', so this clause must come second: |
| if "gcc" in tok or "g++" in tok: |
| return CompilerTool(tool=Path(tok), type=Compiler.GCC) |
| raise LookupError(f"No clang or gcc in {command}") |
| |
| |
| def _c_preprocess_arg_parser() -> argparse.ArgumentParser: |
| """The sole purpose of this is to filter out preprocessing flags. |
| |
| We do not actually care about the parameters of these flags. |
| """ |
| parser = argparse.ArgumentParser( |
| description="Detects C-preprocessing attributes", |
| argument_default=[], # many of the options are repeatable |
| add_help=False, |
| ) |
| parser.add_argument( |
| "-D", |
| type=str, |
| dest="defines", |
| action="append", |
| default=[], |
| help="preprocessing defines", |
| ) |
| parser.add_argument( |
| "-I", |
| type=Path, |
| dest="includes", |
| action="append", |
| default=[], |
| metavar="DIR", |
| help="preprocessing include paths", |
| ) |
| parser.add_argument( |
| "-L", |
| type=Path, |
| dest="libdirs", |
| action="append", |
| default=[], |
| metavar="DIR", |
| help="linking search paths", |
| ) |
| parser.add_argument( |
| "-U", |
| type=str, |
| dest="undefines", |
| action="append", |
| default=[], |
| help="preprocessing undefines", |
| ) |
| parser.add_argument( |
| "-isystem", |
| type=Path, |
| action="append", |
| default=[], |
| metavar="DIR", |
| help="system include paths", |
| ) |
| parser.add_argument( |
| "--sysroot", |
| type=Path, |
| help="compiler sysroot", |
| ) |
| parser.add_argument( |
| "-stdlib", |
| type=str, |
| help="C++ standard library", |
| ) |
| parser.add_argument( |
| "-include", |
| type=Path, |
| action="append", |
| default=[], |
| metavar="FILE", |
| help="prepend include file", |
| ) |
| parser.add_argument( |
| "-M", |
| action="store_true", |
| default=False, |
| help="output make rule", |
| ) |
| parser.add_argument( |
| "-MM", |
| action="store_true", |
| default=False, |
| help="output make rule without system dirs", |
| ) |
| parser.add_argument( |
| "-MG", |
| action="store_true", |
| default=False, |
| help="tolerate missing generated headers", |
| ) |
| parser.add_argument( |
| "-MP", |
| action="store_true", |
| default=False, |
| help="deps include phony targets", |
| ) |
| parser.add_argument( |
| "-MD", |
| action="store_true", |
| default=False, |
| help="make dependencies", |
| ) |
| parser.add_argument( |
| "-MMD", |
| action="store_true", |
| default=False, |
| help="make dependencies, without system headers", |
| ) |
| parser.add_argument( |
| "-MF", |
| type=Path, |
| dest="depfile", |
| default=None, |
| help="name depfile", |
| metavar="DEPFILE", |
| ) |
| parser.add_argument( |
| "-MT", |
| type=Path, |
| help="rename dependency target", |
| ) |
| parser.add_argument( |
| "-MQ", |
| type=Path, |
| help="rename dependency target (quoted)", |
| ) |
| parser.add_argument( |
| "-undef", |
| action="store_true", |
| default=False, |
| help="no predefined macros", |
| ) |
| return parser |
| |
| |
| _C_PREPROCESS_ARG_PARSER = _c_preprocess_arg_parser() |
| |
| |
| def _linker_driver_arg_parser() -> argparse.ArgumentParser: |
| """Parse linker arguments that come from -Wl,""" |
| parser = argparse.ArgumentParser( |
| description="Parse linker flags", |
| argument_default=None, |
| add_help=False, |
| ) |
| parser.add_argument( |
| "-Map", # single-dash |
| dest="mapfile", |
| type=Path, |
| help="linker map file to write", |
| ) |
| parser.add_argument( |
| "--Map", # double-dash |
| dest="mapfile", |
| type=Path, |
| help="linker map file to write", |
| ) |
| parser.add_argument( |
| "--dependency-file", |
| dest="depfile", |
| type=Path, |
| help="dependency file to write", |
| ) |
| parser.add_argument( |
| "--just-symbols", |
| type=Path, |
| help="Use only the symbols of this", |
| ) |
| parser.add_argument( |
| "--retain-symbols-file", |
| type=Path, |
| help="file that lists symbols to retain", |
| ) |
| parser.add_argument( |
| "--version-script", |
| type=Path, |
| help="linking version script", |
| ) |
| return parser |
| |
| |
| _LINKER_DRIVER_PARSER = _linker_driver_arg_parser() |
| |
| # These are flags that are joined with their arguments with |
| # no separator (no '=' or space). |
| _CPP_FUSED_FLAGS = ["-I", "-D", "-L", "-U", "-isystem"] |
| |
| |
| def expand_forwarded_driver_flags(args: Iterable[str]) -> Iterable[str]: |
| """Split '-Wl,foo' into ['-W', 'l,foo'] for easier argparse-ing.""" |
| # This helps distinguish from other options that start with -W, like -Wall. |
| for arg in args: |
| if any(arg.startswith(prefix) for prefix in ("-Wp,", "-Wa,", "-Wl,")): |
| yield "-W" |
| yield arg[2:] |
| else: |
| yield arg |
| |
| |
| def _assign_explicit_flag_defaults(opts: Iterable[str]) -> Iterable[str]: |
| """Workarounds for argparse limitations. |
| |
| argparse doesn't have a way to accept both -flto and -flto=arg |
| because with nargs='?', the former will try to consume the following |
| argument. Instead we make the implicit default explicit just for |
| easy parsing. |
| |
| Default values are taken from `clang --help` documentation. |
| """ |
| for opt in opts: |
| if opt == "-flto": |
| yield "-flto=full" |
| elif opt == "-fprofile-generate": |
| yield "-fprofile-generate=." # directory |
| elif opt == "-fprofile-instr-generate": |
| yield "-fprofile-instr-generate=default.profraw" # file |
| else: |
| yield opt |
| |
| |
| def _interpret_single_dash_longopts( |
| single_dash_longopts: Sequence[str], opts: Iterable[str] |
| ) -> Iterable[str]: |
| """Workaround argparse limitation handling single-dash options. |
| |
| clang/gcc interpret many long options that only start with a single-dash. |
| Unfortunately, ArgumentParse.parse_known_args() uses prefix matching on |
| single-dash flags even with allow_abbrev=False, which means '-r' gets |
| mis-interpreted as '-rtlib'. |
| |
| See https://docs.python.org/3/library/argparse.html#partial-parsing |
| |
| To avoid this prefix matching behavior, we transform known options from |
| single-dash to double-dash, and add parser arguments using the double-dash |
| form only. |
| |
| Args: |
| single_dash_longopts: Set of '-foo' flags that will be interpreted as '--foo'. |
| opts: command-line arguments, which can contain some single-dash long-options. |
| |
| Yields: |
| transformed command-line arguments, with some double-dash substitutions. |
| """ |
| |
| def rewrite_opt(str: str) -> str: |
| for prefix in single_dash_longopts: |
| if str.startswith(prefix): |
| return "-" + str # interpret as double-dash |
| |
| return str |
| |
| return [rewrite_opt(opt) for opt in opts] |
| |
| |
| def _restore_single_dash_longopts( |
| single_dash_longopts: Sequence[str], opts: Iterable[str] |
| ) -> Iterable[str]: |
| """Reverse transformation of _interpret_single_dash_longopts(). |
| |
| This is useful for viewing the original form for testing. |
| """ |
| |
| def rewrite_opt(str: str) -> str: |
| for prefix in single_dash_longopts: |
| if opt.startswith("-" + prefix): # double-dashed |
| return opt[1:] # restore single-dash |
| |
| return opt |
| |
| for opt in opts: |
| yield rewrite_opt(opt) |
| |
| |
| class CxxAction(object): |
| """Attributes of C/C++ (or dialect) compilation command. |
| |
| Suitable for compilers: clang, gcc |
| """ |
| |
| def __init__(self, command: Sequence[str]): |
| self._command = command # keep a copy of the original command |
| |
| # Expand response files before parsing any flags. |
| rsp_files: list[Path] = [] |
| rsp_expanded_args = cl_utils.expand_response_files(command, rsp_files) |
| self._response_files = rsp_files |
| |
| # Expand some difficult-to-parse flags like -Wl... |
| argparseable_args = list( |
| expand_forwarded_driver_flags(rsp_expanded_args) |
| ) |
| |
| # Parse the full compiler command. |
| ( |
| self._attributes, |
| remaining_args, |
| ) = _CXX_COMMAND_SCANNER.parse_known_args( |
| list( |
| _interpret_single_dash_longopts( |
| _SINGLE_DASH_LONGOPTS, |
| _assign_explicit_flag_defaults(argparseable_args), |
| ) |
| ) |
| ) |
| self._compiler = _find_compiler_from_command(argparseable_args) |
| self._sources = list(_compile_action_sources(remaining_args)) |
| self._dialect = _infer_dialect_from_sources(self._sources) |
| self._linker_inputs = list(_link_direct_inputs(remaining_args)) |
| |
| # For testing purposes only, record uninterpreted args as they appeared |
| # before being transformed by _interpret_single_dash_longopts(). |
| self._uninterpreted_args = list( |
| _restore_single_dash_longopts(_SINGLE_DASH_LONGOPTS, remaining_args) |
| ) |
| |
| # Handle -W driver flags. |
| self._preprocessor_driver_flags = [] |
| self._assembler_driver_flags = [] |
| self._linker_driver_flags = [] |
| for driver_flag in self._attributes.driver_flags: |
| forwarded_flags = driver_flag[2:].split(",") |
| if driver_flag.startswith("p,"): |
| self._preprocessor_driver_flags.extend(forwarded_flags) |
| elif driver_flag.startswith("a,"): |
| self._assembler_driver_flags.extend(forwarded_flags) |
| elif driver_flag.startswith("l,"): |
| self._linker_driver_flags.extend(forwarded_flags) |
| |
| # Linker driver flags may contain response files, so expand them. |
| linker_rsp_files: list[Path] = [] |
| expanded_linker_driver_flags = list( |
| cl_utils.expand_response_files( |
| self.linker_driver_flags, linker_rsp_files |
| ) |
| ) |
| self._linker_response_files = linker_rsp_files |
| |
| ( |
| self._linker_attributes, |
| unused_linker_args, |
| ) = _LINKER_DRIVER_PARSER.parse_known_args(expanded_linker_driver_flags) |
| |
| # Infer the compiler command that would be used if preprocessing |
| # were done separately. |
| # Note: this version does not expand response files first, |
| # to minimize differences relative to the original command. |
| command_after_preprocessing = list( |
| cl_utils.expand_fused_flags(self._command, _CPP_FUSED_FLAGS) |
| ) |
| ( |
| self._cpp_attributes, |
| self._command_without_cpp_options, |
| ) = _C_PREPROCESS_ARG_PARSER.parse_known_args( |
| command_after_preprocessing |
| ) |
| |
| @property |
| def command(self) -> Sequence[str]: |
| return self._command |
| |
| @property |
| def output_file(self) -> Optional[Path]: |
| return self._attributes.output # usually this is the -o file |
| |
| @property |
| def crash_diagnostics_dir(self) -> Optional[Path]: |
| return self._attributes.crash_diagnostics_dir |
| |
| @property |
| def compiler(self) -> CompilerTool: |
| return self._compiler |
| |
| @property |
| def depfile(self) -> Optional[Path]: |
| return self._cpp_attributes.depfile |
| |
| @property |
| def target(self) -> str: |
| return self._attributes.target |
| |
| @property |
| def sysroot(self) -> Optional[Path]: |
| return self._attributes.sysroot |
| |
| @property |
| def sources(self) -> Sequence[Source]: |
| return self._sources |
| |
| @property |
| def shared(self) -> bool: |
| return self._attributes.shared |
| |
| @property |
| def libdirs(self) -> Sequence[Path]: |
| return self._attributes.libdirs |
| |
| @property |
| def uninterpreted_args(self) -> Sequence[str]: |
| return self._uninterpreted_args |
| |
| @property |
| def clang_linker_executable(self) -> str: # basename only |
| use_ld = self.use_ld or "lld" |
| if "windows" in self.target: |
| # Targeting Windows uses lld-link. |
| if use_ld != "lld": |
| raise ValueError( |
| f"Unexpected linker selected for Windows. Expected: lld, but got: {use_ld}" |
| ) |
| return use_ld + "-link" |
| else: |
| # For ld.bfd, ld.gold, ld.lld |
| return "ld." + use_ld |
| |
| @property |
| def linker_inputs(self) -> Sequence[Path]: |
| return self._linker_inputs |
| |
| def linker_inputs_from_flags(self) -> Iterable[Path]: |
| if self.linker_retain_symbols_file: |
| yield self.linker_retain_symbols_file |
| if self.linker_version_script: |
| yield self.linker_version_script |
| if self.linker_just_symbols: |
| yield self.linker_just_symbols |
| |
| @property |
| def response_files(self) -> list[Path]: |
| return self._response_files |
| |
| @property |
| def linker_response_files(self) -> Sequence[Path]: |
| # TODO(b/307418630): Remove this workaround after bug is fixed. |
| return self._linker_response_files |
| |
| @property |
| def preprocessor_driver_flags(self) -> Sequence[str]: |
| return self._preprocessor_driver_flags |
| |
| @property |
| def assembler_driver_flags(self) -> Sequence[str]: |
| return self._assembler_driver_flags |
| |
| @property |
| def linker_driver_flags(self) -> Sequence[str]: |
| return self._linker_driver_flags |
| |
| @property |
| def linker_depfile(self) -> Optional[Path]: |
| return self._linker_attributes.depfile |
| |
| @property |
| def linker_mapfile(self) -> Optional[Path]: |
| return self._linker_attributes.mapfile |
| |
| @property |
| def linker_just_symbols(self) -> Optional[Path]: |
| return self._linker_attributes.just_symbols |
| |
| @property |
| def linker_retain_symbols_file(self) -> Optional[Path]: |
| return self._linker_attributes.retain_symbols_file |
| |
| @property |
| def linker_version_script(self) -> Optional[Path]: |
| return self._linker_attributes.version_script |
| |
| @property |
| def rtlib(self) -> Optional[str]: |
| return self._attributes.rtlib |
| |
| @property |
| def static_libstdcxx(self) -> bool: |
| return self._attributes.static_libstdcxx |
| |
| @property |
| def save_temps(self) -> bool: |
| return self._attributes.save_temps |
| |
| @property |
| def compiler_is_clang(self) -> bool: |
| return self.compiler.type == Compiler.CLANG |
| |
| @property |
| def compiler_is_gcc(self) -> bool: |
| return self.compiler.type == Compiler.GCC |
| |
| @property |
| def dialect_is_c(self) -> bool: |
| return self._dialect == SourceLanguage.C |
| |
| @property |
| def dialect_is_cxx(self) -> bool: |
| return self._dialect == SourceLanguage.CXX |
| |
| @property |
| def sanitizers(self) -> AbstractSet[str]: |
| return self._attributes.sanitize |
| |
| @property |
| def using_asan(self) -> bool: |
| return "address" in self._attributes.sanitize |
| |
| @property |
| def using_ubsan(self) -> bool: |
| return "undefined" in self._attributes.sanitize |
| |
| @property |
| def profile_list(self) -> Optional[Path]: |
| return self._attributes.profile_list |
| |
| @property |
| def profile_generate(self) -> Optional[Path]: |
| return self._attributes.profile_generate |
| |
| @property |
| def profile_instr_generate(self) -> Optional[Path]: |
| return self._attributes.profile_instr_generate |
| |
| @property |
| def any_profile(self) -> bool: |
| return ( |
| self.profile_generate is not None |
| or self.profile_instr_generate is not None |
| ) |
| |
| @property |
| def lto(self) -> Optional[str]: |
| return self._attributes.lto |
| |
| @property |
| def use_ld(self) -> Optional[str]: |
| return self._attributes.use_ld |
| |
| @property |
| def unwindlib(self) -> Optional[str]: |
| return self._attributes.unwindlib |
| |
| @property |
| def preprocessed_suffix(self) -> str: |
| if self._dialect == SourceLanguage.CXX: |
| return ".ii" |
| else: |
| return ".i" |
| |
| @property |
| def preprocessed_output(self) -> Path: |
| """This is an explicitly named preprocessed output. |
| |
| Depending on the compiler, this may not necessarily match the implicit |
| name of the corresponding output from --save-temps. |
| """ |
| # replaces .o with .i or .ii |
| assert self.output_file is not None |
| return self.output_file.with_suffix(self.preprocessed_suffix) |
| |
| @property |
| def save_temps_output_stem(self) -> Path: |
| """This is the location stem of --save-temps output files (for clang). |
| |
| Note that ALL parent dirs and ALL extensions (like .cc.o) get removed. |
| |
| Returns: |
| A Path stem, to which extensions like .ii can be appended. |
| """ |
| assert self.output_file is not None |
| name = self.output_file.name |
| return Path(_remove_suffix(name, "".join(self.output_file.suffixes))) |
| |
| @property |
| def uses_macos_sdk(self) -> bool: |
| return str(self.sysroot).startswith("/Library/Developer/") |
| |
| # TODO: scan command for absolute paths (C++-specific) |
| |
| def input_files(self) -> Iterable[Path]: |
| """Files known to be inputs based on flags.""" |
| # Note: reclient already infers many C++ inputs in its own |
| # input processor, so it is not necessary to list them |
| # for remote actions (but it does not hurt). |
| for s in self.sources: |
| yield s.file |
| |
| # Support for -fprofile-list was added to re-client in b/220028444. |
| if self.profile_list: |
| yield self.profile_list |
| |
| def output_files(self) -> Iterable[Path]: |
| # Note: reclient already infers many C++ outputs in its own |
| # input processor, so it is not necessary to list them |
| # for remote actions (but it does not hurt). |
| if self.output_file: |
| yield self.output_file # This should be first, for naming purposes |
| |
| # TODO(b/302613832): remove this once upstream supports it |
| if self.save_temps: |
| stem = self.save_temps_output_stem |
| for suffix in (self.preprocessed_suffix, ".bc", ".s"): |
| yield stem.with_suffix(suffix) |
| |
| def output_dirs(self) -> Iterable[Path]: |
| # TODO(b/272865494): remove this once upstream supports it |
| if self.crash_diagnostics_dir: |
| yield self.crash_diagnostics_dir |
| |
| @property |
| def pdb(self) -> Optional[Path]: |
| if "windows" in self.target: |
| if any("/debug" in f for f in self.linker_driver_flags): |
| if self.output_file: |
| return self.output_file.with_suffix(".pdb") |
| return None |
| |
| def linker_output_files(self) -> Iterable[Path]: |
| if self.output_file: |
| yield self.output_file |
| if self.linker_depfile: |
| yield self.linker_depfile |
| if self.linker_mapfile: |
| yield self.linker_mapfile |
| if self.pdb: |
| yield self.pdb |
| |
| def _preprocess_only_command(self) -> Iterable[str]: |
| # replace the output with a preprocessor output |
| for tok in self._command: |
| if tok == str(self.output_file): |
| yield str(self.preprocessed_output) |
| elif tok == "--save-temps": # no need during preprocessing |
| pass |
| else: |
| # TODO: discard irrelevant flags, like linker flags |
| yield tok |
| |
| # -E tells the compiler to stop after preprocessing |
| yield "-E" |
| |
| # -fno-blocks works around in issue where preprocessing includes |
| # blocks-featured code when it is not wanted. |
| if self.compiler_is_clang: |
| yield "-fno-blocks" |
| |
| def _compile_with_preprocessed_input_command(self) -> Iterable[str]: |
| # replace the first named source file with the preprocessed output |
| used_preprocessed_input = False |
| for tok in self._command_without_cpp_options: |
| if ( |
| tok.endswith(".c") |
| or tok.endswith(".cc") |
| or tok.endswith(".cxx") |
| or tok.endswith(".cpp") |
| ): |
| if ( |
| used_preprocessed_input |
| ): # ignore other sources after the first |
| continue |
| yield str(self.preprocessed_output) |
| used_preprocessed_input = True |
| continue |
| |
| # everything else is kept in the compile command |
| yield tok |
| |
| def split_preprocessing(self) -> Tuple[Sequence[str], Sequence[str]]: |
| """Create separate preprocessing and compile commands. |
| |
| Returns: |
| C-preprocessing command, and |
| modified compile command using preprocessed input |
| """ |
| return ( |
| list(self._preprocess_only_command()), |
| list(self._compile_with_preprocessed_input_command()), |
| ) |
| |
| |
| # TODO: write a main() routine just for printing debug info about a compile |
| # command |