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