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