Allow replacing hardcoded "ninja.exe" in VS generated projects

By specifying the --ninja-executable flag, we can generate VS projects
that invoke a wrapper around ninja.exe, instead of invoking ninja.exe
directly.

The behavior is unchanged if the --ninja-executable flag is not set.

Also s/master/main in README.md

Change-Id: I3b7a30b355976139723f47da5bf8965645dd40d1
Reviewed-on: https://gn-review.googlesource.com/c/gn/+/11680
Reviewed-by: Scott Graham <scottmg@chromium.org>
diff --git a/README.md b/README.md
index d6c9118..f62e28f 100644
--- a/README.md
+++ b/README.md
@@ -5,10 +5,10 @@
 
 Related resources:
 
-  * Documentation in [docs/](https://gn.googlesource.com/gn/+/master/docs/). In
+  * Documentation in [docs/](https://gn.googlesource.com/gn/+/main/docs/). In
     particular [GN Quick Start
-    guide](https://gn.googlesource.com/gn/+/master/docs/quick_start.md)
-    and the [reference](https://gn.googlesource.com/gn/+/master/docs/reference.md)
+    guide](https://gn.googlesource.com/gn/+/main/docs/quick_start.md)
+    and the [reference](https://gn.googlesource.com/gn/+/main/docs/reference.md)
     (the latter is all builtin help converted to a single file).
   * An introductory [presentation](https://docs.google.com/presentation/d/15Zwb53JcncHfEwHpnG_PoIbbzQ3GQi_cpujYwbpcbZo/edit?usp=sharing).
   * The [mailing list](https://groups.google.com/a/chromium.org/forum/#!forum/gn-dev).
@@ -124,10 +124,10 @@
   * [Compiler setup](https://cs.chromium.org/chromium/src/build/config/compiler/BUILD.gn)
 
 and the Fuchsia setup:
-  * [.gn](https://fuchsia.googlesource.com/fuchsia/+/refs/heads/master/.gn)
-  * [BUILDCONFIG.gn](https://fuchsia.googlesource.com/fuchsia/+/refs/heads/master/build/config/BUILDCONFIG.gn)
-  * [Toolchain setup](https://fuchsia.googlesource.com/fuchsia/+/refs/heads/master/build/toolchain/)
-  * [Compiler setup](https://fuchsia.googlesource.com/fuchsia/+/refs/heads/master/build/config/BUILD.gn)
+  * [.gn](https://fuchsia.googlesource.com/fuchsia/+/refs/heads/main/.gn)
+  * [BUILDCONFIG.gn](https://fuchsia.googlesource.com/fuchsia/+/refs/heads/main/build/config/BUILDCONFIG.gn)
+  * [Toolchain setup](https://fuchsia.googlesource.com/fuchsia/+/refs/heads/main/build/toolchain/)
+  * [Compiler setup](https://fuchsia.googlesource.com/fuchsia/+/refs/heads/main/build/config/BUILD.gn)
 
 ## Reporting bugs
 
@@ -147,7 +147,7 @@
 Then, to upload a change for review:
 
     git commit
-    git push origin HEAD:refs/for/master
+    git push origin HEAD:refs/for/main
 
 The first time you do this you'll get an error from the server about a missing
 change-ID. Follow the directions in the error message to install the change-ID
@@ -156,7 +156,7 @@
 When revising a change, use:
 
     git commit --amend
-    git push origin HEAD:refs/for/master
+    git push origin HEAD:refs/for/main
 
 which will add the new changes to the existing code review, rather than creating
 a new one.
@@ -193,7 +193,7 @@
 your own.
 
 GN does not guarantee the backwards-compatibility of new versions and has no
-branches or versioning scheme beyond the sequence of commits to the master git
+branches or versioning scheme beyond the sequence of commits to the main git
 branch (which is expected to be stable).
 
 In practice, however, GN is very backwards-compatible. The core functionality
diff --git a/src/gn/command_gen.cc b/src/gn/command_gen.cc
index e5248b9..0f4ed70 100644
--- a/src/gn/command_gen.cc
+++ b/src/gn/command_gen.cc
@@ -229,13 +229,19 @@
     if (command_line->HasSwitch(kSwitchIdeValueWinSdk))
       win_kit = command_line->GetSwitchValueASCII(kSwitchIdeValueWinSdk);
     std::string ninja_extra_args;
-    if (command_line->HasSwitch(kSwitchNinjaExtraArgs))
+    if (command_line->HasSwitch(kSwitchNinjaExtraArgs)) {
       ninja_extra_args =
           command_line->GetSwitchValueASCII(kSwitchNinjaExtraArgs);
+    }
+    std::string ninja_executable;
+    if (command_line->HasSwitch(kSwitchNinjaExecutable)) {
+      ninja_executable =
+          command_line->GetSwitchValueASCII(kSwitchNinjaExecutable);
+    }
     bool no_deps = command_line->HasSwitch(kSwitchNoDeps);
     bool res = VisualStudioWriter::RunAndWriteFiles(
         build_settings, builder, version, sln_name, filters, win_kit,
-        ninja_extra_args, no_deps, err);
+        ninja_extra_args, ninja_executable, no_deps, err);
     if (res && !quiet) {
       OutputString("Generating Visual Studio projects took " +
                    base::Int64ToString(timer.Elapsed().InMilliseconds()) +
diff --git a/src/gn/visual_studio_writer.cc b/src/gn/visual_studio_writer.cc
index c47c910..e88a469 100644
--- a/src/gn/visual_studio_writer.cc
+++ b/src/gn/visual_studio_writer.cc
@@ -241,6 +241,10 @@
   return true;
 }
 
+std::string GetNinjaExecutable(const std::string& ninja_executable) {
+  return ninja_executable.empty() ? "ninja.exe" : ninja_executable;
+}
+
 }  // namespace
 
 VisualStudioWriter::SolutionEntry::SolutionEntry(const std::string& _name,
@@ -324,6 +328,7 @@
                                           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) {
   std::vector<const Target*> targets;
@@ -361,7 +366,8 @@
       continue;
     }
 
-    if (!writer.WriteProjectFiles(target, ninja_extra_args, err))
+    if (!writer.WriteProjectFiles(target, ninja_extra_args, ninja_executable,
+                                  err))
       return false;
   }
 
@@ -384,6 +390,7 @@
 
 bool VisualStudioWriter::WriteProjectFiles(const Target* target,
                                            const std::string& ninja_extra_args,
+                                           const std::string& ninja_executable,
                                            Err* err) {
   std::string project_name = target->label().name();
   const char* project_config_platform = config_platform_;
@@ -416,7 +423,8 @@
   std::ostream vcxproj_string_out(&vcxproj_storage);
   SourceFileCompileTypePairs source_types;
   if (!WriteProjectFileContents(vcxproj_string_out, *projects_.back(), target,
-                                ninja_extra_args, &source_types, err)) {
+                                ninja_extra_args, ninja_executable,
+                                &source_types, err)) {
     projects_.pop_back();
     return false;
   }
@@ -440,6 +448,7 @@
     const SolutionProject& solution_project,
     const Target* target,
     const std::string& ninja_extra_args,
+    const std::string& ninja_executable,
     SourceFileCompileTypePairs* source_types,
     Err* err) {
   PathOutput path_output(
@@ -526,6 +535,7 @@
   project.SubElement("PropertyGroup", XmlAttributes("Label", "UserMacros"));
 
   std::string ninja_target = GetNinjaTarget(target);
+  std::string ninja_exe = GetNinjaExecutable(ninja_executable);
 
   {
     std::unique_ptr<XmlElementWriter> properties =
@@ -622,9 +632,9 @@
         compile_type = "CustomBuild";
         std::unique_ptr<XmlElementWriter> build = group->SubElement(
             compile_type, "Include", SourceFileWriter(path_output, file));
-        build->SubElement("Command")->Text("call ninja.exe -C $(OutDir) " +
-                                           ninja_extra_args + " " +
-                                           tool_outputs[0].value());
+        build->SubElement("Command")->Text("call " + ninja_exe +
+                                           " -C $(OutDir) " + ninja_extra_args +
+                                           " " + tool_outputs[0].value());
         build->SubElement("Outputs")->Text("$(OutDir)" +
                                            tool_outputs[0].value());
       } else {
@@ -650,7 +660,7 @@
         project.SubElement("Target", XmlAttributes("Name", "Build"));
     build->SubElement(
         "Exec",
-        XmlAttributes("Command", "call ninja.exe -C $(OutDir) " +
+        XmlAttributes("Command", "call " + ninja_exe + " -C $(OutDir) " +
                                      ninja_extra_args + " " + ninja_target));
   }
 
@@ -660,7 +670,8 @@
     clean->SubElement(
         "Exec",
         XmlAttributes("Command",
-                      "call ninja.exe -C $(OutDir) -tclean " + ninja_target));
+                      "call " + ninja_exe + " -C $(OutDir) -tclean " +
+                      ninja_target));
   }
 
   return true;
diff --git a/src/gn/visual_studio_writer.h b/src/gn/visual_studio_writer.h
index e4957a1..8d1bc8e 100644
--- a/src/gn/visual_studio_writer.h
+++ b/src/gn/visual_studio_writer.h
@@ -47,6 +47,7 @@
                                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);
 
@@ -55,6 +56,7 @@
   FRIEND_TEST_ALL_PREFIXES(VisualStudioWriterTest,
                            ResolveSolutionFolders_AbsPath);
   FRIEND_TEST_ALL_PREFIXES(VisualStudioWriterTest, NoDotSlash);
+  FRIEND_TEST_ALL_PREFIXES(VisualStudioWriterTest, NinjaExecutable);
 
   // Solution project or folder.
   struct SolutionEntry {
@@ -109,11 +111,13 @@
 
   bool WriteProjectFiles(const Target* target,
                          const std::string& ninja_extra_args,
+                         const std::string& ninja_executable,
                          Err* err);
   bool 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);
   void WriteFiltersFileContents(std::ostream& out,
diff --git a/src/gn/visual_studio_writer_unittest.cc b/src/gn/visual_studio_writer_unittest.cc
index 5e89fe1..01271a3 100644
--- a/src/gn/visual_studio_writer_unittest.cc
+++ b/src/gn/visual_studio_writer_unittest.cc
@@ -193,8 +193,54 @@
 
   std::stringstream file_contents;
   writer.WriteProjectFileContents(file_contents, *writer.projects_.back(),
-                                  &target, "", &source_types, &err);
+                                  &target, "", "", &source_types, &err);
 
   // Should find args of a ninja clean command, with no ./ before the file name.
   ASSERT_NE(file_contents.str().find("-tclean baz"), std::string::npos);
 }
+
+TEST_F(VisualStudioWriterTest, NinjaExecutable) {
+  VisualStudioWriter writer(setup_.build_settings(), "Win32",
+                            VisualStudioWriter::Version::Vs2015,
+                            "10.0.17134.0");
+
+  std::string path = MakeTestPath("blah.vcxproj");
+  writer.projects_.push_back(
+      std::make_unique<VisualStudioWriter::SolutionProject>(
+          "base", path, MakeGuid(path, "project"), MakeTestPath("/foo"),
+          "Win32"));
+
+  std::unique_ptr<Tool> tool = Tool::CreateTool(CTool::kCToolAlink);
+  tool->set_outputs(SubstitutionList::MakeForTest(
+      "{{root_out_dir}}/{{target_output_name}}{{output_extension}}", ""));
+
+  Toolchain toolchain(setup_.settings(), Label(SourceDir("//tc/"), "tc"));
+  toolchain.SetTool(std::move(tool));
+
+  Target target(setup_.settings(), Label(SourceDir("//baz/"), "baz"));
+  target.set_output_type(Target::STATIC_LIBRARY);
+  target.SetToolchain(&toolchain);
+
+  Err err;
+  ASSERT_TRUE(target.OnResolved(&err));
+
+  VisualStudioWriter::SourceFileCompileTypePairs source_types;
+
+  std::stringstream file_contents_without_flag;
+  writer.WriteProjectFileContents(file_contents_without_flag,
+                                  *writer.projects_.back(), &target, "", "",
+                                  &source_types, &err);
+
+  // Should default to ninja.exe if ninja_executable flag is not set.
+  ASSERT_NE(file_contents_without_flag.str().find("call ninja.exe"),
+            std::string::npos);
+
+  std::stringstream file_contents_with_flag;
+  writer.WriteProjectFileContents(file_contents_with_flag,
+                                  *writer.projects_.back(), &target, "",
+                                  "ninja_wrapper.exe", &source_types, &err);
+
+  // Should use ninja_wrapper.exe because ninja_executable flag is set.
+  ASSERT_NE(file_contents_with_flag.str().find("call ninja_wrapper.exe"),
+            std::string::npos);
+}