GN: Add write_runtime_deps variable

Allows targets to use runtime_deps at build-time.

BUG=593416

Review URL: https://codereview.chromium.org/1804303004

Cr-Original-Commit-Position: refs/heads/master@{#384411}
Cr-Mirrored-From: https://chromium.googlesource.com/chromium/src
Cr-Mirrored-Commit: 0f8ad42ac1a6b159655c51bf7095ff2d21ae540f
diff --git a/tools/gn/BUILD.gn b/tools/gn/BUILD.gn
index bb36b1f..2728c0d 100644
--- a/tools/gn/BUILD.gn
+++ b/tools/gn/BUILD.gn
@@ -277,6 +277,7 @@
     "function_write_file_unittest.cc",
     "functions_target_unittest.cc",
     "functions_unittest.cc",
+    "group_target_generator_unittest.cc",
     "header_checker_unittest.cc",
     "inherited_libraries_unittest.cc",
     "input_conversion_unittest.cc",
diff --git a/tools/gn/group_target_generator_unittest.cc b/tools/gn/group_target_generator_unittest.cc
new file mode 100644
index 0000000..3b0c824
--- /dev/null
+++ b/tools/gn/group_target_generator_unittest.cc
@@ -0,0 +1,46 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "tools/gn/group_target_generator.h"
+#include "tools/gn/scheduler.h"
+#include "tools/gn/test_with_scope.h"
+
+namespace {
+
+// Returns true on success, false if write_file signaled an error.
+bool ParseWriteRuntimeDeps(Scope* scope, const std::string& value) {
+  TestParseInput input(
+      "group(\"foo\") { write_runtime_deps = " + value + "}");
+  if (input.has_error())
+    return false;
+
+  Err err;
+  input.parsed()->Execute(scope, &err);
+  return !err.has_error();
+}
+
+}  // namespace
+
+
+// Tests that actions can't have output substitutions.
+TEST(GroupTargetGenerator, WriteRuntimeDeps) {
+  Scheduler scheduler;
+  TestWithScope setup;
+  Scope::ItemVector items_;
+  setup.scope()->set_item_collector(&items_);
+
+  // Should refuse to write files outside of the output dir.
+  EXPECT_FALSE(ParseWriteRuntimeDeps(setup.scope(), "\"//foo.txt\""));
+  EXPECT_EQ(0U, scheduler.GetWriteRuntimeDepsTargets().size());
+
+  // Should fail for garbage inputs.
+  EXPECT_FALSE(ParseWriteRuntimeDeps(setup.scope(), "0"));
+  EXPECT_EQ(0U, scheduler.GetWriteRuntimeDepsTargets().size());
+
+  // Should be able to write inside the out dir.
+  EXPECT_TRUE(ParseWriteRuntimeDeps(setup.scope(), "\"//out/Debug/foo.txt\""));
+  EXPECT_EQ(1U, scheduler.GetWriteRuntimeDepsTargets().size());
+}
+
diff --git a/tools/gn/runtime_deps.cc b/tools/gn/runtime_deps.cc
index 078bebe..17b9c07 100644
--- a/tools/gn/runtime_deps.cc
+++ b/tools/gn/runtime_deps.cc
@@ -17,6 +17,7 @@
 #include "tools/gn/filesystem_utils.h"
 #include "tools/gn/loader.h"
 #include "tools/gn/output_file.h"
+#include "tools/gn/scheduler.h"
 #include "tools/gn/settings.h"
 #include "tools/gn/switches.h"
 #include "tools/gn/target.h"
@@ -118,25 +119,72 @@
   }
 }
 
-bool WriteRuntimeDepsFile(const Target* target) {
-  SourceFile target_output_as_source =
-      GetMainOutput(target).AsSourceFile(target->settings()->build_settings());
-  std::string data_deps_file_as_str = target_output_as_source.value();
-  data_deps_file_as_str.append(".runtime_deps");
+bool CollectRuntimeDepsFromFlag(const Builder& builder,
+                                RuntimeDepsVector* files_to_write,
+                                Err* err) {
+  std::string deps_target_list_file =
+      base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
+          switches::kRuntimeDepsListFile);
+
+  if (deps_target_list_file.empty())
+    return true;
+
+  std::string list_contents;
+  ScopedTrace load_trace(TraceItem::TRACE_FILE_LOAD, deps_target_list_file);
+  if (!base::ReadFileToString(UTF8ToFilePath(deps_target_list_file),
+                              &list_contents)) {
+    *err = Err(Location(),
+        std::string("File for --") + switches::kRuntimeDepsListFile +
+            " doesn't exist.",
+        "The file given was \"" + deps_target_list_file + "\"");
+    return false;
+  }
+  load_trace.Done();
+
+  SourceDir root_dir("//");
+  Label default_toolchain_label = builder.loader()->GetDefaultToolchain();
+  for (const auto& line :
+       base::SplitString(list_contents, "\n", base::TRIM_WHITESPACE,
+                         base::SPLIT_WANT_ALL)) {
+    if (line.empty())
+      continue;
+    Label label = Label::Resolve(root_dir, default_toolchain_label,
+                                 Value(nullptr, line), err);
+    if (err->has_error())
+      return false;
+
+    const Item* item = builder.GetItem(label);
+    const Target* target = item ? item->AsTarget() : nullptr;
+    if (!target) {
+      *err = Err(Location(), "The label \"" + label.GetUserVisibleName(true) +
+          "\" isn't a target.",
+          "When reading the line:\n  " + line + "\n"
+          "from the --" + switches::kRuntimeDepsListFile + "=" +
+          deps_target_list_file);
+      return false;
+    }
+
+    OutputFile output_file =
+        OutputFile(GetMainOutput(target).value() + ".runtime_deps");
+    files_to_write->push_back(std::make_pair(output_file, target));
+  }
+  return true;
+}
+
+bool WriteRuntimeDepsFile(const OutputFile& output_file,
+                          const Target* target,
+                          Err* err) {
+  SourceFile output_as_source =
+      output_file.AsSourceFile(target->settings()->build_settings());
   base::FilePath data_deps_file =
-      target->settings()->build_settings()->GetFullPath(
-          SourceFile(SourceFile::SwapIn(), &data_deps_file_as_str));
+      target->settings()->build_settings()->GetFullPath(output_as_source);
 
   std::stringstream contents;
   for (const auto& pair : ComputeRuntimeDeps(target))
     contents << pair.first.value() << std::endl;
 
-  ScopedTrace trace(TraceItem::TRACE_FILE_WRITE, data_deps_file_as_str);
-  base::CreateDirectory(data_deps_file.DirName());
-
-  std::string contents_str = contents.str();
-  return base::WriteFile(data_deps_file, contents_str.c_str(),
-                         static_cast<int>(contents_str.size())) > -1;
+  ScopedTrace trace(TraceItem::TRACE_FILE_WRITE, output_as_source.value());
+  return WriteFileIfChanged(data_deps_file, contents.str(), err);
 }
 
 }  // namespace
@@ -146,8 +194,8 @@
     "\n"
     "  Runtime dependencies of a target are exposed via the \"runtime_deps\"\n"
     "  category of \"gn desc\" (see \"gn help desc\") or they can be written\n"
-    "  at build generation time via \"--runtime-deps-list-file\"\n"
-    "  (see \"gn help --runtime-deps-list-file\").\n"
+    "  at build generation time via write_runtime_deps(), or\n"
+    "  --runtime-deps-list-file (see \"gn help --runtime-deps-list-file\").\n"
     "\n"
     "  To a first approximation, the runtime dependencies of a target are\n"
     "  the set of \"data\" files, data directories, and the shared libraries\n"
@@ -225,50 +273,22 @@
 }
 
 bool WriteRuntimeDepsFilesIfNecessary(const Builder& builder, Err* err) {
-  std::string deps_target_list_file =
-      base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
-          switches::kRuntimeDepsListFile);
-  if (deps_target_list_file.empty())
-    return true;  // Nothing to do.
-
-  std::string list_contents;
-  ScopedTrace load_trace(TraceItem::TRACE_FILE_LOAD, deps_target_list_file);
-  if (!base::ReadFileToString(UTF8ToFilePath(deps_target_list_file),
-                              &list_contents)) {
-    *err = Err(Location(),
-        std::string("File for --") + switches::kRuntimeDepsListFile +
-            " doesn't exist.",
-        "The file given was \"" + deps_target_list_file + "\"");
+  RuntimeDepsVector files_to_write;
+  if (!CollectRuntimeDepsFromFlag(builder, &files_to_write, err))
     return false;
+
+  // Files scheduled by write_runtime_deps.
+  for (const Target* target : g_scheduler->GetWriteRuntimeDepsTargets()) {
+    files_to_write.push_back(
+        std::make_pair(target->write_runtime_deps_output(), target));
   }
-  load_trace.Done();
 
-  SourceDir root_dir("//");
-  Label default_toolchain_label = builder.loader()->GetDefaultToolchain();
-  for (const auto& line : base::SplitString(
-           list_contents, "\n", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL)) {
-    if (line.empty())
-      continue;
-    Label label = Label::Resolve(root_dir, default_toolchain_label,
-                                 Value(nullptr, line), err);
-    if (err->has_error())
-      return false;
-
-    const Item* item = builder.GetItem(label);
-    const Target* target = item ? item->AsTarget() : nullptr;
-    if (!target) {
-      *err = Err(Location(), "The label \"" + label.GetUserVisibleName(true) +
-          "\" isn't a target.",
-          "When reading the line:\n  " + line + "\n"
-          "from the --" + switches::kRuntimeDepsListFile + "=" +
-          deps_target_list_file);
-      return false;
-    }
-
+  for (const auto& entry : files_to_write) {
     // Currently this writes all runtime deps files sequentially. We generally
     // expect few of these. We can run this on the worker pool if it looks
     // like it's talking a long time.
-    WriteRuntimeDepsFile(target);
+    if (!WriteRuntimeDepsFile(entry.first, entry.second, err))
+      return false;
   }
   return true;
 }
diff --git a/tools/gn/scheduler.cc b/tools/gn/scheduler.cc
index dfc877d..a711df1 100644
--- a/tools/gn/scheduler.cc
+++ b/tools/gn/scheduler.cc
@@ -12,6 +12,7 @@
 #include "build/build_config.h"
 #include "tools/gn/standard_out.h"
 #include "tools/gn/switches.h"
+#include "tools/gn/target.h"
 
 #if defined(OS_WIN)
 #include <windows.h>
@@ -153,6 +154,28 @@
   unknown_generated_inputs_.insert(std::make_pair(file, target));
 }
 
+void Scheduler::AddWriteRuntimeDepsTarget(const Target* target) {
+  base::AutoLock lock(lock_);
+  write_runtime_deps_targets_.push_back(target);
+}
+
+std::vector<const Target*> Scheduler::GetWriteRuntimeDepsTargets() const {
+  base::AutoLock lock(lock_);
+  return write_runtime_deps_targets_;
+}
+
+bool Scheduler::IsFileGeneratedByWriteRuntimeDeps(
+    const OutputFile& file) const {
+  base::AutoLock lock(lock_);
+  // Number of targets should be quite small, so brute-force search is fine.
+  for (const Target* target : write_runtime_deps_targets_) {
+    if (file == target->write_runtime_deps_output()) {
+      return true;
+    }
+  }
+  return false;
+}
+
 std::multimap<SourceFile, const Target*>
     Scheduler::GetUnknownGeneratedInputs() const {
   base::AutoLock lock(lock_);
diff --git a/tools/gn/scheduler.h b/tools/gn/scheduler.h
index 45225ce..a9bb036 100644
--- a/tools/gn/scheduler.h
+++ b/tools/gn/scheduler.h
@@ -15,6 +15,9 @@
 #include "base/synchronization/lock.h"
 #include "base/threading/sequenced_worker_pool.h"
 #include "tools/gn/input_file_manager.h"
+#include "tools/gn/label.h"
+#include "tools/gn/source_file.h"
+#include "tools/gn/token.h"
 
 class Target;
 
@@ -57,6 +60,11 @@
   // inputs (see AddUnknownGeneratedInput below).
   void AddWrittenFile(const SourceFile& file);
 
+  // Schedules a file to be written due to a target setting write_runtime_deps.
+  void AddWriteRuntimeDepsTarget(const Target* entry);
+  std::vector<const Target*> GetWriteRuntimeDepsTargets() const;
+  bool IsFileGeneratedByWriteRuntimeDeps(const OutputFile& file) const;
+
   // Unknown generated inputs are files that a target declares as an input
   // in the output directory, but which aren't generated by any dependency.
   //
@@ -113,6 +121,7 @@
   // Protected by the lock. See the corresponding Add/Get functions above.
   std::vector<base::FilePath> gen_dependencies_;
   std::vector<SourceFile> written_files_;
+  std::vector<const Target*> write_runtime_deps_targets_;
   std::multimap<SourceFile, const Target*> unknown_generated_inputs_;
 
   DISALLOW_COPY_AND_ASSIGN(Scheduler);
diff --git a/tools/gn/target.cc b/tools/gn/target.cc
index 2386870..005dd97 100644
--- a/tools/gn/target.cc
+++ b/tools/gn/target.cc
@@ -83,6 +83,7 @@
                                        const OutputFile& file,
                                        bool check_private_deps,
                                        bool consider_object_files,
+                                       bool check_data_deps,
                                        std::set<const Target*>* seen_targets) {
   if (seen_targets->find(target) != seen_targets->end())
     return false;  // Already checked this one and it's not found.
@@ -96,6 +97,9 @@
       return true;
   }
 
+  if (file == target->write_runtime_deps_output())
+    return true;
+
   // Check binary target intermediate files if requested.
   if (consider_object_files && target->IsBinary()) {
     std::vector<OutputFile> source_outputs;
@@ -109,11 +113,22 @@
     }
   }
 
+  if (check_data_deps) {
+    check_data_deps = false;  // Consider only direct data_deps.
+    for (const auto& pair : target->data_deps()) {
+      if (EnsureFileIsGeneratedByDependency(pair.ptr, file, false,
+                                            consider_object_files,
+                                            check_data_deps, seen_targets))
+        return true;  // Found a path.
+    }
+  }
+
   // Check all public dependencies (don't do data ones since those are
   // runtime-only).
   for (const auto& pair : target->public_deps()) {
     if (EnsureFileIsGeneratedByDependency(pair.ptr, file, false,
-                                          consider_object_files, seen_targets))
+                                          consider_object_files,
+                                          check_data_deps, seen_targets))
       return true;  // Found a path.
   }
 
@@ -122,14 +137,14 @@
     for (const auto& pair : target->private_deps()) {
       if (EnsureFileIsGeneratedByDependency(pair.ptr, file, false,
                                             consider_object_files,
-                                            seen_targets))
+                                            check_data_deps, seen_targets))
         return true;  // Found a path.
     }
     if (target->output_type() == Target::CREATE_BUNDLE) {
       for (const auto& dep : target->bundle_data().bundle_deps()) {
         if (EnsureFileIsGeneratedByDependency(dep, file, false,
                                               consider_object_files,
-                                              seen_targets))
+                                              check_data_deps, seen_targets))
           return true;  // Found a path.
       }
     }
@@ -752,13 +767,20 @@
   // can be filtered out of this list.
   OutputFile out_file(settings()->build_settings(), source);
   std::set<const Target*> seen_targets;
-  if (!EnsureFileIsGeneratedByDependency(this, out_file, true, false,
+  bool check_data_deps = false;
+  bool consider_object_files = false;
+  if (!EnsureFileIsGeneratedByDependency(this, out_file, true,
+                                         consider_object_files, check_data_deps,
                                          &seen_targets)) {
+    seen_targets.clear();
+    // Allow dependency to be through data_deps for files generated by gn.
+    check_data_deps = g_scheduler->IsFileGeneratedByWriteRuntimeDeps(out_file);
     // Check object files (much slower and very rare) only if the "normal"
     // output check failed.
-    seen_targets.clear();
-    if (!EnsureFileIsGeneratedByDependency(this, out_file, true, true,
-                                           &seen_targets))
+    consider_object_files = !check_data_deps;
+    if (!EnsureFileIsGeneratedByDependency(this, out_file, true,
+                                           consider_object_files,
+                                           check_data_deps, &seen_targets))
       g_scheduler->AddUnknownGeneratedInput(this, source);
   }
 }
diff --git a/tools/gn/target.h b/tools/gn/target.h
index 95e682f..c3fed01 100644
--- a/tools/gn/target.h
+++ b/tools/gn/target.h
@@ -128,6 +128,13 @@
   bool testonly() const { return testonly_; }
   void set_testonly(bool value) { testonly_ = value; }
 
+  OutputFile write_runtime_deps_output() const {
+    return write_runtime_deps_output_;
+  }
+  void set_write_runtime_deps_output(const OutputFile& value) {
+    write_runtime_deps_output_ = value;
+  }
+
   // Compile-time extra dependencies.
   const FileList& inputs() const { return inputs_; }
   FileList& inputs() { return inputs_; }
@@ -322,6 +329,7 @@
   FileList inputs_;
   std::vector<std::string> data_;
   BundleData bundle_data_;
+  OutputFile write_runtime_deps_output_;
 
   LabelTargetVector private_deps_;
   LabelTargetVector public_deps_;
diff --git a/tools/gn/target_generator.cc b/tools/gn/target_generator.cc
index f5b4ec8..4a84bcd 100644
--- a/tools/gn/target_generator.cc
+++ b/tools/gn/target_generator.cc
@@ -58,6 +58,9 @@
   if (!Visibility::FillItemVisibility(target_, scope_, err_))
     return;
 
+  if (!FillWriteRuntimeDeps())
+    return;
+
   // Do type-specific generation.
   DoRun();
 }
@@ -383,3 +386,23 @@
   }
   return !err_->has_error();
 }
+
+bool TargetGenerator::FillWriteRuntimeDeps() {
+  const Value* value = scope_->GetValue(variables::kWriteRuntimeDeps, true);
+  if (!value)
+    return true;
+
+  // Compute the file name and make sure it's in the output dir.
+  SourceFile source_file = scope_->GetSourceDir().ResolveRelativeFile(
+      *value, err_, GetBuildSettings()->root_path_utf8());
+  if (err_->has_error())
+    return false;
+  if (!EnsureStringIsInOutputDir(GetBuildSettings()->build_dir(),
+          source_file.value(), value->origin(), err_))
+    return false;
+  OutputFile output_file(GetBuildSettings(), source_file);
+  target_->set_write_runtime_deps_output(output_file);
+
+  g_scheduler->AddWriteRuntimeDepsTarget(target_);
+  return true;
+}
diff --git a/tools/gn/target_generator.h b/tools/gn/target_generator.h
index 4fa51f6..a3bdd58 100644
--- a/tools/gn/target_generator.h
+++ b/tools/gn/target_generator.h
@@ -71,6 +71,7 @@
   bool FillDependencies();  // Includes data dependencies.
   bool FillTestonly();
   bool FillAssertNoDeps();
+  bool FillWriteRuntimeDeps();
 
   // Reads configs/deps from the given var name, and uses the given setting on
   // the target to save them.
diff --git a/tools/gn/target_unittest.cc b/tools/gn/target_unittest.cc
index 876c2e3..b3a85b7 100644
--- a/tools/gn/target_unittest.cc
+++ b/tools/gn/target_unittest.cc
@@ -627,6 +627,53 @@
   EXPECT_TRUE(scheduler.GetUnknownGeneratedInputs().empty());
 }
 
+TEST(Target, WriteRuntimeDepsGeneratedInputs) {
+  Scheduler scheduler;
+  TestWithScope setup;
+  Err err;
+
+  SourceFile source_file("//out/Debug/generated.runtime_deps");
+  OutputFile output_file(setup.build_settings(), source_file);
+
+  TestTarget generator(setup, "//foo:generator", Target::EXECUTABLE);
+  generator.set_write_runtime_deps_output(output_file);
+  g_scheduler->AddWriteRuntimeDepsTarget(&generator);
+
+  TestTarget middle_data_dep(setup, "//foo:middle", Target::EXECUTABLE);
+  middle_data_dep.data_deps().push_back(LabelTargetPair(&generator));
+
+  // This target has a generated input and no dependency makes it.
+  TestTarget dep_missing(setup, "//foo:no_dep", Target::EXECUTABLE);
+  dep_missing.sources().push_back(source_file);
+  EXPECT_TRUE(dep_missing.OnResolved(&err));
+  AssertSchedulerHasOneUnknownFileMatching(&dep_missing, source_file);
+  scheduler.ClearUnknownGeneratedInputsAndWrittenFiles();
+
+  // This target has a generated file and we've directly dependended on it.
+  TestTarget dep_present(setup, "//foo:with_dep", Target::EXECUTABLE);
+  dep_present.sources().push_back(source_file);
+  dep_present.private_deps().push_back(LabelTargetPair(&generator));
+  EXPECT_TRUE(dep_present.OnResolved(&err));
+  EXPECT_TRUE(scheduler.GetUnknownGeneratedInputs().empty());
+
+  // This target has a generated file and we've indirectly dependended on it
+  // via data_deps.
+  TestTarget dep_indirect(setup, "//foo:with_dep", Target::EXECUTABLE);
+  dep_indirect.sources().push_back(source_file);
+  dep_indirect.data_deps().push_back(LabelTargetPair(&middle_data_dep));
+  EXPECT_TRUE(dep_indirect.OnResolved(&err));
+  AssertSchedulerHasOneUnknownFileMatching(&dep_indirect, source_file);
+  scheduler.ClearUnknownGeneratedInputsAndWrittenFiles();
+
+  // This target has a generated file and we've directly dependended on it
+  // via data_deps.
+  TestTarget data_dep_present(setup, "//foo:with_dep", Target::EXECUTABLE);
+  data_dep_present.sources().push_back(source_file);
+  data_dep_present.data_deps().push_back(LabelTargetPair(&generator));
+  EXPECT_TRUE(data_dep_present.OnResolved(&err));
+  EXPECT_TRUE(scheduler.GetUnknownGeneratedInputs().empty());
+}
+
 // Tests that intermediate object files generated by binary targets are also
 // considered generated for the purposes of input checking. Above, we tested
 // the failure cases for generated inputs, so here only test .o files that are
diff --git a/tools/gn/variables.cc b/tools/gn/variables.cc
index 81bb7e3..88e0ed1 100644
--- a/tools/gn/variables.cc
+++ b/tools/gn/variables.cc
@@ -1516,6 +1516,30 @@
     "  any targets in \"//bar/\" and any subdirectory thereof.\n"
     "    visibility = [ \"./*\", \"//bar/*\" ]\n";
 
+const char kWriteRuntimeDeps[] = "write_runtime_deps";
+const char kWriteRuntimeDeps_HelpShort[] =
+    "write_runtime_deps: Writes the target's runtime_deps to the given path.";
+const char kWriteRuntimeDeps_Help[] =
+    "write_runtime_deps: Writes the target's runtime_deps to the given path.\n"
+    "\n"
+    "  Does not synchronously write the file, but rather schedules it\n"
+    "  to be written at the end of generation.\n"
+    "\n"
+    "  If the file exists and the contents are identical to that being\n"
+    "  written, the file will not be updated. This will prevent unnecessary\n"
+    "  rebuilds of targets that depend on this file.\n"
+    "\n"
+    "  Path must be within the output directory.\n"
+    "\n"
+    "  See \"gn help runtime_deps\" for how the runtime dependencies are\n"
+    "  computed.\n"
+    "\n"
+    "  The format of this file will list one file per line with no escaping.\n"
+    "  The files will be relative to the root_build_dir. The first line of\n"
+    "  the file will be the main output file of the target itself. The file\n"
+    "  contents will be the same as requesting the runtime deps be written on\n"
+    "  the command line (see \"gn help --runtime-deps-list-file\").\n";
+
 // -----------------------------------------------------------------------------
 
 VariableInfo::VariableInfo()
@@ -1596,6 +1620,7 @@
     INSERT_VARIABLE(Sources)
     INSERT_VARIABLE(Testonly)
     INSERT_VARIABLE(Visibility)
+    INSERT_VARIABLE(WriteRuntimeDeps)
   }
   return info_map;
 }
diff --git a/tools/gn/variables.h b/tools/gn/variables.h
index 45fa0eb..27bde5a 100644
--- a/tools/gn/variables.h
+++ b/tools/gn/variables.h
@@ -235,6 +235,10 @@
 extern const char kVisibility_HelpShort[];
 extern const char kVisibility_Help[];
 
+extern const char kWriteRuntimeDeps[];
+extern const char kWriteRuntimeDeps_HelpShort[];
+extern const char kWriteRuntimeDeps_Help[];
+
 // -----------------------------------------------------------------------------
 
 struct VariableInfo {