[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_