[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);
+}