[Mac/GN] Treat create_bundle as a leaf for `gn desc runtime_deps`.

BUG=611414
R=brettw@chromium.org,dpranke@chromium.org,sdefresne@chromium.org

Review-Url: https://codereview.chromium.org/1982463002
Cr-Original-Commit-Position: refs/heads/master@{#394840}
Cr-Mirrored-From: https://chromium.googlesource.com/chromium/src
Cr-Mirrored-Commit: 903e2b85c71675f83767ac9b1687087382b49f39
diff --git a/tools/gn/bundle_data.cc b/tools/gn/bundle_data.cc
index f211c1f..e182365 100644
--- a/tools/gn/bundle_data.cc
+++ b/tools/gn/bundle_data.cc
@@ -129,3 +129,8 @@
   }
   return SourceFile(root_dir().value());
 }
+
+SourceDir BundleData::GetBundleRootDirOutputAsDir(
+    const Settings* settings) const {
+  return SourceDir(GetBundleRootDirOutput(settings).value());
+}
diff --git a/tools/gn/bundle_data.h b/tools/gn/bundle_data.h
index 9a2ea07..3a1ce95 100644
--- a/tools/gn/bundle_data.h
+++ b/tools/gn/bundle_data.h
@@ -77,6 +77,9 @@
   // of its contents.
   SourceFile GetBundleRootDirOutput(const Settings* settings) const;
 
+  // Performs GetBundleRootDirOutput but returns the result as a directory.
+  SourceDir GetBundleRootDirOutputAsDir(const Settings* settings) const;
+
   // Returns the list of inputs for the compilation of the asset catalog.
   SourceFiles& asset_catalog_sources() { return asset_catalog_sources_; }
   const SourceFiles& asset_catalog_sources() const {
diff --git a/tools/gn/runtime_deps.cc b/tools/gn/runtime_deps.cc
index 17b9c07..5352de3 100644
--- a/tools/gn/runtime_deps.cc
+++ b/tools/gn/runtime_deps.cc
@@ -104,6 +104,15 @@
       AddIfNew(output_file.value(), target, deps, found_files);
   }
 
+  // Do not recurse into bundle targets. A bundle's dependencies should be
+  // copied into the bundle itself for run-time access.
+  if (target->output_type() == Target::CREATE_BUNDLE) {
+    SourceDir bundle_root_dir =
+        target->bundle_data().GetBundleRootDirOutputAsDir(target->settings());
+    AddIfNew(bundle_root_dir.value(), target, deps, found_files);
+    return;
+  }
+
   // Non-data dependencies (both public and private).
   for (const auto& dep_pair : target->GetDeps(Target::DEPS_LINKED)) {
     if (dep_pair.ptr->output_type() == Target::EXECUTABLE)
diff --git a/tools/gn/runtime_deps_unittest.cc b/tools/gn/runtime_deps_unittest.cc
index 6c2013d..8b68bd8 100644
--- a/tools/gn/runtime_deps_unittest.cc
+++ b/tools/gn/runtime_deps_unittest.cc
@@ -258,6 +258,83 @@
   EXPECT_TRUE(MakePair("../../dep.data", &dep) == result[0]);
 }
 
+// Tests that the search for dependencies terminates at a bundle target,
+// ignoring any shared libraries or loadable modules that get copied into the
+// bundle.
+TEST(RuntimeDeps, CreateBundle) {
+  TestWithScope setup;
+  Err err;
+
+  // Dependency hierarchy:
+  // main(exe) -> dep(bundle) -> dep(shared_library) -> dep(source set)
+  //                          -> dep(bundle_data) -> dep(loadable_module)
+
+  const SourceDir source_dir("//");
+  const std::string& build_dir = setup.build_settings()->build_dir().value();
+
+  Target loadable_module(setup.settings(),
+                         Label(source_dir, "loadable_module"));
+  InitTargetWithType(setup, &loadable_module, Target::LOADABLE_MODULE);
+  ASSERT_TRUE(loadable_module.OnResolved(&err));
+
+  Target module_data(setup.settings(), Label(source_dir, "module_data"));
+  InitTargetWithType(setup, &module_data, Target::BUNDLE_DATA);
+  module_data.private_deps().push_back(LabelTargetPair(&loadable_module));
+  module_data.bundle_data().file_rules().push_back(BundleFileRule(
+      std::vector<SourceFile>{SourceFile(build_dir + "loadable_module.so")},
+      SubstitutionPattern::MakeForTest("{{bundle_resources_dir}}")));
+  ASSERT_TRUE(module_data.OnResolved(&err));
+
+  Target source_set(setup.settings(), Label(source_dir, "sources"));
+  InitTargetWithType(setup, &source_set, Target::SOURCE_SET);
+  source_set.sources().push_back(SourceFile(source_dir.value() + "foo.cc"));
+  ASSERT_TRUE(source_set.OnResolved(&err));
+
+  Target dylib(setup.settings(), Label(source_dir, "dylib"));
+  dylib.set_output_prefix_override(true);
+  dylib.set_output_extension("");
+  dylib.set_output_name("Bundle");
+  InitTargetWithType(setup, &dylib, Target::SHARED_LIBRARY);
+  dylib.private_deps().push_back(LabelTargetPair(&source_set));
+  ASSERT_TRUE(dylib.OnResolved(&err));
+
+  Target dylib_data(setup.settings(), Label(source_dir, "dylib_data"));
+  InitTargetWithType(setup, &dylib_data, Target::BUNDLE_DATA);
+  dylib_data.private_deps().push_back(LabelTargetPair(&dylib));
+  dylib_data.bundle_data().file_rules().push_back(BundleFileRule(
+      std::vector<SourceFile>{SourceFile(build_dir + "dylib")},
+      SubstitutionPattern::MakeForTest("{{bundle_executable_dir}}")));
+  ASSERT_TRUE(dylib_data.OnResolved(&err));
+
+  Target bundle(setup.settings(), Label(source_dir, "bundle"));
+  InitTargetWithType(setup, &bundle, Target::CREATE_BUNDLE);
+  const std::string root_dir(build_dir + "Bundle.framework/Versions/A/");
+  bundle.bundle_data().root_dir() = SourceDir(root_dir);
+  bundle.bundle_data().resources_dir() = SourceDir(root_dir + "Resources");
+  bundle.bundle_data().executable_dir() = SourceDir(root_dir + "MacOS");
+  bundle.private_deps().push_back(LabelTargetPair(&dylib_data));
+  bundle.private_deps().push_back(LabelTargetPair(&module_data));
+  ASSERT_TRUE(bundle.OnResolved(&err));
+
+  Target main(setup.settings(), Label(source_dir, "main"));
+  InitTargetWithType(setup, &main, Target::EXECUTABLE);
+  main.data_deps().push_back(LabelTargetPair(&bundle));
+  ASSERT_TRUE(main.OnResolved(&err));
+
+  std::vector<std::pair<OutputFile, const Target*>> result =
+      ComputeRuntimeDeps(&main);
+
+  // The result should have deps of main, datadep, final_in.dat
+  ASSERT_EQ(2u, result.size()) << GetVectorDescription(result);
+
+  // The first one should always be the main exe.
+  EXPECT_EQ(MakePair("./main", &main), result[0]);
+
+  // The second one should be the framework bundle, not its included
+  // loadable_module or its intermediate shared_library.
+  EXPECT_EQ(MakePair("Bundle.framework/", &bundle), result[1]);
+}
+
 // Tests that a dependency duplicated in regular and data deps is processed
 // as a data dep.
 TEST(RuntimeDeps, Dupe) {