Add "bundle_data" target as first step for adding bundle support to gn.

The "bundle_data" target is a type of target that has no output (just a
stamp) but list files that need to be present in any bundle that depend
on it (generally application or frameworks that link with code that use
the files at runtime) and where they should be copied into the bundle.

Design: https://docs.google.com/document/d/1bKh57hg6TSBsEmeh0zWpOO5SVGA2INu-D3FGgsyICzk/view

BUG=297668

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

Cr-Original-Commit-Position: refs/heads/master@{#379300}
Cr-Mirrored-From: https://chromium.googlesource.com/chromium/src
Cr-Mirrored-Commit: db6b992c5baaf258d1a7b9687743a93eb13ac54a
diff --git a/tools/gn/BUILD.gn b/tools/gn/BUILD.gn
index 683c8eb..b27ae70 100644
--- a/tools/gn/BUILD.gn
+++ b/tools/gn/BUILD.gn
@@ -24,6 +24,8 @@
     "builder.h",
     "builder_record.cc",
     "builder_record.h",
+    "bundle_data_target_generator.cc",
+    "bundle_data_target_generator.h",
     "c_include_iterator.cc",
     "c_include_iterator.h",
     "command_args.cc",
@@ -110,6 +112,8 @@
     "ninja_binary_target_writer.h",
     "ninja_build_writer.cc",
     "ninja_build_writer.h",
+    "ninja_bundle_data_target_writer.cc",
+    "ninja_bundle_data_target_writer.h",
     "ninja_copy_target_writer.cc",
     "ninja_copy_target_writer.h",
     "ninja_group_target_writer.cc",
diff --git a/tools/gn/action_values.cc b/tools/gn/action_values.cc
index 819ead1..a7ce83d 100644
--- a/tools/gn/action_values.cc
+++ b/tools/gn/action_values.cc
@@ -15,7 +15,10 @@
 void ActionValues::GetOutputsAsSourceFiles(
     const Target* target,
     std::vector<SourceFile>* result) const {
-  if (target->output_type() == Target::COPY_FILES ||
+  if (target->output_type() == Target::BUNDLE_DATA) {
+    // The bundle_data target has no output, the real output will be generated
+    // by the create_bundle target.
+  } else if (target->output_type() == Target::COPY_FILES ||
       target->output_type() == Target::ACTION_FOREACH) {
     // Copy and foreach applies the outputs to the sources.
     SubstitutionWriter::ApplyListToSources(
diff --git a/tools/gn/bundle_data_target_generator.cc b/tools/gn/bundle_data_target_generator.cc
new file mode 100644
index 0000000..81a9b95
--- /dev/null
+++ b/tools/gn/bundle_data_target_generator.cc
@@ -0,0 +1,94 @@
+// 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 "tools/gn/bundle_data_target_generator.h"
+
+#include "tools/gn/parse_tree.h"
+#include "tools/gn/scope.h"
+#include "tools/gn/substitution_type.h"
+#include "tools/gn/target.h"
+#include "tools/gn/value.h"
+#include "tools/gn/variables.h"
+
+BundleDataTargetGenerator::BundleDataTargetGenerator(
+    Target* target,
+    Scope* scope,
+    const FunctionCallNode* function_call,
+    Err* err) : TargetGenerator(target, scope, function_call, err) {}
+
+BundleDataTargetGenerator::~BundleDataTargetGenerator() {}
+
+void BundleDataTargetGenerator::DoRun() {
+  target_->set_output_type(Target::BUNDLE_DATA);
+
+  if (!FillSources())
+    return;
+  if (!FillOutputs())
+    return;
+
+  if (target_->sources().empty()) {
+    *err_ = Err(function_call_, "Empty sources for bundle_data target."
+        "You have to specify at least one file in the \"sources\".");
+    return;
+  }
+  if (target_->action_values().outputs().list().size() != 1) {
+    *err_ = Err(function_call_,
+        "Target bundle_data must have exactly one ouput.",
+        "You must specify exactly one value in the \"output\" array for the"
+        "destination\ninto the generated bundle (see \"gn help bundle_data\"). "
+        "If there are multiple\nsources to copy, use source expansion (see "
+        "\"gn help source_expansion\").");
+    return;
+  }
+}
+
+bool BundleDataTargetGenerator::FillOutputs() {
+  const Value* value = scope_->GetValue(variables::kOutputs, true);
+  if (!value)
+    return true;
+
+  SubstitutionList& outputs = target_->action_values().outputs();
+  if (!outputs.Parse(*value, err_))
+    return false;
+
+  // Check the substitutions used are valid for this purpose.
+  for (SubstitutionType type : outputs.required_types()) {
+    if (!IsValidBundleDataSubstitution(type)) {
+      *err_ = Err(value->origin(), "Invalid substitution type.",
+          "The substitution " + std::string(kSubstitutionNames[type]) +
+          " isn't valid for something\n"
+          "operating on a bundle_data file such as this.");
+      return false;
+    }
+  }
+
+  // Validate that outputs are in the bundle.
+  CHECK(outputs.list().size() == value->list_value().size());
+  for (size_t i = 0; i < outputs.list().size(); i++) {
+    if (!EnsureSubstitutionIsInBundleDir(outputs.list()[i],
+                                         value->list_value()[i]))
+      return false;
+  }
+
+  return true;
+}
+
+bool BundleDataTargetGenerator::EnsureSubstitutionIsInBundleDir(
+    const SubstitutionPattern& pattern,
+    const Value& original_value) {
+  if (pattern.ranges().empty()) {
+    // Pattern is empty, error out (this prevents weirdness below).
+    *err_ = Err(original_value, "This has an empty value in it.");
+    return false;
+  }
+
+  if (SubstitutionIsInBundleDir(pattern.ranges()[0].type))
+    return true;
+
+  *err_ = Err(original_value,
+      "File is not inside bundle directory.",
+      "The given file should be in the output directory. Normally you\n"
+      "would specify {{bundle_resources_dir}} or such substitution.");
+  return false;
+}
diff --git a/tools/gn/bundle_data_target_generator.h b/tools/gn/bundle_data_target_generator.h
new file mode 100644
index 0000000..49bcc45
--- /dev/null
+++ b/tools/gn/bundle_data_target_generator.h
@@ -0,0 +1,33 @@
+// 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.
+
+#ifndef TOOLS_GN_BUNDLE_DATA_TARGET_GENERATOR_H_
+#define TOOLS_GN_BUNDLE_DATA_TARGET_GENERATOR_H_
+
+#include "base/macros.h"
+#include "tools/gn/target_generator.h"
+
+// Populates a Target with the values from a bundle_data rule.
+class BundleDataTargetGenerator : public TargetGenerator {
+ public:
+  BundleDataTargetGenerator(Target* target,
+                            Scope* scope,
+                            const FunctionCallNode* function_call,
+                            Err* err);
+  ~BundleDataTargetGenerator() override;
+
+ protected:
+  void DoRun() override;
+
+ private:
+  bool FillOutputs();
+
+  bool EnsureSubstitutionIsInBundleDir(
+      const SubstitutionPattern& pattern,
+      const Value& original_value);
+
+  DISALLOW_COPY_AND_ASSIGN(BundleDataTargetGenerator);
+};
+
+#endif  // TOOLS_GN_BUNDLE_DATA_TARGET_GENERATOR_H_
diff --git a/tools/gn/command_desc.cc b/tools/gn/command_desc.cc
index 27b1d01..801055b 100644
--- a/tools/gn/command_desc.cc
+++ b/tools/gn/command_desc.cc
@@ -684,7 +684,8 @@
     target->output_type() != Target::GROUP &&
     target->output_type() != Target::COPY_FILES &&
     target->output_type() != Target::ACTION &&
-    target->output_type() != Target::ACTION_FOREACH;
+    target->output_type() != Target::ACTION_FOREACH &&
+    target->output_type() != Target::BUNDLE_DATA;
 
   // Generally we only want to display toolchains on labels when the toolchain
   // is different than the default one for this target (which we always print
diff --git a/tools/gn/functions.cc b/tools/gn/functions.cc
index fb02fa8..37b3401 100644
--- a/tools/gn/functions.cc
+++ b/tools/gn/functions.cc
@@ -803,6 +803,7 @@
 
     INSERT_FUNCTION(Action, true)
     INSERT_FUNCTION(ActionForEach, true)
+    INSERT_FUNCTION(BundleData, true)
     INSERT_FUNCTION(Copy, true)
     INSERT_FUNCTION(Executable, true)
     INSERT_FUNCTION(Group, true)
diff --git a/tools/gn/functions.h b/tools/gn/functions.h
index b746515..1341720 100644
--- a/tools/gn/functions.h
+++ b/tools/gn/functions.h
@@ -80,6 +80,15 @@
                 const std::vector<Value>& args,
                 Err* err);
 
+extern const char kBundleData[];
+extern const char kBundleData_HelpShort[];
+extern const char kBundleData_Help[];
+Value RunBundleData(Scope* scope,
+                    const FunctionCallNode* function,
+                    const std::vector<Value>& args,
+                    BlockNode* block,
+                    Err* err);
+
 extern const char kConfig[];
 extern const char kConfig_HelpShort[];
 extern const char kConfig_Help[];
diff --git a/tools/gn/functions_target.cc b/tools/gn/functions_target.cc
index b04fea8..7a293c7 100644
--- a/tools/gn/functions_target.cc
+++ b/tools/gn/functions_target.cc
@@ -243,6 +243,27 @@
                               block, err);
 }
 
+// bundle_data -----------------------------------------------------------------
+
+const char kBundleData[] = "bundle_data";
+const char kBundleData_HelpShort[] =
+    "bundle_data: Declare a target without output.";
+const char kBundleData_Help[] =
+    "bundle_data: Declare a target without output.\n"
+    "\n"
+    "  This target type allows to declare data that is required at runtime.\n"
+    "  It is used to inform \"create_bundle\" targets of the files to copy\n"
+    "  into generated bundle, see \"gn help create_bundle\" for help.\n";
+
+Value RunBundleData(Scope* scope,
+                    const FunctionCallNode* function,
+                    const std::vector<Value>& args,
+                    BlockNode* block,
+                    Err* err) {
+  return ExecuteGenericTarget(functions::kBundleData, scope, function, args,
+                              block, err);
+}
+
 // copy ------------------------------------------------------------------------
 
 const char kCopy[] = "copy";
diff --git a/tools/gn/gn.gyp b/tools/gn/gn.gyp
index 85754e0..3cd81e3 100644
--- a/tools/gn/gn.gyp
+++ b/tools/gn/gn.gyp
@@ -24,6 +24,8 @@
         'builder.h',
         'builder_record.cc',
         'builder_record.h',
+        'bundle_data_target_generator.cc',
+        'bundle_data_target_generator.h',
         'c_include_iterator.cc',
         'c_include_iterator.h',
         'command_args.cc',
@@ -110,6 +112,8 @@
         'ninja_binary_target_writer.h',
         'ninja_build_writer.cc',
         'ninja_build_writer.h',
+        'ninja_bundle_data_target_writer.cc',
+        'ninja_bundle_data_target_writer.h',
         'ninja_copy_target_writer.cc',
         'ninja_copy_target_writer.h',
         'ninja_group_target_writer.cc',
diff --git a/tools/gn/ninja_bundle_data_target_writer.cc b/tools/gn/ninja_bundle_data_target_writer.cc
new file mode 100644
index 0000000..67b5a10
--- /dev/null
+++ b/tools/gn/ninja_bundle_data_target_writer.cc
@@ -0,0 +1,19 @@
+// 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 "tools/gn/ninja_bundle_data_target_writer.h"
+
+#include "tools/gn/output_file.h"
+
+NinjaBundleDataTargetWriter::NinjaBundleDataTargetWriter(const Target* target,
+                                                         std::ostream& out)
+    : NinjaTargetWriter(target, out) {}
+
+NinjaBundleDataTargetWriter::~NinjaBundleDataTargetWriter() {}
+
+void NinjaBundleDataTargetWriter::Run() {
+  std::vector<OutputFile> files;
+  files.push_back(WriteInputDepsStampAndGetDep(std::vector<const Target*>()));
+  WriteStampForTarget(files, std::vector<OutputFile>());
+}
diff --git a/tools/gn/ninja_bundle_data_target_writer.h b/tools/gn/ninja_bundle_data_target_writer.h
new file mode 100644
index 0000000..c097f67
--- /dev/null
+++ b/tools/gn/ninja_bundle_data_target_writer.h
@@ -0,0 +1,23 @@
+// 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.
+
+#ifndef TOOLS_GN_NINJA_BUNDLE_DATA_TARGET_WRITER_H_
+#define TOOLS_GN_NINJA_BUNDLE_DATA_TARGET_WRITER_H_
+
+#include "base/macros.h"
+#include "tools/gn/ninja_target_writer.h"
+
+// Writes a .ninja file for a bundle_data target type.
+class NinjaBundleDataTargetWriter : public NinjaTargetWriter {
+ public:
+  NinjaBundleDataTargetWriter(const Target* target, std::ostream& out);
+  ~NinjaBundleDataTargetWriter() override;
+
+  void Run() override;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(NinjaBundleDataTargetWriter);
+};
+
+#endif  // TOOLS_GN_NINJA_BUNDLE_DATA_TARGET_WRITER_H_
diff --git a/tools/gn/ninja_target_writer.cc b/tools/gn/ninja_target_writer.cc
index 9f19b2b..f4de7f2 100644
--- a/tools/gn/ninja_target_writer.cc
+++ b/tools/gn/ninja_target_writer.cc
@@ -13,6 +13,7 @@
 #include "tools/gn/filesystem_utils.h"
 #include "tools/gn/ninja_action_target_writer.h"
 #include "tools/gn/ninja_binary_target_writer.h"
+#include "tools/gn/ninja_bundle_data_target_writer.h"
 #include "tools/gn/ninja_copy_target_writer.h"
 #include "tools/gn/ninja_group_target_writer.h"
 #include "tools/gn/ninja_utils.h"
@@ -57,7 +58,10 @@
   std::stringstream file;
 
   // Call out to the correct sub-type of writer.
-  if (target->output_type() == Target::COPY_FILES) {
+  if (target->output_type() == Target::BUNDLE_DATA) {
+    NinjaBundleDataTargetWriter writer(target, file);
+    writer.Run();
+  } else if (target->output_type() == Target::COPY_FILES) {
     NinjaCopyTargetWriter writer(target, file);
     writer.Run();
   } else if (target->output_type() == Target::ACTION ||
diff --git a/tools/gn/substitution_type.cc b/tools/gn/substitution_type.cc
index b66e087..93a32bd 100644
--- a/tools/gn/substitution_type.cc
+++ b/tools/gn/substitution_type.cc
@@ -46,6 +46,11 @@
   "{{output_extension}}",  // SUBSTITUTION_OUTPUT_EXTENSION
   "{{solibs}}",  // SUBSTITUTION_SOLIBS
 
+  "{{bundle_root_dir}}",  // SUBSTITUTION_BUNDLE_ROOT_DIR
+  "{{bundle_resources_dir}}",  // SUBSTITUTION_BUNDLE_RESOURCES_DIR
+  "{{bundle_executable_dir}}",  // SUBSTITUTION_BUNDLE_EXECUTABLE_DIR
+  "{{bundle_plugins_dir}}",  // SUBSTITUTION_BUNDLE_PLUGINS_DIR
+
   "{{response_file_name}}",  // SUBSTITUTION_RSP_FILE_NAME
 };
 
@@ -89,6 +94,11 @@
     "output_extension",  // SUBSTITUTION_OUTPUT_EXTENSION
     "solibs",            // SUBSTITUTION_SOLIBS
 
+    "bundle_root_dir",        // SUBSTITUTION_BUNDLE_ROOT_DIR
+    "bundle_resources_dir",   // SUBSTITUTION_BUNDLE_RESOURCES_DIR
+    "bundle_executable_dir",  // SUBSTITUTION_BUNDLE_EXECUTABLE_DIR
+    "bundle_plugins_dir",     // SUBSTITUTION_BUNDLE_PLUGINS_DIR
+
     "rspfile",  // SUBSTITUTION_RSP_FILE_NAME
 };
 
@@ -116,6 +126,24 @@
          type == SUBSTITUTION_TARGET_OUT_DIR;
 }
 
+bool SubstitutionIsInBundleDir(SubstitutionType type) {
+  return type == SUBSTITUTION_BUNDLE_ROOT_DIR ||
+         type == SUBSTITUTION_BUNDLE_RESOURCES_DIR ||
+         type == SUBSTITUTION_BUNDLE_EXECUTABLE_DIR ||
+         type == SUBSTITUTION_BUNDLE_PLUGINS_DIR;
+}
+
+bool IsValidBundleDataSubstitution(SubstitutionType type) {
+  return type == SUBSTITUTION_LITERAL ||
+         type == SUBSTITUTION_SOURCE_NAME_PART ||
+         type == SUBSTITUTION_SOURCE_FILE_PART ||
+         type == SUBSTITUTION_SOURCE_ROOT_RELATIVE_DIR ||
+         type == SUBSTITUTION_BUNDLE_ROOT_DIR ||
+         type == SUBSTITUTION_BUNDLE_RESOURCES_DIR ||
+         type == SUBSTITUTION_BUNDLE_EXECUTABLE_DIR ||
+         type == SUBSTITUTION_BUNDLE_PLUGINS_DIR;
+}
+
 bool IsValidSourceSubstitution(SubstitutionType type) {
   return type == SUBSTITUTION_LITERAL ||
          type == SUBSTITUTION_SOURCE ||
diff --git a/tools/gn/substitution_type.h b/tools/gn/substitution_type.h
index 02787b1..96ae33a 100644
--- a/tools/gn/substitution_type.h
+++ b/tools/gn/substitution_type.h
@@ -59,6 +59,12 @@
   SUBSTITUTION_OUTPUT_EXTENSION,  // {{output_extension}}
   SUBSTITUTION_SOLIBS,  // {{solibs}}
 
+  // Valid for bundle_data targets.
+  SUBSTITUTION_BUNDLE_ROOT_DIR,  // {{bundle_root_dir}}
+  SUBSTITUTION_BUNDLE_RESOURCES_DIR,  // {{bundle_resources_dir}}
+  SUBSTITUTION_BUNDLE_EXECUTABLE_DIR,  // {{bundle_executable_dir}}
+  SUBSTITUTION_BUNDLE_PLUGINS_DIR,  // {{bundle_plugins_dir}}
+
   // Used only for the args of actions.
   SUBSTITUTION_RSP_FILE_NAME,  // {{response_file_name}}
 
@@ -96,7 +102,13 @@
 // verify that they produce a file in the output directory.
 bool SubstitutionIsInOutputDir(SubstitutionType type);
 
+// Returns true if the given substitution pattern references the bundle
+// directory. This is used to check strings that begin with a substitution to
+// verify that they produce a file in the bundle directory.
+bool SubstitutionIsInBundleDir(SubstitutionType type);
+
 // Returns true if the given substitution is valid for the named purpose.
+bool IsValidBundleDataSubstitution(SubstitutionType type);
 bool IsValidSourceSubstitution(SubstitutionType type);
 // Both compiler and linker tools.
 bool IsValidToolSubstitution(SubstitutionType type);
diff --git a/tools/gn/target.cc b/tools/gn/target.cc
index 594b368..9435d47 100644
--- a/tools/gn/target.cc
+++ b/tools/gn/target.cc
@@ -222,6 +222,8 @@
       return "Action";
     case ACTION_FOREACH:
       return "ActionForEach";
+    case BUNDLE_DATA:
+      return "Bundle data";
     default:
       return "";
   }
@@ -479,6 +481,7 @@
   bool check_tool_outputs = false;
   switch (output_type_) {
     case GROUP:
+    case BUNDLE_DATA:
     case SOURCE_SET:
     case COPY_FILES:
     case ACTION:
diff --git a/tools/gn/target.h b/tools/gn/target.h
index f49fdba..9afd0fa 100644
--- a/tools/gn/target.h
+++ b/tools/gn/target.h
@@ -44,6 +44,7 @@
     COPY_FILES,
     ACTION,
     ACTION_FOREACH,
+    BUNDLE_DATA,
   };
 
   enum DepsIterationType {
diff --git a/tools/gn/target_generator.cc b/tools/gn/target_generator.cc
index b1ed1ea..0a833a0 100644
--- a/tools/gn/target_generator.cc
+++ b/tools/gn/target_generator.cc
@@ -9,6 +9,7 @@
 #include "tools/gn/action_target_generator.h"
 #include "tools/gn/binary_target_generator.h"
 #include "tools/gn/build_settings.h"
+#include "tools/gn/bundle_data_target_generator.h"
 #include "tools/gn/config.h"
 #include "tools/gn/copy_target_generator.h"
 #include "tools/gn/err.h"
@@ -87,7 +88,11 @@
   target->set_defined_from(function_call);
 
   // Create and call out to the proper generator.
-  if (output_type == functions::kCopy) {
+  if (output_type == functions::kBundleData) {
+    BundleDataTargetGenerator generator(
+        target.get(), scope, function_call, err);
+    generator.Run();
+  } else if (output_type == functions::kCopy) {
     CopyTargetGenerator generator(target.get(), scope, function_call, err);
     generator.Run();
   } else if (output_type == functions::kAction) {
diff --git a/tools/gn/toolchain.cc b/tools/gn/toolchain.cc
index 2a70358..ee8e7e8 100644
--- a/tools/gn/toolchain.cc
+++ b/tools/gn/toolchain.cc
@@ -151,9 +151,10 @@
       return Toolchain::TYPE_ALINK;
     case Target::SOURCE_SET:
       return TYPE_STAMP;
-    case Target::COPY_FILES:
     case Target::ACTION:
     case Target::ACTION_FOREACH:
+    case Target::BUNDLE_DATA:
+    case Target::COPY_FILES:
       return TYPE_STAMP;
     default:
       NOTREACHED();