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

Unified 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 side-by-side diff with in-line comments
Download patch
« tools/gn/visual_studio_writer.h ('K') | « tools/gn/visual_studio_writer.h ('k') | no next file » | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: tools/gn/visual_studio_writer.cc
diff --git a/tools/gn/visual_studio_writer.cc b/tools/gn/visual_studio_writer.cc
new file mode 100644
index 0000000000000000000000000000000000000000..f2838531b6686759d4fc408edbe3b84149194d4d
--- /dev/null
+++ b/tools/gn/visual_studio_writer.cc
@@ -0,0 +1,603 @@
+// Copyright (c) 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "tools/gn/visual_studio_writer.h"
+
+#include <fstream>
+#include <iostream>
+#include <string>
+#include <utility>
+
+#include "base/logging.h"
+#include "base/md5.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string_util.h"
+#include "tools/gn/builder.h"
+#include "tools/gn/config.h"
+#include "tools/gn/config_values_extractors.h"
+#include "tools/gn/filesystem_utils.h"
+#include "tools/gn/parse_tree.h"
+#include "tools/gn/path_output.h"
+#include "tools/gn/source_file_type.h"
+#include "tools/gn/target.h"
+#include "tools/gn/variables.h"
+
+struct ProjectConfiguration {
+ bool is_debug;
+ std::string platform;
+};
+
+namespace {
+
+using XmlAttributes = std::vector<std::pair<std::string, std::string>>;
+
+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.
+ public:
+ XmlElement(std::ostream& out,
+ const std::string& tag,
+ const XmlAttributes& attributes);
+ XmlElement(std::ostream& out,
+ const std::string& tag,
+ const XmlAttributes& attributes,
+ int indent);
+ template <class Writer>
+ XmlElement(std::ostream& out,
+ const std::string& tag,
+ const std::string& attribute_name,
+ const Writer& attribute_value_writer,
+ int indent);
+ ~XmlElement();
+
+ void Text(const std::string& content);
+
+ scoped_ptr<XmlElement> SubElement(const std::string& tag);
+ scoped_ptr<XmlElement> SubElement(const std::string& tag,
+ const XmlAttributes& attributes);
+ template <class Writer>
+ scoped_ptr<XmlElement> SubElement(const std::string& tag,
+ const std::string& attribute_name,
+ const Writer& attribute_value_writer);
+
+ private:
+ std::ostream& out_;
+ std::string tag_;
+ int indent_;
+ bool one_line_;
+
+ DISALLOW_COPY_AND_ASSIGN(XmlElement);
+};
+
+XmlElement::XmlElement(std::ostream& out,
+ const std::string& tag,
+ const XmlAttributes& attributes)
+ : XmlElement(out, tag, attributes, 0) {}
+
+XmlElement::XmlElement(std::ostream& out,
+ const std::string& tag,
+ const XmlAttributes& attributes,
+ int indent)
+ : out_(out), tag_(tag), indent_(indent), one_line_(true) {
+ out << std::string(indent, ' ') << '<' << tag;
+ 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.
+ out << ' ' << attribute.first << "=\"" << attribute.second << '"';
+ }
+ out << '>';
+}
+
+template <class Writer>
+XmlElement::XmlElement(std::ostream& out,
+ const std::string& tag,
+ const std::string& attribute_name,
+ const Writer& attribute_value_writer,
+ int indent)
+ : out_(out), tag_(tag), indent_(indent), one_line_(true) {
+ out << std::string(indent, ' ') << '<' << tag;
+ out << ' ' << attribute_name << "=\"";
+ attribute_value_writer(out);
+ out << "\">";
+}
+
+XmlElement::~XmlElement() {
+ if (!one_line_)
+ out_ << std::string(indent_, ' ');
+ out_ << "</" << tag_ << '>' << std::endl;
+}
+
+void XmlElement::Text(const std::string& content) {
+ out_ << content;
+}
+
+scoped_ptr<XmlElement> XmlElement::SubElement(const std::string& tag) {
+ return SubElement(tag, XmlAttributes());
+}
+
+scoped_ptr<XmlElement> XmlElement::SubElement(const std::string& tag,
+ const XmlAttributes& attributes) {
+ if (one_line_) {
+ out_ << std::endl;
+ one_line_ = false;
+ }
+ return make_scoped_ptr(new XmlElement(out_, tag, attributes, indent_ + 2));
+}
+
+template <class Writer>
+scoped_ptr<XmlElement> XmlElement::SubElement(
+ const std::string& tag,
+ const std::string& attribute_name,
+ const Writer& attribute_value_writer) {
+ if (one_line_) {
+ out_ << std::endl;
+ one_line_ = false;
+ }
+ return make_scoped_ptr(new XmlElement(out_, tag, attribute_name,
+ attribute_value_writer, indent_ + 2));
+}
+
+struct SemicolonSeparatedWriter {
+ void operator()(const std::string& value, std::ostream& out) const {
+ out << value + ';';
+ }
+};
+
+struct IncludeDirWriter {
+ explicit IncludeDirWriter(PathOutput& path_output)
+ : path_output_(path_output) {}
+ ~IncludeDirWriter() = default;
+
+ void operator()(const SourceDir& dir, std::ostream& out) const {
+ path_output_.WriteDir(out, dir, PathOutput::DIR_NO_LAST_SLASH);
+ out << ";";
+ }
+
+ PathOutput& path_output_;
+};
+
+struct SourceFileWriter {
+ SourceFileWriter(PathOutput& path_output, const SourceFile& source_file)
+ : path_output_(path_output), source_file_(source_file) {}
+ ~SourceFileWriter() = default;
+
+ void operator()(std::ostream& out) const {
+ path_output_.WriteFile(out, source_file_);
+ }
+
+ PathOutput& path_output_;
+ const SourceFile& source_file_;
+};
+
+// Some compiler options which will be written to project file. We don't need to
+// specify all options because generated project file is going to be used only
+// for compilation of single file. For real build ninja files are used.
+struct CompilerOptions {
+ std::string additional_options;
+ std::string buffer_security_check;
+ std::string forced_include_files;
+ std::string disable_specific_warnings;
+ std::string optimization;
+ std::string runtime_library;
+ std::string treat_warning_as_error;
+ std::string warning_level;
+};
+
+const char kToolsetVersion[] = "v140"; // Visual Studio 2015
+const char kVisualStudioVersion[] = "14.0"; // Visual Studio 2015
+const char kWindowsKitsVersion[] = "10"; // Windows 10 SDK
+const char kWindowsKitsIncludeVersion[] = "10.0.10240.0"; // Windows 10 SDK
+
+std::string GetWindowsKitsIncludeDirs() {
+ // 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
+ std::string kits_path =
+ std::string("C:\\Program Files (x86)\\Windows Kits\\") +
+ kWindowsKitsVersion + "\\";
+ return kits_path + "Include\\" + kWindowsKitsIncludeVersion + "\\shared;" +
+ kits_path + "Include\\" + kWindowsKitsIncludeVersion + "\\um;" +
+ kits_path + "Include\\" + kWindowsKitsIncludeVersion + "\\winrt;";
+}
+
+std::string MakeProjectGuid(std::string target_name) {
+ std::string str = base::MD5String(target_name);
+ return '{' + str.substr(0, 8) + '-' + str.substr(8, 4) + '-' +
+ str.substr(12, 4) + '-' + str.substr(16, 4) + '-' +
+ str.substr(20, 12) + '}';
+}
+
+std::string GetConfigurationType(const Target* target, Err* err) {
+ switch (target->output_type()) {
+ case Target::EXECUTABLE:
+ return "Application";
+ case Target::SHARED_LIBRARY:
+ case Target::LOADABLE_MODULE:
+ return "DynamicLibrary";
+ case Target::STATIC_LIBRARY:
+ case Target::SOURCE_SET:
+ return "StaticLibrary";
+ }
+
+ *err = Err(Location(),
+ "Visual Studio doesn't support '" + target->label().name() +
+ "' target output type: " +
+ Target::GetStringForOutputType(target->output_type()));
+ return "";
brettw 2016/01/08 23:51:50 return std::string(); instead. More semantically c
Tomasz Moniuszko 2016/01/21 10:50:03 Done.
+}
+
+#define SetOption(condition, member, value) \
+ if (condition) { \
+ options->member = value; \
+ return; \
+ }
+
+#define AppendOption(condition, member, value, separator) \
+ if (condition) { \
+ options->member += value + separator; \
+ return; \
+ }
+
+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
+ if (cflag.size() > 2 && cflag[0] == '/') {
+ switch (cflag[1]) {
+ case 'F':
+ AppendOption(cflag.size() > 3 && cflag[2] == 'I', forced_include_files,
+ cflag.substr(3), ';')
+ break;
+
+ case 'G':
+ if (cflag[2] == 'S') {
+ SetOption(cflag.size() == 3, buffer_security_check, "true")
+ SetOption(cflag.size() == 4 && cflag[3] == '-', buffer_security_check,
+ "false")
+ }
+ break;
+
+ case 'M':
+ switch (cflag[2]) {
+ case 'D':
+ SetOption(cflag.size() == 3, runtime_library, "MultiThreadedDLL")
+ SetOption(cflag.size() == 4 && cflag[3] == 'd', runtime_library,
+ "MultiThreadedDebugDLL")
+ break;
+
+ case 'T':
+ SetOption(cflag.size() == 3, runtime_library, "MultiThreaded")
+ SetOption(cflag.size() == 4 && cflag[3] == 'd', runtime_library,
+ "MultiThreadedDebug")
+ break;
+ }
+ break;
+
+ case 'O':
+ switch (cflag[2]) {
+ case '2':
+ SetOption(cflag.size() == 3, optimization, "MaxSpeed")
+ break;
+
+ case 'd':
+ SetOption(cflag.size() == 3, optimization, "Disabled")
+ break;
+ }
+ break;
+
+ case 'T':
+ // Skip flags that cause treating all source files as C and C++ files.
+ if (cflag.size() == 3 && (cflag[2] == 'C' || cflag[2] == 'P'))
+ return;
+ break;
+
+ case 'W':
+ switch (cflag[2]) {
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ SetOption(cflag.size() == 3, warning_level,
+ std::string("Level") + cflag[2])
+ break;
+
+ case 'X':
+ SetOption(cflag.size() == 3, treat_warning_as_error, "true")
+ break;
+ }
+ break;
+
+ case 'w':
+ AppendOption(cflag.size() > 3 && cflag[2] == 'd',
+ disable_specific_warnings, cflag.substr(3), ';')
+ break;
+ }
+ }
+
+ // Put everything else into additional_options.
+ options->additional_options += cflag + ' ';
+}
+
+#undef SetOption
+#undef AppendOption
+
+void ParseCompilerOptions(const std::vector<std::string>& cflags,
+ CompilerOptions* options) {
+ for (const std::string& flag : cflags) {
brettw 2016/01/08 23:51:50 No {}
Tomasz Moniuszko 2016/01/21 10:50:03 Done.
+ ParseCompilerOption(flag, options);
+ }
+}
+
+void ParseCompilerOptions(const Target* target, CompilerOptions* options) {
+ for (ConfigValuesIterator iter(target); !iter.done(); iter.Next()) {
+ ParseCompilerOptions(iter.cur().cflags(), options);
+ ParseCompilerOptions(iter.cur().cflags_c(), options);
+ ParseCompilerOptions(iter.cur().cflags_cc(), options);
+ }
+}
+
+bool WriteMsBuildProject(const BuildSettings* build_settings,
+ std::ostream& out,
+ const Target* target,
+ const ProjectConfiguration& config,
+ Err* err) {
+ PathOutput path_output(target->label().dir(),
+ build_settings->root_path_utf8(),
+ EscapingMode::ESCAPE_NONE);
+
+ out << "<?xml version=\"1.0\" encoding=\"utf-8\"?>" << std::endl;
+ XmlElement project(
+ out, "Project",
+ {{"DefaultTargets", "Build"},
+ {"ToolsVersion", kVisualStudioVersion},
+ {"xmlns", "http://schemas.microsoft.com/developer/msbuild/2003"}});
+
+ {
+ scoped_ptr<XmlElement> configurations =
+ project.SubElement("ItemGroup", {{"Label", "ProjectConfigurations"}});
+ std::string config_name = config.is_debug ? "Debug" : "Release";
+ scoped_ptr<XmlElement> project_config = configurations->SubElement(
+ "ProjectConfiguration",
+ {{"Include", config_name + '|' + config.platform}});
+ project_config->SubElement("Configuration")->Text(config_name);
+ project_config->SubElement("Platform")->Text(config.platform);
+ }
+
+ {
+ scoped_ptr<XmlElement> globals =
+ project.SubElement("PropertyGroup", {{"Label", "Globals"}});
+ globals->SubElement("ProjectGuid")
+ ->Text(MakeProjectGuid(target->label().name()));
+ globals->SubElement("Keyword")->Text("Win32Proj");
+ globals->SubElement("RootNamespace")->Text(target->label().name());
+ globals->SubElement("IgnoreWarnCompileDuplicatedFilename")->Text("true");
+ globals->SubElement("PreferredToolArchitecture")->Text("x64");
+ }
+
+ project.SubElement(
+ "Import", {{"Project", "$(VCTargetsPath)\\Microsoft.Cpp.Default.props"}});
+
+ {
+ scoped_ptr<XmlElement> configuration =
+ project.SubElement("PropertyGroup", {{"Label", "Configuration"}});
+ configuration->SubElement("CharacterSet")->Text("Unicode");
+ std::string configuration_type = GetConfigurationType(target, err);
+ if (configuration_type.empty())
+ return false;
+ configuration->SubElement("ConfigurationType")->Text(configuration_type);
+ }
+
+ {
+ scoped_ptr<XmlElement> locals =
+ project.SubElement("PropertyGroup", {{"Label", "Locals"}});
+ locals->SubElement("PlatformToolset")->Text(kToolsetVersion);
+ }
+
+ project.SubElement("Import",
+ {{"Project", "$(VCTargetsPath)\\Microsoft.Cpp.props"}});
+ project.SubElement(
+ "Import",
+ {{"Project", "$(VCTargetsPath)\\BuildCustomizations\\masm.props"}});
+ project.SubElement("ImportGroup", {{"Label", "ExtensionSettings"}});
+
+ {
+ scoped_ptr<XmlElement> property_sheets =
+ project.SubElement("ImportGroup", {{"Label", "PropertySheets"}});
+ property_sheets->SubElement(
+ "Import",
+ {
+ {"Condition",
+ "exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')"},
+ {"Label", "LocalAppDataPlatform"},
+ {"Project", "$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props"},
+ });
+ }
+
+ project.SubElement("PropertyGroup", {{"Label", "UserMacros"}});
+
+ {
+ scoped_ptr<XmlElement> properties = project.SubElement("PropertyGroup");
+ {
+ scoped_ptr<XmlElement> out_dir = properties->SubElement("OutDir");
+ path_output.WriteDir(out, build_settings->build_dir(),
+ PathOutput::DIR_INCLUDE_LAST_SLASH);
+ }
+ 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
+ ->Text(config.is_debug ? "true" : "false");
+ properties->SubElement("TargetName")->Text("$(ProjectName)");
+ properties->SubElement("TargetPath")
+ ->Text("$(OutDir)\\$(ProjectName)$(TargetExt)");
+ }
+
+ {
+ scoped_ptr<XmlElement> item_definitions =
+ project.SubElement("ItemDefinitionGroup");
+ {
+ scoped_ptr<XmlElement> cl_compile =
+ item_definitions->SubElement("ClCompile");
+ {
+ scoped_ptr<XmlElement> include_dirs =
+ cl_compile->SubElement("AdditionalIncludeDirectories");
+ RecursiveTargetConfigToStream<SourceDir>(
+ target, &ConfigValues::include_dirs, IncludeDirWriter(path_output),
+ out);
+ include_dirs->Text(GetWindowsKitsIncludeDirs() +
+ "$(VSInstallDir)\\VC\\atlmfc\\include;" +
+ "%(AdditionalIncludeDirectories)");
+ }
+ CompilerOptions options;
+ ParseCompilerOptions(target, &options);
+ if (!options.additional_options.empty()) {
+ cl_compile->SubElement("AdditionalOptions")
+ ->Text(options.additional_options + "%(AdditionalOptions)");
+ }
+ if (!options.buffer_security_check.empty()) {
+ cl_compile->SubElement("BufferSecurityCheck")
+ ->Text(options.buffer_security_check);
+ }
+ cl_compile->SubElement("CompileAsWinRT")->Text("false");
+ cl_compile->SubElement("DebugInformationFormat")->Text("ProgramDatabase");
+ if (!options.disable_specific_warnings.empty()) {
+ cl_compile->SubElement("DisableSpecificWarnings")
+ ->Text(options.disable_specific_warnings +
+ "%(DisableSpecificWarnings)");
+ }
+ cl_compile->SubElement("ExceptionHandling")->Text("false");
+ if (!options.forced_include_files.empty()) {
+ cl_compile->SubElement("ForcedIncludeFiles")
+ ->Text(options.forced_include_files);
+ }
+ cl_compile->SubElement("MinimalRebuild")->Text("false");
+ if (!options.optimization.empty())
+ cl_compile->SubElement("Optimization")->Text(options.optimization);
+ if (target->config_values().has_precompiled_headers()) {
+ cl_compile->SubElement("PrecompiledHeader")->Text("Use");
+ cl_compile->SubElement("PrecompiledHeaderFile")
+ ->Text(target->config_values().precompiled_header());
+ } else {
+ cl_compile->SubElement("PrecompiledHeader")->Text("NotUsing");
+ }
+ {
+ scoped_ptr<XmlElement> preprocessor_definitions =
+ cl_compile->SubElement("PreprocessorDefinitions");
+ RecursiveTargetConfigToStream<std::string>(
+ target, &ConfigValues::defines, SemicolonSeparatedWriter(), out);
+ preprocessor_definitions->Text("%(PreprocessorDefinitions)");
+ }
+ if (!options.runtime_library.empty())
+ cl_compile->SubElement("RuntimeLibrary")->Text(options.runtime_library);
+ if (!options.treat_warning_as_error.empty()) {
+ cl_compile->SubElement("TreatWarningAsError")
+ ->Text(options.treat_warning_as_error);
+ }
+ if (!options.warning_level.empty())
+ cl_compile->SubElement("WarningLevel")->Text(options.warning_level);
+ }
+
+ // We don't include resource compilation and link options as ninja files
+ // are used to generate real build.
+ }
+
+ {
+ scoped_ptr<XmlElement> group = project.SubElement("ItemGroup");
+ if (!target->config_values().precompiled_source().is_null()) {
+ group->SubElement(
+ "ClCompile", "Include",
+ SourceFileWriter(path_output,
+ target->config_values().precompiled_source()))
+ ->SubElement("PrecompiledHeader")
+ ->Text("Create");
+ }
+
+ for (const SourceFile& file : target->sources()) {
+ SourceFileType type = GetSourceFileType(file);
+ if (type == SOURCE_H || type == SOURCE_CPP || type == SOURCE_C) {
+ group->SubElement("ClCompile", "Include",
+ SourceFileWriter(path_output, file));
+ }
+ }
+ }
+
+ project.SubElement("Import",
+ {{"Project", "$(VCTargetsPath)\\Microsoft.Cpp.targets"}});
+ project.SubElement(
+ "Import",
+ {{"Project", "$(VCTargetsPath)\\BuildCustomizations\\masm.targets"}});
+ project.SubElement("ImportGroup", {{"Label", "ExtensionTargets"}});
+
+ {
+ scoped_ptr<XmlElement> build =
+ 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.
+ build->SubElement(
+ "Exec", {{"Command", "call ninja.exe -C $(OutDir) $(ProjectName)"}});
+ }
+
+ {
+ scoped_ptr<XmlElement> clean =
+ project.SubElement("Target", {{"Name", "Clean"}});
+ clean->SubElement(
+ "Exec",
+ {{"Command", "call ninja.exe -C $(OutDir) -tclean $(ProjectName)"}});
+ }
+
+ return true;
+}
+
+} // namespace
+
+VisualStudioWriter::VisualStudioWriter(const BuildSettings* build_settings)
+ : build_settings_(build_settings) {}
+
+VisualStudioWriter::~VisualStudioWriter() {}
+
+// static
+bool VisualStudioWriter::RunAndWriteFiles(const BuildSettings* build_settings,
+ Builder* builder,
+ Err* err) {
+ std::vector<const Target*> targets = builder->GetAllResolvedTargets();
+
+ VisualStudioWriter writer(build_settings);
+
+ ProjectConfiguration config;
+ const Value* value = build_settings->build_args().GetArgOverride("is_debug");
+ config.is_debug = value == nullptr || value->boolean_value();
+ config.platform = "Win32";
+ value = build_settings->build_args().GetArgOverride(variables::kTargetCpu);
+ if (value != nullptr && value->string_value() == "x64")
+ config.platform = "x64";
+
+ for (const Target* target : targets) {
+ // Skip actions and groups.
+ if (target->output_type() == Target::GROUP ||
+ target->output_type() == Target::COPY_FILES ||
+ target->output_type() == Target::ACTION ||
+ target->output_type() == Target::ACTION_FOREACH) {
+ continue;
+ }
+
+ if (!writer.WriteProjectFile(target, config, err))
+ return false;
+ }
+
+ return writer.WriteSolutionFile(targets);
+}
+
+bool VisualStudioWriter::WriteProjectFile(const Target* target,
+ const ProjectConfiguration& config,
+ Err* err) const {
+ SourceFile target_file = target->label().dir().ResolveRelativeFile(
+ Value(nullptr, target->label().name() + ".vcxproj"), err);
+ if (target_file.is_null())
+ return false;
+
+ base::FilePath vcxproj_file(build_settings_->GetFullPath(target_file));
+
+ 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
+ file.open(FilePathToUTF8(vcxproj_file).c_str(),
+ std::ios_base::out | std::ios_base::binary);
+ if (file.fail()) {
+ *err = Err(Location(), "Couldn't open " + target->label().name() +
+ ".vcxproj for writing");
+ return false;
+ }
+
+ return WriteMsBuildProject(build_settings_, file, target, config, err);
+}
+
+bool VisualStudioWriter::WriteSolutionFile(
+ const std::vector<const Target*>& targets) const {
+ return true;
+}
« 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