[metadata] Add generated_file target type

This is a generic generated_file target type, but if the metadata attributes
are set it will act as a metadata collection.

Change-Id: Ie3a3c804ad6c69dc2db6ef8b58d705fc1105c365
Reviewed-on: https://gn-review.googlesource.com/c/3420
Commit-Queue: Julie Hockett <juliehockett@google.com>
Reviewed-by: Brett Wilson <brettw@chromium.org>
diff --git a/build/gen.py b/build/gen.py
index b5c2fbe..7a53a80 100755
--- a/build/gen.py
+++ b/build/gen.py
@@ -456,6 +456,7 @@
         'tools/gn/function_template.cc',
         'tools/gn/function_toolchain.cc',
         'tools/gn/function_write_file.cc',
+        'tools/gn/generated_file_target_generator.cc',
         'tools/gn/group_target_generator.cc',
         'tools/gn/header_checker.cc',
         'tools/gn/import_manager.cc',
@@ -478,6 +479,7 @@
         'tools/gn/ninja_bundle_data_target_writer.cc',
         'tools/gn/ninja_copy_target_writer.cc',
         'tools/gn/ninja_create_bundle_target_writer.cc',
+        'tools/gn/ninja_generated_file_target_writer.cc',
         'tools/gn/ninja_group_target_writer.cc',
         'tools/gn/ninja_target_command_util.cc',
         'tools/gn/ninja_target_writer.cc',
@@ -579,6 +581,7 @@
         'tools/gn/ninja_bundle_data_target_writer_unittest.cc',
         'tools/gn/ninja_copy_target_writer_unittest.cc',
         'tools/gn/ninja_create_bundle_target_writer_unittest.cc',
+        'tools/gn/ninja_generated_file_target_writer_unittest.cc',
         'tools/gn/ninja_group_target_writer_unittest.cc',
         'tools/gn/ninja_target_writer_unittest.cc',
         'tools/gn/ninja_toolchain_writer_unittest.cc',
diff --git a/tools/gn/command_desc.cc b/tools/gn/command_desc.cc
index 9dd38c8..36b14b6 100644
--- a/tools/gn/command_desc.cc
+++ b/tools/gn/command_desc.cc
@@ -287,7 +287,8 @@
           {variables::kPrecompiledSource, DefaultHandler},
           {variables::kDeps, DepsHandler},
           {variables::kLibs, DefaultHandler},
-          {variables::kLibDirs, DefaultHandler}};
+          {variables::kLibDirs, DefaultHandler},
+          {variables::kWriteOutputConversion, DefaultHandler}};
 }
 
 void HandleProperty(const std::string& what,
@@ -364,6 +365,7 @@
   HandleProperty(variables::kDeps, handler_map, v, dict);
   HandleProperty(variables::kLibs, handler_map, v, dict);
   HandleProperty(variables::kLibDirs, handler_map, v, dict);
+  HandleProperty(variables::kWriteOutputConversion, handler_map, v, dict);
 
 #undef HandleProperty
 
@@ -468,6 +470,7 @@
   lib_dirs
   libs
   metadata
+  output_conversion
   outputs
   public_configs
   public
diff --git a/tools/gn/desc_builder.cc b/tools/gn/desc_builder.cc
index 209e9f4..a66a419 100644
--- a/tools/gn/desc_builder.cc
+++ b/tools/gn/desc_builder.cc
@@ -51,6 +51,7 @@
 //   "libs" : [ list of libraries ],
 //   "lib_dirs" : [ list of library directories ]
 //   "metadata" : [ dictionary of target metadata values ]
+//   "output_conversion" : "string for output conversion"
 // }
 //
 // Optionally, if "what" is specified while generating description, two other
@@ -459,6 +460,14 @@
       FillInPrecompiledHeader(res.get(), target_->config_values());
     }
 
+    // GeneratedFile vars.
+    if (target_->output_type() == Target::GENERATED_FILE) {
+      if (what(variables::kWriteOutputConversion)) {
+        res->SetKey(variables::kWriteOutputConversion,
+                    std::move(ToBaseValue(target_->output_conversion())));
+      }
+    }
+
     if (what(variables::kDeps))
       res->SetWithoutPathExpansion(variables::kDeps, RenderDeps());
 
@@ -656,7 +665,8 @@
         list->AppendString(elem.AsString());
 
       res->SetWithoutPathExpansion(variables::kOutputs, std::move(list));
-    } else if (target_->output_type() == Target::CREATE_BUNDLE) {
+    } else if (target_->output_type() == Target::CREATE_BUNDLE ||
+               target_->output_type() == Target::GENERATED_FILE) {
       std::vector<SourceFile> output_files;
       target_->bundle_data().GetOutputsAsSourceFiles(target_->settings(),
                                                      &output_files);
diff --git a/tools/gn/functions.cc b/tools/gn/functions.cc
index 6abe758..d43dc59 100644
--- a/tools/gn/functions.cc
+++ b/tools/gn/functions.cc
@@ -1251,6 +1251,7 @@
     INSERT_FUNCTION(SourceSet, true)
     INSERT_FUNCTION(StaticLibrary, true)
     INSERT_FUNCTION(Target, true)
+    INSERT_FUNCTION(GeneratedFile, true)
 
     INSERT_FUNCTION(Assert, false)
     INSERT_FUNCTION(Config, false)
diff --git a/tools/gn/functions.h b/tools/gn/functions.h
index 5c707c4..97f5b0d 100644
--- a/tools/gn/functions.h
+++ b/tools/gn/functions.h
@@ -195,6 +195,15 @@
                           const std::vector<Value>& args,
                           Err* err);
 
+extern const char kGeneratedFile[];
+extern const char kGeneratedFile_HelpShort[];
+extern const char kGeneratedFile_Help[];
+Value RunGeneratedFile(Scope* scope,
+                       const FunctionCallNode* function,
+                       const std::vector<Value>& args,
+                       BlockNode* block,
+                       Err* err);
+
 extern const char kGroup[];
 extern const char kGroup_HelpShort[];
 extern const char kGroup_Help[];
diff --git a/tools/gn/functions_target.cc b/tools/gn/functions_target.cc
index 008b47e..282b793 100644
--- a/tools/gn/functions_target.cc
+++ b/tools/gn/functions_target.cc
@@ -772,4 +772,44 @@
                               block, err);
 }
 
+const char kGeneratedFile[] = "generated_file";
+const char kGeneratedFile_HelpShort[] =
+    "generated_file: Declare a generated_file target.";
+const char kGeneratedFile_Help[] =
+    R"(generated_file: Declare a generated_file target.
+
+  Writes data value(s) to disk on resolution. This target type mirrors some
+  functionality of the write_file() function, but also provides the ability to
+  collect metadata from its dependencies on resolution rather than writing out
+  parse time.
+
+  The `outputs` variable is required to be a list with a single element,
+  specifying the intended location of the output file.
+
+  The `output_conversion` variable specified the format to write the
+  value. See `gn help output_conversion`.
+
+  One of `data` or `data_keys` must be specified; use of `data` will write the
+  contents of that value to file, while use of `data_keys` will trigger a
+  metadata collection walk based on the dependencies of the target and the
+  optional values of the `rebase` and `walk_keys` variables. See
+  `gn help metadata`.
+
+Variables
+
+  data_keys
+  rebase
+  walk_keys
+  output_conversion
+)" DEPS_VARS DEPENDENT_CONFIG_VARS;
+
+Value RunGeneratedFile(Scope* scope,
+                       const FunctionCallNode* function,
+                       const std::vector<Value>& args,
+                       BlockNode* block,
+                       Err* err) {
+  return ExecuteGenericTarget(functions::kGeneratedFile, scope, function, args,
+                              block, err);
+}
+
 }  // namespace functions
diff --git a/tools/gn/generated_file_target_generator.cc b/tools/gn/generated_file_target_generator.cc
new file mode 100644
index 0000000..96a9f37
--- /dev/null
+++ b/tools/gn/generated_file_target_generator.cc
@@ -0,0 +1,69 @@
+// Copyright 2018 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/generated_file_target_generator.h"
+
+#include "tools/gn/err.h"
+#include "tools/gn/filesystem_utils.h"
+#include "tools/gn/parse_tree.h"
+#include "tools/gn/scope.h"
+#include "tools/gn/target.h"
+#include "tools/gn/variables.h"
+
+GeneratedFileTargetGenerator::GeneratedFileTargetGenerator(
+    Target* target,
+    Scope* scope,
+    const FunctionCallNode* function_call,
+    Target::OutputType type,
+    Err* err)
+    : TargetGenerator(target, scope, function_call, err), output_type_(type) {}
+
+GeneratedFileTargetGenerator::~GeneratedFileTargetGenerator() = default;
+
+void GeneratedFileTargetGenerator::DoRun() {
+  target_->set_output_type(output_type_);
+
+  if (!FillOutputs(false))
+    return;
+  if (target_->action_values().outputs().list().size() != 1) {
+    *err_ = Err(
+        function_call_, "generated_file target must have exactly one output.",
+        "You must specify exactly one value in the \"outputs\" array for the "
+        "destination of the write\n(see \"gn help generated_file\").");
+    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\".");
+    return;
+  }
+
+  if (!FillOutputConversion())
+    return;
+}
+
+bool GeneratedFileTargetGenerator::FillContents() {
+  const Value* value = scope_->GetValue(variables::kWriteValueContents, true);
+  if (!value)
+    return false;
+  target_->set_contents(*value);
+  return true;
+}
+
+bool GeneratedFileTargetGenerator::FillOutputConversion() {
+  const Value* value =
+      scope_->GetValue(variables::kWriteOutputConversion, true);
+  if (!value) {
+    target_->set_output_conversion(Value(function_call_, ""));
+    return true;
+  }
+  if (!value->VerifyTypeIs(Value::STRING, err_))
+    return false;
+
+  // Otherwise, the value itself will be checked when the conversion is done.
+  target_->set_output_conversion(*value);
+  return true;
+}
diff --git a/tools/gn/generated_file_target_generator.h b/tools/gn/generated_file_target_generator.h
new file mode 100644
index 0000000..6db9fb1
--- /dev/null
+++ b/tools/gn/generated_file_target_generator.h
@@ -0,0 +1,35 @@
+// Copyright 2018 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_GENERATED_FILE_TARGET_GENERATOR_H_
+#define TOOLS_GN_GENERATED_FILE_TARGET_GENERATOR_H_
+
+#include "base/macros.h"
+#include "tools/gn/target.h"
+#include "tools/gn/target_generator.h"
+
+// Collects and writes specified data.
+class GeneratedFileTargetGenerator : public TargetGenerator {
+ public:
+  GeneratedFileTargetGenerator(Target* target,
+                               Scope* scope,
+                               const FunctionCallNode* function_call,
+                               Target::OutputType type,
+                               Err* err);
+  ~GeneratedFileTargetGenerator() override;
+
+ protected:
+  void DoRun() override;
+
+ private:
+  bool FillGeneratedFileOutput();
+  bool FillOutputConversion();
+  bool FillContents();
+
+  Target::OutputType output_type_;
+
+  DISALLOW_COPY_AND_ASSIGN(GeneratedFileTargetGenerator);
+};
+
+#endif  // TOOLS_GN_GENERATED_FILE_TARGET_GENERATOR_H_
diff --git a/tools/gn/misc/emacs/gn-mode.el b/tools/gn/misc/emacs/gn-mode.el
index 38564c6..872bfd1 100644
--- a/tools/gn/misc/emacs/gn-mode.el
+++ b/tools/gn/misc/emacs/gn-mode.el
@@ -63,7 +63,7 @@
 (defvar gn-font-lock-target-declaration-keywords
   '("action" "action_foreach" "bundle_data" "copy" "create_bundle" "executable"
     "group" "loadable_module" "shared_library" "source_set" "static_library"
-    "target"))
+    "generated_file" "target"))
 
 ;; pool() is handled specially since it's also a variable name
 (defvar gn-font-lock-buildfile-fun-keywords
@@ -92,7 +92,8 @@
     "output_prefix_override" "outputs" "pool" "precompiled_header"
     "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"))
+    "testonly" "visibility" "write_runtime_deps" "bundle_contents_dir"
+    "contents" "output_conversion"))
 
 (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 9c244ff..1d48889 100644
--- a/tools/gn/misc/tm/GN.tmLanguage
+++ b/tools/gn/misc/tm/GN.tmLanguage
@@ -65,7 +65,7 @@
       <key>comment</key>
       <string>targets</string>
       <key>match</key>
-      <string>\b(?:action|action_foreach|copy|executable|group|loadable_module|shared_library|source_set|static_library)\b</string>
+      <string>\b(?:action|action_foreach|copy|executable|group|loadable_module|shared_library|source_set|static_library|generated_file)\b</string>
       <key>name</key>
       <string>entity.name.tag.gn</string>
     </dict>
@@ -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)\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)\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 9dee605..a50f550 100644
--- a/tools/gn/misc/vim/syntax/gn.vim
+++ b/tools/gn/misc/vim/syntax/gn.vim
@@ -27,7 +27,7 @@
 " Target declarations
 syn keyword     gnTarget action action_foreach copy executable group
 syn keyword     gnTarget shared_library source_set static_library
-syn keyword     gnTarget loadable_module
+syn keyword     gnTarget loadable_module generated_file
 hi def link     gnTarget            Type
 
 " Buildfile functions
@@ -48,7 +48,7 @@
 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
+syn keyword     gnVariable visibility contents output_conversion
 hi def link     gnVariable          Keyword
 
 " Strings
diff --git a/tools/gn/ninja_generated_file_target_writer.cc b/tools/gn/ninja_generated_file_target_writer.cc
new file mode 100644
index 0000000..bf57f0c
--- /dev/null
+++ b/tools/gn/ninja_generated_file_target_writer.cc
@@ -0,0 +1,71 @@
+// Copyright 2018 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_generated_file_target_writer.h"
+
+#include "base/strings/string_util.h"
+#include "tools/gn/deps_iterator.h"
+#include "tools/gn/filesystem_utils.h"
+#include "tools/gn/output_conversion.h"
+#include "tools/gn/output_file.h"
+#include "tools/gn/scheduler.h"
+#include "tools/gn/settings.h"
+#include "tools/gn/string_utils.h"
+#include "tools/gn/target.h"
+#include "tools/gn/trace.h"
+
+NinjaGeneratedFileTargetWriter::NinjaGeneratedFileTargetWriter(
+    const Target* target,
+    std::ostream& out)
+    : NinjaTargetWriter(target, out) {}
+
+NinjaGeneratedFileTargetWriter::~NinjaGeneratedFileTargetWriter() = default;
+
+void NinjaGeneratedFileTargetWriter::Run() {
+  // Write the file.
+  GenerateFile();
+
+  // A generated_file target should generate a stamp file with dependencies
+  // on each of the deps and data_deps in the target. The actual collection is
+  // done at gen time, and so ninja doesn't need to know about it.
+  std::vector<OutputFile> output_files;
+  for (const auto& pair : target_->GetDeps(Target::DEPS_LINKED))
+    output_files.push_back(pair.ptr->dependency_output_file());
+
+  std::vector<OutputFile> data_output_files;
+  const LabelTargetVector& data_deps = target_->data_deps();
+  for (const auto& pair : data_deps)
+    data_output_files.push_back(pair.ptr->dependency_output_file());
+
+  WriteStampForTarget(output_files, data_output_files);
+}
+
+void NinjaGeneratedFileTargetWriter::GenerateFile() {
+  std::vector<SourceFile> outputs_as_sources;
+  target_->action_values().GetOutputsAsSourceFiles(target_,
+                                                   &outputs_as_sources);
+  CHECK(outputs_as_sources.size() == 1);
+
+  base::FilePath output =
+      settings_->build_settings()->GetFullPath(outputs_as_sources[0]);
+  ScopedTrace trace(TraceItem::TRACE_FILE_WRITE, outputs_as_sources[0].value());
+
+  // Compute output.
+  Err err;
+  std::ostringstream contents;
+  ConvertValueToOutput(settings_, target_->contents(),
+                       target_->output_conversion(), contents, &err);
+
+  if (err.has_error()) {
+    g_scheduler->FailWithError(err);
+    return;
+  }
+
+  WriteFileIfChanged(output, contents.str(), &err);
+
+  if (err.has_error()) {
+    g_scheduler->FailWithError(err);
+    return;
+  }
+}
diff --git a/tools/gn/ninja_generated_file_target_writer.h b/tools/gn/ninja_generated_file_target_writer.h
new file mode 100644
index 0000000..fa2e6c2
--- /dev/null
+++ b/tools/gn/ninja_generated_file_target_writer.h
@@ -0,0 +1,25 @@
+// Copyright 2018 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_GENERATED_FILE_TARGET_WRITER_H_
+#define TOOLS_GN_NINJA_GENERATED_FILE_TARGET_WRITER_H_
+
+#include "base/macros.h"
+#include "tools/gn/ninja_target_writer.h"
+
+// Writes a .ninja file for a group target type.
+class NinjaGeneratedFileTargetWriter : public NinjaTargetWriter {
+ public:
+  NinjaGeneratedFileTargetWriter(const Target* target, std::ostream& out);
+  ~NinjaGeneratedFileTargetWriter() override;
+
+  void Run() override;
+
+ private:
+  void GenerateFile();
+
+  DISALLOW_COPY_AND_ASSIGN(NinjaGeneratedFileTargetWriter);
+};
+
+#endif  // TOOLS_GN_NINJA_GENERATED_FILE_TARGET_WRITER_H_
diff --git a/tools/gn/ninja_generated_file_target_writer_unittest.cc b/tools/gn/ninja_generated_file_target_writer_unittest.cc
new file mode 100644
index 0000000..a01ef7b
--- /dev/null
+++ b/tools/gn/ninja_generated_file_target_writer_unittest.cc
@@ -0,0 +1,60 @@
+// Copyright 2014 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_generated_file_target_writer.h"
+
+#include "tools/gn/source_file.h"
+#include "tools/gn/target.h"
+#include "tools/gn/test_with_scheduler.h"
+#include "tools/gn/test_with_scope.h"
+#include "util/test/test.h"
+
+using NinjaGeneratedFileTargetWriterTest = TestWithScheduler;
+
+TEST_F(NinjaGeneratedFileTargetWriterTest, Run) {
+  Err err;
+  TestWithScope setup;
+
+  Target target(setup.settings(), Label(SourceDir("//foo/"), "bar"));
+  target.set_output_type(Target::GENERATED_FILE);
+  target.visibility().SetPublic();
+  target.action_values().outputs() =
+      SubstitutionList::MakeForTest("//out/Debug/foo.json");
+  target.set_contents(Value(nullptr, true));
+  target.set_output_conversion(Value(nullptr, "json"));
+
+  Target dep(setup.settings(), Label(SourceDir("//foo/"), "dep"));
+  dep.set_output_type(Target::ACTION);
+  dep.visibility().SetPublic();
+  dep.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(dep.OnResolved(&err));
+
+  Target dep2(setup.settings(), Label(SourceDir("//foo/"), "dep2"));
+  dep2.set_output_type(Target::ACTION);
+  dep2.visibility().SetPublic();
+  dep2.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(dep2.OnResolved(&err));
+
+  Target datadep(setup.settings(), Label(SourceDir("//foo/"), "datadep"));
+  datadep.set_output_type(Target::ACTION);
+  datadep.visibility().SetPublic();
+  datadep.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(datadep.OnResolved(&err));
+
+  target.public_deps().push_back(LabelTargetPair(&dep));
+  target.public_deps().push_back(LabelTargetPair(&dep2));
+  target.data_deps().push_back(LabelTargetPair(&datadep));
+
+  target.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(target.OnResolved(&err)) << err.message();
+
+  std::ostringstream out;
+  NinjaGeneratedFileTargetWriter writer(&target, out);
+  writer.Run();
+
+  const char expected[] =
+      "build obj/foo/bar.stamp: stamp obj/foo/dep.stamp obj/foo/dep2.stamp || "
+      "obj/foo/datadep.stamp\n";
+  EXPECT_EQ(expected, out.str());
+}
diff --git a/tools/gn/ninja_target_writer.cc b/tools/gn/ninja_target_writer.cc
index de87e8f..253436b 100644
--- a/tools/gn/ninja_target_writer.cc
+++ b/tools/gn/ninja_target_writer.cc
@@ -17,6 +17,7 @@
 #include "tools/gn/ninja_bundle_data_target_writer.h"
 #include "tools/gn/ninja_copy_target_writer.h"
 #include "tools/gn/ninja_create_bundle_target_writer.h"
+#include "tools/gn/ninja_generated_file_target_writer.h"
 #include "tools/gn/ninja_group_target_writer.h"
 #include "tools/gn/ninja_utils.h"
 #include "tools/gn/output_file.h"
@@ -83,6 +84,9 @@
   } else if (target->output_type() == Target::GROUP) {
     NinjaGroupTargetWriter writer(target, rules);
     writer.Run();
+  } else if (target->output_type() == Target::GENERATED_FILE) {
+    NinjaGeneratedFileTargetWriter writer(target, rules);
+    writer.Run();
   } else if (target->IsBinary()) {
     needs_file_write = true;
     NinjaBinaryTargetWriter writer(target, rules);
diff --git a/tools/gn/output_conversion.cc b/tools/gn/output_conversion.cc
index 70c21fa..7f62896 100644
--- a/tools/gn/output_conversion.cc
+++ b/tools/gn/output_conversion.cc
@@ -135,6 +135,10 @@
   if (output_conversion == "") {
     OutputDefault(output, out);
   } else if (output_conversion == "list lines") {
+    if (output.type() != Value::LIST) {
+      *err = Err(original_output_conversion, "Not a valid list.");
+      return;
+    }
     OutputListLines(output, out);
   } else if (output_conversion == "string") {
     OutputString(output, out);
diff --git a/tools/gn/target.cc b/tools/gn/target.cc
index bcddf8b..560f6d0 100644
--- a/tools/gn/target.cc
+++ b/tools/gn/target.cc
@@ -318,6 +318,8 @@
       return functions::kBundleData;
     case CREATE_BUNDLE:
       return functions::kCreateBundle;
+    case GENERATED_FILE:
+      return functions::kGeneratedFile;
     default:
       return "";
   }
@@ -631,7 +633,8 @@
     case SOURCE_SET:
     case COPY_FILES:
     case ACTION:
-    case ACTION_FOREACH: {
+    case ACTION_FOREACH:
+    case GENERATED_FILE: {
       // These don't get linked to and use stamps which should be the first
       // entry in the outputs. These stamps are named
       // "<target_out_dir>/<targetname>.stamp".
diff --git a/tools/gn/target.h b/tools/gn/target.h
index 474f9c9..91553f6 100644
--- a/tools/gn/target.h
+++ b/tools/gn/target.h
@@ -46,6 +46,7 @@
     ACTION_FOREACH,
     BUNDLE_DATA,
     CREATE_BUNDLE,
+    GENERATED_FILE,
   };
 
   enum DepsIterationType {
@@ -149,7 +150,7 @@
   const Metadata& metadata() const { return metadata_; }
   Metadata& metadata() { return metadata_; }
 
-  // Collect metadata from this target and its dependencies. This is intended to
+  // Get metadata from this target and its dependencies. This is intended to
   // be called after the target is resolved.
   bool GetMetadata(const std::vector<std::string>& keys_to_extract,
                    const std::vector<std::string>& keys_to_walk,
@@ -158,6 +159,14 @@
                    std::set<const Target*>* targets_walked,
                    Err* err) const;
 
+  // GeneratedFile-related methods.
+  bool GenerateFile(Err* err) const;
+
+  const Value& contents() const { return contents_; }
+  void set_contents(const Value& value) { contents_ = value; }
+  const Value& output_conversion() const { return output_conversion_; }
+  void set_output_conversion(const Value& value) { output_conversion_ = value; }
+
   bool testonly() const { return testonly_; }
   void set_testonly(bool value) { testonly_ = value; }
 
@@ -184,7 +193,7 @@
   bool hard_dep() const {
     return output_type_ == ACTION || output_type_ == ACTION_FOREACH ||
            output_type_ == COPY_FILES || output_type_ == CREATE_BUNDLE ||
-           output_type_ == BUNDLE_DATA;
+           output_type_ == BUNDLE_DATA || output_type_ == GENERATED_FILE;
   }
 
   // Returns the iterator range which can be used in range-based for loops
@@ -404,6 +413,10 @@
 
   Metadata metadata_;
 
+  // GenerateFile values.
+  Value output_conversion_;
+  Value contents_;  // Value::NONE if metadata collection should occur.
+
   DISALLOW_COPY_AND_ASSIGN(Target);
 };
 
diff --git a/tools/gn/target_generator.cc b/tools/gn/target_generator.cc
index c7a9f08..9ad8969 100644
--- a/tools/gn/target_generator.cc
+++ b/tools/gn/target_generator.cc
@@ -19,6 +19,7 @@
 #include "tools/gn/err.h"
 #include "tools/gn/filesystem_utils.h"
 #include "tools/gn/functions.h"
+#include "tools/gn/generated_file_target_generator.h"
 #include "tools/gn/group_target_generator.h"
 #include "tools/gn/metadata.h"
 #include "tools/gn/parse_tree.h"
@@ -139,6 +140,10 @@
     BinaryTargetGenerator generator(target.get(), scope, function_call,
                                     Target::STATIC_LIBRARY, err);
     generator.Run();
+  } else if (output_type == functions::kGeneratedFile) {
+    GeneratedFileTargetGenerator generator(target.get(), scope, function_call,
+                                           Target::GENERATED_FILE, err);
+    generator.Run();
   } else {
     *err = Err(function_call, "Not a known target type",
                "I am very confused by the target type \"" + output_type + "\"");
diff --git a/tools/gn/toolchain.cc b/tools/gn/toolchain.cc
index e34acb5..d9aff43 100644
--- a/tools/gn/toolchain.cc
+++ b/tools/gn/toolchain.cc
@@ -180,6 +180,7 @@
     const Target* target) {
   // The contents of this list might be suprising (i.e. stamp tool for copy
   // rules). See the header for why.
+  // TODO(crbug.com/gn/39): Don't emit stamp files for single-output targets.
   switch (target->output_type()) {
     case Target::GROUP:
       return TYPE_STAMP;
@@ -198,6 +199,7 @@
     case Target::BUNDLE_DATA:
     case Target::CREATE_BUNDLE:
     case Target::COPY_FILES:
+    case Target::GENERATED_FILE:
       return TYPE_STAMP;
     default:
       NOTREACHED();
diff --git a/tools/gn/variables.cc b/tools/gn/variables.cc
index 16de86f..931261d 100644
--- a/tools/gn/variables.cc
+++ b/tools/gn/variables.cc
@@ -2009,6 +2009,26 @@
     visibility = [ "./*", "//bar/*" ]
 )";
 
+const char kWriteValueContents[] = "contents";
+const char kWriteValueContents_HelpShort[] =
+    "contents: Contents to write to file.";
+const char kWriteValueContents_Help[] =
+    R"(contents: Contents to write to file.
+
+  The contents of the file for a generated_file target.
+  See "gn help generated_file".
+)";
+
+const char kWriteOutputConversion[] = "output_conversion";
+const char kWriteOutputConversion_HelpShort[] =
+    "output_conversion: Data format for generated_file targets.";
+const char kWriteOutputConversion_Help[] =
+    R"("output_conversion: Data format for generated_file targets.
+
+  Controls how the "contents" of a generated_file target is formatted.
+  See "gn help output_conversion".
+)";
+
 const char kWriteRuntimeDeps[] = "write_runtime_deps";
 const char kWriteRuntimeDeps_HelpShort[] =
     "write_runtime_deps: Writes the target's runtime_deps to the given path.";
@@ -2138,6 +2158,8 @@
     INSERT_VARIABLE(XcodeTestApplicationName)
     INSERT_VARIABLE(Testonly)
     INSERT_VARIABLE(Visibility)
+    INSERT_VARIABLE(WriteOutputConversion)
+    INSERT_VARIABLE(WriteValueContents)
     INSERT_VARIABLE(WriteRuntimeDeps)
     INSERT_VARIABLE(XcodeExtraAttributes)
   }
diff --git a/tools/gn/variables.h b/tools/gn/variables.h
index a90cb00..eb0190a 100644
--- a/tools/gn/variables.h
+++ b/tools/gn/variables.h
@@ -303,6 +303,14 @@
 extern const char kVisibility_HelpShort[];
 extern const char kVisibility_Help[];
 
+extern const char kWriteValueContents[];
+extern const char kWriteValueContents_HelpShort[];
+extern const char kWriteValueContents_Help[];
+
+extern const char kWriteOutputConversion[];
+extern const char kWriteOutputConversion_HelpShort[];
+extern const char kWriteOutputConversion_Help[];
+
 extern const char kWriteRuntimeDeps[];
 extern const char kWriteRuntimeDeps_HelpShort[];
 extern const char kWriteRuntimeDeps_Help[];