1 // Copyright 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/visual_studio_writer.h"
6
7 #include <algorithm>
8 #include <iterator>
9 #include <map>
10 #include <memory>
11 #include <set>
12 #include <string>
13
14 #include "base/containers/queue.h"
15 #include "base/logging.h"
16 #include "base/strings/string_util.h"
17 #include "base/strings/utf_string_conversions.h"
18 #include "gn/builder.h"
19 #include "gn/commands.h"
20 #include "gn/config.h"
21 #include "gn/config_values_extractors.h"
22 #include "gn/deps_iterator.h"
23 #include "gn/filesystem_utils.h"
24 #include "gn/label_pattern.h"
25 #include "gn/parse_tree.h"
26 #include "gn/path_output.h"
27 #include "gn/standard_out.h"
28 #include "gn/string_output_buffer.h"
29 #include "gn/target.h"
30 #include "gn/variables.h"
31 #include "gn/visual_studio_utils.h"
32 #include "gn/xml_element_writer.h"
33
34 #if defined(OS_WIN)
35 #include "base/win/registry.h"
36 #endif
37
38 namespace {
39
40 struct SemicolonSeparatedWriter {
operator ()__anon600c85de0111::SemicolonSeparatedWriter41 void operator()(const std::string& value, std::ostream& out) const {
42 out << XmlEscape(value) + ';';
43 }
44 };
45
46 struct IncludeDirWriter {
IncludeDirWriter__anon600c85de0111::IncludeDirWriter47 explicit IncludeDirWriter(PathOutput& path_output)
48 : path_output_(path_output) {}
49 ~IncludeDirWriter() = default;
50
operator ()__anon600c85de0111::IncludeDirWriter51 void operator()(const SourceDir& dir, std::ostream& out) const {
52 path_output_.WriteDir(out, dir, PathOutput::DIR_NO_LAST_SLASH);
53 out << ";";
54 }
55
56 PathOutput& path_output_;
57 };
58
59 struct SourceFileWriter {
SourceFileWriter__anon600c85de0111::SourceFileWriter60 SourceFileWriter(PathOutput& path_output, const SourceFile& source_file)
61 : path_output_(path_output), source_file_(source_file) {}
62 ~SourceFileWriter() = default;
63
operator ()__anon600c85de0111::SourceFileWriter64 void operator()(std::ostream& out) const {
65 path_output_.WriteFile(out, source_file_);
66 }
67
68 PathOutput& path_output_;
69 const SourceFile& source_file_;
70 };
71
72 const char kToolsetVersionVs2013[] = "v120"; // Visual Studio 2013
73 const char kToolsetVersionVs2015[] = "v140"; // Visual Studio 2015
74 const char kToolsetVersionVs2017[] = "v141"; // Visual Studio 2017
75 const char kToolsetVersionVs2019[] = "v142"; // Visual Studio 2019
76 const char kToolsetVersionVs2022[] = "v143"; // Visual Studio 2022
77 const char kProjectVersionVs2013[] = "12.0"; // Visual Studio 2013
78 const char kProjectVersionVs2015[] = "14.0"; // Visual Studio 2015
79 const char kProjectVersionVs2017[] = "15.0"; // Visual Studio 2017
80 const char kProjectVersionVs2019[] = "16.0"; // Visual Studio 2019
81 const char kProjectVersionVs2022[] = "17.0"; // Visual Studio 2022
82 const char kVersionStringVs2013[] = "Visual Studio 2013"; // Visual Studio 2013
83 const char kVersionStringVs2015[] = "Visual Studio 2015"; // Visual Studio 2015
84 const char kVersionStringVs2017[] = "Visual Studio 2017"; // Visual Studio 2017
85 const char kVersionStringVs2019[] = "Visual Studio 2019"; // Visual Studio 2019
86 const char kVersionStringVs2022[] = "Visual Studio 2022"; // Visual Studio 2022
87 const char kWindowsKitsVersion[] = "10"; // Windows 10 SDK
88 const char kWindowsKitsDefaultVersion[] = "10"; // Windows 10 SDK
89
90 const char kGuidTypeProject[] = "{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}";
91 const char kGuidTypeFolder[] = "{2150E333-8FDC-42A3-9474-1A3956D46DE8}";
92 const char kGuidSeedProject[] = "project";
93 const char kGuidSeedFolder[] = "folder";
94 const char kGuidSeedFilter[] = "filter";
95
96 const char kConfigurationName[] = "GN";
97
98 const char kCharSetUnicode[] = "_UNICODE";
99 const char kCharSetMultiByte[] = "_MBCS";
100
GetWindowsKitsIncludeDirs(const std::string & win_kit)101 std::string GetWindowsKitsIncludeDirs(const std::string& win_kit) {
102 std::string kits_path;
103
104 #if defined(OS_WIN)
105 const char16_t* const subkeys[] = {
106 u"SOFTWARE\\Microsoft\\Windows Kits\\Installed Roots",
107 u"SOFTWARE\\Wow6432Node\\Microsoft\\Windows Kits\\Installed Roots"};
108
109 std::u16string value_name =
110 base::ASCIIToUTF16("KitsRoot") + base::ASCIIToUTF16(kWindowsKitsVersion);
111
112 for (const char16_t* subkey : subkeys) {
113 base::win::RegKey key(HKEY_LOCAL_MACHINE, subkey, KEY_READ);
114 std::u16string value;
115 if (key.ReadValue(value_name.c_str(), &value) == ERROR_SUCCESS) {
116 kits_path = base::UTF16ToUTF8(value);
117 break;
118 }
119 }
120 #endif // OS_WIN
121
122 if (kits_path.empty()) {
123 kits_path = std::string("C:\\Program Files (x86)\\Windows Kits\\") +
124 kWindowsKitsVersion + "\\";
125 }
126
127 const std::string kit_prefix = kits_path + "Include\\" + win_kit + "\\";
128 return kit_prefix + "shared;" + kit_prefix + "um;" + kit_prefix + "winrt;";
129 }
130
GetConfigurationType(const Target * target,Err * err)131 std::string GetConfigurationType(const Target* target, Err* err) {
132 switch (target->output_type()) {
133 case Target::EXECUTABLE:
134 return "Application";
135 case Target::SHARED_LIBRARY:
136 case Target::LOADABLE_MODULE:
137 return "DynamicLibrary";
138 case Target::STATIC_LIBRARY:
139 case Target::SOURCE_SET:
140 return "StaticLibrary";
141 case Target::GROUP:
142 return "Utility";
143
144 default:
145 *err = Err(Location(),
146 "Visual Studio doesn't support '" + target->label().name() +
147 "' target output type: " +
148 Target::GetStringForOutputType(target->output_type()));
149 return std::string();
150 }
151 }
152
ParseCompilerOptions(const std::vector<std::string> & cflags,CompilerOptions * options)153 void ParseCompilerOptions(const std::vector<std::string>& cflags,
154 CompilerOptions* options) {
155 for (const std::string& flag : cflags)
156 ParseCompilerOption(flag, options);
157 }
158
ParseCompilerOptions(const Target * target,CompilerOptions * options)159 void ParseCompilerOptions(const Target* target, CompilerOptions* options) {
160 for (ConfigValuesIterator iter(target); !iter.done(); iter.Next()) {
161 ParseCompilerOptions(iter.cur().cflags(), options);
162 ParseCompilerOptions(iter.cur().cflags_c(), options);
163 ParseCompilerOptions(iter.cur().cflags_cc(), options);
164 }
165 }
166
ParseLinkerOptions(const std::vector<std::string> & ldflags,LinkerOptions * options)167 void ParseLinkerOptions(const std::vector<std::string>& ldflags,
168 LinkerOptions* options) {
169 for (const std::string& flag : ldflags)
170 ParseLinkerOption(flag, options);
171 }
172
ParseLinkerOptions(const Target * target,LinkerOptions * options)173 void ParseLinkerOptions(const Target* target, LinkerOptions* options) {
174 for (ConfigValuesIterator iter(target); !iter.done(); iter.Next()) {
175 ParseLinkerOptions(iter.cur().ldflags(), options);
176 }
177 }
178
179 // Returns a string piece pointing into the input string identifying the parent
180 // directory path, excluding the last slash. Note that the input pointer must
181 // outlive the output.
FindParentDir(const std::string * path)182 std::string_view FindParentDir(const std::string* path) {
183 DCHECK(path && !path->empty());
184 for (int i = static_cast<int>(path->size()) - 2; i >= 0; --i) {
185 if (IsSlash((*path)[i]))
186 return std::string_view(path->data(), i);
187 }
188 return std::string_view();
189 }
190
FilterTargets(const BuildSettings * build_settings,const Builder & builder,const std::string & filters,bool no_deps,std::vector<const Target * > * targets,Err * err)191 bool FilterTargets(const BuildSettings* build_settings,
192 const Builder& builder,
193 const std::string& filters,
194 bool no_deps,
195 std::vector<const Target*>* targets,
196 Err* err) {
197 if (filters.empty()) {
198 *targets = builder.GetAllResolvedTargets();
199 return true;
200 }
201
202 std::vector<LabelPattern> patterns;
203 if (!commands::FilterPatternsFromString(build_settings, filters, &patterns,
204 err))
205 return false;
206
207 commands::FilterTargetsByPatterns(builder.GetAllResolvedTargets(), patterns,
208 targets);
209
210 if (no_deps)
211 return true;
212
213 std::set<Label> labels;
214 base::queue<const Target*> to_process;
215 for (const Target* target : *targets) {
216 labels.insert(target->label());
217 to_process.push(target);
218 }
219
220 while (!to_process.empty()) {
221 const Target* target = to_process.front();
222 to_process.pop();
223 for (const auto& pair : target->GetDeps(Target::DEPS_ALL)) {
224 if (labels.find(pair.label) == labels.end()) {
225 targets->push_back(pair.ptr);
226 to_process.push(pair.ptr);
227 labels.insert(pair.label);
228 }
229 }
230 }
231
232 return true;
233 }
234
UnicodeTarget(const Target * target)235 bool UnicodeTarget(const Target* target) {
236 for (ConfigValuesIterator it(target); !it.done(); it.Next()) {
237 for (const std::string& define : it.cur().defines()) {
238 if (define == kCharSetUnicode)
239 return true;
240 if (define == kCharSetMultiByte)
241 return false;
242 }
243 }
244 return true;
245 }
246
GetNinjaExecutable(const std::string & ninja_executable)247 std::string GetNinjaExecutable(const std::string& ninja_executable) {
248 return ninja_executable.empty() ? "ninja.exe" : ninja_executable;
249 }
250
251 } // namespace
252
SolutionEntry(const std::string & _name,const std::string & _path,const std::string & _guid)253 VisualStudioWriter::SolutionEntry::SolutionEntry(const std::string& _name,
254 const std::string& _path,
255 const std::string& _guid)
256 : name(_name), path(_path), guid(_guid), parent_folder(nullptr) {}
257
258 VisualStudioWriter::SolutionEntry::~SolutionEntry() = default;
259
SolutionProject(const std::string & _name,const std::string & _path,const std::string & _guid,const std::string & _label_dir_path,const std::string & _config_platform)260 VisualStudioWriter::SolutionProject::SolutionProject(
261 const std::string& _name,
262 const std::string& _path,
263 const std::string& _guid,
264 const std::string& _label_dir_path,
265 const std::string& _config_platform)
266 : SolutionEntry(_name, _path, _guid),
267 label_dir_path(_label_dir_path),
268 config_platform(_config_platform) {
269 // Make sure all paths use the same drive letter case. This is especially
270 // important when searching for the common path prefix.
271 label_dir_path[0] = base::ToUpperASCII(label_dir_path[0]);
272 }
273
274 VisualStudioWriter::SolutionProject::~SolutionProject() = default;
275
SourceFileCompileTypePair(const SourceFile * _file,const char * _compile_type)276 VisualStudioWriter::SourceFileCompileTypePair::SourceFileCompileTypePair(
277 const SourceFile* _file,
278 const char* _compile_type)
279 : file(_file), compile_type(_compile_type) {}
280
281 VisualStudioWriter::SourceFileCompileTypePair::~SourceFileCompileTypePair() =
282 default;
283
VisualStudioWriter(const BuildSettings * build_settings,const char * config_platform,Version version,const std::string & win_kit)284 VisualStudioWriter::VisualStudioWriter(const BuildSettings* build_settings,
285 const char* config_platform,
286 Version version,
287 const std::string& win_kit)
288 : build_settings_(build_settings),
289 config_platform_(config_platform),
290 ninja_path_output_(build_settings->build_dir(),
291 build_settings->root_path_utf8(),
292 EscapingMode::ESCAPE_NINJA_COMMAND),
293 windows_sdk_version_(win_kit) {
294 DCHECK(!win_kit.empty());
295
296 switch (version) {
297 case Version::Vs2013:
298 project_version_ = kProjectVersionVs2013;
299 toolset_version_ = kToolsetVersionVs2013;
300 version_string_ = kVersionStringVs2013;
301 break;
302 case Version::Vs2015:
303 project_version_ = kProjectVersionVs2015;
304 toolset_version_ = kToolsetVersionVs2015;
305 version_string_ = kVersionStringVs2015;
306 break;
307 case Version::Vs2017:
308 project_version_ = kProjectVersionVs2017;
309 toolset_version_ = kToolsetVersionVs2017;
310 version_string_ = kVersionStringVs2017;
311 break;
312 case Version::Vs2019:
313 project_version_ = kProjectVersionVs2019;
314 toolset_version_ = kToolsetVersionVs2019;
315 version_string_ = kVersionStringVs2019;
316 break;
317 case Version::Vs2022:
318 project_version_ = kProjectVersionVs2022;
319 toolset_version_ = kToolsetVersionVs2022;
320 version_string_ = kVersionStringVs2022;
321 break;
322 }
323
324 windows_kits_include_dirs_ = GetWindowsKitsIncludeDirs(win_kit);
325 }
326
327 VisualStudioWriter::~VisualStudioWriter() = default;
328
329 // static
RunAndWriteFiles(const BuildSettings * build_settings,const Builder & builder,Version version,const std::string & sln_name,const std::string & filters,const std::string & win_sdk,const std::string & ninja_extra_args,const std::string & ninja_executable,bool no_deps,Err * err)330 bool VisualStudioWriter::RunAndWriteFiles(const BuildSettings* build_settings,
331 const Builder& builder,
332 Version version,
333 const std::string& sln_name,
334 const std::string& filters,
335 const std::string& win_sdk,
336 const std::string& ninja_extra_args,
337 const std::string& ninja_executable,
338 bool no_deps,
339 Err* err) {
340 std::vector<const Target*> targets;
341 if (!FilterTargets(build_settings, builder, filters, no_deps, &targets, err))
342 return false;
343
344 std::string win_kit = kWindowsKitsDefaultVersion;
345 if (!win_sdk.empty())
346 win_kit = win_sdk;
347
348 const char* config_platform = "Win32";
349
350 // Assume the "target_cpu" variable does not change between different
351 // toolchains.
352 if (!targets.empty()) {
353 const Scope* scope = targets.front()->settings()->base_config();
354 const Value* target_cpu_value = scope->GetValue(variables::kTargetCpu);
355 if (target_cpu_value != nullptr &&
356 target_cpu_value->string_value() == "x64")
357 config_platform = "x64";
358 }
359
360 VisualStudioWriter writer(build_settings, config_platform, version, win_kit);
361 writer.projects_.reserve(targets.size());
362 writer.folders_.reserve(targets.size());
363
364 for (const Target* target : targets) {
365 // Skip actions and bundle targets.
366 if (target->output_type() == Target::ACTION ||
367 target->output_type() == Target::ACTION_FOREACH ||
368 target->output_type() == Target::BUNDLE_DATA ||
369 target->output_type() == Target::COPY_FILES ||
370 target->output_type() == Target::CREATE_BUNDLE ||
371 target->output_type() == Target::GENERATED_FILE) {
372 continue;
373 }
374
375 if (!writer.WriteProjectFiles(target, ninja_extra_args, ninja_executable,
376 err))
377 return false;
378 }
379
380 if (writer.projects_.empty()) {
381 *err = Err(Location(), "No Visual Studio projects generated.");
382 return false;
383 }
384
385 // Sort projects so they appear always in the same order in solution file.
386 // Otherwise solution file is rewritten and reloaded by Visual Studio.
387 std::sort(writer.projects_.begin(), writer.projects_.end(),
388 [](const std::unique_ptr<SolutionProject>& a,
389 const std::unique_ptr<SolutionProject>& b) {
390 return a->path < b->path;
391 });
392
393 writer.ResolveSolutionFolders();
394 return writer.WriteSolutionFile(sln_name, err);
395 }
396
WriteProjectFiles(const Target * target,const std::string & ninja_extra_args,const std::string & ninja_executable,Err * err)397 bool VisualStudioWriter::WriteProjectFiles(const Target* target,
398 const std::string& ninja_extra_args,
399 const std::string& ninja_executable,
400 Err* err) {
401 std::string project_name = target->label().name();
402 const char* project_config_platform = config_platform_;
403 if (!target->settings()->is_default()) {
404 project_name += "_" + target->toolchain()->label().name();
405 const Value* value =
406 target->settings()->base_config()->GetValue(variables::kCurrentCpu);
407 if (value != nullptr && value->string_value() == "x64")
408 project_config_platform = "x64";
409 else
410 project_config_platform = "Win32";
411 }
412
413 SourceFile target_file =
414 GetBuildDirForTargetAsSourceDir(target, BuildDirType::OBJ)
415 .ResolveRelativeFile(Value(nullptr, project_name + ".vcxproj"), err);
416 if (target_file.is_null())
417 return false;
418
419 base::FilePath vcxproj_path = build_settings_->GetFullPath(target_file);
420 std::string vcxproj_path_str = FilePathToUTF8(vcxproj_path);
421
422 projects_.push_back(std::make_unique<SolutionProject>(
423 project_name, vcxproj_path_str,
424 MakeGuid(vcxproj_path_str, kGuidSeedProject),
425 FilePathToUTF8(build_settings_->GetFullPath(target->label().dir())),
426 project_config_platform));
427
428 StringOutputBuffer vcxproj_storage;
429 std::ostream vcxproj_string_out(&vcxproj_storage);
430 SourceFileCompileTypePairs source_types;
431 if (!WriteProjectFileContents(vcxproj_string_out, *projects_.back(), target,
432 ninja_extra_args, ninja_executable,
433 &source_types, err)) {
434 projects_.pop_back();
435 return false;
436 }
437
438 // Only write the content to the file if it's different. That is
439 // both a performance optimization and more importantly, prevents
440 // Visual Studio from reloading the projects.
441 if (!vcxproj_storage.WriteToFileIfChanged(vcxproj_path, err))
442 return false;
443
444 base::FilePath filters_path = UTF8ToFilePath(vcxproj_path_str + ".filters");
445
446 StringOutputBuffer filters_storage;
447 std::ostream filters_string_out(&filters_storage);
448 WriteFiltersFileContents(filters_string_out, target, source_types);
449 return filters_storage.WriteToFileIfChanged(filters_path, err);
450 }
451
WriteProjectFileContents(std::ostream & out,const SolutionProject & solution_project,const Target * target,const std::string & ninja_extra_args,const std::string & ninja_executable,SourceFileCompileTypePairs * source_types,Err * err)452 bool VisualStudioWriter::WriteProjectFileContents(
453 std::ostream& out,
454 const SolutionProject& solution_project,
455 const Target* target,
456 const std::string& ninja_extra_args,
457 const std::string& ninja_executable,
458 SourceFileCompileTypePairs* source_types,
459 Err* err) {
460 PathOutput path_output(
461 GetBuildDirForTargetAsSourceDir(target, BuildDirType::OBJ),
462 build_settings_->root_path_utf8(), EscapingMode::ESCAPE_NONE);
463
464 out << "<?xml version=\"1.0\" encoding=\"utf-8\"?>" << std::endl;
465 XmlElementWriter project(
466 out, "Project",
467 XmlAttributes("DefaultTargets", "Build")
468 .add("ToolsVersion", project_version_)
469 .add("xmlns", "http://schemas.microsoft.com/developer/msbuild/2003"));
470
471 {
472 std::unique_ptr<XmlElementWriter> configurations = project.SubElement(
473 "ItemGroup", XmlAttributes("Label", "ProjectConfigurations"));
474 std::unique_ptr<XmlElementWriter> project_config =
475 configurations->SubElement(
476 "ProjectConfiguration",
477 XmlAttributes("Include", std::string(kConfigurationName) + '|' +
478 solution_project.config_platform));
479 project_config->SubElement("Configuration")->Text(kConfigurationName);
480 project_config->SubElement("Platform")
481 ->Text(solution_project.config_platform);
482 }
483
484 {
485 std::unique_ptr<XmlElementWriter> globals =
486 project.SubElement("PropertyGroup", XmlAttributes("Label", "Globals"));
487 globals->SubElement("ProjectGuid")->Text(solution_project.guid);
488 globals->SubElement("Keyword")->Text("Win32Proj");
489 globals->SubElement("RootNamespace")->Text(target->label().name());
490 globals->SubElement("IgnoreWarnCompileDuplicatedFilename")->Text("true");
491 globals->SubElement("PreferredToolArchitecture")->Text("x64");
492 globals->SubElement("WindowsTargetPlatformVersion")
493 ->Text(windows_sdk_version_);
494 }
495
496 project.SubElement(
497 "Import", XmlAttributes("Project",
498 "$(VCTargetsPath)\\Microsoft.Cpp.Default.props"));
499
500 {
501 std::unique_ptr<XmlElementWriter> configuration = project.SubElement(
502 "PropertyGroup", XmlAttributes("Label", "Configuration"));
503 bool unicode_target = UnicodeTarget(target);
504 configuration->SubElement("CharacterSet")
505 ->Text(unicode_target ? "Unicode" : "MultiByte");
506 std::string configuration_type = GetConfigurationType(target, err);
507 if (configuration_type.empty())
508 return false;
509 configuration->SubElement("ConfigurationType")->Text(configuration_type);
510 }
511
512 {
513 std::unique_ptr<XmlElementWriter> locals =
514 project.SubElement("PropertyGroup", XmlAttributes("Label", "Locals"));
515 locals->SubElement("PlatformToolset")->Text(toolset_version_);
516 }
517
518 project.SubElement(
519 "Import",
520 XmlAttributes("Project", "$(VCTargetsPath)\\Microsoft.Cpp.props"));
521 project.SubElement(
522 "Import",
523 XmlAttributes("Project",
524 "$(VCTargetsPath)\\BuildCustomizations\\masm.props"));
525 project.SubElement("ImportGroup",
526 XmlAttributes("Label", "ExtensionSettings"));
527
528 {
529 std::unique_ptr<XmlElementWriter> property_sheets = project.SubElement(
530 "ImportGroup", XmlAttributes("Label", "PropertySheets"));
531 property_sheets->SubElement(
532 "Import",
533 XmlAttributes(
534 "Condition",
535 "exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')")
536 .add("Label", "LocalAppDataPlatform")
537 .add("Project",
538 "$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props"));
539 }
540
541 project.SubElement("PropertyGroup", XmlAttributes("Label", "UserMacros"));
542
543 std::string ninja_target = GetNinjaTarget(target);
544 std::string ninja_exe = GetNinjaExecutable(ninja_executable);
545
546 {
547 std::unique_ptr<XmlElementWriter> properties =
548 project.SubElement("PropertyGroup");
549 properties->SubElement("OutDir")->Text("$(SolutionDir)");
550 properties->SubElement("TargetName")->Text("$(ProjectName)");
551 if (target->output_type() != Target::GROUP) {
552 properties->SubElement("TargetPath")->Text("$(OutDir)\\" + ninja_target);
553 }
554 }
555
556 {
557 std::unique_ptr<XmlElementWriter> item_definitions =
558 project.SubElement("ItemDefinitionGroup");
559 {
560 std::unique_ptr<XmlElementWriter> cl_compile =
561 item_definitions->SubElement("ClCompile");
562 {
563 std::unique_ptr<XmlElementWriter> include_dirs =
564 cl_compile->SubElement("AdditionalIncludeDirectories");
565 RecursiveTargetConfigToStream<SourceDir>(
566 kRecursiveWriterSkipDuplicates, target, &ConfigValues::include_dirs,
567 IncludeDirWriter(path_output), include_dirs->StartContent(false));
568 include_dirs->Text(windows_kits_include_dirs_ +
569 "$(VSInstallDir)\\VC\\atlmfc\\include;" +
570 "%(AdditionalIncludeDirectories)");
571 }
572 CompilerOptions options;
573 ParseCompilerOptions(target, &options);
574 if (!options.additional_options.empty()) {
575 cl_compile->SubElement("AdditionalOptions")
576 ->Text(options.additional_options + "%(AdditionalOptions)");
577 }
578 if (!options.buffer_security_check.empty()) {
579 cl_compile->SubElement("BufferSecurityCheck")
580 ->Text(options.buffer_security_check);
581 }
582 cl_compile->SubElement("CompileAsWinRT")->Text("false");
583 cl_compile->SubElement("DebugInformationFormat")->Text("ProgramDatabase");
584 if (!options.disable_specific_warnings.empty()) {
585 cl_compile->SubElement("DisableSpecificWarnings")
586 ->Text(options.disable_specific_warnings +
587 "%(DisableSpecificWarnings)");
588 }
589 cl_compile->SubElement("ExceptionHandling")->Text("false");
590 if (!options.forced_include_files.empty()) {
591 cl_compile->SubElement("ForcedIncludeFiles")
592 ->Text(options.forced_include_files);
593 }
594 cl_compile->SubElement("MinimalRebuild")->Text("false");
595 if (!options.optimization.empty())
596 cl_compile->SubElement("Optimization")->Text(options.optimization);
597 cl_compile->SubElement("PrecompiledHeader")->Text("NotUsing");
598 {
599 std::unique_ptr<XmlElementWriter> preprocessor_definitions =
600 cl_compile->SubElement("PreprocessorDefinitions");
601 RecursiveTargetConfigToStream<std::string>(
602 kRecursiveWriterSkipDuplicates, target, &ConfigValues::defines,
603 SemicolonSeparatedWriter(),
604 preprocessor_definitions->StartContent(false));
605 preprocessor_definitions->Text("%(PreprocessorDefinitions)");
606 }
607 if (!options.runtime_library.empty())
608 cl_compile->SubElement("RuntimeLibrary")->Text(options.runtime_library);
609 if (!options.treat_warning_as_error.empty()) {
610 cl_compile->SubElement("TreatWarningAsError")
611 ->Text(options.treat_warning_as_error);
612 }
613 if (!options.warning_level.empty())
614 cl_compile->SubElement("WarningLevel")->Text(options.warning_level);
615 }
616
617 std::unique_ptr<XmlElementWriter> link =
618 item_definitions->SubElement("Link");
619 {
620 LinkerOptions options;
621 ParseLinkerOptions(target, &options);
622 if (!options.subsystem.empty())
623 link->SubElement("SubSystem")->Text(options.subsystem);
624 }
625
626 // We don't include resource compilation and other link options as ninja
627 // files are used to generate real build.
628 }
629
630 {
631 std::unique_ptr<XmlElementWriter> group = project.SubElement("ItemGroup");
632 std::vector<OutputFile> tool_outputs; // Prevent reallocation in loop.
633
634 for (const SourceFile& file : target->sources()) {
635 const char* compile_type;
636 const char* tool_name = Tool::kToolNone;
637 if (target->GetOutputFilesForSource(file, &tool_name, &tool_outputs)) {
638 compile_type = "CustomBuild";
639 std::unique_ptr<XmlElementWriter> build = group->SubElement(
640 compile_type, "Include", SourceFileWriter(path_output, file));
641 build->SubElement("Command")->Text("call " + ninja_exe +
642 " -C $(OutDir) " + ninja_extra_args +
643 " " + tool_outputs[0].value());
644 build->SubElement("Outputs")->Text("$(OutDir)" +
645 tool_outputs[0].value());
646 } else {
647 compile_type = "None";
648 group->SubElement(compile_type, "Include",
649 SourceFileWriter(path_output, file));
650 }
651 source_types->push_back(SourceFileCompileTypePair(&file, compile_type));
652 }
653 }
654
655 project.SubElement(
656 "Import",
657 XmlAttributes("Project", "$(VCTargetsPath)\\Microsoft.Cpp.targets"));
658 project.SubElement(
659 "Import",
660 XmlAttributes("Project",
661 "$(VCTargetsPath)\\BuildCustomizations\\masm.targets"));
662 project.SubElement("ImportGroup", XmlAttributes("Label", "ExtensionTargets"));
663
664 {
665 std::unique_ptr<XmlElementWriter> build =
666 project.SubElement("Target", XmlAttributes("Name", "Build"));
667 build->SubElement(
668 "Exec",
669 XmlAttributes("Command", "call " + ninja_exe + " -C $(OutDir) " +
670 ninja_extra_args + " " + ninja_target));
671 }
672
673 {
674 std::unique_ptr<XmlElementWriter> clean =
675 project.SubElement("Target", XmlAttributes("Name", "Clean"));
676 clean->SubElement(
677 "Exec",
678 XmlAttributes("Command",
679 "call " + ninja_exe + " -C $(OutDir) -tclean " +
680 ninja_target));
681 }
682
683 return true;
684 }
685
WriteFiltersFileContents(std::ostream & out,const Target * target,const SourceFileCompileTypePairs & source_types)686 void VisualStudioWriter::WriteFiltersFileContents(
687 std::ostream& out,
688 const Target* target,
689 const SourceFileCompileTypePairs& source_types) {
690 out << "<?xml version=\"1.0\" encoding=\"utf-8\"?>" << std::endl;
691 XmlElementWriter project(
692 out, "Project",
693 XmlAttributes("ToolsVersion", "4.0")
694 .add("xmlns", "http://schemas.microsoft.com/developer/msbuild/2003"));
695
696 std::ostringstream files_out;
697
698 {
699 std::unique_ptr<XmlElementWriter> filters_group =
700 project.SubElement("ItemGroup");
701 XmlElementWriter files_group(files_out, "ItemGroup", XmlAttributes(), 2);
702
703 // File paths are relative to vcxproj files which are generated to out dirs.
704 // Filters tree structure need to reflect source directories and be relative
705 // to target file. We need two path outputs then.
706 PathOutput file_path_output(
707 GetBuildDirForTargetAsSourceDir(target, BuildDirType::OBJ),
708 build_settings_->root_path_utf8(), EscapingMode::ESCAPE_NONE);
709 PathOutput filter_path_output(target->label().dir(),
710 build_settings_->root_path_utf8(),
711 EscapingMode::ESCAPE_NONE);
712
713 std::set<std::string> processed_filters;
714
715 for (const auto& file_and_type : source_types) {
716 std::unique_ptr<XmlElementWriter> cl_item = files_group.SubElement(
717 file_and_type.compile_type, "Include",
718 SourceFileWriter(file_path_output, *file_and_type.file));
719
720 std::ostringstream target_relative_out;
721 filter_path_output.WriteFile(target_relative_out, *file_and_type.file);
722 std::string target_relative_path = target_relative_out.str();
723 ConvertPathToSystem(&target_relative_path);
724 std::string_view filter_path = FindParentDir(&target_relative_path);
725
726 if (!filter_path.empty()) {
727 std::string filter_path_str(filter_path);
728 while (processed_filters.find(filter_path_str) ==
729 processed_filters.end()) {
730 auto it = processed_filters.insert(filter_path_str).first;
731 filters_group
732 ->SubElement("Filter", XmlAttributes("Include", filter_path_str))
733 ->SubElement("UniqueIdentifier")
734 ->Text(MakeGuid(filter_path_str, kGuidSeedFilter));
735 filter_path_str = std::string(FindParentDir(&(*it)));
736 if (filter_path_str.empty())
737 break;
738 }
739 cl_item->SubElement("Filter")->Text(filter_path);
740 }
741 }
742 }
743
744 project.Text(files_out.str());
745 }
746
WriteSolutionFile(const std::string & sln_name,Err * err)747 bool VisualStudioWriter::WriteSolutionFile(const std::string& sln_name,
748 Err* err) {
749 std::string name = sln_name.empty() ? "all" : sln_name;
750 SourceFile sln_file = build_settings_->build_dir().ResolveRelativeFile(
751 Value(nullptr, name + ".sln"), err);
752 if (sln_file.is_null())
753 return false;
754
755 base::FilePath sln_path = build_settings_->GetFullPath(sln_file);
756
757 StringOutputBuffer storage;
758 std::ostream string_out(&storage);
759 WriteSolutionFileContents(string_out, sln_path.DirName());
760
761 // Only write the content to the file if it's different. That is
762 // both a performance optimization and more importantly, prevents
763 // Visual Studio from reloading the projects.
764 return storage.WriteToFileIfChanged(sln_path, err);
765 }
766
WriteSolutionFileContents(std::ostream & out,const base::FilePath & solution_dir_path)767 void VisualStudioWriter::WriteSolutionFileContents(
768 std::ostream& out,
769 const base::FilePath& solution_dir_path) {
770 out << "Microsoft Visual Studio Solution File, Format Version 12.00"
771 << std::endl;
772 out << "# " << version_string_ << std::endl;
773
774 SourceDir solution_dir(FilePathToUTF8(solution_dir_path));
775 for (const std::unique_ptr<SolutionEntry>& folder : folders_) {
776 out << "Project(\"" << kGuidTypeFolder << "\") = \"(" << folder->name
777 << ")\", \"" << RebasePath(folder->path, solution_dir) << "\", \""
778 << folder->guid << "\"" << std::endl;
779 out << "EndProject" << std::endl;
780 }
781
782 for (const std::unique_ptr<SolutionProject>& project : projects_) {
783 out << "Project(\"" << kGuidTypeProject << "\") = \"" << project->name
784 << "\", \"" << RebasePath(project->path, solution_dir) << "\", \""
785 << project->guid << "\"" << std::endl;
786 out << "EndProject" << std::endl;
787 }
788
789 out << "Global" << std::endl;
790
791 out << "\tGlobalSection(SolutionConfigurationPlatforms) = preSolution"
792 << std::endl;
793 const std::string config_mode_prefix = std::string(kConfigurationName) + '|';
794 const std::string config_mode = config_mode_prefix + config_platform_;
795 out << "\t\t" << config_mode << " = " << config_mode << std::endl;
796 out << "\tEndGlobalSection" << std::endl;
797
798 out << "\tGlobalSection(ProjectConfigurationPlatforms) = postSolution"
799 << std::endl;
800 for (const std::unique_ptr<SolutionProject>& project : projects_) {
801 const std::string project_config_mode =
802 config_mode_prefix + project->config_platform;
803 out << "\t\t" << project->guid << '.' << config_mode
804 << ".ActiveCfg = " << project_config_mode << std::endl;
805 out << "\t\t" << project->guid << '.' << config_mode
806 << ".Build.0 = " << project_config_mode << std::endl;
807 }
808 out << "\tEndGlobalSection" << std::endl;
809
810 out << "\tGlobalSection(SolutionProperties) = preSolution" << std::endl;
811 out << "\t\tHideSolutionNode = FALSE" << std::endl;
812 out << "\tEndGlobalSection" << std::endl;
813
814 out << "\tGlobalSection(NestedProjects) = preSolution" << std::endl;
815 for (const std::unique_ptr<SolutionEntry>& folder : folders_) {
816 if (folder->parent_folder) {
817 out << "\t\t" << folder->guid << " = " << folder->parent_folder->guid
818 << std::endl;
819 }
820 }
821 for (const std::unique_ptr<SolutionProject>& project : projects_) {
822 out << "\t\t" << project->guid << " = " << project->parent_folder->guid
823 << std::endl;
824 }
825 out << "\tEndGlobalSection" << std::endl;
826
827 out << "EndGlobal" << std::endl;
828 }
829
ResolveSolutionFolders()830 void VisualStudioWriter::ResolveSolutionFolders() {
831 root_folder_path_.clear();
832
833 // Get all project directories. Create solution folder for each directory.
834 std::map<std::string_view, SolutionEntry*> processed_paths;
835 for (const std::unique_ptr<SolutionProject>& project : projects_) {
836 std::string_view folder_path = project->label_dir_path;
837 if (IsSlash(folder_path[folder_path.size() - 1]))
838 folder_path = folder_path.substr(0, folder_path.size() - 1);
839 auto it = processed_paths.find(folder_path);
840 if (it != processed_paths.end()) {
841 project->parent_folder = it->second;
842 } else {
843 std::string folder_path_str(folder_path);
844 std::unique_ptr<SolutionEntry> folder = std::make_unique<SolutionEntry>(
845 std::string(
846 FindLastDirComponent(SourceDir(std::string(folder_path)))),
847 folder_path_str, MakeGuid(folder_path_str, kGuidSeedFolder));
848 project->parent_folder = folder.get();
849 processed_paths[folder_path] = folder.get();
850 folders_.push_back(std::move(folder));
851
852 if (root_folder_path_.empty()) {
853 root_folder_path_ = folder_path_str;
854 } else {
855 size_t common_prefix_len = 0;
856 size_t max_common_length =
857 std::min(root_folder_path_.size(), folder_path.size());
858 size_t i;
859 for (i = common_prefix_len; i < max_common_length; ++i) {
860 if (IsSlash(root_folder_path_[i]) && IsSlash(folder_path[i]))
861 common_prefix_len = i + 1;
862 else if (root_folder_path_[i] != folder_path[i])
863 break;
864 }
865 if (i == max_common_length &&
866 (i == folder_path.size() || IsSlash(folder_path[i])))
867 common_prefix_len = max_common_length;
868 if (common_prefix_len < root_folder_path_.size()) {
869 if (IsSlash(root_folder_path_[common_prefix_len - 1]))
870 --common_prefix_len;
871 root_folder_path_ = root_folder_path_.substr(0, common_prefix_len);
872 }
873 }
874 }
875 }
876
877 // Create also all parent folders up to |root_folder_path_|.
878 SolutionFolders additional_folders;
879 for (const std::unique_ptr<SolutionEntry>& solution_folder : folders_) {
880 if (solution_folder->path == root_folder_path_)
881 continue;
882
883 SolutionEntry* folder = solution_folder.get();
884 std::string_view parent_path;
885 while ((parent_path = FindParentDir(&folder->path)) != root_folder_path_) {
886 auto it = processed_paths.find(parent_path);
887 if (it != processed_paths.end()) {
888 folder = it->second;
889 } else {
890 std::unique_ptr<SolutionEntry> new_folder =
891 std::make_unique<SolutionEntry>(
892 std::string(
893 FindLastDirComponent(SourceDir(std::string(parent_path)))),
894 std::string(parent_path),
895 MakeGuid(std::string(parent_path), kGuidSeedFolder));
896 processed_paths[parent_path] = new_folder.get();
897 folder = new_folder.get();
898 additional_folders.push_back(std::move(new_folder));
899 }
900 }
901 }
902 folders_.insert(folders_.end(),
903 std::make_move_iterator(additional_folders.begin()),
904 std::make_move_iterator(additional_folders.end()));
905
906 // Sort folders by path.
907 std::sort(folders_.begin(), folders_.end(),
908 [](const std::unique_ptr<SolutionEntry>& a,
909 const std::unique_ptr<SolutionEntry>& b) {
910 return a->path < b->path;
911 });
912
913 // Match subfolders with their parents. Since |folders_| are sorted by path we
914 // know that parent folder always precedes its children in vector.
915 std::vector<SolutionEntry*> parents;
916 for (const std::unique_ptr<SolutionEntry>& folder : folders_) {
917 while (!parents.empty()) {
918 if (base::StartsWith(folder->path, parents.back()->path,
919 base::CompareCase::SENSITIVE)) {
920 folder->parent_folder = parents.back();
921 break;
922 } else {
923 parents.pop_back();
924 }
925 }
926 parents.push_back(folder.get());
927 }
928 }
929
GetNinjaTarget(const Target * target)930 std::string VisualStudioWriter::GetNinjaTarget(const Target* target) {
931 std::ostringstream ninja_target_out;
932 DCHECK(!target->dependency_output_file().value().empty());
933 ninja_path_output_.WriteFile(ninja_target_out,
934 target->dependency_output_file());
935 std::string s = ninja_target_out.str();
936 if (s.compare(0, 2, "./") == 0)
937 s = s.substr(2);
938 return s;
939 }
940