text: // 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 "gn/xcode_object.h"
  
  #include <iomanip>
  #include <iterator>
  #include <memory>
  #include <sstream>
  #include <utility>
  
  #include "base/logging.h"
  #include "base/macros.h"
  #include "base/strings/string_util.h"
  #include "gn/filesystem_utils.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();
  }
  
  struct SourceTypeForExt {
    const char* ext;
    const char* source_type;
  };
  
  const SourceTypeForExt kSourceTypeForExt[] = {
      {"a", "archive.ar"},
      {"app", "wrapper.application"},
      {"appex", "wrapper.app-extension"},
      {"bdic", "file"},
      {"bundle", "wrapper.cfbundle"},
      {"c", "sourcecode.c.c"},
      {"cc", "sourcecode.cpp.cpp"},
      {"cpp", "sourcecode.cpp.cpp"},
      {"css", "text.css"},
      {"cxx", "sourcecode.cpp.cpp"},
      {"dart", "sourcecode"},
      {"dylib", "compiled.mach-o.dylib"},
      {"framework", "wrapper.framework"},
      {"h", "sourcecode.c.h"},
      {"hxx", "sourcecode.cpp.h"},
      {"icns", "image.icns"},
      {"java", "sourcecode.java"},
      {"js", "sourcecode.javascript"},
      {"kext", "wrapper.kext"},
      {"m", "sourcecode.c.objc"},
      {"mm", "sourcecode.cpp.objcpp"},
      {"nib", "wrapper.nib"},
      {"o", "compiled.mach-o.objfile"},
      {"pdf", "image.pdf"},
      {"pl", "text.script.perl"},
      {"plist", "text.plist.xml"},
      {"pm", "text.script.perl"},
      {"png", "image.png"},
      {"py", "text.script.python"},
      {"r", "sourcecode.rez"},
      {"rez", "sourcecode.rez"},
      {"s", "sourcecode.asm"},
      {"storyboard", "file.storyboard"},
      {"strings", "text.plist.strings"},
      {"swift", "sourcecode.swift"},
      {"ttf", "file"},
      {"xcassets", "folder.assetcatalog"},
      {"xcconfig", "text.xcconfig"},
      {"xcdatamodel", "wrapper.xcdatamodel"},
      {"xcdatamodeld", "wrapper.xcdatamodeld"},
      {"xctest", "wrapper.cfbundle"},
      {"xpc", "wrapper.xpc-service"},
      {"xib", "file.xib"},
      {"y", "sourcecode.yacc"},
  };
  
  const char* GetSourceType(const std::string_view& ext) {
    for (size_t i = 0; i < std::size(kSourceTypeForExt); ++i) {
      if (kSourceTypeForExt[i].ext == ext)
        return kSourceTypeForExt[i].source_type;
    }
  
    return "text";
  }
  
  bool HasExplicitFileType(const std::string_view& ext) {
    return ext == "dart";
  }
  
  bool IsSourceFileForIndexing(const std::string_view& ext) {
    return ext == "c" || ext == "cc" || ext == "cpp" || ext == "cxx" ||
           ext == "m" || ext == "mm";
  }
  
  // Wrapper around a const PBXObject* allowing to print just the object
  // identifier instead of a reference (i.e. identitifer and name). This
  // is used in a few place where Xcode uses the short identifier only.
  struct NoReference {
    const PBXObject* value;
  
    explicit NoReference(const PBXObject* value) : value(value) {}
  };
  
  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 NoReference& obj) {
    out << obj.value->id();
  }
  
  void PrintValue(std::ostream& out, IndentRules rules, const PBXObject* value) {
    out << value->Reference();
  }
  
  void PrintValue(std::ostream& out, IndentRules rules, CompilerFlags flags) {
    out << "{COMPILER_FLAGS = \"";
    switch (flags) {
      case CompilerFlags::HELP:
        out << "--help";
        break;
      case CompilerFlags::NONE:
        break;
    }
    out << "\"; }";
  }
  
  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");
  }
  
  struct PBXGroupComparator {
    using PBXObjectPtr = std::unique_ptr<PBXObject>;
    bool operator()(const PBXObjectPtr& lhs, const PBXObjectPtr& rhs) {
      if (lhs->Class() != rhs->Class())
        return rhs->Class() < lhs->Class();
  
      if (lhs->Class() == PBXGroupClass) {
        PBXGroup* lhs_group = static_cast<PBXGroup*>(lhs.get());
        PBXGroup* rhs_group = static_cast<PBXGroup*>(rhs.get());
        return lhs_group->name() < rhs_group->name();
      }
  
      DCHECK_EQ(lhs->Class(), PBXFileReferenceClass);
      PBXFileReference* lhs_file = static_cast<PBXFileReference*>(lhs.get());
      PBXFileReference* rhs_file = static_cast<PBXFileReference*>(rhs.get());
      return lhs_file->Name() < rhs_file->Name();
    }
  };
  }  // namespace
  
  // PBXObjectClass -------------------------------------------------------------
  
  const char* ToString(PBXObjectClass cls) {
    switch (cls) {
      case PBXAggregateTargetClass:
        return "PBXAggregateTarget";
      case PBXBuildFileClass:
        return "PBXBuildFile";
      case PBXContainerItemProxyClass:
        return "PBXContainerItemProxy";
      case PBXFileReferenceClass:
        return "PBXFileReference";
      case PBXFrameworksBuildPhaseClass:
        return "PBXFrameworksBuildPhase";
      case PBXGroupClass:
        return "PBXGroup";
      case PBXNativeTargetClass:
        return "PBXNativeTarget";
      case PBXProjectClass:
        return "PBXProject";
      case PBXResourcesBuildPhaseClass:
        return "PBXResourcesBuildPhase";
      case PBXShellScriptBuildPhaseClass:
        return "PBXShellScriptBuildPhase";
      case PBXSourcesBuildPhaseClass:
        return "PBXSourcesBuildPhase";
      case PBXTargetDependencyClass:
        return "PBXTargetDependency";
      case XCBuildConfigurationClass:
        return "XCBuildConfiguration";
      case XCConfigurationListClass:
        return "XCConfigurationList";
    }
    NOTREACHED();
    return nullptr;
  }
  
  // PBXObjectVisitor -----------------------------------------------------------
  
  PBXObjectVisitor::PBXObjectVisitor() = default;
  
  PBXObjectVisitor::~PBXObjectVisitor() = default;
  
  // PBXObjectVisitorConst ------------------------------------------------------
  
  PBXObjectVisitorConst::PBXObjectVisitorConst() = default;
  
  PBXObjectVisitorConst::~PBXObjectVisitorConst() = default;
  
  // PBXObject ------------------------------------------------------------------
  
  PBXObject::PBXObject() = default;
  
  PBXObject::~PBXObject() = default;
  
  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);
  }
  
  void PBXObject::Visit(PBXObjectVisitorConst& visitor) const {
    visitor.Visit(this);
  }
  
  // PBXBuildPhase --------------------------------------------------------------
  
  PBXBuildPhase::PBXBuildPhase() = default;
  
  PBXBuildPhase::~PBXBuildPhase() = default;
  
  void PBXBuildPhase::AddBuildFile(std::unique_ptr<PBXBuildFile> build_file) {
    DCHECK(build_file);
    files_.push_back(std::move(build_file));
  }
  
  void PBXBuildPhase::Visit(PBXObjectVisitor& visitor) {
    PBXObject::Visit(visitor);
    for (const auto& file : files_) {
      file->Visit(visitor);
    }
  }
  
  void PBXBuildPhase::Visit(PBXObjectVisitorConst& visitor) const {
    PBXObject::Visit(visitor);
    for (const auto& file : files_) {
      file->Visit(visitor);
    }
  }
  
  // PBXTarget ------------------------------------------------------------------
  
  PBXTarget::PBXTarget(const std::string& name,
                       const std::string& shell_script,
                       const std::string& config_name,
                       const PBXAttributes& attributes,
                       const std::vector<std::string>& include_path)
      : configurations_(std::make_unique<XCConfigurationList>(config_name,
                                                              attributes,
                                                              this,
                                                              include_path)),
        name_(name) {
    if (!shell_script.empty()) {
      build_phases_.push_back(
          std::make_unique<PBXShellScriptBuildPhase>(name, shell_script));
    }
  }
  
  PBXTarget::~PBXTarget() = default;
  
  void PBXTarget::AddDependency(std::unique_ptr<PBXTargetDependency> dependency) {
    DCHECK(dependency);
    dependencies_.push_back(std::move(dependency));
  }
  
  std::string PBXTarget::Name() const {
    return name_;
  }
  
  void PBXTarget::Visit(PBXObjectVisitor& visitor) {
    PBXObject::Visit(visitor);
    configurations_->Visit(visitor);
    for (const auto& dependency : dependencies_)
      dependency->Visit(visitor);
    for (const auto& build_phase : build_phases_)
      build_phase->Visit(visitor);
  }
  
  void PBXTarget::Visit(PBXObjectVisitorConst& visitor) const {
    PBXObject::Visit(visitor);
    configurations_->Visit(visitor);
    for (const auto& dependency : dependencies_)
      dependency->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() = default;
  
  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 PBXBuildPhase* build_phase,
                             const CompilerFlags compiler_flag)
      : file_reference_(file_reference),
        build_phase_(build_phase),
        compiler_flag_(compiler_flag) {
    DCHECK(file_reference_);
    DCHECK(build_phase_);
  }
  
  PBXBuildFile::~PBXBuildFile() = default;
  
  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_);
    if (compiler_flag_ != CompilerFlags::NONE) {
      PrintProperty(out, rules, "settings", compiler_flag_);
    }
    out << "};\n";
  }
  
  // PBXContainerItemProxy ------------------------------------------------------
  PBXContainerItemProxy::PBXContainerItemProxy(const PBXProject* project,
                                               const PBXTarget* target)
      : project_(project), target_(target) {}
  
  PBXContainerItemProxy::~PBXContainerItemProxy() = default;
  
  PBXObjectClass PBXContainerItemProxy::Class() const {
    return PBXContainerItemProxyClass;
  }
  
  std::string PBXContainerItemProxy::Name() const {
    return "PBXContainerItemProxy";
  }
  
  void PBXContainerItemProxy::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, "containerPortal", project_);
    PrintProperty(out, rules, "proxyType", 1u);
    PrintProperty(out, rules, "remoteGlobalIDString", NoReference(target_));
    PrintProperty(out, rules, "remoteInfo", target_->Name());
    out << indent_str << "};\n";
  }
  
  // PBXFileReference -----------------------------------------------------------
  
  PBXFileReference::PBXFileReference(const std::string& name,
                                     const std::string& path,
                                     const std::string& type)
      : name_(name), path_(path), type_(type) {}
  
  PBXFileReference::~PBXFileReference() = default;
  
  PBXObjectClass PBXFileReference::Class() const {
    return PBXFileReferenceClass;
  }
  
  std::string PBXFileReference::Name() const {
    return name_;
  }
  
  std::string PBXFileReference::Comment() const {
    return !name_.empty() ? name_ : 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 {
      std::string_view ext = FindExtension(&name_);
      if (HasExplicitFileType(ext))
        PrintProperty(out, rules, "explicitFileType", GetSourceType(ext));
      else
        PrintProperty(out, rules, "lastKnownFileType", GetSourceType(ext));
    }
  
    if (!name_.empty() && name_ != path_)
      PrintProperty(out, rules, "name", name_);
  
    DCHECK(!path_.empty());
    PrintProperty(out, rules, "path", path_);
    PrintProperty(out, rules, "sourceTree",
                  type_.empty() ? "<group>" : "BUILT_PRODUCTS_DIR");
    out << "};\n";
  }
  
  // PBXFrameworksBuildPhase ----------------------------------------------------
  
  PBXFrameworksBuildPhase::PBXFrameworksBuildPhase() = default;
  
  PBXFrameworksBuildPhase::~PBXFrameworksBuildPhase() = default;
  
  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", files_);
    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() = default;
  
  PBXFileReference* PBXGroup::AddSourceFile(const std::string& navigator_path,
                                            const std::string& source_path) {
    DCHECK(!navigator_path.empty());
    DCHECK(!source_path.empty());
    std::string::size_type sep = navigator_path.find("/");
    if (sep == std::string::npos) {
      // Prevent same file reference being created and added multiple times.
      for (const auto& child : children_) {
        if (child->Class() != PBXFileReferenceClass)
          continue;
  
        PBXFileReference* child_as_file_reference =
            static_cast<PBXFileReference*>(child.get());
        if (child_as_file_reference->Name() == navigator_path &&
            child_as_file_reference->path() == source_path) {
          return child_as_file_reference;
        }
      }
  
      return CreateChild<PBXFileReference>(navigator_path, source_path,
                                           std::string());
    }
  
    PBXGroup* group = nullptr;
    std::string_view component(navigator_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->name_ == component) {
        group = child_as_group;
        break;
      }
    }
  
    if (!group) {
      group =
          CreateChild<PBXGroup>(std::string(component), std::string(component));
    }
  
    DCHECK(group);
    DCHECK(group->name_ == component);
    return group->AddSourceFile(navigator_path.substr(sep + 1), source_path);
  }
  
  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::Visit(PBXObjectVisitorConst& visitor) const {
    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 (is_source_ && !path_.empty())
      PrintProperty(out, rules, "path", path_);
    PrintProperty(out, rules, "sourceTree", "<group>");
    out << indent_str << "};\n";
  }
  
  PBXObject* PBXGroup::AddChildImpl(std::unique_ptr<PBXObject> child) {
    DCHECK(child);
    DCHECK(child->Class() == PBXGroupClass ||
           child->Class() == PBXFileReferenceClass);
  
    PBXObject* child_ptr = child.get();
    if (autosorted()) {
      auto iter = std::lower_bound(children_.begin(), children_.end(), child,
                                   PBXGroupComparator());
      children_.insert(iter, std::move(child));
    } else {
      children_.push_back(std::move(child));
    }
    return child_ptr;
  }
  
  // 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 std::string& product_name,
                                   const PBXFileReference* product_reference,
                                   const std::vector<std::string>& include_paths)
      : PBXTarget(name, shell_script, config_name, attributes, include_paths),
        product_reference_(product_reference),
        product_type_(product_type),
        product_name_(product_name) {
    DCHECK(product_reference_);
    build_phases_.push_back(std::make_unique<PBXSourcesBuildPhase>());
    source_build_phase_ =
        static_cast<PBXSourcesBuildPhase*>(build_phases_.back().get());
  
    build_phases_.push_back(std::make_unique<PBXFrameworksBuildPhase>());
    build_phases_.push_back(std::make_unique<PBXResourcesBuildPhase>());
    resource_build_phase_ =
        static_cast<PBXResourcesBuildPhase*>(build_phases_.back().get());
  }
  
  PBXNativeTarget::~PBXNativeTarget() = default;
  
  void PBXNativeTarget::AddResourceFile(const PBXFileReference* file_reference) {
    DCHECK(file_reference);
    resource_build_phase_->AddBuildFile(std::make_unique<PBXBuildFile>(
        file_reference, resource_build_phase_, CompilerFlags::NONE));
  }
  
  void PBXNativeTarget::AddFileForIndexing(const PBXFileReference* file_reference,
                                           const CompilerFlags compiler_flag) {
    DCHECK(file_reference);
    source_build_phase_->AddBuildFile(std::make_unique<PBXBuildFile>(
        file_reference, source_build_phase_, compiler_flag));
  }
  
  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", dependencies_);
    PrintProperty(out, rules, "name", name_);
    PrintProperty(out, rules, "productName", product_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) {
    main_group_ = std::make_unique<PBXGroup>();
    main_group_->set_autosorted(false);
  
    sources_ = main_group_->CreateChild<PBXGroup>(source_path, "Source");
    sources_->set_is_source(true);
  
    products_ = main_group_->CreateChild<PBXGroup>(std::string(), "Products");
  
    configurations_ = std::make_unique<XCConfigurationList>(
        config_name, attributes, this, std::vector<std::string>());
  }
  
  PBXProject::~PBXProject() = default;
  
  void PBXProject::AddSourceFileToTargetForIndexing(
      const std::string& target_name,
      const std::string& navigator_path,
      const std::string& source_path,
      const CompilerFlags compiler_flag) {
    if (!target_for_indexing_) {
      AddIndexingTarget();
    }
  
    // If target with this name don't exists, we add source file to special
    // target.
    PBXNativeTarget* target = target_for_indexing_;
    auto iter = std::find_if(targets_.begin(), targets_.end(),
                             [&target_name](std::unique_ptr<PBXTarget>& target) {
                               return target->Name() == target_name;
                             });
    if (iter != targets_.end() &&
        iter->get()->Class() == PBXObjectClass::PBXNativeTargetClass) {
      target = static_cast<PBXNativeTarget*>(iter->get());
    }
    AddSourceFile(navigator_path, source_path, compiler_flag, target);
  }
  
  void PBXProject::AddSourceFile(const std::string& navigator_path,
                                 const std::string& source_path,
                                 const CompilerFlags compiler_flag,
                                 PBXNativeTarget* target) {
    PBXFileReference* file_reference =
        sources_->AddSourceFile(navigator_path, source_path);
    std::string_view ext = FindExtension(&source_path);
    if (!IsSourceFileForIndexing(ext))
      return;
  
    DCHECK(target);
    target->AddFileForIndexing(file_reference, compiler_flag);
  }
  
  void PBXProject::AddAggregateTarget(const std::string& name,
                                      const std::string& shell_script) {
    PBXAttributes attributes;
    attributes["CLANG_ENABLE_OBJC_WEAK"] = "YES";
    attributes["CODE_SIGN_IDENTITY"] = "-";
    attributes["CODE_SIGNING_REQUIRED"] = "NO";
    attributes["CONFIGURATION_BUILD_DIR"] = ".";
    attributes["PRODUCT_NAME"] = name;
  
    targets_.push_back(std::make_unique<PBXAggregateTarget>(
        name, shell_script, config_name_, attributes));
  }
  
  void PBXProject::AddIndexingTarget() {
    DCHECK(!target_for_indexing_);
    PBXAttributes attributes;
    attributes["CLANG_ENABLE_OBJC_WEAK"] = "YES";
    attributes["CODE_SIGN_IDENTITY"] = "-";
    attributes["CODE_SIGNING_REQUIRED"] = "NO";
    attributes["EXECUTABLE_PREFIX"] = "";
    attributes["HEADER_SEARCH_PATHS"] = sources_->path();
    attributes["PRODUCT_NAME"] = "sources";
  
    PBXFileReference* product_reference =
        products_->CreateChild<PBXFileReference>(std::string(), "sources",
                                                 "compiled.mach-o.executable");
  
    const char product_type[] = "com.apple.product-type.tool";
    targets_.push_back(std::make_unique<PBXNativeTarget>(
        "sources", std::string(), config_name_, attributes, product_type,
        "sources", product_reference, std::vector<std::string>()));
    target_for_indexing_ = static_cast<PBXNativeTarget*>(targets_.back().get());
  }
  
  PBXNativeTarget* PBXProject::AddNativeTarget(
      const std::string& name,
      const std::string& type,
      const std::string& output_name,
      const std::string& output_type,
      const std::string& output_dir,
      const std::string& shell_script,
      const PBXAttributes& extra_attributes,
      const std::vector<std::string>& include_paths) {
    std::string_view ext = FindExtension(&output_name);
    PBXFileReference* product = products_->CreateChild<PBXFileReference>(
        std::string(), output_name, type.empty() ? GetSourceType(ext) : type);
  
    // Per Xcode build settings documentation: Product Name (PRODUCT_NAME) should
    // the basename of the product generated by the target.
    // Therefore, take the basename of output name without file extension as the
    // "PRODUCT_NAME".
    size_t basename_offset = FindFilenameOffset(output_name);
    std::string output_basename = basename_offset != std::string::npos
                                      ? output_name.substr(basename_offset)
                                      : output_name;
    size_t ext_offset = FindExtensionOffset(output_basename);
    std::string product_name = ext_offset != std::string::npos
                                   ? output_basename.substr(0, ext_offset - 1)
                                   : output_basename;
  
    PBXAttributes attributes = extra_attributes;
    attributes["CLANG_ENABLE_OBJC_WEAK"] = "YES";
    attributes["CODE_SIGN_IDENTITY"] = "-";
    attributes["CODE_SIGNING_REQUIRED"] = "NO";
    attributes["CONFIGURATION_BUILD_DIR"] = output_dir;
    attributes["PRODUCT_NAME"] = product_name;
  
    targets_.push_back(std::make_unique<PBXNativeTarget>(
        name, shell_script, config_name_, attributes, output_type, product_name,
        product, include_paths));
    return static_cast<PBXNativeTarget*>(targets_.back().get());
  }
  
  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::Visit(PBXObjectVisitorConst& visitor) const {
    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", "en");
    PrintProperty(out, rules, "hasScannedForEncodings", 1u);
    PrintProperty(out, rules, "knownRegions",
                  std::vector<std::string>({"en", "Base"}));
    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";
  }
  
  // PBXResourcesBuildPhase -----------------------------------------------------
  
  PBXResourcesBuildPhase::PBXResourcesBuildPhase() = default;
  
  PBXResourcesBuildPhase::~PBXResourcesBuildPhase() = default;
  
  PBXObjectClass PBXResourcesBuildPhase::Class() const {
    return PBXResourcesBuildPhaseClass;
  }
  
  std::string PBXResourcesBuildPhase::Name() const {
    return "Resources";
  }
  
  void PBXResourcesBuildPhase::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";
  }
  
  // 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() = default;
  
  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", files_);
    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() = default;
  
  PBXSourcesBuildPhase::~PBXSourcesBuildPhase() = default;
  
  PBXObjectClass PBXSourcesBuildPhase::Class() const {
    return PBXSourcesBuildPhaseClass;
  }
  
  std::string PBXSourcesBuildPhase::Name() const {
    return "Sources";
  }
  
  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";
  }
  
  PBXTargetDependency::PBXTargetDependency(
      const PBXTarget* target,
      std::unique_ptr<PBXContainerItemProxy> container_item_proxy)
      : target_(target), container_item_proxy_(std::move(container_item_proxy)) {}
  
  PBXTargetDependency::~PBXTargetDependency() = default;
  
  PBXObjectClass PBXTargetDependency::Class() const {
    return PBXTargetDependencyClass;
  }
  
  std::string PBXTargetDependency::Name() const {
    return "PBXTargetDependency";
  }
  
  void PBXTargetDependency::Visit(PBXObjectVisitor& visitor) {
    PBXObject::Visit(visitor);
    container_item_proxy_->Visit(visitor);
  }
  
  void PBXTargetDependency::Visit(PBXObjectVisitorConst& visitor) const {
    PBXObject::Visit(visitor);
    container_item_proxy_->Visit(visitor);
  }
  
  void PBXTargetDependency::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, "target", target_);
    PrintProperty(out, rules, "targetProxy", container_item_proxy_);
    out << indent_str << "};\n";
  }
  
  // XCBuildConfiguration -------------------------------------------------------
  
  XCBuildConfiguration::XCBuildConfiguration(
      const std::string& name,
      const PBXAttributes& attributes,
      const std::vector<std::string>& include_path)
      : attributes_(attributes), name_(name), include_paths_(include_path) {}
  
  XCBuildConfiguration::~XCBuildConfiguration() = default;
  
  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()));
  
    out << indent_str << "buildSettings"
        << " = \n";
    out << indent_str << "{" << (rules.one_line ? " " : "\n");
  
    for (const auto& pair : attributes_) {
      PrintProperty(out, rules, pair.first.c_str(), pair.second);
    }
  
    out << indent_str << "HEADER_SEARCH_PATHS"
        << " = ";
    PrintValue(out, rules, include_paths_);
    out << ";" << (rules.one_line ? " " : "\n");
  
    out << std::string(rules.level, '\t');
    out << "};\n";
  
    PrintProperty(out, rules, "name", name_);
    out << indent_str << "};\n";
  }
  
  // XCConfigurationList --------------------------------------------------------
  
  XCConfigurationList::XCConfigurationList(
      const std::string& name,
      const PBXAttributes& attributes,
      const PBXObject* owner_reference,
      const std::vector<std::string>& include_paths)
      : owner_reference_(owner_reference) {
    DCHECK(owner_reference_);
    configurations_.push_back(
        std::make_unique<XCBuildConfiguration>(name, attributes, include_paths));
  }
  
  XCConfigurationList::~XCConfigurationList() = default;
  
  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::Visit(PBXObjectVisitorConst& visitor) const {
    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";
  }
  