|  | // 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 <memory> | 
|  | #include <sstream> | 
|  | #include <utility> | 
|  |  | 
|  | #include "base/logging.h" | 
|  | #include "base/macros.h" | 
|  | #include "base/strings/string_util.h" | 
|  | #include "tools/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"}, | 
|  | {"xib", "file.xib"}, | 
|  | {"y", "sourcecode.yacc"}, | 
|  | }; | 
|  |  | 
|  | const char* GetSourceType(const std::string_view& ext) { | 
|  | for (size_t i = 0; i < arraysize(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"; | 
|  | } | 
|  |  | 
|  | 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 PBXContainerItemProxyClass: | 
|  | return "PBXContainerItemProxy"; | 
|  | 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 PBXTargetDependencyClass: | 
|  | return "PBXTargetDependency"; | 
|  | case XCBuildConfigurationClass: | 
|  | return "XCBuildConfiguration"; | 
|  | case XCConfigurationListClass: | 
|  | return "XCConfigurationList"; | 
|  | } | 
|  | NOTREACHED(); | 
|  | return nullptr; | 
|  | } | 
|  |  | 
|  | // PBXObjectVisitor ----------------------------------------------------------- | 
|  |  | 
|  | PBXObjectVisitor::PBXObjectVisitor() = default; | 
|  |  | 
|  | PBXObjectVisitor::~PBXObjectVisitor() = 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); | 
|  | } | 
|  |  | 
|  | // PBXBuildPhase -------------------------------------------------------------- | 
|  |  | 
|  | PBXBuildPhase::PBXBuildPhase() = default; | 
|  |  | 
|  | PBXBuildPhase::~PBXBuildPhase() = default; | 
|  |  | 
|  | // 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( | 
|  | 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); | 
|  | } | 
|  |  | 
|  | // 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 PBXSourcesBuildPhase* 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::HELP) { | 
|  | std::map<std::string, std::string> settings = { | 
|  | {"COMPILER_FLAGS", "--help"}, | 
|  | }; | 
|  | PrintProperty(out, rules, "settings", settings); | 
|  | } | 
|  | out << "};\n"; | 
|  | } | 
|  |  | 
|  | // PBXContainerItemProxy ------------------------------------------------------ | 
|  | PBXContainerItemProxy::PBXContainerItemProxy(const PBXProject* project, | 
|  | const PBXTarget* target) | 
|  | : project_(project), target_(target) {} | 
|  |  | 
|  | PBXContainerItemProxy::~PBXContainerItemProxy() = default; | 
|  |  | 
|  | PBXObjectClass PBXContainerItemProxy::Class() const { | 
|  | return PBXContainerItemProxyClass; | 
|  | } | 
|  |  | 
|  | void PBXContainerItemProxy::Visit(PBXObjectVisitor& visitor) { | 
|  | PBXObject::Visit(visitor); | 
|  | } | 
|  |  | 
|  | 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 = {true, 0}; | 
|  | out << indent_str << Reference() << " = {"; | 
|  | PrintProperty(out, rules, "isa", ToString(Class())); | 
|  | PrintProperty(out, rules, "containerPortal", project_); | 
|  | PrintProperty(out, rules, "proxyType", 1u); | 
|  | PrintProperty(out, rules, "remoteGlobalIDString", 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_; | 
|  | } | 
|  |  | 
|  | 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()) | 
|  | 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", 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() = default; | 
|  |  | 
|  | 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& 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; | 
|  | } | 
|  | } | 
|  |  | 
|  | children_.push_back(std::make_unique<PBXFileReference>( | 
|  | navigator_path, source_path, std::string())); | 
|  | return static_cast<PBXFileReference*>(children_.back().get()); | 
|  | } | 
|  |  | 
|  | 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) { | 
|  | children_.push_back(std::make_unique<PBXGroup>(std::string(component), | 
|  | std::string(component))); | 
|  | group = static_cast<PBXGroup*>(children_.back().get()); | 
|  | } | 
|  |  | 
|  | 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::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"; | 
|  | } | 
|  |  | 
|  | // 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) | 
|  | : PBXTarget(name, shell_script, config_name, attributes), | 
|  | 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>()); | 
|  | } | 
|  |  | 
|  | PBXNativeTarget::~PBXNativeTarget() = default; | 
|  |  | 
|  | 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) { | 
|  | attributes_["BuildIndependentTargetsInParallel"] = "YES"; | 
|  |  | 
|  | main_group_.reset(new PBXGroup); | 
|  | sources_ = static_cast<PBXGroup*>( | 
|  | main_group_->AddChild(std::make_unique<PBXGroup>(source_path, "Source"))); | 
|  | sources_->set_is_source(true); | 
|  | products_ = static_cast<PBXGroup*>(main_group_->AddChild( | 
|  | std::make_unique<PBXGroup>(std::string(), "Product"))); | 
|  | main_group_->AddChild(std::make_unique<PBXGroup>(std::string(), "Build")); | 
|  |  | 
|  | configurations_.reset(new XCConfigurationList(config_name, attributes, this)); | 
|  | } | 
|  |  | 
|  | PBXProject::~PBXProject() = default; | 
|  |  | 
|  | void PBXProject::AddSourceFileToIndexingTarget( | 
|  | const std::string& navigator_path, | 
|  | const std::string& source_path, | 
|  | const CompilerFlags compiler_flag) { | 
|  | if (!target_for_indexing_) { | 
|  | AddIndexingTarget(); | 
|  | } | 
|  | AddSourceFile(navigator_path, source_path, compiler_flag, | 
|  | target_for_indexing_); | 
|  | } | 
|  |  | 
|  | 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["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["EXECUTABLE_PREFIX"] = ""; | 
|  | attributes["HEADER_SEARCH_PATHS"] = sources_->path(); | 
|  | attributes["PRODUCT_NAME"] = "sources"; | 
|  |  | 
|  | PBXFileReference* product_reference = static_cast<PBXFileReference*>( | 
|  | products_->AddChild(std::make_unique<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)); | 
|  | 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& shell_script, | 
|  | const PBXAttributes& extra_attributes) { | 
|  | std::string_view ext = FindExtension(&output_name); | 
|  | PBXFileReference* product = static_cast<PBXFileReference*>( | 
|  | products_->AddChild(std::make_unique<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["CODE_SIGNING_REQUIRED"] = "NO"; | 
|  | attributes["CONFIGURATION_BUILD_DIR"] = "."; | 
|  | attributes["PRODUCT_NAME"] = product_name; | 
|  |  | 
|  | targets_.push_back(std::make_unique<PBXNativeTarget>( | 
|  | name, shell_script, config_name_, attributes, output_type, product_name, | 
|  | product)); | 
|  | 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::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() = 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", 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() = default; | 
|  |  | 
|  | PBXSourcesBuildPhase::~PBXSourcesBuildPhase() = default; | 
|  |  | 
|  | 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"; | 
|  | } | 
|  |  | 
|  | 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::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) | 
|  | : attributes_(attributes), name_(name) {} | 
|  |  | 
|  | 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())); | 
|  | 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( | 
|  | std::make_unique<XCBuildConfiguration>(name, attributes)); | 
|  | } | 
|  |  | 
|  | 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::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"; | 
|  | } |