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