Add new build configuration variable to add an extension to build file names.

Using such an extension would result in a name of the form "BUILD.$extension.gn".
This is useful notably for code that is expected to get built in two different GN builds.

Change-Id: I1a8fce9ded95d1300d0bf2a23e983301acb9f9d7
Reviewed-on: https://gn-review.googlesource.com/c/gn/+/8500
Commit-Queue: Brett Wilson <brettw@chromium.org>
Reviewed-by: Brett Wilson <brettw@chromium.org>
diff --git a/src/gn/analyzer_unittest.cc b/src/gn/analyzer_unittest.cc
index 4242fee..d2e89a9 100644
--- a/src/gn/analyzer_unittest.cc
+++ b/src/gn/analyzer_unittest.cc
@@ -35,6 +35,9 @@
   const Settings* GetToolchainSettings(const Label& label) const override {
     return nullptr;
   }
+  SourceFile BuildFileForLabel(const Label& label) const override {
+    return SourceFile(label.dir().value() + "BUILD.gn");
+  }
 
  private:
   ~MockLoader() override = default;
diff --git a/src/gn/builder_unittest.cc b/src/gn/builder_unittest.cc
index 8b844e3..b0f48f0 100644
--- a/src/gn/builder_unittest.cc
+++ b/src/gn/builder_unittest.cc
@@ -27,6 +27,9 @@
   const Settings* GetToolchainSettings(const Label& label) const override {
     return nullptr;
   }
+  SourceFile BuildFileForLabel(const Label& label) const override {
+    return SourceFile(label.dir().value() + "BUILD.gn");
+  }
 
   bool HasLoadedNone() const { return files_.empty(); }
 
diff --git a/src/gn/loader.cc b/src/gn/loader.cc
index d6cf5fe..547e919 100644
--- a/src/gn/loader.cc
+++ b/src/gn/loader.cc
@@ -85,11 +85,6 @@
   Load(BuildFileForLabel(label), origin, label.GetToolchainLabel());
 }
 
-// static
-SourceFile Loader::BuildFileForLabel(const Label& label) {
-  return SourceFile(label.dir().value() + "BUILD.gn");
-}
-
 // -----------------------------------------------------------------------------
 
 LoaderImpl::LoaderImpl(const BuildSettings* build_settings)
@@ -202,6 +197,11 @@
   return &found_toolchain->second->settings;
 }
 
+SourceFile LoaderImpl::BuildFileForLabel(const Label& label) const {
+  return SourceFile(
+      label.dir().value() + "BUILD" + build_file_extension_ + ".gn");
+}
+
 void LoaderImpl::ScheduleLoadFile(const Settings* settings,
                                   const LocationRange& origin,
                                   const SourceFile& file) {
diff --git a/src/gn/loader.h b/src/gn/loader.h
index c4a2dfe..8f95bb8 100644
--- a/src/gn/loader.h
+++ b/src/gn/loader.h
@@ -50,13 +50,13 @@
   // false if we haven't processed this toolchain yet.
   virtual const Settings* GetToolchainSettings(const Label& label) const = 0;
 
+  // Returns the build file that the given label references.
+  virtual SourceFile BuildFileForLabel(const Label& label) const = 0;
+
   // Helper function that extracts the file and toolchain name from the given
   // label, and calls Load().
   void Load(const Label& label, const LocationRange& origin);
 
-  // Returns the build file that the given label references.
-  static SourceFile BuildFileForLabel(const Label& label);
-
   // When processing the default build config, we want to capture the argument
   // of set_default_build_config. The implementation of that function uses this
   // constant as a property key to get the Label* out of the scope where the
@@ -87,6 +87,7 @@
   void ToolchainLoaded(const Toolchain* toolchain) override;
   Label GetDefaultToolchain() const override;
   const Settings* GetToolchainSettings(const Label& label) const override;
+  SourceFile BuildFileForLabel(const Label& label) const override;
 
   // Sets the task runner corresponding to the main thread. By default this
   // class will use the thread active during construction, but there is not
@@ -105,6 +106,12 @@
     async_load_file_ = std::move(cb);
   }
 
+  // Sets the additional extension for build files in this build.
+  // The resulting file name will be "BUILD.<extension>.gn".
+  void set_build_file_extension(std::string extension) {
+    build_file_extension_ = "." + extension;
+  }
+
   const Label& default_toolchain_label() const {
     return default_toolchain_label_;
   }
@@ -173,6 +180,8 @@
   // Records for the build config file loads.
   using ToolchainRecordMap = std::map<Label, std::unique_ptr<ToolchainRecord>>;
   ToolchainRecordMap toolchain_records_;
+
+  std::string build_file_extension_;
 };
 
 #endif  // TOOLS_GN_LOADER_H_
diff --git a/src/gn/loader_unittest.cc b/src/gn/loader_unittest.cc
index 3896b59..ce14894 100644
--- a/src/gn/loader_unittest.cc
+++ b/src/gn/loader_unittest.cc
@@ -335,3 +335,59 @@
 
   EXPECT_FALSE(scheduler().is_failed());
 }
+
+TEST_F(LoaderTest, NonDefaultBuildFileName) {
+  std::string new_name = "BUILD.more.gn";
+
+  SourceFile build_config("//build/config/BUILDCONFIG.gn");
+  build_settings_.set_build_config_file(build_config);
+
+  scoped_refptr<LoaderImpl> loader(new LoaderImpl(&build_settings_));
+  loader->set_build_file_extension("more");
+
+  // The default toolchain needs to be set by the build config file.
+  mock_ifm_.AddCannedResponse(build_config,
+                              "set_default_toolchain(\"//tc:tc\")");
+
+  loader->set_async_load_file(mock_ifm_.GetAsyncCallback());
+
+  // Request the root build file be loaded. This should kick off the default
+  // build config loading.
+  SourceFile root_build("//" + new_name);
+  loader->Load(root_build, LocationRange(), Label());
+  EXPECT_TRUE(mock_ifm_.HasOnePending(build_config));
+
+  // Completing the build config load should kick off the root build file load.
+  mock_ifm_.IssueAllPending();
+  MsgLoop::Current()->RunUntilIdleForTesting();
+  EXPECT_TRUE(mock_ifm_.HasOnePending(root_build));
+
+  // Load the root build file.
+  mock_ifm_.IssueAllPending();
+  MsgLoop::Current()->RunUntilIdleForTesting();
+
+  // Schedule some other file to load in another toolchain.
+  Label second_tc(SourceDir("//tc2/"), "tc2");
+  SourceFile second_file("//foo/" + new_name);
+  loader->Load(second_file, LocationRange(), second_tc);
+  EXPECT_TRUE(mock_ifm_.HasOnePending(SourceFile("//tc2/" + new_name)));
+
+  // Running the toolchain file should schedule the build config file to load
+  // for that toolchain.
+  mock_ifm_.IssueAllPending();
+  MsgLoop::Current()->RunUntilIdleForTesting();
+
+  // We have to tell it we have a toolchain definition now (normally the
+  // builder would do this).
+  const Settings* default_settings = loader->GetToolchainSettings(Label());
+  Toolchain second_tc_object(default_settings, second_tc);
+  loader->ToolchainLoaded(&second_tc_object);
+  EXPECT_TRUE(mock_ifm_.HasOnePending(build_config));
+
+  // Running the build config file should make our second file pending.
+  mock_ifm_.IssueAllPending();
+  MsgLoop::Current()->RunUntilIdleForTesting();
+  EXPECT_TRUE(mock_ifm_.HasOnePending(second_file));
+
+  EXPECT_FALSE(scheduler().is_failed());
+}
diff --git a/src/gn/qt_creator_writer.cc b/src/gn/qt_creator_writer.cc
index 5300821..f5c997a 100644
--- a/src/gn/qt_creator_writer.cc
+++ b/src/gn/qt_creator_writer.cc
@@ -222,7 +222,7 @@
 void QtCreatorWriter::HandleTarget(const Target* target) {
   using namespace QtCreatorWriterUtils;
 
-  SourceFile build_file = Loader::BuildFileForLabel(target->label());
+  SourceFile build_file = builder_.loader()->BuildFileForLabel(target->label());
   sources_.insert(FilePathToUTF8(build_settings_->GetFullPath(build_file)));
   AddToSources(target->settings()->import_manager().GetImportedFiles());
 
diff --git a/src/gn/setup.cc b/src/gn/setup.cc
index 75638cf..4026d1a 100644
--- a/src/gn/setup.cc
+++ b/src/gn/setup.cc
@@ -107,7 +107,8 @@
   root [optional]
       Label of the root build target. The GN build will start by loading the
       build file containing this target name. This defaults to "//:" which will
-      cause the file //BUILD.gn to be loaded.
+      cause the file //BUILD.gn to be loaded. Note that build_file_extension
+      applies to the default case as well.
 
   script_executable [optional]
       Path to specific Python executable or other interpreter to use in
@@ -138,6 +139,13 @@
       This is intended to be used when subprojects declare arguments with
       default values that need to be changed for whatever reason.
 
+  build_file_extension [optional]
+      If set to a non-empty string, this is added to the name of all build files
+      to load.
+      GN will look for build files named "BUILD.$build_file_extension.gn".
+      This is intended to be used during migrations or other situations where
+      there are two independent GN builds in the same directories.
+
 Example .gn file contents
 
   buildconfig = "//build/config/BUILDCONFIG.gn"
@@ -313,7 +321,6 @@
     : build_settings_(),
       loader_(new LoaderImpl(&build_settings_)),
       builder_(loader_.get()),
-      root_build_file_("//BUILD.gn"),
       dotfile_settings_(&build_settings_, std::string()),
       dotfile_scope_(&dotfile_settings_) {
   dotfile_settings_.set_toolchain_label(Label());
@@ -782,6 +789,26 @@
         SourceDir(secondary_value->string_value()));
   }
 
+  // Build file names.
+  const Value* build_file_extension_value =
+      dotfile_scope_.GetValue("build_file_extension", true);
+  if (build_file_extension_value) {
+    if (!build_file_extension_value->VerifyTypeIs(Value::STRING, &err)) {
+      err.PrintToStdout();
+      return false;
+    }
+
+    std::string extension = build_file_extension_value->string_value();
+    auto normalized_extension = UTF8ToFilePath(extension).value();
+    if (normalized_extension.find_first_of(base::FilePath::kSeparators) !=
+        base::FilePath::StringType::npos) {
+      Err(Location(), "Build file extension '" + extension + "' cannot " +
+          "contain a path separator").PrintToStdout();
+      return false;
+    }
+    loader_->set_build_file_extension(extension);
+  }
+
   // Root build file.
   const Value* root_value = dotfile_scope_.GetValue("root", true);
   if (root_value) {
@@ -796,9 +823,10 @@
       err.PrintToStdout();
       return false;
     }
-
-    root_build_file_ = Loader::BuildFileForLabel(root_target_label);
   }
+  // Set the root build file here in order to take into account the values of
+  // "build_file_extension" and "root".
+  root_build_file_ = loader_->BuildFileForLabel(root_target_label);
   build_settings_.SetRootTargetLabel(root_target_label);
 
   // Build config file.
diff --git a/src/gn/setup_unittest.cc b/src/gn/setup_unittest.cc
index 723306e..eda66ab 100644
--- a/src/gn/setup_unittest.cc
+++ b/src/gn/setup_unittest.cc
@@ -5,6 +5,7 @@
 #include "gn/setup.h"
 
 #include "base/command_line.h"
+#include "base/files/file_path.h"
 #include "base/files/file_util.h"
 #include "base/files/scoped_temp_dir.h"
 #include "gn/filesystem_utils.h"
@@ -43,3 +44,37 @@
   ASSERT_EQ(1u, gen_deps.size());
   EXPECT_EQ(gen_deps[0], base::MakeAbsoluteFilePath(dot_gn_name));
 }
+
+static void RunExtensionCheckTest(std::string extension, bool success) {
+  base::CommandLine cmdline(base::CommandLine::NO_PROGRAM);
+
+  // Create a temp directory containing a .gn file and a BUILDCONFIG.gn file,
+  // pass it as --root.
+  base::ScopedTempDir in_temp_dir;
+  ASSERT_TRUE(in_temp_dir.CreateUniqueTempDir());
+  base::FilePath in_path = in_temp_dir.GetPath();
+  base::FilePath dot_gn_name = in_path.Append(FILE_PATH_LITERAL(".gn"));
+  WriteFile(dot_gn_name, "buildconfig = \"//BUILDCONFIG.gn\"\n\
+      build_file_extension = \"" + extension + "\"");
+  WriteFile(in_path.Append(FILE_PATH_LITERAL("BUILDCONFIG.gn")), "");
+  cmdline.AppendSwitchASCII(switches::kRoot, FilePathToUTF8(in_path));
+
+  // Create another temp dir for writing the generated files to.
+  base::ScopedTempDir build_temp_dir;
+  ASSERT_TRUE(build_temp_dir.CreateUniqueTempDir());
+
+  // Run setup and check that its status.
+  Setup setup;
+  EXPECT_EQ(success,
+      setup.DoSetup(FilePathToUTF8(build_temp_dir.GetPath()), true, cmdline));
+}
+
+TEST_F(SetupTest, NoSeparatorInExtension) {
+  RunExtensionCheckTest(
+      "hello" + std::string(1, base::FilePath::kSeparators[0]) + "world",
+      false);
+}
+
+TEST_F(SetupTest, Extension) {
+  RunExtensionCheckTest("yay", true);
+}
diff --git a/src/gn/target.cc b/src/gn/target.cc
index 1b26c06..97e3d37 100644
--- a/src/gn/target.cc
+++ b/src/gn/target.cc
@@ -223,6 +223,9 @@
 
   6. When all targets are resolved, write out the root build.ninja file.
 
+  Note that the BUILD.gn file name may be modulated by .gn arguments such as
+  build_file_extension.
+
 Executing target definitions and templates
 
   Build files are loaded in parallel. This means it is impossible to
diff --git a/src/gn/xcode_writer.cc b/src/gn/xcode_writer.cc
index 2593514..34a3170 100644
--- a/src/gn/xcode_writer.cc
+++ b/src/gn/xcode_writer.cc
@@ -528,7 +528,7 @@
     if (!item->AsConfig() && !item->AsTarget() && !item->AsToolchain())
       continue;
 
-    const SourceFile build = Loader::BuildFileForLabel(item->label());
+    const SourceFile build = builder.loader()->BuildFileForLabel(item->label());
     if (ShouldIncludeFileInProject(build))
       sources.insert(build);