|  | // Copyright 2016 The Chromium Authors. All rights reserved. | 
|  | // Use of this source code is governed by a BSD-style license that can be | 
|  | // found in the LICENSE file. | 
|  |  | 
|  | #include "tools/gn/visual_studio_writer.h" | 
|  |  | 
|  | #include <algorithm> | 
|  | #include <iterator> | 
|  | #include <map> | 
|  | #include <memory> | 
|  | #include <set> | 
|  | #include <string> | 
|  |  | 
|  | #include "base/containers/queue.h" | 
|  | #include "base/logging.h" | 
|  | #include "base/strings/string_util.h" | 
|  | #include "base/strings/utf_string_conversions.h" | 
|  | #include "tools/gn/builder.h" | 
|  | #include "tools/gn/commands.h" | 
|  | #include "tools/gn/config.h" | 
|  | #include "tools/gn/config_values_extractors.h" | 
|  | #include "tools/gn/deps_iterator.h" | 
|  | #include "tools/gn/filesystem_utils.h" | 
|  | #include "tools/gn/label_pattern.h" | 
|  | #include "tools/gn/parse_tree.h" | 
|  | #include "tools/gn/path_output.h" | 
|  | #include "tools/gn/standard_out.h" | 
|  | #include "tools/gn/target.h" | 
|  | #include "tools/gn/variables.h" | 
|  | #include "tools/gn/visual_studio_utils.h" | 
|  | #include "tools/gn/xml_element_writer.h" | 
|  |  | 
|  | #if defined(OS_WIN) | 
|  | #include "base/win/registry.h" | 
|  | #endif | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | struct SemicolonSeparatedWriter { | 
|  | void operator()(const std::string& value, std::ostream& out) const { | 
|  | out << XmlEscape(value) + ';'; | 
|  | } | 
|  | }; | 
|  |  | 
|  | struct IncludeDirWriter { | 
|  | explicit IncludeDirWriter(PathOutput& path_output) | 
|  | : path_output_(path_output) {} | 
|  | ~IncludeDirWriter() = default; | 
|  |  | 
|  | void operator()(const SourceDir& dir, std::ostream& out) const { | 
|  | path_output_.WriteDir(out, dir, PathOutput::DIR_NO_LAST_SLASH); | 
|  | out << ";"; | 
|  | } | 
|  |  | 
|  | PathOutput& path_output_; | 
|  | }; | 
|  |  | 
|  | struct SourceFileWriter { | 
|  | SourceFileWriter(PathOutput& path_output, const SourceFile& source_file) | 
|  | : path_output_(path_output), source_file_(source_file) {} | 
|  | ~SourceFileWriter() = default; | 
|  |  | 
|  | void operator()(std::ostream& out) const { | 
|  | path_output_.WriteFile(out, source_file_); | 
|  | } | 
|  |  | 
|  | PathOutput& path_output_; | 
|  | const SourceFile& source_file_; | 
|  | }; | 
|  |  | 
|  | const char kToolsetVersionVs2013[] = "v120";               // Visual Studio 2013 | 
|  | const char kToolsetVersionVs2015[] = "v140";               // Visual Studio 2015 | 
|  | const char kToolsetVersionVs2017[] = "v141";               // Visual Studio 2017 | 
|  | const char kToolsetVersionVs2019[] = "v142";               // Visual Studio 2019 | 
|  | const char kProjectVersionVs2013[] = "12.0";               // Visual Studio 2013 | 
|  | const char kProjectVersionVs2015[] = "14.0";               // Visual Studio 2015 | 
|  | const char kProjectVersionVs2017[] = "15.0";               // Visual Studio 2017 | 
|  | const char kProjectVersionVs2019[] = "16.0";               // Visual Studio 2019 | 
|  | const char kVersionStringVs2013[] = "Visual Studio 2013";  // Visual Studio 2013 | 
|  | const char kVersionStringVs2015[] = "Visual Studio 2015";  // Visual Studio 2015 | 
|  | const char kVersionStringVs2017[] = "Visual Studio 2017";  // Visual Studio 2017 | 
|  | const char kVersionStringVs2019[] = "Visual Studio 2019";  // Visual Studio 2019 | 
|  | const char kWindowsKitsVersion[] = "10";                   // Windows 10 SDK | 
|  | const char kWindowsKitsDefaultVersion[] = "10.0.17134.0";  // Windows 10 SDK | 
|  |  | 
|  | const char kGuidTypeProject[] = "{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}"; | 
|  | const char kGuidTypeFolder[] = "{2150E333-8FDC-42A3-9474-1A3956D46DE8}"; | 
|  | const char kGuidSeedProject[] = "project"; | 
|  | const char kGuidSeedFolder[] = "folder"; | 
|  | const char kGuidSeedFilter[] = "filter"; | 
|  |  | 
|  | const char kConfigurationName[] = "GN"; | 
|  |  | 
|  | const char kCharSetUnicode[] = "_UNICODE"; | 
|  | const char kCharSetMultiByte[] = "_MBCS"; | 
|  |  | 
|  | std::string GetWindowsKitsIncludeDirs(const std::string& win_kit) { | 
|  | std::string kits_path; | 
|  |  | 
|  | #if defined(OS_WIN) | 
|  | const char16_t* const subkeys[] = { | 
|  | u"SOFTWARE\\Microsoft\\Windows Kits\\Installed Roots", | 
|  | u"SOFTWARE\\Wow6432Node\\Microsoft\\Windows Kits\\Installed Roots"}; | 
|  |  | 
|  | std::u16string value_name = | 
|  | base::ASCIIToUTF16("KitsRoot") + base::ASCIIToUTF16(kWindowsKitsVersion); | 
|  |  | 
|  | for (const char16_t* subkey : subkeys) { | 
|  | base::win::RegKey key(HKEY_LOCAL_MACHINE, subkey, KEY_READ); | 
|  | std::u16string value; | 
|  | if (key.ReadValue(value_name.c_str(), &value) == ERROR_SUCCESS) { | 
|  | kits_path = base::UTF16ToUTF8(value); | 
|  | break; | 
|  | } | 
|  | } | 
|  | #endif  // OS_WIN | 
|  |  | 
|  | if (kits_path.empty()) { | 
|  | kits_path = std::string("C:\\Program Files (x86)\\Windows Kits\\") + | 
|  | kWindowsKitsVersion + "\\"; | 
|  | } | 
|  |  | 
|  | const std::string kit_prefix = kits_path + "Include\\" + win_kit + "\\"; | 
|  | return kit_prefix + "shared;" + kit_prefix + "um;" + kit_prefix + "winrt;"; | 
|  | } | 
|  |  | 
|  | std::string GetConfigurationType(const Target* target, Err* err) { | 
|  | switch (target->output_type()) { | 
|  | case Target::EXECUTABLE: | 
|  | return "Application"; | 
|  | case Target::SHARED_LIBRARY: | 
|  | case Target::LOADABLE_MODULE: | 
|  | return "DynamicLibrary"; | 
|  | case Target::STATIC_LIBRARY: | 
|  | case Target::SOURCE_SET: | 
|  | return "StaticLibrary"; | 
|  | case Target::GROUP: | 
|  | return "Utility"; | 
|  |  | 
|  | default: | 
|  | *err = Err(Location(), | 
|  | "Visual Studio doesn't support '" + target->label().name() + | 
|  | "' target output type: " + | 
|  | Target::GetStringForOutputType(target->output_type())); | 
|  | return std::string(); | 
|  | } | 
|  | } | 
|  |  | 
|  | void ParseCompilerOptions(const std::vector<std::string>& cflags, | 
|  | CompilerOptions* options) { | 
|  | for (const std::string& flag : cflags) | 
|  | ParseCompilerOption(flag, options); | 
|  | } | 
|  |  | 
|  | void ParseCompilerOptions(const Target* target, CompilerOptions* options) { | 
|  | for (ConfigValuesIterator iter(target); !iter.done(); iter.Next()) { | 
|  | ParseCompilerOptions(iter.cur().cflags(), options); | 
|  | ParseCompilerOptions(iter.cur().cflags_c(), options); | 
|  | ParseCompilerOptions(iter.cur().cflags_cc(), options); | 
|  | } | 
|  | } | 
|  |  | 
|  | void ParseLinkerOptions(const std::vector<std::string>& ldflags, | 
|  | LinkerOptions* options) { | 
|  | for (const std::string& flag : ldflags) | 
|  | ParseLinkerOption(flag, options); | 
|  | } | 
|  |  | 
|  | void ParseLinkerOptions(const Target* target, LinkerOptions* options) { | 
|  | for (ConfigValuesIterator iter(target); !iter.done(); iter.Next()) { | 
|  | ParseLinkerOptions(iter.cur().ldflags(), options); | 
|  | } | 
|  | } | 
|  |  | 
|  | // Returns a string piece pointing into the input string identifying the parent | 
|  | // directory path, excluding the last slash. Note that the input pointer must | 
|  | // outlive the output. | 
|  | std::string_view FindParentDir(const std::string* path) { | 
|  | DCHECK(path && !path->empty()); | 
|  | for (int i = static_cast<int>(path->size()) - 2; i >= 0; --i) { | 
|  | if (IsSlash((*path)[i])) | 
|  | return std::string_view(path->data(), i); | 
|  | } | 
|  | return std::string_view(); | 
|  | } | 
|  |  | 
|  | bool FilterTargets(const BuildSettings* build_settings, | 
|  | const Builder& builder, | 
|  | const std::string& filters, | 
|  | bool no_deps, | 
|  | std::vector<const Target*>* targets, | 
|  | Err* err) { | 
|  | if (filters.empty()) { | 
|  | *targets = builder.GetAllResolvedTargets(); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | std::vector<LabelPattern> patterns; | 
|  | if (!commands::FilterPatternsFromString(build_settings, filters, &patterns, | 
|  | err)) | 
|  | return false; | 
|  |  | 
|  | commands::FilterTargetsByPatterns(builder.GetAllResolvedTargets(), patterns, | 
|  | targets); | 
|  |  | 
|  | if (no_deps) | 
|  | return true; | 
|  |  | 
|  | std::set<Label> labels; | 
|  | base::queue<const Target*> to_process; | 
|  | for (const Target* target : *targets) { | 
|  | labels.insert(target->label()); | 
|  | to_process.push(target); | 
|  | } | 
|  |  | 
|  | while (!to_process.empty()) { | 
|  | const Target* target = to_process.front(); | 
|  | to_process.pop(); | 
|  | for (const auto& pair : target->GetDeps(Target::DEPS_ALL)) { | 
|  | if (labels.find(pair.label) == labels.end()) { | 
|  | targets->push_back(pair.ptr); | 
|  | to_process.push(pair.ptr); | 
|  | labels.insert(pair.label); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool UnicodeTarget(const Target* target) { | 
|  | for (ConfigValuesIterator it(target); !it.done(); it.Next()) { | 
|  | for (const std::string& define : it.cur().defines()) { | 
|  | if (define == kCharSetUnicode) | 
|  | return true; | 
|  | if (define == kCharSetMultiByte) | 
|  | return false; | 
|  | } | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | VisualStudioWriter::SolutionEntry::SolutionEntry(const std::string& _name, | 
|  | const std::string& _path, | 
|  | const std::string& _guid) | 
|  | : name(_name), path(_path), guid(_guid), parent_folder(nullptr) {} | 
|  |  | 
|  | VisualStudioWriter::SolutionEntry::~SolutionEntry() = default; | 
|  |  | 
|  | VisualStudioWriter::SolutionProject::SolutionProject( | 
|  | const std::string& _name, | 
|  | const std::string& _path, | 
|  | const std::string& _guid, | 
|  | const std::string& _label_dir_path, | 
|  | const std::string& _config_platform) | 
|  | : SolutionEntry(_name, _path, _guid), | 
|  | label_dir_path(_label_dir_path), | 
|  | config_platform(_config_platform) { | 
|  | // Make sure all paths use the same drive letter case. This is especially | 
|  | // important when searching for the common path prefix. | 
|  | label_dir_path[0] = base::ToUpperASCII(label_dir_path[0]); | 
|  | } | 
|  |  | 
|  | VisualStudioWriter::SolutionProject::~SolutionProject() = default; | 
|  |  | 
|  | VisualStudioWriter::SourceFileCompileTypePair::SourceFileCompileTypePair( | 
|  | const SourceFile* _file, | 
|  | const char* _compile_type) | 
|  | : file(_file), compile_type(_compile_type) {} | 
|  |  | 
|  | VisualStudioWriter::SourceFileCompileTypePair::~SourceFileCompileTypePair() = | 
|  | default; | 
|  |  | 
|  | VisualStudioWriter::VisualStudioWriter(const BuildSettings* build_settings, | 
|  | const char* config_platform, | 
|  | Version version, | 
|  | const std::string& win_kit) | 
|  | : build_settings_(build_settings), | 
|  | config_platform_(config_platform), | 
|  | ninja_path_output_(build_settings->build_dir(), | 
|  | build_settings->root_path_utf8(), | 
|  | EscapingMode::ESCAPE_NINJA_COMMAND), | 
|  | windows_sdk_version_(win_kit) { | 
|  | DCHECK(!win_kit.empty()); | 
|  |  | 
|  | switch (version) { | 
|  | case Version::Vs2013: | 
|  | project_version_ = kProjectVersionVs2013; | 
|  | toolset_version_ = kToolsetVersionVs2013; | 
|  | version_string_ = kVersionStringVs2013; | 
|  | break; | 
|  | case Version::Vs2015: | 
|  | project_version_ = kProjectVersionVs2015; | 
|  | toolset_version_ = kToolsetVersionVs2015; | 
|  | version_string_ = kVersionStringVs2015; | 
|  | break; | 
|  | case Version::Vs2017: | 
|  | project_version_ = kProjectVersionVs2017; | 
|  | toolset_version_ = kToolsetVersionVs2017; | 
|  | version_string_ = kVersionStringVs2017; | 
|  | break; | 
|  | case Version::Vs2019: | 
|  | project_version_ = kProjectVersionVs2019; | 
|  | toolset_version_ = kToolsetVersionVs2019; | 
|  | version_string_ = kVersionStringVs2019; | 
|  | break; | 
|  | default: | 
|  | NOTREACHED() << "Not a valid Visual Studio Version: " << version; | 
|  | } | 
|  |  | 
|  | windows_kits_include_dirs_ = GetWindowsKitsIncludeDirs(win_kit); | 
|  | } | 
|  |  | 
|  | VisualStudioWriter::~VisualStudioWriter() = default; | 
|  |  | 
|  | // static | 
|  | bool VisualStudioWriter::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) { | 
|  | std::vector<const Target*> targets; | 
|  | if (!FilterTargets(build_settings, builder, filters, no_deps, &targets, err)) | 
|  | return false; | 
|  |  | 
|  | std::string win_kit = kWindowsKitsDefaultVersion; | 
|  | if (!win_sdk.empty()) | 
|  | win_kit = win_sdk; | 
|  |  | 
|  | const char* config_platform = "Win32"; | 
|  |  | 
|  | // Assume the "target_cpu" variable does not change between different | 
|  | // toolchains. | 
|  | if (!targets.empty()) { | 
|  | const Scope* scope = targets.front()->settings()->base_config(); | 
|  | const Value* target_cpu_value = scope->GetValue(variables::kTargetCpu); | 
|  | if (target_cpu_value != nullptr && | 
|  | target_cpu_value->string_value() == "x64") | 
|  | config_platform = "x64"; | 
|  | } | 
|  |  | 
|  | VisualStudioWriter writer(build_settings, config_platform, version, win_kit); | 
|  | writer.projects_.reserve(targets.size()); | 
|  | writer.folders_.reserve(targets.size()); | 
|  |  | 
|  | for (const Target* target : targets) { | 
|  | // Skip actions and bundle targets. | 
|  | if (target->output_type() == Target::COPY_FILES || | 
|  | target->output_type() == Target::ACTION || | 
|  | target->output_type() == Target::ACTION_FOREACH || | 
|  | target->output_type() == Target::BUNDLE_DATA) { | 
|  | continue; | 
|  | } | 
|  |  | 
|  | if (!writer.WriteProjectFiles(target, ninja_extra_args, err)) | 
|  | return false; | 
|  | } | 
|  |  | 
|  | if (writer.projects_.empty()) { | 
|  | *err = Err(Location(), "No Visual Studio projects generated."); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | // Sort projects so they appear always in the same order in solution file. | 
|  | // Otherwise solution file is rewritten and reloaded by Visual Studio. | 
|  | std::sort(writer.projects_.begin(), writer.projects_.end(), | 
|  | [](const std::unique_ptr<SolutionProject>& a, | 
|  | const std::unique_ptr<SolutionProject>& b) { | 
|  | return a->path < b->path; | 
|  | }); | 
|  |  | 
|  | writer.ResolveSolutionFolders(); | 
|  | return writer.WriteSolutionFile(sln_name, err); | 
|  | } | 
|  |  | 
|  | bool VisualStudioWriter::WriteProjectFiles(const Target* target, | 
|  | const std::string& ninja_extra_args, | 
|  | Err* err) { | 
|  | std::string project_name = target->label().name(); | 
|  | const char* project_config_platform = config_platform_; | 
|  | if (!target->settings()->is_default()) { | 
|  | project_name += "_" + target->toolchain()->label().name(); | 
|  | const Value* value = | 
|  | target->settings()->base_config()->GetValue(variables::kCurrentCpu); | 
|  | if (value != nullptr && value->string_value() == "x64") | 
|  | project_config_platform = "x64"; | 
|  | else | 
|  | project_config_platform = "Win32"; | 
|  | } | 
|  |  | 
|  | SourceFile target_file = | 
|  | GetBuildDirForTargetAsSourceDir(target, BuildDirType::OBJ) | 
|  | .ResolveRelativeFile(Value(nullptr, project_name + ".vcxproj"), err); | 
|  | if (target_file.is_null()) | 
|  | return false; | 
|  |  | 
|  | base::FilePath vcxproj_path = build_settings_->GetFullPath(target_file); | 
|  | std::string vcxproj_path_str = FilePathToUTF8(vcxproj_path); | 
|  |  | 
|  | projects_.push_back(std::make_unique<SolutionProject>( | 
|  | project_name, vcxproj_path_str, | 
|  | MakeGuid(vcxproj_path_str, kGuidSeedProject), | 
|  | FilePathToUTF8(build_settings_->GetFullPath(target->label().dir())), | 
|  | project_config_platform)); | 
|  |  | 
|  | std::stringstream vcxproj_string_out; | 
|  | SourceFileCompileTypePairs source_types; | 
|  | if (!WriteProjectFileContents(vcxproj_string_out, *projects_.back(), target, | 
|  | ninja_extra_args, &source_types, err)) { | 
|  | projects_.pop_back(); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | // Only write the content to the file if it's different. That is | 
|  | // both a performance optimization and more importantly, prevents | 
|  | // Visual Studio from reloading the projects. | 
|  | if (!WriteFileIfChanged(vcxproj_path, vcxproj_string_out.str(), err)) | 
|  | return false; | 
|  |  | 
|  | base::FilePath filters_path = UTF8ToFilePath(vcxproj_path_str + ".filters"); | 
|  | std::stringstream filters_string_out; | 
|  | WriteFiltersFileContents(filters_string_out, target, source_types); | 
|  | return WriteFileIfChanged(filters_path, filters_string_out.str(), err); | 
|  | } | 
|  |  | 
|  | bool VisualStudioWriter::WriteProjectFileContents( | 
|  | std::ostream& out, | 
|  | const SolutionProject& solution_project, | 
|  | const Target* target, | 
|  | const std::string& ninja_extra_args, | 
|  | SourceFileCompileTypePairs* source_types, | 
|  | Err* err) { | 
|  | PathOutput path_output( | 
|  | GetBuildDirForTargetAsSourceDir(target, BuildDirType::OBJ), | 
|  | build_settings_->root_path_utf8(), EscapingMode::ESCAPE_NONE); | 
|  |  | 
|  | out << "<?xml version=\"1.0\" encoding=\"utf-8\"?>" << std::endl; | 
|  | XmlElementWriter project( | 
|  | out, "Project", | 
|  | XmlAttributes("DefaultTargets", "Build") | 
|  | .add("ToolsVersion", project_version_) | 
|  | .add("xmlns", "http://schemas.microsoft.com/developer/msbuild/2003")); | 
|  |  | 
|  | { | 
|  | std::unique_ptr<XmlElementWriter> configurations = project.SubElement( | 
|  | "ItemGroup", XmlAttributes("Label", "ProjectConfigurations")); | 
|  | std::unique_ptr<XmlElementWriter> project_config = | 
|  | configurations->SubElement( | 
|  | "ProjectConfiguration", | 
|  | XmlAttributes("Include", std::string(kConfigurationName) + '|' + | 
|  | solution_project.config_platform)); | 
|  | project_config->SubElement("Configuration")->Text(kConfigurationName); | 
|  | project_config->SubElement("Platform") | 
|  | ->Text(solution_project.config_platform); | 
|  | } | 
|  |  | 
|  | { | 
|  | std::unique_ptr<XmlElementWriter> globals = | 
|  | project.SubElement("PropertyGroup", XmlAttributes("Label", "Globals")); | 
|  | globals->SubElement("ProjectGuid")->Text(solution_project.guid); | 
|  | globals->SubElement("Keyword")->Text("Win32Proj"); | 
|  | globals->SubElement("RootNamespace")->Text(target->label().name()); | 
|  | globals->SubElement("IgnoreWarnCompileDuplicatedFilename")->Text("true"); | 
|  | globals->SubElement("PreferredToolArchitecture")->Text("x64"); | 
|  | globals->SubElement("WindowsTargetPlatformVersion") | 
|  | ->Text(windows_sdk_version_); | 
|  | } | 
|  |  | 
|  | project.SubElement( | 
|  | "Import", XmlAttributes("Project", | 
|  | "$(VCTargetsPath)\\Microsoft.Cpp.Default.props")); | 
|  |  | 
|  | { | 
|  | std::unique_ptr<XmlElementWriter> configuration = project.SubElement( | 
|  | "PropertyGroup", XmlAttributes("Label", "Configuration")); | 
|  | bool unicode_target = UnicodeTarget(target); | 
|  | configuration->SubElement("CharacterSet") | 
|  | ->Text(unicode_target ? "Unicode" : "MultiByte"); | 
|  | std::string configuration_type = GetConfigurationType(target, err); | 
|  | if (configuration_type.empty()) | 
|  | return false; | 
|  | configuration->SubElement("ConfigurationType")->Text(configuration_type); | 
|  | } | 
|  |  | 
|  | { | 
|  | std::unique_ptr<XmlElementWriter> locals = | 
|  | project.SubElement("PropertyGroup", XmlAttributes("Label", "Locals")); | 
|  | locals->SubElement("PlatformToolset")->Text(toolset_version_); | 
|  | } | 
|  |  | 
|  | project.SubElement( | 
|  | "Import", | 
|  | XmlAttributes("Project", "$(VCTargetsPath)\\Microsoft.Cpp.props")); | 
|  | project.SubElement( | 
|  | "Import", | 
|  | XmlAttributes("Project", | 
|  | "$(VCTargetsPath)\\BuildCustomizations\\masm.props")); | 
|  | project.SubElement("ImportGroup", | 
|  | XmlAttributes("Label", "ExtensionSettings")); | 
|  |  | 
|  | { | 
|  | std::unique_ptr<XmlElementWriter> property_sheets = project.SubElement( | 
|  | "ImportGroup", XmlAttributes("Label", "PropertySheets")); | 
|  | property_sheets->SubElement( | 
|  | "Import", | 
|  | XmlAttributes( | 
|  | "Condition", | 
|  | "exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')") | 
|  | .add("Label", "LocalAppDataPlatform") | 
|  | .add("Project", | 
|  | "$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props")); | 
|  | } | 
|  |  | 
|  | project.SubElement("PropertyGroup", XmlAttributes("Label", "UserMacros")); | 
|  |  | 
|  | std::string ninja_target = GetNinjaTarget(target); | 
|  |  | 
|  | { | 
|  | std::unique_ptr<XmlElementWriter> properties = | 
|  | project.SubElement("PropertyGroup"); | 
|  | properties->SubElement("OutDir")->Text("$(SolutionDir)"); | 
|  | properties->SubElement("TargetName")->Text("$(ProjectName)"); | 
|  | if (target->output_type() != Target::GROUP) { | 
|  | properties->SubElement("TargetPath")->Text("$(OutDir)\\" + ninja_target); | 
|  | } | 
|  | } | 
|  |  | 
|  | { | 
|  | std::unique_ptr<XmlElementWriter> item_definitions = | 
|  | project.SubElement("ItemDefinitionGroup"); | 
|  | { | 
|  | std::unique_ptr<XmlElementWriter> cl_compile = | 
|  | item_definitions->SubElement("ClCompile"); | 
|  | { | 
|  | std::unique_ptr<XmlElementWriter> include_dirs = | 
|  | cl_compile->SubElement("AdditionalIncludeDirectories"); | 
|  | RecursiveTargetConfigToStream<SourceDir>( | 
|  | target, &ConfigValues::include_dirs, IncludeDirWriter(path_output), | 
|  | include_dirs->StartContent(false)); | 
|  | include_dirs->Text(windows_kits_include_dirs_ + | 
|  | "$(VSInstallDir)\\VC\\atlmfc\\include;" + | 
|  | "%(AdditionalIncludeDirectories)"); | 
|  | } | 
|  | CompilerOptions options; | 
|  | ParseCompilerOptions(target, &options); | 
|  | if (!options.additional_options.empty()) { | 
|  | cl_compile->SubElement("AdditionalOptions") | 
|  | ->Text(options.additional_options + "%(AdditionalOptions)"); | 
|  | } | 
|  | if (!options.buffer_security_check.empty()) { | 
|  | cl_compile->SubElement("BufferSecurityCheck") | 
|  | ->Text(options.buffer_security_check); | 
|  | } | 
|  | cl_compile->SubElement("CompileAsWinRT")->Text("false"); | 
|  | cl_compile->SubElement("DebugInformationFormat")->Text("ProgramDatabase"); | 
|  | if (!options.disable_specific_warnings.empty()) { | 
|  | cl_compile->SubElement("DisableSpecificWarnings") | 
|  | ->Text(options.disable_specific_warnings + | 
|  | "%(DisableSpecificWarnings)"); | 
|  | } | 
|  | cl_compile->SubElement("ExceptionHandling")->Text("false"); | 
|  | if (!options.forced_include_files.empty()) { | 
|  | cl_compile->SubElement("ForcedIncludeFiles") | 
|  | ->Text(options.forced_include_files); | 
|  | } | 
|  | cl_compile->SubElement("MinimalRebuild")->Text("false"); | 
|  | if (!options.optimization.empty()) | 
|  | cl_compile->SubElement("Optimization")->Text(options.optimization); | 
|  | cl_compile->SubElement("PrecompiledHeader")->Text("NotUsing"); | 
|  | { | 
|  | std::unique_ptr<XmlElementWriter> preprocessor_definitions = | 
|  | cl_compile->SubElement("PreprocessorDefinitions"); | 
|  | RecursiveTargetConfigToStream<std::string>( | 
|  | target, &ConfigValues::defines, SemicolonSeparatedWriter(), | 
|  | preprocessor_definitions->StartContent(false)); | 
|  | preprocessor_definitions->Text("%(PreprocessorDefinitions)"); | 
|  | } | 
|  | if (!options.runtime_library.empty()) | 
|  | cl_compile->SubElement("RuntimeLibrary")->Text(options.runtime_library); | 
|  | if (!options.treat_warning_as_error.empty()) { | 
|  | cl_compile->SubElement("TreatWarningAsError") | 
|  | ->Text(options.treat_warning_as_error); | 
|  | } | 
|  | if (!options.warning_level.empty()) | 
|  | cl_compile->SubElement("WarningLevel")->Text(options.warning_level); | 
|  | } | 
|  |  | 
|  | std::unique_ptr<XmlElementWriter> link = | 
|  | item_definitions->SubElement("Link"); | 
|  | { | 
|  | LinkerOptions options; | 
|  | ParseLinkerOptions(target, &options); | 
|  | if (!options.subsystem.empty()) | 
|  | link->SubElement("SubSystem")->Text(options.subsystem); | 
|  | } | 
|  |  | 
|  | // We don't include resource compilation and other link options as ninja | 
|  | // files are used to generate real build. | 
|  | } | 
|  |  | 
|  | { | 
|  | std::unique_ptr<XmlElementWriter> group = project.SubElement("ItemGroup"); | 
|  | std::vector<OutputFile> tool_outputs;  // Prevent reallocation in loop. | 
|  |  | 
|  | for (const SourceFile& file : target->sources()) { | 
|  | const char* compile_type; | 
|  | const char* tool_name = Tool::kToolNone; | 
|  | if (target->GetOutputFilesForSource(file, &tool_name, &tool_outputs)) { | 
|  | compile_type = "CustomBuild"; | 
|  | std::unique_ptr<XmlElementWriter> build = group->SubElement( | 
|  | compile_type, "Include", SourceFileWriter(path_output, file)); | 
|  | build->SubElement("Command")->Text("call ninja.exe -C $(OutDir) " + | 
|  | ninja_extra_args + " " + | 
|  | tool_outputs[0].value()); | 
|  | build->SubElement("Outputs")->Text("$(OutDir)" + | 
|  | tool_outputs[0].value()); | 
|  | } else { | 
|  | compile_type = "None"; | 
|  | group->SubElement(compile_type, "Include", | 
|  | SourceFileWriter(path_output, file)); | 
|  | } | 
|  | source_types->push_back(SourceFileCompileTypePair(&file, compile_type)); | 
|  | } | 
|  | } | 
|  |  | 
|  | project.SubElement( | 
|  | "Import", | 
|  | XmlAttributes("Project", "$(VCTargetsPath)\\Microsoft.Cpp.targets")); | 
|  | project.SubElement( | 
|  | "Import", | 
|  | XmlAttributes("Project", | 
|  | "$(VCTargetsPath)\\BuildCustomizations\\masm.targets")); | 
|  | project.SubElement("ImportGroup", XmlAttributes("Label", "ExtensionTargets")); | 
|  |  | 
|  | { | 
|  | std::unique_ptr<XmlElementWriter> build = | 
|  | project.SubElement("Target", XmlAttributes("Name", "Build")); | 
|  | build->SubElement( | 
|  | "Exec", | 
|  | XmlAttributes("Command", "call ninja.exe -C $(OutDir) " + | 
|  | ninja_extra_args + " " + ninja_target)); | 
|  | } | 
|  |  | 
|  | { | 
|  | std::unique_ptr<XmlElementWriter> clean = | 
|  | project.SubElement("Target", XmlAttributes("Name", "Clean")); | 
|  | clean->SubElement( | 
|  | "Exec", | 
|  | XmlAttributes("Command", | 
|  | "call ninja.exe -C $(OutDir) -tclean " + ninja_target)); | 
|  | } | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | void VisualStudioWriter::WriteFiltersFileContents( | 
|  | std::ostream& out, | 
|  | const Target* target, | 
|  | const SourceFileCompileTypePairs& source_types) { | 
|  | out << "<?xml version=\"1.0\" encoding=\"utf-8\"?>" << std::endl; | 
|  | XmlElementWriter project( | 
|  | out, "Project", | 
|  | XmlAttributes("ToolsVersion", "4.0") | 
|  | .add("xmlns", "http://schemas.microsoft.com/developer/msbuild/2003")); | 
|  |  | 
|  | std::ostringstream files_out; | 
|  |  | 
|  | { | 
|  | std::unique_ptr<XmlElementWriter> filters_group = | 
|  | project.SubElement("ItemGroup"); | 
|  | XmlElementWriter files_group(files_out, "ItemGroup", XmlAttributes(), 2); | 
|  |  | 
|  | // File paths are relative to vcxproj files which are generated to out dirs. | 
|  | // Filters tree structure need to reflect source directories and be relative | 
|  | // to target file. We need two path outputs then. | 
|  | PathOutput file_path_output( | 
|  | GetBuildDirForTargetAsSourceDir(target, BuildDirType::OBJ), | 
|  | build_settings_->root_path_utf8(), EscapingMode::ESCAPE_NONE); | 
|  | PathOutput filter_path_output(target->label().dir(), | 
|  | build_settings_->root_path_utf8(), | 
|  | EscapingMode::ESCAPE_NONE); | 
|  |  | 
|  | std::set<std::string> processed_filters; | 
|  |  | 
|  | for (const auto& file_and_type : source_types) { | 
|  | std::unique_ptr<XmlElementWriter> cl_item = files_group.SubElement( | 
|  | file_and_type.compile_type, "Include", | 
|  | SourceFileWriter(file_path_output, *file_and_type.file)); | 
|  |  | 
|  | std::ostringstream target_relative_out; | 
|  | filter_path_output.WriteFile(target_relative_out, *file_and_type.file); | 
|  | std::string target_relative_path = target_relative_out.str(); | 
|  | ConvertPathToSystem(&target_relative_path); | 
|  | std::string_view filter_path = FindParentDir(&target_relative_path); | 
|  |  | 
|  | if (!filter_path.empty()) { | 
|  | std::string filter_path_str(filter_path); | 
|  | while (processed_filters.find(filter_path_str) == | 
|  | processed_filters.end()) { | 
|  | auto it = processed_filters.insert(filter_path_str).first; | 
|  | filters_group | 
|  | ->SubElement("Filter", XmlAttributes("Include", filter_path_str)) | 
|  | ->SubElement("UniqueIdentifier") | 
|  | ->Text(MakeGuid(filter_path_str, kGuidSeedFilter)); | 
|  | filter_path_str = std::string(FindParentDir(&(*it))); | 
|  | if (filter_path_str.empty()) | 
|  | break; | 
|  | } | 
|  | cl_item->SubElement("Filter")->Text(filter_path); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | project.Text(files_out.str()); | 
|  | } | 
|  |  | 
|  | bool VisualStudioWriter::WriteSolutionFile(const std::string& sln_name, | 
|  | Err* err) { | 
|  | std::string name = sln_name.empty() ? "all" : sln_name; | 
|  | SourceFile sln_file = build_settings_->build_dir().ResolveRelativeFile( | 
|  | Value(nullptr, name + ".sln"), err); | 
|  | if (sln_file.is_null()) | 
|  | return false; | 
|  |  | 
|  | base::FilePath sln_path = build_settings_->GetFullPath(sln_file); | 
|  |  | 
|  | std::stringstream string_out; | 
|  | WriteSolutionFileContents(string_out, sln_path.DirName()); | 
|  |  | 
|  | // Only write the content to the file if it's different. That is | 
|  | // both a performance optimization and more importantly, prevents | 
|  | // Visual Studio from reloading the projects. | 
|  | return WriteFileIfChanged(sln_path, string_out.str(), err); | 
|  | } | 
|  |  | 
|  | void VisualStudioWriter::WriteSolutionFileContents( | 
|  | std::ostream& out, | 
|  | const base::FilePath& solution_dir_path) { | 
|  | out << "Microsoft Visual Studio Solution File, Format Version 12.00" | 
|  | << std::endl; | 
|  | out << "# " << version_string_ << std::endl; | 
|  |  | 
|  | SourceDir solution_dir(FilePathToUTF8(solution_dir_path)); | 
|  | for (const std::unique_ptr<SolutionEntry>& folder : folders_) { | 
|  | out << "Project(\"" << kGuidTypeFolder << "\") = \"(" << folder->name | 
|  | << ")\", \"" << RebasePath(folder->path, solution_dir) << "\", \"" | 
|  | << folder->guid << "\"" << std::endl; | 
|  | out << "EndProject" << std::endl; | 
|  | } | 
|  |  | 
|  | for (const std::unique_ptr<SolutionProject>& project : projects_) { | 
|  | out << "Project(\"" << kGuidTypeProject << "\") = \"" << project->name | 
|  | << "\", \"" << RebasePath(project->path, solution_dir) << "\", \"" | 
|  | << project->guid << "\"" << std::endl; | 
|  | out << "EndProject" << std::endl; | 
|  | } | 
|  |  | 
|  | out << "Global" << std::endl; | 
|  |  | 
|  | out << "\tGlobalSection(SolutionConfigurationPlatforms) = preSolution" | 
|  | << std::endl; | 
|  | const std::string config_mode_prefix = std::string(kConfigurationName) + '|'; | 
|  | const std::string config_mode = config_mode_prefix + config_platform_; | 
|  | out << "\t\t" << config_mode << " = " << config_mode << std::endl; | 
|  | out << "\tEndGlobalSection" << std::endl; | 
|  |  | 
|  | out << "\tGlobalSection(ProjectConfigurationPlatforms) = postSolution" | 
|  | << std::endl; | 
|  | for (const std::unique_ptr<SolutionProject>& project : projects_) { | 
|  | const std::string project_config_mode = | 
|  | config_mode_prefix + project->config_platform; | 
|  | out << "\t\t" << project->guid << '.' << config_mode | 
|  | << ".ActiveCfg = " << project_config_mode << std::endl; | 
|  | out << "\t\t" << project->guid << '.' << config_mode | 
|  | << ".Build.0 = " << project_config_mode << std::endl; | 
|  | } | 
|  | out << "\tEndGlobalSection" << std::endl; | 
|  |  | 
|  | out << "\tGlobalSection(SolutionProperties) = preSolution" << std::endl; | 
|  | out << "\t\tHideSolutionNode = FALSE" << std::endl; | 
|  | out << "\tEndGlobalSection" << std::endl; | 
|  |  | 
|  | out << "\tGlobalSection(NestedProjects) = preSolution" << std::endl; | 
|  | for (const std::unique_ptr<SolutionEntry>& folder : folders_) { | 
|  | if (folder->parent_folder) { | 
|  | out << "\t\t" << folder->guid << " = " << folder->parent_folder->guid | 
|  | << std::endl; | 
|  | } | 
|  | } | 
|  | for (const std::unique_ptr<SolutionProject>& project : projects_) { | 
|  | out << "\t\t" << project->guid << " = " << project->parent_folder->guid | 
|  | << std::endl; | 
|  | } | 
|  | out << "\tEndGlobalSection" << std::endl; | 
|  |  | 
|  | out << "EndGlobal" << std::endl; | 
|  | } | 
|  |  | 
|  | void VisualStudioWriter::ResolveSolutionFolders() { | 
|  | root_folder_path_.clear(); | 
|  |  | 
|  | // Get all project directories. Create solution folder for each directory. | 
|  | std::map<std::string_view, SolutionEntry*> processed_paths; | 
|  | for (const std::unique_ptr<SolutionProject>& project : projects_) { | 
|  | std::string_view folder_path = project->label_dir_path; | 
|  | if (IsSlash(folder_path[folder_path.size() - 1])) | 
|  | folder_path = folder_path.substr(0, folder_path.size() - 1); | 
|  | auto it = processed_paths.find(folder_path); | 
|  | if (it != processed_paths.end()) { | 
|  | project->parent_folder = it->second; | 
|  | } else { | 
|  | std::string folder_path_str(folder_path); | 
|  | std::unique_ptr<SolutionEntry> folder = std::make_unique<SolutionEntry>( | 
|  | std::string( | 
|  | FindLastDirComponent(SourceDir(std::string(folder_path)))), | 
|  | folder_path_str, MakeGuid(folder_path_str, kGuidSeedFolder)); | 
|  | project->parent_folder = folder.get(); | 
|  | processed_paths[folder_path] = folder.get(); | 
|  | folders_.push_back(std::move(folder)); | 
|  |  | 
|  | if (root_folder_path_.empty()) { | 
|  | root_folder_path_ = folder_path_str; | 
|  | } else { | 
|  | size_t common_prefix_len = 0; | 
|  | size_t max_common_length = | 
|  | std::min(root_folder_path_.size(), folder_path.size()); | 
|  | size_t i; | 
|  | for (i = common_prefix_len; i < max_common_length; ++i) { | 
|  | if (IsSlash(root_folder_path_[i]) && IsSlash(folder_path[i])) | 
|  | common_prefix_len = i + 1; | 
|  | else if (root_folder_path_[i] != folder_path[i]) | 
|  | break; | 
|  | } | 
|  | if (i == max_common_length && | 
|  | (i == folder_path.size() || IsSlash(folder_path[i]))) | 
|  | common_prefix_len = max_common_length; | 
|  | if (common_prefix_len < root_folder_path_.size()) { | 
|  | if (IsSlash(root_folder_path_[common_prefix_len - 1])) | 
|  | --common_prefix_len; | 
|  | root_folder_path_ = root_folder_path_.substr(0, common_prefix_len); | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | // Create also all parent folders up to |root_folder_path_|. | 
|  | SolutionFolders additional_folders; | 
|  | for (const std::unique_ptr<SolutionEntry>& solution_folder : folders_) { | 
|  | if (solution_folder->path == root_folder_path_) | 
|  | continue; | 
|  |  | 
|  | SolutionEntry* folder = solution_folder.get(); | 
|  | std::string_view parent_path; | 
|  | while ((parent_path = FindParentDir(&folder->path)) != root_folder_path_) { | 
|  | auto it = processed_paths.find(parent_path); | 
|  | if (it != processed_paths.end()) { | 
|  | folder = it->second; | 
|  | } else { | 
|  | std::unique_ptr<SolutionEntry> new_folder = | 
|  | std::make_unique<SolutionEntry>( | 
|  | std::string( | 
|  | FindLastDirComponent(SourceDir(std::string(parent_path)))), | 
|  | std::string(parent_path), | 
|  | MakeGuid(std::string(parent_path), kGuidSeedFolder)); | 
|  | processed_paths[parent_path] = new_folder.get(); | 
|  | folder = new_folder.get(); | 
|  | additional_folders.push_back(std::move(new_folder)); | 
|  | } | 
|  | } | 
|  | } | 
|  | folders_.insert(folders_.end(), | 
|  | std::make_move_iterator(additional_folders.begin()), | 
|  | std::make_move_iterator(additional_folders.end())); | 
|  |  | 
|  | // Sort folders by path. | 
|  | std::sort(folders_.begin(), folders_.end(), | 
|  | [](const std::unique_ptr<SolutionEntry>& a, | 
|  | const std::unique_ptr<SolutionEntry>& b) { | 
|  | return a->path < b->path; | 
|  | }); | 
|  |  | 
|  | // Match subfolders with their parents. Since |folders_| are sorted by path we | 
|  | // know that parent folder always precedes its children in vector. | 
|  | std::vector<SolutionEntry*> parents; | 
|  | for (const std::unique_ptr<SolutionEntry>& folder : folders_) { | 
|  | while (!parents.empty()) { | 
|  | if (base::StartsWith(folder->path, parents.back()->path, | 
|  | base::CompareCase::SENSITIVE)) { | 
|  | folder->parent_folder = parents.back(); | 
|  | break; | 
|  | } else { | 
|  | parents.pop_back(); | 
|  | } | 
|  | } | 
|  | parents.push_back(folder.get()); | 
|  | } | 
|  | } | 
|  |  | 
|  | std::string VisualStudioWriter::GetNinjaTarget(const Target* target) { | 
|  | std::ostringstream ninja_target_out; | 
|  | DCHECK(!target->dependency_output_file().value().empty()); | 
|  | ninja_path_output_.WriteFile(ninja_target_out, | 
|  | target->dependency_output_file()); | 
|  | std::string s = ninja_target_out.str(); | 
|  | if (s.compare(0, 2, "./") == 0) | 
|  | s = s.substr(2); | 
|  | return s; | 
|  | } |