• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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