Absolute path fixes for gn.

To avoid collisions object files and other build ouput is now prefixed
with ABS_PATH and the full path of the input file.
E.g. /some/path/foo.cc maps to //out/obj/ABS_PATH/some/path/foo.o

In addition there are several fixes for absolute paths on Windows:
The drive letter colon is skipped when parsing labels.
Transform C:\ to /C:\ where needed.

BUG=445454
NOPRESUBMIT=true

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

Cr-Commit-Position: refs/heads/master@{#315531}
diff --git a/tools/gn/filesystem_utils.cc b/tools/gn/filesystem_utils.cc
index 89e8a30..7495b05c 100644
--- a/tools/gn/filesystem_utils.cc
+++ b/tools/gn/filesystem_utils.cc
@@ -520,6 +520,22 @@
 
 std::string MakeRelativePath(const std::string& input,
                              const std::string& dest) {
+#if defined(OS_WIN)
+  // Make sure that absolute |input| path starts with a slash if |dest| path
+  // does. Otherwise skipping common prefixes won't work properly. Ensure the
+  // same for |dest| path too.
+  if (IsPathAbsolute(input) && !IsSlash(input[0]) && IsSlash(dest[0])) {
+    std::string corrected_input(1, dest[0]);
+    corrected_input.append(input);
+    return MakeRelativePath(corrected_input, dest);
+  }
+  if (IsPathAbsolute(dest) && !IsSlash(dest[0]) && IsSlash(input[0])) {
+    std::string corrected_dest(1, input[0]);
+    corrected_dest.append(dest);
+    return MakeRelativePath(input, corrected_dest);
+  }
+#endif
+
   std::string ret;
 
   // Skip the common prefixes of the source and dest as long as they end in
@@ -732,6 +748,19 @@
       size_t build_dir_size = build_dir.size();
       result.value().append(&source_dir.value()[build_dir_size],
                             source_dir.value().size() - build_dir_size);
+    } else {
+      result.value().append("ABS_PATH");
+#if defined(OS_WIN)
+      // Windows absolute path contains ':' after drive letter. Remove it to
+      // avoid inserting ':' in the middle of path (eg. "ABS_PATH/C:/").
+      std::string src_dir_value = source_dir.value();
+      const auto colon_pos = src_dir_value.find(':');
+      if (colon_pos != std::string::npos)
+        src_dir_value.erase(src_dir_value.begin() + colon_pos);
+#else
+      const std::string& src_dir_value = source_dir.value();
+#endif
+      result.value().append(src_dir_value);
     }
   }
   return result;
diff --git a/tools/gn/filesystem_utils_unittest.cc b/tools/gn/filesystem_utils_unittest.cc
index d398366..9f874ebd 100644
--- a/tools/gn/filesystem_utils_unittest.cc
+++ b/tools/gn/filesystem_utils_unittest.cc
@@ -505,6 +505,22 @@
   EXPECT_EQ("two/obj/foo/bar/",
             GetOutputDirForSourceDirAsOutputFile(
                 &other_settings, SourceDir("//foo/bar/")).value());
+
+  // Absolute source path
+  EXPECT_EQ("//out/Debug/obj/ABS_PATH/abs/",
+            GetOutputDirForSourceDir(
+                &default_settings, SourceDir("/abs")).value());
+  EXPECT_EQ("obj/ABS_PATH/abs/",
+            GetOutputDirForSourceDirAsOutputFile(
+                &default_settings, SourceDir("/abs")).value());
+#if defined(OS_WIN)
+  EXPECT_EQ("//out/Debug/obj/ABS_PATH/C/abs/",
+            GetOutputDirForSourceDir(
+                &default_settings, SourceDir("/C:/abs")).value());
+  EXPECT_EQ("obj/ABS_PATH/C/abs/",
+            GetOutputDirForSourceDirAsOutputFile(
+                &default_settings, SourceDir("/C:/abs")).value());
+#endif
 }
 
 TEST(FilesystemUtils, GetGenDirForSourceDir) {
diff --git a/tools/gn/function_get_path_info_unittest.cc b/tools/gn/function_get_path_info_unittest.cc
index 855e217..306c57e 100644
--- a/tools/gn/function_get_path_info_unittest.cc
+++ b/tools/gn/function_get_path_info_unittest.cc
@@ -95,8 +95,12 @@
   EXPECT_EQ("//out/Debug/obj/src/foo", Call(".", "out_dir"));
   EXPECT_EQ("//out/Debug/obj/src/foo", Call("bar", "out_dir"));
   EXPECT_EQ("//out/Debug/obj/foo", Call("//foo/bar.txt", "out_dir"));
-  // System paths go into the root obj directory.
-  EXPECT_EQ("//out/Debug/obj", Call("/foo/bar.txt", "out_dir"));
+  // System paths go into the ABS_PATH obj directory.
+  EXPECT_EQ("//out/Debug/obj/ABS_PATH/foo", Call("/foo/bar.txt", "out_dir"));
+#if defined(OS_WIN)
+  EXPECT_EQ("//out/Debug/obj/ABS_PATH/C/foo",
+            Call("/C:/foo/bar.txt", "out_dir"));
+#endif
 }
 
 // Note build dir is "//out/Debug/".
diff --git a/tools/gn/label.cc b/tools/gn/label.cc
index 095128d..89cd335 100644
--- a/tools/gn/label.cc
+++ b/tools/gn/label.cc
@@ -5,7 +5,9 @@
 #include "tools/gn/label.h"
 
 #include "base/logging.h"
+#include "base/strings/string_util.h"
 #include "tools/gn/err.h"
+#include "tools/gn/filesystem_utils.h"
 #include "tools/gn/parse_tree.h"
 #include "tools/gn/value.h"
 
@@ -93,8 +95,23 @@
              Err* err) {
   // To workaround the problem that StringPiece operator[] doesn't return a ref.
   const char* input_str = input.data();
-
-  size_t path_separator = input.find_first_of(":(");
+  size_t offset = 0;
+#if defined(OS_WIN)
+  if (IsPathAbsolute(input)) {
+    if (input[0] != '/') {
+      *err = Err(original_value, "Bad absolute path.",
+                 "Absolute paths must be of the form /C:\\ but this is \"" +
+                     input.as_string() + "\".");
+      return false;
+    }
+    if (input.size() > 3 && input[2] == ':' && IsSlash(input[3]) &&
+        IsAsciiAlpha(input[1])) {
+      // Skip over the drive letter colon.
+      offset = 3;
+    }
+  }
+#endif
+  size_t path_separator = input.find_first_of(":(", offset);
   base::StringPiece location_piece;
   base::StringPiece name_piece;
   base::StringPiece toolchain_piece;
diff --git a/tools/gn/label_pattern.cc b/tools/gn/label_pattern.cc
index 396a9b73..4b31174 100644
--- a/tools/gn/label_pattern.cc
+++ b/tools/gn/label_pattern.cc
@@ -4,6 +4,7 @@
 
 #include "tools/gn/label_pattern.h"
 
+#include "base/strings/string_util.h"
 #include "tools/gn/err.h"
 #include "tools/gn/filesystem_utils.h"
 #include "tools/gn/value.h"
@@ -121,7 +122,23 @@
   // Extract path and name.
   base::StringPiece path;
   base::StringPiece name;
-  size_t colon = str.find(':');
+  size_t offset = 0;
+#if defined(OS_WIN)
+  if (IsPathAbsolute(str)) {
+    if (str[0] != '/') {
+      *err = Err(value, "Bad absolute path.",
+                 "Absolute paths must be of the form /C:\\ but this is \"" +
+                     str.as_string() + "\".");
+      return LabelPattern();
+    }
+    if (str.size() > 3 && str[2] == ':' && IsSlash(str[3]) &&
+        IsAsciiAlpha(str[1])) {
+      // Skip over the drive letter colon.
+      offset = 3;
+    }
+  }
+#endif
+  size_t colon = str.find(':', offset);
   if (colon == std::string::npos) {
     path = base::StringPiece(str);
   } else {
diff --git a/tools/gn/label_unittest.cc b/tools/gn/label_unittest.cc
index 02c658a..9fea812 100644
--- a/tools/gn/label_unittest.cc
+++ b/tools/gn/label_unittest.cc
@@ -33,6 +33,11 @@
       // Absolute paths.
       { "//chrome/", "/chrome:bar",               true , "/chrome/",           "bar",  "//t/",   "d" },
       { "//chrome/", "/chrome/:bar",              true,  "/chrome/",           "bar",  "//t/",   "d" },
+#if defined(OS_WIN)
+      { "//chrome/", "/C:/chrome:bar",            true , "/C:/chrome/",        "bar",  "//t/",   "d" },
+      { "//chrome/", "/C:/chrome/:bar",           true,  "/C:/chrome/",        "bar",  "//t/",   "d" },
+      { "//chrome/", "C:/chrome:bar",             false, "",                   "",     "",       "" },
+#endif
       // Refers to root dir.
       { "//chrome/", "//:bar",                    true,  "//",                 "bar",  "//t/",   "d" },
       // Implicit directory
diff --git a/tools/gn/source_dir.cc b/tools/gn/source_dir.cc
index 9f6c745..8798b4d8 100644
--- a/tools/gn/source_dir.cc
+++ b/tools/gn/source_dir.cc
@@ -82,8 +82,15 @@
             p.as_string()).value());
     NormalizePath(&absolute);
     if (!MakeAbsolutePathRelativeIfPossible(source_root, absolute,
-                                            &ret.value_))
-      ret.value_ = absolute;
+                                            &ret.value_)) {
+#if defined(OS_WIN)
+      // On Windows we'll accept "C:\foo" as an absolute path, which we want
+      // to convert to "/C:..." here.
+      if (absolute[0] != '/')
+        ret.value_ = "/";
+#endif
+      ret.value_.append(absolute.data(), absolute.size());
+    }
     return ret;
   }
 
@@ -134,8 +141,14 @@
         FilePathToUTF8(Resolve(UTF8ToFilePath(source_root)).AppendASCII(
             p.as_string()).value());
     NormalizePath(&absolute);
-    if (!MakeAbsolutePathRelativeIfPossible(source_root, absolute, &ret.value_))
-      ret.value_ = absolute;
+    if (!MakeAbsolutePathRelativeIfPossible(source_root, absolute,
+                                            &ret.value_)) {
+#if defined(OS_WIN)
+      if (absolute[0] != '/')  // See the file case for why we do this check.
+        ret.value_ = "/";
+#endif
+      ret.value_.append(absolute.data(), absolute.size());
+    }
     if (!EndsWithSlash(ret.value_))
       ret.value_.push_back('/');
     return ret;
diff --git a/tools/gn/substitution_writer_unittest.cc b/tools/gn/substitution_writer_unittest.cc
index b59475b..f5dcb78 100644
--- a/tools/gn/substitution_writer_unittest.cc
+++ b/tools/gn/substitution_writer_unittest.cc
@@ -161,7 +161,12 @@
   EXPECT_EQ("/baz.txt", GetRelSubst("/baz.txt", SUBSTITUTION_SOURCE));
   EXPECT_EQ("/.", GetRelSubst("/baz.txt", SUBSTITUTION_SOURCE_DIR));
   EXPECT_EQ("gen", GetRelSubst("/baz.txt", SUBSTITUTION_SOURCE_GEN_DIR));
-  EXPECT_EQ("obj", GetRelSubst("/baz.txt", SUBSTITUTION_SOURCE_OUT_DIR));
+  EXPECT_EQ("obj/ABS_PATH",
+            GetRelSubst("/baz.txt", SUBSTITUTION_SOURCE_OUT_DIR));
+#if defined(OS_WIN)
+  EXPECT_EQ("obj/ABS_PATH/C",
+            GetRelSubst("/C:/baz.txt", SUBSTITUTION_SOURCE_OUT_DIR));
+#endif
 
   EXPECT_EQ(".",
             GetRelSubst("//baz.txt", SUBSTITUTION_SOURCE_ROOT_RELATIVE_DIR));