Refactor filesystem utils to clean up the code and avoid OutputFile mutation

The purpose of this is to allow OutputFile to store a StringAtom, which
requires it to be immutable. Doing so would allow us to cheaply copy
OutputFile objects.

* Rename Get*AsSourceDir to overloaded GetSourceDir.
* Split GetBuildDirAsOutputFile, GetSubBuildDirAsOutputFile, and GetBuildDirForTargetAsOutputFile into:
  * GetBuildDir overloads returning std::string.
  * GetOutputFile overloads that create an outputfile by joining arguments together, avoiding mutation of created OutputFiles
* Convert Target and Scope parameters in GetSourceDir/GetBuildDir/GetOutputFile from pointers to references to ensure null-safety.
* Update all call sites across target writers and helper functions to use the new reference-based and overloaded APIs.

Bug: 491925153
Change-Id: I36e7f46e0f0bbef961d9ea444832ee866a6a6964
Reviewed-on: https://gn-review.googlesource.com/c/gn/+/23922
Reviewed-by: Takuto Ikuta <tikuta@google.com>
Commit-Queue: Matt Stark <msta@google.com>
diff --git a/src/gn/filesystem_utils.cc b/src/gn/filesystem_utils.cc
index e7244e3..b2772ed 100644
--- a/src/gn/filesystem_utils.cc
+++ b/src/gn/filesystem_utils.cc
@@ -177,7 +177,7 @@
 // other generated sources and outputs.
 void AppendFixedAbsolutePathSuffix(const BuildSettings* build_settings,
                                    const SourceDir& source_dir,
-                                   OutputFile* result) {
+                                   std::string* result) {
   const std::string& build_dir = build_settings->build_dir().value();
 
   if (source_dir.value().starts_with(build_dir)) {
@@ -1032,17 +1032,15 @@
       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);
+SourceDir GetSourceDir(const BuildDirContext& context, BuildDirType type) {
+  return SourceDir(context.build_settings->build_dir().value() +
+                   GetBuildDir(context, type));
 }
 
-OutputFile GetBuildDirAsOutputFile(const BuildDirContext& context,
-                                   BuildDirType type) {
-  OutputFile result(GetOutputSubdirName(context.toolchain_label,
-                                        context.is_default_toolchain));
-  DCHECK(result.value().empty() || result.value().back() == '/');
+std::string GetBuildDir(const BuildDirContext& context, BuildDirType type) {
+  std::string result(GetOutputSubdirName(context.toolchain_label,
+                                         context.is_default_toolchain));
+  DCHECK(result.empty() || result.back() == '/');
 
   if (type == BuildDirType::GEN)
     result.append("gen/");
@@ -1053,18 +1051,20 @@
   return result;
 }
 
-SourceDir GetSubBuildDirAsSourceDir(const BuildDirContext& context,
-                                    const SourceDir& source_dir,
-                                    BuildDirType type) {
-  return GetSubBuildDirAsOutputFile(context, source_dir, type)
-      .AsSourceDir(context.build_settings);
+SourceDir GetSourceDir(const BuildDirContext& context,
+                       const SourceDir& source_dir,
+                       BuildDirType type) {
+  std::string path = context.build_settings->build_dir().value();
+  path.append(GetBuildDir(context, source_dir, type));
+  NormalizePath(&path);
+  return SourceDir(std::move(path));
 }
 
-OutputFile GetSubBuildDirAsOutputFile(const BuildDirContext& context,
-                                      const SourceDir& source_dir,
-                                      BuildDirType type) {
+std::string GetBuildDir(const BuildDirContext& context,
+                        const SourceDir& source_dir,
+                        BuildDirType type) {
   DCHECK(type != BuildDirType::TOOLCHAIN_ROOT);
-  OutputFile result = GetBuildDirAsOutputFile(context, type);
+  std::string result_path(GetBuildDir(context, type));
 
   if (source_dir.is_source_absolute()) {
     std::string_view build_dir = context.build_settings->build_dir().value();
@@ -1077,38 +1077,33 @@
       // it with `BUILD_DIR`. This will create results like `obj/BUILD_DIR/gen`
       // or `toolchain2/obj/BUILD_DIR/toolchain1/gen` which look surprising,
       // but guarantee unicity.
-      result.append("BUILD_DIR/");
-      result.append(source_dir_path.substr(build_dir.size()));
+      result_path.append("BUILD_DIR/");
+      result_path.append(source_dir_path.substr(build_dir.size()));
 
     } else {
       // The source dir is source-absolute, so we trim off the two leading
       // slashes to append to the toolchain object directory.
-      result.append(std::string_view(&source_dir.value()[2],
-                                     source_dir.value().size() - 2));
+      result_path.append(std::string_view(&source_dir.value()[2],
+                                          source_dir.value().size() - 2));
     }
   } else {
     // System-absolute.
-    AppendFixedAbsolutePathSuffix(context.build_settings, source_dir, &result);
+    AppendFixedAbsolutePathSuffix(context.build_settings, source_dir,
+                                  &result_path);
   }
-  return result;
+  return result_path;
 }
 
-SourceDir GetBuildDirForTargetAsSourceDir(const Target* target,
-                                          BuildDirType type) {
-  return GetSubBuildDirAsSourceDir(BuildDirContext(target),
-                                   target->label().dir(), type);
+SourceDir GetSourceDir(const Target& target, BuildDirType type) {
+  return GetSourceDir(BuildDirContext(&target), target.label().dir(), type);
 }
 
-OutputFile GetBuildDirForTargetAsOutputFile(const Target* target,
-                                            BuildDirType type) {
-  return GetSubBuildDirAsOutputFile(BuildDirContext(target),
-                                    target->label().dir(), type);
+std::string GetBuildDir(const Target& target, BuildDirType type) {
+  return GetBuildDir(BuildDirContext(&target), target.label().dir(), type);
 }
 
-SourceDir GetScopeCurrentBuildDirAsSourceDir(const Scope* scope,
-                                             BuildDirType type) {
+SourceDir GetSourceDir(const Scope& scope, BuildDirType type) {
   if (type == BuildDirType::TOOLCHAIN_ROOT)
-    return GetBuildDirAsSourceDir(BuildDirContext(scope), type);
-  return GetSubBuildDirAsSourceDir(BuildDirContext(scope),
-                                   scope->GetSourceDir(), type);
+    return GetSourceDir(BuildDirContext(&scope), type);
+  return GetSourceDir(BuildDirContext(&scope), scope.GetSourceDir(), type);
 }
diff --git a/src/gn/filesystem_utils.h b/src/gn/filesystem_utils.h
index 091a37c..2472c54 100644
--- a/src/gn/filesystem_utils.h
+++ b/src/gn/filesystem_utils.h
@@ -271,31 +271,54 @@
 // 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);
+SourceDir GetSourceDir(const BuildDirContext& context, BuildDirType type);
+std::string GetBuildDir(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);
+SourceDir GetSourceDir(const BuildDirContext& context,
+                       const SourceDir& source_dir,
+                       BuildDirType type);
+std::string GetBuildDir(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);
+SourceDir GetSourceDir(const Target& target, BuildDirType type);
+std::string GetBuildDir(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,
+SourceDir GetSourceDir(const Scope& scope, BuildDirType type);
+// Lack of GetBuildDir version is due only to it not currently being needed,
 // please add one if you need it.
 
+template <typename... Args>
+OutputFile GetOutputFile(const BuildDirContext& context,
+                         BuildDirType type,
+                         Args&&... args) {
+  std::string path = GetBuildDir(context, type);
+  (path.append(std::forward<Args>(args)), ...);
+  return OutputFile(path);
+}
+
+template <typename... Args>
+OutputFile GetOutputFile(const BuildDirContext& context,
+                         const SourceDir& source_dir,
+                         BuildDirType type,
+                         Args&&... args) {
+  std::string path = GetBuildDir(context, source_dir, type);
+  (path.append(std::forward<Args>(args)), ...);
+  return OutputFile(path);
+}
+
+template <typename... Args>
+OutputFile GetOutputFile(const Target& target,
+                         BuildDirType type,
+                         Args&&... args) {
+  std::string path = GetBuildDir(target, type);
+  (path.append(std::forward<Args>(args)), ...);
+  return OutputFile(path);
+}
+
 #endif  // TOOLS_GN_FILESYSTEM_UTILS_H_
diff --git a/src/gn/filesystem_utils_unittest.cc b/src/gn/filesystem_utils_unittest.cc
index 7b69a63..f533bde 100644
--- a/src/gn/filesystem_utils_unittest.cc
+++ b/src/gn/filesystem_utils_unittest.cc
@@ -660,24 +660,23 @@
   BuildDirContext default_context(&default_settings);
 
   // Default toolchain out dir as source dirs.
-  EXPECT_EQ("//out/Debug/", GetBuildDirAsSourceDir(default_context,
-                                                   BuildDirType::TOOLCHAIN_ROOT)
-                                .value());
+  EXPECT_EQ(
+      "//out/Debug/",
+      GetSourceDir(default_context, BuildDirType::TOOLCHAIN_ROOT).value());
   EXPECT_EQ("//out/Debug/obj/",
-            GetBuildDirAsSourceDir(default_context, BuildDirType::OBJ).value());
+            GetSourceDir(default_context, BuildDirType::OBJ).value());
   EXPECT_EQ("//out/Debug/gen/",
-            GetBuildDirAsSourceDir(default_context, BuildDirType::GEN).value());
+            GetSourceDir(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/",
-      GetBuildDirAsOutputFile(default_context, BuildDirType::GEN).value());
+      "", GetOutputFile(default_context, BuildDirType::TOOLCHAIN_ROOT).value());
+  EXPECT_EQ("obj/", GetOutputFile(default_context, BuildDirType::OBJ).value());
+  EXPECT_EQ("gen/", GetOutputFile(default_context, BuildDirType::GEN).value());
+  // Test raw string version.
+  EXPECT_EQ("", GetBuildDir(default_context, BuildDirType::TOOLCHAIN_ROOT));
+  EXPECT_EQ("obj/", GetBuildDir(default_context, BuildDirType::OBJ));
+  EXPECT_EQ("gen/", GetBuildDir(default_context, BuildDirType::GEN));
 
   // Check a secondary toolchain.
   Settings other_settings(&build_settings, "two/");
@@ -688,21 +687,19 @@
 
   // Secondary toolchain out dir as source dirs.
   EXPECT_EQ("//out/Debug/two/",
-            GetBuildDirAsSourceDir(other_context, BuildDirType::TOOLCHAIN_ROOT)
-                .value());
+            GetSourceDir(other_context, BuildDirType::TOOLCHAIN_ROOT).value());
   EXPECT_EQ("//out/Debug/two/obj/",
-            GetBuildDirAsSourceDir(other_context, BuildDirType::OBJ).value());
+            GetSourceDir(other_context, BuildDirType::OBJ).value());
   EXPECT_EQ("//out/Debug/two/gen/",
-            GetBuildDirAsSourceDir(other_context, BuildDirType::GEN).value());
+            GetSourceDir(other_context, BuildDirType::GEN).value());
 
   // Secondary toolchain out dir as output files.
   EXPECT_EQ("two/",
-            GetBuildDirAsOutputFile(other_context, BuildDirType::TOOLCHAIN_ROOT)
-                .value());
+            GetOutputFile(other_context, BuildDirType::TOOLCHAIN_ROOT).value());
   EXPECT_EQ("two/obj/",
-            GetBuildDirAsOutputFile(other_context, BuildDirType::OBJ).value());
+            GetOutputFile(other_context, BuildDirType::OBJ).value());
   EXPECT_EQ("two/gen/",
-            GetBuildDirAsOutputFile(other_context, BuildDirType::GEN).value());
+            GetOutputFile(other_context, BuildDirType::GEN).value());
 }
 
 TEST(FilesystemUtils, GetSubBuildDir) {
@@ -718,22 +715,21 @@
 
   // Target in the root.
   EXPECT_EQ("//out/Debug/obj/",
-            GetSubBuildDirAsSourceDir(default_context, SourceDir("//"),
-                                      BuildDirType::OBJ)
+            GetSourceDir(default_context, SourceDir("//"), BuildDirType::OBJ)
                 .value());
-  EXPECT_EQ("gen/", GetSubBuildDirAsOutputFile(default_context, SourceDir("//"),
-                                               BuildDirType::GEN)
-                        .value());
+  EXPECT_EQ("gen/",
+            GetOutputFile(default_context, SourceDir("//"), BuildDirType::GEN)
+                .value());
 
   // Target in another directory.
-  EXPECT_EQ("//out/Debug/obj/foo/bar/",
-            GetSubBuildDirAsSourceDir(default_context, SourceDir("//foo/bar/"),
-                                      BuildDirType::OBJ)
-                .value());
-  EXPECT_EQ("gen/foo/bar/",
-            GetSubBuildDirAsOutputFile(default_context, SourceDir("//foo/bar/"),
-                                       BuildDirType::GEN)
-                .value());
+  EXPECT_EQ(
+      "//out/Debug/obj/foo/bar/",
+      GetSourceDir(default_context, SourceDir("//foo/bar/"), BuildDirType::OBJ)
+          .value());
+  EXPECT_EQ(
+      "gen/foo/bar/",
+      GetOutputFile(default_context, SourceDir("//foo/bar/"), BuildDirType::GEN)
+          .value());
 
   // Secondary toolchain.
   Settings other_settings(&build_settings, "two/");
@@ -742,42 +738,39 @@
   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());
+  EXPECT_EQ(
+      "//out/Debug/two/obj/",
+      GetSourceDir(other_context, SourceDir("//"), BuildDirType::OBJ).value());
+  EXPECT_EQ(
+      "two/gen/",
+      GetOutputFile(other_context, SourceDir("//"), BuildDirType::GEN).value());
 
   // Target in another directory.
-  EXPECT_EQ("//out/Debug/two/obj/foo/bar/",
-            GetSubBuildDirAsSourceDir(other_context, SourceDir("//foo/bar/"),
-                                      BuildDirType::OBJ)
-                .value());
-  EXPECT_EQ("two/gen/foo/bar/",
-            GetSubBuildDirAsOutputFile(other_context, SourceDir("//foo/bar/"),
-                                       BuildDirType::GEN)
-                .value());
+  EXPECT_EQ(
+      "//out/Debug/two/obj/foo/bar/",
+      GetSourceDir(other_context, SourceDir("//foo/bar/"), BuildDirType::OBJ)
+          .value());
+  EXPECT_EQ(
+      "two/gen/foo/bar/",
+      GetOutputFile(other_context, SourceDir("//foo/bar/"), BuildDirType::GEN)
+          .value());
 
   // Absolute source path
   EXPECT_EQ("//out/Debug/obj/ABS_PATH/abs/",
-            GetSubBuildDirAsSourceDir(default_context, SourceDir("/abs"),
-                                      BuildDirType::OBJ)
+            GetSourceDir(default_context, SourceDir("/abs"), BuildDirType::OBJ)
                 .value());
   EXPECT_EQ("gen/ABS_PATH/abs/",
-            GetSubBuildDirAsOutputFile(default_context, SourceDir("/abs"),
-                                       BuildDirType::GEN)
+            GetOutputFile(default_context, SourceDir("/abs"), BuildDirType::GEN)
                 .value());
 #if defined(OS_WIN)
-  EXPECT_EQ("//out/Debug/obj/ABS_PATH/C/abs/",
-            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());
+  EXPECT_EQ(
+      "//out/Debug/obj/ABS_PATH/C/abs/",
+      GetSourceDir(default_context, SourceDir("/C:/abs"), BuildDirType::OBJ)
+          .value());
+  EXPECT_EQ(
+      "gen/ABS_PATH/C/abs/",
+      GetOutputFile(default_context, SourceDir("/C:/abs"), BuildDirType::GEN)
+          .value());
 #endif
 }
 
@@ -788,13 +781,12 @@
 
   Target a(&settings, Label(SourceDir("//foo/bar/"), "baz"));
   EXPECT_EQ("//out/Debug/obj/foo/bar/",
-            GetBuildDirForTargetAsSourceDir(&a, BuildDirType::OBJ).value());
-  EXPECT_EQ("obj/foo/bar/",
-            GetBuildDirForTargetAsOutputFile(&a, BuildDirType::OBJ).value());
+            GetSourceDir(a, BuildDirType::OBJ).value());
+  EXPECT_EQ("obj/foo/bar/", GetOutputFile(a, BuildDirType::OBJ).value());
+  EXPECT_EQ("obj/foo/bar/", GetBuildDir(a, BuildDirType::OBJ));
   EXPECT_EQ("//out/Debug/gen/foo/bar/",
-            GetBuildDirForTargetAsSourceDir(&a, BuildDirType::GEN).value());
-  EXPECT_EQ("gen/foo/bar/",
-            GetBuildDirForTargetAsOutputFile(&a, BuildDirType::GEN).value());
+            GetSourceDir(a, BuildDirType::GEN).value());
+  EXPECT_EQ("gen/foo/bar/", GetOutputFile(a, BuildDirType::GEN).value());
 }
 
 // Tests handling of output dirs when build dir is the same as the root.
@@ -805,21 +797,13 @@
 
   BuildDirContext context(&settings);
 
-  EXPECT_EQ(
-      "//",
-      GetBuildDirAsSourceDir(context, BuildDirType::TOOLCHAIN_ROOT).value());
-  EXPECT_EQ("//gen/",
-            GetBuildDirAsSourceDir(context, BuildDirType::GEN).value());
-  EXPECT_EQ("//obj/",
-            GetBuildDirAsSourceDir(context, BuildDirType::OBJ).value());
+  EXPECT_EQ("//", GetSourceDir(context, BuildDirType::TOOLCHAIN_ROOT).value());
+  EXPECT_EQ("//gen/", GetSourceDir(context, BuildDirType::GEN).value());
+  EXPECT_EQ("//obj/", GetSourceDir(context, BuildDirType::OBJ).value());
 
-  EXPECT_EQ(
-      "",
-      GetBuildDirAsOutputFile(context, BuildDirType::TOOLCHAIN_ROOT).value());
-  EXPECT_EQ("gen/",
-            GetBuildDirAsOutputFile(context, BuildDirType::GEN).value());
-  EXPECT_EQ("obj/",
-            GetBuildDirAsOutputFile(context, BuildDirType::OBJ).value());
+  EXPECT_EQ("", GetOutputFile(context, BuildDirType::TOOLCHAIN_ROOT).value());
+  EXPECT_EQ("gen/", GetOutputFile(context, BuildDirType::GEN).value());
+  EXPECT_EQ("obj/", GetOutputFile(context, BuildDirType::OBJ).value());
 }
 
 TEST(FilesystemUtils, ResolveRelativeTest) {
diff --git a/src/gn/function_get_label_info.cc b/src/gn/function_get_label_info.cc
index 04fa9a7..2d07617 100644
--- a/src/gn/function_get_label_info.cc
+++ b/src/gn/function_get_label_info.cc
@@ -105,23 +105,23 @@
     result.string_value() = DirectoryWithNoLastSlash(label.dir());
 
   } else if (what == "target_gen_dir") {
-    result.string_value() = DirectoryWithNoLastSlash(GetSubBuildDirAsSourceDir(
-        BuildDirContext(scope, label.GetToolchainLabel()), label.dir(),
-        BuildDirType::GEN));
+    result.string_value() = DirectoryWithNoLastSlash(
+        GetSourceDir(BuildDirContext(scope, label.GetToolchainLabel()),
+                     label.dir(), BuildDirType::GEN));
 
   } else if (what == "root_gen_dir") {
-    result.string_value() = DirectoryWithNoLastSlash(GetBuildDirAsSourceDir(
+    result.string_value() = DirectoryWithNoLastSlash(GetSourceDir(
         BuildDirContext(scope, label.GetToolchainLabel()), BuildDirType::GEN));
 
   } else if (what == "target_out_dir") {
-    result.string_value() = DirectoryWithNoLastSlash(GetSubBuildDirAsSourceDir(
-        BuildDirContext(scope, label.GetToolchainLabel()), label.dir(),
-        BuildDirType::OBJ));
+    result.string_value() = DirectoryWithNoLastSlash(
+        GetSourceDir(BuildDirContext(scope, label.GetToolchainLabel()),
+                     label.dir(), BuildDirType::OBJ));
 
   } else if (what == "root_out_dir") {
-    result.string_value() = DirectoryWithNoLastSlash(GetBuildDirAsSourceDir(
-        BuildDirContext(scope, label.GetToolchainLabel()),
-        BuildDirType::TOOLCHAIN_ROOT));
+    result.string_value() = DirectoryWithNoLastSlash(
+        GetSourceDir(BuildDirContext(scope, label.GetToolchainLabel()),
+                     BuildDirType::TOOLCHAIN_ROOT));
 
   } else if (what == "toolchain") {
     result.string_value() = label.GetToolchainLabel().GetUserVisibleName(false);
diff --git a/src/gn/function_get_path_info.cc b/src/gn/function_get_path_info.cc
index 00ed043..a263a8f 100644
--- a/src/gn/function_get_path_info.cc
+++ b/src/gn/function_get_path_info.cc
@@ -90,12 +90,12 @@
       return std::string(dir_incl_slash.substr(0, dir_incl_slash.size() - 1));
     }
     case WHAT_GEN_DIR: {
-      return DirectoryWithNoLastSlash(GetSubBuildDirAsSourceDir(
+      return DirectoryWithNoLastSlash(GetSourceDir(
           BuildDirContext(settings),
           DirForInput(settings, current_dir, input, err), BuildDirType::GEN));
     }
     case WHAT_OUT_DIR: {
-      return DirectoryWithNoLastSlash(GetSubBuildDirAsSourceDir(
+      return DirectoryWithNoLastSlash(GetSourceDir(
           BuildDirContext(settings),
           DirForInput(settings, current_dir, input, err), BuildDirType::OBJ));
     }
diff --git a/src/gn/ninja_binary_target_writer.cc b/src/gn/ninja_binary_target_writer.cc
index c4bbd2b..8c8d2a0 100644
--- a/src/gn/ninja_binary_target_writer.cc
+++ b/src/gn/ninja_binary_target_writer.cc
@@ -196,17 +196,13 @@
     // Make a phony target. We don't need to worry about an empty phony target,
     // as those would have been peeled off already.
     CHECK(!inputs.empty());
-    stamp_or_phony =
-        GetBuildDirForTargetAsOutputFile(target_, BuildDirType::PHONY);
-    stamp_or_phony.append(target_->label().name());
-    stamp_or_phony.append(".inputs");
+    stamp_or_phony = GetOutputFile(*target_, BuildDirType::PHONY,
+                                   target_->label().name(), ".inputs");
     tool = BuiltinTool::kBuiltinToolPhony;
   } else {
     // Make a stamp target.
-    stamp_or_phony =
-        GetBuildDirForTargetAsOutputFile(target_, BuildDirType::OBJ);
-    stamp_or_phony.append(target_->label().name());
-    stamp_or_phony.append(".inputs.stamp");
+    stamp_or_phony = GetOutputFile(*target_, BuildDirType::OBJ,
+                                   target_->label().name(), ".inputs.stamp");
     tool = GetNinjaRulePrefixForToolchain(settings_) +
            GeneralTool::kGeneralToolStamp;
   }
diff --git a/src/gn/ninja_create_bundle_target_writer.cc b/src/gn/ninja_create_bundle_target_writer.cc
index 5b9e733..c91d41c 100644
--- a/src/gn/ninja_create_bundle_target_writer.cc
+++ b/src/gn/ninja_create_bundle_target_writer.cc
@@ -347,15 +347,13 @@
   std::string tool;
   if (settings_->build_settings()->no_stamp_files()) {
     xcassets_input_stamp_or_phony =
-        GetBuildDirForTargetAsOutputFile(target_, BuildDirType::PHONY);
-    xcassets_input_stamp_or_phony.append(target_->label().name());
-    xcassets_input_stamp_or_phony.append(".xcassets.inputdeps");
+        GetOutputFile(*target_, BuildDirType::PHONY, target_->label().name(),
+                      ".xcassets.inputdeps");
     tool = BuiltinTool::kBuiltinToolPhony;
   } else {
     xcassets_input_stamp_or_phony =
-        GetBuildDirForTargetAsOutputFile(target_, BuildDirType::OBJ);
-    xcassets_input_stamp_or_phony.append(target_->label().name());
-    xcassets_input_stamp_or_phony.append(".xcassets.inputdeps.stamp");
+        GetOutputFile(*target_, BuildDirType::OBJ, target_->label().name(),
+                      ".xcassets.inputdeps.stamp");
     tool = GetNinjaRulePrefixForToolchain(settings_) +
            GeneralTool::kGeneralToolStamp;
   }
@@ -434,16 +432,14 @@
     // Make a phony target. We don't need to worry about an empty phony target,
     // as those would have been peeled off already.
     stamp_or_phony =
-        GetBuildDirForTargetAsOutputFile(target_, BuildDirType::PHONY);
-    stamp_or_phony.append(target_->label().name());
-    stamp_or_phony.append(".postprocessing.inputdeps");
+        GetOutputFile(*target_, BuildDirType::PHONY, target_->label().name(),
+                      ".postprocessing.inputdeps");
     tool = BuiltinTool::kBuiltinToolPhony;
   } else {
     // Make a stamp target.
     stamp_or_phony =
-        GetBuildDirForTargetAsOutputFile(target_, BuildDirType::OBJ);
-    stamp_or_phony.append(target_->label().name());
-    stamp_or_phony.append(".postprocessing.inputdeps.stamp");
+        GetOutputFile(*target_, BuildDirType::OBJ, target_->label().name(),
+                      ".postprocessing.inputdeps.stamp");
     tool = GetNinjaRulePrefixForToolchain(settings_) +
            GeneralTool::kGeneralToolStamp;
   }
diff --git a/src/gn/ninja_target_command_util.cc b/src/gn/ninja_target_command_util.cc
index 928851b..825d644 100644
--- a/src/gn/ninja_target_command_util.cc
+++ b/src/gn/ninja_target_command_util.cc
@@ -32,13 +32,8 @@
 OutputFile GetWindowsPCHFile(const Target* target, const char* tool_name) {
   // Use "obj/{dir}/{target_name}_{lang}.pch" which ends up
   // looking like "obj/chrome/browser/browser_cc.pch"
-  OutputFile ret = GetBuildDirForTargetAsOutputFile(target, BuildDirType::OBJ);
-  ret.append(target->label().name());
-  ret.append("_");
-  ret.append(GetPCHLangSuffixForToolType(tool_name));
-  ret.append(".pch");
-
-  return ret;
+  return GetOutputFile(*target, BuildDirType::OBJ, target->label().name(), "_",
+                       GetPCHLangSuffixForToolType(tool_name), ".pch");
 }
 
 void WriteOneFlag(RecursiveWriterConfig config,
diff --git a/src/gn/ninja_target_writer.cc b/src/gn/ninja_target_writer.cc
index 4f3e80d..d3d1eb1 100644
--- a/src/gn/ninja_target_writer.cc
+++ b/src/gn/ninja_target_writer.cc
@@ -650,17 +650,14 @@
   if (settings_->build_settings()->no_stamp_files()) {
     // Make a phony target. We don't need to worry about an empty phony target,
     // as we would return early if there were no inputs.
-    input_stamp_or_phony =
-        GetBuildDirForTargetAsOutputFile(target_, BuildDirType::PHONY);
-    input_stamp_or_phony.append(target_->label().name());
-    input_stamp_or_phony.append(".inputdeps");
+    input_stamp_or_phony = GetOutputFile(*target_, BuildDirType::PHONY,
+                                         target_->label().name(), ".inputdeps");
     tool = BuiltinTool::kBuiltinToolPhony;
   } else {
     // Make a stamp file.
     input_stamp_or_phony =
-        GetBuildDirForTargetAsOutputFile(target_, BuildDirType::OBJ);
-    input_stamp_or_phony.append(target_->label().name());
-    input_stamp_or_phony.append(".inputdeps.stamp");
+        GetOutputFile(*target_, BuildDirType::OBJ, target_->label().name(),
+                      ".inputdeps.stamp");
 
     tool = GetNinjaRulePrefixForToolchain(settings_) +
            GeneralTool::kGeneralToolStamp;
diff --git a/src/gn/ninja_utils.cc b/src/gn/ninja_utils.cc
index ae03c82..8a4ae8c 100644
--- a/src/gn/ninja_utils.cc
+++ b/src/gn/ninja_utils.cc
@@ -11,16 +11,15 @@
 #include "gn/target.h"
 
 SourceFile GetNinjaFileForTarget(const Target* target) {
-  return SourceFile(
-      GetBuildDirForTargetAsSourceDir(target, BuildDirType::OBJ).value() +
-      target->label().name() + ".ninja");
+  return SourceFile(GetSourceDir(*target, BuildDirType::OBJ).value() +
+                    target->label().name() + ".ninja");
 }
 
 SourceFile GetNinjaFileForToolchain(const Settings* settings) {
-  return SourceFile(GetBuildDirAsSourceDir(BuildDirContext(settings),
-                                           BuildDirType::TOOLCHAIN_ROOT)
-                        .value() +
-                    "toolchain.ninja");
+  return SourceFile(
+      GetSourceDir(BuildDirContext(settings), BuildDirType::TOOLCHAIN_ROOT)
+          .value() +
+      "toolchain.ninja");
 }
 
 std::string GetNinjaRulePrefixForToolchain(const Settings* settings) {
@@ -35,13 +34,10 @@
                                      const BuildSettings* build_settings) {
   OutputFile result;
   if (build_settings->no_stamp_files()) {
-    result = GetBuildDirForTargetAsOutputFile(target, BuildDirType::PHONY);
-    result.append(target->label().name());
-    result.append(".public_inputs");
+    return GetOutputFile(*target, BuildDirType::PHONY, target->label().name(),
+                         ".public_inputs");
   } else {
-    result = GetBuildDirForTargetAsOutputFile(target, BuildDirType::OBJ);
-    result.append(target->label().name());
-    result.append(".public_inputs.stamp");
+    return GetOutputFile(*target, BuildDirType::OBJ, target->label().name(),
+                         ".public_inputs.stamp");
   }
-  return result;
 }
diff --git a/src/gn/runtime_deps.cc b/src/gn/runtime_deps.cc
index 1eacee5..855365d 100644
--- a/src/gn/runtime_deps.cc
+++ b/src/gn/runtime_deps.cc
@@ -193,9 +193,8 @@
       // is either a phony alias or was elided entirely (due to lack of real
       // inputs). In either case, there is no file to add an additional
       // extension to, so we should compute our own name in the OBJ BuildDir.
-      output_file = GetBuildDirForTargetAsOutputFile(target, BuildDirType::OBJ);
-      output_file->append(target->GetComputedOutputName());
-      output_file->append(extension);
+      output_file = GetOutputFile(*target, BuildDirType::OBJ,
+                                  target->GetComputedOutputName(), extension);
     }
     if (output_file)
       files_to_write->emplace_back(*output_file, target);
diff --git a/src/gn/rust_project_writer.cc b/src/gn/rust_project_writer.cc
index eb2d4e2..17e92d4 100644
--- a/src/gn/rust_project_writer.cc
+++ b/src/gn/rust_project_writer.cc
@@ -223,8 +223,7 @@
   // Unfortunately this doesn't help if the buildfile author places unrelated
   // generated sources for multiple crates in the same directory, but that's
   // impossible to solve anyway.
-  auto gen_dir = GetBuildDirForTargetAsSourceDir(target, BuildDirType::GEN)
-                     .Resolve(source_root);
+  auto gen_dir = GetSourceDir(*target, BuildDirType::GEN).Resolve(source_root);
   std::vector<SourceDir> include_dirs;
   for (const SourceFile& source : target->sources()) {
     if (gen_dir.IsParent(source.Resolve(source_root))) {
diff --git a/src/gn/scope_per_file_provider.cc b/src/gn/scope_per_file_provider.cc
index 8d706b6..fa7a1d0 100644
--- a/src/gn/scope_per_file_provider.cc
+++ b/src/gn/scope_per_file_provider.cc
@@ -103,8 +103,8 @@
 const Value* ScopePerFileProvider::GetRootGenDir() {
   if (!root_gen_dir_) {
     root_gen_dir_ = std::make_unique<Value>(
-        nullptr, DirectoryWithNoLastSlash(GetBuildDirAsSourceDir(
-                     BuildDirContext(scope_), BuildDirType::GEN)));
+        nullptr, DirectoryWithNoLastSlash(
+                     GetSourceDir(BuildDirContext(scope_), BuildDirType::GEN)));
   }
   return root_gen_dir_.get();
 }
@@ -112,8 +112,8 @@
 const Value* ScopePerFileProvider::GetRootOutDir() {
   if (!root_out_dir_) {
     root_out_dir_ = std::make_unique<Value>(
-        nullptr, DirectoryWithNoLastSlash(GetScopeCurrentBuildDirAsSourceDir(
-                     scope_, BuildDirType::TOOLCHAIN_ROOT)));
+        nullptr, DirectoryWithNoLastSlash(
+                     GetSourceDir(*scope_, BuildDirType::TOOLCHAIN_ROOT)));
   }
   return root_out_dir_.get();
 }
@@ -121,8 +121,8 @@
 const Value* ScopePerFileProvider::GetTargetGenDir() {
   if (!target_gen_dir_) {
     target_gen_dir_ = std::make_unique<Value>(
-        nullptr, DirectoryWithNoLastSlash(GetScopeCurrentBuildDirAsSourceDir(
-                     scope_, BuildDirType::GEN)));
+        nullptr,
+        DirectoryWithNoLastSlash(GetSourceDir(*scope_, BuildDirType::GEN)));
   }
   return target_gen_dir_.get();
 }
@@ -130,8 +130,8 @@
 const Value* ScopePerFileProvider::GetTargetOutDir() {
   if (!target_out_dir_) {
     target_out_dir_ = std::make_unique<Value>(
-        nullptr, DirectoryWithNoLastSlash(GetScopeCurrentBuildDirAsSourceDir(
-                     scope_, BuildDirType::OBJ)));
+        nullptr,
+        DirectoryWithNoLastSlash(GetSourceDir(*scope_, BuildDirType::OBJ)));
   }
   return target_out_dir_.get();
 }
diff --git a/src/gn/substitution_writer.cc b/src/gn/substitution_writer.cc
index 76a5a5c..52d0c81 100644
--- a/src/gn/substitution_writer.cc
+++ b/src/gn/substitution_writer.cc
@@ -368,10 +368,10 @@
                       SourceDir("//"),
                       settings->build_settings()->root_path_utf8());
   } else if (type == &SubstitutionSourceGenDir) {
-    to_rebase = DirectoryWithNoLastSlash(GetSubBuildDirAsSourceDir(
+    to_rebase = DirectoryWithNoLastSlash(GetSourceDir(
         BuildDirContext(settings), source.GetDir(), BuildDirType::GEN));
   } else if (type == &SubstitutionSourceOutDir) {
-    to_rebase = DirectoryWithNoLastSlash(GetSubBuildDirAsSourceDir(
+    to_rebase = DirectoryWithNoLastSlash(GetSourceDir(
         BuildDirContext(settings), source.GetDir(), BuildDirType::OBJ));
   } else if (type == &SubstitutionSourceTargetRelative) {
     if (target) {
@@ -442,20 +442,14 @@
     *result = target->label().GetUserVisibleName(false);
   } else if (type == &SubstitutionRootGenDir) {
     SetDirOrDotWithNoSlash(
-        GetBuildDirAsOutputFile(BuildDirContext(target), BuildDirType::GEN)
-            .value(),
-        result);
+        GetBuildDir(BuildDirContext(target), BuildDirType::GEN), result);
   } else if (type == &SubstitutionRootOutDir) {
     SetDirOrDotWithNoSlash(
         target->settings()->toolchain_output_subdir().value(), result);
   } else if (type == &SubstitutionTargetGenDir) {
-    SetDirOrDotWithNoSlash(
-        GetBuildDirForTargetAsOutputFile(target, BuildDirType::GEN).value(),
-        result);
+    SetDirOrDotWithNoSlash(GetBuildDir(*target, BuildDirType::GEN), result);
   } else if (type == &SubstitutionTargetOutDir) {
-    SetDirOrDotWithNoSlash(
-        GetBuildDirForTargetAsOutputFile(target, BuildDirType::OBJ).value(),
-        result);
+    SetDirOrDotWithNoSlash(GetBuildDir(*target, BuildDirType::OBJ), result);
   } else if (type == &SubstitutionTargetOutputName) {
     *result = target->GetComputedOutputName();
   } else {
diff --git a/src/gn/target.cc b/src/gn/target.cc
index 0048341..c6cef20 100644
--- a/src/gn/target.cc
+++ b/src/gn/target.cc
@@ -979,8 +979,7 @@
       if (settings()->build_settings()->no_stamp_files()) {
         if (HasRealInputs()) {
           dependency_output_alias_ =
-              GetBuildDirForTargetAsOutputFile(this, BuildDirType::PHONY);
-          dependency_output_alias_.append(label().name());
+              GetOutputFile(*this, BuildDirType::PHONY, label().name());
         }
       } else {
         // These don't get linked to and use stamps which should be the first
@@ -989,9 +988,7 @@
         // affect the stamp file name: it is always based on the original target
         // name.
         dependency_output_file_ =
-            GetBuildDirForTargetAsOutputFile(this, BuildDirType::OBJ);
-        dependency_output_file_.append(label().name());
-        dependency_output_file_.append(".stamp");
+            GetOutputFile(*this, BuildDirType::OBJ, label().name(), ".stamp");
       }
       break;
     }
@@ -1409,8 +1406,7 @@
 void Target::set_module_type(ModuleType type) {
   module_type_ = type;
   if (module_type_.test(MODULEMAP_IS_GENERATED)) {
-    auto source_dir = GetBuildDirForTargetAsOutputFile(this, BuildDirType::GEN)
-                          .AsSourceDir(settings()->build_settings());
+    auto source_dir = GetSourceDir(*this, BuildDirType::GEN);
 
     generated_modulemap_file_ = SourceFile(base::StringPrintf(
         "%s%s.modulemap", source_dir.value().c_str(), label().name().c_str()));
diff --git a/src/gn/visual_studio_writer.cc b/src/gn/visual_studio_writer.cc
index d660be8..13e54d4 100644
--- a/src/gn/visual_studio_writer.cc
+++ b/src/gn/visual_studio_writer.cc
@@ -413,7 +413,7 @@
   }
 
   SourceFile target_file =
-      GetBuildDirForTargetAsSourceDir(target, BuildDirType::OBJ)
+      GetSourceDir(*target, BuildDirType::OBJ)
           .ResolveRelativeFile(Value(nullptr, project_name + ".vcxproj"), err);
   if (target_file.is_null())
     return false;
@@ -459,9 +459,9 @@
     const std::string& ninja_executable,
     SourceFileCompileTypePairs* source_types,
     Err* err) {
-  PathOutput path_output(
-      GetBuildDirForTargetAsSourceDir(target, BuildDirType::OBJ),
-      build_settings_->root_path_utf8(), EscapingMode::ESCAPE_NONE);
+  PathOutput path_output(GetSourceDir(*target, BuildDirType::OBJ),
+                         build_settings_->root_path_utf8(),
+                         EscapingMode::ESCAPE_NONE);
 
   out << "<?xml version=\"1.0\" encoding=\"utf-8\"?>" << std::endl;
   XmlElementWriter project(
@@ -704,9 +704,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(
-        GetBuildDirForTargetAsSourceDir(target, BuildDirType::OBJ),
-        build_settings_->root_path_utf8(), EscapingMode::ESCAPE_NONE);
+    PathOutput file_path_output(GetSourceDir(*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);