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

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

Issue 1570113002: Visual Studio generators for GN (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 4 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright (c) 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 <fstream>
8 #include <iostream>
9 #include <string>
10 #include <utility>
11
12 #include "base/logging.h"
13 #include "base/md5.h"
14 #include "base/memory/scoped_ptr.h"
15 #include "base/strings/string_util.h"
16 #include "tools/gn/builder.h"
17 #include "tools/gn/config.h"
18 #include "tools/gn/config_values_extractors.h"
19 #include "tools/gn/filesystem_utils.h"
20 #include "tools/gn/parse_tree.h"
21 #include "tools/gn/path_output.h"
22 #include "tools/gn/source_file_type.h"
23 #include "tools/gn/target.h"
24 #include "tools/gn/variables.h"
25
26 struct ProjectConfiguration {
27 bool is_debug;
28 std::string platform;
29 };
30
31 namespace {
32
33 using XmlAttributes = std::vector<std::pair<std::string, std::string>>;
34
35 class XmlElement {
brettw 2016/01/08 23:51:50 I feel like this should be XMLElementOuptut or XML
Tomasz Moniuszko 2016/01/21 10:50:02 Done.
36 public:
37 XmlElement(std::ostream& out,
38 const std::string& tag,
39 const XmlAttributes& attributes);
40 XmlElement(std::ostream& out,
41 const std::string& tag,
42 const XmlAttributes& attributes,
43 int indent);
44 template <class Writer>
45 XmlElement(std::ostream& out,
46 const std::string& tag,
47 const std::string& attribute_name,
48 const Writer& attribute_value_writer,
49 int indent);
50 ~XmlElement();
51
52 void Text(const std::string& content);
53
54 scoped_ptr<XmlElement> SubElement(const std::string& tag);
55 scoped_ptr<XmlElement> SubElement(const std::string& tag,
56 const XmlAttributes& attributes);
57 template <class Writer>
58 scoped_ptr<XmlElement> SubElement(const std::string& tag,
59 const std::string& attribute_name,
60 const Writer& attribute_value_writer);
61
62 private:
63 std::ostream& out_;
64 std::string tag_;
65 int indent_;
66 bool one_line_;
67
68 DISALLOW_COPY_AND_ASSIGN(XmlElement);
69 };
70
71 XmlElement::XmlElement(std::ostream& out,
72 const std::string& tag,
73 const XmlAttributes& attributes)
74 : XmlElement(out, tag, attributes, 0) {}
75
76 XmlElement::XmlElement(std::ostream& out,
77 const std::string& tag,
78 const XmlAttributes& attributes,
79 int indent)
80 : out_(out), tag_(tag), indent_(indent), one_line_(true) {
81 out << std::string(indent, ' ') << '<' << tag;
82 for (auto attribute : attributes) {
brettw 2016/01/08 23:51:50 I haven't been using {} for single-line stuff like
Tomasz Moniuszko 2016/01/21 10:50:03 Done.
83 out << ' ' << attribute.first << "=\"" << attribute.second << '"';
84 }
85 out << '>';
86 }
87
88 template <class Writer>
89 XmlElement::XmlElement(std::ostream& out,
90 const std::string& tag,
91 const std::string& attribute_name,
92 const Writer& attribute_value_writer,
93 int indent)
94 : out_(out), tag_(tag), indent_(indent), one_line_(true) {
95 out << std::string(indent, ' ') << '<' << tag;
96 out << ' ' << attribute_name << "=\"";
97 attribute_value_writer(out);
98 out << "\">";
99 }
100
101 XmlElement::~XmlElement() {
102 if (!one_line_)
103 out_ << std::string(indent_, ' ');
104 out_ << "</" << tag_ << '>' << std::endl;
105 }
106
107 void XmlElement::Text(const std::string& content) {
108 out_ << content;
109 }
110
111 scoped_ptr<XmlElement> XmlElement::SubElement(const std::string& tag) {
112 return SubElement(tag, XmlAttributes());
113 }
114
115 scoped_ptr<XmlElement> XmlElement::SubElement(const std::string& tag,
116 const XmlAttributes& attributes) {
117 if (one_line_) {
118 out_ << std::endl;
119 one_line_ = false;
120 }
121 return make_scoped_ptr(new XmlElement(out_, tag, attributes, indent_ + 2));
122 }
123
124 template <class Writer>
125 scoped_ptr<XmlElement> XmlElement::SubElement(
126 const std::string& tag,
127 const std::string& attribute_name,
128 const Writer& attribute_value_writer) {
129 if (one_line_) {
130 out_ << std::endl;
131 one_line_ = false;
132 }
133 return make_scoped_ptr(new XmlElement(out_, tag, attribute_name,
134 attribute_value_writer, indent_ + 2));
135 }
136
137 struct SemicolonSeparatedWriter {
138 void operator()(const std::string& value, std::ostream& out) const {
139 out << value + ';';
140 }
141 };
142
143 struct IncludeDirWriter {
144 explicit IncludeDirWriter(PathOutput& path_output)
145 : path_output_(path_output) {}
146 ~IncludeDirWriter() = default;
147
148 void operator()(const SourceDir& dir, std::ostream& out) const {
149 path_output_.WriteDir(out, dir, PathOutput::DIR_NO_LAST_SLASH);
150 out << ";";
151 }
152
153 PathOutput& path_output_;
154 };
155
156 struct SourceFileWriter {
157 SourceFileWriter(PathOutput& path_output, const SourceFile& source_file)
158 : path_output_(path_output), source_file_(source_file) {}
159 ~SourceFileWriter() = default;
160
161 void operator()(std::ostream& out) const {
162 path_output_.WriteFile(out, source_file_);
163 }
164
165 PathOutput& path_output_;
166 const SourceFile& source_file_;
167 };
168
169 // Some compiler options which will be written to project file. We don't need to
170 // specify all options because generated project file is going to be used only
171 // for compilation of single file. For real build ninja files are used.
172 struct CompilerOptions {
173 std::string additional_options;
174 std::string buffer_security_check;
175 std::string forced_include_files;
176 std::string disable_specific_warnings;
177 std::string optimization;
178 std::string runtime_library;
179 std::string treat_warning_as_error;
180 std::string warning_level;
181 };
182
183 const char kToolsetVersion[] = "v140"; // Visual Studio 2015
184 const char kVisualStudioVersion[] = "14.0"; // Visual Studio 2015
185 const char kWindowsKitsVersion[] = "10"; // Windows 10 SDK
186 const char kWindowsKitsIncludeVersion[] = "10.0.10240.0"; // Windows 10 SDK
187
188 std::string GetWindowsKitsIncludeDirs() {
189 // TODO(tmoniuszko): This should be taken from GN args or system environment.
brettw 2016/01/08 23:51:50 I think we should be able to extract these from th
Tomasz Moniuszko 2016/01/21 10:50:03 It's not available in include_dirs. In GYP build i
190 std::string kits_path =
191 std::string("C:\\Program Files (x86)\\Windows Kits\\") +
192 kWindowsKitsVersion + "\\";
193 return kits_path + "Include\\" + kWindowsKitsIncludeVersion + "\\shared;" +
194 kits_path + "Include\\" + kWindowsKitsIncludeVersion + "\\um;" +
195 kits_path + "Include\\" + kWindowsKitsIncludeVersion + "\\winrt;";
196 }
197
198 std::string MakeProjectGuid(std::string target_name) {
199 std::string str = base::MD5String(target_name);
200 return '{' + str.substr(0, 8) + '-' + str.substr(8, 4) + '-' +
201 str.substr(12, 4) + '-' + str.substr(16, 4) + '-' +
202 str.substr(20, 12) + '}';
203 }
204
205 std::string GetConfigurationType(const Target* target, Err* err) {
206 switch (target->output_type()) {
207 case Target::EXECUTABLE:
208 return "Application";
209 case Target::SHARED_LIBRARY:
210 case Target::LOADABLE_MODULE:
211 return "DynamicLibrary";
212 case Target::STATIC_LIBRARY:
213 case Target::SOURCE_SET:
214 return "StaticLibrary";
215 }
216
217 *err = Err(Location(),
218 "Visual Studio doesn't support '" + target->label().name() +
219 "' target output type: " +
220 Target::GetStringForOutputType(target->output_type()));
221 return "";
brettw 2016/01/08 23:51:50 return std::string(); instead. More semantically c
Tomasz Moniuszko 2016/01/21 10:50:03 Done.
222 }
223
224 #define SetOption(condition, member, value) \
225 if (condition) { \
226 options->member = value; \
227 return; \
228 }
229
230 #define AppendOption(condition, member, value, separator) \
231 if (condition) { \
232 options->member += value + separator; \
233 return; \
234 }
235
236 void ParseCompilerOption(const std::string& cflag, CompilerOptions* options) {
brettw 2016/01/08 23:51:50 You can do this in a follow-up, but I would sugges
brettw 2016/01/08 23:51:50 Just checking that we really need to do this. I mi
Tomasz Moniuszko 2016/01/21 10:50:02 It doesn't work properly because project propertie
237 if (cflag.size() > 2 && cflag[0] == '/') {
238 switch (cflag[1]) {
239 case 'F':
240 AppendOption(cflag.size() > 3 && cflag[2] == 'I', forced_include_files,
241 cflag.substr(3), ';')
242 break;
243
244 case 'G':
245 if (cflag[2] == 'S') {
246 SetOption(cflag.size() == 3, buffer_security_check, "true")
247 SetOption(cflag.size() == 4 && cflag[3] == '-', buffer_security_check,
248 "false")
249 }
250 break;
251
252 case 'M':
253 switch (cflag[2]) {
254 case 'D':
255 SetOption(cflag.size() == 3, runtime_library, "MultiThreadedDLL")
256 SetOption(cflag.size() == 4 && cflag[3] == 'd', runtime_library,
257 "MultiThreadedDebugDLL")
258 break;
259
260 case 'T':
261 SetOption(cflag.size() == 3, runtime_library, "MultiThreaded")
262 SetOption(cflag.size() == 4 && cflag[3] == 'd', runtime_library,
263 "MultiThreadedDebug")
264 break;
265 }
266 break;
267
268 case 'O':
269 switch (cflag[2]) {
270 case '2':
271 SetOption(cflag.size() == 3, optimization, "MaxSpeed")
272 break;
273
274 case 'd':
275 SetOption(cflag.size() == 3, optimization, "Disabled")
276 break;
277 }
278 break;
279
280 case 'T':
281 // Skip flags that cause treating all source files as C and C++ files.
282 if (cflag.size() == 3 && (cflag[2] == 'C' || cflag[2] == 'P'))
283 return;
284 break;
285
286 case 'W':
287 switch (cflag[2]) {
288 case '0':
289 case '1':
290 case '2':
291 case '3':
292 case '4':
293 SetOption(cflag.size() == 3, warning_level,
294 std::string("Level") + cflag[2])
295 break;
296
297 case 'X':
298 SetOption(cflag.size() == 3, treat_warning_as_error, "true")
299 break;
300 }
301 break;
302
303 case 'w':
304 AppendOption(cflag.size() > 3 && cflag[2] == 'd',
305 disable_specific_warnings, cflag.substr(3), ';')
306 break;
307 }
308 }
309
310 // Put everything else into additional_options.
311 options->additional_options += cflag + ' ';
312 }
313
314 #undef SetOption
315 #undef AppendOption
316
317 void ParseCompilerOptions(const std::vector<std::string>& cflags,
318 CompilerOptions* options) {
319 for (const std::string& flag : cflags) {
brettw 2016/01/08 23:51:50 No {}
Tomasz Moniuszko 2016/01/21 10:50:03 Done.
320 ParseCompilerOption(flag, options);
321 }
322 }
323
324 void ParseCompilerOptions(const Target* target, CompilerOptions* options) {
325 for (ConfigValuesIterator iter(target); !iter.done(); iter.Next()) {
326 ParseCompilerOptions(iter.cur().cflags(), options);
327 ParseCompilerOptions(iter.cur().cflags_c(), options);
328 ParseCompilerOptions(iter.cur().cflags_cc(), options);
329 }
330 }
331
332 bool WriteMsBuildProject(const BuildSettings* build_settings,
333 std::ostream& out,
334 const Target* target,
335 const ProjectConfiguration& config,
336 Err* err) {
337 PathOutput path_output(target->label().dir(),
338 build_settings->root_path_utf8(),
339 EscapingMode::ESCAPE_NONE);
340
341 out << "<?xml version=\"1.0\" encoding=\"utf-8\"?>" << std::endl;
342 XmlElement project(
343 out, "Project",
344 {{"DefaultTargets", "Build"},
345 {"ToolsVersion", kVisualStudioVersion},
346 {"xmlns", "http://schemas.microsoft.com/developer/msbuild/2003"}});
347
348 {
349 scoped_ptr<XmlElement> configurations =
350 project.SubElement("ItemGroup", {{"Label", "ProjectConfigurations"}});
351 std::string config_name = config.is_debug ? "Debug" : "Release";
352 scoped_ptr<XmlElement> project_config = configurations->SubElement(
353 "ProjectConfiguration",
354 {{"Include", config_name + '|' + config.platform}});
355 project_config->SubElement("Configuration")->Text(config_name);
356 project_config->SubElement("Platform")->Text(config.platform);
357 }
358
359 {
360 scoped_ptr<XmlElement> globals =
361 project.SubElement("PropertyGroup", {{"Label", "Globals"}});
362 globals->SubElement("ProjectGuid")
363 ->Text(MakeProjectGuid(target->label().name()));
364 globals->SubElement("Keyword")->Text("Win32Proj");
365 globals->SubElement("RootNamespace")->Text(target->label().name());
366 globals->SubElement("IgnoreWarnCompileDuplicatedFilename")->Text("true");
367 globals->SubElement("PreferredToolArchitecture")->Text("x64");
368 }
369
370 project.SubElement(
371 "Import", {{"Project", "$(VCTargetsPath)\\Microsoft.Cpp.Default.props"}});
372
373 {
374 scoped_ptr<XmlElement> configuration =
375 project.SubElement("PropertyGroup", {{"Label", "Configuration"}});
376 configuration->SubElement("CharacterSet")->Text("Unicode");
377 std::string configuration_type = GetConfigurationType(target, err);
378 if (configuration_type.empty())
379 return false;
380 configuration->SubElement("ConfigurationType")->Text(configuration_type);
381 }
382
383 {
384 scoped_ptr<XmlElement> locals =
385 project.SubElement("PropertyGroup", {{"Label", "Locals"}});
386 locals->SubElement("PlatformToolset")->Text(kToolsetVersion);
387 }
388
389 project.SubElement("Import",
390 {{"Project", "$(VCTargetsPath)\\Microsoft.Cpp.props"}});
391 project.SubElement(
392 "Import",
393 {{"Project", "$(VCTargetsPath)\\BuildCustomizations\\masm.props"}});
394 project.SubElement("ImportGroup", {{"Label", "ExtensionSettings"}});
395
396 {
397 scoped_ptr<XmlElement> property_sheets =
398 project.SubElement("ImportGroup", {{"Label", "PropertySheets"}});
399 property_sheets->SubElement(
400 "Import",
401 {
402 {"Condition",
403 "exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')"},
404 {"Label", "LocalAppDataPlatform"},
405 {"Project", "$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props"},
406 });
407 }
408
409 project.SubElement("PropertyGroup", {{"Label", "UserMacros"}});
410
411 {
412 scoped_ptr<XmlElement> properties = project.SubElement("PropertyGroup");
413 {
414 scoped_ptr<XmlElement> out_dir = properties->SubElement("OutDir");
415 path_output.WriteDir(out, build_settings->build_dir(),
416 PathOutput::DIR_INCLUDE_LAST_SLASH);
417 }
418 properties->SubElement("LinkIncremental")
Daniel Bratell 2016/01/14 12:08:56 LinkIncremental really needed? VS will not do any
Tomasz Moniuszko 2016/01/21 10:50:02 I thought it may conflict with command-line option
419 ->Text(config.is_debug ? "true" : "false");
420 properties->SubElement("TargetName")->Text("$(ProjectName)");
421 properties->SubElement("TargetPath")
422 ->Text("$(OutDir)\\$(ProjectName)$(TargetExt)");
423 }
424
425 {
426 scoped_ptr<XmlElement> item_definitions =
427 project.SubElement("ItemDefinitionGroup");
428 {
429 scoped_ptr<XmlElement> cl_compile =
430 item_definitions->SubElement("ClCompile");
431 {
432 scoped_ptr<XmlElement> include_dirs =
433 cl_compile->SubElement("AdditionalIncludeDirectories");
434 RecursiveTargetConfigToStream<SourceDir>(
435 target, &ConfigValues::include_dirs, IncludeDirWriter(path_output),
436 out);
437 include_dirs->Text(GetWindowsKitsIncludeDirs() +
438 "$(VSInstallDir)\\VC\\atlmfc\\include;" +
439 "%(AdditionalIncludeDirectories)");
440 }
441 CompilerOptions options;
442 ParseCompilerOptions(target, &options);
443 if (!options.additional_options.empty()) {
444 cl_compile->SubElement("AdditionalOptions")
445 ->Text(options.additional_options + "%(AdditionalOptions)");
446 }
447 if (!options.buffer_security_check.empty()) {
448 cl_compile->SubElement("BufferSecurityCheck")
449 ->Text(options.buffer_security_check);
450 }
451 cl_compile->SubElement("CompileAsWinRT")->Text("false");
452 cl_compile->SubElement("DebugInformationFormat")->Text("ProgramDatabase");
453 if (!options.disable_specific_warnings.empty()) {
454 cl_compile->SubElement("DisableSpecificWarnings")
455 ->Text(options.disable_specific_warnings +
456 "%(DisableSpecificWarnings)");
457 }
458 cl_compile->SubElement("ExceptionHandling")->Text("false");
459 if (!options.forced_include_files.empty()) {
460 cl_compile->SubElement("ForcedIncludeFiles")
461 ->Text(options.forced_include_files);
462 }
463 cl_compile->SubElement("MinimalRebuild")->Text("false");
464 if (!options.optimization.empty())
465 cl_compile->SubElement("Optimization")->Text(options.optimization);
466 if (target->config_values().has_precompiled_headers()) {
467 cl_compile->SubElement("PrecompiledHeader")->Text("Use");
468 cl_compile->SubElement("PrecompiledHeaderFile")
469 ->Text(target->config_values().precompiled_header());
470 } else {
471 cl_compile->SubElement("PrecompiledHeader")->Text("NotUsing");
472 }
473 {
474 scoped_ptr<XmlElement> preprocessor_definitions =
475 cl_compile->SubElement("PreprocessorDefinitions");
476 RecursiveTargetConfigToStream<std::string>(
477 target, &ConfigValues::defines, SemicolonSeparatedWriter(), out);
478 preprocessor_definitions->Text("%(PreprocessorDefinitions)");
479 }
480 if (!options.runtime_library.empty())
481 cl_compile->SubElement("RuntimeLibrary")->Text(options.runtime_library);
482 if (!options.treat_warning_as_error.empty()) {
483 cl_compile->SubElement("TreatWarningAsError")
484 ->Text(options.treat_warning_as_error);
485 }
486 if (!options.warning_level.empty())
487 cl_compile->SubElement("WarningLevel")->Text(options.warning_level);
488 }
489
490 // We don't include resource compilation and link options as ninja files
491 // are used to generate real build.
492 }
493
494 {
495 scoped_ptr<XmlElement> group = project.SubElement("ItemGroup");
496 if (!target->config_values().precompiled_source().is_null()) {
497 group->SubElement(
498 "ClCompile", "Include",
499 SourceFileWriter(path_output,
500 target->config_values().precompiled_source()))
501 ->SubElement("PrecompiledHeader")
502 ->Text("Create");
503 }
504
505 for (const SourceFile& file : target->sources()) {
506 SourceFileType type = GetSourceFileType(file);
507 if (type == SOURCE_H || type == SOURCE_CPP || type == SOURCE_C) {
508 group->SubElement("ClCompile", "Include",
509 SourceFileWriter(path_output, file));
510 }
511 }
512 }
513
514 project.SubElement("Import",
515 {{"Project", "$(VCTargetsPath)\\Microsoft.Cpp.targets"}});
516 project.SubElement(
517 "Import",
518 {{"Project", "$(VCTargetsPath)\\BuildCustomizations\\masm.targets"}});
519 project.SubElement("ImportGroup", {{"Label", "ExtensionTargets"}});
520
521 {
522 scoped_ptr<XmlElement> build =
523 project.SubElement("Target", {{"Name", "Build"}});
brettw 2016/01/08 23:51:50 This {{...}} is "uniform initialization syntax" ri
Tomasz Moniuszko 2016/01/21 10:50:03 Done.
524 build->SubElement(
525 "Exec", {{"Command", "call ninja.exe -C $(OutDir) $(ProjectName)"}});
526 }
527
528 {
529 scoped_ptr<XmlElement> clean =
530 project.SubElement("Target", {{"Name", "Clean"}});
531 clean->SubElement(
532 "Exec",
533 {{"Command", "call ninja.exe -C $(OutDir) -tclean $(ProjectName)"}});
534 }
535
536 return true;
537 }
538
539 } // namespace
540
541 VisualStudioWriter::VisualStudioWriter(const BuildSettings* build_settings)
542 : build_settings_(build_settings) {}
543
544 VisualStudioWriter::~VisualStudioWriter() {}
545
546 // static
547 bool VisualStudioWriter::RunAndWriteFiles(const BuildSettings* build_settings,
548 Builder* builder,
549 Err* err) {
550 std::vector<const Target*> targets = builder->GetAllResolvedTargets();
551
552 VisualStudioWriter writer(build_settings);
553
554 ProjectConfiguration config;
555 const Value* value = build_settings->build_args().GetArgOverride("is_debug");
556 config.is_debug = value == nullptr || value->boolean_value();
557 config.platform = "Win32";
558 value = build_settings->build_args().GetArgOverride(variables::kTargetCpu);
559 if (value != nullptr && value->string_value() == "x64")
560 config.platform = "x64";
561
562 for (const Target* target : targets) {
563 // Skip actions and groups.
564 if (target->output_type() == Target::GROUP ||
565 target->output_type() == Target::COPY_FILES ||
566 target->output_type() == Target::ACTION ||
567 target->output_type() == Target::ACTION_FOREACH) {
568 continue;
569 }
570
571 if (!writer.WriteProjectFile(target, config, err))
572 return false;
573 }
574
575 return writer.WriteSolutionFile(targets);
576 }
577
578 bool VisualStudioWriter::WriteProjectFile(const Target* target,
579 const ProjectConfiguration& config,
580 Err* err) const {
581 SourceFile target_file = target->label().dir().ResolveRelativeFile(
582 Value(nullptr, target->label().name() + ".vcxproj"), err);
583 if (target_file.is_null())
584 return false;
585
586 base::FilePath vcxproj_file(build_settings_->GetFullPath(target_file));
587
588 std::ofstream file;
brettw 2016/01/08 23:51:50 In the other writers, you can see I made a strings
Daniel Bratell 2016/01/14 15:41:14 Just tested. Such a change changes the generation
589 file.open(FilePathToUTF8(vcxproj_file).c_str(),
590 std::ios_base::out | std::ios_base::binary);
591 if (file.fail()) {
592 *err = Err(Location(), "Couldn't open " + target->label().name() +
593 ".vcxproj for writing");
594 return false;
595 }
596
597 return WriteMsBuildProject(build_settings_, file, target, config, err);
598 }
599
600 bool VisualStudioWriter::WriteSolutionFile(
601 const std::vector<const Target*>& targets) const {
602 return true;
603 }
OLDNEW
« tools/gn/visual_studio_writer.h ('K') | « tools/gn/visual_studio_writer.h ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698