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> | |
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 } | |
OLD | NEW |