Make get_label_info take into account the toolchain for target_gen_dir

Previously get_label_info(<label>, "target_gen_dir") discarded toolchain information for the <label> and used the current toolchain. This makes this query useless for anything but the current toolchain. The other label info categories included this properly.

The actual bugfix is passing label.GetToolchainLabel() for the "target_gen_dir" case in function_get_label_info. This is tested by the new test in function-get_label_info_unittest.cc which was missing a test for this case.

Adding the toolchain label required adding more variants of the directory getters in filesystem_utils which were already passed the point of reason in terms of number of variants taking and returning slightly different but related things.

So this does a refactoring of the directory getter functions there to rationalize them among 4 classes of functions which can return one of two things, and a helper class that generalizes the input with whatever information the caller has. The rest of the changes are updates to the callers for this new system. There are some additional cases added to filesystem_utils_unittest.cc to test more combinations.

BUG=632412

Review-Url: https://codereview.chromium.org/2198433004
Cr-Original-Commit-Position: refs/heads/master@{#409334}
Cr-Mirrored-From: https://chromium.googlesource.com/chromium/src
Cr-Mirrored-Commit: 8d6e145e3c939895491b83219583dfef48a94fac
diff --git a/tools/gn/filesystem_utils.cc b/tools/gn/filesystem_utils.cc
index facf442..6152ae0 100644
--- a/tools/gn/filesystem_utils.cc
+++ b/tools/gn/filesystem_utils.cc
@@ -163,6 +163,37 @@
 #endif
 }
 
+// Helper function for computing subdirectories in the build directory
+// corresponding to absolute paths. This will try to resolve the absolute
+// path as a source-relative path first, and otherwise it creates a
+// special subdirectory for absolute paths to keep them from colliding with
+// other generated sources and outputs.
+void AppendFixedAbsolutePathSuffix(const BuildSettings* build_settings,
+                                   const SourceDir& source_dir,
+                                   OutputFile* result) {
+  const std::string& build_dir = build_settings->build_dir().value();
+
+  if (base::StartsWith(source_dir.value(), build_dir,
+                       base::CompareCase::SENSITIVE)) {
+    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);
+  }
+}
+
 }  // namespace
 
 std::string FilePathToUTF8(const base::FilePath::StringType& str) {
@@ -780,87 +811,67 @@
   return write_success;
 }
 
-SourceDir GetToolchainOutputDir(const Settings* settings) {
-  return settings->toolchain_output_subdir().AsSourceDir(
-      settings->build_settings());
+BuildDirContext::BuildDirContext(const Target* target)
+    : BuildDirContext(target->settings()) {
 }
 
-SourceDir GetToolchainOutputDir(const BuildSettings* build_settings,
-                                const Label& toolchain_label, bool is_default) {
-  std::string result = build_settings->build_dir().value();
-  result.append(GetOutputSubdirName(toolchain_label, is_default));
-  return SourceDir(SourceDir::SWAP_IN, &result);
+BuildDirContext::BuildDirContext(const Settings* settings)
+    : BuildDirContext(settings->build_settings(),
+                      settings->toolchain_label(),
+                      settings->is_default()) {
 }
 
-SourceDir GetToolchainGenDir(const Settings* settings) {
-  return GetToolchainGenDirAsOutputFile(settings).AsSourceDir(
-      settings->build_settings());
+BuildDirContext::BuildDirContext(const Scope* execution_scope)
+    : BuildDirContext(execution_scope->settings()) {
 }
 
-OutputFile GetToolchainGenDirAsOutputFile(const Settings* settings) {
-  OutputFile result(settings->toolchain_output_subdir());
-  result.value().append("gen/");
+BuildDirContext::BuildDirContext(const Scope* execution_scope,
+                                 const Label& toolchain_label)
+    : BuildDirContext(execution_scope->settings()->build_settings(),
+                      toolchain_label,
+                      execution_scope->settings()->default_toolchain_label() ==
+                          toolchain_label) {
+}
+
+BuildDirContext::BuildDirContext(const BuildSettings* in_build_settings,
+                                 const Label& in_toolchain_label,
+                                 bool in_is_default_toolchain)
+    : build_settings(in_build_settings),
+      toolchain_label(in_toolchain_label),
+      is_default_toolchain(in_is_default_toolchain) {
+}
+
+SourceDir GetBuildDirAsSourceDir(const BuildDirContext& context,
+                                 BuildDirType type) {
+  return GetBuildDirAsOutputFile(context, type).AsSourceDir(
+      context.build_settings);
+}
+
+OutputFile GetBuildDirAsOutputFile(const BuildDirContext& context,
+                                   BuildDirType type) {
+  OutputFile result(GetOutputSubdirName(context.toolchain_label,
+                                        context.is_default_toolchain));
+  DCHECK(result.value().empty() || result.value().back() == '/');
+
+  if (type == BuildDirType::GEN)
+    result.value().append("gen/");
+  else if (type == BuildDirType::OBJ)
+    result.value().append("obj/");
   return result;
 }
 
-SourceDir GetToolchainGenDir(const BuildSettings* build_settings,
-                             const Label& toolchain_label, bool is_default) {
-  std::string result = GetToolchainOutputDir(
-      build_settings, toolchain_label, is_default).value();
-  result.append("gen/");
-  return SourceDir(SourceDir::SWAP_IN, &result);
+SourceDir GetSubBuildDirAsSourceDir(const BuildDirContext& context,
+                                    const SourceDir& source_dir,
+                                    BuildDirType type) {
+  return GetSubBuildDirAsOutputFile(context, source_dir, type)
+      .AsSourceDir(context.build_settings);
 }
 
-SourceDir GetOutputDirForSourceDir(const Settings* settings,
-                                   const SourceDir& source_dir) {
-  return GetOutputDirForSourceDir(
-      settings->build_settings(), source_dir,
-      settings->toolchain_label(), settings->is_default());
-}
-
-void AppendFixedAbsolutePathSuffix(const BuildSettings* build_settings,
-                                   const SourceDir& source_dir,
-                                   OutputFile* result) {
-  const std::string& build_dir = build_settings->build_dir().value();
-
-  if (base::StartsWith(source_dir.value(), build_dir,
-                       base::CompareCase::SENSITIVE)) {
-    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);
-  }
-}
-
-SourceDir GetOutputDirForSourceDir(
-    const BuildSettings* build_settings,
-    const SourceDir& source_dir,
-    const Label& toolchain_label,
-    bool is_default_toolchain) {
-  return GetOutputDirForSourceDirAsOutputFile(
-          build_settings, source_dir, toolchain_label, is_default_toolchain)
-      .AsSourceDir(build_settings);
-}
-
-OutputFile GetOutputDirForSourceDirAsOutputFile(
-    const BuildSettings* build_settings,
-    const SourceDir& source_dir,
-    const Label& toolchain_label,
-    bool is_default_toolchain) {
-  OutputFile result(GetOutputSubdirName(toolchain_label, is_default_toolchain));
-  result.value().append("obj/");
+OutputFile GetSubBuildDirAsOutputFile(const BuildDirContext& context,
+                                      const SourceDir& source_dir,
+                                      BuildDirType type) {
+  DCHECK(type != BuildDirType::TOOLCHAIN_ROOT);
+  OutputFile result = GetBuildDirAsOutputFile(context, type);
 
   if (source_dir.is_source_absolute()) {
     // The source dir is source-absolute, so we trim off the two leading
@@ -869,69 +880,27 @@
                           source_dir.value().size() - 2);
   } else {
     // System-absolute.
-    AppendFixedAbsolutePathSuffix(build_settings, source_dir, &result);
+    AppendFixedAbsolutePathSuffix(context.build_settings, source_dir, &result);
   }
   return result;
 }
 
-OutputFile GetOutputDirForSourceDirAsOutputFile(const Settings* settings,
-                                                const SourceDir& source_dir) {
-  return GetOutputDirForSourceDirAsOutputFile(
-      settings->build_settings(), source_dir,
-      settings->toolchain_label(), settings->is_default());
+SourceDir GetBuildDirForTargetAsSourceDir(const Target* target,
+                                          BuildDirType type) {
+  return GetSubBuildDirAsSourceDir(
+      BuildDirContext(target), target->label().dir(), type);
 }
 
-SourceDir GetGenDirForSourceDir(const Settings* settings,
-                                const SourceDir& source_dir) {
-  return GetGenDirForSourceDirAsOutputFile(settings, source_dir).AsSourceDir(
-      settings->build_settings());
+OutputFile GetBuildDirForTargetAsOutputFile(const Target* target,
+                                            BuildDirType type) {
+  return GetSubBuildDirAsOutputFile(
+      BuildDirContext(target), target->label().dir(), type);
 }
 
-OutputFile GetGenDirForSourceDirAsOutputFile(const Settings* settings,
-                                             const SourceDir& source_dir) {
-  OutputFile result = GetToolchainGenDirAsOutputFile(settings);
-
-  if (source_dir.is_source_absolute()) {
-    // The source dir should be source-absolute, so we trim off the two leading
-    // slashes to append to the toolchain object directory.
-    DCHECK(source_dir.is_source_absolute());
-    result.value().append(&source_dir.value()[2],
-                          source_dir.value().size() - 2);
-  } else {
-    // System-absolute.
-    AppendFixedAbsolutePathSuffix(settings->build_settings(), source_dir,
-                                  &result);
-  }
-  return result;
-}
-
-SourceDir GetTargetOutputDir(const Target* target) {
-  return GetOutputDirForSourceDirAsOutputFile(
-      target->settings(), target->label().dir()).AsSourceDir(
-          target->settings()->build_settings());
-}
-
-OutputFile GetTargetOutputDirAsOutputFile(const Target* target) {
-  return GetOutputDirForSourceDirAsOutputFile(
-      target->settings(), target->label().dir());
-}
-
-SourceDir GetTargetGenDir(const Target* target) {
-  return GetTargetGenDirAsOutputFile(target).AsSourceDir(
-      target->settings()->build_settings());
-}
-
-OutputFile GetTargetGenDirAsOutputFile(const Target* target) {
-  return GetGenDirForSourceDirAsOutputFile(
-      target->settings(), target->label().dir());
-}
-
-SourceDir GetCurrentOutputDir(const Scope* scope) {
-  return GetOutputDirForSourceDirAsOutputFile(
-      scope->settings(), scope->GetSourceDir()).AsSourceDir(
-          scope->settings()->build_settings());
-}
-
-SourceDir GetCurrentGenDir(const Scope* scope) {
-  return GetGenDirForSourceDir(scope->settings(), scope->GetSourceDir());
+SourceDir GetScopeCurrentBuildDirAsSourceDir(const Scope* scope,
+                                             BuildDirType type) {
+  if (type == BuildDirType::TOOLCHAIN_ROOT)
+    return GetBuildDirAsSourceDir(BuildDirContext(scope), type);
+  return GetSubBuildDirAsSourceDir(
+      BuildDirContext(scope), scope->GetSourceDir(), type);
 }
diff --git a/tools/gn/filesystem_utils.h b/tools/gn/filesystem_utils.h
index aaa08ba..f53f24a 100644
--- a/tools/gn/filesystem_utils.h
+++ b/tools/gn/filesystem_utils.h
@@ -177,42 +177,85 @@
 
 // -----------------------------------------------------------------------------
 
-// These functions return the various flavors of output and gen directories.
-SourceDir GetToolchainOutputDir(const Settings* settings);
-SourceDir GetToolchainOutputDir(const BuildSettings* build_settings,
-                                const Label& label, bool is_default);
+enum class BuildDirType {
+  // Returns the root toolchain dir rather than the generated or output
+  // subdirectories. This is valid only for the toolchain directory getters.
+  // Asking for this for a target or source dir makes no sense.
+  TOOLCHAIN_ROOT,
 
-SourceDir GetToolchainGenDir(const Settings* settings);
-OutputFile GetToolchainGenDirAsOutputFile(const Settings* settings);
-SourceDir GetToolchainGenDir(const BuildSettings* build_settings,
-                             const Label& toolchain_label,
-                             bool is_default);
+  // Generated file directory.
+  GEN,
 
-SourceDir GetOutputDirForSourceDir(const Settings* settings,
-                                   const SourceDir& source_dir);
-SourceDir GetOutputDirForSourceDir(const BuildSettings* build_settings,
-                                   const SourceDir& source_dir,
-                                   const Label& toolchain_label,
-                                   bool is_default_toolchain);
-OutputFile GetOutputDirForSourceDirAsOutputFile(
-    const BuildSettings* build_settings,
-    const SourceDir& source_dir,
-    const Label& toolchain_label,
-    bool is_default_toolchain);
-OutputFile GetOutputDirForSourceDirAsOutputFile(const Settings* settings,
-                                                const SourceDir& source_dir);
+  // Output file directory.
+  OBJ,
+};
 
-SourceDir GetGenDirForSourceDir(const Settings* settings,
-                                 const SourceDir& source_dir);
-OutputFile GetGenDirForSourceDirAsOutputFile(const Settings* settings,
-                                             const SourceDir& source_dir);
+// In different contexts, different information is known about the toolchain in
+// question. If you have a Target or settings object, everything can be
+// extracted from there. But when querying label information on something in
+// another toolchain, for example, the only thing known (it may not even exist)
+// is the toolchain label string and whether it matches the default toolchain.
+//
+// This object extracts the relevant information from a variety of input
+// types for the convenience of the caller.
+class BuildDirContext {
+ public:
+  // Extracts toolchain information associated with the given target.
+  explicit BuildDirContext(const Target* target);
 
-SourceDir GetTargetOutputDir(const Target* target);
-OutputFile GetTargetOutputDirAsOutputFile(const Target* target);
-SourceDir GetTargetGenDir(const Target* target);
-OutputFile GetTargetGenDirAsOutputFile(const Target* target);
+  // Extracts toolchain information associated with the given settings object.
+  explicit BuildDirContext(const Settings* settings);
 
-SourceDir GetCurrentOutputDir(const Scope* scope);
-SourceDir GetCurrentGenDir(const Scope* scope);
+  // Extrats toolchain information from the current toolchain of the scope.
+  explicit BuildDirContext(const Scope* execution_scope);
+
+  // Extracts the default toolchain information from the given execution
+  // scope. The toolchain you want to query must be passed in. This doesn't
+  // use the settings object from the Scope so one can query other toolchains.
+  // If you want to use the scope's current toolchain, use the version above.
+  BuildDirContext(const Scope* execution_scope, const Label& toolchain_label);
+
+  // Specify all information manually.
+  BuildDirContext(const BuildSettings* build_settings,
+                  const Label& toolchain_label,
+                  bool is_default_toolchain);
+
+  const BuildSettings* build_settings;
+  const Label& toolchain_label;
+  bool is_default_toolchain;
+};
+
+// Returns the root, object, or generated file directory for the toolchain.
+//
+// The toolchain object file root is never exposed in GN (there is no
+// root_obj_dir variable) so BuildDirType::OBJ would normally never be passed
+// to this function except when it's called by one of the variants below that
+// append paths to it.
+SourceDir GetBuildDirAsSourceDir(const BuildDirContext& context,
+                                 BuildDirType type);
+OutputFile GetBuildDirAsOutputFile(const BuildDirContext& context,
+                                   BuildDirType type);
+
+// Returns the output or generated file directory corresponding to the given
+// source directory.
+SourceDir GetSubBuildDirAsSourceDir(const BuildDirContext& context,
+                                    const SourceDir& source_dir,
+                                    BuildDirType type);
+OutputFile GetSubBuildDirAsOutputFile(const BuildDirContext& context,
+                                      const SourceDir& source_dir,
+                                      BuildDirType type);
+
+// Returns the output or generated file directory corresponding to the given
+// target.
+SourceDir GetBuildDirForTargetAsSourceDir(const Target* target,
+                                          BuildDirType type);
+OutputFile GetBuildDirForTargetAsOutputFile(const Target* target,
+                                            BuildDirType type);
+
+// Returns the scope's current directory.
+SourceDir GetScopeCurrentBuildDirAsSourceDir(const Scope* scope,
+                                             BuildDirType type);
+// Lack of OutputDir version is due only to it not currently being needed,
+// please add one if you need it.
 
 #endif  // TOOLS_GN_FILESYSTEM_UTILS_H_
diff --git a/tools/gn/filesystem_utils_unittest.cc b/tools/gn/filesystem_utils_unittest.cc
index 894b3da..240ed61 100644
--- a/tools/gn/filesystem_utils_unittest.cc
+++ b/tools/gn/filesystem_utils_unittest.cc
@@ -638,47 +638,61 @@
   Label default_toolchain_label(SourceDir("//toolchain/"), "default");
   default_settings.set_toolchain_label(default_toolchain_label);
   default_settings.set_default_toolchain_label(default_toolchain_label);
+  BuildDirContext default_context(&default_settings);
 
-  // Default toolchain out dir.
+  // Default toolchain out dir as source dirs.
   EXPECT_EQ("//out/Debug/",
-            GetToolchainOutputDir(&default_settings).value());
-  EXPECT_EQ("//out/Debug/",
-            GetToolchainOutputDir(&build_settings, default_toolchain_label,
-                                  true).value());
-
-  // Default toolchain gen dir.
+            GetBuildDirAsSourceDir(default_context,
+                                   BuildDirType::TOOLCHAIN_ROOT).value());
+  EXPECT_EQ("//out/Debug/obj/",
+            GetBuildDirAsSourceDir(default_context,
+                                   BuildDirType::OBJ).value());
   EXPECT_EQ("//out/Debug/gen/",
-            GetToolchainGenDir(&default_settings).value());
+            GetBuildDirAsSourceDir(default_context,
+                                   BuildDirType::GEN).value());
+
+  // Default toolchain our dir as output files.
+  EXPECT_EQ("",
+            GetBuildDirAsOutputFile(default_context,
+                                    BuildDirType::TOOLCHAIN_ROOT).value());
+  EXPECT_EQ("obj/",
+            GetBuildDirAsOutputFile(default_context,
+                                    BuildDirType::OBJ).value());
   EXPECT_EQ("gen/",
-            GetToolchainGenDirAsOutputFile(&default_settings).value());
-  EXPECT_EQ("//out/Debug/gen/",
-            GetToolchainGenDir(&build_settings, default_toolchain_label,
-                               true).value());
+            GetBuildDirAsOutputFile(default_context,
+                                    BuildDirType::GEN).value());
 
   // Check a secondary toolchain.
   Settings other_settings(&build_settings, "two/");
   Label other_toolchain_label(SourceDir("//toolchain/"), "two");
-  default_settings.set_toolchain_label(other_toolchain_label);
-  default_settings.set_default_toolchain_label(default_toolchain_label);
+  other_settings.set_toolchain_label(other_toolchain_label);
+  other_settings.set_default_toolchain_label(default_toolchain_label);
+  BuildDirContext other_context(&other_settings);
 
-  // Secondary toolchain out dir.
+  // Secondary toolchain out dir as source dirs.
   EXPECT_EQ("//out/Debug/two/",
-            GetToolchainOutputDir(&other_settings).value());
-  EXPECT_EQ("//out/Debug/two/",
-            GetToolchainOutputDir(&build_settings, other_toolchain_label,
-                                  false).value());
-
-  // Secondary toolchain gen dir.
+            GetBuildDirAsSourceDir(other_context,
+                                   BuildDirType::TOOLCHAIN_ROOT).value());
+  EXPECT_EQ("//out/Debug/two/obj/",
+            GetBuildDirAsSourceDir(other_context,
+                                   BuildDirType::OBJ).value());
   EXPECT_EQ("//out/Debug/two/gen/",
-            GetToolchainGenDir(&other_settings).value());
+            GetBuildDirAsSourceDir(other_context,
+                                   BuildDirType::GEN).value());
+
+  // Secondary toolchain out dir as output files.
+  EXPECT_EQ("two/",
+            GetBuildDirAsOutputFile(other_context,
+                                    BuildDirType::TOOLCHAIN_ROOT).value());
+  EXPECT_EQ("two/obj/",
+            GetBuildDirAsOutputFile(other_context,
+                                    BuildDirType::OBJ).value());
   EXPECT_EQ("two/gen/",
-            GetToolchainGenDirAsOutputFile(&other_settings).value());
-  EXPECT_EQ("//out/Debug/two/gen/",
-            GetToolchainGenDir(&build_settings, other_toolchain_label,
-                               false).value());
+            GetBuildDirAsOutputFile(other_context,
+                                    BuildDirType::GEN).value());
 }
 
-TEST(FilesystemUtils, GetOutDirForSourceDir) {
+TEST(FilesystemUtils, GetSubBuildDir) {
   BuildSettings build_settings;
   build_settings.SetBuildDir(SourceDir("//out/Debug/"));
 
@@ -687,102 +701,83 @@
   Settings default_settings(&build_settings, "");
   default_settings.set_toolchain_label(default_toolchain_label);
   default_settings.set_default_toolchain_label(default_toolchain_label);
-  EXPECT_EQ("//out/Debug/obj/",
-            GetOutputDirForSourceDir(
-                &default_settings, SourceDir("//")).value());
-  EXPECT_EQ("obj/",
-            GetOutputDirForSourceDirAsOutputFile(
-                &default_settings, SourceDir("//")).value());
+  BuildDirContext default_context(&default_settings);
 
+  // Target in the root.
+  EXPECT_EQ("//out/Debug/obj/",
+            GetSubBuildDirAsSourceDir(
+                default_context, SourceDir("//"), BuildDirType::OBJ).value());
+  EXPECT_EQ("gen/",
+            GetSubBuildDirAsOutputFile(
+                default_context, SourceDir("//"), BuildDirType::GEN).value());
+
+  // Target in another directory.
   EXPECT_EQ("//out/Debug/obj/foo/bar/",
-            GetOutputDirForSourceDir(
-                &default_settings, SourceDir("//foo/bar/")).value());
-  EXPECT_EQ("obj/foo/bar/",
-            GetOutputDirForSourceDirAsOutputFile(
-                &default_settings, SourceDir("//foo/bar/")).value());
+            GetSubBuildDirAsSourceDir(
+                default_context, SourceDir("//foo/bar/"), BuildDirType::OBJ)
+            .value());
+  EXPECT_EQ("gen/foo/bar/",
+            GetSubBuildDirAsOutputFile(
+                default_context, SourceDir("//foo/bar/"), BuildDirType::GEN)
+            .value());
 
   // Secondary toolchain.
   Settings other_settings(&build_settings, "two/");
   other_settings.set_toolchain_label(Label(SourceDir("//toolchain/"), "two"));
   other_settings.set_default_toolchain_label(default_toolchain_label);
-  EXPECT_EQ("//out/Debug/two/obj/",
-            GetOutputDirForSourceDir(
-                &other_settings, SourceDir("//")).value());
-  EXPECT_EQ("two/obj/",
-            GetOutputDirForSourceDirAsOutputFile(
-                &other_settings, SourceDir("//")).value());
+  BuildDirContext other_context(&other_settings);
 
+  // Target in the root.
+  EXPECT_EQ("//out/Debug/two/obj/",
+            GetSubBuildDirAsSourceDir(
+                other_context, SourceDir("//"), BuildDirType::OBJ).value());
+  EXPECT_EQ("two/gen/",
+            GetSubBuildDirAsOutputFile(
+                other_context, SourceDir("//"), BuildDirType::GEN).value());
+
+  // Target in another directory.
   EXPECT_EQ("//out/Debug/two/obj/foo/bar/",
-            GetOutputDirForSourceDir(&other_settings,
-                                     SourceDir("//foo/bar/")).value());
-  EXPECT_EQ("two/obj/foo/bar/",
-            GetOutputDirForSourceDirAsOutputFile(
-                &other_settings, SourceDir("//foo/bar/")).value());
+            GetSubBuildDirAsSourceDir(
+                other_context, SourceDir("//foo/bar/"), BuildDirType::OBJ)
+            .value());
+  EXPECT_EQ("two/gen/foo/bar/",
+            GetSubBuildDirAsOutputFile(
+                other_context, SourceDir("//foo/bar/"), BuildDirType::GEN)
+            .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());
+            GetSubBuildDirAsSourceDir(
+                default_context, SourceDir("/abs"), BuildDirType::OBJ).value());
+  EXPECT_EQ("gen/ABS_PATH/abs/",
+            GetSubBuildDirAsOutputFile(
+                default_context, SourceDir("/abs"), BuildDirType::GEN).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());
+            GetSubBuildDirAsSourceDir(
+                default_context, SourceDir("/C:/abs"), BuildDirType::OBJ)
+                .value());
+  EXPECT_EQ("gen/ABS_PATH/C/abs/",
+            GetSubBuildDirAsOutputFile(
+                default_context, SourceDir("/C:/abs"), BuildDirType::GEN)
+                .value());
 #endif
 }
 
-TEST(FilesystemUtils, GetGenDirForSourceDir) {
-  BuildSettings build_settings;
-  build_settings.SetBuildDir(SourceDir("//out/Debug/"));
-
-  // Test the default toolchain.
-  Settings default_settings(&build_settings, "");
-  EXPECT_EQ("//out/Debug/gen/",
-            GetGenDirForSourceDir(
-                &default_settings, SourceDir("//")).value());
-  EXPECT_EQ("gen/",
-            GetGenDirForSourceDirAsOutputFile(
-                &default_settings, SourceDir("//")).value());
-
-  EXPECT_EQ("//out/Debug/gen/foo/bar/",
-            GetGenDirForSourceDir(
-                &default_settings, SourceDir("//foo/bar/")).value());
-  EXPECT_EQ("gen/foo/bar/",
-            GetGenDirForSourceDirAsOutputFile(
-                &default_settings, SourceDir("//foo/bar/")).value());
-
-  // Secondary toolchain.
-  Settings other_settings(&build_settings, "two/");
-  EXPECT_EQ("//out/Debug/two/gen/",
-            GetGenDirForSourceDir(
-                &other_settings, SourceDir("//")).value());
-  EXPECT_EQ("two/gen/",
-            GetGenDirForSourceDirAsOutputFile(
-                &other_settings, SourceDir("//")).value());
-
-  EXPECT_EQ("//out/Debug/two/gen/foo/bar/",
-            GetGenDirForSourceDir(
-                &other_settings, SourceDir("//foo/bar/")).value());
-  EXPECT_EQ("two/gen/foo/bar/",
-            GetGenDirForSourceDirAsOutputFile(
-                &other_settings, SourceDir("//foo/bar/")).value());
-}
-
-TEST(FilesystemUtils, GetTargetDirs) {
+TEST(FilesystemUtils, GetBuildDirForTarget) {
   BuildSettings build_settings;
   build_settings.SetBuildDir(SourceDir("//out/Debug/"));
   Settings settings(&build_settings, "");
 
   Target a(&settings, Label(SourceDir("//foo/bar/"), "baz"));
-  EXPECT_EQ("//out/Debug/obj/foo/bar/", GetTargetOutputDir(&a).value());
-  EXPECT_EQ("obj/foo/bar/", GetTargetOutputDirAsOutputFile(&a).value());
-  EXPECT_EQ("//out/Debug/gen/foo/bar/", GetTargetGenDir(&a).value());
-  EXPECT_EQ("gen/foo/bar/", GetTargetGenDirAsOutputFile(&a).value());
+  EXPECT_EQ("//out/Debug/obj/foo/bar/",
+            GetBuildDirForTargetAsSourceDir(&a, BuildDirType::OBJ).value());
+  EXPECT_EQ("obj/foo/bar/",
+            GetBuildDirForTargetAsOutputFile(&a, BuildDirType::OBJ).value());
+  EXPECT_EQ("//out/Debug/gen/foo/bar/",
+            GetBuildDirForTargetAsSourceDir(&a, BuildDirType::GEN).value());
+  EXPECT_EQ("gen/foo/bar/",
+            GetBuildDirForTargetAsOutputFile(&a, BuildDirType::GEN).value());
 }
 
 // Tests handling of output dirs when build dir is the same as the root.
@@ -791,15 +786,21 @@
   build_settings.SetBuildDir(SourceDir("//"));
   Settings settings(&build_settings, "");
 
-  EXPECT_EQ("//", GetToolchainOutputDir(&settings).value());
-  EXPECT_EQ("//gen/", GetToolchainGenDir(&settings).value());
-  EXPECT_EQ("gen/", GetToolchainGenDirAsOutputFile(&settings).value());
+  BuildDirContext context(&settings);
+
+  EXPECT_EQ("//",
+            GetBuildDirAsSourceDir(context, BuildDirType::TOOLCHAIN_ROOT)
+                .value());
+  EXPECT_EQ("//gen/",
+            GetBuildDirAsSourceDir(context, BuildDirType::GEN).value());
   EXPECT_EQ("//obj/",
-            GetOutputDirForSourceDir(&settings, SourceDir("//")).value());
-  EXPECT_EQ("obj/",
-            GetOutputDirForSourceDirAsOutputFile(
-                &settings, SourceDir("//")).value());
+            GetBuildDirAsSourceDir(context, BuildDirType::OBJ).value());
+
+  EXPECT_EQ("",
+            GetBuildDirAsOutputFile(context, BuildDirType::TOOLCHAIN_ROOT)
+                .value());
   EXPECT_EQ("gen/",
-            GetGenDirForSourceDirAsOutputFile(
-                &settings, SourceDir("//")).value());
+            GetBuildDirAsOutputFile(context, BuildDirType::GEN).value());
+  EXPECT_EQ("obj/",
+            GetBuildDirAsOutputFile(context, BuildDirType::OBJ).value());
 }
diff --git a/tools/gn/function_get_label_info.cc b/tools/gn/function_get_label_info.cc
index be22cca..58cb964 100644
--- a/tools/gn/function_get_label_info.cc
+++ b/tools/gn/function_get_label_info.cc
@@ -11,14 +11,6 @@
 
 namespace functions {
 
-namespace {
-
-bool ToolchainIsDefault(const Scope* scope, const Label& toolchain_label) {
-  return scope->settings()->default_toolchain_label() == toolchain_label;
-}
-
-}  // namespace
-
 const char kGetLabelInfo[] = "get_label_info";
 const char kGetLabelInfo_HelpShort[] =
     "get_label_info: Get an attribute from a target's label.";
@@ -117,29 +109,25 @@
     result.string_value() = DirectoryWithNoLastSlash(label.dir());
 
   } else if (what == "target_gen_dir") {
-    result.string_value() = DirectoryWithNoLastSlash(
-        GetGenDirForSourceDir(scope->settings(), label.dir()));
+    result.string_value() = DirectoryWithNoLastSlash(GetSubBuildDirAsSourceDir(
+        BuildDirContext(scope, label.GetToolchainLabel()),
+        label.dir(),
+        BuildDirType::GEN));
 
   } else if (what == "root_gen_dir") {
-    Label toolchain_label = label.GetToolchainLabel();
-    result.string_value() = DirectoryWithNoLastSlash(
-        GetToolchainGenDir(scope->settings()->build_settings(),
-                           toolchain_label,
-                           ToolchainIsDefault(scope, toolchain_label)));
+    result.string_value() = DirectoryWithNoLastSlash(GetBuildDirAsSourceDir(
+        BuildDirContext(scope, label.GetToolchainLabel()), BuildDirType::GEN));
 
   } else if (what == "target_out_dir") {
-    Label toolchain_label = label.GetToolchainLabel();
-    result.string_value() = DirectoryWithNoLastSlash(
-        GetOutputDirForSourceDir(scope->settings()->build_settings(),
-                                 label.dir(), toolchain_label,
-                                 ToolchainIsDefault(scope, toolchain_label)));
+    result.string_value() = DirectoryWithNoLastSlash(GetSubBuildDirAsSourceDir(
+        BuildDirContext(scope, label.GetToolchainLabel()),
+        label.dir(),
+        BuildDirType::OBJ));
 
   } else if (what == "root_out_dir") {
-    Label toolchain_label = label.GetToolchainLabel();
-    result.string_value() = DirectoryWithNoLastSlash(
-        GetToolchainOutputDir(scope->settings()->build_settings(),
-                              toolchain_label,
-                              ToolchainIsDefault(scope, toolchain_label)));
+    result.string_value() = DirectoryWithNoLastSlash(GetBuildDirAsSourceDir(
+        BuildDirContext(scope, label.GetToolchainLabel()),
+        BuildDirType::TOOLCHAIN_ROOT));
 
   } else if (what == "toolchain") {
     result.string_value() = label.GetToolchainLabel().GetUserVisibleName(false);
diff --git a/tools/gn/function_get_label_info_unittest.cc b/tools/gn/function_get_label_info_unittest.cc
index ddec830..65e46d0 100644
--- a/tools/gn/function_get_label_info_unittest.cc
+++ b/tools/gn/function_get_label_info_unittest.cc
@@ -73,14 +73,20 @@
 
 TEST_F(GetLabelInfoTest, TargetOutDir) {
   EXPECT_EQ("//out/Debug/obj/src/foo", Call(":name", "target_out_dir"));
-  EXPECT_EQ("//out/Debug", Call(":name", "root_out_dir"));
-
   EXPECT_EQ("//out/Debug/obj/foo",
             Call("//foo:name(//toolchain:default)", "target_out_dir"));
   EXPECT_EQ("//out/Debug/random/obj/foo",
             Call("//foo:name(//toolchain:random)", "target_out_dir"));
 }
 
+TEST_F(GetLabelInfoTest, TargetGenDir) {
+  EXPECT_EQ("//out/Debug/gen/src/foo", Call(":name", "target_gen_dir"));
+  EXPECT_EQ("//out/Debug/gen/foo",
+            Call("//foo:name(//toolchain:default)", "target_gen_dir"));
+  EXPECT_EQ("//out/Debug/random/gen/foo",
+            Call("//foo:name(//toolchain:random)", "target_gen_dir"));
+}
+
 TEST_F(GetLabelInfoTest, LabelNoToolchain) {
   EXPECT_EQ("//src/foo:name", Call(":name", "label_no_toolchain"));
   EXPECT_EQ("//src/foo:name",
diff --git a/tools/gn/function_get_path_info.cc b/tools/gn/function_get_path_info.cc
index d994f06..c14f416 100644
--- a/tools/gn/function_get_path_info.cc
+++ b/tools/gn/function_get_path_info.cc
@@ -88,16 +88,16 @@
       return dir_incl_slash.substr(0, dir_incl_slash.size() - 1).as_string();
     }
     case WHAT_GEN_DIR: {
-      return DirectoryWithNoLastSlash(
-          GetGenDirForSourceDir(settings,
-                                DirForInput(settings, current_dir,
-                                            input, err)));
+      return DirectoryWithNoLastSlash(GetSubBuildDirAsSourceDir(
+          BuildDirContext(settings),
+          DirForInput(settings, current_dir, input, err),
+          BuildDirType::GEN));
     }
     case WHAT_OUT_DIR: {
-      return DirectoryWithNoLastSlash(
-          GetOutputDirForSourceDir(settings,
-                                   DirForInput(settings, current_dir,
-                                               input, err)));
+      return DirectoryWithNoLastSlash(GetSubBuildDirAsSourceDir(
+          BuildDirContext(settings),
+          DirForInput(settings, current_dir, input, err),
+          BuildDirType::OBJ));
     }
     case WHAT_ABSPATH: {
       if (!input_string.empty() &&
@@ -168,7 +168,7 @@
     "      The output file directory corresponding to the path of the\n"
     "      given file, not including a trailing slash.\n"
     "        \"//foo/bar/baz.txt\" => \"//out/Default/obj/foo/bar\"\n"
-
+    "\n"
     "  \"gen_dir\"\n"
     "      The generated file directory corresponding to the path of the\n"
     "      given file, not including a trailing slash.\n"
diff --git a/tools/gn/ninja_binary_target_writer.cc b/tools/gn/ninja_binary_target_writer.cc
index 129ddb1..da46149 100644
--- a/tools/gn/ninja_binary_target_writer.cc
+++ b/tools/gn/ninja_binary_target_writer.cc
@@ -417,10 +417,8 @@
     return OutputFile(settings_->build_settings(), target_->inputs()[0]);
 
   // Make a stamp file.
-  OutputFile input_stamp_file(
-      RebasePath(GetTargetOutputDir(target_).value(),
-                 settings_->build_settings()->build_dir(),
-                 settings_->build_settings()->root_path_utf8()));
+  OutputFile input_stamp_file =
+      GetBuildDirForTargetAsOutputFile(target_, BuildDirType::OBJ);
   input_stamp_file.value().append(target_->label().name());
   input_stamp_file.value().append(".inputs.stamp");
 
@@ -1039,7 +1037,7 @@
     Toolchain::ToolType tool_type) const {
   // Use "obj/{dir}/{target_name}_{lang}.pch" which ends up
   // looking like "obj/chrome/browser/browser_cc.pch"
-  OutputFile ret = GetTargetOutputDirAsOutputFile(target_);
+  OutputFile ret = GetBuildDirForTargetAsOutputFile(target_, BuildDirType::OBJ);
   ret.value().append(target_->label().name());
   ret.value().push_back('_');
   ret.value().append(GetPCHLangSuffixForToolType(tool_type));
diff --git a/tools/gn/ninja_create_bundle_target_writer.cc b/tools/gn/ninja_create_bundle_target_writer.cc
index 351ec0c..3b87129 100644
--- a/tools/gn/ninja_create_bundle_target_writer.cc
+++ b/tools/gn/ninja_create_bundle_target_writer.cc
@@ -174,9 +174,7 @@
     return dependencies[0]->dependency_output_file();
 
   OutputFile xcassets_input_stamp_file =
-      OutputFile(RebasePath(GetTargetOutputDir(target_).value(),
-                            settings_->build_settings()->build_dir(),
-                            settings_->build_settings()->root_path_utf8()));
+      GetBuildDirForTargetAsOutputFile(target_, BuildDirType::OBJ);
   xcassets_input_stamp_file.value().append(target_->label().name());
   xcassets_input_stamp_file.value().append(".xcassets.inputdeps.stamp");
 
@@ -261,9 +259,7 @@
                      dependencies.end());
 
   OutputFile code_signing_input_stamp_file =
-      OutputFile(RebasePath(GetTargetOutputDir(target_).value(),
-                            settings_->build_settings()->build_dir(),
-                            settings_->build_settings()->root_path_utf8()));
+      GetBuildDirForTargetAsOutputFile(target_, BuildDirType::OBJ);
   code_signing_input_stamp_file.value().append(target_->label().name());
   code_signing_input_stamp_file.value().append(".codesigning.inputdeps.stamp");
 
diff --git a/tools/gn/ninja_target_writer.cc b/tools/gn/ninja_target_writer.cc
index ccb3eb7..2b30d65 100644
--- a/tools/gn/ninja_target_writer.cc
+++ b/tools/gn/ninja_target_writer.cc
@@ -262,10 +262,8 @@
   }
 
   // Make a stamp file.
-  OutputFile input_stamp_file(
-      RebasePath(GetTargetOutputDir(target_).value(),
-                 settings_->build_settings()->build_dir(),
-                 settings_->build_settings()->root_path_utf8()));
+  OutputFile input_stamp_file =
+      GetBuildDirForTargetAsOutputFile(target_, BuildDirType::OBJ);
   input_stamp_file.value().append(target_->label().name());
   input_stamp_file.value().append(".inputdeps.stamp");
 
diff --git a/tools/gn/ninja_utils.cc b/tools/gn/ninja_utils.cc
index 60d4d36..9e90392 100644
--- a/tools/gn/ninja_utils.cc
+++ b/tools/gn/ninja_utils.cc
@@ -9,13 +9,15 @@
 #include "tools/gn/target.h"
 
 SourceFile GetNinjaFileForTarget(const Target* target) {
-  return SourceFile(GetTargetOutputDir(target).value() +
-                    target->label().name() + ".ninja");
+  return SourceFile(
+      GetBuildDirForTargetAsSourceDir(target, BuildDirType::OBJ).value() +
+      target->label().name() + ".ninja");
 }
 
 SourceFile GetNinjaFileForToolchain(const Settings* settings) {
-  return SourceFile(GetToolchainOutputDir(settings).value() +
-                    "toolchain.ninja");
+  return SourceFile(GetBuildDirAsSourceDir(
+      BuildDirContext(settings), BuildDirType::TOOLCHAIN_ROOT).value() +
+      "toolchain.ninja");
 }
 
 std::string GetNinjaRulePrefixForToolchain(const Settings* settings) {
diff --git a/tools/gn/scope_per_file_provider.cc b/tools/gn/scope_per_file_provider.cc
index d2dea11..737a5cd 100644
--- a/tools/gn/scope_per_file_provider.cc
+++ b/tools/gn/scope_per_file_provider.cc
@@ -85,7 +85,8 @@
   if (!root_gen_dir_) {
     root_gen_dir_.reset(new Value(
         nullptr,
-        DirectoryWithNoLastSlash(GetToolchainGenDir(scope_->settings()))));
+        DirectoryWithNoLastSlash(GetBuildDirAsSourceDir(
+            BuildDirContext(scope_), BuildDirType::GEN))));
   }
   return root_gen_dir_.get();
 }
@@ -94,15 +95,18 @@
   if (!root_out_dir_) {
     root_out_dir_.reset(new Value(
         nullptr,
-        DirectoryWithNoLastSlash(GetToolchainOutputDir(scope_->settings()))));
+        DirectoryWithNoLastSlash(GetScopeCurrentBuildDirAsSourceDir(
+            scope_, BuildDirType::TOOLCHAIN_ROOT))));
   }
   return root_out_dir_.get();
 }
 
 const Value* ScopePerFileProvider::GetTargetGenDir() {
   if (!target_gen_dir_) {
-    target_gen_dir_.reset(
-        new Value(nullptr, DirectoryWithNoLastSlash(GetCurrentGenDir(scope_))));
+    target_gen_dir_.reset(new Value(
+        nullptr,
+        DirectoryWithNoLastSlash(
+            GetScopeCurrentBuildDirAsSourceDir(scope_, BuildDirType::GEN))));
   }
   return target_gen_dir_.get();
 }
@@ -110,7 +114,9 @@
 const Value* ScopePerFileProvider::GetTargetOutDir() {
   if (!target_out_dir_) {
     target_out_dir_.reset(new Value(
-        nullptr, DirectoryWithNoLastSlash(GetCurrentOutputDir(scope_))));
+        nullptr,
+        DirectoryWithNoLastSlash(
+            GetScopeCurrentBuildDirAsSourceDir(scope_, BuildDirType::OBJ))));
   }
   return target_out_dir_.get();
 }
diff --git a/tools/gn/substitution_writer.cc b/tools/gn/substitution_writer.cc
index a82e7ee..ce57485 100644
--- a/tools/gn/substitution_writer.cc
+++ b/tools/gn/substitution_writer.cc
@@ -364,13 +364,13 @@
           settings->build_settings()->root_path_utf8());
 
     case SUBSTITUTION_SOURCE_GEN_DIR:
-      to_rebase = DirectoryWithNoLastSlash(
-          GetGenDirForSourceDir(settings, source.GetDir()));
+      to_rebase = DirectoryWithNoLastSlash(GetSubBuildDirAsSourceDir(
+          BuildDirContext(settings), source.GetDir(), BuildDirType::GEN));
       break;
 
     case SUBSTITUTION_SOURCE_OUT_DIR:
-      to_rebase = DirectoryWithNoLastSlash(
-          GetOutputDirForSourceDir(settings, source.GetDir()));
+      to_rebase = DirectoryWithNoLastSlash(GetSubBuildDirAsSourceDir(
+          BuildDirContext(settings), source.GetDir(), BuildDirType::OBJ));
       break;
 
     default:
@@ -433,7 +433,8 @@
       break;
     case SUBSTITUTION_ROOT_GEN_DIR:
       SetDirOrDotWithNoSlash(
-          GetToolchainGenDirAsOutputFile(target->settings()).value(),
+          GetBuildDirAsOutputFile(BuildDirContext(target),
+                                  BuildDirType::GEN).value(),
           result);
       break;
     case SUBSTITUTION_ROOT_OUT_DIR:
@@ -443,12 +444,12 @@
       break;
     case SUBSTITUTION_TARGET_GEN_DIR:
       SetDirOrDotWithNoSlash(
-          GetTargetGenDirAsOutputFile(target).value(),
+          GetBuildDirForTargetAsOutputFile(target, BuildDirType::GEN).value(),
           result);
       break;
     case SUBSTITUTION_TARGET_OUT_DIR:
       SetDirOrDotWithNoSlash(
-          GetTargetOutputDirAsOutputFile(target).value(),
+          GetBuildDirForTargetAsOutputFile(target, BuildDirType::OBJ).value(),
           result);
       break;
     case SUBSTITUTION_TARGET_OUTPUT_NAME:
diff --git a/tools/gn/target.cc b/tools/gn/target.cc
index 206db83..aafdc4f 100644
--- a/tools/gn/target.cc
+++ b/tools/gn/target.cc
@@ -543,7 +543,8 @@
       // These don't get linked to and use stamps which should be the first
       // entry in the outputs. These stamps are named
       // "<target_out_dir>/<targetname>.stamp".
-      dependency_output_file_ = GetTargetOutputDirAsOutputFile(this);
+      dependency_output_file_ =
+          GetBuildDirForTargetAsOutputFile(this, BuildDirType::OBJ);
       dependency_output_file_.value().append(GetComputedOutputName());
       dependency_output_file_.value().append(".stamp");
       break;
diff --git a/tools/gn/visual_studio_writer.cc b/tools/gn/visual_studio_writer.cc
index fb6d923..870d96c 100644
--- a/tools/gn/visual_studio_writer.cc
+++ b/tools/gn/visual_studio_writer.cc
@@ -337,8 +337,9 @@
       project_config_platform = "Win32";
   }
 
-  SourceFile target_file = GetTargetOutputDir(target).ResolveRelativeFile(
-      Value(nullptr, project_name + ".vcxproj"), err);
+  SourceFile target_file =
+      GetBuildDirForTargetAsSourceDir(target, BuildDirType::OBJ)
+          .ResolveRelativeFile(Value(nullptr, project_name + ".vcxproj"), err);
   if (target_file.is_null())
     return false;
 
@@ -377,9 +378,9 @@
     const Target* target,
     SourceFileCompileTypePairs* source_types,
     Err* err) {
-  PathOutput path_output(GetTargetOutputDir(target),
-                         build_settings_->root_path_utf8(),
-                         EscapingMode::ESCAPE_NONE);
+  PathOutput path_output(
+      GetBuildDirForTargetAsSourceDir(target, BuildDirType::OBJ),
+      build_settings_->root_path_utf8(), EscapingMode::ESCAPE_NONE);
 
   out << "<?xml version=\"1.0\" encoding=\"utf-8\"?>" << std::endl;
   XmlElementWriter project(
@@ -612,9 +613,9 @@
     // File paths are relative to vcxproj files which are generated to out dirs.
     // Filters tree structure need to reflect source directories and be relative
     // to target file. We need two path outputs then.
-    PathOutput file_path_output(GetTargetOutputDir(target),
-                                build_settings_->root_path_utf8(),
-                                EscapingMode::ESCAPE_NONE);
+    PathOutput file_path_output(
+        GetBuildDirForTargetAsSourceDir(target, BuildDirType::OBJ),
+        build_settings_->root_path_utf8(), EscapingMode::ESCAPE_NONE);
     PathOutput filter_path_output(target->label().dir(),
                                   build_settings_->root_path_utf8(),
                                   EscapingMode::ESCAPE_NONE);