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 += "<";
+ else if (c == '>')
+ result += ">";
+ else if (c == '&')
+ result += "&";
+ 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());