| // Copyright 2018 The Goma Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "lib/gcc_flags.h" |
| |
| #include "absl/strings/ascii.h" |
| #include "absl/strings/match.h" |
| #include "absl/strings/str_join.h" |
| #include "absl/strings/str_split.h" |
| #include "base/filesystem.h" |
| #include "base/options.h" |
| #include "base/path.h" |
| #include "glog/logging.h" |
| #include "glog/stl_logging.h" |
| #include "lib/cmdline_parser.h" |
| #include "lib/compiler_flags.h" |
| #include "lib/file_helper.h" |
| #include "lib/flag_parser.h" |
| #include "lib/known_warning_options.h" |
| #include "lib/path_resolver.h" |
| #include "lib/path_util.h" |
| using std::string; |
| |
| namespace devtools_goma { |
| |
| /* static */ |
| string GCCFlags::GetCompilerName(absl::string_view arg) { |
| absl::string_view name = GetBasename(arg); |
| if (name.find("clang++") != string::npos) { |
| return "clang++"; |
| } |
| if (name.find("clang") != string::npos) { |
| return "clang"; |
| } |
| if (name.find("g++") != string::npos || name == "c++") { |
| return "g++"; |
| } |
| return "gcc"; |
| } |
| |
| // Return the key 'gcc' or 'g++' with architecture and version |
| // stripped from compiler_name. |
| string GCCFlags::compiler_name() const { |
| return GetCompilerName(compiler_name_); |
| } |
| |
| GCCFlags::GCCFlags(const std::vector<string>& args, const string& cwd) |
| : CxxFlags(args, cwd), |
| is_cplusplus_(false), |
| has_nostdinc_(false), |
| has_no_integrated_as_(false), |
| has_pipe_(false), |
| has_ffreestanding_(false), |
| has_fno_hosted_(false), |
| has_fno_sanitize_blacklist_(false), |
| has_fsyntax_only_(false), |
| has_wrapper_(false), |
| has_fplugin_(false), |
| is_precompiling_header_(false), |
| is_stdin_input_(false), |
| has_fmodules_(false), |
| has_fimplicit_module_maps_(false), |
| has_emit_module_(false) { |
| if (!CompilerFlags::ExpandPosixArgs(cwd, args, &expanded_args_, |
| &optional_input_filenames_)) { |
| Fail("Unable to expand args"); |
| return; |
| } |
| bool has_at_file = !optional_input_filenames_.empty(); |
| bool no_integrated_as = false; |
| bool fno_integrated_as = false; |
| bool ffreestanding = false; |
| bool fno_hosted = false; |
| bool fsyntax_only = false; |
| bool print_file_name = false; |
| |
| bool fmodules = false; |
| bool fno_implicit_module_maps = false; |
| std::vector<string> xclang_flags; |
| |
| FlagParser parser; |
| DefineFlags(&parser); |
| |
| FlagParser::Flag* flag_c = parser.AddBoolFlag("c"); |
| FlagParser::Flag* flag_S = parser.AddBoolFlag("S"); |
| FlagParser::Flag* flag_E = parser.AddBoolFlag("E"); |
| FlagParser::Flag* flag_M = parser.AddBoolFlag("M"); |
| FlagParser::Flag* flag_MM = parser.AddBoolFlag("MM"); |
| FlagParser::Flag* flag_MD = parser.AddBoolFlag("MD"); |
| FlagParser::Flag* flag_MMD = parser.AddBoolFlag("MMD"); |
| FlagParser::Flag* flag_g = parser.AddPrefixFlag("g"); |
| parser.AddBoolFlag("nostdinc")->SetSeenOutput(&has_nostdinc_); |
| parser.AddBoolFlag("nostdinc++")->SetOutput(&compiler_info_flags_); |
| parser.AddBoolFlag("nostdlibinc")->SetOutput(&compiler_info_flags_); |
| parser.AddBoolFlag("integrated-as")->SetOutput(&compiler_info_flags_); |
| parser.AddBoolFlag("no-integrated-as")->SetSeenOutput(&no_integrated_as); |
| parser.AddBoolFlag("fno-integrated-as")->SetSeenOutput(&fno_integrated_as); |
| parser.AddBoolFlag("pipe")->SetSeenOutput(&has_pipe_); |
| parser.AddBoolFlag("-pipe")->SetSeenOutput(&has_pipe_); |
| parser.AddBoolFlag("ffreestanding")->SetSeenOutput(&ffreestanding); |
| parser.AddBoolFlag("fno-hosted")->SetSeenOutput(&fno_hosted); |
| parser.AddBoolFlag("fsyntax-only")->SetSeenOutput(&fsyntax_only); |
| parser.AddBoolFlag("print-file-name")->SetSeenOutput(&print_file_name); |
| parser.AddBoolFlag("-print-file-name")->SetSeenOutput(&print_file_name); |
| FlagParser::Flag* flag_x = parser.AddFlag("x"); |
| FlagParser::Flag* flag_o = parser.AddFlag("o"); |
| FlagParser::Flag* flag_MF = parser.AddFlag("MF"); |
| FlagParser::Flag* flag_isysroot = parser.AddFlag("isysroot"); |
| // TODO: Consider split -fprofile-* flags? Some options take an extra |
| // arguement, other do not. Merging such kind of flags do not look good. |
| FlagParser::Flag* flag_fprofile = parser.AddPrefixFlag("fprofile-"); |
| FlagParser::Flag* flag_fprofile_sample_use = |
| parser.AddFlag("fprofile-sample-use"); |
| FlagParser::Flag* flag_fthinlto_index = |
| parser.AddPrefixFlag("fthinlto-index="); |
| |
| parser.AddFlag("wrapper")->SetSeenOutput(&has_wrapper_); |
| parser.AddPrefixFlag("fplugin=")->SetSeenOutput(&has_fplugin_); |
| |
| // -mllvm takes extra arg. |
| // ASAN uses -mllvm -asan-blacklist=$FILE |
| // TSAN uses -mllvm -tsan-blacklist=$FILE |
| std::vector<string> llvm_options; |
| parser.AddFlag("mllvm")->SetOutput(&llvm_options); |
| FlagParser::Flag* flag_fsanitize_blacklist = |
| parser.AddFlag("fsanitize-blacklist"); |
| FlagParser::Flag* flag_fsanitize = parser.AddFlag("fsanitize"); |
| flag_fsanitize->SetOutput(&compiler_info_flags_); |
| |
| parser.AddBoolFlag("fno-sanitize-blacklist") |
| ->SetSeenOutput(&has_fno_sanitize_blacklist_); |
| |
| FlagParser::Flag* flag_resource_dir = parser.AddFlag("resource-dir"); |
| flag_resource_dir->SetOutput(&compiler_info_flags_); |
| |
| FlagParser::Flag* flag_fdebug_prefix_map = |
| parser.AddFlag("fdebug-prefix-map"); |
| FlagParser::Flag* flag_gsplit_dwarf = parser.AddBoolFlag("gsplit-dwarf"); |
| flag_gsplit_dwarf->SetOutput(&compiler_info_flags_); |
| |
| parser.AddFlag("m")->SetOutput(&compiler_info_flags_); |
| parser.AddFlag("arch")->SetOutput(&compiler_info_flags_); |
| parser.AddFlag("target")->SetOutput(&compiler_info_flags_); |
| parser.AddFlag("-target")->SetOutput(&compiler_info_flags_); |
| parser.AddFlag("gcc-toolchain")->SetOutput(&compiler_info_flags_); |
| parser.AddFlag("-gcc-toolchain")->SetOutput(&compiler_info_flags_); |
| // TODO: Uncomment this and remove isysroot_ once we stop |
| // supporting API version 0. |
| // parser.AddFlag("isysroot")->SetOutput(&compiler_info_flags_); |
| parser.AddFlag("imultilib")->SetOutput(&compiler_info_flags_); |
| parser.AddFlag("isystem")->SetOutput(&compiler_info_flags_); |
| parser.AddFlag("iquote")->SetOutput(&compiler_info_flags_); |
| parser.AddFlag("idirafter")->SetOutput(&compiler_info_flags_); |
| parser.AddFlag("-sysroot")->SetOutput(&compiler_info_flags_); |
| parser.AddFlag("B")->SetOutput(&compiler_info_flags_); |
| parser.AddFlag("iframework")->SetOutput(&compiler_info_flags_); |
| parser.AddPrefixFlag("O")->SetOutput(&compiler_info_flags_); |
| parser.AddFlag("b")->SetOutput(&compiler_info_flags_); |
| parser.AddFlag("V")->SetOutput(&compiler_info_flags_); |
| parser.AddFlag("specs")->SetOutput(&compiler_info_flags_); |
| parser.AddFlag("-specs")->SetOutput(&compiler_info_flags_); |
| parser.AddFlag("std")->SetOutput(&compiler_info_flags_); |
| parser.AddFlag("-std")->SetOutput(&compiler_info_flags_); |
| parser.AddPrefixFlag("f")->SetOutput(&compiler_info_flags_); |
| parser.AddBoolFlag("pthread")->SetOutput(&compiler_info_flags_); |
| parser.AddBoolFlag("undef")->SetOutput(&compiler_info_flags_); |
| // If pnacl-clang, it need to support --pnacl-bias and --pnacl-*-bias. |
| // See: b/17982273 |
| if (IsPNaClClangCommand(compiler_base_name())) { |
| parser.AddPrefixFlag("-pnacl-bias=")->SetOutput(&compiler_info_flags_); |
| parser.AddBoolFlag("-pnacl-arm-bias")->SetOutput(&compiler_info_flags_); |
| parser.AddBoolFlag("-pnacl-mips-bias")->SetOutput(&compiler_info_flags_); |
| parser.AddBoolFlag("-pnacl-i686-bias")->SetOutput(&compiler_info_flags_); |
| parser.AddBoolFlag("-pnacl-x86_64-bias")->SetOutput(&compiler_info_flags_); |
| parser.AddBoolFlag("-pnacl-allow-translate") |
| ->SetOutput(&compiler_info_flags_); |
| } |
| parser.AddBoolFlag("no-canonical-prefixes")->SetOutput(&compiler_info_flags_); |
| parser.AddFlag("Xclang")->SetOutput(&xclang_flags); |
| parser.AddFlag("I")->SetValueOutputWithCallback(nullptr, |
| &non_system_include_dirs_); |
| |
| // TODO: -fmodules is clang only. |
| // See https://clang.llvm.org/docs/Modules.html |
| // TODO: Support -fbuiltin-module-maps? |
| parser.AddBoolFlag("fmodules")->SetSeenOutput(&fmodules); |
| parser.AddBoolFlag("fno-implicit-module-maps") |
| ->SetSeenOutput(&fno_implicit_module_maps); |
| FlagParser::Flag* flag_fmodule_file = parser.AddPrefixFlag("fmodule-file="); |
| FlagParser::Flag* flag_fmodule_map_file = |
| parser.AddPrefixFlag("fmodule-map-file="); |
| // -fmodule-name should not be added to compiler_info_flags_, since |
| // -fmodule-name is considered as an input for clang modules, especially for |
| // `-Xclang -emit-module`. |
| parser.AddPrefixFlag("-fmodule-name="); |
| |
| // We should allow both -imacro and --imacro, -include and --include. |
| // See: b/10020850. |
| std::vector<string> includes, imacros; |
| parser.AddFlag("imacros")->SetValueOutputWithCallback(nullptr, &imacros); |
| parser.AddFlag("-imacros")->SetValueOutputWithCallback(nullptr, &imacros); |
| parser.AddFlag("include")->SetValueOutputWithCallback(nullptr, &includes); |
| parser.AddFlag("-include")->SetValueOutputWithCallback(nullptr, &includes); |
| // TODO: We need to consider the order of -I and -F. |
| parser.AddFlag("F")->SetValueOutputWithCallback(nullptr, &framework_dirs_); |
| // TODO: Support -iprefix, -I-, and etc. |
| MacroStore<true> defined_macro_store(&commandline_macros_); |
| MacroStore<false> undefined_macro_store(&commandline_macros_); |
| parser.AddFlag("D")->SetCallbackForParsedArgs(&defined_macro_store); |
| parser.AddFlag("U")->SetCallbackForParsedArgs(&undefined_macro_store); |
| |
| // Special handle for "-W", "-Wa,", "-Wl,", "-Wp,". |
| // We want to parse "-Wa,", "-Wp," |
| // We want to mark "-Wl," unknown. |
| // However, we want to parse -Wsomething. |
| FlagParser::Flag* flag_W = parser.AddPrefixFlag("W"); |
| FlagParser::Flag* flag_Wa = parser.AddPrefixFlag("Wa,"); |
| FlagParser::Flag* flag_Wl = parser.AddPrefixFlag("Wl,"); |
| FlagParser::Flag* flag_Wp = parser.AddPrefixFlag("Wp,"); |
| std::vector<string> assembler_flags; |
| std::vector<string> preprocessor_flags; |
| flag_Wa->SetValueOutputWithCallback(nullptr, &assembler_flags); |
| flag_Wp->SetValueOutputWithCallback(nullptr, &preprocessor_flags); |
| |
| parser.AddNonFlag()->SetOutput(&input_filenames_); |
| |
| parser.Parse(expanded_args_); |
| unknown_flags_ = parser.unknown_flag_args(); |
| |
| // We need to handle -Xclang specially. |
| ProcessXclangFlags(xclang_flags); |
| |
| // -Wa, is a flag for assembler. |
| // -Wa,--noexecstack is often used. |
| if (!assembler_flags.empty()) { |
| std::vector<string> subflags; |
| for (const auto& fs : assembler_flags) { |
| for (auto&& f : absl::StrSplit(fs, ',')) { |
| subflags.emplace_back(f); |
| } |
| } |
| |
| FlagParser pp; |
| FlagParser::Options* opts = pp.mutable_options(); |
| opts->flag_prefix = '-'; |
| opts->allows_equal_arg = true; |
| opts->allows_nonspace_arg = true; |
| opts->has_command_name = false; |
| |
| pp.AddBoolFlag("-noexecstack"); // --noexecstack to make stack unexecutable |
| pp.AddFlag("-defsym"); // --defsym,SYM=VALUE to defin symbol SYM. |
| pp.AddPrefixFlag("I"); // -Iout/somewhere; add include path |
| pp.AddBoolFlag("gdwarf-2"); // -gdwarf-2; debug info |
| pp.AddFlag("march"); // -march=foo; set architecture |
| pp.AddFlag("mfpu"); // -mfpu=foo; set cpu |
| |
| pp.Parse(subflags); |
| for (const auto& unknown : pp.unknown_flag_args()) { |
| unknown_flags_.push_back("-Wa," + unknown); |
| } |
| } |
| |
| if (flag_Wl->seen()) { |
| // For "-Wl,", Mark the whole flag as unknown. |
| // We won't support linker flags. |
| for (const auto& v : flag_Wl->values()) { |
| unknown_flags_.push_back("-Wl," + v); |
| } |
| } |
| |
| // Note: -Wp,-D -Wp,FOOBAR can be considered as -Wp,-D,FOOBAR |
| if (!preprocessor_flags.empty()) { |
| std::vector<string> subflags; |
| for (const auto& fs : preprocessor_flags) { |
| for (auto&& f : absl::StrSplit(fs, ',')) { |
| subflags.emplace_back(f); |
| } |
| } |
| |
| FlagParser pp; |
| FlagParser::Options* opts = pp.mutable_options(); |
| opts->flag_prefix = '-'; |
| opts->allows_equal_arg = true; |
| opts->allows_nonspace_arg = true; |
| opts->has_command_name = false; |
| |
| pp.AddFlag("D")->SetCallbackForParsedArgs(&defined_macro_store); |
| pp.AddFlag("U")->SetCallbackForParsedArgs(&undefined_macro_store); |
| FlagParser::Flag* flag_MD_pp = pp.AddFlag("MD"); |
| |
| pp.Parse(subflags); |
| |
| if (flag_MD_pp->seen()) { |
| output_files_.push_back(flag_MD_pp->GetLastValue()); |
| } |
| for (const auto& unknown : pp.unknown_flag_args()) { |
| unknown_flags_.push_back("-Wp," + unknown); |
| } |
| } |
| |
| // Check -W flag. |
| for (const auto& value : flag_W->values()) { |
| if (!IsKnownWarningOption(value)) { |
| unknown_flags_.push_back("-W" + value); |
| } |
| } |
| |
| // Check debug flags. We match -g with prefix flag. It covers too much. |
| // If the value is not known, we'd like to mark it as unknown option. |
| for (const auto& value : flag_g->values()) { |
| if (!IsKnownDebugOption(value)) { |
| unknown_flags_.push_back("-g" + value); |
| } |
| } |
| |
| if (!has_at_file) { |
| // no @file in args. |
| CHECK_EQ(args_, expanded_args_); |
| expanded_args_.clear(); |
| } |
| |
| if (flag_isysroot->seen()) |
| isysroot_ = flag_isysroot->GetLastValue(); |
| if (flag_resource_dir->seen()) |
| resource_dir_ = flag_resource_dir->GetLastValue(); |
| if (flag_fsanitize->seen()) { |
| for (const auto& value : flag_fsanitize->values()) { |
| for (auto&& v : absl::StrSplit(value, ',')) { |
| fsanitize_.insert(string(v)); |
| } |
| } |
| } |
| if (flag_fdebug_prefix_map->seen()) { |
| for (const auto& value : flag_fdebug_prefix_map->values()) { |
| size_t pos = value.find("="); |
| if (pos == string::npos) { |
| LOG(ERROR) << "invalid argument is given to -fdebug-prefix-map:" |
| << value; |
| return; |
| } |
| bool inserted = fdebug_prefix_map_ |
| .insert(std::make_pair(value.substr(0, pos), |
| value.substr(pos + 1))) |
| .second; |
| LOG_IF(INFO, !inserted) << "-fdebug-prefix-map has duplicated entry." |
| << " ignored: " << value; |
| } |
| // -fdebug-prefix-map does not affect system include dirs or |
| // predefined macros. We do not include it in compiler_info_flags_. |
| // Especially for clang, it is only used in lib/CodeGen/CGDebugInfo.cpp, |
| // which is code to generate debug info. |
| } |
| |
| is_successful_ = true; |
| |
| mode_ = COMPILE; |
| if (flag_E->seen() || flag_M->seen() || flag_MM->seen()) { |
| mode_ = PREPROCESS; |
| } else if (!flag_c->seen() && !flag_S->seen()) { |
| mode_ = LINK; |
| } |
| |
| if (input_filenames_.size() == 1) { |
| if (input_filenames_[0] == "-" || input_filenames_[0] == "/dev/stdin") { |
| is_stdin_input_ = true; |
| } |
| } else if (mode_ != LINK && input_filenames_.size() > 1) { |
| string buf = absl::StrJoin(input_filenames_, ", "); |
| Fail("multiple input file names: " + buf); |
| } |
| |
| if (!llvm_options.empty()) { |
| // TODO: no need to set -*-blacklist options in compiler_info_flags_? |
| std::copy(llvm_options.begin(), llvm_options.end(), |
| back_inserter(compiler_info_flags_)); |
| |
| FlagParser llvm_parser; |
| FlagParser::Options* opts = llvm_parser.mutable_options(); |
| opts->flag_prefix = '-'; |
| opts->allows_equal_arg = true; |
| opts->has_command_name = false; |
| |
| llvm_parser.AddFlag("asan-blacklist") |
| ->SetValueOutputWithCallback(nullptr, &optional_input_filenames_); |
| llvm_parser.AddFlag("tsan-blacklist") |
| ->SetValueOutputWithCallback(nullptr, &optional_input_filenames_); |
| llvm_parser.Parse(llvm_options); |
| } |
| // Any files specified by -fsanitize-blacklist must exist in goma server |
| // even if -fno-sanitize-blacklist is set, or clang dies. |
| // Please see also: |
| // https://github.com/llvm-mirror/clang/blob/5b04748157cbb00ccb3e91f6633a1561b3250e25/lib/Driver/SanitizerArgs.cpp#L485 |
| if (flag_fsanitize_blacklist->seen()) { |
| const std::vector<string>& values = flag_fsanitize_blacklist->values(); |
| for (const auto& value : values) { |
| // -fsanitize-blacklist doesn't affect system include dirs or |
| // predefined macros, so don't include it in compiler_info_flags_. |
| optional_input_filenames_.push_back(value); |
| } |
| } |
| |
| if (flag_x->seen()) { |
| compiler_info_flags_.push_back("-x"); |
| compiler_info_flags_.push_back(flag_x->GetLastValue()); |
| } |
| if (has_nostdinc_) { |
| compiler_info_flags_.push_back("-nostdinc"); |
| } |
| if (no_integrated_as) { |
| compiler_info_flags_.push_back("-no-integrated-as"); |
| has_no_integrated_as_ = true; |
| } |
| if (fno_integrated_as) { |
| compiler_info_flags_.push_back("-fno-integrated-as"); |
| has_no_integrated_as_ = true; |
| } |
| if (ffreestanding) { |
| compiler_info_flags_.push_back("-ffreestanding"); |
| has_ffreestanding_ = true; |
| } |
| if (fno_hosted) { |
| compiler_info_flags_.push_back("-fno-hosted"); |
| has_fno_hosted_ = true; |
| } |
| if (fsyntax_only) { |
| compiler_info_flags_.push_back("-fsyntax-only"); |
| has_fsyntax_only_ = true; |
| } |
| if (fmodules) { |
| compiler_info_flags_.push_back("-fmodules"); |
| has_fmodules_ = true; |
| if (!fno_implicit_module_maps) { |
| // -fmodules implies -fimplicit-module-maps |
| // unless -fno-implicit-module-maps. |
| has_fimplicit_module_maps_ = true; |
| } |
| } |
| |
| if (!isysroot_.empty()) { |
| compiler_info_flags_.push_back("-isysroot"); |
| compiler_info_flags_.push_back(isysroot_); |
| } |
| |
| // Workaround for ChromeOS. |
| // https://code.google.com/p/chromium/issues/detail?id=338646 |
| // |
| // TODO: remove this when we drop chromeos wrapper support. |
| // In https://code.google.com/p/chromium/issues/detail?id=316963, |
| // we are discussing about the drop of chromeos wrapper support. |
| // In other words, goma is called by the wrapper, and we do not have |
| // the wrapper installed in the goma server. |
| for (const auto& it : commandline_macros_) { |
| if (it.first == "__KERNEL__" && it.second) { |
| compiler_info_flags_.push_back("-D__KERNEL__"); |
| break; |
| } |
| } |
| |
| // All files specified by -imacros are processed before all files |
| // specified by -include. |
| std::copy(imacros.begin(), imacros.end(), back_inserter(root_includes_)); |
| std::copy(includes.begin(), includes.end(), back_inserter(root_includes_)); |
| |
| if (print_file_name) { |
| Fail("not supported on remote"); |
| } |
| |
| if (flag_x->seen()) { |
| lang_ = flag_x->GetLastValue(); |
| } else { |
| lang_ = GetLanguage(compiler_name_, |
| (!input_filenames_.empty() ? input_filenames_[0] : "")); |
| } |
| is_cplusplus_ = (lang_.find("c++") != string::npos); |
| if (mode_ == COMPILE) |
| is_precompiling_header_ = absl::EndsWith(lang_, "-header"); |
| |
| { // outout |
| string output; |
| if (flag_o->seen()) { |
| output = flag_o->GetLastValue(); |
| } |
| // Create a default output flag. |
| if (output.empty() && !input_filenames_.empty()) { |
| absl::string_view stem = GetStem(input_filenames_[0]); |
| if (mode_ == LINK) { |
| output = "a.out"; |
| } else if (flag_M->seen() || flag_MM->seen()) { |
| // If -M or -MM is specified, output will be done to stdout. |
| // However, if -MF is specified, output must be to -MF value. |
| if (flag_MF->seen()) { |
| output_files_.push_back(flag_MF->GetLastValue()); |
| } |
| return; |
| } else if (flag_E->seen()) { |
| // preprocess. No output. |
| // If -E, -M and -MF are all specified in the command line, |
| // gcc outputs deps to -MF value. |
| return; |
| } else if (flag_S->seen()) { |
| output = absl::StrCat(stem, ".s"); |
| } else if (is_precompiling_header_) { |
| output = input_filenames_[0] + ".gch"; |
| } else if (flag_c->seen()) { |
| output = absl::StrCat(stem, ".o"); |
| } |
| } |
| if (!output.empty()) { |
| // make output as output_files_[0]. |
| // Since we logs output_files_[0], it is usually preferred to be so. |
| output_files_.insert(output_files_.begin(), output); |
| |
| // if -MD or -MMD flag was specified, and -MF flag was not specified, |
| // assume .d file output. |
| if ((flag_MD->seen() || flag_MMD->seen()) && !flag_MF->seen()) { |
| size_t ext_start = output.rfind('.'); |
| if (string::npos != ext_start) { |
| output_files_.push_back(output.substr(0, ext_start) + ".d"); |
| } |
| } |
| |
| if (flag_gsplit_dwarf->seen()) { |
| if (mode_ == COMPILE) { |
| output_files_.push_back( |
| file::JoinPath(GetDirname(output), GetStem(output)) + ".dwo"); |
| } |
| |
| const string& input0 = input_filenames_[0]; |
| if (mode_ == LINK && GetExtension(input0) != "o") { |
| output_files_.push_back( |
| file::JoinPath(GetDirname(input0), GetStem(input0)) + ".dwo"); |
| } |
| } |
| } |
| } |
| |
| if (flag_MF->seen()) { |
| output_files_.push_back(flag_MF->GetLastValue()); |
| } |
| |
| bool use_profile_input = false; |
| string profile_input_dir = "."; |
| |
| for (const auto& flag : flag_fprofile->values()) { |
| compiler_info_flags_.emplace_back("-fprofile-" + flag); |
| |
| // Pick the last profile dir, this is how GCC works. |
| if (absl::StartsWith(flag, "dir=")) { |
| profile_input_dir = flag.substr(strlen("dir=")); |
| } else if (absl::StartsWith(flag, "generate=")) { |
| profile_input_dir = flag.substr(strlen("generate=")); |
| } |
| } |
| |
| for (const auto& flag : flag_fprofile->values()) { |
| use_profile_input |= absl::StartsWith(flag, "use"); |
| |
| if (absl::StartsWith(flag, "use=")) { |
| const string& use_path = flag.substr(strlen("use=")); |
| |
| // https://clang.llvm.org/docs/ClangCommandLineReference.html#cmdoption-clang1-fprofile-use |
| if (IsClangCommand(compiler_name_) && |
| file::IsDirectory( |
| file::JoinPathRespectAbsolute(cwd, profile_input_dir, use_path), |
| file::Defaults()) |
| .ok()) { |
| optional_input_filenames_.push_back(file::JoinPathRespectAbsolute( |
| profile_input_dir, use_path, "default.profdata")); |
| } else { |
| optional_input_filenames_.push_back( |
| file::JoinPathRespectAbsolute(profile_input_dir, use_path)); |
| } |
| } |
| } |
| |
| if (fmodules) { |
| // -fmodule-file=[<name>=]<file> |
| // According to my experiment, if several -fmodule-file are specified, |
| // only the first one is valid. |
| // -fmodule-file works only if -fmodules is specified. |
| // |
| // See https://clang.llvm.org/docs/Modules.html |
| if (flag_fmodule_file->seen() && !flag_fmodule_file->values().empty()) { |
| absl::string_view path = flag_fmodule_file->value(0); |
| absl::string_view::size_type pos = path.find('='); |
| if (pos != absl::string_view::npos) { |
| clang_module_file_.first = string(path.substr(0, pos)); |
| clang_module_file_.second = string(path.substr(pos + 1)); |
| } else { |
| clang_module_file_.first = ""; |
| clang_module_file_.second = string(path); |
| } |
| } |
| |
| // -fmodule-map-file=<file> |
| if (flag_fmodule_map_file->seen() && |
| !flag_fmodule_map_file->values().empty()) { |
| clang_module_map_file_ = flag_fmodule_map_file->values().front(); |
| } |
| } |
| |
| if (!IsClangCommand(compiler_name_) && use_profile_input && |
| !is_precompiling_header_) { |
| for (const auto& filename : input_filenames_) { |
| size_t ext_start = filename.rfind('.'); |
| if (ext_start == string::npos) |
| continue; |
| size_t last_dir = filename.rfind('/'); |
| if (last_dir == string::npos) |
| last_dir = 0; |
| else |
| last_dir++; |
| optional_input_filenames_.push_back(file::JoinPath( |
| profile_input_dir, |
| filename.substr(last_dir, ext_start - last_dir) + ".gcda")); |
| } |
| } |
| if (flag_fprofile_sample_use->seen()) { |
| optional_input_filenames_.push_back( |
| flag_fprofile_sample_use->GetLastValue()); |
| } |
| if (flag_fthinlto_index->seen()) { |
| optional_input_filenames_.push_back(flag_fthinlto_index->GetLastValue()); |
| thinlto_index_ = flag_fthinlto_index->GetLastValue(); |
| } |
| } |
| |
| const std::vector<string> GCCFlags::include_dirs() const { |
| std::vector<string> dirs(non_system_include_dirs_); |
| std::copy(framework_dirs_.begin(), framework_dirs_.end(), |
| back_inserter(dirs)); |
| return dirs; |
| } |
| |
| bool GCCFlags::IsClientImportantEnv(const char* env) const { |
| if (IsServerImportantEnv(env)) { |
| return true; |
| } |
| |
| // Allow WINEDEBUG= only in client. |
| if (absl::StartsWith(env, "WINEDEBUG=")) { |
| return true; |
| } |
| |
| // These are used for nacl on Win. |
| // Don't send this to server. |
| if ((absl::StartsWithIgnoreCase(env, "PATHEXT=")) || |
| (absl::StartsWithIgnoreCase(env, "SystemRoot="))) { |
| return true; |
| } |
| |
| // Allow DEVELOPER_DIR only in client. |
| if (absl::StartsWith(env, "DEVELOPER_DIR=")) { |
| return true; |
| } |
| |
| return false; |
| } |
| |
| bool GCCFlags::IsServerImportantEnv(const char* env) const { |
| // http://gcc.gnu.org/onlinedocs/gcc/Environment-Variables.html |
| // |
| // Although ld(1) manual mentions following variables, they are not added |
| // without actual needs. That is because it may lead security risks and |
| // gold (linker used by chromium) seems not use them. |
| // - LD_RUN_PATH |
| // - LD_LIBRARY_PATH |
| // |
| // - MACOSX_DEPLOYMENT_TARGET and SDKROOT are used for Darwin targets. |
| // https://clang.llvm.org/docs/CommandGuide/clang.html#envvar-MACOSX_DEPLOYMENT_TARGET |
| // https://llvm.org/viewvc/llvm-project/cfe/trunk/lib/Driver/ToolChains/Darwin.cpp?revision=346276&view=markup |
| // |
| // PWD is used for current working directory. b/27487704 |
| |
| static const char* kCheckEnvs[] = { |
| "LIBRARY_PATH=", |
| "CPATH=", |
| "C_INCLUDE_PATH=", |
| "CPLUS_INCLUDE_PATH=", |
| "OBJC_INCLUDE_PATH=", |
| "DEPENDENCIES_OUTPUT=", |
| "SUNPRO_DEPENDENCIES=", |
| "MACOSX_DEPLOYMENT_TARGET=", |
| "SDKROOT=", |
| "PWD=", |
| }; |
| |
| for (const char* check_env : kCheckEnvs) { |
| if (absl::StartsWith(env, check_env)) { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| /* static */ |
| void GCCFlags::DefineFlags(FlagParser* parser) { |
| FlagParser::Options* opts = parser->mutable_options(); |
| opts->flag_prefix = '-'; |
| opts->allows_equal_arg = true; |
| opts->allows_nonspace_arg = true; |
| |
| // clang options can be taken from: |
| // https://github.com/llvm-mirror/clang/blob/master/include/clang/Driver/Options.td |
| // gcc options |
| // https://gcc.gnu.org/onlinedocs/gcc-6.4.0/gcc/Option-Summary.html#Option-Summary |
| |
| static const struct { |
| const char* name; |
| FlagType flag_type; |
| } kFlags[] = { |
| // gcc/clang flags |
| {"-C", kBool}, // preprocessor option; don't remove comment |
| {"-P", |
| kBool}, // preprocessor option; Disable linemarker output in -E mode |
| {"-include", kNormal}, // preprocess <file> first |
| {"-macros", kNormal}, // preprocess <file> first |
| {"-param", kNormal}, |
| {"-sysroot", kNormal}, |
| {"-version", kBool}, // --version |
| {"B", kNormal}, // add dir to compiler's search paths |
| {"D", kNormal}, // preprocessor defines |
| {"F", kNormal}, |
| {"I", kNormal}, // add dir to header search paths |
| {"L", kNormal}, // add dir to linker search paths |
| {"MF", kNormal}, // specify dependency output |
| {"MP", kBool}, // Create phony target for each dependency (other than |
| // main file) |
| {"MQ", kBool}, // Specify name of main file output to quote in depfile |
| {"MT", kNormal}, |
| {"Qunused-arguments", |
| kBool}, // Don't emit warning for unused driver arguments |
| {"V", kNormal}, // specify target version |
| {"W", kPrefix}, // -Wsomething; disable/disable warnings |
| {"Wa,", kPrefix}, // Options to assembly |
| {"Wl,", kPrefix}, // Options to linker |
| {"Wp,", kPrefix}, // Options to proprocessor |
| {"Xassembler", kNormal}, |
| {"Xlinker", kNormal}, |
| {"Xpreprocessor", kNormal}, |
| {"ansi", kBool}, // -ansi. choose c dialect |
| {"arch", kNormal}, // processor type |
| {"b", kNormal}, // specify target machine |
| {"dA", kBool}, // Annotate the assembler output with |
| // miscellaneous debugging information. |
| {"dD", kBool}, // Like '-dM', without predefined macros etc. |
| {"dM", kBool}, // Generate a list of ‘#define’ directiv. |
| {"fplugin=", kPrefix}, // -fplugin=<dsopath>; gcc plugin |
| {"g", kPrefix}, // debug information. NOTE: Needs special treatment. |
| {"gsplit-dwarf", kBool}, // to enable the generation of split DWARF. |
| {"idirafter", kNormal}, |
| {"iframework", kNormal}, |
| {"imacros", kNormal}, // preprocess <file> first |
| {"imultilib", kNormal}, |
| {"include", kNormal}, // preprocess <file> first |
| {"iquote", kNormal}, |
| {"isysroot", kNormal}, |
| {"isystem", kNormal}, |
| {"m", kNormal}, // machine dependent options |
| {"o", kNormal}, // specify output |
| {"pedantic", kBool}, // old form of -Wpedantic (older gcc has this) |
| {"pg", kBool}, // Generate extra code for gprof |
| {"specs", kNormal}, |
| {"std", kNormal}, |
| {"target", kNormal}, |
| {"v", kBool}, // Show commands to run and use verbose output |
| {"w", kBool}, // Inhibit all warning messages. |
| {"x", kNormal}, // specify language |
| |
| // darwin options |
| {"-serialize-diagnostics", kNormal}, |
| {"allowable_client", kNormal}, |
| {"client_name", kNormal}, |
| {"compatibility_version", kNormal}, |
| {"current_version", kNormal}, |
| {"dylib_file", kNormal}, |
| {"dylinker_install_name", kNormal}, |
| {"exported_symbols_list", kNormal}, |
| {"filelist", kNormal}, |
| {"framework", kNormal}, |
| {"image_base", kNormal}, |
| {"init", kNormal}, |
| {"install_name", kNormal}, |
| {"multiply_defined", kNormal}, |
| {"multiply_defined_unused", kNormal}, |
| {"no-canonical-prefixes", kBool}, |
| {"pagezero_size", kNormal}, |
| {"read_only_relocs", kNormal}, |
| {"seg_addr_table", kNormal}, |
| {"seg_addr_table_filename", kNormal}, |
| {"segs_read_only_addr", kNormal}, |
| {"segs_read_write_addr", kNormal}, |
| {"sub_library", kNormal}, |
| {"sub_umbrella", kNormal}, |
| {"umbrella", kNormal}, |
| {"undefined", kNormal}, |
| {"unexported_symbols_list", kNormal}, |
| {"weak_reference_mismatches", kNormal}, |
| // TODO: -segproto takes 3 arguments (segname, max_prot and |
| // init_prot) |
| // TODO: -segaddr takes 2 arguments (name and address) |
| // TODO: -sectobjectsymbols takes 2 arguments (segname and sectname) |
| // TODO: -sectorder takes 3 arguments (segname, sectname and |
| // orderfile) |
| |
| // for clang |
| {"-coverage", kBool}, // take code coverage |
| {"-no-system-header-prefix=", |
| kPrefix}, // Specify header is not a system |
| // header |
| // --no-system-header-prefix=<prefix> |
| {"-system-header-prefix", kNormal}, // Specify header is a system header |
| // (for diagnosis) |
| // --system-header-prefix=<prefix> or |
| // --sytem-header-prefix=<arg> |
| {"Xanalyzer", kNormal}, |
| {"Xclang", kNormal}, |
| {"gcc-toolchain", kNormal}, |
| {"nostdlibinc", kBool}, // Do not search the standard system directories |
| // for include files, but do search compiler |
| // builtin include directories. |
| {"print-libgcc-file-name", kBool}, // Print the library path for the |
| // currently used compiler runtime |
| // library |
| {"print-prog-name=", kPrefix}, // Print the full program path of |
| // <name> -print-prog-name=<name> |
| |
| // linker flags |
| // https://gcc.gnu.org/onlinedocs/gcc/Link-Options.html |
| {"nodefaultlibs", kBool}, // Do not use the standard system libraries |
| {"nostdlib", |
| kBool}, // Do not use the standard system startup files or libraries |
| {"nostdlib++", kBool}, // Don't use the ld_stdlib++ section |
| {"pie", |
| kBool}, // Produce a dynamically linked position independent executable |
| {"rdynamic", kBool}, // Pass the flag -export-dynamic to the ELF linker |
| {"static", kBool}, // this overrides -pie and prevents linking with the |
| // shared libraries. |
| }; |
| |
| for (const auto& f : kFlags) { |
| switch (f.flag_type) { |
| case kNormal: |
| parser->AddFlag(f.name); |
| break; |
| case kPrefix: |
| parser->AddPrefixFlag(f.name); |
| break; |
| case kBool: |
| parser->AddBoolFlag(f.name); |
| break; |
| } |
| } |
| } |
| |
| // static |
| bool GCCFlags::IsKnownWarningOption(absl::string_view option) { |
| // TODO: If we can have constexpr version of is_sorted, |
| // we can check this in compile time. |
| DCHECK(std::is_sorted( |
| std::begin(kKnownWarningOptions), std::end(kKnownWarningOptions), |
| [](absl::string_view lhs, absl::string_view rhs) { return lhs < rhs; })) |
| << "kKnownWarningOptions must be sorted"; |
| |
| // for "foo=x", take "foo=" only. |
| string::size_type p = option.find('='); |
| if (p != string::npos) { |
| option = option.substr(0, p + 1); // Keep '='. |
| } |
| |
| // Remove "no-" |
| if (absl::StartsWith(option, "no-")) { |
| option = option.substr(strlen("no-")); |
| } |
| |
| return std::binary_search(std::begin(kKnownWarningOptions), |
| std::end(kKnownWarningOptions), option); |
| } |
| |
| // static |
| bool GCCFlags::IsKnownDebugOption(absl::string_view v) { |
| // See https://gcc.gnu.org/onlinedocs/gcc/Debugging-Options.html |
| // -gz is not handled here, since it's used like -gz=<type>. |
| // It's not suitable to handle it here. |
| static const char* const kKnownDebugOptions[]{ |
| "", |
| "0", |
| "1", |
| "2", |
| "3", |
| "column-info", |
| "dw", |
| "dwarf", |
| "dwarf-2", |
| "dwarf-3", |
| "dwarf-4", |
| "dwarf-5", |
| "gdb", |
| "gdb1", |
| "gdb2", |
| "gdb3", |
| "gnu-pubnames", |
| "line-tables-only", |
| "no-column-info", |
| "no-record-gcc-switches", |
| "no-strict-dwarf", |
| "pubnames", |
| "record-gcc-switches", |
| "split-dwarf", |
| "stabs", |
| "stabs+", |
| "stabs0", |
| "stabs1", |
| "stabs2", |
| "stabs3", |
| "strict-dwarf", |
| "vms", |
| "vms0", |
| "vms1", |
| "vms2", |
| "vms3", |
| "xcoff", |
| "xcoff+", |
| "xcoff0", |
| "xcoff1", |
| "xcoff2", |
| "xcoff3", |
| }; |
| |
| DCHECK(std::is_sorted( |
| std::begin(kKnownDebugOptions), std::end(kKnownDebugOptions), |
| [](absl::string_view lhs, absl::string_view rhs) { return lhs < rhs; })) |
| << "kKnownDebugOptions must be sorted"; |
| |
| return std::binary_search(std::begin(kKnownDebugOptions), |
| std::end(kKnownDebugOptions), v); |
| } |
| |
| /* static */ |
| string GCCFlags::GetLanguage(const string& compiler_name, |
| const string& input_filename) { |
| // Decision based on a compiler name. |
| bool is_cplusplus = false; |
| if (compiler_name.find("g++") != string::npos) { |
| is_cplusplus = true; |
| } |
| if (input_filename.empty()) |
| return is_cplusplus ? "c++" : "c"; |
| |
| // Decision based on a file extension. |
| absl::string_view suffix = GetExtension(input_filename); |
| if (!is_cplusplus && suffix != "c") { |
| // GCC may change the language by suffix of input files. |
| // See gcc/gcc.c and gcc/cp/lang-specs.h . |
| // Note that slow operation is OK because we've checked .c first |
| // so we come here rarely. |
| if (suffix == "cc" || suffix == "cxx" || suffix == "cpp" || |
| suffix == "cp" || suffix == "c++" || suffix == "C" || suffix == "CPP" || |
| suffix == "ii" || suffix == "H" || suffix == "hpp" || suffix == "hp" || |
| suffix == "hxx" || suffix == "h++" || suffix == "HPP" || |
| suffix == "tcc" || suffix == "hh" || suffix == "mm" || suffix == "M" || |
| suffix == "mii") { |
| is_cplusplus = true; |
| } |
| } |
| if (is_cplusplus && suffix == "m") { |
| // g++ and clang++ think .m as objc not objc++. (b/11521718) |
| is_cplusplus = false; |
| } |
| |
| const string lang = is_cplusplus ? "c++" : "c"; |
| if (!suffix.empty()) { |
| if (suffix[0] == 'm' || suffix[0] == 'M') |
| return string("objective-") + lang; |
| |
| if (suffix[0] == 'h' || suffix[0] == 'H' || suffix == "tcc") |
| return lang + "-header"; |
| } |
| return lang; |
| } |
| |
| void GCCFlags::ProcessXclangFlags(const std::vector<string>& xclang_flags) { |
| // xclang_flags should be like: |
| // ["-Xclang", "something", "-Xclang", "foo"] |
| // The odd element must be always "-Xclang". |
| // However, the current flag parser might accept -Xclang=foo. |
| // So, evil input like |
| // ["-Xclang=foo", "-Xclang", "bar"] |
| // might come. |
| |
| for (size_t i = 0; i < xclang_flags.size();) { |
| // Not just "-Xclang". Keep as-is. |
| if (xclang_flags[i] != "-Xclang") { |
| compiler_info_flags_.push_back(xclang_flags[i]); |
| ++i; |
| continue; |
| } |
| |
| DCHECK_EQ("-Xclang", xclang_flags[i]) << xclang_flags; |
| |
| // Last orphan -Xclang? Keep as-is anyway. |
| if (i + 1 >= xclang_flags.size()) { |
| compiler_info_flags_.push_back(xclang_flags[i]); |
| ++i; |
| continue; |
| } |
| |
| // There are several -Xclang flags to cause CompilerInfoBuilder to fail. |
| // These flags usually won't change CompilerInfo, so it is OK if it's not |
| // included in compiler_info_flags_. |
| |
| // -Xclang -emit-module needs special handle for include processor. |
| if (xclang_flags[i + 1] == "-emit-module") { |
| has_emit_module_ = true; |
| i += 2; |
| continue; |
| } |
| |
| // -Xclang -emit-ast prints AST to stdout, so it makes CompilerInfoBuilder |
| // be confused. This flag doesn't change CompilerInfo. |
| // -Xclang -emit-pch prints pch object to stdout, so it will fail to take |
| // macro values. |
| if (xclang_flags[i + 1] == "-emit-ast" || |
| xclang_flags[i + 1] == "-emit-pch") { |
| i += 2; |
| continue; |
| } |
| |
| // TODO: There might be more flags to cause CompilerInfoBuilder |
| // to fail. |
| |
| // everything else |
| compiler_info_flags_.push_back(xclang_flags[i]); |
| compiler_info_flags_.push_back(xclang_flags[i + 1]); |
| i += 2; |
| } |
| } |
| |
| string GetCxxCompilerVersionFromCommandOutputs(const string& /* command */, |
| const string& dumpversion, |
| const string& version) { |
| string result(GetFirstLine(dumpversion)); |
| // Both GCC and clang contain their full version info in the first |
| // line of their --version output. |
| // E.g., clang version 2.9 (trunk 127176), gcc (Ubuntu 4.4.3-4ubuntu5) 4.4.3 |
| result += "[" + NormalizeGccVersion(GetFirstLine(version)) + "]"; |
| return result; |
| } |
| |
| string GetFirstLine(const string& buf) { |
| size_t pos = buf.find_first_of("\r\n"); |
| if (pos == string::npos) { |
| return buf; |
| } |
| return buf.substr(0, pos); |
| } |
| |
| string NormalizeGccVersion(const string& version) { |
| // gcc version string format: |
| // <program name> <package version string> <version string> |
| // Note: <package version string> is "(<something>)" by default. |
| // Then, we can expect the string until '(' is <program name>. |
| size_t pos = version.find('('); |
| if (pos == string::npos) |
| return version; |
| |
| const string program_name = version.substr(0, pos); |
| // No need to normalize clang. |
| if (program_name.find("clang") != string::npos) |
| return version; |
| // Only need to normalize cc/c++/gcc/g++/<arch>-<os>-gcc/<arch>-<os>-g++. |
| // TODO: should we handle <arch>-<os>-cc or so? |
| if (program_name.find("g++") == string::npos && |
| program_name.find("gcc") == string::npos && program_name != "c++ " && |
| program_name != "cc ") { |
| return version; |
| } |
| |
| return version.substr(pos); |
| } |
| |
| /* static */ |
| bool GCCFlags::IsGCCCommand(absl::string_view arg) { |
| const absl::string_view stem = GetStem(arg); |
| if (stem.find("gcc") != absl::string_view::npos || |
| stem.find("g++") != absl::string_view::npos) |
| return true; |
| // As a substring "cc" would be found even in other commands such |
| // as "distcc", we check if the name is "cc" or "*-cc" |
| // (e.g., "i586-mingw32msvc-cc"). |
| if (stem == "c++" || stem == "cc" || absl::EndsWith(arg, "-cc")) |
| return true; |
| if (IsClangCommand(arg)) |
| return true; |
| return false; |
| } |
| |
| /* static */ |
| bool GCCFlags::IsClangCommand(absl::string_view arg) { |
| const absl::string_view stem = GetStem(arg); |
| // allow pnacl-clang etc. |
| // However, don't allow clang-tidy. |
| if (stem == "clang" || stem == "clang++" || absl::EndsWith(stem, "-clang") || |
| absl::EndsWith(stem, "-clang++")) |
| return true; |
| |
| // For b/25937763 but we should not consider the followings as clang: |
| // clang-cl, clang-check, clang-tblgen, clang-format, clang-tidy-diff, etc. |
| constexpr absl::string_view kClang = "clang-"; |
| constexpr absl::string_view kClangxx = "clang++-"; |
| absl::string_view version = stem; |
| if (absl::StartsWith(stem, kClang)) |
| version.remove_prefix(kClang.size()); |
| else if (absl::StartsWith(stem, kClangxx)) |
| version.remove_prefix(kClangxx.size()); |
| if (stem == version) |
| return false; |
| // version should only have digits and '.'. |
| return version.find_first_not_of("0123456789.") == absl::string_view::npos; |
| } |
| |
| /* static */ |
| bool GCCFlags::IsNaClGCCCommand(absl::string_view arg) { |
| const absl::string_view basename = GetBasename(arg); |
| return basename.find("nacl-gcc") != absl::string_view::npos || |
| basename.find("nacl-g++") != absl::string_view::npos; |
| } |
| |
| /* static */ |
| bool GCCFlags::IsNaClClangCommand(absl::string_view arg) { |
| // pnacl-clang and pnalc-clang++ are not nacl-clang. |
| if (IsPNaClClangCommand(arg)) { |
| return false; |
| } |
| const absl::string_view basename = GetBasename(arg); |
| return basename.find("nacl-clang") != absl::string_view::npos || |
| basename.find("nacl-clang++") != absl::string_view::npos; |
| } |
| |
| /* static */ |
| bool GCCFlags::IsPNaClClangCommand(absl::string_view arg) { |
| const absl::string_view stem = GetStem(arg); |
| return stem == "pnacl-clang" || stem == "pnacl-clang++"; |
| } |
| |
| } // namespace devtools_goma |