[GN] Add support for generating Xcode projects.

tools/gn/xcode_object.{cc,h}

  Implements a class hierarchy mimicking the structure of an Xcode
  pbxproj file with methods to dump them as text file.

tools/gn/xcode_writer.{cc,h}

  Implements generation of Xcode project structure from GN settings
  and for dumping the output to pbxproj and xcworkspacedata files.

tools/gn/*

  Add product_type field to create_bundle target. This is used when
  generating the Xcode project file to use the correct product type
  in Xcode.

  Fix a minor DCHECK failure in create_bundle_target_generator.cc.

build/config/compiler/BUILD.gn

  Remove -fdebug-prefix-map from the clang command-line as it does
  not work with Xcode (and is unnecessary as the path given to the
  compiler are relative already).

  With this flag, Xcode cannot find the source file when debbugging
  the application, without it breakpoint and displaying code work.

BUG=597975

Review-Url: https://codereview.chromium.org/1827103005
Cr-Original-Commit-Position: refs/heads/master@{#391377}
Cr-Mirrored-From: https://chromium.googlesource.com/chromium/src
Cr-Mirrored-Commit: 6ee04b5ce48981fd997b94c58240602df1a329d6
diff --git a/tools/gn/BUILD.gn b/tools/gn/BUILD.gn
index 204d909..d377ca3 100644
--- a/tools/gn/BUILD.gn
+++ b/tools/gn/BUILD.gn
@@ -207,6 +207,10 @@
     "visual_studio_utils.h",
     "visual_studio_writer.cc",
     "visual_studio_writer.h",
+    "xcode_object.cc",
+    "xcode_object.h",
+    "xcode_writer.cc",
+    "xcode_writer.h",
     "xml_element_writer.cc",
     "xml_element_writer.h",
   ]
diff --git a/tools/gn/DEPS b/tools/gn/DEPS
new file mode 100644
index 0000000..0de07bb
--- /dev/null
+++ b/tools/gn/DEPS
@@ -0,0 +1,3 @@
+include_rules = [
+  "+third_party/re2",
+]
diff --git a/tools/gn/bundle_data.h b/tools/gn/bundle_data.h
index d742674..9a2ea07 100644
--- a/tools/gn/bundle_data.h
+++ b/tools/gn/bundle_data.h
@@ -98,6 +98,9 @@
   SourceDir& plugins_dir() { return plugins_dir_; }
   const SourceDir& plugins_dir() const { return plugins_dir_; }
 
+  std::string& product_type() { return product_type_; }
+  const std::string& product_type() const { return product_type_; }
+
   // Recursive collection of all bundle_data that the target depends on.
   const UniqueTargets& bundle_deps() const { return bundle_deps_; }
 
@@ -112,6 +115,12 @@
   SourceDir resources_dir_;
   SourceDir executable_dir_;
   SourceDir plugins_dir_;
+
+  // This is the target type as known to Xcode. This is only used to generate
+  // the Xcode project file when using --ide=xcode.
+  std::string product_type_;
+
+  DISALLOW_COPY_AND_ASSIGN(BundleData);
 };
 
 #endif  // TOOLS_GN_BUNDLE_DATA_H_
diff --git a/tools/gn/command_gen.cc b/tools/gn/command_gen.cc
index fee3f1a..74a5d90 100644
--- a/tools/gn/command_gen.cc
+++ b/tools/gn/command_gen.cc
@@ -20,6 +20,7 @@
 #include "tools/gn/switches.h"
 #include "tools/gn/target.h"
 #include "tools/gn/visual_studio_writer.h"
+#include "tools/gn/xcode_writer.h"
 
 namespace commands {
 
@@ -32,7 +33,11 @@
 const char kSwitchIdeValueVs[] = "vs";
 const char kSwitchIdeValueVs2013[] = "vs2013";
 const char kSwitchIdeValueVs2015[] = "vs2015";
+const char kSwitchIdeValueXcode[] = "xcode";
+const char kSwitchNinjaExtraArgs[] = "ninja-extra-args";
+const char kSwitchRootTarget[] = "root-target";
 const char kSwitchSln[] = "sln";
+const char kSwitchWorkspace[] = "workspace";
 
 // Called on worker thread to write the ninja file.
 void BackgroundDoWrite(const Target* target) {
@@ -188,6 +193,19 @@
                    "ms\n");
     }
     return res;
+  } else if (ide == kSwitchIdeValueXcode) {
+    bool res = XcodeWriter::RunAndWriteFiles(
+        command_line->GetSwitchValueASCII(kSwitchWorkspace),
+        command_line->GetSwitchValueASCII(kSwitchRootTarget),
+        command_line->GetSwitchValueASCII(kSwitchNinjaExtraArgs),
+        command_line->GetSwitchValueASCII(kSwitchFilters), build_settings,
+        builder, err);
+    if (res && !command_line->HasSwitch(switches::kQuiet)) {
+      OutputString("Generating Xcode projects took " +
+                   base::Int64ToString(timer.Elapsed().InMilliseconds()) +
+                   "ms\n");
+    }
+    return res;
   }
 
   *err = Err(Location(), "Unknown IDE: " + ide);
@@ -225,16 +243,35 @@
     "             (default Visual Studio version: 2015)\n"
     "      \"vs2013\" - Visual Studio 2013 project/solution files.\n"
     "      \"vs2015\" - Visual Studio 2015 project/solution files.\n"
-    "\n"
-    "  --sln=<file_name>\n"
-    "      Override default sln file name (\"all\"). Solution file is written\n"
-    "      to the root build directory. Only for Visual Studio.\n"
+    "      \"xcode\" - Xcode workspace/solution files.\n"
     "\n"
     "  --filters=<path_prefixes>\n"
     "      Semicolon-separated list of label patterns used to limit the set\n"
     "      of generated projects (see \"gn help label_pattern\"). Only\n"
-    "      matching targets will be included to the solution. Only for Visual\n"
-    "      Studio.\n"
+    "      matching targets will be included to the solution. Only used for\n"
+    "      Visual Studio and Xcode.\n"
+    "\n"
+    "Visual Studio Flags\n"
+    "\n"
+    "  --sln=<file_name>\n"
+    "      Override default sln file name (\"all\"). Solution file is written\n"
+    "      to the root build directory.\n"
+    "\n"
+    "Xcode Flags\n"
+    "\n"
+    "  --workspace=<file_name>\n"
+    "      Override defaut workspace file name (\"all\"). The workspace file\n"
+    "      is written to the root build directory.\n"
+    "\n"
+    "  --ninja-extra-args=<string>\n"
+    "      This string is passed without any quoting to the ninja invocation\n"
+    "      command-line. Can be used to configure ninja flags, like \"-j\" if\n"
+    "      using goma for example.\n"
+    "\n"
+    "  --root-target=<target_name>\n"
+    "      Name of the target corresponding to \"All\" target in Xcode. If\n"
+    "      unset, \"All\" invokes ninja without any target thus build all the\n"
+    "      targets.\n"
     "\n"
     "Eclipse IDE Support\n"
     "\n"
diff --git a/tools/gn/commands.cc b/tools/gn/commands.cc
index ed7a009..8682d92 100644
--- a/tools/gn/commands.cc
+++ b/tools/gn/commands.cc
@@ -5,6 +5,7 @@
 #include "tools/gn/commands.h"
 
 #include "base/command_line.h"
+#include "base/strings/string_split.h"
 #include "tools/gn/builder.h"
 #include "tools/gn/filesystem_utils.h"
 #include "tools/gn/item.h"
@@ -468,6 +469,27 @@
   }
 }
 
+bool FilterPatternsFromString(const BuildSettings* build_settings,
+                              const std::string& label_list_string,
+                              std::vector<LabelPattern>* filters,
+                              Err* err) {
+  std::vector<std::string> tokens = base::SplitString(
+      label_list_string, ";", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
+  SourceDir root_dir =
+      SourceDirForCurrentDirectory(build_settings->root_path());
+
+  filters->reserve(tokens.size());
+  for (const std::string& token : tokens) {
+    LabelPattern pattern =
+        LabelPattern::GetPattern(root_dir, Value(nullptr, token), err);
+    if (err->has_error())
+      return false;
+    filters->push_back(pattern);
+  }
+
+  return true;
+}
+
 void FilterAndPrintTargets(bool indent, std::vector<const Target*>* targets) {
   if (targets->empty())
     return;
diff --git a/tools/gn/commands.h b/tools/gn/commands.h
index 9d8af5c..fdd10fc 100644
--- a/tools/gn/commands.h
+++ b/tools/gn/commands.h
@@ -140,6 +140,12 @@
                              const std::vector<LabelPattern>& filter,
                              UniqueVector<const Target*>* output);
 
+// Builds a list of pattern from a semicolon-separated list of labels.
+bool FilterPatternsFromString(const BuildSettings* build_settings,
+                              const std::string& label_list_string,
+                              std::vector<LabelPattern>* filters,
+                              Err* err);
+
 // These are the documentation strings for the command-line flags used by
 // FilterAndPrintTargets. Commands that call that function should incorporate
 // these into their help.
diff --git a/tools/gn/create_bundle_target_generator.cc b/tools/gn/create_bundle_target_generator.cc
index 6085419..8374998 100644
--- a/tools/gn/create_bundle_target_generator.cc
+++ b/tools/gn/create_bundle_target_generator.cc
@@ -41,6 +41,14 @@
                     variables::kBundlePlugInsDir,
                     &bundle_data.plugins_dir()))
     return;
+
+  const Value* value = scope_->GetValue(variables::kProductType, true);
+  if (value) {
+    if (!value->VerifyTypeIs(Value::STRING, err_))
+      return;
+
+    bundle_data.product_type().assign(value->string_value());
+  }
 }
 
 bool CreateBundleTargetGenerator::GetBundleDir(
@@ -53,6 +61,8 @@
   if (!value->VerifyTypeIs(Value::STRING, err_))
     return false;
   std::string str = value->string_value();
+  if (!str.empty() && str[str.size() - 1] != '/')
+    str.push_back('/');
   if (!EnsureStringIsInOutputDir(GetBuildSettings()->build_dir(), str,
                                  value->origin(), err_))
     return false;
diff --git a/tools/gn/functions_target.cc b/tools/gn/functions_target.cc
index 7ec5a50..cc863ac 100644
--- a/tools/gn/functions_target.cc
+++ b/tools/gn/functions_target.cc
@@ -331,7 +331,8 @@
     "Variables\n"
     "\n"
     "  bundle_root_dir*, bundle_resources_dir*, bundle_executable_dir*,\n"
-    "  bundle_plugins_dir*, deps, data_deps, public_deps, visibility\n"
+    "  bundle_plugins_dir*, deps, data_deps, public_deps, visibility,\n"
+    "  product_type\n"
     "  * = required\n"
     "\n"
     "Example\n"
@@ -378,6 +379,7 @@
     "      }\n"
     "\n"
     "      create_bundle(\"${app_name}.app\") {\n"
+    "        product_type = \"com.apple.product-type.application\"\n"
     "        deps = [\n"
     "          \":${app_name}_bundle_executable\",\n"
     "          \":${app_name}_bundle_info_plist\",\n"
diff --git a/tools/gn/gn.gyp b/tools/gn/gn.gyp
index 79f83a7..86b5d4b 100644
--- a/tools/gn/gn.gyp
+++ b/tools/gn/gn.gyp
@@ -207,6 +207,10 @@
         'visual_studio_utils.h',
         'visual_studio_writer.cc',
         'visual_studio_writer.h',
+        'xcode_object.cc',
+        'xcode_object.h',
+        'xcode_writer.cc',
+        'xcode_writer.h',
         'xml_element_writer.cc',
         'xml_element_writer.h',
       ],
diff --git a/tools/gn/variables.cc b/tools/gn/variables.cc
index eea5bca..a65d940 100644
--- a/tools/gn/variables.cc
+++ b/tools/gn/variables.cc
@@ -1326,6 +1326,19 @@
     "  using \"msvc\"-style precompiled headers. It will be implicitly added\n"
     "  to the sources of the target. See \"gn help precompiled_header\".\n";
 
+const char kProductType[] = "product_type";
+const char kProductType_HelpShort[] =
+    "product_type: [string] Product type for Xcode projects.";
+const char kProductType_Help[] =
+    "product_type: Product type for Xcode projects.\n"
+    "\n"
+    "  Correspond to the type of the product of a create_bundle target. Only\n"
+    "  meaningful to Xcode (used as part of the Xcode project generation).\n"
+    "\n"
+    "  When generating Xcode project files, only create_bundle target with\n"
+    "  a non-empty product_type will have a corresponding target in Xcode\n"
+    "  project.\n";
+
 const char kPublic[] = "public";
 const char kPublic_HelpShort[] =
     "public: [file list] Declare public header files for a target.";
@@ -1698,6 +1711,7 @@
     INSERT_VARIABLE(Outputs)
     INSERT_VARIABLE(PrecompiledHeader)
     INSERT_VARIABLE(PrecompiledSource)
+    INSERT_VARIABLE(ProductType)
     INSERT_VARIABLE(Public)
     INSERT_VARIABLE(PublicConfigs)
     INSERT_VARIABLE(PublicDeps)
diff --git a/tools/gn/variables.h b/tools/gn/variables.h
index 2523c1f..0aaa8e0 100644
--- a/tools/gn/variables.h
+++ b/tools/gn/variables.h
@@ -215,6 +215,10 @@
 extern const char kPrecompiledSource_HelpShort[];
 extern const char kPrecompiledSource_Help[];
 
+extern const char kProductType[];
+extern const char kProductType_HelpShort[];
+extern const char kProductType_Help[];
+
 extern const char kPublic[];
 extern const char kPublic_HelpShort[];
 extern const char kPublic_Help[];
diff --git a/tools/gn/visual_studio_writer.cc b/tools/gn/visual_studio_writer.cc
index 87dfa81..50d6878 100644
--- a/tools/gn/visual_studio_writer.cc
+++ b/tools/gn/visual_studio_writer.cc
@@ -12,7 +12,6 @@
 #include <string>
 
 #include "base/logging.h"
-#include "base/strings/string_split.h"
 #include "base/strings/string_util.h"
 #include "base/strings/utf_string_conversions.h"
 #include "tools/gn/builder.h"
@@ -237,18 +236,10 @@
   if (dir_filters.empty()) {
     targets = builder->GetAllResolvedTargets();
   } else {
-    std::vector<std::string> tokens = base::SplitString(
-        dir_filters, ";", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
-    SourceDir root_dir =
-        SourceDirForCurrentDirectory(build_settings->root_path());
-
     std::vector<LabelPattern> filters;
-    for (const std::string& token : tokens) {
-      LabelPattern pattern =
-          LabelPattern::GetPattern(root_dir, Value(nullptr, token), err);
-      if (err->has_error())
-        return false;
-      filters.push_back(pattern);
+    if (!commands::FilterPatternsFromString(build_settings, dir_filters,
+                                            &filters, err)) {
+      return false;
     }
 
     commands::FilterTargetsByPatterns(builder->GetAllResolvedTargets(), filters,
diff --git a/tools/gn/xcode_object.cc b/tools/gn/xcode_object.cc
new file mode 100644
index 0000000..bc89944
--- /dev/null
+++ b/tools/gn/xcode_object.cc
@@ -0,0 +1,858 @@
+// 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/xcode_object.h"
+
+#include <iomanip>
+#include <sstream>
+#include <utility>
+
+#include "base/files/file_path.h"
+#include "base/logging.h"
+#include "base/memory/ptr_util.h"
+#include "base/strings/string_util.h"
+
+// Helper methods -------------------------------------------------------------
+
+namespace {
+struct IndentRules {
+  bool one_line;
+  unsigned level;
+};
+
+std::vector<std::unique_ptr<PBXObject>> EmptyPBXObjectVector() {
+  return std::vector<std::unique_ptr<PBXObject>>();
+}
+
+bool CharNeedEscaping(char c) {
+  if (base::IsAsciiAlpha(c) || base::IsAsciiDigit(c))
+    return false;
+  if (c == '$' || c == '.' || c == '/' || c == '_')
+    return false;
+  return true;
+}
+
+bool StringNeedEscaping(const std::string& string) {
+  if (string.empty())
+    return true;
+  if (string.find("___") != std::string::npos)
+    return true;
+
+  for (char c : string) {
+    if (CharNeedEscaping(c))
+      return true;
+  }
+  return false;
+}
+
+std::string EncodeString(const std::string& string) {
+  if (!StringNeedEscaping(string))
+    return string;
+
+  std::stringstream buffer;
+  buffer << '"';
+  for (char c : string) {
+    if (c <= 31) {
+      switch (c) {
+        case '\a':
+          buffer << "\\a";
+          break;
+        case '\b':
+          buffer << "\\b";
+          break;
+        case '\t':
+          buffer << "\\t";
+          break;
+        case '\n':
+        case '\r':
+          buffer << "\\n";
+          break;
+        case '\v':
+          buffer << "\\v";
+          break;
+        case '\f':
+          buffer << "\\f";
+          break;
+        default:
+          buffer << std::hex << std::setw(4) << std::left << "\\U"
+                 << static_cast<unsigned>(c);
+          break;
+      }
+    } else {
+      if (c == '"' || c == '\\')
+        buffer << '\\';
+      buffer << c;
+    }
+  }
+  buffer << '"';
+  return buffer.str();
+}
+
+const char* GetSourceType(const base::FilePath::StringType& ext) {
+  std::map<base::FilePath::StringType, const char*> extension_map = {
+      {FILE_PATH_LITERAL(".a"), "archive.ar"},
+      {FILE_PATH_LITERAL(".app"), "wrapper.application"},
+      {FILE_PATH_LITERAL(".bdic"), "file"},
+      {FILE_PATH_LITERAL(".bundle"), "wrapper.cfbundle"},
+      {FILE_PATH_LITERAL(".c"), "sourcecode.c.c"},
+      {FILE_PATH_LITERAL(".cc"), "sourcecode.cpp.cpp"},
+      {FILE_PATH_LITERAL(".cpp"), "sourcecode.cpp.cpp"},
+      {FILE_PATH_LITERAL(".css"), "text.css"},
+      {FILE_PATH_LITERAL(".cxx"), "sourcecode.cpp.cpp"},
+      {FILE_PATH_LITERAL(".dart"), "sourcecode"},
+      {FILE_PATH_LITERAL(".dylib"), "compiled.mach-o.dylib"},
+      {FILE_PATH_LITERAL(".framework"), "wrapper.framework"},
+      {FILE_PATH_LITERAL(".h"), "sourcecode.c.h"},
+      {FILE_PATH_LITERAL(".hxx"), "sourcecode.cpp.h"},
+      {FILE_PATH_LITERAL(".icns"), "image.icns"},
+      {FILE_PATH_LITERAL(".java"), "sourcecode.java"},
+      {FILE_PATH_LITERAL(".js"), "sourcecode.javascript"},
+      {FILE_PATH_LITERAL(".kext"), "wrapper.kext"},
+      {FILE_PATH_LITERAL(".m"), "sourcecode.c.objc"},
+      {FILE_PATH_LITERAL(".mm"), "sourcecode.cpp.objcpp"},
+      {FILE_PATH_LITERAL(".nib"), "wrapper.nib"},
+      {FILE_PATH_LITERAL(".o"), "compiled.mach-o.objfile"},
+      {FILE_PATH_LITERAL(".pdf"), "image.pdf"},
+      {FILE_PATH_LITERAL(".pl"), "text.script.perl"},
+      {FILE_PATH_LITERAL(".plist"), "text.plist.xml"},
+      {FILE_PATH_LITERAL(".pm"), "text.script.perl"},
+      {FILE_PATH_LITERAL(".png"), "image.png"},
+      {FILE_PATH_LITERAL(".py"), "text.script.python"},
+      {FILE_PATH_LITERAL(".r"), "sourcecode.rez"},
+      {FILE_PATH_LITERAL(".rez"), "sourcecode.rez"},
+      {FILE_PATH_LITERAL(".s"), "sourcecode.asm"},
+      {FILE_PATH_LITERAL(".storyboard"), "file.storyboard"},
+      {FILE_PATH_LITERAL(".strings"), "text.plist.strings"},
+      {FILE_PATH_LITERAL(".swift"), "sourcecode.swift"},
+      {FILE_PATH_LITERAL(".ttf"), "file"},
+      {FILE_PATH_LITERAL(".xcassets"), "folder.assetcatalog"},
+      {FILE_PATH_LITERAL(".xcconfig"), "text.xcconfig"},
+      {FILE_PATH_LITERAL(".xcdatamodel"), "wrapper.xcdatamodel"},
+      {FILE_PATH_LITERAL(".xcdatamodeld"), "wrapper.xcdatamodeld"},
+      {FILE_PATH_LITERAL(".xib"), "file.xib"},
+      {FILE_PATH_LITERAL(".y"), "sourcecode.yacc"},
+  };
+
+  const auto& iter = extension_map.find(ext);
+  if (iter != extension_map.end()) {
+    return iter->second;
+  }
+
+  return "text";
+}
+
+bool HasExplicitFileType(const base::FilePath::StringType& ext) {
+  return ext == FILE_PATH_LITERAL(".dart");
+}
+
+bool IsSourceFileForIndexing(const base::FilePath::StringType& ext) {
+  return ext == FILE_PATH_LITERAL(".c") || ext == FILE_PATH_LITERAL(".cc") ||
+         ext == FILE_PATH_LITERAL(".cpp") || ext == FILE_PATH_LITERAL(".cxx") ||
+         ext == FILE_PATH_LITERAL(".m") || ext == FILE_PATH_LITERAL(".mm");
+}
+
+void PrintValue(std::ostream& out, IndentRules rules, unsigned value) {
+  out << value;
+}
+
+void PrintValue(std::ostream& out, IndentRules rules, const char* value) {
+  out << EncodeString(value);
+}
+
+void PrintValue(std::ostream& out,
+                IndentRules rules,
+                const std::string& value) {
+  out << EncodeString(value);
+}
+
+void PrintValue(std::ostream& out, IndentRules rules, const PBXObject* value) {
+  out << value->Reference();
+}
+
+template <typename ObjectClass>
+void PrintValue(std::ostream& out,
+                IndentRules rules,
+                const std::unique_ptr<ObjectClass>& value) {
+  PrintValue(out, rules, value.get());
+}
+
+template <typename ValueType>
+void PrintValue(std::ostream& out,
+                IndentRules rules,
+                const std::vector<ValueType>& values) {
+  IndentRules sub_rule{rules.one_line, rules.level + 1};
+  out << "(" << (rules.one_line ? " " : "\n");
+  for (const auto& value : values) {
+    if (!sub_rule.one_line)
+      out << std::string(sub_rule.level, '\t');
+
+    PrintValue(out, sub_rule, value);
+    out << "," << (rules.one_line ? " " : "\n");
+  }
+
+  if (!rules.one_line && rules.level)
+    out << std::string(rules.level, '\t');
+  out << ")";
+}
+
+template <typename ValueType>
+void PrintValue(std::ostream& out,
+                IndentRules rules,
+                const std::map<std::string, ValueType>& values) {
+  IndentRules sub_rule{rules.one_line, rules.level + 1};
+  out << "{" << (rules.one_line ? " " : "\n");
+  for (const auto& pair : values) {
+    if (!sub_rule.one_line)
+      out << std::string(sub_rule.level, '\t');
+
+    out << pair.first << " = ";
+    PrintValue(out, sub_rule, pair.second);
+    out << ";" << (rules.one_line ? " " : "\n");
+  }
+
+  if (!rules.one_line && rules.level)
+    out << std::string(rules.level, '\t');
+  out << "}";
+}
+
+template <typename ValueType>
+void PrintProperty(std::ostream& out,
+                   IndentRules rules,
+                   const char* name,
+                   ValueType&& value) {
+  if (!rules.one_line && rules.level)
+    out << std::string(rules.level, '\t');
+
+  out << name << " = ";
+  PrintValue(out, rules, std::forward<ValueType>(value));
+  out << ";" << (rules.one_line ? " " : "\n");
+}
+}  // namespace
+
+// PBXObjectClass -------------------------------------------------------------
+
+const char* ToString(PBXObjectClass cls) {
+  switch (cls) {
+    case PBXAggregateTargetClass:
+      return "PBXAggregateTarget";
+    case PBXBuildFileClass:
+      return "PBXBuildFile";
+    case PBXFileReferenceClass:
+      return "PBXFileReference";
+    case PBXFrameworksBuildPhaseClass:
+      return "PBXFrameworksBuildPhase";
+    case PBXGroupClass:
+      return "PBXGroup";
+    case PBXNativeTargetClass:
+      return "PBXNativeTarget";
+    case PBXProjectClass:
+      return "PBXProject";
+    case PBXShellScriptBuildPhaseClass:
+      return "PBXShellScriptBuildPhase";
+    case PBXSourcesBuildPhaseClass:
+      return "PBXSourcesBuildPhase";
+    case XCBuildConfigurationClass:
+      return "XCBuildConfiguration";
+    case XCConfigurationListClass:
+      return "XCConfigurationList";
+  }
+  NOTREACHED();
+  return nullptr;
+}
+
+// PBXObjectVisitor -----------------------------------------------------------
+
+PBXObjectVisitor::PBXObjectVisitor() {}
+
+PBXObjectVisitor::~PBXObjectVisitor() {}
+
+// PBXObject ------------------------------------------------------------------
+
+PBXObject::PBXObject() {}
+
+PBXObject::~PBXObject() {}
+
+void PBXObject::SetId(const std::string& id) {
+  DCHECK(id_.empty());
+  DCHECK(!id.empty());
+  id_.assign(id);
+}
+
+std::string PBXObject::Reference() const {
+  std::string comment = Comment();
+  if (comment.empty())
+    return id_;
+
+  return id_ + " /* " + comment + " */";
+}
+
+std::string PBXObject::Comment() const {
+  return Name();
+}
+
+void PBXObject::Visit(PBXObjectVisitor& visitor) {
+  visitor.Visit(this);
+}
+
+// PBXBuildPhase --------------------------------------------------------------
+
+PBXBuildPhase::PBXBuildPhase() {}
+
+PBXBuildPhase::~PBXBuildPhase() {}
+
+// PBXTarget ------------------------------------------------------------------
+
+PBXTarget::PBXTarget(const std::string& name,
+                     const std::string& shell_script,
+                     const std::string& config_name,
+                     const PBXAttributes& attributes)
+    : configurations_(new XCConfigurationList(config_name, attributes, this)),
+      name_(name) {
+  if (!shell_script.empty()) {
+    build_phases_.push_back(
+        base::WrapUnique(new PBXShellScriptBuildPhase(name, shell_script)));
+  }
+}
+
+PBXTarget::~PBXTarget() {}
+
+std::string PBXTarget::Name() const {
+  return name_;
+}
+
+void PBXTarget::Visit(PBXObjectVisitor& visitor) {
+  PBXObject::Visit(visitor);
+  configurations_->Visit(visitor);
+  for (const auto& build_phase : build_phases_) {
+    build_phase->Visit(visitor);
+  }
+}
+
+// PBXAggregateTarget ---------------------------------------------------------
+
+PBXAggregateTarget::PBXAggregateTarget(const std::string& name,
+                                       const std::string& shell_script,
+                                       const std::string& config_name,
+                                       const PBXAttributes& attributes)
+    : PBXTarget(name, shell_script, config_name, attributes) {}
+
+PBXAggregateTarget::~PBXAggregateTarget() {}
+
+PBXObjectClass PBXAggregateTarget::Class() const {
+  return PBXAggregateTargetClass;
+}
+
+void PBXAggregateTarget::Print(std::ostream& out, unsigned indent) const {
+  const std::string indent_str(indent, '\t');
+  const IndentRules rules = {false, indent + 1};
+  out << indent_str << Reference() << " = {\n";
+  PrintProperty(out, rules, "isa", ToString(Class()));
+  PrintProperty(out, rules, "buildConfigurationList", configurations_);
+  PrintProperty(out, rules, "buildPhases", build_phases_);
+  PrintProperty(out, rules, "dependencies", EmptyPBXObjectVector());
+  PrintProperty(out, rules, "name", name_);
+  PrintProperty(out, rules, "productName", name_);
+  out << indent_str << "};\n";
+}
+
+// PBXBuildFile ---------------------------------------------------------------
+
+PBXBuildFile::PBXBuildFile(const PBXFileReference* file_reference,
+                           const PBXSourcesBuildPhase* build_phase)
+    : file_reference_(file_reference), build_phase_(build_phase) {
+  DCHECK(file_reference_);
+  DCHECK(build_phase_);
+}
+
+PBXBuildFile::~PBXBuildFile() {}
+
+PBXObjectClass PBXBuildFile::Class() const {
+  return PBXBuildFileClass;
+}
+
+std::string PBXBuildFile::Name() const {
+  return file_reference_->Name() + " in " + build_phase_->Name();
+}
+
+void PBXBuildFile::Print(std::ostream& out, unsigned indent) const {
+  const std::string indent_str(indent, '\t');
+  const IndentRules rules = {true, 0};
+  out << indent_str << Reference() << " = {";
+  PrintProperty(out, rules, "isa", ToString(Class()));
+  PrintProperty(out, rules, "fileRef", file_reference_);
+  out << "};\n";
+}
+
+// PBXFileReference -----------------------------------------------------------
+
+PBXFileReference::PBXFileReference(const std::string& name,
+                                   const std::string& path,
+                                   const std::string& type)
+    : name_(name), path_(path), type_(type) {}
+
+PBXFileReference::~PBXFileReference() {}
+
+PBXObjectClass PBXFileReference::Class() const {
+  return PBXFileReferenceClass;
+}
+
+std::string PBXFileReference::Name() const {
+  return path_;
+}
+
+void PBXFileReference::Print(std::ostream& out, unsigned indent) const {
+  const std::string indent_str(indent, '\t');
+  const IndentRules rules = {true, 0};
+  out << indent_str << Reference() << " = {";
+  PrintProperty(out, rules, "isa", ToString(Class()));
+
+  if (!type_.empty()) {
+    PrintProperty(out, rules, "explicitFileType", type_);
+    PrintProperty(out, rules, "includeInIndex", 0u);
+  } else {
+    const base::FilePath::StringType ext =
+        base::FilePath::FromUTF8Unsafe(path_).Extension();
+
+    if (HasExplicitFileType(ext))
+      PrintProperty(out, rules, "explicitFileType", GetSourceType(ext));
+    else
+      PrintProperty(out, rules, "lastKnownFileType", GetSourceType(ext));
+  }
+
+  if (name_ != path_ && !name_.empty())
+    PrintProperty(out, rules, "name", name_);
+
+  PrintProperty(out, rules, "path", path_);
+  PrintProperty(out, rules, "sourceTree",
+                type_.empty() ? "<group>" : "BUILT_PRODUCTS_DIR");
+  out << "};\n";
+}
+
+// PBXFrameworksBuildPhase ----------------------------------------------------
+
+PBXFrameworksBuildPhase::PBXFrameworksBuildPhase() {}
+
+PBXFrameworksBuildPhase::~PBXFrameworksBuildPhase() {}
+
+PBXObjectClass PBXFrameworksBuildPhase::Class() const {
+  return PBXFrameworksBuildPhaseClass;
+}
+
+std::string PBXFrameworksBuildPhase::Name() const {
+  return "Frameworks";
+}
+
+void PBXFrameworksBuildPhase::Print(std::ostream& out, unsigned indent) const {
+  const std::string indent_str(indent, '\t');
+  const IndentRules rules = {false, indent + 1};
+  out << indent_str << Reference() << " = {\n";
+  PrintProperty(out, rules, "isa", ToString(Class()));
+  PrintProperty(out, rules, "buildActionMask", 0x7fffffffu);
+  PrintProperty(out, rules, "files", EmptyPBXObjectVector());
+  PrintProperty(out, rules, "runOnlyForDeploymentPostprocessing", 0u);
+  out << indent_str << "};\n";
+}
+
+// PBXGroup -------------------------------------------------------------------
+
+PBXGroup::PBXGroup(const std::string& path, const std::string& name)
+    : name_(name), path_(path) {}
+
+PBXGroup::~PBXGroup() {}
+
+PBXObject* PBXGroup::AddChild(std::unique_ptr<PBXObject> child) {
+  DCHECK(child);
+  children_.push_back(std::move(child));
+  return children_.back().get();
+}
+
+PBXFileReference* PBXGroup::AddSourceFile(const std::string& source_path) {
+  DCHECK(!source_path.empty());
+  std::string::size_type sep = source_path.find("/");
+  if (sep == std::string::npos) {
+    children_.push_back(base::WrapUnique(
+        new PBXFileReference(std::string(), source_path, std::string())));
+    return static_cast<PBXFileReference*>(children_.back().get());
+  }
+
+  PBXGroup* group = nullptr;
+  base::StringPiece component(source_path.data(), sep);
+  for (const auto& child : children_) {
+    if (child->Class() != PBXGroupClass)
+      continue;
+
+    PBXGroup* child_as_group = static_cast<PBXGroup*>(child.get());
+    if (child_as_group->path_ == component) {
+      group = child_as_group;
+      break;
+    }
+  }
+
+  if (!group) {
+    children_.push_back(base::WrapUnique(new PBXGroup(component.as_string())));
+    group = static_cast<PBXGroup*>(children_.back().get());
+  }
+
+  DCHECK(group);
+  DCHECK(group->path_ == component);
+  return group->AddSourceFile(source_path.substr(sep + 1));
+}
+
+PBXObjectClass PBXGroup::Class() const {
+  return PBXGroupClass;
+}
+
+std::string PBXGroup::Name() const {
+  if (!name_.empty())
+    return name_;
+  if (!path_.empty())
+    return path_;
+  return std::string();
+}
+
+void PBXGroup::Visit(PBXObjectVisitor& visitor) {
+  PBXObject::Visit(visitor);
+  for (const auto& child : children_) {
+    child->Visit(visitor);
+  }
+}
+
+void PBXGroup::Print(std::ostream& out, unsigned indent) const {
+  const std::string indent_str(indent, '\t');
+  const IndentRules rules = {false, indent + 1};
+  out << indent_str << Reference() << " = {\n";
+  PrintProperty(out, rules, "isa", ToString(Class()));
+  PrintProperty(out, rules, "children", children_);
+  if (!name_.empty())
+    PrintProperty(out, rules, "name", name_);
+  if (!path_.empty())
+    PrintProperty(out, rules, "path", path_);
+  PrintProperty(out, rules, "sourceTree", "<group>");
+  out << indent_str << "};\n";
+}
+
+// PBXNativeTarget ------------------------------------------------------------
+
+PBXNativeTarget::PBXNativeTarget(const std::string& name,
+                                 const std::string& shell_script,
+                                 const std::string& config_name,
+                                 const PBXAttributes& attributes,
+                                 const std::string& product_type,
+                                 const PBXFileReference* product_reference)
+    : PBXTarget(name, shell_script, config_name, attributes),
+      product_reference_(product_reference),
+      product_type_(product_type) {
+  DCHECK(product_reference_);
+  build_phases_.push_back(base::WrapUnique(new PBXSourcesBuildPhase));
+  source_build_phase_ =
+      static_cast<PBXSourcesBuildPhase*>(build_phases_.back().get());
+
+  build_phases_.push_back(base::WrapUnique(new PBXFrameworksBuildPhase));
+}
+
+PBXNativeTarget::~PBXNativeTarget() {}
+
+void PBXNativeTarget::AddFileForIndexing(
+    const PBXFileReference* file_reference) {
+  DCHECK(file_reference);
+  source_build_phase_->AddBuildFile(
+      base::WrapUnique(new PBXBuildFile(file_reference, source_build_phase_)));
+}
+
+PBXObjectClass PBXNativeTarget::Class() const {
+  return PBXNativeTargetClass;
+}
+
+void PBXNativeTarget::Print(std::ostream& out, unsigned indent) const {
+  const std::string indent_str(indent, '\t');
+  const IndentRules rules = {false, indent + 1};
+  out << indent_str << Reference() << " = {\n";
+  PrintProperty(out, rules, "isa", ToString(Class()));
+  PrintProperty(out, rules, "buildConfigurationList", configurations_);
+  PrintProperty(out, rules, "buildPhases", build_phases_);
+  PrintProperty(out, rules, "buildRules", EmptyPBXObjectVector());
+  PrintProperty(out, rules, "dependencies", EmptyPBXObjectVector());
+  PrintProperty(out, rules, "name", name_);
+  PrintProperty(out, rules, "productName", name_);
+  PrintProperty(out, rules, "productReference", product_reference_);
+  PrintProperty(out, rules, "productType", product_type_);
+  out << indent_str << "};\n";
+}
+
+// PBXProject -----------------------------------------------------------------
+
+PBXProject::PBXProject(const std::string& name,
+                       const std::string& config_name,
+                       const std::string& source_path,
+                       const PBXAttributes& attributes)
+    : name_(name), config_name_(config_name), target_for_indexing_(nullptr) {
+  attributes_["BuildIndependentTargetsInParallel"] = "YES";
+
+  main_group_.reset(new PBXGroup);
+  sources_ = static_cast<PBXGroup*>(main_group_->AddChild(
+      base::WrapUnique(new PBXGroup(source_path, "Source"))));
+  products_ = static_cast<PBXGroup*>(main_group_->AddChild(
+      base::WrapUnique(new PBXGroup(std::string(), "Product"))));
+  main_group_->AddChild(base::WrapUnique(new PBXGroup(std::string(), "Build")));
+
+  configurations_.reset(new XCConfigurationList(config_name, attributes, this));
+}
+
+PBXProject::~PBXProject() {}
+
+void PBXProject::AddSourceFile(const std::string& source_path) {
+  PBXFileReference* file_reference = sources_->AddSourceFile(source_path);
+  const base::FilePath::StringType ext =
+      base::FilePath::FromUTF8Unsafe(source_path).Extension();
+  if (!IsSourceFileForIndexing(ext))
+    return;
+
+  if (!target_for_indexing_) {
+    PBXAttributes attributes;
+    attributes["EXECUTABLE_PREFIX"] = "";
+    attributes["HEADER_SEARCH_PATHS"] = sources_->path();
+    attributes["PRODUCT_NAME"] = name_;
+
+    PBXFileReference* product_reference = static_cast<PBXFileReference*>(
+        products_->AddChild(base::WrapUnique(new PBXFileReference(
+            std::string(), name_, "compiled.mach-o.executable"))));
+
+    const char product_type[] = "com.apple.product-type.tool";
+    targets_.push_back(base::WrapUnique(
+        new PBXNativeTarget(name_, std::string(), config_name_, attributes,
+                            product_type, product_reference)));
+    target_for_indexing_ = static_cast<PBXNativeTarget*>(targets_.back().get());
+  }
+
+  DCHECK(target_for_indexing_);
+  target_for_indexing_->AddFileForIndexing(file_reference);
+}
+
+void PBXProject::AddAggregateTarget(const std::string& name,
+                                    const std::string& shell_script) {
+  PBXAttributes attributes;
+  attributes["CODE_SIGNING_REQUIRED"] = "NO";
+  attributes["CONFIGURATION_BUILD_DIR"] = ".";
+  attributes["PRODUCT_NAME"] = name;
+
+  targets_.push_back(base::WrapUnique(
+      new PBXAggregateTarget(name, shell_script, config_name_, attributes)));
+}
+
+void PBXProject::AddNativeTarget(const std::string& name,
+                                 const std::string& type,
+                                 const std::string& output_name,
+                                 const std::string& output_type,
+                                 const std::string& shell_script) {
+  const base::FilePath::StringType ext =
+      base::FilePath::FromUTF8Unsafe(output_name).Extension();
+
+  PBXFileReference* product = static_cast<PBXFileReference*>(
+      products_->AddChild(base::WrapUnique(new PBXFileReference(
+          name, output_name, type.empty() ? GetSourceType(ext) : type))));
+
+  PBXAttributes attributes;
+  attributes["CODE_SIGNING_REQUIRED"] = "NO";
+  attributes["CONFIGURATION_BUILD_DIR"] = ".";
+  attributes["PRODUCT_NAME"] = name;
+
+  targets_.push_back(base::WrapUnique(new PBXNativeTarget(
+      name, shell_script, config_name_, attributes, output_type, product)));
+}
+
+void PBXProject::SetProjectDirPath(const std::string& project_dir_path) {
+  DCHECK(!project_dir_path.empty());
+  project_dir_path_.assign(project_dir_path);
+}
+
+void PBXProject::SetProjectRoot(const std::string& project_root) {
+  DCHECK(!project_root.empty());
+  project_root_.assign(project_root);
+}
+
+void PBXProject::AddTarget(std::unique_ptr<PBXTarget> target) {
+  DCHECK(target);
+  targets_.push_back(std::move(target));
+}
+
+PBXObjectClass PBXProject::Class() const {
+  return PBXProjectClass;
+}
+
+std::string PBXProject::Name() const {
+  return name_;
+}
+
+std::string PBXProject::Comment() const {
+  return "Project object";
+}
+
+void PBXProject::Visit(PBXObjectVisitor& visitor) {
+  PBXObject::Visit(visitor);
+  configurations_->Visit(visitor);
+  main_group_->Visit(visitor);
+  for (const auto& target : targets_) {
+    target->Visit(visitor);
+  }
+}
+
+void PBXProject::Print(std::ostream& out, unsigned indent) const {
+  const std::string indent_str(indent, '\t');
+  const IndentRules rules = {false, indent + 1};
+  out << indent_str << Reference() << " = {\n";
+  PrintProperty(out, rules, "isa", ToString(Class()));
+  PrintProperty(out, rules, "attributes", attributes_);
+  PrintProperty(out, rules, "buildConfigurationList", configurations_);
+  PrintProperty(out, rules, "compatibilityVersion", "Xcode 3.2");
+  PrintProperty(out, rules, "developmentRegion", "English");
+  PrintProperty(out, rules, "hasScannedForEncodings", 1u);
+  PrintProperty(out, rules, "knownRegions", std::vector<std::string>({"en"}));
+  PrintProperty(out, rules, "mainGroup", main_group_);
+  PrintProperty(out, rules, "projectDirPath", project_dir_path_);
+  PrintProperty(out, rules, "projectRoot", project_root_);
+  PrintProperty(out, rules, "targets", targets_);
+  out << indent_str << "};\n";
+}
+
+// PBXShellScriptBuildPhase ---------------------------------------------------
+
+PBXShellScriptBuildPhase::PBXShellScriptBuildPhase(
+    const std::string& name,
+    const std::string& shell_script)
+    : name_("Action \"Compile and copy " + name + " via ninja\""),
+      shell_script_(shell_script) {}
+
+PBXShellScriptBuildPhase::~PBXShellScriptBuildPhase() {}
+
+PBXObjectClass PBXShellScriptBuildPhase::Class() const {
+  return PBXShellScriptBuildPhaseClass;
+}
+
+std::string PBXShellScriptBuildPhase::Name() const {
+  return name_;
+}
+
+void PBXShellScriptBuildPhase::Print(std::ostream& out, unsigned indent) const {
+  const std::string indent_str(indent, '\t');
+  const IndentRules rules = {false, indent + 1};
+  out << indent_str << Reference() << " = {\n";
+  PrintProperty(out, rules, "isa", ToString(Class()));
+  PrintProperty(out, rules, "buildActionMask", 0x7fffffffu);
+  PrintProperty(out, rules, "files", EmptyPBXObjectVector());
+  PrintProperty(out, rules, "inputPaths", EmptyPBXObjectVector());
+  PrintProperty(out, rules, "name", name_);
+  PrintProperty(out, rules, "outputPaths", EmptyPBXObjectVector());
+  PrintProperty(out, rules, "runOnlyForDeploymentPostprocessing", 0u);
+  PrintProperty(out, rules, "shellPath", "/bin/sh");
+  PrintProperty(out, rules, "shellScript", shell_script_);
+  PrintProperty(out, rules, "showEnvVarsInLog", 0u);
+  out << indent_str << "};\n";
+}
+
+// PBXSourcesBuildPhase -------------------------------------------------------
+
+PBXSourcesBuildPhase::PBXSourcesBuildPhase() {}
+
+PBXSourcesBuildPhase::~PBXSourcesBuildPhase() {}
+
+void PBXSourcesBuildPhase::AddBuildFile(
+    std::unique_ptr<PBXBuildFile> build_file) {
+  files_.push_back(std::move(build_file));
+}
+
+PBXObjectClass PBXSourcesBuildPhase::Class() const {
+  return PBXSourcesBuildPhaseClass;
+}
+
+std::string PBXSourcesBuildPhase::Name() const {
+  return "Sources";
+}
+
+void PBXSourcesBuildPhase::Visit(PBXObjectVisitor& visitor) {
+  PBXBuildPhase::Visit(visitor);
+  for (const auto& file : files_) {
+    file->Visit(visitor);
+  }
+}
+
+void PBXSourcesBuildPhase::Print(std::ostream& out, unsigned indent) const {
+  const std::string indent_str(indent, '\t');
+  const IndentRules rules = {false, indent + 1};
+  out << indent_str << Reference() << " = {\n";
+  PrintProperty(out, rules, "isa", ToString(Class()));
+  PrintProperty(out, rules, "buildActionMask", 0x7fffffffu);
+  PrintProperty(out, rules, "files", files_);
+  PrintProperty(out, rules, "runOnlyForDeploymentPostprocessing", 0u);
+  out << indent_str << "};\n";
+}
+
+// XCBuildConfiguration -------------------------------------------------------
+
+XCBuildConfiguration::XCBuildConfiguration(const std::string& name,
+                                           const PBXAttributes& attributes)
+    : attributes_(attributes), name_(name) {}
+
+XCBuildConfiguration::~XCBuildConfiguration() {}
+
+PBXObjectClass XCBuildConfiguration::Class() const {
+  return XCBuildConfigurationClass;
+}
+
+std::string XCBuildConfiguration::Name() const {
+  return name_;
+}
+
+void XCBuildConfiguration::Print(std::ostream& out, unsigned indent) const {
+  const std::string indent_str(indent, '\t');
+  const IndentRules rules = {false, indent + 1};
+  out << indent_str << Reference() << " = {\n";
+  PrintProperty(out, rules, "isa", ToString(Class()));
+  PrintProperty(out, rules, "buildSettings", attributes_);
+  PrintProperty(out, rules, "name", name_);
+  out << indent_str << "};\n";
+}
+
+// XCConfigurationList --------------------------------------------------------
+
+XCConfigurationList::XCConfigurationList(const std::string& name,
+                                         const PBXAttributes& attributes,
+                                         const PBXObject* owner_reference)
+    : owner_reference_(owner_reference) {
+  DCHECK(owner_reference_);
+  configurations_.push_back(
+      base::WrapUnique(new XCBuildConfiguration(name, attributes)));
+}
+
+XCConfigurationList::~XCConfigurationList() {}
+
+PBXObjectClass XCConfigurationList::Class() const {
+  return XCConfigurationListClass;
+}
+
+std::string XCConfigurationList::Name() const {
+  std::stringstream buffer;
+  buffer << "Build configuration list for "
+         << ToString(owner_reference_->Class()) << " \""
+         << owner_reference_->Name() << "\"";
+  return buffer.str();
+}
+
+void XCConfigurationList::Visit(PBXObjectVisitor& visitor) {
+  PBXObject::Visit(visitor);
+  for (const auto& configuration : configurations_) {
+    configuration->Visit(visitor);
+  }
+}
+
+void XCConfigurationList::Print(std::ostream& out, unsigned indent) const {
+  const std::string indent_str(indent, '\t');
+  const IndentRules rules = {false, indent + 1};
+  out << indent_str << Reference() << " = {\n";
+  PrintProperty(out, rules, "isa", ToString(Class()));
+  PrintProperty(out, rules, "buildConfigurations", configurations_);
+  PrintProperty(out, rules, "defaultConfigurationIsVisible", 1u);
+  PrintProperty(out, rules, "defaultConfigurationName",
+                configurations_[0]->Name());
+  out << indent_str << "};\n";
+}
diff --git a/tools/gn/xcode_object.h b/tools/gn/xcode_object.h
new file mode 100644
index 0000000..25f2415
--- /dev/null
+++ b/tools/gn/xcode_object.h
@@ -0,0 +1,392 @@
+// 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_XCODE_OBJECT_H_
+#define TOOLS_GN_XCODE_OBJECT_H_
+
+#include <iosfwd>
+#include <map>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/macros.h"
+
+// Helper classes to generate Xcode project files.
+//
+// This code is based on gyp xcodeproj_file.py generator. It does not support
+// all features of Xcode project but instead just enough to implement a hybrid
+// mode where Xcode uses external scripts to perform the compilation steps.
+//
+// See https://chromium.googlesource.com/external/gyp/+/master/pylib/gyp/xcodeproj_file.py
+// for more information on Xcode project file format.
+
+// PBXObjectClass -------------------------------------------------------------
+
+enum PBXObjectClass {
+  // Those values needs to stay sorted in alphabetic order.
+  PBXAggregateTargetClass,
+  PBXBuildFileClass,
+  PBXFileReferenceClass,
+  PBXFrameworksBuildPhaseClass,
+  PBXGroupClass,
+  PBXNativeTargetClass,
+  PBXProjectClass,
+  PBXShellScriptBuildPhaseClass,
+  PBXSourcesBuildPhaseClass,
+  XCBuildConfigurationClass,
+  XCConfigurationListClass,
+};
+
+const char* ToString(PBXObjectClass cls);
+
+// Forward-declarations -------------------------------------------------------
+
+class PBXAggregateTarget;
+class PBXBuildFile;
+class PBXFileReference;
+class PBXBuildPhase;
+class PBXFrameworksBuildPhase;
+class PBXGroup;
+class PBXNativeTarget;
+class PBXObject;
+class PBXProject;
+class PBXShellScriptBuildPhase;
+class PBXSourcesBuildPhase;
+class PBXTarget;
+class XCBuildConfiguration;
+class XCConfigurationList;
+
+using PBXAttributes = std::map<std::string, std::string>;
+
+// PBXObjectVisitor -----------------------------------------------------------
+
+class PBXObjectVisitor {
+ public:
+  PBXObjectVisitor();
+  virtual ~PBXObjectVisitor();
+  virtual void Visit(PBXObject* object) = 0;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(PBXObjectVisitor);
+};
+
+// PBXObject ------------------------------------------------------------------
+
+class PBXObject {
+ public:
+  PBXObject();
+  virtual ~PBXObject();
+
+  const std::string id() const { return id_; }
+  void SetId(const std::string& id);
+
+  std::string Reference() const;
+
+  virtual PBXObjectClass Class() const = 0;
+  virtual std::string Name() const = 0;
+  virtual std::string Comment() const;
+  virtual void Visit(PBXObjectVisitor& visitor);
+  virtual void Print(std::ostream& out, unsigned indent) const = 0;
+
+ private:
+  std::string id_;
+
+  DISALLOW_COPY_AND_ASSIGN(PBXObject);
+};
+
+// PBXBuildPhase --------------------------------------------------------------
+
+class PBXBuildPhase : public PBXObject {
+ public:
+  PBXBuildPhase();
+  ~PBXBuildPhase() override;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(PBXBuildPhase);
+};
+
+// PBXTarget ------------------------------------------------------------------
+
+class PBXTarget : public PBXObject {
+ public:
+  PBXTarget(const std::string& name,
+            const std::string& shell_script,
+            const std::string& config_name,
+            const PBXAttributes& attributes);
+  ~PBXTarget() override;
+
+  // PXBObject implementation.
+  std::string Name() const override;
+  void Visit(PBXObjectVisitor& visitor) override;
+
+ protected:
+  std::unique_ptr<XCConfigurationList> configurations_;
+  std::vector<std::unique_ptr<PBXBuildPhase>> build_phases_;
+  PBXSourcesBuildPhase* source_build_phase_;
+  std::string name_;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(PBXTarget);
+};
+
+// PBXAggregateTarget ---------------------------------------------------------
+
+class PBXAggregateTarget : public PBXTarget {
+ public:
+  PBXAggregateTarget(const std::string& name,
+                     const std::string& shell_script,
+                     const std::string& config_name,
+                     const PBXAttributes& attributes);
+  ~PBXAggregateTarget() override;
+
+  // PXBObject implementation.
+  PBXObjectClass Class() const override;
+  void Print(std::ostream& out, unsigned indent) const override;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(PBXAggregateTarget);
+};
+
+// PBXBuildFile ---------------------------------------------------------------
+
+class PBXBuildFile : public PBXObject {
+ public:
+  PBXBuildFile(const PBXFileReference* file_reference,
+               const PBXSourcesBuildPhase* build_phase);
+  ~PBXBuildFile() override;
+
+  // PXBObject implementation.
+  PBXObjectClass Class() const override;
+  std::string Name() const override;
+  void Print(std::ostream& out, unsigned indent) const override;
+
+ private:
+  const PBXFileReference* file_reference_;
+  const PBXSourcesBuildPhase* build_phase_;
+
+  DISALLOW_COPY_AND_ASSIGN(PBXBuildFile);
+};
+
+// PBXFileReference -----------------------------------------------------------
+
+class PBXFileReference : public PBXObject {
+ public:
+  PBXFileReference(const std::string& name,
+                   const std::string& path,
+                   const std::string& type);
+  ~PBXFileReference() override;
+
+  // PBXObject implementation.
+  PBXObjectClass Class() const override;
+  std::string Name() const override;
+  void Print(std::ostream& out, unsigned indent) const override;
+
+ private:
+  std::string name_;
+  std::string path_;
+  std::string type_;
+
+  DISALLOW_COPY_AND_ASSIGN(PBXFileReference);
+};
+
+// PBXFrameworksBuildPhase ----------------------------------------------------
+
+class PBXFrameworksBuildPhase : public PBXBuildPhase {
+ public:
+  PBXFrameworksBuildPhase();
+  ~PBXFrameworksBuildPhase() override;
+
+  // PBXObject implementation.
+  PBXObjectClass Class() const override;
+  std::string Name() const override;
+  void Print(std::ostream& out, unsigned indent) const override;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(PBXFrameworksBuildPhase);
+};
+
+// PBXGroup -------------------------------------------------------------------
+
+class PBXGroup : public PBXObject {
+ public:
+  explicit PBXGroup(const std::string& path = std::string(),
+                    const std::string& name = std::string());
+  ~PBXGroup() override;
+
+  const std::string& path() const { return path_; }
+
+  PBXObject* AddChild(std::unique_ptr<PBXObject> child);
+  PBXFileReference* AddSourceFile(const std::string& source_path);
+
+  // PBXObject implementation.
+  PBXObjectClass Class() const override;
+  std::string Name() const override;
+  void Visit(PBXObjectVisitor& visitor) override;
+  void Print(std::ostream& out, unsigned indent) const override;
+
+ private:
+  std::vector<std::unique_ptr<PBXObject>> children_;
+  std::string name_;
+  std::string path_;
+
+  DISALLOW_COPY_AND_ASSIGN(PBXGroup);
+};
+
+// PBXNativeTarget ------------------------------------------------------------
+
+class PBXNativeTarget : public PBXTarget {
+ public:
+  PBXNativeTarget(const std::string& name,
+                  const std::string& shell_script,
+                  const std::string& config_name,
+                  const PBXAttributes& attributes,
+                  const std::string& product_type,
+                  const PBXFileReference* product_reference);
+  ~PBXNativeTarget() override;
+
+  void AddFileForIndexing(const PBXFileReference* file_reference);
+
+  // PBXObject implementation.
+  PBXObjectClass Class() const override;
+  void Print(std::ostream& out, unsigned indent) const override;
+
+ private:
+  const PBXFileReference* product_reference_;
+  std::string product_type_;
+
+  DISALLOW_COPY_AND_ASSIGN(PBXNativeTarget);
+};
+
+// PBXProject -----------------------------------------------------------------
+
+class PBXProject : public PBXObject {
+ public:
+  PBXProject(const std::string& name,
+             const std::string& config_name,
+             const std::string& source_path,
+             const PBXAttributes& attributes);
+  ~PBXProject() override;
+
+  void AddSourceFile(const std::string& source_path);
+  void AddAggregateTarget(const std::string& name,
+                          const std::string& shell_script);
+  void AddNativeTarget(const std::string& name,
+                       const std::string& type,
+                       const std::string& output_name,
+                       const std::string& output_type,
+                       const std::string& shell_script);
+
+  void SetProjectDirPath(const std::string& project_dir_path);
+  void SetProjectRoot(const std::string& project_root);
+  void AddTarget(std::unique_ptr<PBXTarget> target);
+
+  // PBXObject implementation.
+  PBXObjectClass Class() const override;
+  std::string Name() const override;
+  std::string Comment() const override;
+  void Visit(PBXObjectVisitor& visitor) override;
+  void Print(std::ostream& out, unsigned indent) const override;
+
+ private:
+  PBXAttributes attributes_;
+  std::unique_ptr<XCConfigurationList> configurations_;
+  std::unique_ptr<PBXGroup> main_group_;
+  std::string project_dir_path_;
+  std::string project_root_;
+  std::vector<std::unique_ptr<PBXTarget>> targets_;
+  std::string name_;
+  std::string config_name_;
+
+  PBXGroup* sources_;
+  PBXGroup* products_;
+  PBXNativeTarget* target_for_indexing_;
+
+  DISALLOW_COPY_AND_ASSIGN(PBXProject);
+};
+
+// PBXShellScriptBuildPhase ---------------------------------------------------
+
+class PBXShellScriptBuildPhase : public PBXBuildPhase {
+ public:
+  PBXShellScriptBuildPhase(const std::string& name,
+                           const std::string& shell_script);
+  ~PBXShellScriptBuildPhase() override;
+
+  // PBXObject implementation.
+  PBXObjectClass Class() const override;
+  std::string Name() const override;
+  void Print(std::ostream& out, unsigned indent) const override;
+
+ private:
+  std::string name_;
+  std::string shell_script_;
+
+  DISALLOW_COPY_AND_ASSIGN(PBXShellScriptBuildPhase);
+};
+
+// PBXSourcesBuildPhase -------------------------------------------------------
+
+class PBXSourcesBuildPhase : public PBXBuildPhase {
+ public:
+  PBXSourcesBuildPhase();
+  ~PBXSourcesBuildPhase() override;
+
+  void AddBuildFile(std::unique_ptr<PBXBuildFile> build_file);
+
+  // PBXObject implementation.
+  PBXObjectClass Class() const override;
+  std::string Name() const override;
+  void Visit(PBXObjectVisitor& visitor) override;
+  void Print(std::ostream& out, unsigned indent) const override;
+
+ private:
+  std::vector<std::unique_ptr<PBXBuildFile>> files_;
+
+  DISALLOW_COPY_AND_ASSIGN(PBXSourcesBuildPhase);
+};
+
+// XCBuildConfiguration -------------------------------------------------------
+
+class XCBuildConfiguration : public PBXObject {
+ public:
+  XCBuildConfiguration(const std::string& name,
+                       const PBXAttributes& attributes);
+  ~XCBuildConfiguration() override;
+
+  // PBXObject implementation.
+  PBXObjectClass Class() const override;
+  std::string Name() const override;
+  void Print(std::ostream& out, unsigned indent) const override;
+
+ private:
+  PBXAttributes attributes_;
+  std::string name_;
+
+  DISALLOW_COPY_AND_ASSIGN(XCBuildConfiguration);
+};
+
+// XCConfigurationList --------------------------------------------------------
+
+class XCConfigurationList : public PBXObject {
+ public:
+  XCConfigurationList(const std::string& name,
+                      const PBXAttributes& attributes,
+                      const PBXObject* owner_reference);
+  ~XCConfigurationList() override;
+
+  // PBXObject implementation.
+  PBXObjectClass Class() const override;
+  std::string Name() const override;
+  void Visit(PBXObjectVisitor& visitor) override;
+  void Print(std::ostream& out, unsigned indent) const override;
+
+ private:
+  std::vector<std::unique_ptr<XCBuildConfiguration>> configurations_;
+  const PBXObject* owner_reference_;
+
+  DISALLOW_COPY_AND_ASSIGN(XCConfigurationList);
+};
+
+#endif  // TOOLS_GN_XCODE_OBJECT_H_
diff --git a/tools/gn/xcode_writer.cc b/tools/gn/xcode_writer.cc
new file mode 100644
index 0000000..4168f7f
--- /dev/null
+++ b/tools/gn/xcode_writer.cc
@@ -0,0 +1,432 @@
+// 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/xcode_writer.h"
+
+#include <iomanip>
+#include <map>
+#include <memory>
+#include <sstream>
+#include <string>
+#include <utility>
+
+#include "base/environment.h"
+#include "base/logging.h"
+#include "base/sha1.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "tools/gn/args.h"
+#include "tools/gn/build_settings.h"
+#include "tools/gn/builder.h"
+#include "tools/gn/commands.h"
+#include "tools/gn/deps_iterator.h"
+#include "tools/gn/filesystem_utils.h"
+#include "tools/gn/settings.h"
+#include "tools/gn/source_file.h"
+#include "tools/gn/target.h"
+#include "tools/gn/value.h"
+#include "tools/gn/variables.h"
+#include "tools/gn/xcode_object.h"
+
+namespace {
+
+XcodeWriter::TargetOsType GetTargetOs(const Args& args) {
+  const Value* target_os_value = args.GetArgOverride(variables::kTargetOs);
+  if (target_os_value) {
+    if (target_os_value->type() == Value::STRING) {
+      if (target_os_value->string_value() == "ios")
+        return XcodeWriter::WRITER_TARGET_OS_IOS;
+    }
+  }
+  return XcodeWriter::WRITER_TARGET_OS_MACOS;
+}
+
+std::string GetArchs(const Args& args) {
+  const Value* target_cpu_value = args.GetArgOverride(variables::kTargetCpu);
+  if (target_cpu_value) {
+    if (target_cpu_value->type() == Value::STRING) {
+      if (target_cpu_value->string_value() == "x86")
+        return "i386";
+      if (target_cpu_value->string_value() == "x64")
+        return "x86_64";
+      if (target_cpu_value->string_value() == "arm")
+        return "armv7";
+      if (target_cpu_value->string_value() == "armv7")
+        return "armv7";
+      if (target_cpu_value->string_value() == "arm64")
+        return "armv64";
+    }
+  }
+  return "x86_64";
+}
+
+std::string GetBuildScript(const std::string& target_name,
+                           const std::string& build_path,
+                           const std::string& ninja_extra_args) {
+  std::stringstream script;
+  script << "echo note: \"Compile and copy " << target_name << " via ninja\"\n"
+         << "exec ";
+  if (!build_path.empty())
+    script << "env PATH=\"" << build_path << "\" ";
+  script << "ninja -C .";
+  if (!ninja_extra_args.empty())
+    script << " " << ninja_extra_args;
+  if (!target_name.empty())
+    script << " " << target_name;
+  script << "\nexit 1\n";
+  return script.str();
+}
+
+class CollectPBXObjectsPerClassHelper : public PBXObjectVisitor {
+ public:
+  CollectPBXObjectsPerClassHelper() {}
+
+  void Visit(PBXObject* object) override {
+    DCHECK(object);
+    objects_per_class_[object->Class()].push_back(object);
+  }
+
+  const std::map<PBXObjectClass, std::vector<const PBXObject*>>&
+  objects_per_class() const {
+    return objects_per_class_;
+  }
+
+ private:
+  std::map<PBXObjectClass, std::vector<const PBXObject*>> objects_per_class_;
+
+  DISALLOW_COPY_AND_ASSIGN(CollectPBXObjectsPerClassHelper);
+};
+
+std::map<PBXObjectClass, std::vector<const PBXObject*>>
+CollectPBXObjectsPerClass(PBXProject* project) {
+  CollectPBXObjectsPerClassHelper visitor;
+  project->Visit(visitor);
+  return visitor.objects_per_class();
+}
+
+class RecursivelyAssignIdsHelper : public PBXObjectVisitor {
+ public:
+  RecursivelyAssignIdsHelper(const std::string& seed)
+      : seed_(seed), counter_(0) {}
+
+  void Visit(PBXObject* object) override {
+    std::stringstream buffer;
+    buffer << seed_ << " " << object->Name() << " " << counter_;
+    std::string hash = base::SHA1HashString(buffer.str());
+    DCHECK_EQ(hash.size() % 4, 0u);
+
+    uint32_t id[3] = {0, 0, 0};
+    const uint32_t* ptr = reinterpret_cast<const uint32_t*>(hash.data());
+    for (size_t i = 0; i < hash.size() / 4; i++)
+      id[i % 3] ^= ptr[i];
+
+    object->SetId(base::HexEncode(id, sizeof(id)));
+    ++counter_;
+  }
+
+ private:
+  std::string seed_;
+  int64_t counter_;
+
+  DISALLOW_COPY_AND_ASSIGN(RecursivelyAssignIdsHelper);
+};
+
+void RecursivelyAssignIds(PBXProject* project) {
+  RecursivelyAssignIdsHelper visitor(project->Name());
+  project->Visit(visitor);
+}
+
+}  // namespace
+
+// static
+bool XcodeWriter::RunAndWriteFiles(const std::string& workspace_name,
+                                   const std::string& root_target_name,
+                                   const std::string& ninja_extra_args,
+                                   const std::string& dir_filters_string,
+                                   const BuildSettings* build_settings,
+                                   Builder* builder,
+                                   Err* err) {
+  const XcodeWriter::TargetOsType target_os =
+      GetTargetOs(build_settings->build_args());
+
+  PBXAttributes attributes;
+  switch (target_os) {
+    case XcodeWriter::WRITER_TARGET_OS_IOS:
+      attributes["SDKROOT"] = "iphoneos";
+      attributes["TARGETED_DEVICE_FAMILY"] = "1,2";
+      break;
+    case XcodeWriter::WRITER_TARGET_OS_MACOS:
+      attributes["ARCHS"] = GetArchs(build_settings->build_args());
+      attributes["SDKROOT"] = "macosx10.11";
+      break;
+  }
+
+  const std::string source_path =
+      base::FilePath::FromUTF8Unsafe(
+          RebasePath("//", build_settings->build_dir()))
+          .StripTrailingSeparators()
+          .AsUTF8Unsafe();
+
+  std::string config_name = build_settings->build_dir()
+                                .Resolve(base::FilePath())
+                                .StripTrailingSeparators()
+                                .BaseName()
+                                .AsUTF8Unsafe();
+  DCHECK(!config_name.empty());
+
+  std::string::size_type separator = config_name.find('-');
+  if (separator != std::string::npos)
+    config_name = config_name.substr(0, separator);
+
+  std::vector<const Target*> targets;
+  std::vector<const Target*> all_targets = builder->GetAllResolvedTargets();
+  if (!XcodeWriter::FilterTargets(build_settings, all_targets,
+                                  dir_filters_string, &targets, err)) {
+    return false;
+  }
+
+  XcodeWriter workspace(workspace_name);
+  workspace.CreateProductsProject(targets, attributes, source_path, config_name,
+                                  root_target_name, ninja_extra_args,
+                                  target_os);
+
+  workspace.CreateSourcesProject(all_targets, build_settings->build_dir(),
+                                 attributes, source_path, config_name,
+                                 target_os);
+
+  return workspace.WriteFiles(build_settings, err);
+}
+
+XcodeWriter::XcodeWriter(const std::string& name) : name_(name) {
+  if (name_.empty())
+    name_.assign("all");
+}
+
+XcodeWriter::~XcodeWriter() {}
+
+// static
+bool XcodeWriter::FilterTargets(const BuildSettings* build_settings,
+                                const std::vector<const Target*>& all_targets,
+                                const std::string& dir_filters_string,
+                                std::vector<const Target*>* targets,
+                                Err* err) {
+  // Filter targets according to the semicolon-delimited list of label patterns,
+  // if defined, first.
+  targets->reserve(all_targets.size());
+  if (dir_filters_string.empty()) {
+    *targets = all_targets;
+  } else {
+    std::vector<LabelPattern> filters;
+    if (!commands::FilterPatternsFromString(build_settings, dir_filters_string,
+                                            &filters, err)) {
+      return false;
+    }
+
+    commands::FilterTargetsByPatterns(all_targets, filters, targets);
+  }
+
+  // Filter out all target of type EXECUTABLE that are direct dependency of
+  // a BUNDLE_DATA target (under the assumption that they will be part of a
+  // CREATE_BUNDLE target generating an application bundle). Sort the list
+  // of targets per pointer to use binary search for the removal.
+  std::sort(targets->begin(), targets->end());
+
+  for (const Target* target : all_targets) {
+    if (!target->settings()->is_default())
+      continue;
+
+    if (target->output_type() != Target::BUNDLE_DATA)
+      continue;
+
+    for (const auto& pair : target->GetDeps(Target::DEPS_LINKED)) {
+      if (pair.ptr->output_type() != Target::EXECUTABLE)
+        continue;
+
+      auto iter = std::lower_bound(targets->begin(), targets->end(), pair.ptr);
+      if (iter != targets->end() && *iter == pair.ptr)
+        targets->erase(iter);
+    }
+  }
+
+  // Sort the list of targets per-label to get a consistent ordering of them
+  // in the generated Xcode project (and thus stability of the file generated).
+  std::sort(targets->begin(), targets->end(),
+            [](const Target* a, const Target* b) {
+              return a->label().name() < b->label().name();
+            });
+
+  return true;
+}
+
+void XcodeWriter::CreateProductsProject(
+    const std::vector<const Target*>& targets,
+    const PBXAttributes& attributes,
+    const std::string& source_path,
+    const std::string& config_name,
+    const std::string& root_target,
+    const std::string& ninja_extra_args,
+    TargetOsType target_os) {
+  std::unique_ptr<PBXProject> main_project(
+      new PBXProject("products", config_name, source_path, attributes));
+
+  std::string build_path;
+  std::unique_ptr<base::Environment> env(base::Environment::Create());
+  env->GetVar("PATH", &build_path);
+
+  main_project->AddAggregateTarget(
+      "All", GetBuildScript(root_target, build_path, ninja_extra_args));
+
+  for (const Target* target : targets) {
+    switch (target->output_type()) {
+      case Target::EXECUTABLE:
+        if (target_os == XcodeWriter::WRITER_TARGET_OS_IOS)
+          continue;
+
+        main_project->AddNativeTarget(
+            target->label().name(), "compiled.mach-o.executable",
+            target->output_name().empty() ? target->label().name()
+                                          : target->output_name(),
+            "com.apple.product-type.tool",
+            GetBuildScript(target->label().name(), build_path,
+                           ninja_extra_args));
+        break;
+
+      case Target::CREATE_BUNDLE:
+        if (target->bundle_data().product_type().empty())
+          continue;
+
+        main_project->AddNativeTarget(
+            target->label().name(), std::string(),
+            target->bundle_data()
+                .GetBundleRootDirOutput(target->settings())
+                .Resolve(base::FilePath())
+                .AsUTF8Unsafe(),
+            target->bundle_data().product_type(),
+            GetBuildScript(target->label().name(), build_path,
+                           ninja_extra_args));
+        break;
+
+      default:
+        break;
+    }
+  }
+
+  projects_.push_back(std::move(main_project));
+}
+
+void XcodeWriter::CreateSourcesProject(
+    const std::vector<const Target*>& targets,
+    const SourceDir& root_build_dir,
+    const PBXAttributes& attributes,
+    const std::string& source_path,
+    const std::string& config_name,
+    TargetOsType target_os) {
+  std::vector<SourceFile> sources;
+  for (const Target* target : targets) {
+    if (!target->settings()->is_default())
+      continue;
+
+    for (const SourceFile& source : target->sources()) {
+      if (source.is_system_absolute())
+        continue;
+
+      if (IsStringInOutputDir(root_build_dir, source.value()))
+        continue;
+
+      sources.push_back(source);
+    }
+  }
+
+  std::unique_ptr<PBXProject> sources_for_indexing(
+      new PBXProject("sources", config_name, source_path, attributes));
+
+  // Sort sources to ensure determinisn of the project file generation and
+  // remove duplicate reference to the source files (can happen due to the
+  // bundle_data targets).
+  std::sort(sources.begin(), sources.end());
+  sources.erase(std::unique(sources.begin(), sources.end()), sources.end());
+
+  for (const SourceFile& source : sources) {
+    base::FilePath source_path = source.Resolve(base::FilePath());
+    sources_for_indexing->AddSourceFile(source_path.AsUTF8Unsafe());
+  }
+
+  projects_.push_back(std::move(sources_for_indexing));
+}
+
+bool XcodeWriter::WriteFiles(const BuildSettings* build_settings, Err* err) {
+  for (const auto& project : projects_) {
+    if (!WriteProjectFile(build_settings, project.get(), err))
+      return false;
+  }
+
+  SourceFile xcworkspacedata_file =
+      build_settings->build_dir().ResolveRelativeFile(
+          Value(nullptr, name_ + ".xcworkspace/contents.xcworkspacedata"), err);
+  if (xcworkspacedata_file.is_null())
+    return false;
+
+  std::stringstream xcworkspacedata_string_out;
+  WriteWorkspaceContent(xcworkspacedata_string_out);
+
+  return WriteFileIfChanged(build_settings->GetFullPath(xcworkspacedata_file),
+                            xcworkspacedata_string_out.str(), err);
+}
+
+bool XcodeWriter::WriteProjectFile(const BuildSettings* build_settings,
+                                   PBXProject* project,
+                                   Err* err) {
+  SourceFile pbxproj_file = build_settings->build_dir().ResolveRelativeFile(
+      Value(nullptr, project->Name() + ".xcodeproj/project.pbxproj"), err);
+  if (pbxproj_file.is_null())
+    return false;
+
+  std::stringstream pbxproj_string_out;
+  WriteProjectContent(pbxproj_string_out, project);
+
+  if (!WriteFileIfChanged(build_settings->GetFullPath(pbxproj_file),
+                          pbxproj_string_out.str(), err))
+    return false;
+
+  return true;
+}
+
+void XcodeWriter::WriteWorkspaceContent(std::ostream& out) {
+  out << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+      << "<Workspace version = \"1.0\">\n";
+  for (const auto& project : projects_) {
+    out << "  <FileRef location = \"group:" << project->Name()
+        << ".xcodeproj\"></FileRef>\n";
+  }
+  out << "</Workspace>\n";
+}
+
+void XcodeWriter::WriteProjectContent(std::ostream& out, PBXProject* project) {
+  RecursivelyAssignIds(project);
+
+  out << "// !$*UTF8*$!\n"
+      << "{\n"
+      << "\tarchiveVersion = 1;\n"
+      << "\tclasses = {\n"
+      << "\t};\n"
+      << "\tobjectVersion = 46;\n"
+      << "\tobjects = {\n";
+
+  for (auto& pair : CollectPBXObjectsPerClass(project)) {
+    out << "\n"
+        << "/* Begin " << ToString(pair.first) << " section */\n";
+    std::sort(pair.second.begin(), pair.second.end(),
+              [](const PBXObject* a, const PBXObject* b) {
+                return a->id() < b->id();
+              });
+    for (const auto& object : pair.second) {
+      object->Print(out, 2);
+    }
+    out << "/* End " << ToString(pair.first) << " section */\n";
+  }
+
+  out << "\t};\n"
+      << "\trootObject = " << project->Reference() << ";\n"
+      << "}\n";
+}
diff --git a/tools/gn/xcode_writer.h b/tools/gn/xcode_writer.h
new file mode 100644
index 0000000..dd162c6
--- /dev/null
+++ b/tools/gn/xcode_writer.h
@@ -0,0 +1,97 @@
+// 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_XCODE_WRITER_H_
+#define TOOLS_GN_XCODE_WRITER_H_
+
+#include <iosfwd>
+#include <map>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/macros.h"
+
+class Builder;
+class BuildSettings;
+class Err;
+class SourceDir;
+class Target;
+
+using PBXAttributes = std::map<std::string, std::string>;
+class PBXProject;
+
+class XcodeWriter {
+ public:
+  enum TargetOsType {
+    WRITER_TARGET_OS_IOS,
+    WRITER_TARGET_OS_MACOS,
+  };
+
+  // Writes Xcode workspace and project files.
+  //
+  // |workspace_name| is the optional name of the workspace file name ("all"
+  // is used if not specified). |root_target_name| is the name of the main
+  // target corresponding to building "All" (for example "gn_all" in Chromium).
+  // |ninja_extra_args| are additional arguments to pass to ninja invocation
+  // (can be used to increase limit of concurrent processes when using goma).
+  // |dir_filters_string| is optional semicolon-separated list of label patterns
+  // used to limit the set of generated projects. Only matching targets will be
+  // included to the workspace. On failure will populate |err| and return false.
+  static bool RunAndWriteFiles(const std::string& workspace_name,
+                               const std::string& root_target_name,
+                               const std::string& ninja_extra_args,
+                               const std::string& dir_filters_string,
+                               const BuildSettings* build_settings,
+                               Builder* builder,
+                               Err* err);
+
+ private:
+  XcodeWriter(const std::string& name);
+  ~XcodeWriter();
+
+  // Filters the list of targets to only return the targets with artifacts
+  // usable from Xcode (mostly application bundles). On failure populate |err|
+  // and return false.
+  static bool FilterTargets(const BuildSettings* build_settings,
+                            const std::vector<const Target*>& all_targets,
+                            const std::string& dir_filters_string,
+                            std::vector<const Target*>* targets,
+                            Err* err);
+
+  // Generate the "products.xcodeproj" project that reference all products
+  // (i.e. targets that have a build artefact usable from Xcode, mostly
+  // application bundles).
+  void CreateProductsProject(const std::vector<const Target*>& targets,
+                             const PBXAttributes& attributes,
+                             const std::string& source_path,
+                             const std::string& config_name,
+                             const std::string& root_target,
+                             const std::string& ninja_extra_args,
+                             TargetOsType target_os);
+
+  // Generates the "sources.xcodeproj" project that reference all source
+  // files to allow Xcode to index them.
+  void CreateSourcesProject(const std::vector<const Target*>& targets,
+                            const SourceDir& root_build_dir,
+                            const PBXAttributes& attributes,
+                            const std::string& source_path,
+                            const std::string& config_name,
+                            TargetOsType target_os);
+
+  bool WriteFiles(const BuildSettings* build_settings, Err* err);
+  bool WriteProjectFile(const BuildSettings* build_settings,
+                        PBXProject* project,
+                        Err* err);
+
+  void WriteWorkspaceContent(std::ostream& out);
+  void WriteProjectContent(std::ostream& out, PBXProject* project);
+
+  std::string name_;
+  std::vector<std::unique_ptr<PBXProject>> projects_;
+
+  DISALLOW_COPY_AND_ASSIGN(XcodeWriter);
+};
+
+#endif  // TOOLS_GN_XCODE_WRITER_H_