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