Port "Eclipse CDT settings" file generation from GYP to GN

Differences between GN and GYP version:
- GN version does not generate .classpath file because there is a checked in
  version at tools/android/eclipse/.classpath
- GN version does not extract includes dirs and defines from clang compiler.
  A yet to be written python script will do this.

BUG=530676

Review URL: https://codereview.chromium.org/1649783003

Cr-Original-Commit-Position: refs/heads/master@{#376512}
Cr-Mirrored-From: https://chromium.googlesource.com/chromium/src
Cr-Mirrored-Commit: 19ca979da4f9f00296e52beb33f3ecff241852e8
diff --git a/tools/gn/BUILD.gn b/tools/gn/BUILD.gn
index eb60e08..683c8eb 100644
--- a/tools/gn/BUILD.gn
+++ b/tools/gn/BUILD.gn
@@ -50,6 +50,8 @@
     "copy_target_generator.h",
     "deps_iterator.cc",
     "deps_iterator.h",
+    "eclipse_writer.cc",
+    "eclipse_writer.h",
     "err.cc",
     "err.h",
     "escape.cc",
diff --git a/tools/gn/command_gen.cc b/tools/gn/command_gen.cc
index b4faaa5..7ba7789 100644
--- a/tools/gn/command_gen.cc
+++ b/tools/gn/command_gen.cc
@@ -10,6 +10,7 @@
 #include "base/timer/elapsed_timer.h"
 #include "tools/gn/build_settings.h"
 #include "tools/gn/commands.h"
+#include "tools/gn/eclipse_writer.h"
 #include "tools/gn/ninja_target_writer.h"
 #include "tools/gn/ninja_writer.h"
 #include "tools/gn/runtime_deps.h"
@@ -26,6 +27,7 @@
 
 const char kSwitchCheck[] = "check";
 const char kSwitchIde[] = "ide";
+const char kSwitchIdeValueEclipse[] = "eclipse";
 const char kSwitchIdeValueVs[] = "vs";
 
 // Called on worker thread to write the ninja file.
@@ -151,8 +153,16 @@
                   const BuildSettings* build_settings,
                   Builder* builder,
                   Err* err) {
-  if (ide == kSwitchIdeValueVs) {
-    base::ElapsedTimer timer;
+  base::ElapsedTimer timer;
+  if (ide == kSwitchIdeValueEclipse) {
+    bool res = EclipseWriter::RunAndWriteFile(build_settings, builder, err);
+    if (res) {
+      OutputString("Generating Eclipse settings took " +
+                   base::Int64ToString(timer.Elapsed().InMilliseconds()) +
+                   "ms\n");
+    }
+    return res;
+  } else if (ide == kSwitchIdeValueVs) {
     bool res =
         VisualStudioWriter::RunAndWriteFiles(build_settings, builder, err);
     if (res &&
@@ -189,8 +199,20 @@
     "  --ide=<ide_name>\n"
     "    Also generate files for an IDE. Currently supported values:\n"
     "      'vs' - Visual Studio project/solution files.\n"
+    "      'eclipse' - Eclipse CDT settings file.\n"
     "\n"
-    "  See \"gn help switches\" for the common command-line switches.\n";
+    "  See \"gn help switches\" for the common command-line switches.\n"
+    "\n"
+    "Eclipse IDE Support\n"
+    "\n"
+    "  GN DOES NOT generate Eclipse CDT projects. Instead, it generates a\n"
+    "  settings file which can be imported into an Eclipse CDT project. The\n"
+    "  XML file contains a list of include paths and defines. Because GN does\n"
+    "  not generate a full .cproject definition, it is not possible to\n"
+    "  properly define includes/defines for each file individually.\n"
+    "  Instead, one set of includes/defines is generated for the entire\n"
+    "  project. This works fairly well but may still result in a few indexer\n"
+    "  issues here and there.\n";
 
 int RunGen(const std::vector<std::string>& args) {
   base::ElapsedTimer timer;
diff --git a/tools/gn/eclipse_writer.cc b/tools/gn/eclipse_writer.cc
new file mode 100644
index 0000000..e38e247
--- /dev/null
+++ b/tools/gn/eclipse_writer.cc
@@ -0,0 +1,172 @@
+// Copyright 2016 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/eclipse_writer.h"
+
+#include <fstream>
+
+#include "base/files/file_path.h"
+#include "base/memory/scoped_ptr.h"
+#include "tools/gn/builder.h"
+#include "tools/gn/config_values_extractors.h"
+#include "tools/gn/filesystem_utils.h"
+#include "tools/gn/loader.h"
+#include "tools/gn/xml_element_writer.h"
+
+namespace {
+
+// Escapes |unescaped| for use in XML element content.
+std::string EscapeForXML(const std::string& unescaped) {
+  std::string result;
+  result.reserve(unescaped.length());
+  for (const char c : unescaped) {
+    if (c == '<')
+      result += "&lt;";
+    else if (c == '>')
+      result += "&gt;";
+    else if (c == '&')
+      result += "&amp;";
+    else
+      result.push_back(c);
+  }
+  return result;
+}
+
+}  // namespace
+
+EclipseWriter::EclipseWriter(const BuildSettings* build_settings,
+                             const Builder* builder,
+                             std::ostream& out)
+    : build_settings_(build_settings), builder_(builder), out_(out) {
+  languages_.push_back("C++ Source File");
+  languages_.push_back("C Source File");
+  languages_.push_back("Assembly Source File");
+  languages_.push_back("GNU C++");
+  languages_.push_back("GNU C");
+  languages_.push_back("Assembly");
+}
+
+EclipseWriter::~EclipseWriter() {}
+
+// static
+bool EclipseWriter::RunAndWriteFile(
+    const BuildSettings* build_settings,
+    const Builder* builder,
+    Err* err) {
+  base::FilePath file = build_settings->GetFullPath(build_settings->build_dir())
+                            .AppendASCII("eclipse-cdt-settings.xml");
+  std::ofstream file_out;
+  file_out.open(FilePathToUTF8(file).c_str(),
+                std::ios_base::out | std::ios_base::binary);
+  if (file_out.fail()) {
+    *err =
+        Err(Location(), "Couldn't open eclipse-cdt-settings.xml for writing");
+    return false;
+  }
+
+  EclipseWriter gen(build_settings, builder, file_out);
+  gen.Run();
+  return true;
+}
+
+void EclipseWriter::Run() {
+  GetAllIncludeDirs();
+  GetAllDefines();
+  WriteCDTSettings();
+}
+
+void EclipseWriter::GetAllIncludeDirs() {
+  std::vector<const Target*> targets = builder_->GetAllResolvedTargets();
+  for (const Target* target : targets) {
+    if (!UsesDefaultToolchain(target))
+      continue;
+
+    for (ConfigValuesIterator it(target); !it.done(); it.Next()) {
+      for (const SourceDir& include_dir : it.cur().include_dirs()) {
+        include_dirs_.insert(
+            FilePathToUTF8(build_settings_->GetFullPath(include_dir)));
+      }
+    }
+  }
+}
+
+void EclipseWriter::GetAllDefines() {
+  std::vector<const Target*> targets = builder_->GetAllResolvedTargets();
+  for (const Target* target : targets) {
+    if (!UsesDefaultToolchain(target))
+      continue;
+
+    for (ConfigValuesIterator it(target); !it.done(); it.Next()) {
+      for (const std::string& define : it.cur().defines()) {
+        size_t equal_pos = define.find('=');
+        std::string define_key;
+        std::string define_value;
+        if (equal_pos == std::string::npos) {
+          define_key = define;
+        } else {
+          define_key = define.substr(0, equal_pos);
+          define_value = define.substr(equal_pos + 1);
+        }
+        defines_[define_key] = define_value;
+      }
+    }
+  }
+}
+
+bool EclipseWriter::UsesDefaultToolchain(const Target* target) const {
+  return target->toolchain()->label() ==
+         builder_->loader()->GetDefaultToolchain();
+}
+
+void EclipseWriter::WriteCDTSettings() {
+  out_ << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" << std::endl;
+  XmlElementWriter cdt_properties_element(out_, "cdtprojectproperties",
+                                          XmlAttributes());
+
+  {
+    const char* kIncludesSectionName =
+        "org.eclipse.cdt.internal.ui.wizards.settingswizards.IncludePaths";
+    scoped_ptr<XmlElementWriter> section_element =
+        cdt_properties_element.SubElement(
+            "section", XmlAttributes("name", kIncludesSectionName));
+
+    section_element->SubElement(
+        "language", XmlAttributes("name", "holder for library settings"));
+
+    for (const std::string& language : languages_) {
+      scoped_ptr<XmlElementWriter> language_element =
+          section_element->SubElement("language",
+                                      XmlAttributes("name", language));
+      for (const std::string& include_dir : include_dirs_) {
+        language_element
+            ->SubElement("includepath",
+                         XmlAttributes("workspace_path", "false"))
+            ->Text(EscapeForXML(include_dir));
+      }
+    }
+  }
+
+  {
+    const char* kMacrosSectionName =
+        "org.eclipse.cdt.internal.ui.wizards.settingswizards.Macros";
+    scoped_ptr<XmlElementWriter> section_element =
+        cdt_properties_element.SubElement(
+            "section", XmlAttributes("name", kMacrosSectionName));
+
+    section_element->SubElement(
+        "language", XmlAttributes("name", "holder for library settings"));
+
+    for (const std::string& language : languages_) {
+      scoped_ptr<XmlElementWriter> language_element =
+          section_element->SubElement("language",
+                                      XmlAttributes("name", language));
+      for (const auto& key_val : defines_) {
+        scoped_ptr<XmlElementWriter> macro_element =
+            language_element->SubElement("macro");
+        macro_element->SubElement("name")->Text(EscapeForXML(key_val.first));
+        macro_element->SubElement("value")->Text(EscapeForXML(key_val.second));
+      }
+    }
+  }
+}
diff --git a/tools/gn/eclipse_writer.h b/tools/gn/eclipse_writer.h
new file mode 100644
index 0000000..560b872
--- /dev/null
+++ b/tools/gn/eclipse_writer.h
@@ -0,0 +1,67 @@
+// Copyright 2016 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_ECLIPSE_WRITER_H_
+#define TOOLS_GN_ECLIPSE_WRITER_H_
+
+#include <iosfwd>
+#include <map>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/macros.h"
+
+class BuildSettings;
+class Builder;
+class Err;
+class Target;
+
+class EclipseWriter {
+ public:
+  static bool RunAndWriteFile(const BuildSettings* build_settings,
+                              const Builder* builder,
+                              Err* err);
+
+ private:
+  EclipseWriter(const BuildSettings* build_settings,
+                const Builder* builder,
+                std::ostream& out);
+  ~EclipseWriter();
+
+  void Run();
+
+  // Populates |include_dirs_| with the include dirs of all the targets for the
+  // default toolchain.
+  void GetAllIncludeDirs();
+
+  // Populates |defines_| with the defines of all the targets for the default
+  // toolchain.
+  void GetAllDefines();
+
+  // Returns true if |target| uses the default toolchain.
+  bool UsesDefaultToolchain(const Target* target) const;
+
+  // Writes the XML settings file.
+  void WriteCDTSettings();
+
+  const BuildSettings* build_settings_;
+  const Builder* builder_;
+
+  // The output stream for the settings file.
+  std::ostream& out_;
+
+  // Eclipse languages for which the include dirs and defines apply.
+  std::vector<std::string> languages_;
+
+  // The include dirs of all the targets which use the default toolchain.
+  std::set<std::string> include_dirs_;
+
+  // The defines of all the targets which use the default toolchain.
+  std::map<std::string, std::string> defines_;
+
+  DISALLOW_COPY_AND_ASSIGN(EclipseWriter);
+};
+
+#endif  // TOOLS_GN_ECLIPSE_WRITER_H_
diff --git a/tools/gn/gn.gyp b/tools/gn/gn.gyp
index 24cff71..85754e0 100644
--- a/tools/gn/gn.gyp
+++ b/tools/gn/gn.gyp
@@ -50,6 +50,8 @@
         'copy_target_generator.h',
         'deps_iterator.cc',
         'deps_iterator.h',
+        'eclipse_writer.cc',
+        'eclipse_writer.h',
         'err.cc',
         'err.h',
         'escape.cc',
diff --git a/tools/gn/xml_element_writer.cc b/tools/gn/xml_element_writer.cc
index 51e0488..a608ee3 100644
--- a/tools/gn/xml_element_writer.cc
+++ b/tools/gn/xml_element_writer.cc
@@ -38,7 +38,9 @@
 
 XmlElementWriter::~XmlElementWriter() {
   if (!opening_tag_finished_) {
-    out_ << "/>" << std::endl;
+    // The XML spec does not require a space before the closing slash. However,
+    // Eclipse is unable to parse XML settings files if there is no space.
+    out_ << " />" << std::endl;
   } else {
     if (!one_line_)
       out_ << std::string(indent_, ' ');
diff --git a/tools/gn/xml_element_writer_unittest.cc b/tools/gn/xml_element_writer_unittest.cc
index 6702061..93dfd47 100644
--- a/tools/gn/xml_element_writer_unittest.cc
+++ b/tools/gn/xml_element_writer_unittest.cc
@@ -24,27 +24,27 @@
 TEST(XmlElementWriter, EmptyElement) {
   std::ostringstream out;
   { XmlElementWriter writer(out, "foo", XmlAttributes()); }
-  EXPECT_EQ("<foo/>\n", out.str());
+  EXPECT_EQ("<foo />\n", out.str());
 
   std::ostringstream out_attr;
   {
     XmlElementWriter writer(out_attr, "foo",
                             XmlAttributes("bar", "abc").add("baz", "123"));
   }
-  EXPECT_EQ("<foo bar=\"abc\" baz=\"123\"/>\n", out_attr.str());
+  EXPECT_EQ("<foo bar=\"abc\" baz=\"123\" />\n", out_attr.str());
 
   std::ostringstream out_indent;
   {
     XmlElementWriter writer(out_indent, "foo", XmlAttributes("bar", "baz"), 2);
   }
-  EXPECT_EQ("  <foo bar=\"baz\"/>\n", out_indent.str());
+  EXPECT_EQ("  <foo bar=\"baz\" />\n", out_indent.str());
 
   std::ostringstream out_writer;
   {
     XmlElementWriter writer(out_writer, "foo", "bar", MockValueWriter("baz"),
                             2);
   }
-  EXPECT_EQ("  <foo bar=\"baz\"/>\n", out_writer.str());
+  EXPECT_EQ("  <foo bar=\"baz\" />\n", out_writer.str());
 }
 
 TEST(XmlElementWriter, ElementWithText) {
@@ -67,10 +67,10 @@
   }
   std::string expected =
       "<root aaa=\"000\">\n"
-      "  <foo/>\n"
+      "  <foo />\n"
       "  <bar bbb=\"111\">hello</bar>\n"
       "  <baz ccc=\"222\">\n"
-      "    <grandchild/>\n"
+      "    <grandchild />\n"
       "  </baz>\n"
       "</root>\n";
   EXPECT_EQ(expected, out.str());