gn: Support build directories outside the source tree.

The main use-case is to support using a fast SSD or ramdisk for the
build directory, but a slower persistent disk for the source code.

The general idea behind this change is modifying

 - RebaseSourceAbsolutePath()
 - SourceDir::ResolveRelativeFile()
 - SourceDir::ResolveRelativeDir()
 - GetOutputDirForSourceDirAsOutputFile()
 - PathOutput

to work with paths reaching out of the source directory.

Thanks to jam@ for the Windows-fixes.

BUG=343728
TEST=New unit tests + Unit tests pass.
TEST=`gn gen /ssd/out/Debug && ninja -C /ssd/out/Debug` work as expected.

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

Cr-Commit-Position: refs/heads/master@{#303719}
diff --git a/tools/gn/action_target_generator.cc b/tools/gn/action_target_generator.cc
index 7936359..09c89e4 100644
--- a/tools/gn/action_target_generator.cc
+++ b/tools/gn/action_target_generator.cc
@@ -73,7 +73,8 @@
     return false;
 
   SourceFile script_file =
-      scope_->GetSourceDir().ResolveRelativeFile(value->string_value());
+      scope_->GetSourceDir().ResolveRelativeFile(value->string_value(),
+          scope_->settings()->build_settings()->root_path_utf8());
   if (script_file.value().empty()) {
     *err_ = Err(*value, "script name is empty");
     return false;
diff --git a/tools/gn/build_settings.cc b/tools/gn/build_settings.cc
index 37d50f5..3ffb8283 100644
--- a/tools/gn/build_settings.cc
+++ b/tools/gn/build_settings.cc
@@ -17,7 +17,6 @@
       python_path_(other.python_path_),
       build_config_file_(other.build_config_file_),
       build_dir_(other.build_dir_),
-      build_to_source_dir_string_(other.build_to_source_dir_string_),
       build_args_(other.build_args_) {
 }
 
@@ -36,7 +35,6 @@
 
 void BuildSettings::SetBuildDir(const SourceDir& d) {
   build_dir_ = d;
-  build_to_source_dir_string_ = InvertDir(d);
 }
 
 base::FilePath BuildSettings::GetFullPath(const SourceFile& file) const {
diff --git a/tools/gn/build_settings.h b/tools/gn/build_settings.h
index daea82dd..33e454b 100644
--- a/tools/gn/build_settings.h
+++ b/tools/gn/build_settings.h
@@ -57,12 +57,6 @@
   const SourceDir& build_dir() const { return build_dir_; }
   void SetBuildDir(const SourceDir& dir);
 
-  // The inverse of relative_build_dir, ending with a separator.
-  // Example: relative_build_dir_ = "out/Debug/" this will be "../../"
-  const std::string& build_to_source_dir_string() const {
-    return build_to_source_dir_string_;
-  }
-
   // The build args are normally specified on the command-line.
   Args& build_args() { return build_args_; }
   const Args& build_args() const { return build_args_; }
@@ -99,7 +93,6 @@
 
   SourceFile build_config_file_;
   SourceDir build_dir_;
-  std::string build_to_source_dir_string_;
   Args build_args_;
 
   ItemDefinedCallback item_defined_callback_;
diff --git a/tools/gn/command_format.cc b/tools/gn/command_format.cc
index 890cf2c0..8162698 100644
--- a/tools/gn/command_format.cc
+++ b/tools/gn/command_format.cc
@@ -746,7 +746,8 @@
   Setup setup;
   SourceDir source_dir =
       SourceDirForCurrentDirectory(setup.build_settings().root_path());
-  SourceFile file = source_dir.ResolveRelativeFile(args[0]);
+  SourceFile file = source_dir.ResolveRelativeFile(args[0],
+      setup.build_settings().root_path_utf8());
 
   std::string output_string;
   if (FormatFileToString(&setup, file, dump_tree, &output_string)) {
diff --git a/tools/gn/filesystem_utils.cc b/tools/gn/filesystem_utils.cc
index 1e4194c..ebfb942 100644
--- a/tools/gn/filesystem_utils.cc
+++ b/tools/gn/filesystem_utils.cc
@@ -421,27 +421,6 @@
 #endif
 }
 
-std::string InvertDir(const SourceDir& path) {
-  const std::string value = path.value();
-  if (value.empty())
-    return std::string();
-
-  DCHECK(value[0] == '/');
-  size_t begin_index = 1;
-
-  // If the input begins with two slashes, skip over both (this is a
-  // source-relative dir). These must be forward slashes only.
-  if (value.size() > 1 && value[1] == '/')
-    begin_index = 2;
-
-  std::string ret;
-  for (size_t i = begin_index; i < value.size(); i++) {
-    if (IsSlash(value[i]))
-      ret.append("../");
-  }
-  return ret;
-}
-
 void NormalizePath(std::string* path) {
   char* pathbuf = path->empty() ? NULL : &(*path)[0];
 
@@ -539,18 +518,13 @@
 #endif
 }
 
-std::string RebaseSourceAbsolutePath(const std::string& input,
-                                     const SourceDir& dest_dir) {
-  CHECK(input.size() >= 2 && input[0] == '/' && input[1] == '/')
-      << "Input to rebase isn't source-absolute: " << input;
-  CHECK(dest_dir.is_source_absolute())
-      << "Dir to rebase to isn't source-absolute: " << dest_dir.value();
-
-  const std::string& dest = dest_dir.value();
+std::string MakeRelativePath(const std::string& input,
+                             const std::string& dest) {
+  std::string ret;
 
   // Skip the common prefixes of the source and dest as long as they end in
   // a [back]slash.
-  size_t common_prefix_len = 2;  // The beginning two "//" are always the same.
+  size_t common_prefix_len = 0;
   size_t max_common_length = std::min(input.size(), dest.size());
   for (size_t i = common_prefix_len; i < max_common_length; i++) {
     if (IsSlash(input[i]) && IsSlash(dest[i]))
@@ -560,7 +534,6 @@
   }
 
   // Invert the dest dir starting from the end of the common prefix.
-  std::string ret;
   for (size_t i = common_prefix_len; i < dest.size(); i++) {
     if (IsSlash(dest[i]))
       ret.append("../");
@@ -576,6 +549,58 @@
   return ret;
 }
 
+std::string RebasePath(const std::string& input,
+                       const SourceDir& dest_dir,
+                       const base::StringPiece& source_root) {
+  std::string ret;
+  DCHECK(source_root.empty() || !source_root.ends_with("/"));
+
+  bool input_is_source_path = (input.size() >= 2 &&
+                               input[0] == '/' && input[1] == '/');
+
+  if (!source_root.empty() &&
+      (!input_is_source_path || !dest_dir.is_source_absolute())) {
+    std::string input_full;
+    std::string dest_full;
+    if (input_is_source_path) {
+      source_root.AppendToString(&input_full);
+      input_full.push_back('/');
+      input_full.append(input, 2, std::string::npos);
+    } else {
+      input_full.append(input);
+    }
+    if (dest_dir.is_source_absolute()) {
+      source_root.AppendToString(&dest_full);
+      dest_full.push_back('/');
+      dest_full.append(dest_dir.value(), 2, std::string::npos);
+    } else {
+#if defined(OS_WIN)
+      // On Windows, SourceDir system-absolute paths start
+      // with /, e.g. "/C:/foo/bar".
+      const std::string& value = dest_dir.value();
+      if (value.size() > 2 && value[2] == ':')
+        dest_full.append(dest_dir.value().substr(1));
+      else
+        dest_full.append(dest_dir.value());
+#else
+      dest_full.append(dest_dir.value());
+#endif
+    }
+    bool remove_slash = false;
+    if (!EndsWithSlash(input_full)) {
+      input_full.push_back('/');
+      remove_slash = true;
+    }
+    ret = MakeRelativePath(input_full, dest_full);
+    if (remove_slash && ret.size() > 1)
+      ret.resize(ret.size() - 1);
+    return ret;
+  }
+
+  ret = MakeRelativePath(input, dest_dir.value());
+  return ret;
+}
+
 std::string DirectoryWithNoLastSlash(const SourceDir& dir) {
   std::string ret;
 
@@ -698,6 +723,16 @@
     // slashes to append to the toolchain object directory.
     result.value().append(&source_dir.value()[2],
                           source_dir.value().size() - 2);
+  } else {
+    // system-absolute
+    const std::string& build_dir =
+        settings->build_settings()->build_dir().value();
+
+    if (StartsWithASCII(source_dir.value(), build_dir, true)) {
+      size_t build_dir_size = build_dir.size();
+      result.value().append(&source_dir.value()[build_dir_size],
+                            source_dir.value().size() - build_dir_size);
+    }
   }
   return result;
 }
diff --git a/tools/gn/filesystem_utils.h b/tools/gn/filesystem_utils.h
index 20773cc7..f5cf494 100644
--- a/tools/gn/filesystem_utils.h
+++ b/tools/gn/filesystem_utils.h
@@ -116,11 +116,6 @@
                                         const base::StringPiece& path,
                                         std::string* dest);
 
-// Converts a directory to its inverse (e.g. "/foo/bar/" -> "../../").
-// This will be the empty string for the root directories ("/" and "//"), and
-// in all other cases, this is guaranteed to end in a slash.
-std::string InvertDir(const SourceDir& dir);
-
 // Collapses "." and sequential "/"s and evaluates "..".
 void NormalizePath(std::string* path);
 
@@ -128,10 +123,17 @@
 // for other systems.
 void ConvertPathToSystem(std::string* path);
 
-// Takes a source-absolute path (must begin with "//") and makes it relative
-// to the given directory, which also must be source-absolute.
-std::string RebaseSourceAbsolutePath(const std::string& input,
-                                     const SourceDir& dest_dir);
+// Takes a path, |input|, and makes it relative to the given directory
+// |dest_dir|. Both inputs may be source-relative (e.g. begins with
+// with "//") or may be absolute.
+//
+// If supplied, the |source_root| parameter is the absolute path to
+// the source root and not end in a slash. Unless you know that the
+// inputs are always source relative, this should be supplied.
+std::string RebasePath(
+    const std::string& input,
+    const SourceDir& dest_dir,
+    const base::StringPiece& source_root = base::StringPiece());
 
 // Returns the given directory with no terminating slash at the end, such that
 // appending a slash and more stuff will produce a valid path.
diff --git a/tools/gn/filesystem_utils_unittest.cc b/tools/gn/filesystem_utils_unittest.cc
index ba6b9bd..7bcfd1a 100644
--- a/tools/gn/filesystem_utils_unittest.cc
+++ b/tools/gn/filesystem_utils_unittest.cc
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include "base/files/file_path.h"
 #include "base/strings/string_util.h"
 #include "base/strings/utf_string_conversions.h"
 #include "build/build_config.h"
@@ -169,16 +170,6 @@
 #endif
 }
 
-TEST(FilesystemUtils, InvertDir) {
-  EXPECT_TRUE(InvertDir(SourceDir()) == "");
-  EXPECT_TRUE(InvertDir(SourceDir("/")) == "");
-  EXPECT_TRUE(InvertDir(SourceDir("//")) == "");
-
-  EXPECT_TRUE(InvertDir(SourceDir("//foo/bar")) == "../../");
-  EXPECT_TRUE(InvertDir(SourceDir("//foo\\bar")) == "../../");
-  EXPECT_TRUE(InvertDir(SourceDir("/foo/bar/")) == "../../");
-}
-
 TEST(FilesystemUtils, NormalizePath) {
   std::string input;
 
@@ -247,54 +238,120 @@
   EXPECT_EQ("../bar", input);
 }
 
-TEST(FilesystemUtils, RebaseSourceAbsolutePath) {
+TEST(FilesystemUtils, RebasePath) {
+  base::StringPiece source_root("/source/root");
+
   // Degenerate case.
-  EXPECT_EQ(".", RebaseSourceAbsolutePath("//", SourceDir("//")));
-  EXPECT_EQ(".",
-            RebaseSourceAbsolutePath("//foo/bar/", SourceDir("//foo/bar/")));
+  EXPECT_EQ(".", RebasePath("//", SourceDir("//"), source_root));
+  EXPECT_EQ(".", RebasePath("//foo/bar/", SourceDir("//foo/bar/"),
+                            source_root));
 
   // Going up the tree.
-  EXPECT_EQ("../foo",
-            RebaseSourceAbsolutePath("//foo", SourceDir("//bar/")));
-  EXPECT_EQ("../foo/",
-            RebaseSourceAbsolutePath("//foo/", SourceDir("//bar/")));
-  EXPECT_EQ("../../foo",
-            RebaseSourceAbsolutePath("//foo", SourceDir("//bar/moo")));
-  EXPECT_EQ("../../foo/",
-            RebaseSourceAbsolutePath("//foo/", SourceDir("//bar/moo")));
+  EXPECT_EQ("../foo", RebasePath("//foo", SourceDir("//bar/"), source_root));
+  EXPECT_EQ("../foo/", RebasePath("//foo/", SourceDir("//bar/"), source_root));
+  EXPECT_EQ("../../foo", RebasePath("//foo", SourceDir("//bar/moo"),
+                                    source_root));
+  EXPECT_EQ("../../foo/", RebasePath("//foo/", SourceDir("//bar/moo"),
+                                     source_root));
 
   // Going down the tree.
-  EXPECT_EQ("foo/bar",
-            RebaseSourceAbsolutePath("//foo/bar", SourceDir("//")));
-  EXPECT_EQ("foo/bar/",
-            RebaseSourceAbsolutePath("//foo/bar/", SourceDir("//")));
+  EXPECT_EQ("foo/bar", RebasePath("//foo/bar", SourceDir("//"), source_root));
+  EXPECT_EQ("foo/bar/", RebasePath("//foo/bar/", SourceDir("//"),
+                                   source_root));
 
   // Going up and down the tree.
-  EXPECT_EQ("../../foo/bar",
-            RebaseSourceAbsolutePath("//foo/bar", SourceDir("//a/b/")));
-  EXPECT_EQ("../../foo/bar/",
-            RebaseSourceAbsolutePath("//foo/bar/", SourceDir("//a/b/")));
+  EXPECT_EQ("../../foo/bar", RebasePath("//foo/bar", SourceDir("//a/b/"),
+                                        source_root));
+  EXPECT_EQ("../../foo/bar/", RebasePath("//foo/bar/", SourceDir("//a/b/"),
+                                         source_root));
 
   // Sharing prefix.
-  EXPECT_EQ("foo",
-            RebaseSourceAbsolutePath("//a/foo", SourceDir("//a/")));
-  EXPECT_EQ("foo/",
-            RebaseSourceAbsolutePath("//a/foo/", SourceDir("//a/")));
-  EXPECT_EQ("foo",
-            RebaseSourceAbsolutePath("//a/b/foo", SourceDir("//a/b/")));
-  EXPECT_EQ("foo/",
-            RebaseSourceAbsolutePath("//a/b/foo/", SourceDir("//a/b/")));
-  EXPECT_EQ("foo/bar",
-            RebaseSourceAbsolutePath("//a/b/foo/bar", SourceDir("//a/b/")));
-  EXPECT_EQ("foo/bar/",
-            RebaseSourceAbsolutePath("//a/b/foo/bar/", SourceDir("//a/b/")));
+  EXPECT_EQ("foo", RebasePath("//a/foo", SourceDir("//a/"), source_root));
+  EXPECT_EQ("foo/", RebasePath("//a/foo/", SourceDir("//a/"), source_root));
+  EXPECT_EQ("foo", RebasePath("//a/b/foo", SourceDir("//a/b/"), source_root));
+  EXPECT_EQ("foo/", RebasePath("//a/b/foo/", SourceDir("//a/b/"),
+                               source_root));
+  EXPECT_EQ("foo/bar", RebasePath("//a/b/foo/bar", SourceDir("//a/b/"),
+                                  source_root));
+  EXPECT_EQ("foo/bar/", RebasePath("//a/b/foo/bar/", SourceDir("//a/b/"),
+                                   source_root));
 
   // One could argue about this case. Since the input doesn't have a slash it
   // would normally not be treated like a directory and we'd go up, which is
   // simpler. However, since it matches the output directory's name, we could
   // potentially infer that it's the same and return "." for this.
-  EXPECT_EQ("../bar",
-            RebaseSourceAbsolutePath("//foo/bar", SourceDir("//foo/bar/")));
+  EXPECT_EQ("../bar", RebasePath("//foo/bar", SourceDir("//foo/bar/"),
+                                 source_root));
+
+  // Check when only |input| is system-absolute
+  EXPECT_EQ("foo", RebasePath("/source/root/foo", SourceDir("//"),
+                              base::StringPiece("/source/root")));
+  EXPECT_EQ("foo/", RebasePath("/source/root/foo/", SourceDir("//"),
+                               base::StringPiece("/source/root")));
+  EXPECT_EQ("../../builddir/Out/Debug",
+            RebasePath("/builddir/Out/Debug", SourceDir("//"),
+                       base::StringPiece("/source/root")));
+  EXPECT_EQ("../../../builddir/Out/Debug",
+            RebasePath("/builddir/Out/Debug", SourceDir("//"),
+                       base::StringPiece("/source/root/foo")));
+  EXPECT_EQ("../../../builddir/Out/Debug/",
+            RebasePath("/builddir/Out/Debug/", SourceDir("//"),
+                       base::StringPiece("/source/root/foo")));
+  EXPECT_EQ("../../path/to/foo",
+            RebasePath("/path/to/foo", SourceDir("//"),
+                       base::StringPiece("/source/root")));
+  EXPECT_EQ("../../../path/to/foo",
+            RebasePath("/path/to/foo", SourceDir("//a"),
+                       base::StringPiece("/source/root")));
+  EXPECT_EQ("../../../../path/to/foo",
+            RebasePath("/path/to/foo", SourceDir("//a/b"),
+                       base::StringPiece("/source/root")));
+
+  // Check when only |dest_dir| is system-absolute.
+  EXPECT_EQ(".",
+            RebasePath("//", SourceDir("/source/root"),
+                       base::StringPiece("/source/root")));
+  EXPECT_EQ("foo",
+            RebasePath("//foo", SourceDir("/source/root"),
+                       base::StringPiece("/source/root")));
+  EXPECT_EQ("../foo",
+            RebasePath("//foo", SourceDir("/source/root/bar"),
+                       base::StringPiece("/source/root")));
+  EXPECT_EQ("../../../source/root/foo",
+            RebasePath("//foo", SourceDir("/other/source/root"),
+                       base::StringPiece("/source/root")));
+  EXPECT_EQ("../../../../source/root/foo",
+            RebasePath("//foo", SourceDir("/other/source/root/bar"),
+                       base::StringPiece("/source/root")));
+
+  // Check when |input| and |dest_dir| are both system-absolute. Also,
+  // in this case |source_root| is never used so set it to a dummy
+  // value.
+  EXPECT_EQ("foo",
+            RebasePath("/source/root/foo", SourceDir("/source/root"),
+                       base::StringPiece("/x/y/z")));
+  EXPECT_EQ("foo/",
+            RebasePath("/source/root/foo/", SourceDir("/source/root"),
+                       base::StringPiece("/x/y/z")));
+  EXPECT_EQ("../../builddir/Out/Debug",
+            RebasePath("/builddir/Out/Debug",SourceDir("/source/root"),
+                       base::StringPiece("/x/y/z")));
+  EXPECT_EQ("../../../builddir/Out/Debug",
+            RebasePath("/builddir/Out/Debug", SourceDir("/source/root/foo"),
+                       base::StringPiece("/source/root/foo")));
+  EXPECT_EQ("../../../builddir/Out/Debug/",
+            RebasePath("/builddir/Out/Debug/", SourceDir("/source/root/foo"),
+                       base::StringPiece("/source/root/foo")));
+  EXPECT_EQ("../../path/to/foo",
+            RebasePath("/path/to/foo", SourceDir("/source/root"),
+                       base::StringPiece("/x/y/z")));
+  EXPECT_EQ("../../../path/to/foo",
+            RebasePath("/path/to/foo", SourceDir("/source/root/a"),
+                       base::StringPiece("/x/y/z")));
+  EXPECT_EQ("../../../../path/to/foo",
+            RebasePath("/path/to/foo", SourceDir("/source/root/a/b"),
+                       base::StringPiece("/x/y/z")));
+
 }
 
 TEST(FilesystemUtils, DirectoryWithNoLastSlash) {
diff --git a/tools/gn/function_exec_script.cc b/tools/gn/function_exec_script.cc
index 05270b2..2b21b72 100644
--- a/tools/gn/function_exec_script.cc
+++ b/tools/gn/function_exec_script.cc
@@ -100,7 +100,8 @@
   if (!args[0].VerifyTypeIs(Value::STRING, err))
     return Value();
   SourceFile script_source =
-      cur_dir.ResolveRelativeFile(args[0].string_value());
+      cur_dir.ResolveRelativeFile(args[0].string_value(),
+          scope->settings()->build_settings()->root_path_utf8());
   base::FilePath script_path = build_settings->GetFullPath(script_source);
   if (!build_settings->secondary_source_path().empty() &&
       !base::PathExists(script_path)) {
@@ -124,7 +125,8 @@
         return Value();
       g_scheduler->AddGenDependency(
           build_settings->GetFullPath(cur_dir.ResolveRelativeFile(
-              dep.string_value())));
+              dep.string_value(),
+              scope->settings()->build_settings()->root_path_utf8())));
     }
   }
 
diff --git a/tools/gn/function_get_path_info.cc b/tools/gn/function_get_path_info.cc
index 67f9344..a3f2c10 100644
--- a/tools/gn/function_get_path_info.cc
+++ b/tools/gn/function_get_path_info.cc
@@ -26,15 +26,18 @@
 
 // Returns the directory containing the input (resolving it against the
 // |current_dir|), regardless of whether the input is a directory or a file.
-SourceDir DirForInput(const SourceDir& current_dir,
+SourceDir DirForInput(const Settings* settings,
+                      const SourceDir& current_dir,
                       const std::string& input_string) {
   if (!input_string.empty() && input_string[input_string.size() - 1] == '/') {
     // Input is a directory.
-    return current_dir.ResolveRelativeDir(input_string);
+    return current_dir.ResolveRelativeDir(input_string,
+        settings->build_settings()->root_path_utf8());
   }
 
   // Input is a directory.
-  return current_dir.ResolveRelativeFile(input_string).GetDir();
+  return current_dir.ResolveRelativeFile(input_string,
+      settings->build_settings()->root_path_utf8()).GetDir();
 }
 
 std::string GetOnePathInfo(const Settings* settings,
@@ -81,18 +84,24 @@
     case WHAT_GEN_DIR: {
       return DirectoryWithNoLastSlash(
           GetGenDirForSourceDir(settings,
-                                DirForInput(current_dir, input_string)));
+                                DirForInput(settings, current_dir,
+                                            input_string)));
     }
     case WHAT_OUT_DIR: {
       return DirectoryWithNoLastSlash(
           GetOutputDirForSourceDir(settings,
-                                   DirForInput(current_dir, input_string)));
+                                   DirForInput(settings, current_dir,
+                                               input_string)));
     }
     case WHAT_ABSPATH: {
-      if (!input_string.empty() && input_string[input_string.size() - 1] == '/')
-        return current_dir.ResolveRelativeDir(input_string).value();
-      else
-        return current_dir.ResolveRelativeFile(input_string).value();
+      if (!input_string.empty() &&
+          input_string[input_string.size() - 1] == '/') {
+        return current_dir.ResolveRelativeDir(input_string,
+            settings->build_settings()->root_path_utf8()).value();
+      } else {
+        return current_dir.ResolveRelativeFile(input_string,
+            settings->build_settings()->root_path_utf8()).value();
+      }
     }
     default:
       NOTREACHED();
diff --git a/tools/gn/function_read_file.cc b/tools/gn/function_read_file.cc
index f35e5a6..5a7662a 100644
--- a/tools/gn/function_read_file.cc
+++ b/tools/gn/function_read_file.cc
@@ -53,7 +53,8 @@
 
   // Compute the file name.
   const SourceDir& cur_dir = scope->GetSourceDir();
-  SourceFile source_file = cur_dir.ResolveRelativeFile(args[0].string_value());
+  SourceFile source_file = cur_dir.ResolveRelativeFile(args[0].string_value(),
+      scope->settings()->build_settings()->root_path_utf8());
   base::FilePath file_path =
       scope->settings()->build_settings()->GetFullPath(source_file);
 
diff --git a/tools/gn/function_rebase_path.cc b/tools/gn/function_rebase_path.cc
index 078f007..0dde36af 100644
--- a/tools/gn/function_rebase_path.cc
+++ b/tools/gn/function_rebase_path.cc
@@ -71,10 +71,12 @@
     base::FilePath system_path;
     if (looks_like_dir) {
       system_path = scope->settings()->build_settings()->GetFullPath(
-          from_dir.ResolveRelativeDir(string_value));
+          from_dir.ResolveRelativeDir(string_value,
+              scope->settings()->build_settings()->root_path_utf8()));
     } else {
       system_path = scope->settings()->build_settings()->GetFullPath(
-          from_dir.ResolveRelativeFile(string_value));
+          from_dir.ResolveRelativeFile(string_value,
+              scope->settings()->build_settings()->root_path_utf8()));
     }
     result = Value(function, FilePathToUTF8(system_path));
     if (looks_like_dir)
@@ -82,23 +84,20 @@
     return result;
   }
 
-  if (from_dir.is_system_absolute() || to_dir.is_system_absolute()) {
-    *err = Err(function, "System-absolute directories are not supported for "
-        "the source or dest dir for rebase_path. It would be nice to add this "
-        "if you're so inclined!");
-    return result;
-  }
-
   result = Value(function, Value::STRING);
   if (looks_like_dir) {
-    result.string_value() = RebaseSourceAbsolutePath(
-        from_dir.ResolveRelativeDir(string_value).value(),
-        to_dir);
+    result.string_value() = RebasePath(
+        from_dir.ResolveRelativeDir(string_value,
+            scope->settings()->build_settings()->root_path_utf8()).value(),
+        to_dir,
+        scope->settings()->build_settings()->root_path_utf8());
     MakeSlashEndingMatchInput(string_value, &result.string_value());
   } else {
-    result.string_value() = RebaseSourceAbsolutePath(
-        from_dir.ResolveRelativeFile(string_value).value(),
-        to_dir);
+    result.string_value() = RebasePath(
+        from_dir.ResolveRelativeFile(string_value,
+            scope->settings()->build_settings()->root_path_utf8()).value(),
+        to_dir,
+        scope->settings()->build_settings()->root_path_utf8());
   }
 
   return result;
@@ -233,8 +232,9 @@
     if (!args[kArgIndexDest].VerifyTypeIs(Value::STRING, err))
       return result;
     if (!args[kArgIndexDest].string_value().empty()) {
-      to_dir =
-          current_dir.ResolveRelativeDir(args[kArgIndexDest].string_value());
+      to_dir = current_dir.ResolveRelativeDir(
+          args[kArgIndexDest].string_value(),
+          scope->settings()->build_settings()->root_path_utf8());
       convert_to_system_absolute = false;
     }
   }
@@ -244,8 +244,9 @@
   if (args.size() > kArgIndexFrom) {
     if (!args[kArgIndexFrom].VerifyTypeIs(Value::STRING, err))
       return result;
-    from_dir =
-        current_dir.ResolveRelativeDir(args[kArgIndexFrom].string_value());
+    from_dir = current_dir.ResolveRelativeDir(
+        args[kArgIndexFrom].string_value(),
+        scope->settings()->build_settings()->root_path_utf8());
   } else {
     // Default to current directory if unspecified.
     from_dir = current_dir;
diff --git a/tools/gn/function_rebase_path_unittest.cc b/tools/gn/function_rebase_path_unittest.cc
index 0537d10..20b492e 100644
--- a/tools/gn/function_rebase_path_unittest.cc
+++ b/tools/gn/function_rebase_path_unittest.cc
@@ -78,6 +78,59 @@
 #endif
 }
 
+TEST(RebasePath, StringsSystemPaths) {
+  TestWithScope setup;
+  Scope* scope = setup.scope();
+
+#if defined(OS_WIN)
+  setup.build_settings()->SetBuildDir(SourceDir("C:/ssd/out/Debug"));
+  setup.build_settings()->SetRootPath(base::FilePath(L"C:/hdd/src"));
+
+  // Test system absolute to-dir.
+  EXPECT_EQ("../../ssd/out/Debug",
+            RebaseOne(scope, ".", "//", "C:/ssd/out/Debug"));
+  EXPECT_EQ("../../ssd/out/Debug/",
+            RebaseOne(scope, "./", "//", "C:/ssd/out/Debug"));
+  EXPECT_EQ("../../ssd/out/Debug/foo",
+            RebaseOne(scope, "foo", "//", "C:/ssd/out/Debug"));
+  EXPECT_EQ("../../ssd/out/Debug/foo/",
+            RebaseOne(scope, "foo/", "//", "C:/ssd/out/Debug"));
+
+  // Test system absolute from-dir.
+  EXPECT_EQ("../../../hdd/src",
+            RebaseOne(scope, ".", "C:/ssd/out/Debug", "//"));
+  EXPECT_EQ("../../../hdd/src/",
+            RebaseOne(scope, "./", "C:/ssd/out/Debug", "//"));
+  EXPECT_EQ("../../../hdd/src/foo",
+            RebaseOne(scope, "foo", "C:/ssd/out/Debug", "//"));
+  EXPECT_EQ("../../../hdd/src/foo/",
+            RebaseOne(scope, "foo/", "C:/ssd/out/Debug", "//"));
+#else
+  setup.build_settings()->SetBuildDir(SourceDir("/ssd/out/Debug"));
+  setup.build_settings()->SetRootPath(base::FilePath("/hdd/src"));
+
+  // Test system absolute to-dir.
+  EXPECT_EQ("../../ssd/out/Debug",
+            RebaseOne(scope, ".", "//", "/ssd/out/Debug"));
+  EXPECT_EQ("../../ssd/out/Debug/",
+            RebaseOne(scope, "./", "//", "/ssd/out/Debug"));
+  EXPECT_EQ("../../ssd/out/Debug/foo",
+            RebaseOne(scope, "foo", "//", "/ssd/out/Debug"));
+  EXPECT_EQ("../../ssd/out/Debug/foo/",
+            RebaseOne(scope, "foo/", "//", "/ssd/out/Debug"));
+
+  // Test system absolute from-dir.
+  EXPECT_EQ("../../../hdd/src",
+            RebaseOne(scope, ".", "/ssd/out/Debug", "//"));
+  EXPECT_EQ("../../../hdd/src/",
+            RebaseOne(scope, "./", "/ssd/out/Debug", "//"));
+  EXPECT_EQ("../../../hdd/src/foo",
+            RebaseOne(scope, "foo", "/ssd/out/Debug", "//"));
+  EXPECT_EQ("../../../hdd/src/foo/",
+            RebaseOne(scope, "foo/", "/ssd/out/Debug", "//"));
+#endif
+}
+
 // Test list input.
 TEST(RebasePath, List) {
   TestWithScope setup;
diff --git a/tools/gn/function_write_file.cc b/tools/gn/function_write_file.cc
index 55f4d5d..0d273fc 100644
--- a/tools/gn/function_write_file.cc
+++ b/tools/gn/function_write_file.cc
@@ -57,7 +57,8 @@
   if (!args[0].VerifyTypeIs(Value::STRING, err))
     return Value();
   const SourceDir& cur_dir = scope->GetSourceDir();
-  SourceFile source_file = cur_dir.ResolveRelativeFile(args[0].string_value());
+  SourceFile source_file = cur_dir.ResolveRelativeFile(args[0].string_value(),
+      scope->settings()->build_settings()->root_path_utf8());
   if (!EnsureStringIsInOutputDir(
           scope->settings()->build_settings()->build_dir(),
           source_file.value(), args[0].origin(), err))
diff --git a/tools/gn/functions.cc b/tools/gn/functions.cc
index 0d60d688a..84d5ea43 100644
--- a/tools/gn/functions.cc
+++ b/tools/gn/functions.cc
@@ -468,7 +468,8 @@
 
   const SourceDir& input_dir = scope->GetSourceDir();
   SourceFile import_file =
-      input_dir.ResolveRelativeFile(args[0].string_value());
+      input_dir.ResolveRelativeFile(args[0].string_value(),
+          scope->settings()->build_settings()->root_path_utf8());
   scope->settings()->import_manager().DoImport(import_file, function,
                                                scope, err);
   return Value();
diff --git a/tools/gn/item.cc b/tools/gn/item.cc
index 7dfd4ad..a1118e8 100644
--- a/tools/gn/item.cc
+++ b/tools/gn/item.cc
@@ -5,6 +5,7 @@
 #include "tools/gn/item.h"
 
 #include "base/logging.h"
+#include "tools/gn/settings.h"
 
 Item::Item(const Settings* settings, const Label& label)
     : settings_(settings),
diff --git a/tools/gn/ninja_action_target_writer.cc b/tools/gn/ninja_action_target_writer.cc
index 25c86337..84ec072 100644
--- a/tools/gn/ninja_action_target_writer.cc
+++ b/tools/gn/ninja_action_target_writer.cc
@@ -17,6 +17,7 @@
     : NinjaTargetWriter(target, out),
       path_output_no_escaping_(
           target->settings()->build_settings()->build_dir(),
+          target->settings()->build_settings()->root_path_utf8(),
           ESCAPE_NONE) {
 }
 
diff --git a/tools/gn/ninja_binary_target_writer.cc b/tools/gn/ninja_binary_target_writer.cc
index b341639..16cc320 100644
--- a/tools/gn/ninja_binary_target_writer.cc
+++ b/tools/gn/ninja_binary_target_writer.cc
@@ -102,8 +102,10 @@
   // Include directories.
   if (subst.used[SUBSTITUTION_INCLUDE_DIRS]) {
     out_ << kSubstitutionNinjaNames[SUBSTITUTION_INCLUDE_DIRS] << " =";
-    PathOutput include_path_output(path_output_.current_dir(),
-                                   ESCAPE_NINJA_COMMAND);
+    PathOutput include_path_output(
+        path_output_.current_dir(),
+        settings_->build_settings()->root_path_utf8(),
+        ESCAPE_NINJA_COMMAND);
     RecursiveTargetConfigToStream<SourceDir>(
         target_, &ConfigValues::include_dirs,
         IncludeWriter(include_path_output), out_);
@@ -150,6 +152,7 @@
     if (tool_type != Toolchain::TYPE_NONE) {
       out_ << "build";
       path_output_.WriteFiles(out_, tool_outputs);
+
       out_ << ": " << rule_prefix << Toolchain::ToolTypeToName(tool_type);
       out_ << " ";
       path_output_.WriteFile(out_, source);
@@ -288,6 +291,7 @@
     // Since we're passing these on the command line to the linker and not
     // to Ninja, we need to do shell escaping.
     PathOutput lib_path_output(path_output_.current_dir(),
+                               settings_->build_settings()->root_path_utf8(),
                                ESCAPE_NINJA_COMMAND);
     for (size_t i = 0; i < all_lib_dirs.size(); i++) {
       out_ << " " << tool_->lib_dir_switch();
diff --git a/tools/gn/ninja_build_writer.cc b/tools/gn/ninja_build_writer.cc
index fa83874b..deb0d7f 100644
--- a/tools/gn/ninja_build_writer.cc
+++ b/tools/gn/ninja_build_writer.cc
@@ -86,7 +86,8 @@
       default_toolchain_targets_(default_toolchain_targets),
       out_(out),
       dep_out_(dep_out),
-      path_output_(build_settings->build_dir(), ESCAPE_NINJA) {
+      path_output_(build_settings->build_dir(),
+                   build_settings->root_path_utf8(), ESCAPE_NINJA) {
 }
 
 NinjaBuildWriter::~NinjaBuildWriter() {
diff --git a/tools/gn/ninja_target_writer.cc b/tools/gn/ninja_target_writer.cc
index 870cbb6..8a5d0ee 100644
--- a/tools/gn/ninja_target_writer.cc
+++ b/tools/gn/ninja_target_writer.cc
@@ -28,7 +28,9 @@
     : settings_(target->settings()),
       target_(target),
       out_(out),
-      path_output_(settings_->build_settings()->build_dir(), ESCAPE_NINJA) {
+      path_output_(settings_->build_settings()->build_dir(),
+                   settings_->build_settings()->root_path_utf8(),
+                   ESCAPE_NINJA) {
 }
 
 NinjaTargetWriter::~NinjaTargetWriter() {
@@ -174,8 +176,9 @@
   // optimal thing in all cases.
 
   OutputFile input_stamp_file(
-      RebaseSourceAbsolutePath(GetTargetOutputDir(target_).value(),
-                               settings_->build_settings()->build_dir()));
+      RebasePath(GetTargetOutputDir(target_).value(),
+                 settings_->build_settings()->build_dir(),
+                 settings_->build_settings()->root_path_utf8()));
   input_stamp_file.value().append(target_->label().name());
   input_stamp_file.value().append(".inputdeps.stamp");
 
diff --git a/tools/gn/ninja_toolchain_writer.cc b/tools/gn/ninja_toolchain_writer.cc
index 5deead5..0122471 100644
--- a/tools/gn/ninja_toolchain_writer.cc
+++ b/tools/gn/ninja_toolchain_writer.cc
@@ -32,7 +32,9 @@
       toolchain_(toolchain),
       targets_(targets),
       out_(out),
-      path_output_(settings_->build_settings()->build_dir(), ESCAPE_NINJA) {
+      path_output_(settings_->build_settings()->build_dir(),
+                   settings_->build_settings()->root_path_utf8(),
+                   ESCAPE_NINJA) {
 }
 
 NinjaToolchainWriter::~NinjaToolchainWriter() {
diff --git a/tools/gn/output_file.cc b/tools/gn/output_file.cc
index d1fb88e8..cc6dc98 100644
--- a/tools/gn/output_file.cc
+++ b/tools/gn/output_file.cc
@@ -16,8 +16,9 @@
 
 OutputFile::OutputFile(const BuildSettings* build_settings,
                        const SourceFile& source_file)
-    : value_(RebaseSourceAbsolutePath(source_file.value(),
-                                      build_settings->build_dir())) {
+    : value_(RebasePath(source_file.value(),
+                        build_settings->build_dir(),
+                        build_settings->root_path_utf8())) {
 }
 
 OutputFile::~OutputFile() {
diff --git a/tools/gn/path_output.cc b/tools/gn/path_output.cc
index de398ec..5b333d8 100644
--- a/tools/gn/path_output.cc
+++ b/tools/gn/path_output.cc
@@ -4,19 +4,19 @@
 
 #include "tools/gn/path_output.h"
 
+#include "base/strings/string_util.h"
 #include "build/build_config.h"
 #include "tools/gn/filesystem_utils.h"
 #include "tools/gn/output_file.h"
 #include "tools/gn/string_utils.h"
 
-PathOutput::PathOutput(const SourceDir& current_dir, EscapingMode escaping)
+PathOutput::PathOutput(const SourceDir& current_dir,
+                       const base::StringPiece& source_root,
+                       EscapingMode escaping)
     : current_dir_(current_dir) {
-  CHECK(current_dir.is_source_absolute())
-      << "Currently this only supports writing to output directories inside "
-         "the source root. There needs to be some tweaks to PathOutput to make "
-         "doing this work correctly.";
-  inverse_current_dir_ = InvertDir(current_dir_);
-
+  inverse_current_dir_ = RebasePath("//", current_dir, source_root);
+  if (!EndsWithSlash(inverse_current_dir_))
+    inverse_current_dir_.push_back('/');
   options_.mode = escaping;
 }
 
diff --git a/tools/gn/path_output.h b/tools/gn/path_output.h
index 12fc5ac5..f7beb42 100644
--- a/tools/gn/path_output.h
+++ b/tools/gn/path_output.h
@@ -33,7 +33,8 @@
     DIR_NO_LAST_SLASH,
   };
 
-  PathOutput(const SourceDir& current_dir, EscapingMode escaping);
+  PathOutput(const SourceDir& current_dir, const base::StringPiece& source_root,
+             EscapingMode escaping);
   ~PathOutput();
 
   // Read-only since inverse_current_dir_ is computed depending on this.
diff --git a/tools/gn/path_output_unittest.cc b/tools/gn/path_output_unittest.cc
index 217f6dcf..131f55ec5 100644
--- a/tools/gn/path_output_unittest.cc
+++ b/tools/gn/path_output_unittest.cc
@@ -4,6 +4,7 @@
 
 #include <sstream>
 
+#include "base/files/file_path.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "tools/gn/output_file.h"
 #include "tools/gn/path_output.h"
@@ -12,7 +13,8 @@
 
 TEST(PathOutput, Basic) {
   SourceDir build_dir("//out/Debug/");
-  PathOutput writer(build_dir, ESCAPE_NONE);
+  base::StringPiece source_root("/source/root");
+  PathOutput writer(build_dir, source_root, ESCAPE_NONE);
   {
     // Normal source-root path.
     std::ostringstream out;
@@ -53,7 +55,8 @@
 // Same as basic but the output dir is the root.
 TEST(PathOutput, BasicInRoot) {
   SourceDir build_dir("//");
-  PathOutput writer(build_dir, ESCAPE_NONE);
+  base::StringPiece source_root("/source/root");
+  PathOutput writer(build_dir, source_root, ESCAPE_NONE);
   {
     // Normal source-root path.
     std::ostringstream out;
@@ -70,7 +73,8 @@
 
 TEST(PathOutput, NinjaEscaping) {
   SourceDir build_dir("//out/Debug/");
-  PathOutput writer(build_dir, ESCAPE_NINJA);
+  base::StringPiece source_root("/source/root");
+  PathOutput writer(build_dir, source_root, ESCAPE_NINJA);
   {
     // Spaces and $ in filenames.
     std::ostringstream out;
@@ -87,7 +91,8 @@
 
 TEST(PathOutput, NinjaForkEscaping) {
   SourceDir build_dir("//out/Debug/");
-  PathOutput writer(build_dir, ESCAPE_NINJA_COMMAND);
+  base::StringPiece source_root("/source/root");
+  PathOutput writer(build_dir, source_root, ESCAPE_NINJA_COMMAND);
 
   // Spaces in filenames should get quoted on Windows.
   writer.set_escape_platform(ESCAPE_PLATFORM_WIN);
@@ -140,7 +145,8 @@
 
 TEST(PathOutput, InhibitQuoting) {
   SourceDir build_dir("//out/Debug/");
-  PathOutput writer(build_dir, ESCAPE_NINJA_COMMAND);
+  base::StringPiece source_root("/source/root");
+  PathOutput writer(build_dir, source_root, ESCAPE_NINJA_COMMAND);
   writer.set_inhibit_quoting(true);
 
   writer.set_escape_platform(ESCAPE_PLATFORM_WIN);
@@ -163,7 +169,8 @@
 TEST(PathOutput, WriteDir) {
   {
     SourceDir build_dir("//out/Debug/");
-    PathOutput writer(build_dir, ESCAPE_NINJA);
+    base::StringPiece source_root("/source/root");
+    PathOutput writer(build_dir, source_root, ESCAPE_NINJA);
     {
       std::ostringstream out;
       writer.WriteDir(out, SourceDir("//foo/bar/"),
@@ -259,7 +266,8 @@
   }
   {
     // Empty build dir writer.
-    PathOutput root_writer(SourceDir("//"), ESCAPE_NINJA);
+    base::StringPiece source_root("/source/root");
+    PathOutput root_writer(SourceDir("//"), source_root, ESCAPE_NINJA);
     {
       std::ostringstream out;
       root_writer.WriteDir(out, SourceDir("//"),
diff --git a/tools/gn/setup.cc b/tools/gn/setup.cc
index fdbf0f247..5ba87b9 100644
--- a/tools/gn/setup.cc
+++ b/tools/gn/setup.cc
@@ -441,7 +441,7 @@
 bool Setup::FillBuildDir(const std::string& build_dir, bool require_exists) {
   SourceDir resolved =
       SourceDirForCurrentDirectory(build_settings_.root_path()).
-          ResolveRelativeDir(build_dir);
+    ResolveRelativeDir(build_dir, build_settings_.root_path_utf8());
   if (resolved.is_null()) {
     Err(Location(), "Couldn't resolve build directory.",
         "The build directory supplied (\"" + build_dir + "\") was not valid.").
diff --git a/tools/gn/source_dir.cc b/tools/gn/source_dir.cc
index c59cf88..9f6c745 100644
--- a/tools/gn/source_dir.cc
+++ b/tools/gn/source_dir.cc
@@ -12,7 +12,12 @@
 
 void AssertValueSourceDirString(const std::string& s) {
   if (!s.empty()) {
+#if defined(OS_WIN)
+    DCHECK(s[0] == '/' ||
+           (s.size() > 2 && s[0] != '/' && s[1] == ':' && IsSlash(s[2])));
+#else
     DCHECK(s[0] == '/');
+#endif
     DCHECK(EndsWithSlash(s));
   }
 }
@@ -44,6 +49,8 @@
     const base::StringPiece& source_root) const {
   SourceFile ret;
 
+  DCHECK(source_root.empty() || !source_root.ends_with("/"));
+
   // It's an error to resolve an empty string or one that is a directory
   // (indicated by a trailing slash) because this is the function that expects
   // to return a file.
@@ -69,6 +76,20 @@
     return ret;
   }
 
+  if (!source_root.empty()) {
+    std::string absolute =
+        FilePathToUTF8(Resolve(UTF8ToFilePath(source_root)).AppendASCII(
+            p.as_string()).value());
+    NormalizePath(&absolute);
+    if (!MakeAbsolutePathRelativeIfPossible(source_root, absolute,
+                                            &ret.value_))
+      ret.value_ = absolute;
+    return ret;
+  }
+
+  // With no source_root_, there's nothing we can do about
+  // e.g. p=../../../path/to/file and value_=//source and we'll
+  // errornously return //file.
   ret.value_.reserve(value_.size() + p.size());
   ret.value_.assign(value_);
   ret.value_.append(p.data(), p.size());
@@ -82,6 +103,8 @@
     const base::StringPiece& source_root) const {
   SourceDir ret;
 
+  DCHECK(source_root.empty() || !source_root.ends_with("/"));
+
   if (p.empty())
     return ret;
   if (p.size() >= 2 && p[0] == '/' && p[1] == '/') {
@@ -106,6 +129,18 @@
     return ret;
   }
 
+  if (!source_root.empty()) {
+    std::string absolute =
+        FilePathToUTF8(Resolve(UTF8ToFilePath(source_root)).AppendASCII(
+            p.as_string()).value());
+    NormalizePath(&absolute);
+    if (!MakeAbsolutePathRelativeIfPossible(source_root, absolute, &ret.value_))
+      ret.value_ = absolute;
+    if (!EndsWithSlash(ret.value_))
+      ret.value_.push_back('/');
+    return ret;
+  }
+
   ret.value_.reserve(value_.size() + p.size());
   ret.value_.assign(value_);
   ret.value_.append(p.data(), p.size());
diff --git a/tools/gn/source_dir_unittest.cc b/tools/gn/source_dir_unittest.cc
index d2ea3aa..5825b97 100644
--- a/tools/gn/source_dir_unittest.cc
+++ b/tools/gn/source_dir_unittest.cc
@@ -8,48 +8,88 @@
 
 TEST(SourceDir, ResolveRelativeFile) {
   SourceDir base("//base/");
+#if defined(OS_WIN)
+  base::StringPiece source_root("C:/source/root");
+#else
+  base::StringPiece source_root("/source/root");
+#endif
 
   // Empty input is an error.
-  EXPECT_TRUE(base.ResolveRelativeFile("") == SourceFile());
+  EXPECT_TRUE(base.ResolveRelativeFile("", source_root) == SourceFile());
 
   // These things are directories, so should be an error.
-  EXPECT_TRUE(base.ResolveRelativeFile("//foo/bar/") == SourceFile());
-  EXPECT_TRUE(base.ResolveRelativeFile("bar/") == SourceFile());
+  EXPECT_TRUE(base.ResolveRelativeFile("//foo/bar/", source_root) ==
+              SourceFile());
+  EXPECT_TRUE(base.ResolveRelativeFile("bar/", source_root) ==
+              SourceFile());
 
   // Absolute paths should be passed unchanged.
-  EXPECT_TRUE(base.ResolveRelativeFile("//foo") == SourceFile("//foo"));
-  EXPECT_TRUE(base.ResolveRelativeFile("/foo") == SourceFile("/foo"));
+  EXPECT_TRUE(base.ResolveRelativeFile("//foo",source_root) ==
+              SourceFile("//foo"));
+  EXPECT_TRUE(base.ResolveRelativeFile("/foo", source_root) ==
+              SourceFile("/foo"));
 
   // Basic relative stuff.
-  EXPECT_TRUE(base.ResolveRelativeFile("foo") == SourceFile("//base/foo"));
-  EXPECT_TRUE(base.ResolveRelativeFile("./foo") == SourceFile("//base/foo"));
-  EXPECT_TRUE(base.ResolveRelativeFile("../foo") == SourceFile("//foo"));
-  EXPECT_TRUE(base.ResolveRelativeFile("../../foo") == SourceFile("//foo"));
+  EXPECT_TRUE(base.ResolveRelativeFile("foo", source_root) ==
+              SourceFile("//base/foo"));
+  EXPECT_TRUE(base.ResolveRelativeFile("./foo", source_root) ==
+              SourceFile("//base/foo"));
+  EXPECT_TRUE(base.ResolveRelativeFile("../foo", source_root) ==
+              SourceFile("//foo"));
+
+  // If the given relative path points outside the source root, we
+  // expect an absolute path.
+#if defined(OS_WIN)
+  EXPECT_TRUE(base.ResolveRelativeFile("../../foo", source_root) ==
+              SourceFile("C:/source/foo"));
+#else
+  EXPECT_TRUE(base.ResolveRelativeFile("../../foo", source_root) ==
+              SourceFile("/source/foo"));
+#endif
 
 #if defined(OS_WIN)
   // Note that we don't canonicalize the backslashes to forward slashes.
   // This could potentially be changed in the future which would mean we should
   // just change the expected result.
-  EXPECT_TRUE(base.ResolveRelativeFile("C:\\foo\\bar.txt") ==
+  EXPECT_TRUE(base.ResolveRelativeFile("C:\\foo\\bar.txt", source_root) ==
               SourceFile("/C:/foo/bar.txt"));
 #endif
 }
 
 TEST(SourceDir, ResolveRelativeDir) {
   SourceDir base("//base/");
+#if defined(OS_WIN)
+  base::StringPiece source_root("C:/source/root");
+#else
+  base::StringPiece source_root("/source/root");
+#endif
 
   // Empty input is an error.
-  EXPECT_TRUE(base.ResolveRelativeDir("") == SourceDir());
+  EXPECT_TRUE(base.ResolveRelativeDir("", source_root) == SourceDir());
 
   // Absolute paths should be passed unchanged.
-  EXPECT_TRUE(base.ResolveRelativeDir("//foo") == SourceDir("//foo/"));
-  EXPECT_TRUE(base.ResolveRelativeDir("/foo") == SourceDir("/foo/"));
+  EXPECT_TRUE(base.ResolveRelativeDir("//foo", source_root) ==
+              SourceDir("//foo/"));
+  EXPECT_TRUE(base.ResolveRelativeDir("/foo", source_root) ==
+              SourceDir("/foo/"));
 
   // Basic relative stuff.
-  EXPECT_TRUE(base.ResolveRelativeDir("foo") == SourceDir("//base/foo/"));
-  EXPECT_TRUE(base.ResolveRelativeDir("./foo") == SourceDir("//base/foo/"));
-  EXPECT_TRUE(base.ResolveRelativeDir("../foo") == SourceDir("//foo/"));
-  EXPECT_TRUE(base.ResolveRelativeDir("../../foo/") == SourceDir("//foo/"));
+  EXPECT_TRUE(base.ResolveRelativeDir("foo", source_root) ==
+              SourceDir("//base/foo/"));
+  EXPECT_TRUE(base.ResolveRelativeDir("./foo", source_root) ==
+              SourceDir("//base/foo/"));
+  EXPECT_TRUE(base.ResolveRelativeDir("../foo", source_root) ==
+              SourceDir("//foo/"));
+
+  // If the given relative path points outside the source root, we
+  // expect an absolute path.
+#if defined(OS_WIN)
+  EXPECT_TRUE(base.ResolveRelativeDir("../../foo", source_root) ==
+              SourceDir("C:/source/foo/"));
+#else
+  EXPECT_TRUE(base.ResolveRelativeDir("../../foo", source_root) ==
+              SourceDir("/source/foo/"));
+#endif
 
 #if defined(OS_WIN)
   // Note that we don't canonicalize the existing backslashes to forward
diff --git a/tools/gn/source_file.cc b/tools/gn/source_file.cc
index b3eb560..71d8592c 100644
--- a/tools/gn/source_file.cc
+++ b/tools/gn/source_file.cc
@@ -9,21 +9,33 @@
 #include "tools/gn/filesystem_utils.h"
 #include "tools/gn/source_dir.h"
 
+namespace {
+
+void AssertValueSourceFileString(const std::string& s) {
+#if defined(OS_WIN)
+  DCHECK(s[0] == '/' ||
+         (s.size() > 2 && s[0] != '/' && s[1] == ':' && IsSlash(s[2])));
+#else
+  DCHECK(s[0] == '/');
+#endif
+  DCHECK(!EndsWithSlash(s));
+}
+
+}  // namespace
+
 SourceFile::SourceFile() {
 }
 
 SourceFile::SourceFile(const base::StringPiece& p)
     : value_(p.data(), p.size()) {
   DCHECK(!value_.empty());
-  DCHECK(value_[0] == '/');
-  DCHECK(!EndsWithSlash(value_));
+  AssertValueSourceFileString(value_);
 }
 
 SourceFile::SourceFile(SwapIn, std::string* value) {
   value_.swap(*value);
   DCHECK(!value_.empty());
-  DCHECK(value_[0] == '/');
-  DCHECK(!EndsWithSlash(value_));
+  AssertValueSourceFileString(value_);
 }
 
 SourceFile::~SourceFile() {
diff --git a/tools/gn/substitution_writer.cc b/tools/gn/substitution_writer.cc
index 65a1d1fe..a642e47 100644
--- a/tools/gn/substitution_writer.cc
+++ b/tools/gn/substitution_writer.cc
@@ -235,10 +235,6 @@
     const SubstitutionPattern& pattern,
     const SourceFile& source) {
   SourceFile result_as_source = ApplyPatternToSource(settings, pattern, source);
-  CHECK(result_as_source.is_source_absolute())
-      << "The result of the pattern \""
-      << pattern.AsString()
-      << "\" was not an absolute path beginning in \"//\".";
   return OutputFile(settings->build_settings(), result_as_source);
 }
 
@@ -357,8 +353,9 @@
     case SUBSTITUTION_SOURCE_ROOT_RELATIVE_DIR:
       if (source.is_system_absolute())
         return DirectoryWithNoLastSlash(source.GetDir());
-      return RebaseSourceAbsolutePath(
-          DirectoryWithNoLastSlash(source.GetDir()), SourceDir("//"));
+      return RebasePath(
+          DirectoryWithNoLastSlash(source.GetDir()), SourceDir("//"),
+          settings->build_settings()->root_path_utf8());
 
     case SUBSTITUTION_SOURCE_GEN_DIR:
       to_rebase = DirectoryWithNoLastSlash(
@@ -382,7 +379,8 @@
   // extension extraction) will have been handled via early return above.
   if (output_style == OUTPUT_ABSOLUTE)
     return to_rebase;
-  return RebaseSourceAbsolutePath(to_rebase, relative_to);
+  return RebasePath(to_rebase, relative_to,
+                    settings->build_settings()->root_path_utf8());
 }
 
 // static
diff --git a/tools/gn/value_extractors.cc b/tools/gn/value_extractors.cc
index 9e6a37e..6dfaa2f 100644
--- a/tools/gn/value_extractors.cc
+++ b/tools/gn/value_extractors.cc
@@ -58,8 +58,6 @@
   return true;
 }
 
-// This extractor rejects files with system-absolute file paths. If we need
-// that in the future, we'll have to add some flag to control this.
 struct RelativeFileConverter {
   RelativeFileConverter(const BuildSettings* build_settings_in,
                         const SourceDir& current_dir_in)
@@ -71,13 +69,6 @@
       return false;
     *out = current_dir.ResolveRelativeFile(v.string_value(),
                                            build_settings->root_path_utf8());
-    if (out->is_system_absolute()) {
-      *err = Err(v, "System-absolute file path.",
-          "You can't list a system-absolute file path here. Please include "
-          "only files in\nthe source tree. Maybe you meant to begin with two "
-          "slashes to indicate an\nabsolute path in the source tree?");
-      return false;
-    }
     return true;
   }
   const BuildSettings* build_settings;