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_