Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(420)

Side by Side Diff: tools/gn/visual_studio_writer.cc

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

Powered by Google App Engine
This is Rietveld 408576698