Add support for generating QtCreator projects from GN.

This adds a new command line argument "--ide=" value to "gn gen"
which, when specified, generates a QtCreator project.

QtCreator is a quite powerful general-purpose (despite Qt in the name)
IDE when developing on Linux system with code completion and navigation.

Some interest in it has been demonstrated in the following thread:
https://groups.google.com/a/chromium.org/forum/#!topic/gn-dev/9U4_ytjrah8

BUG=

Review-Url: https://codereview.chromium.org/1883093002
Cr-Original-Commit-Position: refs/heads/master@{#393514}
Cr-Mirrored-From: https://chromium.googlesource.com/chromium/src
Cr-Mirrored-Commit: af72603e3cd01c17d98c3edf982b064516260bc6
diff --git a/tools/gn/BUILD.gn b/tools/gn/BUILD.gn
index d377ca3..2dfcf7f 100644
--- a/tools/gn/BUILD.gn
+++ b/tools/gn/BUILD.gn
@@ -146,6 +146,8 @@
     "path_output.h",
     "pattern.cc",
     "pattern.h",
+    "qt_creator_writer.cc",
+    "qt_creator_writer.h",
     "runtime_deps.cc",
     "runtime_deps.h",
     "scheduler.cc",
diff --git a/tools/gn/command_gen.cc b/tools/gn/command_gen.cc
index 74a5d90..f872304 100644
--- a/tools/gn/command_gen.cc
+++ b/tools/gn/command_gen.cc
@@ -13,6 +13,7 @@
 #include "tools/gn/eclipse_writer.h"
 #include "tools/gn/ninja_target_writer.h"
 #include "tools/gn/ninja_writer.h"
+#include "tools/gn/qt_creator_writer.h"
 #include "tools/gn/runtime_deps.h"
 #include "tools/gn/scheduler.h"
 #include "tools/gn/setup.h"
@@ -30,6 +31,7 @@
 const char kSwitchFilters[] = "filters";
 const char kSwitchIde[] = "ide";
 const char kSwitchIdeValueEclipse[] = "eclipse";
+const char kSwitchIdeValueQtCreator[] = "qtcreator";
 const char kSwitchIdeValueVs[] = "vs";
 const char kSwitchIdeValueVs2013[] = "vs2013";
 const char kSwitchIdeValueVs2015[] = "vs2015";
@@ -206,6 +208,18 @@
                    "ms\n");
     }
     return res;
+  } else if (ide == kSwitchIdeValueQtCreator) {
+    std::string root_target;
+    if (command_line->HasSwitch(kSwitchRootTarget))
+      root_target = command_line->GetSwitchValueASCII(kSwitchRootTarget);
+    bool res = QtCreatorWriter::RunAndWriteFile(build_settings, builder, err,
+                                                root_target);
+    if (res && !command_line->HasSwitch(switches::kQuiet)) {
+      OutputString("Generating QtCreator projects took " +
+                   base::Int64ToString(timer.Elapsed().InMilliseconds()) +
+                   "ms\n");
+    }
+    return res;
   }
 
   *err = Err(Location(), "Unknown IDE: " + ide);
@@ -244,6 +258,7 @@
     "      \"vs2013\" - Visual Studio 2013 project/solution files.\n"
     "      \"vs2015\" - Visual Studio 2015 project/solution files.\n"
     "      \"xcode\" - Xcode workspace/solution files.\n"
+    "      \"qtcreator\" - QtCreator project files.\n"
     "\n"
     "  --filters=<path_prefixes>\n"
     "      Semicolon-separated list of label patterns used to limit the set\n"
@@ -269,9 +284,17 @@
     "      using goma for example.\n"
     "\n"
     "  --root-target=<target_name>\n"
-    "      Name of the target corresponding to \"All\" target in Xcode. If\n"
-    "      unset, \"All\" invokes ninja without any target thus build all the\n"
-    "      targets.\n"
+    "      Name of the target corresponding to \"All\" target in Xcode.\n"
+    "      If unset, \"All\" invokes ninja without any target\n"
+    "      and builds everything.\n"
+    "\n"
+    "QtCreator Flags\n"
+    "\n"
+    "  --root-target=<target_name>\n"
+    "      Name of the root target for which the QtCreator project will be\n"
+    "      generated to contain files of it and its dependencies. If unset, \n"
+    "      the whole build graph will be omitted.\n"
+    "\n"
     "\n"
     "Eclipse IDE Support\n"
     "\n"
diff --git a/tools/gn/gn.gyp b/tools/gn/gn.gyp
index 86b5d4b..8428948 100644
--- a/tools/gn/gn.gyp
+++ b/tools/gn/gn.gyp
@@ -146,6 +146,8 @@
         'path_output.h',
         'pattern.cc',
         'pattern.h',
+        'qt_creator_writer.cc',
+        'qt_creator_writer.h',
         'runtime_deps.cc',
         'runtime_deps.h',
         'scheduler.cc',
diff --git a/tools/gn/import_manager.cc b/tools/gn/import_manager.cc
index a1555b9..01c88b5 100644
--- a/tools/gn/import_manager.cc
+++ b/tools/gn/import_manager.cc
@@ -111,3 +111,11 @@
   return import_scope->NonRecursiveMergeTo(scope, options, node_for_err,
                                            "import", err);
 }
+
+std::vector<SourceFile> ImportManager::GetImportedFiles() const {
+  std::vector<SourceFile> imported_files;
+  imported_files.resize(imports_.size());
+  std::transform(imports_.begin(), imports_.end(), imported_files.begin(),
+                 [](const ImportMap::value_type& val) { return val.first; });
+  return imported_files;
+}
diff --git a/tools/gn/import_manager.h b/tools/gn/import_manager.h
index 78bb613..f371520 100644
--- a/tools/gn/import_manager.h
+++ b/tools/gn/import_manager.h
@@ -7,6 +7,7 @@
 
 #include <map>
 #include <memory>
+#include <vector>
 
 #include "base/macros.h"
 #include "base/synchronization/lock.h"
@@ -30,6 +31,8 @@
                 Scope* scope,
                 Err* err);
 
+  std::vector<SourceFile> GetImportedFiles() const;
+
  private:
   struct ImportInfo;
 
diff --git a/tools/gn/qt_creator_writer.cc b/tools/gn/qt_creator_writer.cc
new file mode 100644
index 0000000..96f247f
--- /dev/null
+++ b/tools/gn/qt_creator_writer.cc
@@ -0,0 +1,174 @@
+// Copyright (c) 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/qt_creator_writer.h"
+
+#include <set>
+#include <sstream>
+#include <string>
+
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/strings/utf_string_conversions.h"
+
+#include "tools/gn/builder.h"
+#include "tools/gn/config_values_extractors.h"
+#include "tools/gn/deps_iterator.h"
+#include "tools/gn/filesystem_utils.h"
+#include "tools/gn/label.h"
+#include "tools/gn/loader.h"
+
+namespace {
+base::FilePath::CharType kProjectDirName[] =
+    FILE_PATH_LITERAL("qtcreator_project");
+base::FilePath::CharType kProjectName[] = FILE_PATH_LITERAL("all");
+base::FilePath::CharType kMainProjectFileSuffix[] =
+    FILE_PATH_LITERAL(".creator");
+base::FilePath::CharType kSourcesFileSuffix[] = FILE_PATH_LITERAL(".files");
+base::FilePath::CharType kIncludesFileSuffix[] = FILE_PATH_LITERAL(".includes");
+base::FilePath::CharType kDefinesFileSuffix[] = FILE_PATH_LITERAL(".config");
+}
+
+// static
+bool QtCreatorWriter::RunAndWriteFile(const BuildSettings* build_settings,
+                                      const Builder* builder,
+                                      Err* err,
+                                      const std::string& root_target) {
+  base::FilePath project_dir =
+      build_settings->GetFullPath(build_settings->build_dir())
+          .Append(kProjectDirName);
+  if (!base::DirectoryExists(project_dir)) {
+    base::File::Error error;
+    if (!base::CreateDirectoryAndGetError(project_dir, &error)) {
+      *err =
+          Err(Location(), "Could not create the QtCreator project directory '" +
+                              FilePathToUTF8(project_dir) + "': " +
+                              base::File::ErrorToString(error));
+      return false;
+    }
+  }
+
+  base::FilePath project_prefix = project_dir.Append(kProjectName);
+  QtCreatorWriter gen(build_settings, builder, project_prefix, root_target);
+  gen.Run();
+  if (gen.err_.has_error()) {
+    *err = gen.err_;
+    return false;
+  }
+  return true;
+}
+
+QtCreatorWriter::QtCreatorWriter(const BuildSettings* build_settings,
+                                 const Builder* builder,
+                                 const base::FilePath& project_prefix,
+                                 const std::string& root_target_name)
+    : build_settings_(build_settings),
+      builder_(builder),
+      project_prefix_(project_prefix),
+      root_target_name_(root_target_name) {}
+
+QtCreatorWriter::~QtCreatorWriter() {}
+
+void QtCreatorWriter::CollectDeps(const Target* target) {
+  for (const auto& dep : target->GetDeps(Target::DEPS_ALL)) {
+    const Target* dep_target = dep.ptr;
+    if (targets_.count(dep_target))
+      continue;
+    targets_.insert(dep_target);
+    CollectDeps(dep_target);
+  }
+}
+
+bool QtCreatorWriter::DiscoverTargets() {
+  auto all_targets = builder_->GetAllResolvedTargets();
+
+  if (root_target_name_.empty()) {
+    targets_ = std::set<const Target*>(all_targets.begin(), all_targets.end());
+    return true;
+  }
+
+  const Target* root_target = nullptr;
+  for (const Target* target : all_targets) {
+    if (target->label().name() == root_target_name_) {
+      root_target = target;
+      break;
+    }
+  }
+
+  if (!root_target) {
+    err_ = Err(Location(), "Target '" + root_target_name_ + "' not found.");
+    return false;
+  }
+
+  targets_.insert(root_target);
+  CollectDeps(root_target);
+  return true;
+}
+
+void QtCreatorWriter::AddToSources(const Target::FileList& files) {
+  for (const SourceFile& file : files) {
+    const std::string& file_path =
+        FilePathToUTF8(build_settings_->GetFullPath(file));
+    sources_.insert(file_path);
+  }
+}
+
+void QtCreatorWriter::HandleTarget(const Target* target) {
+  SourceFile build_file = Loader::BuildFileForLabel(target->label());
+  sources_.insert(FilePathToUTF8(build_settings_->GetFullPath(build_file)));
+  AddToSources(target->settings()->import_manager().GetImportedFiles());
+
+  AddToSources(target->sources());
+  AddToSources(target->public_headers());
+  AddToSources(target->inputs());
+
+  for (ConfigValuesIterator it(target); !it.done(); it.Next()) {
+    SourceFile precompiled_source = it.cur().precompiled_source();
+    if (!precompiled_source.is_null()) {
+      sources_.insert(
+          FilePathToUTF8(build_settings_->GetFullPath(precompiled_source)));
+    }
+
+    for (const SourceDir& include_dir : it.cur().include_dirs()) {
+      includes_.insert(
+          FilePathToUTF8(build_settings_->GetFullPath(include_dir)));
+    }
+
+    for (std::string define : it.cur().defines()) {
+      size_t equal_pos = define.find('=');
+      if (equal_pos != std::string::npos)
+        define[equal_pos] = ' ';
+      define.insert(0, "#define ");
+      defines_.insert(define);
+    }
+  }
+}
+
+void QtCreatorWriter::GenerateFile(const base::FilePath::CharType* suffix,
+                                   const std::set<std::string>& items) {
+  const base::FilePath file_path = project_prefix_.AddExtension(suffix);
+  std::ostringstream output;
+  for (const std::string& item : items)
+    output << item << std::endl;
+  WriteFileIfChanged(file_path, output.str(), &err_);
+}
+
+void QtCreatorWriter::Run() {
+  if (!DiscoverTargets())
+    return;
+
+  for (const Target* target : targets_) {
+    if (target->toolchain()->label() !=
+        builder_->loader()->GetDefaultToolchain())
+      continue;
+    HandleTarget(target);
+  }
+
+  std::set<std::string> empty_list;
+
+  GenerateFile(kMainProjectFileSuffix, empty_list);
+  GenerateFile(kSourcesFileSuffix, sources_);
+  GenerateFile(kIncludesFileSuffix, includes_);
+  GenerateFile(kDefinesFileSuffix, defines_);
+}
diff --git a/tools/gn/qt_creator_writer.h b/tools/gn/qt_creator_writer.h
new file mode 100644
index 0000000..cea75fd
--- /dev/null
+++ b/tools/gn/qt_creator_writer.h
@@ -0,0 +1,56 @@
+// Copyright (c) 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_QT_CREATOR_WRITER_H_
+#define TOOLS_GN_QT_CREATOR_WRITER_H_
+
+#include <set>
+#include <string>
+
+#include "base/files/file_path.h"
+#include "base/macros.h"
+#include "tools/gn/err.h"
+#include "tools/gn/target.h"
+
+class Builder;
+class BuildSettings;
+
+class QtCreatorWriter {
+ public:
+  static bool RunAndWriteFile(const BuildSettings* build_settings,
+                              const Builder* builder,
+                              Err* err,
+                              const std::string& root_target);
+
+ private:
+  QtCreatorWriter(const BuildSettings* build_settings,
+                  const Builder* builder,
+                  const base::FilePath& project_prefix,
+                  const std::string& root_target_name);
+  ~QtCreatorWriter();
+
+  void Run();
+
+  bool DiscoverTargets();
+  void HandleTarget(const Target* target);
+
+  void CollectDeps(const Target* target);
+  void AddToSources(const Target::FileList& files);
+  void GenerateFile(const base::FilePath::CharType* suffix,
+                    const std::set<std::string>& items);
+
+  const BuildSettings* build_settings_;
+  const Builder* builder_;
+  base::FilePath project_prefix_;
+  std::string root_target_name_;
+  std::set<const Target*> targets_;
+  std::set<std::string> sources_;
+  std::set<std::string> includes_;
+  std::set<std::string> defines_;
+  Err err_;
+
+  DISALLOW_COPY_AND_ASSIGN(QtCreatorWriter);
+};
+
+#endif  // TOOLS_GN_QT_CREATOR_WRITER_H_