| // 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 "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 "gn/builder.h" | |
| #include "gn/commands.h" | |
| #include "gn/config.h" | |
| #include "gn/config_values_extractors.h" | |
| #include "gn/deps_iterator.h" | |
| #include "gn/filesystem_utils.h" | |
| #include "gn/label_pattern.h" | |
| #include "gn/parse_tree.h" | |
| #include "gn/path_output.h" | |
| #include "gn/standard_out.h" | |
| #include "gn/string_output_buffer.h" | |
| #include "gn/target.h" | |
| #include "gn/variables.h" | |
| #include "gn/visual_studio_utils.h" | |
| #include "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 kToolsetVersionVs2022[] = "v143"; // Visual Studio 2022 | |
| 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 kProjectVersionVs2022[] = "17.0"; // Visual Studio 2022 | |
| 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 kVersionStringVs2022[] = "Visual Studio 2022"; // Visual Studio 2022 | |
| const char kWindowsKitsVersion[] = "10"; // Windows 10 SDK | |
| const char kWindowsKitsDefaultVersion[] = "10"; // 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: | |
| case Target::RUST_PROC_MACRO: | |
| return "DynamicLibrary"; | |
| case Target::STATIC_LIBRARY: | |
| case Target::SOURCE_SET: | |
| case Target::RUST_LIBRARY: | |
| 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; | |
| } | |
| std::string GetNinjaExecutable(const std::string& ninja_executable) { | |
| return ninja_executable.empty() ? "ninja.exe" : ninja_executable; | |
| } | |
| } // 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; | |
| case Version::Vs2022: | |
| project_version_ = kProjectVersionVs2022; | |
| toolset_version_ = kToolsetVersionVs2022; | |
| version_string_ = kVersionStringVs2022; | |
| break; | |
| } | |
| 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, | |
| const std::string& ninja_executable, | |
| 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::ACTION || | |
| target->output_type() == Target::ACTION_FOREACH || | |
| target->output_type() == Target::BUNDLE_DATA || | |
| target->output_type() == Target::COPY_FILES || | |
| target->output_type() == Target::CREATE_BUNDLE || | |
| target->output_type() == Target::GENERATED_FILE) { | |
| continue; | |
| } | |
| if (!writer.WriteProjectFiles(target, ninja_extra_args, ninja_executable, | |
| 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, | |
| const std::string& ninja_executable, | |
| 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 = | |
| GetSourceDir(*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)); | |
| StringOutputBuffer vcxproj_storage; | |
| std::ostream vcxproj_string_out(&vcxproj_storage); | |
| SourceFileCompileTypePairs source_types; | |
| if (!WriteProjectFileContents(vcxproj_string_out, *projects_.back(), target, | |
| ninja_extra_args, ninja_executable, | |
| &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 (!vcxproj_storage.WriteToFileIfChanged(vcxproj_path, err)) | |
| return false; | |
| base::FilePath filters_path = UTF8ToFilePath(vcxproj_path_str + ".filters"); | |
| StringOutputBuffer filters_storage; | |
| std::ostream filters_string_out(&filters_storage); | |
| WriteFiltersFileContents(filters_string_out, target, source_types); | |
| return filters_storage.WriteToFileIfChanged(filters_path, err); | |
| } | |
| bool VisualStudioWriter::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) { | |
| PathOutput path_output(GetSourceDir(*target, BuildDirType::OBJ), | |
| build_settings_->root_path_utf8(), | |
| EscapingMode::ESCAPE_NONE); | |
| out << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"; | |
| 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")); | |
| auto [ninja_target, ninja_target_is_phony] = GetNinjaTarget(target); | |
| std::string ninja_exe = GetNinjaExecutable(ninja_executable); | |
| { | |
| std::unique_ptr<XmlElementWriter> properties = | |
| project.SubElement("PropertyGroup"); | |
| properties->SubElement("OutDir")->Text("$(SolutionDir)"); | |
| properties->SubElement("TargetName")->Text("$(ProjectName)"); | |
| if (target->output_type() != Target::GROUP && !ninja_target_is_phony) { | |
| 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>( | |
| kRecursiveWriterSkipDuplicates, 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>( | |
| kRecursiveWriterSkipDuplicates, 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 + " " + | |
| std::string(tool_outputs[0].value())); | |
| build->SubElement("Outputs")->Text( | |
| "$(OutDir)" + std::string(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\"?>\n"; | |
| 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(GetSourceDir(*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); | |
| StringOutputBuffer storage; | |
| std::ostream string_out(&storage); | |
| 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 storage.WriteToFileIfChanged(sln_path, err); | |
| } | |
| void VisualStudioWriter::WriteSolutionFileContents( | |
| std::ostream& out, | |
| const base::FilePath& solution_dir_path) { | |
| out << "Microsoft Visual Studio Solution File, Format Version 12.00\n"; | |
| out << "# " << version_string_ << '\n'; | |
| 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 << "\"\n"; | |
| out << "EndProject\n"; | |
| } | |
| for (const std::unique_ptr<SolutionProject>& project : projects_) { | |
| out << "Project(\"" << kGuidTypeProject << "\") = \"" << project->name | |
| << "\", \"" << RebasePath(project->path, solution_dir) << "\", \"" | |
| << project->guid << "\"\n"; | |
| out << "EndProject\n"; | |
| } | |
| out << "Global\n"; | |
| out << "\tGlobalSection(SolutionConfigurationPlatforms) = preSolution\n"; | |
| 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 << '\n'; | |
| out << "\tEndGlobalSection\n"; | |
| out << "\tGlobalSection(ProjectConfigurationPlatforms) = postSolution\n"; | |
| 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 << '\n'; | |
| out << "\t\t" << project->guid << '.' << config_mode | |
| << ".Build.0 = " << project_config_mode << '\n'; | |
| } | |
| out << "\tEndGlobalSection\n"; | |
| out << "\tGlobalSection(SolutionProperties) = preSolution\n"; | |
| out << "\t\tHideSolutionNode = FALSE\n"; | |
| out << "\tEndGlobalSection\n"; | |
| out << "\tGlobalSection(NestedProjects) = preSolution\n"; | |
| for (const std::unique_ptr<SolutionEntry>& folder : folders_) { | |
| if (folder->parent_folder) { | |
| out << "\t\t" << folder->guid << " = " << folder->parent_folder->guid | |
| << '\n'; | |
| } | |
| } | |
| for (const std::unique_ptr<SolutionProject>& project : projects_) { | |
| out << "\t\t" << project->guid << " = " << project->parent_folder->guid | |
| << '\n'; | |
| } | |
| out << "\tEndGlobalSection\n"; | |
| out << "EndGlobal\n"; | |
| } | |
| 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 (folder->path.starts_with(parents.back()->path)) { | |
| folder->parent_folder = parents.back(); | |
| break; | |
| } else { | |
| parents.pop_back(); | |
| } | |
| } | |
| parents.push_back(folder.get()); | |
| } | |
| } | |
| std::pair<std::string, bool> VisualStudioWriter::GetNinjaTarget( | |
| const Target* target) { | |
| std::ostringstream ninja_target_out; | |
| bool is_phony = false; | |
| OutputFile output_file; | |
| if (target->has_dependency_output_file()) { | |
| output_file = target->dependency_output_file(); | |
| } else if (target->has_dependency_output_alias()) { | |
| output_file = target->dependency_output_alias(); | |
| is_phony = true; | |
| } | |
| ninja_path_output_.WriteFile(ninja_target_out, output_file); | |
| std::string s = ninja_target_out.str(); | |
| if (s.compare(0, 2, "./") == 0) | |
| s = s.substr(2); | |
| return std::make_pair(s, is_phony); | |
| } |