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());