1 // Copyright 2016 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "gn/xcode_object.h"
6
7 #include <iomanip>
8 #include <iterator>
9 #include <memory>
10 #include <sstream>
11 #include <utility>
12
13 #include "base/logging.h"
14 #include "base/strings/string_util.h"
15 #include "gn/filesystem_utils.h"
16
17 // Helper methods -------------------------------------------------------------
18
19 namespace {
20 struct IndentRules {
21 bool one_line;
22 unsigned level;
23 };
24
EmptyPBXObjectVector()25 std::vector<std::unique_ptr<PBXObject>> EmptyPBXObjectVector() {
26 return std::vector<std::unique_ptr<PBXObject>>();
27 }
28
CharNeedEscaping(char c)29 bool CharNeedEscaping(char c) {
30 if (base::IsAsciiAlpha(c) || base::IsAsciiDigit(c))
31 return false;
32 if (c == '$' || c == '.' || c == '/' || c == '_')
33 return false;
34 return true;
35 }
36
StringNeedEscaping(const std::string & string)37 bool StringNeedEscaping(const std::string& string) {
38 if (string.empty())
39 return true;
40 if (string.find("___") != std::string::npos)
41 return true;
42
43 for (char c : string) {
44 if (CharNeedEscaping(c))
45 return true;
46 }
47 return false;
48 }
49
EncodeString(const std::string & string)50 std::string EncodeString(const std::string& string) {
51 if (!StringNeedEscaping(string))
52 return string;
53
54 std::stringstream buffer;
55 buffer << '"';
56 for (char c : string) {
57 if (c <= 31) {
58 switch (c) {
59 case '\a':
60 buffer << "\\a";
61 break;
62 case '\b':
63 buffer << "\\b";
64 break;
65 case '\t':
66 buffer << "\\t";
67 break;
68 case '\n':
69 case '\r':
70 buffer << "\\n";
71 break;
72 case '\v':
73 buffer << "\\v";
74 break;
75 case '\f':
76 buffer << "\\f";
77 break;
78 default:
79 buffer << std::hex << std::setw(4) << std::left << "\\U"
80 << static_cast<unsigned>(c);
81 break;
82 }
83 } else {
84 if (c == '"' || c == '\\')
85 buffer << '\\';
86 buffer << c;
87 }
88 }
89 buffer << '"';
90 return buffer.str();
91 }
92
93 struct SourceTypeForExt {
94 const char* ext;
95 const char* source_type;
96 };
97
98 const SourceTypeForExt kSourceTypeForExt[] = {
99 {"a", "archive.ar"},
100 {"app", "wrapper.application"},
101 {"appex", "wrapper.app-extension"},
102 {"bdic", "file"},
103 {"bundle", "wrapper.cfbundle"},
104 {"c", "sourcecode.c.c"},
105 {"cc", "sourcecode.cpp.cpp"},
106 {"cpp", "sourcecode.cpp.cpp"},
107 {"css", "text.css"},
108 {"cxx", "sourcecode.cpp.cpp"},
109 {"dart", "sourcecode"},
110 {"dylib", "compiled.mach-o.dylib"},
111 {"framework", "wrapper.framework"},
112 {"h", "sourcecode.c.h"},
113 {"hxx", "sourcecode.cpp.h"},
114 {"icns", "image.icns"},
115 {"java", "sourcecode.java"},
116 {"js", "sourcecode.javascript"},
117 {"kext", "wrapper.kext"},
118 {"m", "sourcecode.c.objc"},
119 {"md", "net.daringfireball.markdown"},
120 {"mm", "sourcecode.cpp.objcpp"},
121 {"nib", "wrapper.nib"},
122 {"o", "compiled.mach-o.objfile"},
123 {"pdf", "image.pdf"},
124 {"pl", "text.script.perl"},
125 {"plist", "text.plist.xml"},
126 {"pm", "text.script.perl"},
127 {"png", "image.png"},
128 {"py", "text.script.python"},
129 {"r", "sourcecode.rez"},
130 {"rez", "sourcecode.rez"},
131 {"s", "sourcecode.asm"},
132 {"storyboard", "file.storyboard"},
133 {"strings", "text.plist.strings"},
134 {"swift", "sourcecode.swift"},
135 {"ts", "sourcecode.javascript"},
136 {"ttf", "file"},
137 {"xcassets", "folder.assetcatalog"},
138 {"xcconfig", "text.xcconfig"},
139 {"xcdatamodel", "wrapper.xcdatamodel"},
140 {"xcdatamodeld", "wrapper.xcdatamodeld"},
141 {"xctest", "wrapper.cfbundle"},
142 {"xib", "file.xib"},
143 {"xpc", "wrapper.xpc-service"},
144 {"y", "sourcecode.yacc"},
145 };
146
GetSourceType(std::string_view ext)147 const char* GetSourceType(std::string_view ext) {
148 for (size_t i = 0; i < std::size(kSourceTypeForExt); ++i) {
149 if (kSourceTypeForExt[i].ext == ext)
150 return kSourceTypeForExt[i].source_type;
151 }
152
153 return "text";
154 }
155
HasExplicitFileType(std::string_view ext)156 bool HasExplicitFileType(std::string_view ext) {
157 return ext == "dart" || ext == "ts";
158 }
159
IsSourceFileForIndexing(std::string_view ext)160 bool IsSourceFileForIndexing(std::string_view ext) {
161 return ext == "c" || ext == "cc" || ext == "cpp" || ext == "cxx" ||
162 ext == "m" || ext == "mm";
163 }
164
165 // Wrapper around a const PBXObject* allowing to print just the object
166 // identifier instead of a reference (i.e. identitifer and name). This
167 // is used in a few place where Xcode uses the short identifier only.
168 struct NoReference {
169 const PBXObject* value;
170
NoReference__anonda775fc00111::NoReference171 explicit NoReference(const PBXObject* value) : value(value) {}
172 };
173
PrintValue(std::ostream & out,IndentRules rules,unsigned value)174 void PrintValue(std::ostream& out, IndentRules rules, unsigned value) {
175 out << value;
176 }
177
PrintValue(std::ostream & out,IndentRules rules,const char * value)178 void PrintValue(std::ostream& out, IndentRules rules, const char* value) {
179 out << EncodeString(value);
180 }
181
PrintValue(std::ostream & out,IndentRules rules,const std::string & value)182 void PrintValue(std::ostream& out,
183 IndentRules rules,
184 const std::string& value) {
185 out << EncodeString(value);
186 }
187
PrintValue(std::ostream & out,IndentRules rules,const NoReference & obj)188 void PrintValue(std::ostream& out, IndentRules rules, const NoReference& obj) {
189 out << obj.value->id();
190 }
191
PrintValue(std::ostream & out,IndentRules rules,const PBXObject * value)192 void PrintValue(std::ostream& out, IndentRules rules, const PBXObject* value) {
193 out << value->Reference();
194 }
195
196 template <typename ObjectClass>
PrintValue(std::ostream & out,IndentRules rules,const std::unique_ptr<ObjectClass> & value)197 void PrintValue(std::ostream& out,
198 IndentRules rules,
199 const std::unique_ptr<ObjectClass>& value) {
200 PrintValue(out, rules, value.get());
201 }
202
203 template <typename ValueType>
PrintValue(std::ostream & out,IndentRules rules,const std::vector<ValueType> & values)204 void PrintValue(std::ostream& out,
205 IndentRules rules,
206 const std::vector<ValueType>& values) {
207 IndentRules sub_rule{rules.one_line, rules.level + 1};
208 out << "(" << (rules.one_line ? " " : "\n");
209 for (const auto& value : values) {
210 if (!sub_rule.one_line)
211 out << std::string(sub_rule.level, '\t');
212
213 PrintValue(out, sub_rule, value);
214 out << "," << (rules.one_line ? " " : "\n");
215 }
216
217 if (!rules.one_line && rules.level)
218 out << std::string(rules.level, '\t');
219 out << ")";
220 }
221
222 template <typename ValueType>
PrintValue(std::ostream & out,IndentRules rules,const std::map<std::string,ValueType> & values)223 void PrintValue(std::ostream& out,
224 IndentRules rules,
225 const std::map<std::string, ValueType>& values) {
226 IndentRules sub_rule{rules.one_line, rules.level + 1};
227 out << "{" << (rules.one_line ? " " : "\n");
228 for (const auto& pair : values) {
229 if (!sub_rule.one_line)
230 out << std::string(sub_rule.level, '\t');
231
232 out << pair.first << " = ";
233 PrintValue(out, sub_rule, pair.second);
234 out << ";" << (rules.one_line ? " " : "\n");
235 }
236
237 if (!rules.one_line && rules.level)
238 out << std::string(rules.level, '\t');
239 out << "}";
240 }
241
242 template <typename ValueType>
PrintProperty(std::ostream & out,IndentRules rules,const char * name,ValueType && value)243 void PrintProperty(std::ostream& out,
244 IndentRules rules,
245 const char* name,
246 ValueType&& value) {
247 if (!rules.one_line && rules.level)
248 out << std::string(rules.level, '\t');
249
250 out << name << " = ";
251 PrintValue(out, rules, std::forward<ValueType>(value));
252 out << ";" << (rules.one_line ? " " : "\n");
253 }
254
255 struct PBXGroupComparator {
256 using PBXObjectPtr = std::unique_ptr<PBXObject>;
operator ()__anonda775fc00111::PBXGroupComparator257 bool operator()(const PBXObjectPtr& lhs, const PBXObjectPtr& rhs) {
258 if (lhs.get() == rhs.get())
259 return false;
260
261 // Ensure that PBXGroup that should sort last are sorted last.
262 const bool lhs_sort_last = SortLast(lhs);
263 const bool rhs_sort_last = SortLast(rhs);
264 if (lhs_sort_last != rhs_sort_last)
265 return rhs_sort_last;
266
267 if (lhs->Class() != rhs->Class())
268 return rhs->Class() < lhs->Class();
269
270 return lhs->Name() < rhs->Name();
271 }
272
SortLast__anonda775fc00111::PBXGroupComparator273 bool SortLast(const PBXObjectPtr& ptr) {
274 if (ptr->Class() != PBXGroupClass)
275 return false;
276
277 return static_cast<PBXGroup*>(ptr.get())->SortLast();
278 }
279 };
280 } // namespace
281
282 // PBXObjectClass -------------------------------------------------------------
283
ToString(PBXObjectClass cls)284 const char* ToString(PBXObjectClass cls) {
285 switch (cls) {
286 case PBXAggregateTargetClass:
287 return "PBXAggregateTarget";
288 case PBXBuildFileClass:
289 return "PBXBuildFile";
290 case PBXContainerItemProxyClass:
291 return "PBXContainerItemProxy";
292 case PBXFileReferenceClass:
293 return "PBXFileReference";
294 case PBXFrameworksBuildPhaseClass:
295 return "PBXFrameworksBuildPhase";
296 case PBXGroupClass:
297 return "PBXGroup";
298 case PBXNativeTargetClass:
299 return "PBXNativeTarget";
300 case PBXProjectClass:
301 return "PBXProject";
302 case PBXResourcesBuildPhaseClass:
303 return "PBXResourcesBuildPhase";
304 case PBXShellScriptBuildPhaseClass:
305 return "PBXShellScriptBuildPhase";
306 case PBXSourcesBuildPhaseClass:
307 return "PBXSourcesBuildPhase";
308 case PBXTargetDependencyClass:
309 return "PBXTargetDependency";
310 case XCBuildConfigurationClass:
311 return "XCBuildConfiguration";
312 case XCConfigurationListClass:
313 return "XCConfigurationList";
314 }
315 NOTREACHED();
316 return nullptr;
317 }
318
319 // PBXObjectVisitor -----------------------------------------------------------
320
321 PBXObjectVisitor::PBXObjectVisitor() = default;
322
323 PBXObjectVisitor::~PBXObjectVisitor() = default;
324
325 // PBXObjectVisitorConst ------------------------------------------------------
326
327 PBXObjectVisitorConst::PBXObjectVisitorConst() = default;
328
329 PBXObjectVisitorConst::~PBXObjectVisitorConst() = default;
330
331 // PBXObject ------------------------------------------------------------------
332
333 PBXObject::PBXObject() = default;
334
335 PBXObject::~PBXObject() = default;
336
SetId(const std::string & id)337 void PBXObject::SetId(const std::string& id) {
338 DCHECK(id_.empty());
339 DCHECK(!id.empty());
340 id_.assign(id);
341 }
342
Reference() const343 std::string PBXObject::Reference() const {
344 std::string comment = Comment();
345 if (comment.empty())
346 return id_;
347
348 return id_ + " /* " + comment + " */";
349 }
350
Comment() const351 std::string PBXObject::Comment() const {
352 return Name();
353 }
354
Visit(PBXObjectVisitor & visitor)355 void PBXObject::Visit(PBXObjectVisitor& visitor) {
356 visitor.Visit(this);
357 }
358
Visit(PBXObjectVisitorConst & visitor) const359 void PBXObject::Visit(PBXObjectVisitorConst& visitor) const {
360 visitor.Visit(this);
361 }
362
363 // PBXBuildPhase --------------------------------------------------------------
364
365 PBXBuildPhase::PBXBuildPhase() = default;
366
367 PBXBuildPhase::~PBXBuildPhase() = default;
368
AddBuildFile(std::unique_ptr<PBXBuildFile> build_file)369 void PBXBuildPhase::AddBuildFile(std::unique_ptr<PBXBuildFile> build_file) {
370 DCHECK(build_file);
371 files_.push_back(std::move(build_file));
372 }
373
Visit(PBXObjectVisitor & visitor)374 void PBXBuildPhase::Visit(PBXObjectVisitor& visitor) {
375 PBXObject::Visit(visitor);
376 for (const auto& file : files_) {
377 file->Visit(visitor);
378 }
379 }
380
Visit(PBXObjectVisitorConst & visitor) const381 void PBXBuildPhase::Visit(PBXObjectVisitorConst& visitor) const {
382 PBXObject::Visit(visitor);
383 for (const auto& file : files_) {
384 file->Visit(visitor);
385 }
386 }
387
388 // PBXTarget ------------------------------------------------------------------
389
PBXTarget(const std::string & name,const std::string & shell_script,const std::vector<std::string> & configs,const PBXAttributes & attributes)390 PBXTarget::PBXTarget(const std::string& name,
391 const std::string& shell_script,
392 const std::vector<std::string>& configs,
393 const PBXAttributes& attributes)
394 : configurations_(
395 std::make_unique<XCConfigurationList>(configs, attributes, this)),
396 name_(name) {
397 if (!shell_script.empty()) {
398 build_phases_.push_back(
399 std::make_unique<PBXShellScriptBuildPhase>(name, shell_script));
400 }
401 }
402
403 PBXTarget::~PBXTarget() = default;
404
AddDependency(std::unique_ptr<PBXTargetDependency> dependency)405 void PBXTarget::AddDependency(std::unique_ptr<PBXTargetDependency> dependency) {
406 DCHECK(dependency);
407 dependencies_.push_back(std::move(dependency));
408 }
409
Name() const410 std::string PBXTarget::Name() const {
411 return name_;
412 }
413
Visit(PBXObjectVisitor & visitor)414 void PBXTarget::Visit(PBXObjectVisitor& visitor) {
415 PBXObject::Visit(visitor);
416 configurations_->Visit(visitor);
417 for (const auto& dependency : dependencies_)
418 dependency->Visit(visitor);
419 for (const auto& build_phase : build_phases_)
420 build_phase->Visit(visitor);
421 }
422
Visit(PBXObjectVisitorConst & visitor) const423 void PBXTarget::Visit(PBXObjectVisitorConst& visitor) const {
424 PBXObject::Visit(visitor);
425 configurations_->Visit(visitor);
426 for (const auto& dependency : dependencies_)
427 dependency->Visit(visitor);
428 for (const auto& build_phase : build_phases_)
429 build_phase->Visit(visitor);
430 }
431
432 // PBXAggregateTarget ---------------------------------------------------------
433
PBXAggregateTarget(const std::string & name,const std::string & shell_script,const std::vector<std::string> & configs,const PBXAttributes & attributes)434 PBXAggregateTarget::PBXAggregateTarget(const std::string& name,
435 const std::string& shell_script,
436 const std::vector<std::string>& configs,
437 const PBXAttributes& attributes)
438 : PBXTarget(name, shell_script, configs, attributes) {}
439
440 PBXAggregateTarget::~PBXAggregateTarget() = default;
441
Class() const442 PBXObjectClass PBXAggregateTarget::Class() const {
443 return PBXAggregateTargetClass;
444 }
445
Print(std::ostream & out,unsigned indent) const446 void PBXAggregateTarget::Print(std::ostream& out, unsigned indent) const {
447 const std::string indent_str(indent, '\t');
448 const IndentRules rules = {false, indent + 1};
449 out << indent_str << Reference() << " = {\n";
450 PrintProperty(out, rules, "isa", ToString(Class()));
451 PrintProperty(out, rules, "buildConfigurationList", configurations_);
452 PrintProperty(out, rules, "buildPhases", build_phases_);
453 PrintProperty(out, rules, "dependencies", EmptyPBXObjectVector());
454 PrintProperty(out, rules, "name", name_);
455 PrintProperty(out, rules, "productName", name_);
456 out << indent_str << "};\n";
457 }
458
459 // PBXBuildFile ---------------------------------------------------------------
460
PBXBuildFile(const PBXFileReference * file_reference,const PBXBuildPhase * build_phase)461 PBXBuildFile::PBXBuildFile(const PBXFileReference* file_reference,
462 const PBXBuildPhase* build_phase)
463 : file_reference_(file_reference), build_phase_(build_phase) {
464 DCHECK(file_reference_);
465 DCHECK(build_phase_);
466 }
467
468 PBXBuildFile::~PBXBuildFile() = default;
469
Class() const470 PBXObjectClass PBXBuildFile::Class() const {
471 return PBXBuildFileClass;
472 }
473
Name() const474 std::string PBXBuildFile::Name() const {
475 return file_reference_->Name() + " in " + build_phase_->Name();
476 }
477
Print(std::ostream & out,unsigned indent) const478 void PBXBuildFile::Print(std::ostream& out, unsigned indent) const {
479 const std::string indent_str(indent, '\t');
480 const IndentRules rules = {true, 0};
481 out << indent_str << Reference() << " = {";
482 PrintProperty(out, rules, "isa", ToString(Class()));
483 PrintProperty(out, rules, "fileRef", file_reference_);
484 out << "};\n";
485 }
486
487 // PBXContainerItemProxy ------------------------------------------------------
PBXContainerItemProxy(const PBXProject * project,const PBXTarget * target)488 PBXContainerItemProxy::PBXContainerItemProxy(const PBXProject* project,
489 const PBXTarget* target)
490 : project_(project), target_(target) {}
491
492 PBXContainerItemProxy::~PBXContainerItemProxy() = default;
493
Class() const494 PBXObjectClass PBXContainerItemProxy::Class() const {
495 return PBXContainerItemProxyClass;
496 }
497
Name() const498 std::string PBXContainerItemProxy::Name() const {
499 return "PBXContainerItemProxy";
500 }
501
Print(std::ostream & out,unsigned indent) const502 void PBXContainerItemProxy::Print(std::ostream& out, unsigned indent) const {
503 const std::string indent_str(indent, '\t');
504 const IndentRules rules = {false, indent + 1};
505 out << indent_str << Reference() << " = {\n";
506 PrintProperty(out, rules, "isa", ToString(Class()));
507 PrintProperty(out, rules, "containerPortal", project_);
508 PrintProperty(out, rules, "proxyType", 1u);
509 PrintProperty(out, rules, "remoteGlobalIDString", NoReference(target_));
510 PrintProperty(out, rules, "remoteInfo", target_->Name());
511 out << indent_str << "};\n";
512 }
513
514 // PBXFileReference -----------------------------------------------------------
515
PBXFileReference(const std::string & name,const std::string & path,const std::string & type)516 PBXFileReference::PBXFileReference(const std::string& name,
517 const std::string& path,
518 const std::string& type)
519 : name_(name), path_(path), type_(type) {}
520
521 PBXFileReference::~PBXFileReference() = default;
522
Class() const523 PBXObjectClass PBXFileReference::Class() const {
524 return PBXFileReferenceClass;
525 }
526
Name() const527 std::string PBXFileReference::Name() const {
528 return name_;
529 }
530
Comment() const531 std::string PBXFileReference::Comment() const {
532 return !name_.empty() ? name_ : path_;
533 }
534
Print(std::ostream & out,unsigned indent) const535 void PBXFileReference::Print(std::ostream& out, unsigned indent) const {
536 const std::string indent_str(indent, '\t');
537 const IndentRules rules = {true, 0};
538 out << indent_str << Reference() << " = {";
539 PrintProperty(out, rules, "isa", ToString(Class()));
540
541 if (!type_.empty()) {
542 PrintProperty(out, rules, "explicitFileType", type_);
543 PrintProperty(out, rules, "includeInIndex", 0u);
544 } else {
545 std::string_view ext = FindExtension(&name_);
546 const char* prop_name =
547 HasExplicitFileType(ext) ? "explicitFileType" : "lastKnownFileType";
548 PrintProperty(out, rules, prop_name, GetSourceType(ext));
549 }
550
551 if (!name_.empty() && name_ != path_)
552 PrintProperty(out, rules, "name", name_);
553
554 DCHECK(!path_.empty());
555 PrintProperty(out, rules, "path", path_);
556 PrintProperty(out, rules, "sourceTree",
557 type_.empty() ? "<group>" : "BUILT_PRODUCTS_DIR");
558 out << "};\n";
559 }
560
561 // PBXFrameworksBuildPhase ----------------------------------------------------
562
563 PBXFrameworksBuildPhase::PBXFrameworksBuildPhase() = default;
564
565 PBXFrameworksBuildPhase::~PBXFrameworksBuildPhase() = default;
566
Class() const567 PBXObjectClass PBXFrameworksBuildPhase::Class() const {
568 return PBXFrameworksBuildPhaseClass;
569 }
570
Name() const571 std::string PBXFrameworksBuildPhase::Name() const {
572 return "Frameworks";
573 }
574
Print(std::ostream & out,unsigned indent) const575 void PBXFrameworksBuildPhase::Print(std::ostream& out, unsigned indent) const {
576 const std::string indent_str(indent, '\t');
577 const IndentRules rules = {false, indent + 1};
578 out << indent_str << Reference() << " = {\n";
579 PrintProperty(out, rules, "isa", ToString(Class()));
580 PrintProperty(out, rules, "buildActionMask", 0x7fffffffu);
581 PrintProperty(out, rules, "files", files_);
582 PrintProperty(out, rules, "runOnlyForDeploymentPostprocessing", 0u);
583 out << indent_str << "};\n";
584 }
585
586 // PBXGroup -------------------------------------------------------------------
587
PBXGroup(const std::string & path,const std::string & name)588 PBXGroup::PBXGroup(const std::string& path, const std::string& name)
589 : name_(name), path_(path) {}
590
591 PBXGroup::~PBXGroup() = default;
592
AddSourceFile(const std::string & navigator_path,const std::string & source_path)593 PBXFileReference* PBXGroup::AddSourceFile(const std::string& navigator_path,
594 const std::string& source_path) {
595 DCHECK(!navigator_path.empty());
596 DCHECK(!source_path.empty());
597
598 std::string::size_type sep = navigator_path.find("/");
599 if (sep == std::string::npos) {
600 // Prevent same file reference being created and added multiple times.
601 for (const auto& child : children_) {
602 if (child->Class() != PBXFileReferenceClass)
603 continue;
604
605 PBXFileReference* child_as_file_reference =
606 static_cast<PBXFileReference*>(child.get());
607 if (child_as_file_reference->Name() == navigator_path &&
608 child_as_file_reference->path() == navigator_path) {
609 return child_as_file_reference;
610 }
611 }
612
613 return CreateChild<PBXFileReference>(navigator_path, navigator_path,
614 std::string());
615 }
616
617 PBXGroup* group = nullptr;
618 std::string_view component(navigator_path.data(), sep);
619 for (const auto& child : children_) {
620 if (child->Class() != PBXGroupClass)
621 continue;
622
623 PBXGroup* child_as_group = static_cast<PBXGroup*>(child.get());
624 if (child_as_group->name_ == component) {
625 group = child_as_group;
626 break;
627 }
628 }
629
630 if (!group) {
631 group =
632 CreateChild<PBXGroup>(std::string(component), std::string(component));
633 }
634
635 DCHECK(group);
636 DCHECK(group->name_ == component);
637 return group->AddSourceFile(navigator_path.substr(sep + 1), source_path);
638 }
639
Class() const640 PBXObjectClass PBXGroup::Class() const {
641 return PBXGroupClass;
642 }
643
Name() const644 std::string PBXGroup::Name() const {
645 if (!name_.empty())
646 return name_;
647 if (!path_.empty())
648 return path_;
649 return std::string();
650 }
651
Visit(PBXObjectVisitor & visitor)652 void PBXGroup::Visit(PBXObjectVisitor& visitor) {
653 PBXObject::Visit(visitor);
654 for (const auto& child : children_) {
655 child->Visit(visitor);
656 }
657 }
658
Visit(PBXObjectVisitorConst & visitor) const659 void PBXGroup::Visit(PBXObjectVisitorConst& visitor) const {
660 PBXObject::Visit(visitor);
661 for (const auto& child : children_) {
662 child->Visit(visitor);
663 }
664 }
665
Print(std::ostream & out,unsigned indent) const666 void PBXGroup::Print(std::ostream& out, unsigned indent) const {
667 const std::string indent_str(indent, '\t');
668 const IndentRules rules = {false, indent + 1};
669 out << indent_str << Reference() << " = {\n";
670 PrintProperty(out, rules, "isa", ToString(Class()));
671 PrintProperty(out, rules, "children", children_);
672 if (!name_.empty() && name_ != path_)
673 PrintProperty(out, rules, "name", name_);
674 if (!path_.empty())
675 PrintProperty(out, rules, "path", path_);
676 PrintProperty(out, rules, "sourceTree", "<group>");
677 out << indent_str << "};\n";
678 }
679
SortLast() const680 bool PBXGroup::SortLast() const {
681 return false;
682 }
683
AddChildImpl(std::unique_ptr<PBXObject> child)684 PBXObject* PBXGroup::AddChildImpl(std::unique_ptr<PBXObject> child) {
685 DCHECK(child);
686 DCHECK(child->Class() == PBXGroupClass ||
687 child->Class() == PBXFileReferenceClass);
688
689 auto iter = std::lower_bound(children_.begin(), children_.end(), child,
690 PBXGroupComparator());
691 return children_.insert(iter, std::move(child))->get();
692 }
693
694 // PBXMainGroup ---------------------------------------------------------------
695
PBXMainGroup(const std::string & source_path)696 PBXMainGroup::PBXMainGroup(const std::string& source_path)
697 : PBXGroup(source_path, std::string()) {}
698
699 PBXMainGroup::~PBXMainGroup() = default;
700
Name() const701 std::string PBXMainGroup::Name() const {
702 return std::string();
703 }
704
705 // PBXProductsGroup -----------------------------------------------------------
706
PBXProductsGroup()707 PBXProductsGroup::PBXProductsGroup() : PBXGroup(std::string(), "Products") {}
708
709 PBXProductsGroup::~PBXProductsGroup() = default;
710
SortLast() const711 bool PBXProductsGroup::SortLast() const {
712 return true;
713 }
714
715 // PBXNativeTarget ------------------------------------------------------------
716
PBXNativeTarget(const std::string & name,const std::string & shell_script,const std::vector<std::string> & configs,const PBXAttributes & attributes,const std::string & product_type,const std::string & product_name,const PBXFileReference * product_reference)717 PBXNativeTarget::PBXNativeTarget(const std::string& name,
718 const std::string& shell_script,
719 const std::vector<std::string>& configs,
720 const PBXAttributes& attributes,
721 const std::string& product_type,
722 const std::string& product_name,
723 const PBXFileReference* product_reference)
724 : PBXTarget(name, shell_script, configs, attributes),
725 product_reference_(product_reference),
726 product_type_(product_type),
727 product_name_(product_name) {
728 DCHECK(product_reference_);
729 build_phases_.push_back(std::make_unique<PBXSourcesBuildPhase>());
730 source_build_phase_ =
731 static_cast<PBXSourcesBuildPhase*>(build_phases_.back().get());
732
733 build_phases_.push_back(std::make_unique<PBXFrameworksBuildPhase>());
734 build_phases_.push_back(std::make_unique<PBXResourcesBuildPhase>());
735 resource_build_phase_ =
736 static_cast<PBXResourcesBuildPhase*>(build_phases_.back().get());
737 }
738
739 PBXNativeTarget::~PBXNativeTarget() = default;
740
AddResourceFile(const PBXFileReference * file_reference)741 void PBXNativeTarget::AddResourceFile(const PBXFileReference* file_reference) {
742 DCHECK(file_reference);
743 resource_build_phase_->AddBuildFile(
744 std::make_unique<PBXBuildFile>(file_reference, resource_build_phase_));
745 }
746
AddFileForIndexing(const PBXFileReference * file_reference)747 void PBXNativeTarget::AddFileForIndexing(
748 const PBXFileReference* file_reference) {
749 DCHECK(file_reference);
750 source_build_phase_->AddBuildFile(
751 std::make_unique<PBXBuildFile>(file_reference, source_build_phase_));
752 }
753
Class() const754 PBXObjectClass PBXNativeTarget::Class() const {
755 return PBXNativeTargetClass;
756 }
757
Print(std::ostream & out,unsigned indent) const758 void PBXNativeTarget::Print(std::ostream& out, unsigned indent) const {
759 const std::string indent_str(indent, '\t');
760 const IndentRules rules = {false, indent + 1};
761 out << indent_str << Reference() << " = {\n";
762 PrintProperty(out, rules, "isa", ToString(Class()));
763 PrintProperty(out, rules, "buildConfigurationList", configurations_);
764 PrintProperty(out, rules, "buildPhases", build_phases_);
765 PrintProperty(out, rules, "buildRules", EmptyPBXObjectVector());
766 PrintProperty(out, rules, "dependencies", dependencies_);
767 PrintProperty(out, rules, "name", name_);
768 PrintProperty(out, rules, "productName", product_name_);
769 PrintProperty(out, rules, "productReference", product_reference_);
770 PrintProperty(out, rules, "productType", product_type_);
771 out << indent_str << "};\n";
772 }
773
774 // PBXProject -----------------------------------------------------------------
775
PBXProject(const std::string & name,std::vector<std::string> configs,const std::string & source_path,const PBXAttributes & attributes)776 PBXProject::PBXProject(const std::string& name,
777 std::vector<std::string> configs,
778 const std::string& source_path,
779 const PBXAttributes& attributes)
780 : name_(name), configs_(std::move(configs)), target_for_indexing_(nullptr) {
781 main_group_ = std::make_unique<PBXMainGroup>(source_path);
782 products_ = main_group_->CreateChild<PBXProductsGroup>();
783
784 configurations_ =
785 std::make_unique<XCConfigurationList>(configs_, attributes, this);
786 }
787
788 PBXProject::~PBXProject() = default;
789
AddSourceFileToIndexingTarget(const std::string & navigator_path,const std::string & source_path)790 void PBXProject::AddSourceFileToIndexingTarget(
791 const std::string& navigator_path,
792 const std::string& source_path) {
793 if (!target_for_indexing_) {
794 AddIndexingTarget();
795 }
796 AddSourceFile(navigator_path, source_path, target_for_indexing_);
797 }
798
AddSourceFile(const std::string & navigator_path,const std::string & source_path,PBXNativeTarget * target)799 void PBXProject::AddSourceFile(const std::string& navigator_path,
800 const std::string& source_path,
801 PBXNativeTarget* target) {
802 PBXFileReference* file_reference =
803 main_group_->AddSourceFile(navigator_path, source_path);
804 std::string_view ext = FindExtension(&source_path);
805 if (!IsSourceFileForIndexing(ext))
806 return;
807
808 DCHECK(target);
809 target->AddFileForIndexing(file_reference);
810 }
811
AddAggregateTarget(const std::string & name,const std::string & output_dir,const std::string & shell_script)812 void PBXProject::AddAggregateTarget(const std::string& name,
813 const std::string& output_dir,
814 const std::string& shell_script) {
815 PBXAttributes attributes;
816 attributes["CLANG_ENABLE_OBJC_WEAK"] = "YES";
817 attributes["CODE_SIGNING_REQUIRED"] = "NO";
818 attributes["CONFIGURATION_BUILD_DIR"] = output_dir;
819 attributes["PRODUCT_NAME"] = name;
820
821 targets_.push_back(std::make_unique<PBXAggregateTarget>(
822 name, shell_script, configs_, attributes));
823 }
824
AddIndexingTarget()825 void PBXProject::AddIndexingTarget() {
826 DCHECK(!target_for_indexing_);
827 PBXAttributes attributes;
828 attributes["CLANG_ENABLE_OBJC_WEAK"] = "YES";
829 attributes["CODE_SIGNING_REQUIRED"] = "NO";
830 attributes["EXECUTABLE_PREFIX"] = "";
831 attributes["HEADER_SEARCH_PATHS"] = main_group_->path();
832 attributes["PRODUCT_NAME"] = "sources";
833
834 PBXFileReference* product_reference =
835 products_->CreateChild<PBXFileReference>(std::string(), "sources",
836 "compiled.mach-o.executable");
837
838 const char product_type[] = "com.apple.product-type.tool";
839 targets_.push_back(std::make_unique<PBXNativeTarget>(
840 "sources", std::string(), configs_, attributes, product_type, "sources",
841 product_reference));
842 target_for_indexing_ = static_cast<PBXNativeTarget*>(targets_.back().get());
843 }
844
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)845 PBXNativeTarget* PBXProject::AddNativeTarget(
846 const std::string& name,
847 const std::string& type,
848 const std::string& output_name,
849 const std::string& output_type,
850 const std::string& output_dir,
851 const std::string& shell_script,
852 const PBXAttributes& extra_attributes) {
853 std::string_view ext = FindExtension(&output_name);
854 PBXFileReference* product = products_->CreateChild<PBXFileReference>(
855 std::string(), output_name, type.empty() ? GetSourceType(ext) : type);
856
857 // Per Xcode build settings documentation: Product Name (PRODUCT_NAME) should
858 // the basename of the product generated by the target.
859 // Therefore, take the basename of output name without file extension as the
860 // "PRODUCT_NAME".
861 size_t basename_offset = FindFilenameOffset(output_name);
862 std::string output_basename = basename_offset != std::string::npos
863 ? output_name.substr(basename_offset)
864 : output_name;
865 size_t ext_offset = FindExtensionOffset(output_basename);
866 std::string product_name = ext_offset != std::string::npos
867 ? output_basename.substr(0, ext_offset - 1)
868 : output_basename;
869
870 PBXAttributes attributes = extra_attributes;
871 attributes["CLANG_ENABLE_OBJC_WEAK"] = "YES";
872 attributes["CODE_SIGNING_REQUIRED"] = "NO";
873 attributes["CONFIGURATION_BUILD_DIR"] = output_dir;
874 attributes["PRODUCT_NAME"] = product_name;
875 attributes["EXCLUDED_SOURCE_FILE_NAMES"] = "*.*";
876
877 targets_.push_back(std::make_unique<PBXNativeTarget>(
878 name, shell_script, configs_, attributes, output_type, product_name,
879 product));
880 return static_cast<PBXNativeTarget*>(targets_.back().get());
881 }
882
SetProjectDirPath(const std::string & project_dir_path)883 void PBXProject::SetProjectDirPath(const std::string& project_dir_path) {
884 DCHECK(!project_dir_path.empty());
885 project_dir_path_.assign(project_dir_path);
886 }
887
SetProjectRoot(const std::string & project_root)888 void PBXProject::SetProjectRoot(const std::string& project_root) {
889 DCHECK(!project_root.empty());
890 project_root_.assign(project_root);
891 }
892
AddTarget(std::unique_ptr<PBXTarget> target)893 void PBXProject::AddTarget(std::unique_ptr<PBXTarget> target) {
894 DCHECK(target);
895 targets_.push_back(std::move(target));
896 }
897
Class() const898 PBXObjectClass PBXProject::Class() const {
899 return PBXProjectClass;
900 }
901
Name() const902 std::string PBXProject::Name() const {
903 return name_;
904 }
905
Comment() const906 std::string PBXProject::Comment() const {
907 return "Project object";
908 }
909
Visit(PBXObjectVisitor & visitor)910 void PBXProject::Visit(PBXObjectVisitor& visitor) {
911 PBXObject::Visit(visitor);
912 configurations_->Visit(visitor);
913 main_group_->Visit(visitor);
914 for (const auto& target : targets_) {
915 target->Visit(visitor);
916 }
917 }
918
Visit(PBXObjectVisitorConst & visitor) const919 void PBXProject::Visit(PBXObjectVisitorConst& visitor) const {
920 PBXObject::Visit(visitor);
921 configurations_->Visit(visitor);
922 main_group_->Visit(visitor);
923 for (const auto& target : targets_) {
924 target->Visit(visitor);
925 }
926 }
Print(std::ostream & out,unsigned indent) const927 void PBXProject::Print(std::ostream& out, unsigned indent) const {
928 const std::string indent_str(indent, '\t');
929 const IndentRules rules = {false, indent + 1};
930 out << indent_str << Reference() << " = {\n";
931 PrintProperty(out, rules, "isa", ToString(Class()));
932 PrintProperty(out, rules, "attributes", attributes_);
933 PrintProperty(out, rules, "buildConfigurationList", configurations_);
934 PrintProperty(out, rules, "compatibilityVersion", "Xcode 3.2");
935 PrintProperty(out, rules, "developmentRegion", "en");
936 PrintProperty(out, rules, "hasScannedForEncodings", 1u);
937 PrintProperty(out, rules, "knownRegions",
938 std::vector<std::string>({"en", "Base"}));
939 PrintProperty(out, rules, "mainGroup", main_group_);
940 PrintProperty(out, rules, "productRefGroup", products_);
941 PrintProperty(out, rules, "projectDirPath", project_dir_path_);
942 PrintProperty(out, rules, "projectRoot", project_root_);
943 PrintProperty(out, rules, "targets", targets_);
944 out << indent_str << "};\n";
945 }
946
947 // PBXResourcesBuildPhase -----------------------------------------------------
948
949 PBXResourcesBuildPhase::PBXResourcesBuildPhase() = default;
950
951 PBXResourcesBuildPhase::~PBXResourcesBuildPhase() = default;
952
Class() const953 PBXObjectClass PBXResourcesBuildPhase::Class() const {
954 return PBXResourcesBuildPhaseClass;
955 }
956
Name() const957 std::string PBXResourcesBuildPhase::Name() const {
958 return "Resources";
959 }
960
Print(std::ostream & out,unsigned indent) const961 void PBXResourcesBuildPhase::Print(std::ostream& out, unsigned indent) const {
962 const std::string indent_str(indent, '\t');
963 const IndentRules rules = {false, indent + 1};
964 out << indent_str << Reference() << " = {\n";
965 PrintProperty(out, rules, "isa", ToString(Class()));
966 PrintProperty(out, rules, "buildActionMask", 0x7fffffffu);
967 PrintProperty(out, rules, "files", files_);
968 PrintProperty(out, rules, "runOnlyForDeploymentPostprocessing", 0u);
969 out << indent_str << "};\n";
970 }
971
972 // PBXShellScriptBuildPhase ---------------------------------------------------
973
PBXShellScriptBuildPhase(const std::string & name,const std::string & shell_script)974 PBXShellScriptBuildPhase::PBXShellScriptBuildPhase(
975 const std::string& name,
976 const std::string& shell_script)
977 : name_("Action \"Compile and copy " + name + " via ninja\""),
978 shell_script_(shell_script) {}
979
980 PBXShellScriptBuildPhase::~PBXShellScriptBuildPhase() = default;
981
Class() const982 PBXObjectClass PBXShellScriptBuildPhase::Class() const {
983 return PBXShellScriptBuildPhaseClass;
984 }
985
Name() const986 std::string PBXShellScriptBuildPhase::Name() const {
987 return name_;
988 }
989
Print(std::ostream & out,unsigned indent) const990 void PBXShellScriptBuildPhase::Print(std::ostream& out, unsigned indent) const {
991 const std::string indent_str(indent, '\t');
992 const IndentRules rules = {false, indent + 1};
993 out << indent_str << Reference() << " = {\n";
994 PrintProperty(out, rules, "isa", ToString(Class()));
995 PrintProperty(out, rules, "alwaysOutOfDate", 1u);
996 PrintProperty(out, rules, "buildActionMask", 0x7fffffffu);
997 PrintProperty(out, rules, "files", files_);
998 PrintProperty(out, rules, "inputPaths", EmptyPBXObjectVector());
999 PrintProperty(out, rules, "name", name_);
1000 PrintProperty(out, rules, "outputPaths", EmptyPBXObjectVector());
1001 PrintProperty(out, rules, "runOnlyForDeploymentPostprocessing", 0u);
1002 PrintProperty(out, rules, "shellPath", "/bin/sh");
1003 PrintProperty(out, rules, "shellScript", shell_script_);
1004 PrintProperty(out, rules, "showEnvVarsInLog", 0u);
1005 out << indent_str << "};\n";
1006 }
1007
1008 // PBXSourcesBuildPhase -------------------------------------------------------
1009
1010 PBXSourcesBuildPhase::PBXSourcesBuildPhase() = default;
1011
1012 PBXSourcesBuildPhase::~PBXSourcesBuildPhase() = default;
1013
Class() const1014 PBXObjectClass PBXSourcesBuildPhase::Class() const {
1015 return PBXSourcesBuildPhaseClass;
1016 }
1017
Name() const1018 std::string PBXSourcesBuildPhase::Name() const {
1019 return "Sources";
1020 }
1021
Print(std::ostream & out,unsigned indent) const1022 void PBXSourcesBuildPhase::Print(std::ostream& out, unsigned indent) const {
1023 const std::string indent_str(indent, '\t');
1024 const IndentRules rules = {false, indent + 1};
1025 out << indent_str << Reference() << " = {\n";
1026 PrintProperty(out, rules, "isa", ToString(Class()));
1027 PrintProperty(out, rules, "buildActionMask", 0x7fffffffu);
1028 PrintProperty(out, rules, "files", files_);
1029 PrintProperty(out, rules, "runOnlyForDeploymentPostprocessing", 0u);
1030 out << indent_str << "};\n";
1031 }
1032
PBXTargetDependency(const PBXTarget * target,std::unique_ptr<PBXContainerItemProxy> container_item_proxy)1033 PBXTargetDependency::PBXTargetDependency(
1034 const PBXTarget* target,
1035 std::unique_ptr<PBXContainerItemProxy> container_item_proxy)
1036 : target_(target), container_item_proxy_(std::move(container_item_proxy)) {}
1037
1038 PBXTargetDependency::~PBXTargetDependency() = default;
1039
Class() const1040 PBXObjectClass PBXTargetDependency::Class() const {
1041 return PBXTargetDependencyClass;
1042 }
1043
Name() const1044 std::string PBXTargetDependency::Name() const {
1045 return "PBXTargetDependency";
1046 }
1047
Visit(PBXObjectVisitor & visitor)1048 void PBXTargetDependency::Visit(PBXObjectVisitor& visitor) {
1049 PBXObject::Visit(visitor);
1050 container_item_proxy_->Visit(visitor);
1051 }
1052
Visit(PBXObjectVisitorConst & visitor) const1053 void PBXTargetDependency::Visit(PBXObjectVisitorConst& visitor) const {
1054 PBXObject::Visit(visitor);
1055 container_item_proxy_->Visit(visitor);
1056 }
1057
Print(std::ostream & out,unsigned indent) const1058 void PBXTargetDependency::Print(std::ostream& out, unsigned indent) const {
1059 const std::string indent_str(indent, '\t');
1060 const IndentRules rules = {false, indent + 1};
1061 out << indent_str << Reference() << " = {\n";
1062 PrintProperty(out, rules, "isa", ToString(Class()));
1063 PrintProperty(out, rules, "target", target_);
1064 PrintProperty(out, rules, "targetProxy", container_item_proxy_);
1065 out << indent_str << "};\n";
1066 }
1067
1068 // XCBuildConfiguration -------------------------------------------------------
1069
XCBuildConfiguration(const std::string & name,const PBXAttributes & attributes)1070 XCBuildConfiguration::XCBuildConfiguration(const std::string& name,
1071 const PBXAttributes& attributes)
1072 : attributes_(attributes), name_(name) {}
1073
1074 XCBuildConfiguration::~XCBuildConfiguration() = default;
1075
Class() const1076 PBXObjectClass XCBuildConfiguration::Class() const {
1077 return XCBuildConfigurationClass;
1078 }
1079
Name() const1080 std::string XCBuildConfiguration::Name() const {
1081 return name_;
1082 }
1083
Print(std::ostream & out,unsigned indent) const1084 void XCBuildConfiguration::Print(std::ostream& out, unsigned indent) const {
1085 const std::string indent_str(indent, '\t');
1086 const IndentRules rules = {false, indent + 1};
1087 out << indent_str << Reference() << " = {\n";
1088 PrintProperty(out, rules, "isa", ToString(Class()));
1089 PrintProperty(out, rules, "buildSettings", attributes_);
1090 PrintProperty(out, rules, "name", name_);
1091 out << indent_str << "};\n";
1092 }
1093
1094 // XCConfigurationList --------------------------------------------------------
1095
XCConfigurationList(const std::vector<std::string> & configs,const PBXAttributes & attributes,const PBXObject * owner_reference)1096 XCConfigurationList::XCConfigurationList(
1097 const std::vector<std::string>& configs,
1098 const PBXAttributes& attributes,
1099 const PBXObject* owner_reference)
1100 : owner_reference_(owner_reference) {
1101 DCHECK(owner_reference_);
1102 for (const std::string& config_name : configs) {
1103 configurations_.push_back(
1104 std::make_unique<XCBuildConfiguration>(config_name, attributes));
1105 }
1106 }
1107
1108 XCConfigurationList::~XCConfigurationList() = default;
1109
Class() const1110 PBXObjectClass XCConfigurationList::Class() const {
1111 return XCConfigurationListClass;
1112 }
1113
Name() const1114 std::string XCConfigurationList::Name() const {
1115 std::stringstream buffer;
1116 buffer << "Build configuration list for "
1117 << ToString(owner_reference_->Class()) << " \""
1118 << owner_reference_->Name() << "\"";
1119 return buffer.str();
1120 }
1121
Visit(PBXObjectVisitor & visitor)1122 void XCConfigurationList::Visit(PBXObjectVisitor& visitor) {
1123 PBXObject::Visit(visitor);
1124 for (const auto& configuration : configurations_) {
1125 configuration->Visit(visitor);
1126 }
1127 }
1128
Visit(PBXObjectVisitorConst & visitor) const1129 void XCConfigurationList::Visit(PBXObjectVisitorConst& visitor) const {
1130 PBXObject::Visit(visitor);
1131 for (const auto& configuration : configurations_) {
1132 configuration->Visit(visitor);
1133 }
1134 }
1135
Print(std::ostream & out,unsigned indent) const1136 void XCConfigurationList::Print(std::ostream& out, unsigned indent) const {
1137 const std::string indent_str(indent, '\t');
1138 const IndentRules rules = {false, indent + 1};
1139 out << indent_str << Reference() << " = {\n";
1140 PrintProperty(out, rules, "isa", ToString(Class()));
1141 PrintProperty(out, rules, "buildConfigurations", configurations_);
1142 PrintProperty(out, rules, "defaultConfigurationIsVisible", 0u);
1143 PrintProperty(out, rules, "defaultConfigurationName",
1144 configurations_[0]->Name());
1145 out << indent_str << "};\n";
1146 }
1147