| // 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 "gn/qt_creator_writer.h" | |
| #include <optional> | |
| #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 "gn/builder.h" | |
| #include "gn/config_values_extractors.h" | |
| #include "gn/deps_iterator.h" | |
| #include "gn/filesystem_utils.h" | |
| #include "gn/label.h" | |
| #include "gn/loader.h" | |
| #include "gn/string_output_buffer.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"); | |
| } // namespace | |
| // 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() = default; | |
| void QtCreatorWriter::CollectDeps(const Target* target) { | |
| for (const auto& dep : target->GetDeps(Target::DEPS_ALL)) { | |
| const Target* dep_target = dep.ptr; | |
| if (!targets_.add(dep_target)) | |
| continue; | |
| CollectDeps(dep_target); | |
| } | |
| } | |
| bool QtCreatorWriter::DiscoverTargets() { | |
| auto all_targets = builder_.GetAllResolvedTargets(); | |
| if (root_target_name_.empty()) { | |
| targets_.clear(); | |
| targets_.insert(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); | |
| } | |
| } | |
| namespace QtCreatorWriterUtils { | |
| enum class CVersion { | |
| C99, | |
| C11, | |
| }; | |
| enum class CxxVersion { | |
| CXX98, | |
| CXX03, | |
| CXX11, | |
| CXX14, | |
| CXX17, | |
| }; | |
| std::string ToMacro(CVersion version) { | |
| const std::string s = "__STDC_VERSION__"; | |
| switch (version) { | |
| case CVersion::C99: | |
| return s + " 199901L"; | |
| case CVersion::C11: | |
| return s + " 201112L"; | |
| } | |
| return std::string(); | |
| } | |
| std::string ToMacro(CxxVersion version) { | |
| const std::string name = "__cplusplus"; | |
| switch (version) { | |
| case CxxVersion::CXX98: | |
| case CxxVersion::CXX03: | |
| return name + " 199711L"; | |
| case CxxVersion::CXX11: | |
| return name + " 201103L"; | |
| case CxxVersion::CXX14: | |
| return name + " 201402L"; | |
| case CxxVersion::CXX17: | |
| return name + " 201703L"; | |
| } | |
| return std::string(); | |
| } | |
| const std::map<std::string, CVersion> kFlagToCVersion{ | |
| {"-std=gnu99", CVersion::C99}, | |
| {"-std=c99", CVersion::C99}, | |
| {"-std=gnu11", CVersion::C11}, | |
| {"-std=c11", CVersion::C11}}; | |
| const std::map<std::string, CxxVersion> kFlagToCxxVersion{ | |
| {"-std=gnu++11", CxxVersion::CXX11}, {"-std=c++11", CxxVersion::CXX11}, | |
| {"-std=gnu++98", CxxVersion::CXX98}, {"-std=c++98", CxxVersion::CXX98}, | |
| {"-std=gnu++03", CxxVersion::CXX03}, {"-std=c++03", CxxVersion::CXX03}, | |
| {"-std=gnu++14", CxxVersion::CXX14}, {"-std=c++14", CxxVersion::CXX14}, | |
| {"-std=c++1y", CxxVersion::CXX14}, {"-std=gnu++17", CxxVersion::CXX17}, | |
| {"-std=c++17", CxxVersion::CXX17}, {"-std=c++1z", CxxVersion::CXX17}, | |
| }; | |
| template <typename Enum> | |
| struct CompVersion { | |
| bool operator()(Enum a, Enum b) { | |
| return static_cast<int>(a) < static_cast<int>(b); | |
| } | |
| }; | |
| struct CompilerOptions { | |
| std::optional<CVersion> c_version_; | |
| std::optional<CxxVersion> cxx_version_; | |
| void SetCVersion(CVersion ver) { SetVersionImpl(c_version_, ver); } | |
| void SetCxxVersion(CxxVersion ver) { SetVersionImpl(cxx_version_, ver); } | |
| private: | |
| template <typename Version> | |
| void SetVersionImpl(std::optional<Version>& cur_ver, Version ver) { | |
| if (cur_ver) | |
| cur_ver = std::max(*cur_ver, ver, CompVersion<Version>{}); | |
| else | |
| cur_ver = ver; | |
| } | |
| }; | |
| void ParseCompilerOption(const std::string& flag, CompilerOptions* options) { | |
| auto c_ver = kFlagToCVersion.find(flag); | |
| if (c_ver != kFlagToCVersion.end()) | |
| options->SetCVersion(c_ver->second); | |
| auto cxx_ver = kFlagToCxxVersion.find(flag); | |
| if (cxx_ver != kFlagToCxxVersion.end()) | |
| options->SetCxxVersion(cxx_ver->second); | |
| } | |
| void ParseCompilerOptions(const std::vector<std::string>& cflags, | |
| CompilerOptions* options) { | |
| for (const std::string& flag : cflags) | |
| ParseCompilerOption(flag, options); | |
| } | |
| } // namespace QtCreatorWriterUtils | |
| void QtCreatorWriter::HandleTarget(const Target* target) { | |
| using namespace QtCreatorWriterUtils; | |
| SourceFile build_file = builder_.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()); | |
| for (ConfigValuesIterator it(target); !it.done(); it.Next()) { | |
| for (const auto& input : it.cur().inputs()) | |
| sources_.insert(FilePathToUTF8(build_settings_->GetFullPath(input))); | |
| 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))); | |
| } | |
| static constexpr const char* define_str = "#define "; | |
| 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_str); | |
| defines_.insert(define); | |
| } | |
| CompilerOptions options; | |
| ParseCompilerOptions(it.cur().cflags(), &options); | |
| ParseCompilerOptions(it.cur().cflags_c(), &options); | |
| ParseCompilerOptions(it.cur().cflags_cc(), &options); | |
| auto add_define_version = [this](auto& ver) { | |
| if (ver) | |
| defines_.insert(define_str + ToMacro(*ver)); | |
| }; | |
| add_define_version(options.c_version_); | |
| add_define_version(options.cxx_version_); | |
| } | |
| } | |
| void QtCreatorWriter::GenerateFile(const base::FilePath::CharType* suffix, | |
| const std::set<std::string>& items) { | |
| const base::FilePath file_path = project_prefix_.AddExtension(suffix); | |
| StringOutputBuffer storage; | |
| std::ostream output(&storage); | |
| for (const std::string& item : items) | |
| output << item << std::endl; | |
| storage.WriteToFileIfChanged(file_path, &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_); | |
| } |