[metadata] Adding metadata vars to write_data target

Change-Id: I87fa283e764549fd8f42d2c118e8489083a79fda
Reviewed-on: https://gn-review.googlesource.com/c/3421
Commit-Queue: Julie Hockett <juliehockett@google.com>
Reviewed-by: Brett Wilson <brettw@chromium.org>
diff --git a/tools/gn/command_desc.cc b/tools/gn/command_desc.cc
index 36b14b6..a4b2c87 100644
--- a/tools/gn/command_desc.cc
+++ b/tools/gn/command_desc.cc
@@ -288,6 +288,9 @@
           {variables::kDeps, DepsHandler},
           {variables::kLibs, DefaultHandler},
           {variables::kLibDirs, DefaultHandler},
+          {variables::kDataKeys, DefaultHandler},
+          {variables::kRebase, DefaultHandler},
+          {variables::kWalkKeys, DefaultHandler},
           {variables::kWriteOutputConversion, DefaultHandler}};
 }
 
@@ -365,6 +368,9 @@
   HandleProperty(variables::kDeps, handler_map, v, dict);
   HandleProperty(variables::kLibs, handler_map, v, dict);
   HandleProperty(variables::kLibDirs, handler_map, v, dict);
+  HandleProperty(variables::kDataKeys, handler_map, v, dict);
+  HandleProperty(variables::kRebase, handler_map, v, dict);
+  HandleProperty(variables::kWalkKeys, handler_map, v, dict);
   HandleProperty(variables::kWriteOutputConversion, handler_map, v, dict);
 
 #undef HandleProperty
@@ -461,6 +467,7 @@
   cflags_cc [--blame]
   check_includes
   configs [--tree] (see below)
+  data_keys
   defines [--blame]
   depfile
   deps [--all] [--tree] (see below)
@@ -474,10 +481,12 @@
   outputs
   public_configs
   public
+  rebase
   script
   sources
   testonly
   visibility
+  walk_keys
 
   runtime_deps
       Compute all runtime deps for the given target. This is a computed list
diff --git a/tools/gn/desc_builder.cc b/tools/gn/desc_builder.cc
index a66a419..3bc7e86 100644
--- a/tools/gn/desc_builder.cc
+++ b/tools/gn/desc_builder.cc
@@ -51,6 +51,9 @@
 //   "libs" : [ list of libraries ],
 //   "lib_dirs" : [ list of library directories ]
 //   "metadata" : [ dictionary of target metadata values ]
+//   "data_keys" : [ list of target data keys ]
+//   "walk_keys" : [ list of target walk keys ]
+//   "rebase" : true or false
 //   "output_conversion" : "string for output conversion"
 // }
 //
@@ -466,6 +469,22 @@
         res->SetKey(variables::kWriteOutputConversion,
                     std::move(ToBaseValue(target_->output_conversion())));
       }
+      if (what(variables::kDataKeys)) {
+        base::ListValue keys;
+        for (const auto& k : target_->data_keys())
+          keys.GetList().push_back(base::Value(k));
+        res->SetKey(variables::kDataKeys, std::move(keys));
+      }
+      if (what(variables::kRebase)) {
+        res->SetKey(variables::kRebase,
+                    std::move(base::Value(target_->rebase())));
+      }
+      if (what(variables::kWalkKeys)) {
+        base::ListValue keys;
+        for (const auto& k : target_->walk_keys())
+          keys.GetList().push_back(base::Value(k));
+        res->SetKey(variables::kWalkKeys, std::move(keys));
+      }
     }
 
     if (what(variables::kDeps))
diff --git a/tools/gn/generated_file_target_generator.cc b/tools/gn/generated_file_target_generator.cc
index 96a9f37..f953f38 100644
--- a/tools/gn/generated_file_target_generator.cc
+++ b/tools/gn/generated_file_target_generator.cc
@@ -17,7 +17,10 @@
     const FunctionCallNode* function_call,
     Target::OutputType type,
     Err* err)
-    : TargetGenerator(target, scope, function_call, err), output_type_(type) {}
+    : TargetGenerator(target, scope, function_call, err),
+      output_type_(type),
+      contents_defined_(false),
+      data_keys_defined_(false) {}
 
 GeneratedFileTargetGenerator::~GeneratedFileTargetGenerator() = default;
 
@@ -34,13 +37,26 @@
     return;
   }
 
-  if (!FillContents()) {
-    *err_ = Err(function_call_, "Contents should be set.",
-                "The generated_file target requires the \"contents\" variable "
-                "be set. See \"gn help generated_file\".");
+  if (!FillContents())
+    return;
+  if (!FillDataKeys())
+    return;
+
+  // One of data and data_keys should be defined.
+  if (!contents_defined_ && !data_keys_defined_) {
+    *err_ = Err(
+        function_call_, "Either contents or data_keys should be set.",
+        "The generated_file target requires either the \"contents\" variable "
+        "or the \"data_keys\" variable be set. See \"gn help "
+        "generated_file\".");
     return;
   }
 
+  if (!FillRebase())
+    return;
+  if (!FillWalkKeys())
+    return;
+
   if (!FillOutputConversion())
     return;
 }
@@ -48,8 +64,23 @@
 bool GeneratedFileTargetGenerator::FillContents() {
   const Value* value = scope_->GetValue(variables::kWriteValueContents, true);
   if (!value)
-    return false;
+    return true;
   target_->set_contents(*value);
+  contents_defined_ = true;
+  return true;
+}
+
+bool GeneratedFileTargetGenerator::IsMetadataCollectionTarget(
+    const base::StringPiece& variable,
+    const ParseNode* origin) {
+  if (contents_defined_) {
+    *err_ =
+        Err(origin, variable.as_string() + " won't be used.",
+            "\"contents\" is defined on this target, and so setting " +
+                variable.as_string() +
+                " will have no effect as no metdata collection will occur.");
+    return false;
+  }
   return true;
 }
 
@@ -67,3 +98,61 @@
   target_->set_output_conversion(*value);
   return true;
 }
+
+bool GeneratedFileTargetGenerator::FillRebase() {
+  const Value* value = scope_->GetValue(variables::kRebase, true);
+  if (!value)
+    return true;
+  if (!IsMetadataCollectionTarget(variables::kRebase, value->origin()))
+    return false;
+  if (!value->VerifyTypeIs(Value::BOOLEAN, err_))
+    return false;
+  target_->set_rebase(value->boolean_value());
+  return true;
+}
+
+bool GeneratedFileTargetGenerator::FillDataKeys() {
+  const Value* value = scope_->GetValue(variables::kDataKeys, true);
+  if (!value)
+    return true;
+  if (!IsMetadataCollectionTarget(variables::kDataKeys, value->origin()))
+    return false;
+  if (!value->VerifyTypeIs(Value::LIST, err_))
+    return false;
+
+  for (const Value& v : value->list_value()) {
+    // Keys must be strings.
+    if (!v.VerifyTypeIs(Value::STRING, err_))
+      return false;
+    target_->data_keys().push_back(v.string_value());
+  }
+
+  data_keys_defined_ = true;
+  return true;
+}
+
+bool GeneratedFileTargetGenerator::FillWalkKeys() {
+  const Value* value = scope_->GetValue(variables::kWalkKeys, true);
+  // If we define this and contents, that's an error.
+  if (value &&
+      !IsMetadataCollectionTarget(variables::kWalkKeys, value->origin()))
+    return false;
+
+  // If we don't define it, we want the default value which is a list
+  // containing the empty string.
+  if (!value) {
+    target_->walk_keys().push_back("");
+    return true;
+  }
+
+  // Otherwise, pull and validate the specified value.
+  if (!value->VerifyTypeIs(Value::LIST, err_))
+    return false;
+  for (const Value& v : value->list_value()) {
+    // Keys must be strings.
+    if (!v.VerifyTypeIs(Value::STRING, err_))
+      return false;
+    target_->walk_keys().push_back(v.string_value());
+  }
+  return true;
+}
diff --git a/tools/gn/generated_file_target_generator.h b/tools/gn/generated_file_target_generator.h
index 6db9fb1..fb2523d 100644
--- a/tools/gn/generated_file_target_generator.h
+++ b/tools/gn/generated_file_target_generator.h
@@ -26,6 +26,20 @@
   bool FillGeneratedFileOutput();
   bool FillOutputConversion();
   bool FillContents();
+  bool FillDataKeys();
+  bool FillWalkKeys();
+  bool FillRebase();
+
+  // Returns false if `contents` is defined (i.e. if this target was provided
+  // with explicit contents to write). Returns false otherwise, indicating that
+  // it is okay to set metadata collection variables on this target.
+  //
+  // Should be called before FillContents().
+  bool IsMetadataCollectionTarget(const base::StringPiece& variable,
+                                  const ParseNode* origin);
+
+  bool contents_defined_;
+  bool data_keys_defined_;
 
   Target::OutputType output_type_;
 
diff --git a/tools/gn/misc/emacs/gn-mode.el b/tools/gn/misc/emacs/gn-mode.el
index 872bfd1..aa33133 100644
--- a/tools/gn/misc/emacs/gn-mode.el
+++ b/tools/gn/misc/emacs/gn-mode.el
@@ -93,7 +93,7 @@
     "precompiled_header_type" "precompiled_source" "product_type" "public"
     "public_configs" "public_deps" "response_file_contents" "script" "sources"
     "testonly" "visibility" "write_runtime_deps" "bundle_contents_dir"
-    "contents" "output_conversion"))
+    "contents" "output_conversion" "rebase" "data_keys" "walk_keys"))
 
 (defconst gn-font-lock-keywords
   `((,(regexp-opt gn-font-lock-reserved-keywords 'words) .
diff --git a/tools/gn/misc/tm/GN.tmLanguage b/tools/gn/misc/tm/GN.tmLanguage
index 1d48889..5cccf4b 100644
--- a/tools/gn/misc/tm/GN.tmLanguage
+++ b/tools/gn/misc/tm/GN.tmLanguage
@@ -89,7 +89,7 @@
       <key>comment</key>
       <string>target variables</string>
       <key>match</key>
-      <string>\b(?:all_dependent_configs|allow_circular_includes_from|args|asmflags|cflags|cflags_c|cflags_cc|cflags_objc|cflags_objcc|check_includes|complete_static_lib|configs|data|data_deps|defines|depfile|deps|include_dirs|inputs|ldflags|lib_dirs|libs|output_extension|output_name|outputs|public|public_configs|public_deps|script|sources|testonly|visibility|contents|output_conversion)\b</string>
+      <string>\b(?:all_dependent_configs|allow_circular_includes_from|args|asmflags|cflags|cflags_c|cflags_cc|cflags_objc|cflags_objcc|check_includes|complete_static_lib|configs|data|data_deps|defines|depfile|deps|include_dirs|inputs|ldflags|lib_dirs|libs|output_extension|output_name|outputs|public|public_configs|public_deps|script|sources|testonly|visibility|contents|output_conversion|rebase|data_keys|walk_keys)\b</string>
       <key>name</key>
       <string>entity.other.attribute-name.gn</string>
     </dict>
diff --git a/tools/gn/misc/vim/syntax/gn.vim b/tools/gn/misc/vim/syntax/gn.vim
index a50f550..9d5b060 100644
--- a/tools/gn/misc/vim/syntax/gn.vim
+++ b/tools/gn/misc/vim/syntax/gn.vim
@@ -48,7 +48,8 @@
 syn keyword     gnVariable include_dirs inputs ldflags lib_dirs libs
 syn keyword     gnVariable output_extension output_name outputs public
 syn keyword     gnVariable public_configs public_deps scripte sources testonly
-syn keyword     gnVariable visibility contents output_conversion
+syn keyword     gnVariable visibility contents output_conversion rebase
+syn keyword     gnVariable data_keys walk_keys
 hi def link     gnVariable          Keyword
 
 " Strings
diff --git a/tools/gn/target.cc b/tools/gn/target.cc
index 560f6d0..89810c6 100644
--- a/tools/gn/target.cc
+++ b/tools/gn/target.cc
@@ -287,7 +287,8 @@
       check_includes_(true),
       complete_static_lib_(false),
       testonly_(false),
-      toolchain_(nullptr) {}
+      toolchain_(nullptr),
+      rebase_(false) {}
 
 Target::~Target() = default;
 
diff --git a/tools/gn/target.h b/tools/gn/target.h
index 91553f6..e75b97a 100644
--- a/tools/gn/target.h
+++ b/tools/gn/target.h
@@ -167,6 +167,14 @@
   const Value& output_conversion() const { return output_conversion_; }
   void set_output_conversion(const Value& value) { output_conversion_ = value; }
 
+  // Metadata collection methods for WriteData targets.
+  bool rebase() const { return rebase_; }
+  void set_rebase(bool value) { rebase_ = value; }
+  const std::vector<std::string>& data_keys() const { return data_keys_; }
+  std::vector<std::string>& data_keys() { return data_keys_; }
+  const std::vector<std::string>& walk_keys() const { return walk_keys_; }
+  std::vector<std::string>& walk_keys() { return walk_keys_; }
+
   bool testonly() const { return testonly_; }
   void set_testonly(bool value) { testonly_ = value; }
 
@@ -413,10 +421,15 @@
 
   Metadata metadata_;
 
-  // GenerateFile values.
+  // GeneratedFile values.
   Value output_conversion_;
   Value contents_;  // Value::NONE if metadata collection should occur.
 
+  // GeneratedFile as metadata collection values.
+  bool rebase_;
+  std::vector<std::string> data_keys_;
+  std::vector<std::string> walk_keys_;
+
   DISALLOW_COPY_AND_ASSIGN(Target);
 };
 
diff --git a/tools/gn/variables.cc b/tools/gn/variables.cc
index 931261d..7723636 100644
--- a/tools/gn/variables.cc
+++ b/tools/gn/variables.cc
@@ -1000,6 +1000,19 @@
   }
 )";
 
+const char kDataKeys[] = "data_keys";
+const char kDataKeys_HelpShort[] =
+    "data_keys: [string list] Keys from which to collect metadata.";
+const char kDataKeys_Help[] =
+    R"(data_keys: Keys from which to collect metadata.
+
+  These keys are used to identify metadata to collect. If a walked target
+  defines this key in its metadata, its value will be appended to the resulting
+  collection.
+
+  See "gn help generated_file".
+)";
+
 const char kDefines[] = "defines";
 const char kDefines_HelpShort[] =
     "defines: [string list] C preprocessor defines.";
@@ -1822,6 +1835,26 @@
   }
 )";
 
+const char kRebase[] = "rebase";
+const char kRebase_HelpShort[] =
+    "rebase: [boolean] Rebase collected metadata as files.";
+const char kRebase_Help[] =
+    R"(rebase: Rebase collected metadata as files.
+
+  A boolean that triggers a rebase of collected metadata strings based on their
+  declared file. Defaults to false.
+
+  Metadata generally declares files as strings relative to the local build file.
+  However, this data is often used in other contexts, and so setting this flag
+  will force the metadata collection to be rebased according to the local build
+  file's location and thus allow the filename to be used anywhere.
+
+  Setting this flag will raise an error if any target's specified metadata is
+  not a string value.
+
+  See also "gn help generated_file".
+)";
+
 const char kResponseFileContents[] = "response_file_contents";
 const char kResponseFileContents_HelpShort[] =
     "response_file_contents: [string list] Contents of .rsp file for actions.";
@@ -2009,6 +2042,25 @@
     visibility = [ "./*", "//bar/*" ]
 )";
 
+const char kWalkKeys[] = "walk_keys";
+const char kWalkKeys_HelpShort[] =
+    "walk_keys: [string list] Key(s) for managing the metadata collection "
+    "walk.";
+const char kWalkKeys_Help[] =
+    R"(walk_keys: Key(s) for managing the metadata collection walk.
+
+  Defaults to [].
+
+  These keys are used to control the next step in a collection walk, acting as
+  barriers. If a specified key is defined in a target's metadata, the walk will
+  use the targets listed in that value to determine which targets are walked.
+
+  If no walk_keys are specified for a generated_file target (i.e. "[]"), the
+  walk will touch all deps and data_deps of the specified target recursively.
+
+  See "gn help generated_file".
+)";
+
 const char kWriteValueContents[] = "contents";
 const char kWriteValueContents_HelpShort[] =
     "contents: Contents to write to file.";
@@ -2128,6 +2180,7 @@
     INSERT_VARIABLE(Configs)
     INSERT_VARIABLE(Data)
     INSERT_VARIABLE(DataDeps)
+    INSERT_VARIABLE(DataKeys)
     INSERT_VARIABLE(Defines)
     INSERT_VARIABLE(Depfile)
     INSERT_VARIABLE(Deps)
@@ -2152,12 +2205,14 @@
     INSERT_VARIABLE(Public)
     INSERT_VARIABLE(PublicConfigs)
     INSERT_VARIABLE(PublicDeps)
+    INSERT_VARIABLE(Rebase)
     INSERT_VARIABLE(ResponseFileContents)
     INSERT_VARIABLE(Script)
     INSERT_VARIABLE(Sources)
     INSERT_VARIABLE(XcodeTestApplicationName)
     INSERT_VARIABLE(Testonly)
     INSERT_VARIABLE(Visibility)
+    INSERT_VARIABLE(WalkKeys)
     INSERT_VARIABLE(WriteOutputConversion)
     INSERT_VARIABLE(WriteValueContents)
     INSERT_VARIABLE(WriteRuntimeDeps)
diff --git a/tools/gn/variables.h b/tools/gn/variables.h
index eb0190a..0ef15b8 100644
--- a/tools/gn/variables.h
+++ b/tools/gn/variables.h
@@ -183,6 +183,10 @@
 extern const char kDataDeps_HelpShort[];
 extern const char kDataDeps_Help[];
 
+extern const char kDataKeys[];
+extern const char kDataKeys_HelpShort[];
+extern const char kDataKeys_Help[];
+
 extern const char kDefines[];
 extern const char kDefines_HelpShort[];
 extern const char kDefines_Help[];
@@ -279,6 +283,10 @@
 extern const char kPublicDeps_HelpShort[];
 extern const char kPublicDeps_Help[];
 
+extern const char kRebase[];
+extern const char kRebase_HelpShort[];
+extern const char kRebase_Help[];
+
 extern const char kResponseFileContents[];
 extern const char kResponseFileContents_HelpShort[];
 extern const char kResponseFileContents_Help[];
@@ -303,6 +311,10 @@
 extern const char kVisibility_HelpShort[];
 extern const char kVisibility_Help[];
 
+extern const char kWalkKeys[];
+extern const char kWalkKeys_HelpShort[];
+extern const char kWalkKeys_Help[];
+
 extern const char kWriteValueContents[];
 extern const char kWriteValueContents_HelpShort[];
 extern const char kWriteValueContents_Help[];