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