Limit the set of Visual Studio projects generated by GN

BUG=589099

Review URL: https://codereview.chromium.org/1718093006

Cr-Original-Commit-Position: refs/heads/master@{#377871}
Cr-Mirrored-From: https://chromium.googlesource.com/chromium/src
Cr-Mirrored-Commit: 014e81d144453d840e09eb9695b0dee262bc3507
diff --git a/tools/gn/command_gen.cc b/tools/gn/command_gen.cc
index b5ffc14..fee3f1a 100644
--- a/tools/gn/command_gen.cc
+++ b/tools/gn/command_gen.cc
@@ -26,11 +26,13 @@
 namespace {
 
 const char kSwitchCheck[] = "check";
+const char kSwitchFilters[] = "filters";
 const char kSwitchIde[] = "ide";
 const char kSwitchIdeValueEclipse[] = "eclipse";
 const char kSwitchIdeValueVs[] = "vs";
 const char kSwitchIdeValueVs2013[] = "vs2013";
 const char kSwitchIdeValueVs2015[] = "vs2015";
+const char kSwitchSln[] = "sln";
 
 // Called on worker thread to write the ninja file.
 void BackgroundDoWrite(const Target* target) {
@@ -155,11 +157,13 @@
                   const BuildSettings* build_settings,
                   Builder* builder,
                   Err* err) {
+  const base::CommandLine* command_line =
+      base::CommandLine::ForCurrentProcess();
   base::ElapsedTimer timer;
+
   if (ide == kSwitchIdeValueEclipse) {
     bool res = EclipseWriter::RunAndWriteFile(build_settings, builder, err);
-    if (res &&
-        !base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kQuiet)) {
+    if (res && !command_line->HasSwitch(switches::kQuiet)) {
       OutputString("Generating Eclipse settings took " +
                    base::Int64ToString(timer.Elapsed().InMilliseconds()) +
                    "ms\n");
@@ -170,10 +174,15 @@
     VisualStudioWriter::Version version =
         ide == kSwitchIdeValueVs2013 ? VisualStudioWriter::Version::Vs2013
                                      : VisualStudioWriter::Version::Vs2015;
-    bool res = VisualStudioWriter::RunAndWriteFiles(build_settings, builder,
-                                                    version, err);
-    if (res &&
-        !base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kQuiet)) {
+    std::string sln_name;
+    if (command_line->HasSwitch(kSwitchSln))
+      sln_name = command_line->GetSwitchValueASCII(kSwitchSln);
+    std::string filters;
+    if (command_line->HasSwitch(kSwitchFilters))
+      filters = command_line->GetSwitchValueASCII(kSwitchFilters);
+    bool res = VisualStudioWriter::RunAndWriteFiles(
+        build_settings, builder, version, sln_name, filters, err);
+    if (res && !command_line->HasSwitch(switches::kQuiet)) {
       OutputString("Generating Visual Studio projects took " +
                    base::Int64ToString(timer.Elapsed().InMilliseconds()) +
                    "ms\n");
@@ -193,7 +202,7 @@
 const char kGen_Help[] =
     "gn gen: Generate ninja files.\n"
     "\n"
-    "  gn gen [--ide=<ide_name>] <out_dir>\n"
+    "  gn gen [<ide options>] <out_dir>\n"
     "\n"
     "  Generates ninja files from the current tree and puts them in the given\n"
     "  output directory.\n"
@@ -203,15 +212,29 @@
     "  Or it can be a directory relative to the current directory such as:\n"
     "      out/foo\n"
     "\n"
+    "  See \"gn help switches\" for the common command-line switches.\n"
+    "\n"
+    "IDE options\n"
+    "\n"
+    "  GN optionally generates files for IDE. Possibilities for <ide options>\n"
+    "\n"
     "  --ide=<ide_name>\n"
-    "    Also generate files for an IDE. Currently supported values:\n"
+    "      Generate files for an IDE. Currently supported values:\n"
     "      \"eclipse\" - Eclipse CDT settings file.\n"
     "      \"vs\" - Visual Studio project/solution files.\n"
     "             (default Visual Studio version: 2015)\n"
     "      \"vs2013\" - Visual Studio 2013 project/solution files.\n"
     "      \"vs2015\" - Visual Studio 2015 project/solution files.\n"
     "\n"
-    "  See \"gn help switches\" for the common command-line switches.\n"
+    "  --sln=<file_name>\n"
+    "      Override default sln file name (\"all\"). Solution file is written\n"
+    "      to the root build directory. Only for Visual Studio.\n"
+    "\n"
+    "  --filters=<path_prefixes>\n"
+    "      Semicolon-separated list of label patterns used to limit the set\n"
+    "      of generated projects (see \"gn help label_pattern\"). Only\n"
+    "      matching targets will be included to the solution. Only for Visual\n"
+    "      Studio.\n"
     "\n"
     "Eclipse IDE Support\n"
     "\n"
diff --git a/tools/gn/visual_studio_writer.cc b/tools/gn/visual_studio_writer.cc
index 6c364b3..54a7bf2 100644
--- a/tools/gn/visual_studio_writer.cc
+++ b/tools/gn/visual_studio_writer.cc
@@ -11,12 +11,15 @@
 
 #include "base/logging.h"
 #include "base/memory/scoped_ptr.h"
+#include "base/strings/string_split.h"
 #include "base/strings/string_util.h"
 #include "base/strings/utf_string_conversions.h"
 #include "tools/gn/builder.h"
+#include "tools/gn/commands.h"
 #include "tools/gn/config.h"
 #include "tools/gn/config_values_extractors.h"
 #include "tools/gn/filesystem_utils.h"
+#include "tools/gn/label_pattern.h"
 #include "tools/gn/parse_tree.h"
 #include "tools/gn/path_output.h"
 #include "tools/gn/source_file_type.h"
@@ -214,8 +217,30 @@
 bool VisualStudioWriter::RunAndWriteFiles(const BuildSettings* build_settings,
                                           Builder* builder,
                                           Version version,
+                                          const std::string& sln_name,
+                                          const std::string& dir_filters,
                                           Err* err) {
-  std::vector<const Target*> targets = builder->GetAllResolvedTargets();
+  std::vector<const Target*> targets;
+  if (dir_filters.empty()) {
+    targets = builder->GetAllResolvedTargets();
+  } else {
+    std::vector<std::string> tokens = base::SplitString(
+        dir_filters, ";", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
+    SourceDir root_dir =
+        SourceDirForCurrentDirectory(build_settings->root_path());
+
+    std::vector<LabelPattern> filters;
+    for (const std::string& token : tokens) {
+      LabelPattern pattern =
+          LabelPattern::GetPattern(root_dir, Value(nullptr, token), err);
+      if (err->has_error())
+        return false;
+      filters.push_back(pattern);
+    }
+
+    commands::FilterTargetsByPatterns(builder->GetAllResolvedTargets(), filters,
+                                      &targets);
+  }
 
   const char* config_platform = "Win32";
 
@@ -259,7 +284,7 @@
             });
 
   writer.ResolveSolutionFolders();
-  return writer.WriteSolutionFile(err);
+  return writer.WriteSolutionFile(sln_name, err);
 }
 
 bool VisualStudioWriter::WriteProjectFiles(const Target* target, Err* err) {
@@ -590,9 +615,11 @@
   project.Text(files_out.str());
 }
 
-bool VisualStudioWriter::WriteSolutionFile(Err* err) {
+bool VisualStudioWriter::WriteSolutionFile(const std::string& sln_name,
+                                           Err* err) {
+  std::string name = sln_name.empty() ? "all" : sln_name;
   SourceFile sln_file = build_settings_->build_dir().ResolveRelativeFile(
-      Value(nullptr, "all.sln"), err);
+      Value(nullptr, name + ".sln"), err);
   if (sln_file.is_null())
     return false;
 
@@ -704,7 +731,8 @@
           else if (root_folder_path_[i] != folder_path[i])
             break;
         }
-        if (i == max_common_length)
+        if (i == max_common_length &&
+            (i == folder_path.size() || IsSlash(folder_path[i])))
           common_prefix_len = max_common_length;
         if (common_prefix_len < root_folder_path_.size()) {
           if (IsSlash(root_folder_path_[common_prefix_len - 1]))
diff --git a/tools/gn/visual_studio_writer.h b/tools/gn/visual_studio_writer.h
index 6950dd6..5b578ed 100644
--- a/tools/gn/visual_studio_writer.h
+++ b/tools/gn/visual_studio_writer.h
@@ -29,10 +29,16 @@
     Vs2015       // Visual Studio 2015
   };
 
-  // On failure will populate |err| and will return false.
+  // Writes Visual Studio project and solution files. |sln_name| is the optional
+  // solution file name ("all" is used if not specified). |dir_filters| is
+  // optional semicolon-separated list of label patterns used to limit the set
+  // of generated projects. Only matching targets will be included to the
+  // solution. On failure will populate |err| and will return false.
   static bool RunAndWriteFiles(const BuildSettings* build_settings,
                                Builder* builder,
                                Version version,
+                               const std::string& sln_name,
+                               const std::string& dir_filters,
                                Err* err);
 
  private:
@@ -74,9 +80,9 @@
   using SolutionProjects = std::vector<SolutionProject*>;
   using SolutionFolders = std::vector<SolutionEntry*>;
 
-  explicit VisualStudioWriter(const BuildSettings* build_settings,
-                              const char* config_platform,
-                              Version version);
+  VisualStudioWriter(const BuildSettings* build_settings,
+                     const char* config_platform,
+                     Version version);
   ~VisualStudioWriter();
 
   bool WriteProjectFiles(const Target* target, Err* err);
@@ -85,7 +91,7 @@
                                 const Target* target,
                                 Err* err);
   void WriteFiltersFileContents(std::ostream& out, const Target* target);
-  bool WriteSolutionFile(Err* err);
+  bool WriteSolutionFile(const std::string& sln_name, Err* err);
   void WriteSolutionFileContents(std::ostream& out,
                                  const base::FilePath& solution_dir_path);