OLD | NEW |
(Empty) | |
| 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 <map> |
| 9 #include <set> |
| 10 #include <string> |
| 11 |
| 12 #include "base/files/file_util.h" |
| 13 #include "base/logging.h" |
| 14 #include "base/memory/scoped_ptr.h" |
| 15 #include "base/strings/string_util.h" |
| 16 #include "base/strings/utf_string_conversions.h" |
| 17 #include "tools/gn/builder.h" |
| 18 #include "tools/gn/config.h" |
| 19 #include "tools/gn/config_values_extractors.h" |
| 20 #include "tools/gn/filesystem_utils.h" |
| 21 #include "tools/gn/parse_tree.h" |
| 22 #include "tools/gn/path_output.h" |
| 23 #include "tools/gn/source_file_type.h" |
| 24 #include "tools/gn/standard_out.h" |
| 25 #include "tools/gn/target.h" |
| 26 #include "tools/gn/variables.h" |
| 27 #include "tools/gn/visual_studio_utils.h" |
| 28 #include "tools/gn/xml_element_writer.h" |
| 29 |
| 30 #if defined(OS_WIN) |
| 31 #include "base/win/registry.h" |
| 32 #endif |
| 33 |
| 34 namespace { |
| 35 |
| 36 struct SemicolonSeparatedWriter { |
| 37 void operator()(const std::string& value, std::ostream& out) const { |
| 38 out << value + ';'; |
| 39 } |
| 40 }; |
| 41 |
| 42 struct IncludeDirWriter { |
| 43 explicit IncludeDirWriter(PathOutput& path_output) |
| 44 : path_output_(path_output) {} |
| 45 ~IncludeDirWriter() = default; |
| 46 |
| 47 void operator()(const SourceDir& dir, std::ostream& out) const { |
| 48 path_output_.WriteDir(out, dir, PathOutput::DIR_NO_LAST_SLASH); |
| 49 out << ";"; |
| 50 } |
| 51 |
| 52 PathOutput& path_output_; |
| 53 }; |
| 54 |
| 55 struct SourceFileWriter { |
| 56 SourceFileWriter(PathOutput& path_output, const SourceFile& source_file) |
| 57 : path_output_(path_output), source_file_(source_file) {} |
| 58 ~SourceFileWriter() = default; |
| 59 |
| 60 void operator()(std::ostream& out) const { |
| 61 path_output_.WriteFile(out, source_file_); |
| 62 } |
| 63 |
| 64 PathOutput& path_output_; |
| 65 const SourceFile& source_file_; |
| 66 }; |
| 67 |
| 68 const char kToolsetVersion[] = "v140"; // Visual Studio 2015 |
| 69 const char kVisualStudioVersion[] = "14.0"; // Visual Studio 2015 |
| 70 const char kWindowsKitsVersion[] = "10"; // Windows 10 SDK |
| 71 const char kWindowsKitsIncludeVersion[] = "10.0.10240.0"; // Windows 10 SDK |
| 72 |
| 73 const char kGuidTypeProject[] = "{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}"; |
| 74 const char kGuidTypeFolder[] = "{2150E333-8FDC-42A3-9474-1A3956D46DE8}"; |
| 75 const char kGuidSeedProject[] = "project"; |
| 76 const char kGuidSeedFolder[] = "folder"; |
| 77 const char kGuidSeedFilter[] = "filter"; |
| 78 |
| 79 std::string GetWindowsKitsIncludeDirs() { |
| 80 std::string kits_path; |
| 81 |
| 82 #if defined(OS_WIN) |
| 83 const base::char16* const subkeys[] = { |
| 84 L"SOFTWARE\\Microsoft\\Windows Kits\\Installed Roots", |
| 85 L"SOFTWARE\\Wow6432Node\\Microsoft\\Windows Kits\\Installed Roots"}; |
| 86 |
| 87 base::string16 value_name = |
| 88 base::ASCIIToUTF16("KitsRoot") + base::ASCIIToUTF16(kWindowsKitsVersion); |
| 89 |
| 90 for (const base::char16* subkey : subkeys) { |
| 91 base::win::RegKey key(HKEY_LOCAL_MACHINE, subkey, KEY_READ); |
| 92 base::string16 value; |
| 93 if (key.ReadValue(value_name.c_str(), &value) == ERROR_SUCCESS) { |
| 94 kits_path = base::UTF16ToUTF8(value); |
| 95 break; |
| 96 } |
| 97 } |
| 98 #endif // OS_WIN |
| 99 |
| 100 if (kits_path.empty()) { |
| 101 kits_path = std::string("C:\\Program Files (x86)\\Windows Kits\\") + |
| 102 kWindowsKitsVersion + "\\"; |
| 103 } |
| 104 |
| 105 return kits_path + "Include\\" + kWindowsKitsIncludeVersion + "\\shared;" + |
| 106 kits_path + "Include\\" + kWindowsKitsIncludeVersion + "\\um;" + |
| 107 kits_path + "Include\\" + kWindowsKitsIncludeVersion + "\\winrt;"; |
| 108 } |
| 109 |
| 110 std::string GetConfigurationType(const Target* target, Err* err) { |
| 111 switch (target->output_type()) { |
| 112 case Target::EXECUTABLE: |
| 113 return "Application"; |
| 114 case Target::SHARED_LIBRARY: |
| 115 case Target::LOADABLE_MODULE: |
| 116 return "DynamicLibrary"; |
| 117 case Target::STATIC_LIBRARY: |
| 118 case Target::SOURCE_SET: |
| 119 return "StaticLibrary"; |
| 120 |
| 121 default: |
| 122 *err = Err(Location(), |
| 123 "Visual Studio doesn't support '" + target->label().name() + |
| 124 "' target output type: " + |
| 125 Target::GetStringForOutputType(target->output_type())); |
| 126 return std::string(); |
| 127 } |
| 128 } |
| 129 |
| 130 void ParseCompilerOptions(const std::vector<std::string>& cflags, |
| 131 CompilerOptions* options) { |
| 132 for (const std::string& flag : cflags) |
| 133 ParseCompilerOption(flag, options); |
| 134 } |
| 135 |
| 136 void ParseCompilerOptions(const Target* target, CompilerOptions* options) { |
| 137 for (ConfigValuesIterator iter(target); !iter.done(); iter.Next()) { |
| 138 ParseCompilerOptions(iter.cur().cflags(), options); |
| 139 ParseCompilerOptions(iter.cur().cflags_c(), options); |
| 140 ParseCompilerOptions(iter.cur().cflags_cc(), options); |
| 141 } |
| 142 } |
| 143 |
| 144 // Returns a string piece pointing into the input string identifying the parent |
| 145 // directory path, excluding the last slash. Note that the input pointer must |
| 146 // outlive the output. |
| 147 base::StringPiece FindParentDir(const std::string* path) { |
| 148 DCHECK(path && !path->empty()); |
| 149 for (int i = static_cast<int>(path->size()) - 2; i >= 0; --i) { |
| 150 if (IsSlash((*path)[i])) |
| 151 return base::StringPiece(path->data(), i); |
| 152 } |
| 153 return base::StringPiece(); |
| 154 } |
| 155 |
| 156 bool HasSameContent(std::stringstream& data_1, const base::FilePath& data_2) { |
| 157 // Compare file sizes first. Quick and will save us some time if they are |
| 158 // different sizes. |
| 159 int64_t data_1_len = data_1.tellp(); |
| 160 |
| 161 int64_t data_2_len; |
| 162 if (!base::GetFileSize(data_2, &data_2_len) || data_1_len != data_2_len) |
| 163 return false; |
| 164 |
| 165 std::string data_2_data; |
| 166 data_2_data.resize(data_2_len); |
| 167 if (!base::ReadFileToString(data_2, &data_2_data)) |
| 168 return false; |
| 169 |
| 170 std::string data_1_data; |
| 171 data_1_data = data_1.str(); |
| 172 |
| 173 return data_1_data == data_2_data; |
| 174 } |
| 175 |
| 176 } // namespace |
| 177 |
| 178 VisualStudioWriter::SolutionEntry::SolutionEntry(const std::string& _name, |
| 179 const std::string& _path, |
| 180 const std::string& _guid) |
| 181 : name(_name), path(_path), guid(_guid), parent_folder(nullptr) {} |
| 182 |
| 183 VisualStudioWriter::SolutionEntry::~SolutionEntry() = default; |
| 184 |
| 185 VisualStudioWriter::VisualStudioWriter(const BuildSettings* build_settings) |
| 186 : build_settings_(build_settings) { |
| 187 const Value* value = build_settings->build_args().GetArgOverride("is_debug"); |
| 188 is_debug_config_ = value == nullptr || value->boolean_value(); |
| 189 config_platform_ = "Win32"; |
| 190 value = build_settings->build_args().GetArgOverride(variables::kTargetCpu); |
| 191 if (value != nullptr && value->string_value() == "x64") |
| 192 config_platform_ = "x64"; |
| 193 |
| 194 windows_kits_include_dirs_ = GetWindowsKitsIncludeDirs(); |
| 195 } |
| 196 |
| 197 VisualStudioWriter::~VisualStudioWriter() { |
| 198 STLDeleteContainerPointers(projects_.begin(), projects_.end()); |
| 199 STLDeleteContainerPointers(folders_.begin(), folders_.end()); |
| 200 } |
| 201 |
| 202 // static |
| 203 bool VisualStudioWriter::RunAndWriteFiles(const BuildSettings* build_settings, |
| 204 Builder* builder, |
| 205 Err* err) { |
| 206 std::vector<const Target*> targets = builder->GetAllResolvedTargets(); |
| 207 |
| 208 VisualStudioWriter writer(build_settings); |
| 209 writer.projects_.reserve(targets.size()); |
| 210 writer.folders_.reserve(targets.size()); |
| 211 |
| 212 std::set<std::string> processed_targets; |
| 213 for (const Target* target : targets) { |
| 214 // Skip targets which are duplicated in vector. |
| 215 std::string target_path = |
| 216 target->label().dir().value() + target->label().name(); |
| 217 if (processed_targets.find(target_path) != processed_targets.end()) |
| 218 continue; |
| 219 |
| 220 // Skip actions and groups. |
| 221 if (target->output_type() == Target::GROUP || |
| 222 target->output_type() == Target::COPY_FILES || |
| 223 target->output_type() == Target::ACTION || |
| 224 target->output_type() == Target::ACTION_FOREACH) { |
| 225 continue; |
| 226 } |
| 227 |
| 228 if (!writer.WriteProjectFiles(target, err)) |
| 229 return false; |
| 230 |
| 231 processed_targets.insert(target_path); |
| 232 } |
| 233 |
| 234 if (writer.projects_.empty()) { |
| 235 *err = Err(Location(), "No Visual Studio projects generated."); |
| 236 return false; |
| 237 } |
| 238 |
| 239 writer.ResolveSolutionFolders(); |
| 240 return writer.WriteSolutionFile(err); |
| 241 } |
| 242 |
| 243 bool VisualStudioWriter::WriteProjectFiles(const Target* target, Err* err) { |
| 244 SourceFile target_file = GetTargetOutputDir(target).ResolveRelativeFile( |
| 245 Value(nullptr, target->label().name() + ".vcxproj"), err); |
| 246 if (target_file.is_null()) |
| 247 return false; |
| 248 |
| 249 base::FilePath vcxproj_path = build_settings_->GetFullPath(target_file); |
| 250 std::string vcxproj_path_str = FilePathToUTF8(vcxproj_path); |
| 251 |
| 252 projects_.push_back( |
| 253 new SolutionEntry(target->label().name(), vcxproj_path_str, |
| 254 MakeGuid(vcxproj_path_str, kGuidSeedProject))); |
| 255 projects_.back()->label_dir_path = |
| 256 FilePathToUTF8(build_settings_->GetFullPath(target->label().dir())); |
| 257 |
| 258 std::stringstream vcxproj_string_out; |
| 259 if (!WriteProjectFileContents(vcxproj_string_out, *projects_.back(), target, |
| 260 err)) { |
| 261 projects_.pop_back(); |
| 262 return false; |
| 263 } |
| 264 |
| 265 // Only write the content to the file if it's different. That is |
| 266 // both a performance optimization and more importantly, prevents |
| 267 // Visual Studio from reloading the projects. |
| 268 if (!HasSameContent(vcxproj_string_out, vcxproj_path)) { |
| 269 std::string content = vcxproj_string_out.str(); |
| 270 int size = static_cast<int>(content.size()); |
| 271 if (base::WriteFile(vcxproj_path, content.c_str(), size) != size) { |
| 272 *err = Err(Location(), "Couldn't open " + target->label().name() + |
| 273 ".vcxproj for writing"); |
| 274 return false; |
| 275 } |
| 276 } |
| 277 |
| 278 base::FilePath filters_path = UTF8ToFilePath(vcxproj_path_str + ".filters"); |
| 279 |
| 280 std::stringstream filters_string_out; |
| 281 WriteFiltersFileContents(filters_string_out, target); |
| 282 |
| 283 if (!HasSameContent(filters_string_out, filters_path)) { |
| 284 std::string content = filters_string_out.str(); |
| 285 int size = static_cast<int>(content.size()); |
| 286 if (base::WriteFile(filters_path, content.c_str(), size) != size) { |
| 287 *err = Err(Location(), "Couldn't open " + target->label().name() + |
| 288 ".vcxproj.filters for writing"); |
| 289 return false; |
| 290 } |
| 291 } |
| 292 |
| 293 return true; |
| 294 } |
| 295 |
| 296 bool VisualStudioWriter::WriteProjectFileContents( |
| 297 std::ostream& out, |
| 298 const SolutionEntry& solution_project, |
| 299 const Target* target, |
| 300 Err* err) { |
| 301 PathOutput path_output(GetTargetOutputDir(target), |
| 302 build_settings_->root_path_utf8(), |
| 303 EscapingMode::ESCAPE_NONE); |
| 304 |
| 305 out << "<?xml version=\"1.0\" encoding=\"utf-8\"?>" << std::endl; |
| 306 XmlElementWriter project( |
| 307 out, "Project", |
| 308 XmlAttributes("DefaultTargets", "Build") |
| 309 .add("ToolsVersion", kVisualStudioVersion) |
| 310 .add("xmlns", "http://schemas.microsoft.com/developer/msbuild/2003")); |
| 311 |
| 312 { |
| 313 scoped_ptr<XmlElementWriter> configurations = project.SubElement( |
| 314 "ItemGroup", XmlAttributes("Label", "ProjectConfigurations")); |
| 315 std::string config_name = is_debug_config_ ? "Debug" : "Release"; |
| 316 scoped_ptr<XmlElementWriter> project_config = configurations->SubElement( |
| 317 "ProjectConfiguration", |
| 318 XmlAttributes("Include", config_name + '|' + config_platform_)); |
| 319 project_config->SubElement("Configuration")->Text(config_name); |
| 320 project_config->SubElement("Platform")->Text(config_platform_); |
| 321 } |
| 322 |
| 323 { |
| 324 scoped_ptr<XmlElementWriter> globals = |
| 325 project.SubElement("PropertyGroup", XmlAttributes("Label", "Globals")); |
| 326 globals->SubElement("ProjectGuid")->Text(solution_project.guid); |
| 327 globals->SubElement("Keyword")->Text("Win32Proj"); |
| 328 globals->SubElement("RootNamespace")->Text(target->label().name()); |
| 329 globals->SubElement("IgnoreWarnCompileDuplicatedFilename")->Text("true"); |
| 330 globals->SubElement("PreferredToolArchitecture")->Text("x64"); |
| 331 } |
| 332 |
| 333 project.SubElement( |
| 334 "Import", XmlAttributes("Project", |
| 335 "$(VCTargetsPath)\\Microsoft.Cpp.Default.props")); |
| 336 |
| 337 { |
| 338 scoped_ptr<XmlElementWriter> configuration = project.SubElement( |
| 339 "PropertyGroup", XmlAttributes("Label", "Configuration")); |
| 340 configuration->SubElement("CharacterSet")->Text("Unicode"); |
| 341 std::string configuration_type = GetConfigurationType(target, err); |
| 342 if (configuration_type.empty()) |
| 343 return false; |
| 344 configuration->SubElement("ConfigurationType")->Text(configuration_type); |
| 345 } |
| 346 |
| 347 { |
| 348 scoped_ptr<XmlElementWriter> locals = |
| 349 project.SubElement("PropertyGroup", XmlAttributes("Label", "Locals")); |
| 350 locals->SubElement("PlatformToolset")->Text(kToolsetVersion); |
| 351 } |
| 352 |
| 353 project.SubElement( |
| 354 "Import", |
| 355 XmlAttributes("Project", "$(VCTargetsPath)\\Microsoft.Cpp.props")); |
| 356 project.SubElement( |
| 357 "Import", |
| 358 XmlAttributes("Project", |
| 359 "$(VCTargetsPath)\\BuildCustomizations\\masm.props")); |
| 360 project.SubElement("ImportGroup", |
| 361 XmlAttributes("Label", "ExtensionSettings")); |
| 362 |
| 363 { |
| 364 scoped_ptr<XmlElementWriter> property_sheets = project.SubElement( |
| 365 "ImportGroup", XmlAttributes("Label", "PropertySheets")); |
| 366 property_sheets->SubElement( |
| 367 "Import", |
| 368 XmlAttributes( |
| 369 "Condition", |
| 370 "exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')") |
| 371 .add("Label", "LocalAppDataPlatform") |
| 372 .add("Project", |
| 373 "$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props")); |
| 374 } |
| 375 |
| 376 project.SubElement("PropertyGroup", XmlAttributes("Label", "UserMacros")); |
| 377 |
| 378 { |
| 379 scoped_ptr<XmlElementWriter> properties = |
| 380 project.SubElement("PropertyGroup"); |
| 381 { |
| 382 scoped_ptr<XmlElementWriter> out_dir = properties->SubElement("OutDir"); |
| 383 path_output.WriteDir(out_dir->StartContent(false), |
| 384 build_settings_->build_dir(), |
| 385 PathOutput::DIR_INCLUDE_LAST_SLASH); |
| 386 } |
| 387 properties->SubElement("TargetName")->Text("$(ProjectName)"); |
| 388 properties->SubElement("TargetPath") |
| 389 ->Text("$(OutDir)\\$(ProjectName)$(TargetExt)"); |
| 390 } |
| 391 |
| 392 { |
| 393 scoped_ptr<XmlElementWriter> item_definitions = |
| 394 project.SubElement("ItemDefinitionGroup"); |
| 395 { |
| 396 scoped_ptr<XmlElementWriter> cl_compile = |
| 397 item_definitions->SubElement("ClCompile"); |
| 398 { |
| 399 scoped_ptr<XmlElementWriter> include_dirs = |
| 400 cl_compile->SubElement("AdditionalIncludeDirectories"); |
| 401 RecursiveTargetConfigToStream<SourceDir>( |
| 402 target, &ConfigValues::include_dirs, IncludeDirWriter(path_output), |
| 403 include_dirs->StartContent(false)); |
| 404 include_dirs->Text(windows_kits_include_dirs_ + |
| 405 "$(VSInstallDir)\\VC\\atlmfc\\include;" + |
| 406 "%(AdditionalIncludeDirectories)"); |
| 407 } |
| 408 CompilerOptions options; |
| 409 ParseCompilerOptions(target, &options); |
| 410 if (!options.additional_options.empty()) { |
| 411 cl_compile->SubElement("AdditionalOptions") |
| 412 ->Text(options.additional_options + "%(AdditionalOptions)"); |
| 413 } |
| 414 if (!options.buffer_security_check.empty()) { |
| 415 cl_compile->SubElement("BufferSecurityCheck") |
| 416 ->Text(options.buffer_security_check); |
| 417 } |
| 418 cl_compile->SubElement("CompileAsWinRT")->Text("false"); |
| 419 cl_compile->SubElement("DebugInformationFormat")->Text("ProgramDatabase"); |
| 420 if (!options.disable_specific_warnings.empty()) { |
| 421 cl_compile->SubElement("DisableSpecificWarnings") |
| 422 ->Text(options.disable_specific_warnings + |
| 423 "%(DisableSpecificWarnings)"); |
| 424 } |
| 425 cl_compile->SubElement("ExceptionHandling")->Text("false"); |
| 426 if (!options.forced_include_files.empty()) { |
| 427 cl_compile->SubElement("ForcedIncludeFiles") |
| 428 ->Text(options.forced_include_files); |
| 429 } |
| 430 cl_compile->SubElement("MinimalRebuild")->Text("false"); |
| 431 if (!options.optimization.empty()) |
| 432 cl_compile->SubElement("Optimization")->Text(options.optimization); |
| 433 if (target->config_values().has_precompiled_headers()) { |
| 434 cl_compile->SubElement("PrecompiledHeader")->Text("Use"); |
| 435 cl_compile->SubElement("PrecompiledHeaderFile") |
| 436 ->Text(target->config_values().precompiled_header()); |
| 437 } else { |
| 438 cl_compile->SubElement("PrecompiledHeader")->Text("NotUsing"); |
| 439 } |
| 440 { |
| 441 scoped_ptr<XmlElementWriter> preprocessor_definitions = |
| 442 cl_compile->SubElement("PreprocessorDefinitions"); |
| 443 RecursiveTargetConfigToStream<std::string>( |
| 444 target, &ConfigValues::defines, SemicolonSeparatedWriter(), |
| 445 preprocessor_definitions->StartContent(false)); |
| 446 preprocessor_definitions->Text("%(PreprocessorDefinitions)"); |
| 447 } |
| 448 if (!options.runtime_library.empty()) |
| 449 cl_compile->SubElement("RuntimeLibrary")->Text(options.runtime_library); |
| 450 if (!options.treat_warning_as_error.empty()) { |
| 451 cl_compile->SubElement("TreatWarningAsError") |
| 452 ->Text(options.treat_warning_as_error); |
| 453 } |
| 454 if (!options.warning_level.empty()) |
| 455 cl_compile->SubElement("WarningLevel")->Text(options.warning_level); |
| 456 } |
| 457 |
| 458 // We don't include resource compilation and link options as ninja files |
| 459 // are used to generate real build. |
| 460 } |
| 461 |
| 462 { |
| 463 scoped_ptr<XmlElementWriter> group = project.SubElement("ItemGroup"); |
| 464 if (!target->config_values().precompiled_source().is_null()) { |
| 465 group |
| 466 ->SubElement( |
| 467 "ClCompile", "Include", |
| 468 SourceFileWriter(path_output, |
| 469 target->config_values().precompiled_source())) |
| 470 ->SubElement("PrecompiledHeader") |
| 471 ->Text("Create"); |
| 472 } |
| 473 |
| 474 for (const SourceFile& file : target->sources()) { |
| 475 SourceFileType type = GetSourceFileType(file); |
| 476 if (type == SOURCE_H || type == SOURCE_CPP || type == SOURCE_C) { |
| 477 group->SubElement(type == SOURCE_H ? "ClInclude" : "ClCompile", |
| 478 "Include", SourceFileWriter(path_output, file)); |
| 479 } |
| 480 } |
| 481 } |
| 482 |
| 483 project.SubElement( |
| 484 "Import", |
| 485 XmlAttributes("Project", "$(VCTargetsPath)\\Microsoft.Cpp.targets")); |
| 486 project.SubElement( |
| 487 "Import", |
| 488 XmlAttributes("Project", |
| 489 "$(VCTargetsPath)\\BuildCustomizations\\masm.targets")); |
| 490 project.SubElement("ImportGroup", XmlAttributes("Label", "ExtensionTargets")); |
| 491 |
| 492 { |
| 493 scoped_ptr<XmlElementWriter> build = |
| 494 project.SubElement("Target", XmlAttributes("Name", "Build")); |
| 495 build->SubElement( |
| 496 "Exec", |
| 497 XmlAttributes("Command", "call ninja.exe -C $(OutDir) $(ProjectName)")); |
| 498 } |
| 499 |
| 500 { |
| 501 scoped_ptr<XmlElementWriter> clean = |
| 502 project.SubElement("Target", XmlAttributes("Name", "Clean")); |
| 503 clean->SubElement( |
| 504 "Exec", |
| 505 XmlAttributes("Command", |
| 506 "call ninja.exe -C $(OutDir) -tclean $(ProjectName)")); |
| 507 } |
| 508 |
| 509 return true; |
| 510 } |
| 511 |
| 512 void VisualStudioWriter::WriteFiltersFileContents(std::ostream& out, |
| 513 const Target* target) { |
| 514 out << "<?xml version=\"1.0\" encoding=\"utf-8\"?>" << std::endl; |
| 515 XmlElementWriter project( |
| 516 out, "Project", |
| 517 XmlAttributes("ToolsVersion", "4.0") |
| 518 .add("xmlns", "http://schemas.microsoft.com/developer/msbuild/2003")); |
| 519 |
| 520 std::ostringstream files_out; |
| 521 |
| 522 { |
| 523 scoped_ptr<XmlElementWriter> filters_group = |
| 524 project.SubElement("ItemGroup"); |
| 525 XmlElementWriter files_group(files_out, "ItemGroup", XmlAttributes(), 2); |
| 526 |
| 527 // File paths are relative to vcxproj files which are generated to out dirs. |
| 528 // Filters tree structure need to reflect source directories and be relative |
| 529 // to target file. We need two path outputs then. |
| 530 PathOutput file_path_output(GetTargetOutputDir(target), |
| 531 build_settings_->root_path_utf8(), |
| 532 EscapingMode::ESCAPE_NONE); |
| 533 PathOutput filter_path_output(target->label().dir(), |
| 534 build_settings_->root_path_utf8(), |
| 535 EscapingMode::ESCAPE_NONE); |
| 536 |
| 537 std::set<std::string> processed_filters; |
| 538 |
| 539 for (const SourceFile& file : target->sources()) { |
| 540 SourceFileType type = GetSourceFileType(file); |
| 541 if (type == SOURCE_H || type == SOURCE_CPP || type == SOURCE_C) { |
| 542 scoped_ptr<XmlElementWriter> cl_item = files_group.SubElement( |
| 543 type == SOURCE_H ? "ClInclude" : "ClCompile", "Include", |
| 544 SourceFileWriter(file_path_output, file)); |
| 545 |
| 546 std::ostringstream target_relative_out; |
| 547 filter_path_output.WriteFile(target_relative_out, file); |
| 548 std::string target_relative_path = target_relative_out.str(); |
| 549 ConvertPathToSystem(&target_relative_path); |
| 550 base::StringPiece filter_path = FindParentDir(&target_relative_path); |
| 551 |
| 552 if (!filter_path.empty()) { |
| 553 std::string filter_path_str = filter_path.as_string(); |
| 554 while (processed_filters.find(filter_path_str) == |
| 555 processed_filters.end()) { |
| 556 auto it = processed_filters.insert(filter_path_str).first; |
| 557 filters_group |
| 558 ->SubElement("Filter", |
| 559 XmlAttributes("Include", filter_path_str)) |
| 560 ->SubElement("UniqueIdentifier") |
| 561 ->Text(MakeGuid(filter_path_str, kGuidSeedFilter)); |
| 562 filter_path_str = FindParentDir(&(*it)).as_string(); |
| 563 if (filter_path_str.empty()) |
| 564 break; |
| 565 } |
| 566 cl_item->SubElement("Filter")->Text(filter_path); |
| 567 } |
| 568 } |
| 569 } |
| 570 } |
| 571 |
| 572 project.Text(files_out.str()); |
| 573 } |
| 574 |
| 575 bool VisualStudioWriter::WriteSolutionFile(Err* err) { |
| 576 SourceFile sln_file = build_settings_->build_dir().ResolveRelativeFile( |
| 577 Value(nullptr, "all.sln"), err); |
| 578 if (sln_file.is_null()) |
| 579 return false; |
| 580 |
| 581 base::FilePath sln_path = build_settings_->GetFullPath(sln_file); |
| 582 |
| 583 std::stringstream string_out; |
| 584 WriteSolutionFileContents(string_out, sln_path.DirName()); |
| 585 |
| 586 // Only write the content to the file if it's different. That is |
| 587 // both a performance optimization and more importantly, prevents |
| 588 // Visual Studio from reloading the projects. |
| 589 if (HasSameContent(string_out, sln_path)) |
| 590 return true; |
| 591 |
| 592 std::string content = string_out.str(); |
| 593 int size = static_cast<int>(content.size()); |
| 594 if (base::WriteFile(sln_path, content.c_str(), size) != size) { |
| 595 *err = Err(Location(), "Couldn't open all.sln for writing"); |
| 596 return false; |
| 597 } |
| 598 |
| 599 return true; |
| 600 } |
| 601 |
| 602 void VisualStudioWriter::WriteSolutionFileContents( |
| 603 std::ostream& out, |
| 604 const base::FilePath& solution_dir_path) { |
| 605 out << "Microsoft Visual Studio Solution File, Format Version 12.00" |
| 606 << std::endl; |
| 607 out << "# Visual Studio 2015" << std::endl; |
| 608 |
| 609 SourceDir solution_dir(FilePathToUTF8(solution_dir_path)); |
| 610 for (const SolutionEntry* folder : folders_) { |
| 611 out << "Project(\"" << kGuidTypeFolder << "\") = \"(" << folder->name |
| 612 << ")\", \"" << RebasePath(folder->path, solution_dir, "/") << "\", \"" |
| 613 << folder->guid << "\"" << std::endl; |
| 614 out << "EndProject" << std::endl; |
| 615 } |
| 616 |
| 617 for (const SolutionEntry* project : projects_) { |
| 618 out << "Project(\"" << kGuidTypeProject << "\") = \"" << project->name |
| 619 << "\", \"" << RebasePath(project->path, solution_dir, "/") << "\", \"" |
| 620 << project->guid << "\"" << std::endl; |
| 621 out << "EndProject" << std::endl; |
| 622 } |
| 623 |
| 624 out << "Global" << std::endl; |
| 625 |
| 626 out << "\tGlobalSection(SolutionConfigurationPlatforms) = preSolution" |
| 627 << std::endl; |
| 628 const std::string config_mode = |
| 629 std::string(is_debug_config_ ? "Debug" : "Release") + '|' + |
| 630 config_platform_; |
| 631 out << "\t\t" << config_mode << " = " << config_mode << std::endl; |
| 632 out << "\tEndGlobalSection" << std::endl; |
| 633 |
| 634 out << "\tGlobalSection(ProjectConfigurationPlatforms) = postSolution" |
| 635 << std::endl; |
| 636 for (const SolutionEntry* project : projects_) { |
| 637 out << "\t\t" << project->guid << '.' << config_mode |
| 638 << ".ActiveCfg = " << config_mode << std::endl; |
| 639 out << "\t\t" << project->guid << '.' << config_mode |
| 640 << ".Build.0 = " << config_mode << std::endl; |
| 641 } |
| 642 out << "\tEndGlobalSection" << std::endl; |
| 643 |
| 644 out << "\tGlobalSection(SolutionProperties) = preSolution" << std::endl; |
| 645 out << "\t\tHideSolutionNode = FALSE" << std::endl; |
| 646 out << "\tEndGlobalSection" << std::endl; |
| 647 |
| 648 out << "\tGlobalSection(NestedProjects) = preSolution" << std::endl; |
| 649 for (const SolutionEntry* folder : folders_) { |
| 650 if (folder->parent_folder) { |
| 651 out << "\t\t" << folder->guid << " = " << folder->parent_folder->guid |
| 652 << std::endl; |
| 653 } |
| 654 } |
| 655 for (const SolutionEntry* project : projects_) { |
| 656 out << "\t\t" << project->guid << " = " << project->parent_folder->guid |
| 657 << std::endl; |
| 658 } |
| 659 out << "\tEndGlobalSection" << std::endl; |
| 660 |
| 661 out << "EndGlobal" << std::endl; |
| 662 } |
| 663 |
| 664 void VisualStudioWriter::ResolveSolutionFolders() { |
| 665 root_folder_path_.clear(); |
| 666 |
| 667 // Get all project directories. Create solution folder for each directory. |
| 668 std::map<base::StringPiece, SolutionEntry*> processed_paths; |
| 669 for (SolutionEntry* project : projects_) { |
| 670 base::StringPiece folder_path = project->label_dir_path; |
| 671 if (IsSlash(folder_path[folder_path.size() - 1])) |
| 672 folder_path = folder_path.substr(0, folder_path.size() - 1); |
| 673 auto it = processed_paths.find(folder_path); |
| 674 if (it != processed_paths.end()) { |
| 675 project->parent_folder = it->second; |
| 676 } else { |
| 677 std::string folder_path_str = folder_path.as_string(); |
| 678 SolutionEntry* folder = new SolutionEntry( |
| 679 FindLastDirComponent(SourceDir(folder_path)).as_string(), |
| 680 folder_path_str, MakeGuid(folder_path_str, kGuidSeedFolder)); |
| 681 folders_.push_back(folder); |
| 682 project->parent_folder = folder; |
| 683 processed_paths[folder_path] = folder; |
| 684 |
| 685 if (root_folder_path_.empty()) { |
| 686 root_folder_path_ = folder_path_str; |
| 687 } else { |
| 688 size_t common_prefix_len = 0; |
| 689 size_t max_common_length = |
| 690 std::min(root_folder_path_.size(), folder_path.size()); |
| 691 size_t i; |
| 692 for (i = common_prefix_len; i < max_common_length; ++i) { |
| 693 if (IsSlash(root_folder_path_[i]) && IsSlash(folder_path[i])) |
| 694 common_prefix_len = i + 1; |
| 695 else if (root_folder_path_[i] != folder_path[i]) |
| 696 break; |
| 697 } |
| 698 if (i == max_common_length) |
| 699 common_prefix_len = max_common_length; |
| 700 if (common_prefix_len < root_folder_path_.size()) { |
| 701 if (IsSlash(root_folder_path_[common_prefix_len - 1])) |
| 702 --common_prefix_len; |
| 703 root_folder_path_ = root_folder_path_.substr(0, common_prefix_len); |
| 704 } |
| 705 } |
| 706 } |
| 707 } |
| 708 |
| 709 // Create also all parent folders up to |root_folder_path_|. |
| 710 SolutionEntries additional_folders; |
| 711 for (SolutionEntry* folder : folders_) { |
| 712 if (folder->path == root_folder_path_) |
| 713 continue; |
| 714 |
| 715 base::StringPiece parent_path; |
| 716 while ((parent_path = FindParentDir(&folder->path)) != root_folder_path_) { |
| 717 auto it = processed_paths.find(parent_path); |
| 718 if (it != processed_paths.end()) { |
| 719 folder = it->second; |
| 720 } else { |
| 721 folder = new SolutionEntry( |
| 722 FindLastDirComponent(SourceDir(parent_path)).as_string(), |
| 723 parent_path.as_string(), |
| 724 MakeGuid(parent_path.as_string(), kGuidSeedFolder)); |
| 725 additional_folders.push_back(folder); |
| 726 processed_paths[parent_path] = folder; |
| 727 } |
| 728 } |
| 729 } |
| 730 folders_.insert(folders_.end(), additional_folders.begin(), |
| 731 additional_folders.end()); |
| 732 |
| 733 // Sort folders by path. |
| 734 std::sort(folders_.begin(), folders_.end(), |
| 735 [](const SolutionEntry* a, const SolutionEntry* b) { |
| 736 return a->path < b->path; |
| 737 }); |
| 738 |
| 739 // Match subfolders with their parents. Since |folders_| are sorted by path we |
| 740 // know that parent folder always precedes its children in vector. |
| 741 SolutionEntries parents; |
| 742 for (SolutionEntry* folder : folders_) { |
| 743 while (!parents.empty()) { |
| 744 if (base::StartsWith(folder->path, parents.back()->path, |
| 745 base::CompareCase::SENSITIVE)) { |
| 746 folder->parent_folder = parents.back(); |
| 747 break; |
| 748 } else { |
| 749 parents.pop_back(); |
| 750 } |
| 751 } |
| 752 parents.push_back(folder); |
| 753 } |
| 754 } |
OLD | NEW |