[gn] Add --export-compile-commands switch to gen Adding a command line switch to generate a compilation database (compile_commands.json) in the out/ directory. Compilation databases are used by clang-based tools (e.g. clang-tidy, clangd, clang-rename). CMake already does this with the -DEXPORT_COMPILE_COMMANDS option, which regenerates the compdb whenever the ninja files are regenerated. Use: gn gen out/<builddir> --export-compile-commands Setting this switch adds ~900ms to the `gn gen` command when run on Chromium (wihtout --export-compile-commands == 2.7sec, with --export-compile-commands == 3.6sec). Change-Id: Ia3738e340b9de60da62557fb431a68a0618957fb Reviewed-on: https://gn-review.googlesource.com/2040 Commit-Queue: Brett Wilson <brettw@chromium.org> Reviewed-by: Brett Wilson <brettw@chromium.org>
diff --git a/build/gen.py b/build/gen.py index 6fb6dfc..eceeafe 100755 --- a/build/gen.py +++ b/build/gen.py
@@ -375,6 +375,7 @@ 'tools/gn/command_path.cc', 'tools/gn/command_refs.cc', 'tools/gn/commands.cc', + 'tools/gn/compile_commands_writer.cc', 'tools/gn/config.cc', 'tools/gn/config_values.cc', 'tools/gn/config_values_extractors.cc', @@ -425,6 +426,7 @@ 'tools/gn/ninja_copy_target_writer.cc', 'tools/gn/ninja_create_bundle_target_writer.cc', 'tools/gn/ninja_group_target_writer.cc', + 'tools/gn/ninja_target_command_util.cc', 'tools/gn/ninja_target_writer.cc', 'tools/gn/ninja_toolchain_writer.cc', 'tools/gn/ninja_utils.cc',
diff --git a/tools/gn/command_gen.cc b/tools/gn/command_gen.cc index eb1b4d2..d959fe4 100644 --- a/tools/gn/command_gen.cc +++ b/tools/gn/command_gen.cc
@@ -11,6 +11,7 @@ #include "base/timer/elapsed_timer.h" #include "tools/gn/build_settings.h" #include "tools/gn/commands.h" +#include "tools/gn/compile_commands_writer.h" #include "tools/gn/eclipse_writer.h" #include "tools/gn/json_project_writer.h" #include "tools/gn/ninja_target_writer.h" @@ -49,6 +50,7 @@ const char kSwitchJsonFileName[] = "json-file-name"; const char kSwitchJsonIdeScript[] = "json-ide-script"; const char kSwitchJsonIdeScriptArgs[] = "json-ide-script-args"; +const char kSwitchExportCompileCommands[] = "export-compile-commands"; // Collects Ninja rules for each toolchain. The lock protectes the rules. struct TargetWriteInfo { @@ -279,6 +281,26 @@ return false; } +bool RunCompileCommandsWriter(const BuildSettings* build_settings, + const Builder& builder, + Err* err) { + const base::CommandLine* command_line = + base::CommandLine::ForCurrentProcess(); + bool quiet = command_line->HasSwitch(switches::kQuiet); + base::ElapsedTimer timer; + + std::string file_name = "compile_commands.json"; + + bool res = CompileCommandsWriter::RunAndWriteFiles(build_settings, builder, + file_name, quiet, err); + if (res && !quiet) { + OutputString("Generating compile_commands took " + + base::Int64ToString(timer.Elapsed().InMilliseconds()) + + "ms\n"); + } + return res; +} + } // namespace const char kGen[] = "gen"; @@ -455,6 +477,13 @@ return 1; } + if (command_line->HasSwitch(kSwitchExportCompileCommands) && + !RunCompileCommandsWriter(&setup->build_settings(), setup->builder(), + &err)) { + err.PrintToStdout(); + return 1; + } + TickDelta elapsed_time = timer.Elapsed(); if (!command_line->HasSwitch(switches::kQuiet)) {
diff --git a/tools/gn/compile_commands_writer.cc b/tools/gn/compile_commands_writer.cc new file mode 100644 index 0000000..772d8d2 --- /dev/null +++ b/tools/gn/compile_commands_writer.cc
@@ -0,0 +1,284 @@ +// Copyright 2018 The Chromium 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 "tools/gn/compile_commands_writer.h" + +#include <sstream> + +#include "base/strings/stringprintf.h" +#include "tools/gn/builder.h" +#include "tools/gn/config_values_extractors.h" +#include "tools/gn/escape.h" +#include "tools/gn/filesystem_utils.h" +#include "tools/gn/ninja_target_command_util.h" +#include "tools/gn/path_output.h" +#include "tools/gn/substitution_writer.h" + +// Structure of JSON output file +// [ +// { +// "directory": "The build directory." +// "file": "The main source file processed by this compilation step. +// Must be absolute or relative to the above build directory." +// "command": "The compile command executed." +// } +// ... +// ] + +namespace { + +#if defined(OS_WIN) +const char kPrettyPrintLineEnding[] = "\r\n"; +#else +const char kPrettyPrintLineEnding[] = "\n"; +#endif + +struct CompileFlags { + std::string includes; + std::string defines; + std::string cflags; + std::string cflags_c; + std::string cflags_cc; + std::string cflags_objc; + std::string cflags_objcc; +}; + +void SetupCompileFlags(const Target* target, + PathOutput& path_output, + EscapeOptions opts, + CompileFlags& flags) { + bool has_precompiled_headers = + target->config_values().has_precompiled_headers(); + + std::ostringstream defines_out; + RecursiveTargetConfigToStream<std::string>( + target, &ConfigValues::defines, + DefineWriter(ESCAPE_NINJA_PREFORMATTED_COMMAND, true), defines_out); + flags.defines = defines_out.str(); + + std::ostringstream includes_out; + RecursiveTargetConfigToStream<SourceDir>(target, &ConfigValues::include_dirs, + IncludeWriter(path_output), + includes_out); + flags.includes = includes_out.str(); + + std::ostringstream cflags_out; + WriteOneFlag(target, SUBSTITUTION_CFLAGS, false, Toolchain::TYPE_NONE, + &ConfigValues::cflags, opts, path_output, cflags_out, + /*write_substitution=*/false); + flags.cflags = cflags_out.str(); + + std::ostringstream cflags_c_out; + WriteOneFlag(target, SUBSTITUTION_CFLAGS_C, has_precompiled_headers, + Toolchain::TYPE_CC, &ConfigValues::cflags_c, opts, path_output, + cflags_c_out, /*write_substitution=*/false); + flags.cflags_c = cflags_c_out.str(); + + std::ostringstream cflags_cc_out; + WriteOneFlag(target, SUBSTITUTION_CFLAGS_CC, has_precompiled_headers, + Toolchain::TYPE_CXX, &ConfigValues::cflags_cc, opts, path_output, + cflags_cc_out, /*write_substitution=*/false); + flags.cflags_cc = cflags_cc_out.str(); + + std::ostringstream cflags_objc_out; + WriteOneFlag(target, SUBSTITUTION_CFLAGS_OBJC, has_precompiled_headers, + Toolchain::TYPE_OBJC, &ConfigValues::cflags_objc, opts, + path_output, cflags_objc_out, /*write_substitution=*/false); + flags.cflags_objc = cflags_objc_out.str(); + + std::ostringstream cflags_objcc_out; + WriteOneFlag(target, SUBSTITUTION_CFLAGS_OBJCC, has_precompiled_headers, + Toolchain::TYPE_OBJCXX, &ConfigValues::cflags_objcc, opts, + path_output, cflags_objcc_out, /*write_substitution=*/false); + flags.cflags_objcc = cflags_objcc_out.str(); +} + +void WriteFile(const SourceFile& source, + PathOutput& path_output, + std::string* compile_commands) { + std::ostringstream rel_source_path; + path_output.WriteFile(rel_source_path, source); + compile_commands->append(" \"file\": \""); + compile_commands->append(rel_source_path.str()); +} + +void WriteDirectory(std::string build_dir, std::string* compile_commands) { + compile_commands->append("\","); + compile_commands->append(kPrettyPrintLineEnding); + compile_commands->append(" \"directory\": \""); + compile_commands->append(build_dir); + compile_commands->append("\","); +} + +void WriteCommand(const Target* target, + const SourceFile& source, + const CompileFlags& flags, + std::vector<OutputFile>& tool_outputs, + PathOutput& path_output, + SourceFileType source_type, + Toolchain::ToolType tool_type, + EscapeOptions opts, + std::string* compile_commands) { + EscapeOptions no_quoting(opts); + no_quoting.inhibit_quoting = true; + const Tool* tool = target->toolchain()->GetTool(tool_type); + std::ostringstream command_out; + + for (const auto& range : tool->command().ranges()) { + // TODO: this is emitting a bonus space prior to each substitution. + switch (range.type) { + case SUBSTITUTION_LITERAL: + EscapeStringToStream(command_out, range.literal, no_quoting); + break; + case SUBSTITUTION_OUTPUT: + path_output.WriteFiles(command_out, tool_outputs); + break; + case SUBSTITUTION_DEFINES: + command_out << flags.defines; + break; + case SUBSTITUTION_INCLUDE_DIRS: + command_out << flags.includes; + break; + case SUBSTITUTION_CFLAGS: + command_out << flags.cflags; + break; + case SUBSTITUTION_CFLAGS_C: + if (source_type == SOURCE_C) + command_out << flags.cflags_c; + break; + case SUBSTITUTION_CFLAGS_CC: + if (source_type == SOURCE_CPP) + command_out << flags.cflags_cc; + break; + case SUBSTITUTION_CFLAGS_OBJC: + if (source_type == SOURCE_M) + command_out << flags.cflags_objc; + break; + case SUBSTITUTION_CFLAGS_OBJCC: + if (source_type == SOURCE_MM) + command_out << flags.cflags_objcc; + break; + case SUBSTITUTION_LABEL: + case SUBSTITUTION_LABEL_NAME: + case SUBSTITUTION_ROOT_GEN_DIR: + case SUBSTITUTION_ROOT_OUT_DIR: + case SUBSTITUTION_TARGET_GEN_DIR: + case SUBSTITUTION_TARGET_OUT_DIR: + case SUBSTITUTION_TARGET_OUTPUT_NAME: + case SUBSTITUTION_SOURCE: + case SUBSTITUTION_SOURCE_NAME_PART: + case SUBSTITUTION_SOURCE_FILE_PART: + case SUBSTITUTION_SOURCE_DIR: + case SUBSTITUTION_SOURCE_ROOT_RELATIVE_DIR: + case SUBSTITUTION_SOURCE_GEN_DIR: + case SUBSTITUTION_SOURCE_OUT_DIR: + case SUBSTITUTION_SOURCE_TARGET_RELATIVE: + EscapeStringToStream(command_out, + SubstitutionWriter::GetCompilerSubstitution( + target, source, range.type), + opts); + break; + + // Other flags shouldn't be relevant to compiling C/C++/ObjC/ObjC++ + // source files. + default: + NOTREACHED() << "Unsupported substitution for this type of target : " + << kSubstitutionNames[range.type]; + continue; + } + } + compile_commands->append(kPrettyPrintLineEnding); + compile_commands->append(" \"command\": \""); + compile_commands->append(command_out.str()); +} + +void RenderJSON(const BuildSettings* build_settings, + const Builder& builder, + std::vector<const Target*>& all_targets, + std::string* compile_commands) { + // TODO: Determine out an appropriate size to reserve. + compile_commands->reserve(all_targets.size() * 100); + compile_commands->append("["); + compile_commands->append(kPrettyPrintLineEnding); + bool first = true; + auto build_dir = build_settings->GetFullPath(build_settings->build_dir()); + std::vector<OutputFile> tool_outputs; // Prevent reallocation in loop. + + EscapeOptions opts; + opts.mode = ESCAPE_NINJA_PREFORMATTED_COMMAND; + + for (const auto* target : all_targets) { + if (!target->IsBinary()) + continue; + + // Precompute values that are the same for all sources in a target to avoid + // computing for every source. + + PathOutput path_output( + target->settings()->build_settings()->build_dir(), + target->settings()->build_settings()->root_path_utf8(), + ESCAPE_NINJA_COMMAND); + + CompileFlags flags; + SetupCompileFlags(target, path_output, opts, flags); + + for (const auto& source : target->sources()) { + // If this source is not a C/C++/ObjC/ObjC++ source (not header) file, + // continue as it does not belong in the compilation database. + SourceFileType source_type = GetSourceFileType(source); + if (source_type != SOURCE_CPP && source_type != SOURCE_C && + source_type != SOURCE_M && source_type != SOURCE_MM) + continue; + + Toolchain::ToolType tool_type = Toolchain::TYPE_NONE; + if (!target->GetOutputFilesForSource(source, &tool_type, &tool_outputs)) + continue; + + if (!first) { + compile_commands->append(","); + compile_commands->append(kPrettyPrintLineEnding); + } + first = false; + compile_commands->append(" {"); + compile_commands->append(kPrettyPrintLineEnding); + + WriteFile(source, path_output, compile_commands); + WriteDirectory(base::StringPrintf("%" PRIsFP, build_dir.value().c_str()), + compile_commands); + WriteCommand(target, source, flags, tool_outputs, path_output, + source_type, tool_type, opts, compile_commands); + compile_commands->append("\""); + compile_commands->append(kPrettyPrintLineEnding); + compile_commands->append(" }"); + } + } + + compile_commands->append(kPrettyPrintLineEnding); + compile_commands->append("]"); + compile_commands->append(kPrettyPrintLineEnding); +} + +} // namespace + +bool CompileCommandsWriter::RunAndWriteFiles( + const BuildSettings* build_settings, + const Builder& builder, + const std::string& file_name, + bool quiet, + Err* err) { + SourceFile output_file = build_settings->build_dir().ResolveRelativeFile( + Value(nullptr, file_name), err); + if (output_file.is_null()) + return false; + + base::FilePath output_path = build_settings->GetFullPath(output_file); + + std::vector<const Target*> all_targets = builder.GetAllResolvedTargets(); + + std::string json; + RenderJSON(build_settings, builder, all_targets, &json); + if (!WriteFileIfChanged(output_path, json, err)) + return false; + return true; +}
diff --git a/tools/gn/compile_commands_writer.h b/tools/gn/compile_commands_writer.h new file mode 100644 index 0000000..ad98dc2 --- /dev/null +++ b/tools/gn/compile_commands_writer.h
@@ -0,0 +1,23 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef TOOLS_GN_COMPILE_COMMANDS_WRITER_H_ +#define TOOLS_GN_COMPILE_COMMANDS_WRITER_H_ + +#include "tools/gn/err.h" +#include "tools/gn/target.h" + +class Builder; +class BuildSettings; + +class CompileCommandsWriter { + public: + static bool RunAndWriteFiles(const BuildSettings* build_setting, + const Builder& builder, + const std::string& file_name, + bool quiet, + Err* err); +}; + +#endif // TOOLS_GN_COMPILE_COMMANDS_WRITER_H_
diff --git a/tools/gn/ninja_binary_target_writer.cc b/tools/gn/ninja_binary_target_writer.cc index 48982d3..9e5632e 100644 --- a/tools/gn/ninja_binary_target_writer.cc +++ b/tools/gn/ninja_binary_target_writer.cc
@@ -19,6 +19,7 @@ #include "tools/gn/escape.h" #include "tools/gn/filesystem_utils.h" #include "tools/gn/ninja_utils.h" +#include "tools/gn/ninja_target_command_util.h" #include "tools/gn/scheduler.h" #include "tools/gn/settings.h" #include "tools/gn/source_file_type.h" @@ -50,78 +51,6 @@ return opts; } -struct DefineWriter { - DefineWriter() { options.mode = ESCAPE_NINJA_COMMAND; } - - void operator()(const std::string& s, std::ostream& out) const { - out << " "; - EscapeStringToStream(out, "-D" + s, options); - } - - EscapeOptions options; -}; - -struct IncludeWriter { - explicit IncludeWriter(PathOutput& path_output) : path_output_(path_output) {} - ~IncludeWriter() = default; - - void operator()(const SourceDir& d, std::ostream& out) const { - std::ostringstream path_out; - path_output_.WriteDir(path_out, d, PathOutput::DIR_NO_LAST_SLASH); - const std::string& path = path_out.str(); - if (path[0] == '"') - out << " \"-I" << path.substr(1); - else - out << " -I" << path; - } - - PathOutput& path_output_; -}; - -// Returns the language-specific suffix for precompiled header files. -const char* GetPCHLangSuffixForToolType(Toolchain::ToolType type) { - switch (type) { - case Toolchain::TYPE_CC: - return "c"; - case Toolchain::TYPE_CXX: - return "cc"; - case Toolchain::TYPE_OBJC: - return "m"; - case Toolchain::TYPE_OBJCXX: - return "mm"; - default: - NOTREACHED() << "Not a valid PCH tool type: " << type; - return ""; - } -} - -std::string GetWindowsPCHObjectExtension(Toolchain::ToolType tool_type, - const std::string& obj_extension) { - const char* lang_suffix = GetPCHLangSuffixForToolType(tool_type); - std::string result = "."; - // For MSVC, annotate the obj files with the language type. For example: - // obj/foo/target_name.precompile.obj -> - // obj/foo/target_name.precompile.cc.obj - result += lang_suffix; - result += obj_extension; - return result; -} - -std::string GetGCCPCHOutputExtension(Toolchain::ToolType tool_type) { - const char* lang_suffix = GetPCHLangSuffixForToolType(tool_type); - std::string result = "."; - // For GCC, the output name must have a .gch suffix and be annotated with - // the language type. For example: - // obj/foo/target_name.header.h -> - // obj/foo/target_name.header.h-cc.gch - // In order for the compiler to pick it up, the output name (minus the .gch - // suffix MUST match whatever is passed to the -include flag). - result += "h-"; - result += lang_suffix; - result += ".gch"; - return result; -} - // Returns the language-specific lang recognized by gcc’s -x flag for // precompiled header files. const char* GetPCHLangForToolType(Toolchain::ToolType type) { @@ -140,55 +69,6 @@ } } -// Fills |outputs| with the object or gch file for the precompiled header of the -// given type (flag type and tool type must match). -void GetPCHOutputFiles(const Target* target, - Toolchain::ToolType tool_type, - std::vector<OutputFile>* outputs) { - outputs->clear(); - - // Compute the tool. This must use the tool type passed in rather than the - // detected file type of the precompiled source file since the same - // precompiled source file will be used for separate C/C++ compiles. - const Tool* tool = target->toolchain()->GetTool(tool_type); - if (!tool) - return; - SubstitutionWriter::ApplyListToCompilerAsOutputFile( - target, target->config_values().precompiled_source(), tool->outputs(), - outputs); - - if (outputs->empty()) - return; - if (outputs->size() > 1) - outputs->resize(1); // Only link the first output from the compiler tool. - - std::string& output_value = (*outputs)[0].value(); - size_t extension_offset = FindExtensionOffset(output_value); - if (extension_offset == std::string::npos) { - // No extension found. - return; - } - DCHECK(extension_offset >= 1); - DCHECK(output_value[extension_offset - 1] == '.'); - - std::string output_extension; - Tool::PrecompiledHeaderType header_type = tool->precompiled_header_type(); - switch (header_type) { - case Tool::PCH_MSVC: - output_extension = GetWindowsPCHObjectExtension( - tool_type, output_value.substr(extension_offset - 1)); - break; - case Tool::PCH_GCC: - output_extension = GetGCCPCHOutputExtension(tool_type); - break; - case Tool::PCH_NONE: - NOTREACHED() << "No outputs for no PCH type."; - break; - } - output_value.replace(extension_offset - 1, std::string::npos, - output_extension); -} - // Appends the object files generated by the given source set to the given // output vector. void AddSourceSetObjectFiles(const Target* source_set, @@ -372,29 +252,29 @@ EscapeOptions opts = GetFlagOptions(); if (used_types.Get(SOURCE_S) || used_types.Get(SOURCE_ASM)) { - WriteOneFlag(SUBSTITUTION_ASMFLAGS, false, Toolchain::TYPE_NONE, - &ConfigValues::asmflags, opts); + WriteOneFlag(target_, SUBSTITUTION_ASMFLAGS, false, Toolchain::TYPE_NONE, + &ConfigValues::asmflags, opts, path_output_, out_); } if (used_types.Get(SOURCE_C) || used_types.Get(SOURCE_CPP) || used_types.Get(SOURCE_M) || used_types.Get(SOURCE_MM)) { - WriteOneFlag(SUBSTITUTION_CFLAGS, false, Toolchain::TYPE_NONE, - &ConfigValues::cflags, opts); + WriteOneFlag(target_, SUBSTITUTION_CFLAGS, false, Toolchain::TYPE_NONE, + &ConfigValues::cflags, opts, path_output_, out_); } if (used_types.Get(SOURCE_C)) { - WriteOneFlag(SUBSTITUTION_CFLAGS_C, has_precompiled_headers, - Toolchain::TYPE_CC, &ConfigValues::cflags_c, opts); + WriteOneFlag(target_, SUBSTITUTION_CFLAGS_C, has_precompiled_headers, + Toolchain::TYPE_CC, &ConfigValues::cflags_c, opts, path_output_, out_); } if (used_types.Get(SOURCE_CPP)) { - WriteOneFlag(SUBSTITUTION_CFLAGS_CC, has_precompiled_headers, - Toolchain::TYPE_CXX, &ConfigValues::cflags_cc, opts); + WriteOneFlag(target_, SUBSTITUTION_CFLAGS_CC, has_precompiled_headers, + Toolchain::TYPE_CXX, &ConfigValues::cflags_cc, opts, path_output_, out_); } if (used_types.Get(SOURCE_M)) { - WriteOneFlag(SUBSTITUTION_CFLAGS_OBJC, has_precompiled_headers, - Toolchain::TYPE_OBJC, &ConfigValues::cflags_objc, opts); + WriteOneFlag(target_, SUBSTITUTION_CFLAGS_OBJC, has_precompiled_headers, + Toolchain::TYPE_OBJC, &ConfigValues::cflags_objc, opts, path_output_, out_); } if (used_types.Get(SOURCE_MM)) { - WriteOneFlag(SUBSTITUTION_CFLAGS_OBJCC, has_precompiled_headers, - Toolchain::TYPE_OBJCXX, &ConfigValues::cflags_objcc, opts); + WriteOneFlag(target_, SUBSTITUTION_CFLAGS_OBJCC, has_precompiled_headers, + Toolchain::TYPE_OBJCXX, &ConfigValues::cflags_objcc, opts, path_output_, out_); } WriteSharedVars(subst); @@ -440,59 +320,6 @@ return input_stamp_file; } -void NinjaBinaryTargetWriter::WriteOneFlag( - SubstitutionType subst_enum, - bool has_precompiled_headers, - Toolchain::ToolType tool_type, - const std::vector<std::string>& (ConfigValues::*getter)() const, - EscapeOptions flag_escape_options) { - if (!target_->toolchain()->substitution_bits().used[subst_enum]) - return; - - out_ << kSubstitutionNinjaNames[subst_enum] << " ="; - - if (has_precompiled_headers) { - const Tool* tool = target_->toolchain()->GetTool(tool_type); - if (tool && tool->precompiled_header_type() == Tool::PCH_MSVC) { - // Name the .pch file. - out_ << " /Fp"; - path_output_.WriteFile(out_, GetWindowsPCHFile(tool_type)); - - // Enables precompiled headers and names the .h file. It's a string - // rather than a file name (so no need to rebase or use path_output_). - out_ << " /Yu" << target_->config_values().precompiled_header(); - RecursiveTargetConfigStringsToStream(target_, getter, flag_escape_options, - out_); - } else if (tool && tool->precompiled_header_type() == Tool::PCH_GCC) { - // The targets to build the .gch files should omit the -include flag - // below. To accomplish this, each substitution flag is overwritten in the - // target rule and these values are repeated. The -include flag is omitted - // in place of the required -x <header lang> flag for .gch targets. - RecursiveTargetConfigStringsToStream(target_, getter, flag_escape_options, - out_); - - // Compute the gch file (it will be language-specific). - std::vector<OutputFile> outputs; - GetPCHOutputFiles(target_, tool_type, &outputs); - if (!outputs.empty()) { - // Trim the .gch suffix for the -include flag. - // e.g. for gch file foo/bar/target.precompiled.h.gch: - // -include foo/bar/target.precompiled.h - std::string pch_file = outputs[0].value(); - pch_file.erase(pch_file.length() - 4); - out_ << " -include " << pch_file; - } - } else { - RecursiveTargetConfigStringsToStream(target_, getter, flag_escape_options, - out_); - } - } else { - RecursiveTargetConfigStringsToStream(target_, getter, flag_escape_options, - out_); - } - out_ << std::endl; -} - void NinjaBinaryTargetWriter::WritePCHCommands( const SourceFileTypeSet& used_types, const OutputFile& input_dep, @@ -1033,19 +860,6 @@ } } -OutputFile NinjaBinaryTargetWriter::GetWindowsPCHFile( - Toolchain::ToolType tool_type) const { - // Use "obj/{dir}/{target_name}_{lang}.pch" which ends up - // looking like "obj/chrome/browser/browser_cc.pch" - OutputFile ret = GetBuildDirForTargetAsOutputFile(target_, BuildDirType::OBJ); - ret.value().append(target_->label().name()); - ret.value().push_back('_'); - ret.value().append(GetPCHLangSuffixForToolType(tool_type)); - ret.value().append(".pch"); - - return ret; -} - bool NinjaBinaryTargetWriter::CheckForDuplicateObjectFiles( const std::vector<OutputFile>& files) const { std::unordered_set<std::string> set;
diff --git a/tools/gn/ninja_binary_target_writer.h b/tools/gn/ninja_binary_target_writer.h index 4bc5b44..5cb7ab5 100644 --- a/tools/gn/ninja_binary_target_writer.h +++ b/tools/gn/ninja_binary_target_writer.h
@@ -37,21 +37,6 @@ // will be empty if there are no inputs. OutputFile WriteInputsStampAndGetDep() const; - // has_precompiled_headers is set when this substitution matches a tool type - // that supports precompiled headers, and this target supports precompiled - // headers. It doesn't indicate if the tool has precompiled headers (this - // will be looked up by this function). - // - // The tool_type indicates the corresponding tool for flags that are - // tool-specific (e.g. "cflags_c"). For non-tool-specific flags (e.g. - // "defines") tool_type should be TYPE_NONE. - void WriteOneFlag(SubstitutionType subst_enum, - bool has_precompiled_headers, - Toolchain::ToolType tool_type, - const std::vector<std::string>& (ConfigValues::*getter)() - const, - EscapeOptions flag_escape_options); - // Writes build lines required for precompiled headers. Any generated // object files will be appended to the |object_files|. Any generated // non-object files (for instance, .gch files from a GCC toolchain, are @@ -140,9 +125,6 @@ void WriteOrderOnlyDependencies( const UniqueVector<const Target*>& non_linkable_deps); - // Returns the computed name of the Windows .pch file for the given - // tool type. The tool must support precompiled headers. - OutputFile GetWindowsPCHFile(Toolchain::ToolType tool_type) const; // Checks for duplicates in the given list of output files. If any duplicates // are found, throws an error and return false.
diff --git a/tools/gn/ninja_binary_target_writer_unittest.cc b/tools/gn/ninja_binary_target_writer_unittest.cc index 6d02a8b..a0a7ce8 100644 --- a/tools/gn/ninja_binary_target_writer_unittest.cc +++ b/tools/gn/ninja_binary_target_writer_unittest.cc
@@ -9,6 +9,7 @@ #include <utility> #include "tools/gn/config.h" +#include "tools/gn/ninja_target_command_util.h" #include "tools/gn/scheduler.h" #include "tools/gn/target.h" #include "tools/gn/test_with_scheduler.h"
diff --git a/tools/gn/ninja_target_command_util.cc b/tools/gn/ninja_target_command_util.cc new file mode 100644 index 0000000..29662cd --- /dev/null +++ b/tools/gn/ninja_target_command_util.cc
@@ -0,0 +1,179 @@ +// Copyright 2018 The Chromium 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 "tools/gn/ninja_target_command_util.h" + +#include <string.h> + +#include "tools/gn/substitution_writer.h" + +namespace { + +// Returns the language-specific suffix for precompiled header files. +const char* GetPCHLangSuffixForToolType(Toolchain::ToolType type) { + switch (type) { + case Toolchain::TYPE_CC: + return "c"; + case Toolchain::TYPE_CXX: + return "cc"; + case Toolchain::TYPE_OBJC: + return "m"; + case Toolchain::TYPE_OBJCXX: + return "mm"; + default: + NOTREACHED() << "Not a valid PCH tool type: " << type; + return ""; + } +} + +} // namespace + +// Returns the computed name of the Windows .pch file for the given +// tool type. The tool must support precompiled headers. +OutputFile GetWindowsPCHFile(const Target* target, + Toolchain::ToolType tool_type) { + // Use "obj/{dir}/{target_name}_{lang}.pch" which ends up + // looking like "obj/chrome/browser/browser_cc.pch" + OutputFile ret = GetBuildDirForTargetAsOutputFile(target, BuildDirType::OBJ); + ret.value().append(target->label().name()); + ret.value().push_back('_'); + ret.value().append(GetPCHLangSuffixForToolType(tool_type)); + ret.value().append(".pch"); + + return ret; +} + +void WriteOneFlag(const Target* target, + SubstitutionType subst_enum, + bool has_precompiled_headers, + Toolchain::ToolType tool_type, + const std::vector<std::string>& (ConfigValues::*getter)() + const, + EscapeOptions flag_escape_options, + PathOutput& path_output, + std::ostream& out, + bool write_substitution) { + if (!target->toolchain()->substitution_bits().used[subst_enum]) + return; + + if (write_substitution) + out << kSubstitutionNinjaNames[subst_enum] << " ="; + + if (has_precompiled_headers) { + const Tool* tool = target->toolchain()->GetTool(tool_type); + if (tool && tool->precompiled_header_type() == Tool::PCH_MSVC) { + // Name the .pch file. + out << " /Fp"; + path_output.WriteFile(out, GetWindowsPCHFile(target, tool_type)); + + // Enables precompiled headers and names the .h file. It's a string + // rather than a file name (so no need to rebase or use path_output). + out << " /Yu" << target->config_values().precompiled_header(); + RecursiveTargetConfigStringsToStream(target, getter, flag_escape_options, + out); + } else if (tool && tool->precompiled_header_type() == Tool::PCH_GCC) { + // The targets to build the .gch files should omit the -include flag + // below. To accomplish this, each substitution flag is overwritten in the + // target rule and these values are repeated. The -include flag is omitted + // in place of the required -x <header lang> flag for .gch targets. + RecursiveTargetConfigStringsToStream(target, getter, flag_escape_options, + out); + + // Compute the gch file (it will be language-specific). + std::vector<OutputFile> outputs; + GetPCHOutputFiles(target, tool_type, &outputs); + if (!outputs.empty()) { + // Trim the .gch suffix for the -include flag. + // e.g. for gch file foo/bar/target.precompiled.h.gch: + // -include foo/bar/target.precompiled.h + std::string pch_file = outputs[0].value(); + pch_file.erase(pch_file.length() - 4); + out << " -include " << pch_file; + } + } else { + RecursiveTargetConfigStringsToStream(target, getter, flag_escape_options, + out); + } + } else { + RecursiveTargetConfigStringsToStream(target, getter, flag_escape_options, + out); + } + + if (write_substitution) + out << std::endl; +} + +void GetPCHOutputFiles(const Target* target, + Toolchain::ToolType tool_type, + std::vector<OutputFile>* outputs) { + outputs->clear(); + + // Compute the tool. This must use the tool type passed in rather than the + // detected file type of the precompiled source file since the same + // precompiled source file will be used for separate C/C++ compiles. + const Tool* tool = target->toolchain()->GetTool(tool_type); + if (!tool) + return; + SubstitutionWriter::ApplyListToCompilerAsOutputFile( + target, target->config_values().precompiled_source(), tool->outputs(), + outputs); + + if (outputs->empty()) + return; + if (outputs->size() > 1) + outputs->resize(1); // Only link the first output from the compiler tool. + + std::string& output_value = (*outputs)[0].value(); + size_t extension_offset = FindExtensionOffset(output_value); + if (extension_offset == std::string::npos) { + // No extension found. + return; + } + DCHECK(extension_offset >= 1); + DCHECK(output_value[extension_offset - 1] == '.'); + + std::string output_extension; + Tool::PrecompiledHeaderType header_type = tool->precompiled_header_type(); + switch (header_type) { + case Tool::PCH_MSVC: + output_extension = GetWindowsPCHObjectExtension( + tool_type, output_value.substr(extension_offset - 1)); + break; + case Tool::PCH_GCC: + output_extension = GetGCCPCHOutputExtension(tool_type); + break; + case Tool::PCH_NONE: + NOTREACHED() << "No outputs for no PCH type."; + break; + } + output_value.replace(extension_offset - 1, std::string::npos, + output_extension); +} + +std::string GetGCCPCHOutputExtension(Toolchain::ToolType tool_type) { + const char* lang_suffix = GetPCHLangSuffixForToolType(tool_type); + std::string result = "."; + // For GCC, the output name must have a .gch suffix and be annotated with + // the language type. For example: + // obj/foo/target_name.header.h -> + // obj/foo/target_name.header.h-cc.gch + // In order for the compiler to pick it up, the output name (minus the .gch + // suffix MUST match whatever is passed to the -include flag). + result += "h-"; + result += lang_suffix; + result += ".gch"; + return result; +} + +std::string GetWindowsPCHObjectExtension(Toolchain::ToolType tool_type, + const std::string& obj_extension) { + const char* lang_suffix = GetPCHLangSuffixForToolType(tool_type); + std::string result = "."; + // For MSVC, annotate the obj files with the language type. For example: + // obj/foo/target_name.precompile.obj -> + // obj/foo/target_name.precompile.cc.obj + result += lang_suffix; + result += obj_extension; + return result; +}
diff --git a/tools/gn/ninja_target_command_util.h b/tools/gn/ninja_target_command_util.h new file mode 100644 index 0000000..619add3 --- /dev/null +++ b/tools/gn/ninja_target_command_util.h
@@ -0,0 +1,84 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef TOOLS_GN_NINJA_TARGET_COMMAND_WRITER_H_ +#define TOOLS_GN_NINJA_TARGET_COMMAND_WRITER_H_ + +#include "base/json/string_escape.h" +#include "tools/gn/config_values_extractors.h" +#include "tools/gn/escape.h" +#include "tools/gn/filesystem_utils.h" +#include "tools/gn/path_output.h" +#include "tools/gn/target.h" +#include "tools/gn/toolchain.h" + +struct DefineWriter { + DefineWriter() { options.mode = ESCAPE_NINJA_COMMAND; } + DefineWriter(EscapingMode mode, bool escape_strings) + : escape_strings(escape_strings) { + options.mode = mode; + } + + void operator()(const std::string& s, std::ostream& out) const { + out << " "; + if (escape_strings) { + std::string dest; + base::EscapeJSONString(s, false, &dest); + EscapeStringToStream(out, "-D" + dest, options); + return; + } + EscapeStringToStream(out, "-D" + s, options); + } + + EscapeOptions options; + bool escape_strings = false; +}; + +struct IncludeWriter { + explicit IncludeWriter(PathOutput& path_output) : path_output_(path_output) {} + ~IncludeWriter() = default; + + void operator()(const SourceDir& d, std::ostream& out) const { + std::ostringstream path_out; + path_output_.WriteDir(path_out, d, PathOutput::DIR_NO_LAST_SLASH); + const std::string& path = path_out.str(); + if (path[0] == '"') + out << " \"-I" << path.substr(1); + else + out << " -I" << path; + } + + PathOutput& path_output_; +}; + +// has_precompiled_headers is set when this substitution matches a tool type +// that supports precompiled headers, and this target supports precompiled +// headers. It doesn't indicate if the tool has precompiled headers (this +// will be looked up by this function). +// +// The tool_type indicates the corresponding tool for flags that are +// tool-specific (e.g. "cflags_c"). For non-tool-specific flags (e.g. +// "defines") tool_type should be TYPE_NONE. +void WriteOneFlag(const Target* target, + SubstitutionType subst_enum, + bool has_precompiled_headers, + Toolchain::ToolType tool_type, + const std::vector<std::string>& (ConfigValues::*getter)() + const, + EscapeOptions flag_escape_options, + PathOutput& path_output, + std::ostream& out, + bool write_substitution = true); + +// Fills |outputs| with the object or gch file for the precompiled header of the +// given type (flag type and tool type must match). +void GetPCHOutputFiles(const Target* target, + Toolchain::ToolType tool_type, + std::vector<OutputFile>* outputs); + +std::string GetGCCPCHOutputExtension(Toolchain::ToolType tool_type); +std::string GetWindowsPCHObjectExtension(Toolchain::ToolType tool_type, + const std::string& obj_extension); + +#endif // TOOLS_GN_NINJA_TARGET_COMMAND_WRITER_H_