1 // Copyright (c) 2016 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "gn/qt_creator_writer.h"
6
7 #include <optional>
8 #include <set>
9 #include <sstream>
10 #include <string>
11
12 #include "base/files/file_path.h"
13 #include "base/files/file_util.h"
14 #include "base/strings/utf_string_conversions.h"
15
16 #include "gn/builder.h"
17 #include "gn/config_values_extractors.h"
18 #include "gn/deps_iterator.h"
19 #include "gn/filesystem_utils.h"
20 #include "gn/label.h"
21 #include "gn/loader.h"
22
23 namespace {
24 base::FilePath::CharType kProjectDirName[] =
25 FILE_PATH_LITERAL("qtcreator_project");
26 base::FilePath::CharType kProjectName[] = FILE_PATH_LITERAL("all");
27 base::FilePath::CharType kMainProjectFileSuffix[] =
28 FILE_PATH_LITERAL(".creator");
29 base::FilePath::CharType kSourcesFileSuffix[] = FILE_PATH_LITERAL(".files");
30 base::FilePath::CharType kIncludesFileSuffix[] = FILE_PATH_LITERAL(".includes");
31 base::FilePath::CharType kDefinesFileSuffix[] = FILE_PATH_LITERAL(".config");
32 } // namespace
33
34 // static
RunAndWriteFile(const BuildSettings * build_settings,const Builder & builder,Err * err,const std::string & root_target)35 bool QtCreatorWriter::RunAndWriteFile(const BuildSettings* build_settings,
36 const Builder& builder,
37 Err* err,
38 const std::string& root_target) {
39 base::FilePath project_dir =
40 build_settings->GetFullPath(build_settings->build_dir())
41 .Append(kProjectDirName);
42 if (!base::DirectoryExists(project_dir)) {
43 base::File::Error error;
44 if (!base::CreateDirectoryAndGetError(project_dir, &error)) {
45 *err =
46 Err(Location(), "Could not create the QtCreator project directory '" +
47 FilePathToUTF8(project_dir) +
48 "': " + base::File::ErrorToString(error));
49 return false;
50 }
51 }
52
53 base::FilePath project_prefix = project_dir.Append(kProjectName);
54 QtCreatorWriter gen(build_settings, builder, project_prefix, root_target);
55 gen.Run();
56 if (gen.err_.has_error()) {
57 *err = gen.err_;
58 return false;
59 }
60 return true;
61 }
62
QtCreatorWriter(const BuildSettings * build_settings,const Builder & builder,const base::FilePath & project_prefix,const std::string & root_target_name)63 QtCreatorWriter::QtCreatorWriter(const BuildSettings* build_settings,
64 const Builder& builder,
65 const base::FilePath& project_prefix,
66 const std::string& root_target_name)
67 : build_settings_(build_settings),
68 builder_(builder),
69 project_prefix_(project_prefix),
70 root_target_name_(root_target_name) {}
71
72 QtCreatorWriter::~QtCreatorWriter() = default;
73
CollectDeps(const Target * target)74 void QtCreatorWriter::CollectDeps(const Target* target) {
75 for (const auto& dep : target->GetDeps(Target::DEPS_ALL)) {
76 const Target* dep_target = dep.ptr;
77 if (targets_.count(dep_target))
78 continue;
79 targets_.insert(dep_target);
80 CollectDeps(dep_target);
81 }
82 }
83
DiscoverTargets()84 bool QtCreatorWriter::DiscoverTargets() {
85 auto all_targets = builder_.GetAllResolvedTargets();
86
87 if (root_target_name_.empty()) {
88 targets_ = std::set<const Target*>(all_targets.begin(), all_targets.end());
89 return true;
90 }
91
92 const Target* root_target = nullptr;
93 for (const Target* target : all_targets) {
94 if (target->label().name() == root_target_name_) {
95 root_target = target;
96 break;
97 }
98 }
99
100 if (!root_target) {
101 err_ = Err(Location(), "Target '" + root_target_name_ + "' not found.");
102 return false;
103 }
104
105 targets_.insert(root_target);
106 CollectDeps(root_target);
107 return true;
108 }
109
AddToSources(const Target::FileList & files)110 void QtCreatorWriter::AddToSources(const Target::FileList& files) {
111 for (const SourceFile& file : files) {
112 const std::string& file_path =
113 FilePathToUTF8(build_settings_->GetFullPath(file));
114 sources_.insert(file_path);
115 }
116 }
117
118 namespace QtCreatorWriterUtils {
119
120 enum class CVersion {
121 C99,
122 C11,
123 };
124
125 enum class CxxVersion {
126 CXX98,
127 CXX03,
128 CXX11,
129 CXX14,
130 CXX17,
131 };
132
ToMacro(CVersion version)133 std::string ToMacro(CVersion version) {
134 const std::string s = "__STDC_VERSION__";
135
136 switch (version) {
137 case CVersion::C99:
138 return s + " 199901L";
139 case CVersion::C11:
140 return s + " 201112L";
141 }
142
143 return std::string();
144 }
145
ToMacro(CxxVersion version)146 std::string ToMacro(CxxVersion version) {
147 const std::string name = "__cplusplus";
148
149 switch (version) {
150 case CxxVersion::CXX98:
151 case CxxVersion::CXX03:
152 return name + " 199711L";
153 case CxxVersion::CXX11:
154 return name + " 201103L";
155 case CxxVersion::CXX14:
156 return name + " 201402L";
157 case CxxVersion::CXX17:
158 return name + " 201703L";
159 }
160
161 return std::string();
162 }
163
164 const std::map<std::string, CVersion> kFlagToCVersion{
165 {"-std=gnu99", CVersion::C99},
166 {"-std=c99", CVersion::C99},
167 {"-std=gnu11", CVersion::C11},
168 {"-std=c11", CVersion::C11}};
169
170 const std::map<std::string, CxxVersion> kFlagToCxxVersion{
171 {"-std=gnu++11", CxxVersion::CXX11}, {"-std=c++11", CxxVersion::CXX11},
172 {"-std=gnu++98", CxxVersion::CXX98}, {"-std=c++98", CxxVersion::CXX98},
173 {"-std=gnu++03", CxxVersion::CXX03}, {"-std=c++03", CxxVersion::CXX03},
174 {"-std=gnu++14", CxxVersion::CXX14}, {"-std=c++14", CxxVersion::CXX14},
175 {"-std=c++1y", CxxVersion::CXX14}, {"-std=gnu++17", CxxVersion::CXX17},
176 {"-std=c++17", CxxVersion::CXX17}, {"-std=c++1z", CxxVersion::CXX17},
177 };
178
179 template <typename Enum>
180 struct CompVersion {
operator ()QtCreatorWriterUtils::CompVersion181 bool operator()(Enum a, Enum b) {
182 return static_cast<int>(a) < static_cast<int>(b);
183 }
184 };
185
186 struct CompilerOptions {
187 std::optional<CVersion> c_version_;
188 std::optional<CxxVersion> cxx_version_;
189
SetCVersionQtCreatorWriterUtils::CompilerOptions190 void SetCVersion(CVersion ver) { SetVersionImpl(c_version_, ver); }
191
SetCxxVersionQtCreatorWriterUtils::CompilerOptions192 void SetCxxVersion(CxxVersion ver) { SetVersionImpl(cxx_version_, ver); }
193
194 private:
195 template <typename Version>
SetVersionImplQtCreatorWriterUtils::CompilerOptions196 void SetVersionImpl(std::optional<Version>& cur_ver, Version ver) {
197 if (cur_ver)
198 cur_ver = std::max(*cur_ver, ver, CompVersion<Version>{});
199 else
200 cur_ver = ver;
201 }
202 };
203
ParseCompilerOption(const std::string & flag,CompilerOptions * options)204 void ParseCompilerOption(const std::string& flag, CompilerOptions* options) {
205 auto c_ver = kFlagToCVersion.find(flag);
206 if (c_ver != kFlagToCVersion.end())
207 options->SetCVersion(c_ver->second);
208
209 auto cxx_ver = kFlagToCxxVersion.find(flag);
210 if (cxx_ver != kFlagToCxxVersion.end())
211 options->SetCxxVersion(cxx_ver->second);
212 }
213
ParseCompilerOptions(const std::vector<std::string> & cflags,CompilerOptions * options)214 void ParseCompilerOptions(const std::vector<std::string>& cflags,
215 CompilerOptions* options) {
216 for (const std::string& flag : cflags)
217 ParseCompilerOption(flag, options);
218 }
219
220 } // namespace QtCreatorWriterUtils
221
HandleTarget(const Target * target)222 void QtCreatorWriter::HandleTarget(const Target* target) {
223 using namespace QtCreatorWriterUtils;
224
225 SourceFile build_file = Loader::BuildFileForLabel(target->label());
226 sources_.insert(FilePathToUTF8(build_settings_->GetFullPath(build_file)));
227 AddToSources(target->settings()->import_manager().GetImportedFiles());
228
229 AddToSources(target->sources());
230 AddToSources(target->public_headers());
231
232 for (ConfigValuesIterator it(target); !it.done(); it.Next()) {
233 for (const auto& input : it.cur().inputs())
234 sources_.insert(FilePathToUTF8(build_settings_->GetFullPath(input)));
235
236 SourceFile precompiled_source = it.cur().precompiled_source();
237 if (!precompiled_source.is_null()) {
238 sources_.insert(
239 FilePathToUTF8(build_settings_->GetFullPath(precompiled_source)));
240 }
241
242 for (const SourceDir& include_dir : it.cur().include_dirs()) {
243 includes_.insert(
244 FilePathToUTF8(build_settings_->GetFullPath(include_dir)));
245 }
246
247 static constexpr const char* define_str = "#define ";
248 for (std::string define : it.cur().defines()) {
249 size_t equal_pos = define.find('=');
250 if (equal_pos != std::string::npos)
251 define[equal_pos] = ' ';
252 define.insert(0, define_str);
253 defines_.insert(define);
254 }
255
256 CompilerOptions options;
257 ParseCompilerOptions(it.cur().cflags(), &options);
258 ParseCompilerOptions(it.cur().cflags_c(), &options);
259 ParseCompilerOptions(it.cur().cflags_cc(), &options);
260
261 auto add_define_version = [this](auto& ver) {
262 if (ver)
263 defines_.insert(define_str + ToMacro(*ver));
264 };
265 add_define_version(options.c_version_);
266 add_define_version(options.cxx_version_);
267 }
268 }
269
GenerateFile(const base::FilePath::CharType * suffix,const std::set<std::string> & items)270 void QtCreatorWriter::GenerateFile(const base::FilePath::CharType* suffix,
271 const std::set<std::string>& items) {
272 const base::FilePath file_path = project_prefix_.AddExtension(suffix);
273 std::ostringstream output;
274 for (const std::string& item : items)
275 output << item << std::endl;
276 WriteFileIfChanged(file_path, output.str(), &err_);
277 }
278
Run()279 void QtCreatorWriter::Run() {
280 if (!DiscoverTargets())
281 return;
282
283 for (const Target* target : targets_) {
284 if (target->toolchain()->label() !=
285 builder_.loader()->GetDefaultToolchain())
286 continue;
287 HandleTarget(target);
288 }
289
290 std::set<std::string> empty_list;
291
292 GenerateFile(kMainProjectFileSuffix, empty_list);
293 GenerateFile(kSourcesFileSuffix, sources_);
294 GenerateFile(kIncludesFileSuffix, includes_);
295 GenerateFile(kDefinesFileSuffix, defines_);
296 }
297