[compdb] Properly escape for JSON
Also adds tests for the compile_commands writer.
Change-Id: Ifee5c0b8b74a0c65ac488ead05370f344e76cfae
Reviewed-on: https://gn-review.googlesource.com/2540
Commit-Queue: Julie Hockett <juliehockett@google.com>
Reviewed-by: Brett Wilson <brettw@chromium.org>
Reviewed-by: Petr Hosek <phosek@google.com>
diff --git a/build/gen.py b/build/gen.py
index 454d49d..2dccbbb 100755
--- a/build/gen.py
+++ b/build/gen.py
@@ -544,6 +544,7 @@
'tools/gn/builder_unittest.cc',
'tools/gn/c_include_iterator_unittest.cc',
'tools/gn/command_format_unittest.cc',
+ 'tools/gn/compile_commands_writer_unittest.cc',
'tools/gn/config_unittest.cc',
'tools/gn/config_values_extractors_unittest.cc',
'tools/gn/escape_unittest.cc',
diff --git a/tools/gn/compile_commands_writer.cc b/tools/gn/compile_commands_writer.cc
index 772d8d2..6e31b23 100644
--- a/tools/gn/compile_commands_writer.cc
+++ b/tools/gn/compile_commands_writer.cc
@@ -6,6 +6,7 @@
#include <sstream>
+#include "base/json/string_escape.h"
#include "base/strings/stringprintf.h"
#include "tools/gn/builder.h"
#include "tools/gn/config_values_extractors.h"
@@ -55,43 +56,44 @@
RecursiveTargetConfigToStream<std::string>(
target, &ConfigValues::defines,
DefineWriter(ESCAPE_NINJA_PREFORMATTED_COMMAND, true), defines_out);
- flags.defines = defines_out.str();
+ base::EscapeJSONString(defines_out.str(), false, &flags.defines);
std::ostringstream includes_out;
RecursiveTargetConfigToStream<SourceDir>(target, &ConfigValues::include_dirs,
IncludeWriter(path_output),
includes_out);
- flags.includes = includes_out.str();
+ base::EscapeJSONString(includes_out.str(), false, &flags.includes);
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();
+ base::EscapeJSONString(cflags_out.str(), false, &flags.cflags);
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();
+ base::EscapeJSONString(cflags_c_out.str(), false, &flags.cflags_c);
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();
+ base::EscapeJSONString(cflags_cc_out.str(), false, &flags.cflags_cc);
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();
+ path_output, cflags_objc_out,
+ /*write_substitution=*/false);
+ base::EscapeJSONString(cflags_objc_out.str(), false, &flags.cflags_objc);
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();
+ base::EscapeJSONString(cflags_objcc_out.str(), false, &flags.cflags_objcc);
}
void WriteFile(const SourceFile& source,
@@ -193,10 +195,11 @@
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) {
+} // namespace
+
+void CompileCommandsWriter::RenderJSON(const BuildSettings* build_settings,
+ 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("[");
@@ -259,8 +262,6 @@
compile_commands->append(kPrettyPrintLineEnding);
}
-} // namespace
-
bool CompileCommandsWriter::RunAndWriteFiles(
const BuildSettings* build_settings,
const Builder& builder,
@@ -277,7 +278,7 @@
std::vector<const Target*> all_targets = builder.GetAllResolvedTargets();
std::string json;
- RenderJSON(build_settings, builder, all_targets, &json);
+ RenderJSON(build_settings, 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
index ad98dc2..2f1ec83 100644
--- a/tools/gn/compile_commands_writer.h
+++ b/tools/gn/compile_commands_writer.h
@@ -18,6 +18,9 @@
const std::string& file_name,
bool quiet,
Err* err);
+ static void RenderJSON(const BuildSettings* build_settings,
+ std::vector<const Target*>& all_targets,
+ std::string* compile_commands);
};
#endif // TOOLS_GN_COMPILE_COMMANDS_WRITER_H_
diff --git a/tools/gn/compile_commands_writer_unittest.cc b/tools/gn/compile_commands_writer_unittest.cc
new file mode 100644
index 0000000..57c89dc
--- /dev/null
+++ b/tools/gn/compile_commands_writer_unittest.cc
@@ -0,0 +1,590 @@
+// 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 <memory>
+#include <sstream>
+#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"
+#include "tools/gn/test_with_scope.h"
+#include "util/build_config.h"
+#include "util/test/test.h"
+
+namespace {
+
+// InputConversion needs a global scheduler object.
+class CompileCommandsTest : public TestWithScheduler {
+ public:
+ CompileCommandsTest() = default;
+
+ const BuildSettings* build_settings() { return setup_.build_settings(); }
+ const Settings* settings() { return setup_.settings(); }
+ const TestWithScope& setup() { return setup_; }
+ const Toolchain* toolchain() { return setup_.toolchain(); }
+
+ private:
+ TestWithScope setup_;
+};
+
+} // namespace
+
+TEST_F(CompileCommandsTest, SourceSet) {
+ Err err;
+
+ std::vector<const Target*> targets;
+ Target target(settings(), Label(SourceDir("//foo/"), "bar"));
+ target.set_output_type(Target::SOURCE_SET);
+ target.visibility().SetPublic();
+ target.sources().push_back(SourceFile("//foo/input1.cc"));
+ target.sources().push_back(SourceFile("//foo/input2.cc"));
+ // Also test object files, which should be just passed through to the
+ // dependents to link.
+ target.sources().push_back(SourceFile("//foo/input3.o"));
+ target.sources().push_back(SourceFile("//foo/input4.obj"));
+ target.SetToolchain(toolchain());
+ ASSERT_TRUE(target.OnResolved(&err));
+ targets.push_back(&target);
+
+ // Source set itself.
+ {
+ std::string out;
+ CompileCommandsWriter writer;
+ writer.RenderJSON(build_settings(), targets, &out);
+
+#if defined(OS_WIN)
+ const char expected[] =
+ "[\r\n"
+ " {\r\n"
+ " \"file\": \"../../foo/input1.cc\",\r\n"
+ " \"directory\": \"out/Debug/\",\r\n"
+ " \"command\": \"c++ ../../foo/input1.cc -o "
+ "obj/foo/bar.input1.o\"\r\n"
+ " },\r\n"
+ " {\r\n"
+ " \"file\": \"../../foo/input2.cc\",\r\n"
+ " \"directory\": \"out/Debug/\",\r\n"
+ " \"command\": \"c++ ../../foo/input2.cc -o "
+ "obj/foo/bar.input2.o\"\r\n"
+ " }\r\n"
+ "]\r\n";
+#else
+ const char expected[] =
+ "[\n"
+ " {\n"
+ " \"file\": \"../../foo/input1.cc\",\n"
+ " \"directory\": \"out/Debug/\",\n"
+ " \"command\": \"c++ ../../foo/input1.cc -o "
+ "obj/foo/bar.input1.o\"\n"
+ " },\n"
+ " {\n"
+ " \"file\": \"../../foo/input2.cc\",\n"
+ " \"directory\": \"out/Debug/\",\n"
+ " \"command\": \"c++ ../../foo/input2.cc -o "
+ "obj/foo/bar.input2.o\"\n"
+ " }\n"
+ "]\n";
+#endif
+ EXPECT_EQ(expected, out);
+ }
+
+ // A shared library that depends on the source set.
+ Target shlib_target(settings(), Label(SourceDir("//foo/"), "shlib"));
+ shlib_target.sources().push_back(SourceFile("//foo/input3.cc"));
+ shlib_target.set_output_type(Target::SHARED_LIBRARY);
+ shlib_target.public_deps().push_back(LabelTargetPair(&target));
+ shlib_target.SetToolchain(toolchain());
+ ASSERT_TRUE(shlib_target.OnResolved(&err));
+ targets.push_back(&shlib_target);
+
+ {
+ std::string out;
+ CompileCommandsWriter writer;
+ writer.RenderJSON(build_settings(), targets, &out);
+
+#if defined(OS_WIN)
+ const char expected[] =
+ "[\r\n"
+ " {\r\n"
+ " \"file\": \"../../foo/input1.cc\",\r\n"
+ " \"directory\": \"out/Debug/\",\r\n"
+ " \"command\": \"c++ ../../foo/input1.cc -o "
+ "obj/foo/bar.input1.o\"\r\n"
+ " },\r\n"
+ " {\r\n"
+ " \"file\": \"../../foo/input2.cc\",\r\n"
+ " \"directory\": \"out/Debug/\",\r\n"
+ " \"command\": \"c++ ../../foo/input2.cc -o "
+ "obj/foo/bar.input2.o\"\r\n"
+ " },\r\n"
+ " {\r\n"
+ " \"file\": \"../../foo/input3.cc\",\r\n"
+ " \"directory\": \"out/Debug/\",\r\n"
+ " \"command\": \"c++ ../../foo/input3.cc -o "
+ "obj/foo/libshlib.input3.o\"\r\n"
+ " }\r\n"
+ "]\r\n";
+#else
+ const char expected[] =
+ "[\n"
+ " {\n"
+ " \"file\": \"../../foo/input1.cc\",\n"
+ " \"directory\": \"out/Debug/\",\n"
+ " \"command\": \"c++ ../../foo/input1.cc -o "
+ "obj/foo/bar.input1.o\"\n"
+ " },\n"
+ " {\n"
+ " \"file\": \"../../foo/input2.cc\",\n"
+ " \"directory\": \"out/Debug/\",\n"
+ " \"command\": \"c++ ../../foo/input2.cc -o "
+ "obj/foo/bar.input2.o\"\n"
+ " },\n"
+ " {\n"
+ " \"file\": \"../../foo/input3.cc\",\n"
+ " \"directory\": \"out/Debug/\",\n"
+ " \"command\": \"c++ ../../foo/input3.cc -o "
+ "obj/foo/libshlib.input3.o\"\n"
+ " }\n"
+ "]\n";
+#endif
+ EXPECT_EQ(expected, out);
+ }
+
+ // A static library that depends on the source set (should not link it).
+ Target stlib_target(settings(), Label(SourceDir("//foo/"), "stlib"));
+ stlib_target.sources().push_back(SourceFile("//foo/input4.cc"));
+ stlib_target.set_output_type(Target::STATIC_LIBRARY);
+ stlib_target.public_deps().push_back(LabelTargetPair(&target));
+ stlib_target.SetToolchain(toolchain());
+ ASSERT_TRUE(stlib_target.OnResolved(&err));
+ targets.push_back(&stlib_target);
+
+ {
+ std::string out;
+ CompileCommandsWriter writer;
+ writer.RenderJSON(build_settings(), targets, &out);
+
+#if defined(OS_WIN)
+ const char expected[] =
+ "[\r\n"
+ " {\r\n"
+ " \"file\": \"../../foo/input1.cc\",\r\n"
+ " \"directory\": \"out/Debug/\",\r\n"
+ " \"command\": \"c++ ../../foo/input1.cc -o "
+ "obj/foo/bar.input1.o\"\r\n"
+ " },\r\n"
+ " {\r\n"
+ " \"file\": \"../../foo/input2.cc\",\r\n"
+ " \"directory\": \"out/Debug/\",\r\n"
+ " \"command\": \"c++ ../../foo/input2.cc -o "
+ "obj/foo/bar.input2.o\"\r\n"
+ " },\r\n"
+ " {\r\n"
+ " \"file\": \"../../foo/input3.cc\",\r\n"
+ " \"directory\": \"out/Debug/\",\r\n"
+ " \"command\": \"c++ ../../foo/input3.cc -o "
+ "obj/foo/libshlib.input3.o\"\r\n"
+ " },\r\n"
+ " {\r\n"
+ " \"file\": \"../../foo/input4.cc\",\r\n"
+ " \"directory\": \"out/Debug/\",\r\n"
+ " \"command\": \"c++ ../../foo/input4.cc -o "
+ "obj/foo/libstlib.input4.o\"\r\n"
+ " }\r\n"
+ "]\r\n";
+#else
+ const char expected[] =
+ "[\n"
+ " {\n"
+ " \"file\": \"../../foo/input1.cc\",\n"
+ " \"directory\": \"out/Debug/\",\n"
+ " \"command\": \"c++ ../../foo/input1.cc -o "
+ "obj/foo/bar.input1.o\"\n"
+ " },\n"
+ " {\n"
+ " \"file\": \"../../foo/input2.cc\",\n"
+ " \"directory\": \"out/Debug/\",\n"
+ " \"command\": \"c++ ../../foo/input2.cc -o "
+ "obj/foo/bar.input2.o\"\n"
+ " },\n"
+ " {\n"
+ " \"file\": \"../../foo/input3.cc\",\n"
+ " \"directory\": \"out/Debug/\",\n"
+ " \"command\": \"c++ ../../foo/input3.cc -o "
+ "obj/foo/libshlib.input3.o\"\n"
+ " },\n"
+ " {\n"
+ " \"file\": \"../../foo/input4.cc\",\n"
+ " \"directory\": \"out/Debug/\",\n"
+ " \"command\": \"c++ ../../foo/input4.cc -o "
+ "obj/foo/libstlib.input4.o\"\n"
+ " }\n"
+ "]\n";
+#endif
+ EXPECT_EQ(expected, out);
+ }
+}
+
+TEST_F(CompileCommandsTest, EscapeDefines) {
+ Err err;
+
+ std::vector<const Target*> targets;
+ TestTarget target(setup(), "//foo:bar", Target::STATIC_LIBRARY);
+ target.sources().push_back(SourceFile("//foo/input.cc"));
+ target.config_values().defines().push_back("BOOL_DEF");
+ target.config_values().defines().push_back("INT_DEF=123");
+ target.config_values().defines().push_back("STR_DEF=\"ABCD-1\"");
+ ASSERT_TRUE(target.OnResolved(&err));
+ targets.push_back(&target);
+
+ std::string out;
+ CompileCommandsWriter writer;
+ writer.RenderJSON(build_settings(), targets, &out);
+
+ const char expected[] =
+ "-DBOOL_DEF -DINT_DEF=123 -DSTR_DEF=\\\\\\\"ABCD-1\\\\\\\"";
+ EXPECT_TRUE(out.find(expected) != std::string::npos);
+}
+
+TEST_F(CompileCommandsTest, WinPrecompiledHeaders) {
+ Err err;
+
+ // This setup's toolchain does not have precompiled headers defined.
+ // A precompiled header toolchain.
+ Settings pch_settings(build_settings(), "withpch/");
+ Toolchain pch_toolchain(&pch_settings,
+ Label(SourceDir("//toolchain/"), "withpch"));
+ pch_settings.set_toolchain_label(pch_toolchain.label());
+ pch_settings.set_default_toolchain_label(toolchain()->label());
+
+ // Declare a C++ compiler that supports PCH.
+ std::unique_ptr<Tool> cxx_tool = std::make_unique<Tool>();
+ TestWithScope::SetCommandForTool(
+ "c++ {{source}} {{cflags}} {{cflags_cc}} {{defines}} {{include_dirs}} "
+ "-o {{output}}",
+ cxx_tool.get());
+ cxx_tool->set_outputs(SubstitutionList::MakeForTest(
+ "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.o"));
+ cxx_tool->set_precompiled_header_type(Tool::PCH_MSVC);
+ pch_toolchain.SetTool(Toolchain::TYPE_CXX, std::move(cxx_tool));
+
+ // Add a C compiler as well.
+ std::unique_ptr<Tool> cc_tool = std::make_unique<Tool>();
+ TestWithScope::SetCommandForTool(
+ "cc {{source}} {{cflags}} {{cflags_c}} {{defines}} {{include_dirs}} "
+ "-o {{output}}",
+ cc_tool.get());
+ cc_tool->set_outputs(SubstitutionList::MakeForTest(
+ "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.o"));
+ cc_tool->set_precompiled_header_type(Tool::PCH_MSVC);
+ pch_toolchain.SetTool(Toolchain::TYPE_CC, std::move(cc_tool));
+ pch_toolchain.ToolchainSetupComplete();
+
+ // This target doesn't specify precompiled headers.
+ {
+ std::vector<const Target*> targets;
+ Target no_pch_target(&pch_settings,
+ Label(SourceDir("//foo/"), "no_pch_target"));
+ no_pch_target.set_output_type(Target::SOURCE_SET);
+ no_pch_target.visibility().SetPublic();
+ no_pch_target.sources().push_back(SourceFile("//foo/input1.cc"));
+ no_pch_target.sources().push_back(SourceFile("//foo/input2.c"));
+ no_pch_target.config_values().cflags_c().push_back("-std=c99");
+ no_pch_target.SetToolchain(&pch_toolchain);
+ ASSERT_TRUE(no_pch_target.OnResolved(&err));
+ targets.push_back(&no_pch_target);
+
+ std::string out;
+ CompileCommandsWriter writer;
+ writer.RenderJSON(build_settings(), targets, &out);
+
+#if defined(OS_WIN)
+ const char no_pch_expected[] =
+ "[\r\n"
+ " {\r\n"
+ " \"file\": \"../../foo/input1.cc\",\r\n"
+ " \"directory\": \"out/Debug/\",\r\n"
+ " \"command\": \"c++ ../../foo/input1.cc -o "
+ "withpch/obj/foo/no_pch_target.input1.o\"\r\n"
+ " },\r\n"
+ " {\r\n"
+ " \"file\": \"../../foo/input2.c\",\r\n"
+ " \"directory\": \"out/Debug/\",\r\n"
+ " \"command\": \"cc ../../foo/input2.c -std=c99 -o "
+ "withpch/obj/foo/no_pch_target.input2.o\"\r\n"
+ " }\r\n"
+ "]\r\n";
+#else
+ const char no_pch_expected[] =
+ "[\n"
+ " {\n"
+ " \"file\": \"../../foo/input1.cc\",\n"
+ " \"directory\": \"out/Debug/\",\n"
+ " \"command\": \"c++ ../../foo/input1.cc -o "
+ "withpch/obj/foo/no_pch_target.input1.o\"\n"
+ " },\n"
+ " {\n"
+ " \"file\": \"../../foo/input2.c\",\n"
+ " \"directory\": \"out/Debug/\",\n"
+ " \"command\": \"cc ../../foo/input2.c -std=c99 -o "
+ "withpch/obj/foo/no_pch_target.input2.o\"\n"
+ " }\n"
+ "]\n";
+#endif
+ EXPECT_EQ(no_pch_expected, out);
+ }
+
+ // This target specifies PCH.
+ {
+ std::vector<const Target*> targets;
+ Target pch_target(&pch_settings, Label(SourceDir("//foo/"), "pch_target"));
+ pch_target.config_values().set_precompiled_header("build/precompile.h");
+ pch_target.config_values().set_precompiled_source(
+ SourceFile("//build/precompile.cc"));
+ pch_target.set_output_type(Target::SOURCE_SET);
+ pch_target.visibility().SetPublic();
+ pch_target.sources().push_back(SourceFile("//foo/input1.cc"));
+ pch_target.sources().push_back(SourceFile("//foo/input2.c"));
+ pch_target.SetToolchain(&pch_toolchain);
+ ASSERT_TRUE(pch_target.OnResolved(&err));
+ targets.push_back(&pch_target);
+
+ std::string out;
+ CompileCommandsWriter writer;
+ writer.RenderJSON(build_settings(), targets, &out);
+
+#if defined(OS_WIN)
+ const char pch_win_expected[] =
+ "[\r\n"
+ " {\r\n"
+ " \"file\": \"../../foo/input1.cc\",\r\n"
+ " \"directory\": \"out/Debug/\",\r\n"
+ " \"command\": \"c++ ../../foo/input1.cc "
+ "/Fpwithpch/obj/foo/pch_target_cc.pch /Yubuild/precompile.h -o "
+ "withpch/obj/foo/pch_target.input1.o\"\r\n"
+ " },\r\n"
+ " {\r\n"
+ " \"file\": \"../../foo/input2.c\",\r\n"
+ " \"directory\": \"out/Debug/\",\r\n"
+ " \"command\": \"cc ../../foo/input2.c "
+ "/Fpwithpch/obj/foo/pch_target_c.pch /Yubuild/precompile.h -o "
+ "withpch/obj/foo/pch_target.input2.o\"\r\n"
+ " }\r\n"
+ "]\r\n";
+#else
+ const char pch_win_expected[] =
+ "[\n"
+ " {\n"
+ " \"file\": \"../../foo/input1.cc\",\n"
+ " \"directory\": \"out/Debug/\",\n"
+ " \"command\": \"c++ ../../foo/input1.cc "
+ "/Fpwithpch/obj/foo/pch_target_cc.pch /Yubuild/precompile.h -o "
+ "withpch/obj/foo/pch_target.input1.o\"\n"
+ " },\n"
+ " {\n"
+ " \"file\": \"../../foo/input2.c\",\n"
+ " \"directory\": \"out/Debug/\",\n"
+ " \"command\": \"cc ../../foo/input2.c "
+ "/Fpwithpch/obj/foo/pch_target_c.pch /Yubuild/precompile.h -o "
+ "withpch/obj/foo/pch_target.input2.o\"\n"
+ " }\n"
+ "]\n";
+#endif
+ EXPECT_EQ(pch_win_expected, out);
+ }
+}
+
+TEST_F(CompileCommandsTest, GCCPrecompiledHeaders) {
+ Err err;
+
+ // This setup's toolchain does not have precompiled headers defined.
+ // A precompiled header toolchain.
+ Settings pch_settings(build_settings(), "withpch/");
+ Toolchain pch_toolchain(&pch_settings,
+ Label(SourceDir("//toolchain/"), "withpch"));
+ pch_settings.set_toolchain_label(pch_toolchain.label());
+ pch_settings.set_default_toolchain_label(toolchain()->label());
+
+ // Declare a C++ compiler that supports PCH.
+ std::unique_ptr<Tool> cxx_tool = std::make_unique<Tool>();
+ TestWithScope::SetCommandForTool(
+ "c++ {{source}} {{cflags}} {{cflags_cc}} {{defines}} {{include_dirs}} "
+ "-o {{output}}",
+ cxx_tool.get());
+ cxx_tool->set_outputs(SubstitutionList::MakeForTest(
+ "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.o"));
+ cxx_tool->set_precompiled_header_type(Tool::PCH_GCC);
+ pch_toolchain.SetTool(Toolchain::TYPE_CXX, std::move(cxx_tool));
+ pch_toolchain.ToolchainSetupComplete();
+
+ // Add a C compiler as well.
+ std::unique_ptr<Tool> cc_tool = std::make_unique<Tool>();
+ TestWithScope::SetCommandForTool(
+ "cc {{source}} {{cflags}} {{cflags_c}} {{defines}} {{include_dirs}} "
+ "-o {{output}}",
+ cc_tool.get());
+ cc_tool->set_outputs(SubstitutionList::MakeForTest(
+ "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.o"));
+ cc_tool->set_precompiled_header_type(Tool::PCH_GCC);
+ pch_toolchain.SetTool(Toolchain::TYPE_CC, std::move(cc_tool));
+ pch_toolchain.ToolchainSetupComplete();
+
+ // This target doesn't specify precompiled headers.
+ {
+ std::vector<const Target*> targets;
+ Target no_pch_target(&pch_settings,
+ Label(SourceDir("//foo/"), "no_pch_target"));
+ no_pch_target.set_output_type(Target::SOURCE_SET);
+ no_pch_target.visibility().SetPublic();
+ no_pch_target.sources().push_back(SourceFile("//foo/input1.cc"));
+ no_pch_target.sources().push_back(SourceFile("//foo/input2.c"));
+ no_pch_target.config_values().cflags_c().push_back("-std=c99");
+ no_pch_target.SetToolchain(&pch_toolchain);
+ ASSERT_TRUE(no_pch_target.OnResolved(&err));
+ targets.push_back(&no_pch_target);
+
+ std::string out;
+ CompileCommandsWriter writer;
+ writer.RenderJSON(build_settings(), targets, &out);
+
+#if defined(OS_WIN)
+ const char no_pch_expected[] =
+ "[\r\n"
+ " {\r\n"
+ " \"file\": \"../../foo/input1.cc\",\r\n"
+ " \"directory\": \"out/Debug/\",\r\n"
+ " \"command\": \"c++ ../../foo/input1.cc -o "
+ "withpch/obj/foo/no_pch_target.input1.o\"\r\n"
+ " },\r\n"
+ " {\r\n"
+ " \"file\": \"../../foo/input2.c\",\r\n"
+ " \"directory\": \"out/Debug/\",\r\n"
+ " \"command\": \"cc ../../foo/input2.c -std=c99 -o "
+ "withpch/obj/foo/no_pch_target.input2.o\"\r\n"
+ " }\r\n"
+ "]\r\n";
+#else
+ const char no_pch_expected[] =
+ "[\n"
+ " {\n"
+ " \"file\": \"../../foo/input1.cc\",\n"
+ " \"directory\": \"out/Debug/\",\n"
+ " \"command\": \"c++ ../../foo/input1.cc -o "
+ "withpch/obj/foo/no_pch_target.input1.o\"\n"
+ " },\n"
+ " {\n"
+ " \"file\": \"../../foo/input2.c\",\n"
+ " \"directory\": \"out/Debug/\",\n"
+ " \"command\": \"cc ../../foo/input2.c -std=c99 -o "
+ "withpch/obj/foo/no_pch_target.input2.o\"\n"
+ " }\n"
+ "]\n";
+#endif
+ EXPECT_EQ(no_pch_expected, out);
+ }
+
+ // This target specifies PCH.
+ {
+ std::vector<const Target*> targets;
+ Target pch_target(&pch_settings, Label(SourceDir("//foo/"), "pch_target"));
+ pch_target.config_values().set_precompiled_source(
+ SourceFile("//build/precompile.h"));
+ pch_target.config_values().cflags_c().push_back("-std=c99");
+ pch_target.set_output_type(Target::SOURCE_SET);
+ pch_target.visibility().SetPublic();
+ pch_target.sources().push_back(SourceFile("//foo/input1.cc"));
+ pch_target.sources().push_back(SourceFile("//foo/input2.c"));
+ pch_target.SetToolchain(&pch_toolchain);
+ ASSERT_TRUE(pch_target.OnResolved(&err));
+ targets.push_back(&pch_target);
+
+ std::string out;
+ CompileCommandsWriter writer;
+ writer.RenderJSON(build_settings(), targets, &out);
+
+#if defined(OS_WIN)
+ const char pch_gcc_expected[] =
+ "[\r\n"
+ " {\r\n"
+ " \"file\": \"../../foo/input1.cc\",\r\n"
+ " \"directory\": \"out/Debug/\",\r\n"
+ " \"command\": \"c++ ../../foo/input1.cc -include "
+ "withpch/obj/build/pch_target.precompile.h-cc -o "
+ "withpch/obj/foo/pch_target.input1.o\"\r\n"
+ " },\r\n"
+ " {\r\n"
+ " \"file\": \"../../foo/input2.c\",\r\n"
+ " \"directory\": \"out/Debug/\",\r\n"
+ " \"command\": \"cc ../../foo/input2.c -std=c99 -include "
+ "withpch/obj/build/pch_target.precompile.h-c -o "
+ "withpch/obj/foo/pch_target.input2.o\"\r\n"
+ " }\r\n"
+ "]\r\n";
+#else
+ const char pch_gcc_expected[] =
+ "[\n"
+ " {\n"
+ " \"file\": \"../../foo/input1.cc\",\n"
+ " \"directory\": \"out/Debug/\",\n"
+ " \"command\": \"c++ ../../foo/input1.cc -include "
+ "withpch/obj/build/pch_target.precompile.h-cc -o "
+ "withpch/obj/foo/pch_target.input1.o\"\n"
+ " },\n"
+ " {\n"
+ " \"file\": \"../../foo/input2.c\",\n"
+ " \"directory\": \"out/Debug/\",\n"
+ " \"command\": \"cc ../../foo/input2.c -std=c99 -include "
+ "withpch/obj/build/pch_target.precompile.h-c -o "
+ "withpch/obj/foo/pch_target.input2.o\"\n"
+ " }\n"
+ "]\n";
+#endif
+ EXPECT_EQ(pch_gcc_expected, out);
+ }
+}
+
+TEST_F(CompileCommandsTest, EscapedFlags) {
+ Err err;
+
+ std::vector<const Target*> targets;
+ Target target(settings(), Label(SourceDir("//foo/"), "bar"));
+ target.set_output_type(Target::SOURCE_SET);
+ target.sources().push_back(SourceFile("//foo/input1.c"));
+ target.config_values().cflags_c().push_back("-DCONFIG=\"/config\"");
+ target.SetToolchain(toolchain());
+ ASSERT_TRUE(target.OnResolved(&err));
+ targets.push_back(&target);
+
+ std::string out;
+ CompileCommandsWriter writer;
+ writer.RenderJSON(build_settings(), targets, &out);
+
+#if defined(OS_WIN)
+ const char expected[] =
+ "[\r\n"
+ " {\r\n"
+ " \"file\": \"../../foo/input1.c\",\r\n"
+ " \"directory\": \"out/Debug/\",\r\n"
+ " \"command\": \"cc ../../foo/input1.c -DCONFIG=\\\"/config\\\" "
+ "-o obj/foo/bar.input1.o\"\r\n"
+ " }\r\n"
+ "]\r\n";
+#else
+ const char expected[] =
+ "[\n"
+ " {\n"
+ " \"file\": \"../../foo/input1.c\",\n"
+ " \"directory\": \"out/Debug/\",\n"
+ " \"command\": \"cc ../../foo/input1.c -DCONFIG=\\\"/config\\\" "
+ "-o obj/foo/bar.input1.o\"\n"
+ " }\n"
+ "]\n";
+#endif
+ EXPECT_EQ(expected, out);
+}