blob: 1d08f22bc1f81a9db25be6ddd0a22642391b79b3 [file] [log] [blame]
// 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(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(std::string_view ext) {
return ext == "dart";
}
bool IsSourceFileForIndexing(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();
}
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)
: configurations_(
std::make_unique<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);
}
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)
: file_reference_(file_reference), build_phase_(build_phase) {
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_);
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)
: 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>());
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_));
}
void PBXNativeTarget::AddFileForIndexing(
const PBXFileReference* file_reference) {
DCHECK(file_reference);
source_build_phase_->AddBuildFile(
std::make_unique<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", 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);
}
PBXProject::~PBXProject() = default;
void PBXProject::AddSourceFileToIndexingTarget(
const std::string& navigator_path,
const std::string& source_path) {
if (!target_for_indexing_) {
AddIndexingTarget();
}
AddSourceFile(navigator_path, source_path, target_for_indexing_);
}
void PBXProject::AddSourceFile(const std::string& navigator_path,
const std::string& source_path,
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);
}
void PBXProject::AddAggregateTarget(const std::string& name,
const std::string& shell_script) {
PBXAttributes attributes;
attributes["CLANG_ENABLE_OBJC_WEAK"] = "YES";
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_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));
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) {
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_SIGNING_REQUIRED"] = "NO";
attributes["CONFIGURATION_BUILD_DIR"] = output_dir;
attributes["PRODUCT_NAME"] = product_name;
attributes["EXCLUDED_SOURCE_FILE_NAMES"] = "*.*";
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::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", "/usr/bin/python3");
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)
: 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::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";
}