Make regen with --dotfile and --root work.

Also add a test for GetSelfInvocationCommandLine().

Also fix a bug in MakeAbsoluteFilePathRelativeIfPossible() found
by the new test: It now works correctly if the two paths passed in
have different slash types right after the drive letter. If handed
"C:\foo" "C:/foo/f", the function used to return "..\foo\f", now it
returns just "f". This also makes the exe_path in the gn regen command
look nicer, and might fix a few other things.

BUG=23

Change-Id: I683d6f0ef5d742686181ff11545a62c052f06605
Reviewed-on: https://gn-review.googlesource.com/c/3280
Commit-Queue: Brett Wilson <brettw@chromium.org>
Reviewed-by: Brett Wilson <brettw@chromium.org>
diff --git a/tools/gn/build_settings.cc b/tools/gn/build_settings.cc
index 84f2a3c..3cc211b 100644
--- a/tools/gn/build_settings.cc
+++ b/tools/gn/build_settings.cc
@@ -12,7 +12,8 @@
 BuildSettings::BuildSettings() = default;
 
 BuildSettings::BuildSettings(const BuildSettings& other)
-    : root_path_(other.root_path_),
+    : dotfile_name_(other.dotfile_name_),
+      root_path_(other.root_path_),
       root_path_utf8_(other.root_path_utf8_),
       secondary_source_path_(other.secondary_source_path_),
       python_path_(other.python_path_),
diff --git a/tools/gn/build_settings.h b/tools/gn/build_settings.h
index 52f3625..51a7d6b 100644
--- a/tools/gn/build_settings.h
+++ b/tools/gn/build_settings.h
@@ -39,8 +39,10 @@
   // Absolute path of the source root on the local system. Everything is
   // relative to this. Does not end in a [back]slash.
   const base::FilePath& root_path() const { return root_path_; }
+  const base::FilePath& dotfile_name() const { return dotfile_name_; }
   const std::string& root_path_utf8() const { return root_path_utf8_; }
   void SetRootPath(const base::FilePath& r);
+  void set_dotfile_name(const base::FilePath& d) { dotfile_name_ = d; }
 
   // When nonempty, specifies a parallel directory higherarchy in which to
   // search for buildfiles if they're not found in the root higherarchy. This
@@ -119,6 +121,7 @@
 
  private:
   Label root_target_label_;
+  base::FilePath dotfile_name_;
   base::FilePath root_path_;
   std::string root_path_utf8_;
   base::FilePath secondary_source_path_;
diff --git a/tools/gn/filesystem_utils.cc b/tools/gn/filesystem_utils.cc
index e8f0696..2fc4b24 100644
--- a/tools/gn/filesystem_utils.cc
+++ b/tools/gn/filesystem_utils.cc
@@ -428,9 +428,14 @@
   target.GetComponents(&target_components);
 #if defined(OS_WIN)
   // On Windows, it's impossible to have a relative path from C:\foo to D:\bar,
-  // so return the target as an aboslute path instead.
+  // so return the target as an absolute path instead.
   if (base_components[0] != target_components[0])
     return target;
+
+  // GetComponents() returns the first slash after the root. Set it to the
+  // same value in both component lists so that relative paths between
+  // "C:/foo/..." and "C:\foo\..." are computed correctly.
+  target_components[1] = base_components[1];
 #endif
   size_t i;
   for (i = 0; i < base_components.size() && i < target_components.size(); i++) {
diff --git a/tools/gn/filesystem_utils_unittest.cc b/tools/gn/filesystem_utils_unittest.cc
index 87e37f8..dcaa099 100644
--- a/tools/gn/filesystem_utils_unittest.cc
+++ b/tools/gn/filesystem_utils_unittest.cc
@@ -188,6 +188,10 @@
       base::FilePath(L"..\\.."),
       MakeAbsoluteFilePathRelativeIfPossible(
           base::FilePath(L"C:\\src\\out\\Debug"), base::FilePath(L"C:\\src")));
+  EXPECT_EQ(
+      base::FilePath(L"..\\.."),
+      MakeAbsoluteFilePathRelativeIfPossible(
+          base::FilePath(L"C:\\src\\out\\Debug"), base::FilePath(L"C:/src")));
   EXPECT_EQ(base::FilePath(L"."),
             MakeAbsoluteFilePathRelativeIfPossible(base::FilePath(L"C:\\src"),
                                                    base::FilePath(L"C:\\src")));
diff --git a/tools/gn/ninja_build_writer.cc b/tools/gn/ninja_build_writer.cc
index 56d6c5f..f13a8b9 100644
--- a/tools/gn/ninja_build_writer.cc
+++ b/tools/gn/ninja_build_writer.cc
@@ -47,7 +47,10 @@
   const Target* last_seen;
 };
 
-std::string GetSelfInvocationCommand(const BuildSettings* build_settings) {
+} // namespace
+
+base::CommandLine GetSelfInvocationCommandLine(
+    const BuildSettings* build_settings) {
   const base::FilePath build_path =
       build_settings->build_dir().Resolve(build_settings->root_path());
 
@@ -81,6 +84,18 @@
   escape_shell.inhibit_quoting = true;
 #endif
 
+  // If both --root and --dotfile are passed, make sure the --dotfile is
+  // made relative to the build dir here.
+  base::FilePath dotfile_path = build_settings->dotfile_name();
+  if (!dotfile_path.empty()) {
+    if (build_path.IsAbsolute()) {
+      dotfile_path =
+          MakeAbsoluteFilePathRelativeIfPossible(build_path, dotfile_path);
+    }
+    cmdline.AppendSwitchPath(std::string("--") + switches::kDotfile,
+                             dotfile_path.NormalizePathSeparatorsTo('/'));
+  }
+
   const base::CommandLine& our_cmdline =
       *base::CommandLine::ForCurrentProcess();
   const base::CommandLine::SwitchMap& switches = our_cmdline.GetSwitches();
@@ -91,13 +106,21 @@
     // implicitly in the future. Keeping --args would mean changes to the file
     // would be ignored.
     if (i->first != switches::kQuiet && i->first != switches::kRoot &&
-        i->first != switches::kArgs) {
+        i->first != switches::kDotfile && i->first != switches::kArgs) {
       std::string escaped_value =
           EscapeString(FilePathToUTF8(i->second), escape_shell, nullptr);
       cmdline.AppendSwitchASCII(i->first, escaped_value);
     }
   }
 
+  return cmdline;
+}
+
+namespace {
+
+std::string GetSelfInvocationCommand(const BuildSettings* build_settings) {
+  base::CommandLine cmdline = GetSelfInvocationCommandLine(
+      build_settings);
 #if defined(OS_WIN)
   return base::WideToUTF8(cmdline.GetCommandLineString());
 #else
diff --git a/tools/gn/ninja_build_writer.h b/tools/gn/ninja_build_writer.h
index 9d2ac4c..f4351e4 100644
--- a/tools/gn/ninja_build_writer.h
+++ b/tools/gn/ninja_build_writer.h
@@ -20,6 +20,10 @@
 class Target;
 class Toolchain;
 
+namespace base {
+class CommandLine;
+}  // base
+
 // Generates the toplevel "build.ninja" file. This references the individual
 // toolchain files and lists all input .gn files as dependencies of the
 // build itself.
@@ -70,4 +74,8 @@
 
 extern const char kNinjaRules_Help[];
 
+// Exposed for testing.
+base::CommandLine GetSelfInvocationCommandLine(
+    const BuildSettings* build_settings);
+
 #endif  // TOOLS_GN_NINJA_BUILD_WRITER_H_
diff --git a/tools/gn/ninja_build_writer_unittest.cc b/tools/gn/ninja_build_writer_unittest.cc
index 5e19df2..55ffcc9 100644
--- a/tools/gn/ninja_build_writer_unittest.cc
+++ b/tools/gn/ninja_build_writer_unittest.cc
@@ -4,9 +4,12 @@
 
 #include <sstream>
 
+#include "base/command_line.h"
+#include "base/files/file_util.h"
 #include "tools/gn/ninja_build_writer.h"
 #include "tools/gn/pool.h"
 #include "tools/gn/scheduler.h"
+#include "tools/gn/switches.h"
 #include "tools/gn/target.h"
 #include "tools/gn/test_with_scheduler.h"
 #include "tools/gn/test_with_scope.h"
@@ -14,6 +17,55 @@
 
 using NinjaBuildWriterTest = TestWithScheduler;
 
+class ScopedDotGNFile {
+ public:
+  ScopedDotGNFile(const base::FilePath& path)
+      : path_(path),
+        file_(path, base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE) {
+    EXPECT_TRUE(file_.IsValid());
+  }
+  ~ScopedDotGNFile() {
+    file_.Close();
+    base::DeleteFile(path_, false);
+  }
+
+ private:
+  base::FilePath path_;
+  base::File file_;
+};
+
+TEST_F(NinjaBuildWriterTest, GetSelfInvocationCommandLine) {
+  // TestWithScope sets up a config with a build dir of //out/Debug.
+  TestWithScope setup;
+  base::CommandLine cmd_out(base::CommandLine::NO_PROGRAM);
+
+  // Setup sets the default root dir to ".".
+  base::FilePath root(FILE_PATH_LITERAL("."));
+  base::FilePath root_realpath = base::MakeAbsoluteFilePath(root);
+
+  base::FilePath gn(FILE_PATH_LITERAL("testdot.gn"));
+
+  // The file must exist on disk for MakeAbsoluteFilePath() to work.
+  ScopedDotGNFile dot_gn(gn);
+  base::FilePath gn_realpath = base::MakeAbsoluteFilePath(gn);
+
+  // Without any parameters the self invocation should pass --root=../..
+  // (from //out/Debug to //).
+  setup.build_settings()->SetRootPath(root_realpath);
+  cmd_out = GetSelfInvocationCommandLine(setup.build_settings());
+  EXPECT_EQ("../..", cmd_out.GetSwitchValueASCII(switches::kRoot));
+  EXPECT_FALSE(cmd_out.HasSwitch(switches::kDotfile));
+
+  // If --root is . and --dotfile is foo/.gn, then --dotfile also needs
+  // to to become ../../foo/.gn.
+  setup.build_settings()->SetRootPath(root_realpath);
+  setup.build_settings()->set_dotfile_name(gn_realpath);
+  cmd_out = GetSelfInvocationCommandLine(setup.build_settings());
+  EXPECT_EQ("../..", cmd_out.GetSwitchValueASCII(switches::kRoot));
+  EXPECT_EQ("../../testdot.gn",
+            cmd_out.GetSwitchValueASCII(switches::kDotfile));
+}
+
 TEST_F(NinjaBuildWriterTest, TwoTargets) {
   TestWithScope setup;
   Err err;
diff --git a/tools/gn/setup.cc b/tools/gn/setup.cc
index 9d2e7b1..27c6475 100644
--- a/tools/gn/setup.cc
+++ b/tools/gn/setup.cc
@@ -561,19 +561,21 @@
     // When --root is specified, an alternate --dotfile can also be set.
     // --dotfile should be a real file path and not a "//foo" source-relative
     // path.
-    base::FilePath dot_file_path =
+    base::FilePath dotfile_path =
         cmdline.GetSwitchValuePath(switches::kDotfile);
-    if (dot_file_path.empty()) {
+    if (dotfile_path.empty()) {
       dotfile_name_ = root_path.Append(kGnFile);
     } else {
-      dotfile_name_ = base::MakeAbsoluteFilePath(dot_file_path);
+      dotfile_name_ = base::MakeAbsoluteFilePath(dotfile_path);
       if (dotfile_name_.empty()) {
         Err(Location(), "Could not load dotfile.",
-            "The file \"" + FilePathToUTF8(dot_file_path) +
+            "The file \"" + FilePathToUTF8(dotfile_path) +
                 "\" couldn't be loaded.")
             .PrintToStdout();
         return false;
       }
+      // Only set dotfile_name if it was passed explicitly.
+      build_settings_.set_dotfile_name(dotfile_name_);
     }
   } else {
     // In the default case, look for a dotfile and that also tells us where the
diff --git a/tools/gn/switches.cc b/tools/gn/switches.cc
index 3d77642..1e0082c 100644
--- a/tools/gn/switches.cc
+++ b/tools/gn/switches.cc
@@ -63,9 +63,6 @@
   Normally GN loads the ".gn"file  from the source root for some basic
   configuration (see "gn help dotfile"). This flag allows you to
   use a different file.
-
-  Note that this interacts with "--root" in a possibly incorrect way.
-  It would be nice to test the edge cases and document or fix.
 )";
 
 const char kFailOnUnusedArgs[] = "fail-on-unused-args";