Add 'gen_deps' variable

This will allow targets to ensure that other targets in non-default
toolchains will get generated even when they're not transitively
included in the build graph.

Change-Id: I5369f2440c02d3b3b8ef9bee598a37c95b8bfbeb
Reviewed-on: https://gn-review.googlesource.com/c/gn/+/12280
Reviewed-by: Brett Wilson <brettw@chromium.org>
Commit-Queue: Brett Wilson <brettw@chromium.org>
diff --git a/misc/vim/syntax/gn.vim b/misc/vim/syntax/gn.vim
index 79faa70..851cec6 100644
--- a/misc/vim/syntax/gn.vim
+++ b/misc/vim/syntax/gn.vim
@@ -51,7 +51,7 @@
 syn keyword     gnVariable lib_dirs libs output_extension output_name outputs
 syn keyword     gnVariable public public_configs public_deps scripte sources
 syn keyword     gnVariable testonly visibility contents output_conversion rebase
-syn keyword     gnVariable walk_keys
+syn keyword     gnVariable walk_keys gen_deps
 hi def link     gnVariable          Keyword
 
 " Strings
diff --git a/src/gn/builder.cc b/src/gn/builder.cc
index d31eaa4..d1ba662 100644
--- a/src/gn/builder.cc
+++ b/src/gn/builder.cc
@@ -238,6 +238,7 @@
       !AddDeps(record, target->configs().vector(), err) ||
       !AddDeps(record, target->all_dependent_configs(), err) ||
       !AddDeps(record, target->public_configs(), err) ||
+      !AddGenDeps(record, target->gen_deps(), err) ||
       !AddActionValuesDep(record, target->action_values(), err) ||
       !AddToolchainDep(record, target, err))
     return false;
@@ -404,6 +405,19 @@
   return true;
 }
 
+bool Builder::AddGenDeps(BuilderRecord* record,
+                         const LabelTargetVector& targets,
+                         Err* err) {
+  for (const auto& target : targets) {
+    BuilderRecord* dep_record = GetOrCreateRecordOfType(
+        target.label, target.origin, BuilderRecord::ITEM_TARGET, err);
+    if (!dep_record)
+      return false;
+    record->AddGenDep(dep_record);
+  }
+  return true;
+}
+
 bool Builder::AddActionValuesDep(BuilderRecord* record,
                                  const ActionValues& action_values,
                                  Err* err) {
@@ -435,6 +449,9 @@
 
 void Builder::RecursiveSetShouldGenerate(BuilderRecord* record, bool force) {
   if (!record->should_generate()) {
+    // This function can encounter cycles because gen_deps aren't a DAG. Setting
+    // the should_generate flag before iterating avoids infinite recursion in
+    // that case.
     record->set_should_generate(true);
 
     // This may have caused the item to go into "resolved and generated" state.
diff --git a/src/gn/builder.h b/src/gn/builder.h
index 8f328fa..bd62c18 100644
--- a/src/gn/builder.h
+++ b/src/gn/builder.h
@@ -94,6 +94,9 @@
   bool AddDeps(BuilderRecord* record,
                const LabelTargetVector& targets,
                Err* err);
+  bool AddGenDeps(BuilderRecord* record,
+                  const LabelTargetVector& targets,
+                  Err* err);
   bool AddActionValuesDep(BuilderRecord* record,
                           const ActionValues& action_values,
                           Err* err);
diff --git a/src/gn/builder_record.cc b/src/gn/builder_record.cc
index cbe8dae..6325cdc 100644
--- a/src/gn/builder_record.cc
+++ b/src/gn/builder_record.cc
@@ -58,6 +58,12 @@
   return ITEM_UNKNOWN;
 }
 
+void BuilderRecord::AddGenDep(BuilderRecord* record) {
+  // Records don't have to wait on resolution of their gen deps, since all they
+  // need to do is propagate should_generate to them.
+  all_deps_.insert(record);
+}
+
 void BuilderRecord::AddDep(BuilderRecord* record) {
   all_deps_.insert(record);
   if (!record->resolved()) {
diff --git a/src/gn/builder_record.h b/src/gn/builder_record.h
index 989c239..439893b 100644
--- a/src/gn/builder_record.h
+++ b/src/gn/builder_record.h
@@ -74,7 +74,8 @@
 
   bool can_resolve() const { return item_ && unresolved_deps_.empty(); }
 
-  // All records this one is depending on.
+  // All records this one is depending on. Note that this includes gen_deps for
+  // targets, which can have cycles.
   BuilderRecordSet& all_deps() { return all_deps_; }
   const BuilderRecordSet& all_deps() const { return all_deps_; }
 
@@ -89,6 +90,7 @@
     return waiting_on_resolution_;
   }
 
+  void AddGenDep(BuilderRecord* record);
   void AddDep(BuilderRecord* record);
 
  private:
diff --git a/src/gn/builder_unittest.cc b/src/gn/builder_unittest.cc
index b0f48f0..81b9f89 100644
--- a/src/gn/builder_unittest.cc
+++ b/src/gn/builder_unittest.cc
@@ -2,10 +2,13 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include <algorithm>
+
 #include "gn/builder.h"
 #include "gn/config.h"
 #include "gn/loader.h"
 #include "gn/target.h"
+#include "gn/test_with_scheduler.h"
 #include "gn/test_with_scope.h"
 #include "gn/toolchain.h"
 #include "util/test/test.h"
@@ -55,6 +58,9 @@
     files_.clear();
     return match;
   }
+  bool HasLoadedOnce(const SourceFile& f) {
+    return count(files_.begin(), files_.end(), f) == 1;
+  }
 
  private:
   ~MockLoader() override = default;
@@ -62,7 +68,7 @@
   std::vector<SourceFile> files_;
 };
 
-class BuilderTest : public testing::Test {
+class BuilderTest : public TestWithScheduler {
  public:
   BuilderTest()
       : loader_(new MockLoader),
@@ -223,6 +229,92 @@
   EXPECT_TRUE(b_record->should_generate());
 }
 
+// Test that "gen_deps" forces targets to be generated.
+TEST_F(BuilderTest, GenDeps) {
+  DefineToolchain();
+
+  // Define another toolchain
+  Settings settings2(&build_settings_, "alternate/");
+  Label alt_tc(SourceDir("//tc/"), "alternate");
+  settings2.set_toolchain_label(alt_tc);
+  Toolchain* tc2 = new Toolchain(&settings2, alt_tc);
+  TestWithScope::SetupToolchain(tc2);
+  builder_.ItemDefined(std::unique_ptr<Item>(tc2));
+
+  // Construct the dependency chain A -> B -gen-> C -gen-> D where A is the only
+  // target in the default toolchain. This should cause all 4 targets to be
+  // generated.
+  Label a_label(SourceDir("//a/"), "a", settings_.toolchain_label().dir(),
+                settings_.toolchain_label().name());
+  Label b_label(SourceDir("//b/"), "b", alt_tc.dir(), alt_tc.name());
+  Label c_label(SourceDir("//c/"), "c", alt_tc.dir(), alt_tc.name());
+  Label d_label(SourceDir("//d/"), "d", alt_tc.dir(), alt_tc.name());
+
+  Target* c = new Target(&settings2, c_label);
+  c->set_output_type(Target::EXECUTABLE);
+  c->gen_deps().push_back(LabelTargetPair(d_label));
+  builder_.ItemDefined(std::unique_ptr<Item>(c));
+
+  Target* b = new Target(&settings2, b_label);
+  b->set_output_type(Target::EXECUTABLE);
+  b->gen_deps().push_back(LabelTargetPair(c_label));
+  builder_.ItemDefined(std::unique_ptr<Item>(b));
+
+  Target* a = new Target(&settings_, a_label);
+  a->set_output_type(Target::EXECUTABLE);
+  a->private_deps().push_back(LabelTargetPair(b_label));
+  builder_.ItemDefined(std::unique_ptr<Item>(a));
+
+  // At this point, "should generate" should have propogated to C which should
+  // request for D to be loaded
+  EXPECT_TRUE(loader_->HasLoadedOnce(SourceFile("//d/BUILD.gn")));
+
+  Target* d = new Target(&settings2, d_label);
+  d->set_output_type(Target::EXECUTABLE);
+  builder_.ItemDefined(std::unique_ptr<Item>(d));
+
+  BuilderRecord* a_record = builder_.GetRecord(a_label);
+  BuilderRecord* b_record = builder_.GetRecord(b_label);
+  BuilderRecord* c_record = builder_.GetRecord(c_label);
+  BuilderRecord* d_record = builder_.GetRecord(d_label);
+  EXPECT_TRUE(a_record->should_generate());
+  EXPECT_TRUE(b_record->should_generate());
+  EXPECT_TRUE(c_record->should_generate());
+  EXPECT_TRUE(d_record->should_generate());
+}
+
+// Test that circular dependencies between gen_deps and deps are allowed
+TEST_F(BuilderTest, GenDepsCircle) {
+  DefineToolchain();
+  Settings settings2(&build_settings_, "alternate/");
+  Label alt_tc(SourceDir("//tc/"), "alternate");
+  settings2.set_toolchain_label(alt_tc);
+  Toolchain* tc2 = new Toolchain(&settings2, alt_tc);
+  TestWithScope::SetupToolchain(tc2);
+  builder_.ItemDefined(std::unique_ptr<Item>(tc2));
+
+  // A is in the default toolchain and lists B as a gen_dep
+  // B is in an alternate toolchain and lists A as a normal dep
+  Label a_label(SourceDir("//a/"), "a", settings_.toolchain_label().dir(),
+                settings_.toolchain_label().name());
+  Label b_label(SourceDir("//b/"), "b", alt_tc.dir(), alt_tc.name());
+
+  Target* a = new Target(&settings_, a_label);
+  a->gen_deps().push_back(LabelTargetPair(b_label));
+  a->set_output_type(Target::EXECUTABLE);
+  builder_.ItemDefined(std::unique_ptr<Item>(a));
+
+  Target* b = new Target(&settings2, b_label);
+  b->private_deps().push_back(LabelTargetPair(a_label));
+  b->set_output_type(Target::EXECUTABLE);
+  builder_.ItemDefined(std::unique_ptr<Item>(b));
+
+  Err err;
+  EXPECT_TRUE(builder_.CheckForBadItems(&err));
+  BuilderRecord* b_record = builder_.GetRecord(b_label);
+  EXPECT_TRUE(b_record->should_generate());
+}
+
 // Tests that configs applied to a config get loaded (bug 536844).
 TEST_F(BuilderTest, ConfigLoad) {
   SourceDir toolchain_dir = settings_.toolchain_label().dir();
diff --git a/src/gn/command_desc.cc b/src/gn/command_desc.cc
index 82a3822..fcb3434 100644
--- a/src/gn/command_desc.cc
+++ b/src/gn/command_desc.cc
@@ -296,6 +296,7 @@
           {variables::kPrecompiledHeader, DefaultHandler},
           {variables::kPrecompiledSource, DefaultHandler},
           {variables::kDeps, DepsHandler},
+          {variables::kGenDeps, DefaultHandler},
           {variables::kLibs, DefaultHandler},
           {variables::kLibDirs, DefaultHandler},
           {variables::kDataKeys, DefaultHandler},
diff --git a/src/gn/desc_builder.cc b/src/gn/desc_builder.cc
index 3b6ab23..5a8fc4c 100644
--- a/src/gn/desc_builder.cc
+++ b/src/gn/desc_builder.cc
@@ -52,6 +52,7 @@
 //   "precompiled_header" : "name of precompiled header file",
 //   "precompiled_source" : "path to precompiled source",
 //   "deps : [ list of target dependencies ],
+//   "gen_deps : [ list of generate dependencies ],
 //   "libs" : [ list of libraries ],
 //   "lib_dirs" : [ list of library directories ]
 //   "metadata" : [ dictionary of target metadata values ]
@@ -569,6 +570,9 @@
     if (what(variables::kDeps))
       res->SetWithoutPathExpansion(variables::kDeps, RenderDeps());
 
+    if (what(variables::kGenDeps) && !target_->gen_deps().empty())
+      res->SetWithoutPathExpansion(variables::kGenDeps, RenderGenDeps());
+
     // Runtime deps are special, print only when explicitly asked for and not in
     // overview mode.
     if (what_.find("runtime_deps") != what_.end())
@@ -711,6 +715,18 @@
     return std::move(res);
   }
 
+  ValuePtr RenderGenDeps() {
+    auto res = std::make_unique<base::ListValue>();
+    Label default_tc = target_->settings()->default_toolchain_label();
+    std::vector<std::string> gen_deps;
+    for (const auto& pair : target_->gen_deps())
+      gen_deps.push_back(pair.label.GetUserVisibleName(default_tc));
+    std::sort(gen_deps.begin(), gen_deps.end());
+    for (const auto& dep : gen_deps)
+      res->AppendString(dep);
+    return std::move(res);
+  }
+
   ValuePtr RenderRuntimeDeps() {
     auto res = std::make_unique<base::ListValue>();
 
diff --git a/src/gn/target.cc b/src/gn/target.cc
index 1dcd88d..da6b7e4 100644
--- a/src/gn/target.cc
+++ b/src/gn/target.cc
@@ -260,6 +260,13 @@
   required (directly or transitively) to build a target in the default
   toolchain.
 
+  Some targets might be associated but without a formal build dependency (for
+  example, related tools or optional variants). A target that is marked as
+  "generated" can propagate its generated state to an associated target using
+  "gen_deps". This will make the referenced dependency have Ninja rules
+  generated in the same cases the source target has but without a build-time
+  dependency and even in non-default toolchains.
+
   See also "gn help ninja_rules".
 
 Dependencies
diff --git a/src/gn/target.h b/src/gn/target.h
index 590d78e..59fe806 100644
--- a/src/gn/target.h
+++ b/src/gn/target.h
@@ -262,6 +262,11 @@
   const LabelTargetVector& data_deps() const { return data_deps_; }
   LabelTargetVector& data_deps() { return data_deps_; }
 
+  // gen_deps only propagate the "should_generate" flag. These dependencies can
+  // have cycles so care should be taken if iterating over them recursively.
+  const LabelTargetVector& gen_deps() const { return gen_deps_; }
+  LabelTargetVector& gen_deps() { return gen_deps_; }
+
   // List of configs that this class inherits settings from. Once a target is
   // resolved, this will also list all-dependent and public configs.
   const UniqueVector<LabelConfigPair>& configs() const { return configs_; }
@@ -477,6 +482,7 @@
   LabelTargetVector private_deps_;
   LabelTargetVector public_deps_;
   LabelTargetVector data_deps_;
+  LabelTargetVector gen_deps_;
 
   // See getters for more info.
   UniqueVector<LabelConfigPair> configs_;
diff --git a/src/gn/target_generator.cc b/src/gn/target_generator.cc
index 0ae9daf..4138889 100644
--- a/src/gn/target_generator.cc
+++ b/src/gn/target_generator.cc
@@ -260,6 +260,8 @@
     return false;
   if (!FillGenericDeps(variables::kDataDeps, &target_->data_deps()))
     return false;
+  if (!FillGenericDeps(variables::kGenDeps, &target_->gen_deps()))
+    return false;
 
   // "data_deps" was previously named "datadeps". For backwards-compat, read
   // the old one if no "data_deps" were specified.
diff --git a/src/gn/variables.cc b/src/gn/variables.cc
index 285f3b9..433d280 100644
--- a/src/gn/variables.cc
+++ b/src/gn/variables.cc
@@ -12,10 +12,9 @@
 // Built-in variables ----------------------------------------------------------
 
 const char kGnVersion[] = "gn_version";
-const char kGnVersion_HelpShort[] =
-    "gn_version: [number] The version of gn.";
+const char kGnVersion_HelpShort[] = "gn_version: [number] The version of gn.";
 const char kGnVersion_Help[] =
-  R"(gn_version: [number] The version of gn.
+    R"(gn_version: [number] The version of gn.
 
   Corresponds to the number printed by `gn --version`.
 
@@ -411,7 +410,7 @@
   "     those configs appear in the list.\n"                                 \
   "  5. all_dependent_configs pulled from dependencies, in the order of\n"   \
   "     the \"deps\" list. This is done recursively. If a config appears\n"  \
-  "     more than once, only the first occurrence will be used.\n"            \
+  "     more than once, only the first occurrence will be used.\n"           \
   "  6. public_configs pulled from dependencies, in the order of the\n"      \
   "     \"deps\" list. If a dependency is public, they will be applied\n"    \
   "     recursively.\n"
@@ -509,6 +508,23 @@
   }
 )";
 
+const char kGenDeps[] = "gen_deps";
+const char kGenDeps_HelpShort[] =
+    "gen_deps: [label list] "
+    "Declares targets that should generate when this one does.";
+const char kGenDeps_Help[] =
+    R"(gen_deps: Declares targets that should generate when this one does.
+
+  A list of target labels.
+
+  Not all GN targets that get evaluated are actually turned into ninja targets
+  (see "gn help execution"). If this target is generated, then any targets in
+  the "gen_deps" list will also be generated, regardless of the usual critera.
+
+  Since "gen_deps" are not build time dependencies, there can be cycles between
+  "deps" and "gen_deps" or within "gen_deps" itself.
+)";
+
 const char kArflags[] = "arflags";
 const char kArflags_HelpShort[] =
     "arflags: [string list] Arguments passed to static_library archiver.";
@@ -2265,6 +2281,7 @@
   if (info_map.empty()) {
     INSERT_VARIABLE(AllDependentConfigs)
     INSERT_VARIABLE(AllowCircularIncludesFrom)
+    INSERT_VARIABLE(GenDeps)
     INSERT_VARIABLE(Arflags)
     INSERT_VARIABLE(Args)
     INSERT_VARIABLE(Asmflags)
diff --git a/src/gn/variables.h b/src/gn/variables.h
index f5794d1..169a5af 100644
--- a/src/gn/variables.h
+++ b/src/gn/variables.h
@@ -354,6 +354,10 @@
 extern const char kXcodeExtraAttributes_HelpShort[];
 extern const char kXcodeExtraAttributes_Help[];
 
+extern const char kGenDeps[];
+extern const char kGenDeps_HelpShort[];
+extern const char kGenDeps_Help[];
+
 // -----------------------------------------------------------------------------
 
 struct VariableInfo {