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 {"mm", "sourcecode.cpp.objcpp"},
120 {"nib", "wrapper.nib"},
121 {"o", "compiled.mach-o.objfile"},
122 {"pdf", "image.pdf"},
123 {"pl", "text.script.perl"},
124 {"plist", "text.plist.xml"},
125 {"pm", "text.script.perl"},
126 {"png", "image.png"},
127 {"py", "text.script.python"},
128 {"r", "sourcecode.rez"},
129 {"rez", "sourcecode.rez"},
130 {"s", "sourcecode.asm"},
131 {"storyboard", "file.storyboard"},
132 {"strings", "text.plist.strings"},
133 {"swift", "sourcecode.swift"},
134 {"ttf", "file"},
135 {"xcassets", "folder.assetcatalog"},
136 {"xcconfig", "text.xcconfig"},
137 {"xcdatamodel", "wrapper.xcdatamodel"},
138 {"xcdatamodeld", "wrapper.xcdatamodeld"},
139 {"xctest", "wrapper.cfbundle"},
140 {"xpc", "wrapper.xpc-service"},
141 {"xib", "file.xib"},
142 {"y", "sourcecode.yacc"},
143 };
144
GetSourceType(std::string_view ext)145 const char* GetSourceType(std::string_view ext) {
146 for (size_t i = 0; i < std::size(kSourceTypeForExt); ++i) {
147 if (kSourceTypeForExt[i].ext == ext)
148 return kSourceTypeForExt[i].source_type;
149 }
150
151 return "text";
152 }
153
HasExplicitFileType(std::string_view ext)154 bool HasExplicitFileType(std::string_view ext) {
155 return ext == "dart";
156 }
157
IsSourceFileForIndexing(std::string_view ext)158 bool IsSourceFileForIndexing(std::string_view ext) {
159 return ext == "c" || ext == "cc" || ext == "cpp" || ext == "cxx" ||
160 ext == "m" || ext == "mm";
161 }
162
163 // Wrapper around a const PBXObject* allowing to print just the object
164 // identifier instead of a reference (i.e. identitifer and name). This
165 // is used in a few place where Xcode uses the short identifier only.
166 struct NoReference {
167 const PBXObject* value;
168
NoReference__anonfce4980b0111::NoReference169 explicit NoReference(const PBXObject* value) : value(value) {}
170 };
171
PrintValue(std::ostream & out,IndentRules rules,unsigned value)172 void PrintValue(std::ostream& out, IndentRules rules, unsigned value) {
173 out << value;
174 }
175
PrintValue(std::ostream & out,IndentRules rules,const char * value)176 void PrintValue(std::ostream& out, IndentRules rules, const char* value) {
177 out << EncodeString(value);
178 }
179
PrintValue(std::ostream & out,IndentRules rules,const std::string & value)180 void PrintValue(std::ostream& out,
181 IndentRules rules,
182 const std::string& value) {
183 out << EncodeString(value);
184 }
185
PrintValue(std::ostream & out,IndentRules rules,const NoReference & obj)186 void PrintValue(std::ostream& out, IndentRules rules, const NoReference& obj) {
187 out << obj.value->id();
188 }
189
PrintValue(std::ostream & out,IndentRules rules,const PBXObject * value)190 void PrintValue(std::ostream& out, IndentRules rules, const PBXObject* value) {
191 out << value->Reference();
192 }
193
194 template <typename ObjectClass>
PrintValue(std::ostream & out,IndentRules rules,const std::unique_ptr<ObjectClass> & value)195 void PrintValue(std::ostream& out,
196 IndentRules rules,
197 const std::unique_ptr<ObjectClass>& value) {
198 PrintValue(out, rules, value.get());
199 }
200
201 template <typename ValueType>
PrintValue(std::ostream & out,IndentRules rules,const std::vector<ValueType> & values)202 void PrintValue(std::ostream& out,
203 IndentRules rules,
204 const std::vector<ValueType>& values) {
205 IndentRules sub_rule{rules.one_line, rules.level + 1};
206 out << "(" << (rules.one_line ? " " : "\n");
207 for (const auto& value : values) {
208 if (!sub_rule.one_line)
209 out << std::string(sub_rule.level, '\t');
210
211 PrintValue(out, sub_rule, value);
212 out << "," << (rules.one_line ? " " : "\n");
213 }
214
215 if (!rules.one_line && rules.level)
216 out << std::string(rules.level, '\t');
217 out << ")";
218 }
219
220 template <typename ValueType>
PrintValue(std::ostream & out,IndentRules rules,const std::map<std::string,ValueType> & values)221 void PrintValue(std::ostream& out,
222 IndentRules rules,
223 const std::map<std::string, ValueType>& values) {
224 IndentRules sub_rule{rules.one_line, rules.level + 1};
225 out << "{" << (rules.one_line ? " " : "\n");
226 for (const auto& pair : values) {
227 if (!sub_rule.one_line)
228 out << std::string(sub_rule.level, '\t');
229
230 out << pair.first << " = ";
231 PrintValue(out, sub_rule, pair.second);
232 out << ";" << (rules.one_line ? " " : "\n");
233 }
234
235 if (!rules.one_line && rules.level)
236 out << std::string(rules.level, '\t');
237 out << "}";
238 }
239
240 template <typename ValueType>
PrintProperty(std::ostream & out,IndentRules rules,const char * name,ValueType && value)241 void PrintProperty(std::ostream& out,
242 IndentRules rules,
243 const char* name,
244 ValueType&& value) {
245 if (!rules.one_line && rules.level)
246 out << std::string(rules.level, '\t');
247
248 out << name << " = ";
249 PrintValue(out, rules, std::forward<ValueType>(value));
250 out << ";" << (rules.one_line ? " " : "\n");
251 }
252
253 struct PBXGroupComparator {
254 using PBXObjectPtr = std::unique_ptr<PBXObject>;
operator ()__anonfce4980b0111::PBXGroupComparator255 bool operator()(const PBXObjectPtr& lhs, const PBXObjectPtr& rhs) {
256 if (lhs->Class() != rhs->Class())
257 return rhs->Class() < lhs->Class();
258
259 if (lhs->Class() == PBXGroupClass) {
260 PBXGroup* lhs_group = static_cast<PBXGroup*>(lhs.get());
261 PBXGroup* rhs_group = static_cast<PBXGroup*>(rhs.get());
262 return lhs_group->name() < rhs_group->name();
263 }
264
265 DCHECK_EQ(lhs->Class(), PBXFileReferenceClass);
266 PBXFileReference* lhs_file = static_cast<PBXFileReference*>(lhs.get());
267 PBXFileReference* rhs_file = static_cast<PBXFileReference*>(rhs.get());
268 return lhs_file->Name() < rhs_file->Name();
269 }
270 };
271 } // namespace
272
273 // PBXObjectClass -------------------------------------------------------------
274
ToString(PBXObjectClass cls)275 const char* ToString(PBXObjectClass cls) {
276 switch (cls) {
277 case PBXAggregateTargetClass:
278 return "PBXAggregateTarget";
279 case PBXBuildFileClass:
280 return "PBXBuildFile";
281 case PBXContainerItemProxyClass:
282 return "PBXContainerItemProxy";
283 case PBXFileReferenceClass:
284 return "PBXFileReference";
285 case PBXFrameworksBuildPhaseClass:
286 return "PBXFrameworksBuildPhase";
287 case PBXGroupClass:
288 return "PBXGroup";
289 case PBXNativeTargetClass:
290 return "PBXNativeTarget";
291 case PBXProjectClass:
292 return "PBXProject";
293 case PBXResourcesBuildPhaseClass:
294 return "PBXResourcesBuildPhase";
295 case PBXShellScriptBuildPhaseClass:
296 return "PBXShellScriptBuildPhase";
297 case PBXSourcesBuildPhaseClass:
298 return "PBXSourcesBuildPhase";
299 case PBXTargetDependencyClass:
300 return "PBXTargetDependency";
301 case XCBuildConfigurationClass:
302 return "XCBuildConfiguration";
303 case XCConfigurationListClass:
304 return "XCConfigurationList";
305 }
306 NOTREACHED();
307 return nullptr;
308 }
309
310 // PBXObjectVisitor -----------------------------------------------------------
311
312 PBXObjectVisitor::PBXObjectVisitor() = default;
313
314 PBXObjectVisitor::~PBXObjectVisitor() = default;
315
316 // PBXObjectVisitorConst ------------------------------------------------------
317
318 PBXObjectVisitorConst::PBXObjectVisitorConst() = default;
319
320 PBXObjectVisitorConst::~PBXObjectVisitorConst() = default;
321
322 // PBXObject ------------------------------------------------------------------
323
324 PBXObject::PBXObject() = default;
325
326 PBXObject::~PBXObject() = default;
327
SetId(const std::string & id)328 void PBXObject::SetId(const std::string& id) {
329 DCHECK(id_.empty());
330 DCHECK(!id.empty());
331 id_.assign(id);
332 }
333
Reference() const334 std::string PBXObject::Reference() const {
335 std::string comment = Comment();
336 if (comment.empty())
337 return id_;
338
339 return id_ + " /* " + comment + " */";
340 }
341
Comment() const342 std::string PBXObject::Comment() const {
343 return Name();
344 }
345
Visit(PBXObjectVisitor & visitor)346 void PBXObject::Visit(PBXObjectVisitor& visitor) {
347 visitor.Visit(this);
348 }
349
Visit(PBXObjectVisitorConst & visitor) const350 void PBXObject::Visit(PBXObjectVisitorConst& visitor) const {
351 visitor.Visit(this);
352 }
353
354 // PBXBuildPhase --------------------------------------------------------------
355
356 PBXBuildPhase::PBXBuildPhase() = default;
357
358 PBXBuildPhase::~PBXBuildPhase() = default;
359
AddBuildFile(std::unique_ptr<PBXBuildFile> build_file)360 void PBXBuildPhase::AddBuildFile(std::unique_ptr<PBXBuildFile> build_file) {
361 DCHECK(build_file);
362 files_.push_back(std::move(build_file));
363 }
364
Visit(PBXObjectVisitor & visitor)365 void PBXBuildPhase::Visit(PBXObjectVisitor& visitor) {
366 PBXObject::Visit(visitor);
367 for (const auto& file : files_) {
368 file->Visit(visitor);
369 }
370 }
371
Visit(PBXObjectVisitorConst & visitor) const372 void PBXBuildPhase::Visit(PBXObjectVisitorConst& visitor) const {
373 PBXObject::Visit(visitor);
374 for (const auto& file : files_) {
375 file->Visit(visitor);
376 }
377 }
378
379 // PBXTarget ------------------------------------------------------------------
380
PBXTarget(const std::string & name,const std::string & shell_script,const std::string & config_name,const PBXAttributes & attributes)381 PBXTarget::PBXTarget(const std::string& name,
382 const std::string& shell_script,
383 const std::string& config_name,
384 const PBXAttributes& attributes)
385 : configurations_(
386 std::make_unique<XCConfigurationList>(config_name, attributes, this)),
387 name_(name) {
388 if (!shell_script.empty()) {
389 build_phases_.push_back(
390 std::make_unique<PBXShellScriptBuildPhase>(name, shell_script));
391 }
392 }
393
394 PBXTarget::~PBXTarget() = default;
395
AddDependency(std::unique_ptr<PBXTargetDependency> dependency)396 void PBXTarget::AddDependency(std::unique_ptr<PBXTargetDependency> dependency) {
397 DCHECK(dependency);
398 dependencies_.push_back(std::move(dependency));
399 }
400
Name() const401 std::string PBXTarget::Name() const {
402 return name_;
403 }
404
Visit(PBXObjectVisitor & visitor)405 void PBXTarget::Visit(PBXObjectVisitor& visitor) {
406 PBXObject::Visit(visitor);
407 configurations_->Visit(visitor);
408 for (const auto& dependency : dependencies_)
409 dependency->Visit(visitor);
410 for (const auto& build_phase : build_phases_)
411 build_phase->Visit(visitor);
412 }
413
Visit(PBXObjectVisitorConst & visitor) const414 void PBXTarget::Visit(PBXObjectVisitorConst& visitor) const {
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
423 // PBXAggregateTarget ---------------------------------------------------------
424
PBXAggregateTarget(const std::string & name,const std::string & shell_script,const std::string & config_name,const PBXAttributes & attributes)425 PBXAggregateTarget::PBXAggregateTarget(const std::string& name,
426 const std::string& shell_script,
427 const std::string& config_name,
428 const PBXAttributes& attributes)
429 : PBXTarget(name, shell_script, config_name, attributes) {}
430
431 PBXAggregateTarget::~PBXAggregateTarget() = default;
432
Class() const433 PBXObjectClass PBXAggregateTarget::Class() const {
434 return PBXAggregateTargetClass;
435 }
436
Print(std::ostream & out,unsigned indent) const437 void PBXAggregateTarget::Print(std::ostream& out, unsigned indent) const {
438 const std::string indent_str(indent, '\t');
439 const IndentRules rules = {false, indent + 1};
440 out << indent_str << Reference() << " = {\n";
441 PrintProperty(out, rules, "isa", ToString(Class()));
442 PrintProperty(out, rules, "buildConfigurationList", configurations_);
443 PrintProperty(out, rules, "buildPhases", build_phases_);
444 PrintProperty(out, rules, "dependencies", EmptyPBXObjectVector());
445 PrintProperty(out, rules, "name", name_);
446 PrintProperty(out, rules, "productName", name_);
447 out << indent_str << "};\n";
448 }
449
450 // PBXBuildFile ---------------------------------------------------------------
451
PBXBuildFile(const PBXFileReference * file_reference,const PBXBuildPhase * build_phase)452 PBXBuildFile::PBXBuildFile(const PBXFileReference* file_reference,
453 const PBXBuildPhase* build_phase)
454 : file_reference_(file_reference), build_phase_(build_phase) {
455 DCHECK(file_reference_);
456 DCHECK(build_phase_);
457 }
458
459 PBXBuildFile::~PBXBuildFile() = default;
460
Class() const461 PBXObjectClass PBXBuildFile::Class() const {
462 return PBXBuildFileClass;
463 }
464
Name() const465 std::string PBXBuildFile::Name() const {
466 return file_reference_->Name() + " in " + build_phase_->Name();
467 }
468
Print(std::ostream & out,unsigned indent) const469 void PBXBuildFile::Print(std::ostream& out, unsigned indent) const {
470 const std::string indent_str(indent, '\t');
471 const IndentRules rules = {true, 0};
472 out << indent_str << Reference() << " = {";
473 PrintProperty(out, rules, "isa", ToString(Class()));
474 PrintProperty(out, rules, "fileRef", file_reference_);
475 out << "};\n";
476 }
477
478 // PBXContainerItemProxy ------------------------------------------------------
PBXContainerItemProxy(const PBXProject * project,const PBXTarget * target)479 PBXContainerItemProxy::PBXContainerItemProxy(const PBXProject* project,
480 const PBXTarget* target)
481 : project_(project), target_(target) {}
482
483 PBXContainerItemProxy::~PBXContainerItemProxy() = default;
484
Class() const485 PBXObjectClass PBXContainerItemProxy::Class() const {
486 return PBXContainerItemProxyClass;
487 }
488
Name() const489 std::string PBXContainerItemProxy::Name() const {
490 return "PBXContainerItemProxy";
491 }
492
Print(std::ostream & out,unsigned indent) const493 void PBXContainerItemProxy::Print(std::ostream& out, unsigned indent) const {
494 const std::string indent_str(indent, '\t');
495 const IndentRules rules = {false, indent + 1};
496 out << indent_str << Reference() << " = {\n";
497 PrintProperty(out, rules, "isa", ToString(Class()));
498 PrintProperty(out, rules, "containerPortal", project_);
499 PrintProperty(out, rules, "proxyType", 1u);
500 PrintProperty(out, rules, "remoteGlobalIDString", NoReference(target_));
501 PrintProperty(out, rules, "remoteInfo", target_->Name());
502 out << indent_str << "};\n";
503 }
504
505 // PBXFileReference -----------------------------------------------------------
506
PBXFileReference(const std::string & name,const std::string & path,const std::string & type)507 PBXFileReference::PBXFileReference(const std::string& name,
508 const std::string& path,
509 const std::string& type)
510 : name_(name), path_(path), type_(type) {}
511
512 PBXFileReference::~PBXFileReference() = default;
513
Class() const514 PBXObjectClass PBXFileReference::Class() const {
515 return PBXFileReferenceClass;
516 }
517
Name() const518 std::string PBXFileReference::Name() const {
519 return name_;
520 }
521
Comment() const522 std::string PBXFileReference::Comment() const {
523 return !name_.empty() ? name_ : path_;
524 }
525
Print(std::ostream & out,unsigned indent) const526 void PBXFileReference::Print(std::ostream& out, unsigned indent) const {
527 const std::string indent_str(indent, '\t');
528 const IndentRules rules = {true, 0};
529 out << indent_str << Reference() << " = {";
530 PrintProperty(out, rules, "isa", ToString(Class()));
531
532 if (!type_.empty()) {
533 PrintProperty(out, rules, "explicitFileType", type_);
534 PrintProperty(out, rules, "includeInIndex", 0u);
535 } else {
536 std::string_view ext = FindExtension(&name_);
537 if (HasExplicitFileType(ext))
538 PrintProperty(out, rules, "explicitFileType", GetSourceType(ext));
539 else
540 PrintProperty(out, rules, "lastKnownFileType", GetSourceType(ext));
541 }
542
543 if (!name_.empty() && name_ != path_)
544 PrintProperty(out, rules, "name", name_);
545
546 DCHECK(!path_.empty());
547 PrintProperty(out, rules, "path", path_);
548 PrintProperty(out, rules, "sourceTree",
549 type_.empty() ? "<group>" : "BUILT_PRODUCTS_DIR");
550 out << "};\n";
551 }
552
553 // PBXFrameworksBuildPhase ----------------------------------------------------
554
555 PBXFrameworksBuildPhase::PBXFrameworksBuildPhase() = default;
556
557 PBXFrameworksBuildPhase::~PBXFrameworksBuildPhase() = default;
558
Class() const559 PBXObjectClass PBXFrameworksBuildPhase::Class() const {
560 return PBXFrameworksBuildPhaseClass;
561 }
562
Name() const563 std::string PBXFrameworksBuildPhase::Name() const {
564 return "Frameworks";
565 }
566
Print(std::ostream & out,unsigned indent) const567 void PBXFrameworksBuildPhase::Print(std::ostream& out, unsigned indent) const {
568 const std::string indent_str(indent, '\t');
569 const IndentRules rules = {false, indent + 1};
570 out << indent_str << Reference() << " = {\n";
571 PrintProperty(out, rules, "isa", ToString(Class()));
572 PrintProperty(out, rules, "buildActionMask", 0x7fffffffu);
573 PrintProperty(out, rules, "files", files_);
574 PrintProperty(out, rules, "runOnlyForDeploymentPostprocessing", 0u);
575 out << indent_str << "};\n";
576 }
577
578 // PBXGroup -------------------------------------------------------------------
579
PBXGroup(const std::string & path,const std::string & name)580 PBXGroup::PBXGroup(const std::string& path, const std::string& name)
581 : name_(name), path_(path) {}
582
583 PBXGroup::~PBXGroup() = default;
584
AddSourceFile(const std::string & navigator_path,const std::string & source_path)585 PBXFileReference* PBXGroup::AddSourceFile(const std::string& navigator_path,
586 const std::string& source_path) {
587 DCHECK(!navigator_path.empty());
588 DCHECK(!source_path.empty());
589 std::string::size_type sep = navigator_path.find("/");
590 if (sep == std::string::npos) {
591 // Prevent same file reference being created and added multiple times.
592 for (const auto& child : children_) {
593 if (child->Class() != PBXFileReferenceClass)
594 continue;
595
596 PBXFileReference* child_as_file_reference =
597 static_cast<PBXFileReference*>(child.get());
598 if (child_as_file_reference->Name() == navigator_path &&
599 child_as_file_reference->path() == source_path) {
600 return child_as_file_reference;
601 }
602 }
603
604 return CreateChild<PBXFileReference>(navigator_path, source_path,
605 std::string());
606 }
607
608 PBXGroup* group = nullptr;
609 std::string_view component(navigator_path.data(), sep);
610 for (const auto& child : children_) {
611 if (child->Class() != PBXGroupClass)
612 continue;
613
614 PBXGroup* child_as_group = static_cast<PBXGroup*>(child.get());
615 if (child_as_group->name_ == component) {
616 group = child_as_group;
617 break;
618 }
619 }
620
621 if (!group) {
622 group =
623 CreateChild<PBXGroup>(std::string(component), std::string(component));
624 }
625
626 DCHECK(group);
627 DCHECK(group->name_ == component);
628 return group->AddSourceFile(navigator_path.substr(sep + 1), source_path);
629 }
630
Class() const631 PBXObjectClass PBXGroup::Class() const {
632 return PBXGroupClass;
633 }
634
Name() const635 std::string PBXGroup::Name() const {
636 if (!name_.empty())
637 return name_;
638 if (!path_.empty())
639 return path_;
640 return std::string();
641 }
642
Visit(PBXObjectVisitor & visitor)643 void PBXGroup::Visit(PBXObjectVisitor& visitor) {
644 PBXObject::Visit(visitor);
645 for (const auto& child : children_) {
646 child->Visit(visitor);
647 }
648 }
649
Visit(PBXObjectVisitorConst & visitor) const650 void PBXGroup::Visit(PBXObjectVisitorConst& visitor) const {
651 PBXObject::Visit(visitor);
652 for (const auto& child : children_) {
653 child->Visit(visitor);
654 }
655 }
656
Print(std::ostream & out,unsigned indent) const657 void PBXGroup::Print(std::ostream& out, unsigned indent) const {
658 const std::string indent_str(indent, '\t');
659 const IndentRules rules = {false, indent + 1};
660 out << indent_str << Reference() << " = {\n";
661 PrintProperty(out, rules, "isa", ToString(Class()));
662 PrintProperty(out, rules, "children", children_);
663 if (!name_.empty())
664 PrintProperty(out, rules, "name", name_);
665 if (is_source_ && !path_.empty())
666 PrintProperty(out, rules, "path", path_);
667 PrintProperty(out, rules, "sourceTree", "<group>");
668 out << indent_str << "};\n";
669 }
670
AddChildImpl(std::unique_ptr<PBXObject> child)671 PBXObject* PBXGroup::AddChildImpl(std::unique_ptr<PBXObject> child) {
672 DCHECK(child);
673 DCHECK(child->Class() == PBXGroupClass ||
674 child->Class() == PBXFileReferenceClass);
675
676 PBXObject* child_ptr = child.get();
677 if (autosorted()) {
678 auto iter = std::lower_bound(children_.begin(), children_.end(), child,
679 PBXGroupComparator());
680 children_.insert(iter, std::move(child));
681 } else {
682 children_.push_back(std::move(child));
683 }
684 return child_ptr;
685 }
686
687 // PBXNativeTarget ------------------------------------------------------------
688
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)689 PBXNativeTarget::PBXNativeTarget(const std::string& name,
690 const std::string& shell_script,
691 const std::string& config_name,
692 const PBXAttributes& attributes,
693 const std::string& product_type,
694 const std::string& product_name,
695 const PBXFileReference* product_reference)
696 : PBXTarget(name, shell_script, config_name, attributes),
697 product_reference_(product_reference),
698 product_type_(product_type),
699 product_name_(product_name) {
700 DCHECK(product_reference_);
701 build_phases_.push_back(std::make_unique<PBXSourcesBuildPhase>());
702 source_build_phase_ =
703 static_cast<PBXSourcesBuildPhase*>(build_phases_.back().get());
704
705 build_phases_.push_back(std::make_unique<PBXFrameworksBuildPhase>());
706 build_phases_.push_back(std::make_unique<PBXResourcesBuildPhase>());
707 resource_build_phase_ =
708 static_cast<PBXResourcesBuildPhase*>(build_phases_.back().get());
709 }
710
711 PBXNativeTarget::~PBXNativeTarget() = default;
712
AddResourceFile(const PBXFileReference * file_reference)713 void PBXNativeTarget::AddResourceFile(const PBXFileReference* file_reference) {
714 DCHECK(file_reference);
715 resource_build_phase_->AddBuildFile(
716 std::make_unique<PBXBuildFile>(file_reference, resource_build_phase_));
717 }
718
AddFileForIndexing(const PBXFileReference * file_reference)719 void PBXNativeTarget::AddFileForIndexing(
720 const PBXFileReference* file_reference) {
721 DCHECK(file_reference);
722 source_build_phase_->AddBuildFile(
723 std::make_unique<PBXBuildFile>(file_reference, source_build_phase_));
724 }
725
Class() const726 PBXObjectClass PBXNativeTarget::Class() const {
727 return PBXNativeTargetClass;
728 }
729
Print(std::ostream & out,unsigned indent) const730 void PBXNativeTarget::Print(std::ostream& out, unsigned indent) const {
731 const std::string indent_str(indent, '\t');
732 const IndentRules rules = {false, indent + 1};
733 out << indent_str << Reference() << " = {\n";
734 PrintProperty(out, rules, "isa", ToString(Class()));
735 PrintProperty(out, rules, "buildConfigurationList", configurations_);
736 PrintProperty(out, rules, "buildPhases", build_phases_);
737 PrintProperty(out, rules, "buildRules", EmptyPBXObjectVector());
738 PrintProperty(out, rules, "dependencies", dependencies_);
739 PrintProperty(out, rules, "name", name_);
740 PrintProperty(out, rules, "productName", product_name_);
741 PrintProperty(out, rules, "productReference", product_reference_);
742 PrintProperty(out, rules, "productType", product_type_);
743 out << indent_str << "};\n";
744 }
745
746 // PBXProject -----------------------------------------------------------------
747
PBXProject(const std::string & name,const std::string & config_name,const std::string & source_path,const PBXAttributes & attributes)748 PBXProject::PBXProject(const std::string& name,
749 const std::string& config_name,
750 const std::string& source_path,
751 const PBXAttributes& attributes)
752 : name_(name), config_name_(config_name), target_for_indexing_(nullptr) {
753 main_group_ = std::make_unique<PBXGroup>();
754 main_group_->set_autosorted(false);
755
756 sources_ = main_group_->CreateChild<PBXGroup>(source_path, "Source");
757 sources_->set_is_source(true);
758
759 products_ = main_group_->CreateChild<PBXGroup>(std::string(), "Products");
760
761 configurations_ =
762 std::make_unique<XCConfigurationList>(config_name, attributes, this);
763 }
764
765 PBXProject::~PBXProject() = default;
766
AddSourceFileToIndexingTarget(const std::string & navigator_path,const std::string & source_path)767 void PBXProject::AddSourceFileToIndexingTarget(
768 const std::string& navigator_path,
769 const std::string& source_path) {
770 if (!target_for_indexing_) {
771 AddIndexingTarget();
772 }
773 AddSourceFile(navigator_path, source_path, target_for_indexing_);
774 }
775
AddSourceFile(const std::string & navigator_path,const std::string & source_path,PBXNativeTarget * target)776 void PBXProject::AddSourceFile(const std::string& navigator_path,
777 const std::string& source_path,
778 PBXNativeTarget* target) {
779 PBXFileReference* file_reference =
780 sources_->AddSourceFile(navigator_path, source_path);
781 std::string_view ext = FindExtension(&source_path);
782 if (!IsSourceFileForIndexing(ext))
783 return;
784
785 DCHECK(target);
786 target->AddFileForIndexing(file_reference);
787 }
788
AddAggregateTarget(const std::string & name,const std::string & shell_script)789 void PBXProject::AddAggregateTarget(const std::string& name,
790 const std::string& shell_script) {
791 PBXAttributes attributes;
792 attributes["CLANG_ENABLE_OBJC_WEAK"] = "YES";
793 attributes["CODE_SIGNING_REQUIRED"] = "NO";
794 attributes["CONFIGURATION_BUILD_DIR"] = ".";
795 attributes["PRODUCT_NAME"] = name;
796
797 targets_.push_back(std::make_unique<PBXAggregateTarget>(
798 name, shell_script, config_name_, attributes));
799 }
800
AddIndexingTarget()801 void PBXProject::AddIndexingTarget() {
802 DCHECK(!target_for_indexing_);
803 PBXAttributes attributes;
804 attributes["CLANG_ENABLE_OBJC_WEAK"] = "YES";
805 attributes["CODE_SIGNING_REQUIRED"] = "NO";
806 attributes["EXECUTABLE_PREFIX"] = "";
807 attributes["HEADER_SEARCH_PATHS"] = sources_->path();
808 attributes["PRODUCT_NAME"] = "sources";
809
810 PBXFileReference* product_reference =
811 products_->CreateChild<PBXFileReference>(std::string(), "sources",
812 "compiled.mach-o.executable");
813
814 const char product_type[] = "com.apple.product-type.tool";
815 targets_.push_back(std::make_unique<PBXNativeTarget>(
816 "sources", std::string(), config_name_, attributes, product_type,
817 "sources", product_reference));
818 target_for_indexing_ = static_cast<PBXNativeTarget*>(targets_.back().get());
819 }
820
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)821 PBXNativeTarget* PBXProject::AddNativeTarget(
822 const std::string& name,
823 const std::string& type,
824 const std::string& output_name,
825 const std::string& output_type,
826 const std::string& output_dir,
827 const std::string& shell_script,
828 const PBXAttributes& extra_attributes) {
829 std::string_view ext = FindExtension(&output_name);
830 PBXFileReference* product = products_->CreateChild<PBXFileReference>(
831 std::string(), output_name, type.empty() ? GetSourceType(ext) : type);
832
833 // Per Xcode build settings documentation: Product Name (PRODUCT_NAME) should
834 // the basename of the product generated by the target.
835 // Therefore, take the basename of output name without file extension as the
836 // "PRODUCT_NAME".
837 size_t basename_offset = FindFilenameOffset(output_name);
838 std::string output_basename = basename_offset != std::string::npos
839 ? output_name.substr(basename_offset)
840 : output_name;
841 size_t ext_offset = FindExtensionOffset(output_basename);
842 std::string product_name = ext_offset != std::string::npos
843 ? output_basename.substr(0, ext_offset - 1)
844 : output_basename;
845
846 PBXAttributes attributes = extra_attributes;
847 attributes["CLANG_ENABLE_OBJC_WEAK"] = "YES";
848 attributes["CODE_SIGNING_REQUIRED"] = "NO";
849 attributes["CONFIGURATION_BUILD_DIR"] = output_dir;
850 attributes["PRODUCT_NAME"] = product_name;
851 attributes["EXCLUDED_SOURCE_FILE_NAMES"] = "*.*";
852
853 targets_.push_back(std::make_unique<PBXNativeTarget>(
854 name, shell_script, config_name_, attributes, output_type, product_name,
855 product));
856 return static_cast<PBXNativeTarget*>(targets_.back().get());
857 }
858
SetProjectDirPath(const std::string & project_dir_path)859 void PBXProject::SetProjectDirPath(const std::string& project_dir_path) {
860 DCHECK(!project_dir_path.empty());
861 project_dir_path_.assign(project_dir_path);
862 }
863
SetProjectRoot(const std::string & project_root)864 void PBXProject::SetProjectRoot(const std::string& project_root) {
865 DCHECK(!project_root.empty());
866 project_root_.assign(project_root);
867 }
868
AddTarget(std::unique_ptr<PBXTarget> target)869 void PBXProject::AddTarget(std::unique_ptr<PBXTarget> target) {
870 DCHECK(target);
871 targets_.push_back(std::move(target));
872 }
873
Class() const874 PBXObjectClass PBXProject::Class() const {
875 return PBXProjectClass;
876 }
877
Name() const878 std::string PBXProject::Name() const {
879 return name_;
880 }
881
Comment() const882 std::string PBXProject::Comment() const {
883 return "Project object";
884 }
885
Visit(PBXObjectVisitor & visitor)886 void PBXProject::Visit(PBXObjectVisitor& visitor) {
887 PBXObject::Visit(visitor);
888 configurations_->Visit(visitor);
889 main_group_->Visit(visitor);
890 for (const auto& target : targets_) {
891 target->Visit(visitor);
892 }
893 }
894
Visit(PBXObjectVisitorConst & visitor) const895 void PBXProject::Visit(PBXObjectVisitorConst& visitor) const {
896 PBXObject::Visit(visitor);
897 configurations_->Visit(visitor);
898 main_group_->Visit(visitor);
899 for (const auto& target : targets_) {
900 target->Visit(visitor);
901 }
902 }
Print(std::ostream & out,unsigned indent) const903 void PBXProject::Print(std::ostream& out, unsigned indent) const {
904 const std::string indent_str(indent, '\t');
905 const IndentRules rules = {false, indent + 1};
906 out << indent_str << Reference() << " = {\n";
907 PrintProperty(out, rules, "isa", ToString(Class()));
908 PrintProperty(out, rules, "attributes", attributes_);
909 PrintProperty(out, rules, "buildConfigurationList", configurations_);
910 PrintProperty(out, rules, "compatibilityVersion", "Xcode 3.2");
911 PrintProperty(out, rules, "developmentRegion", "en");
912 PrintProperty(out, rules, "hasScannedForEncodings", 1u);
913 PrintProperty(out, rules, "knownRegions",
914 std::vector<std::string>({"en", "Base"}));
915 PrintProperty(out, rules, "mainGroup", main_group_);
916 PrintProperty(out, rules, "projectDirPath", project_dir_path_);
917 PrintProperty(out, rules, "projectRoot", project_root_);
918 PrintProperty(out, rules, "targets", targets_);
919 out << indent_str << "};\n";
920 }
921
922 // PBXResourcesBuildPhase -----------------------------------------------------
923
924 PBXResourcesBuildPhase::PBXResourcesBuildPhase() = default;
925
926 PBXResourcesBuildPhase::~PBXResourcesBuildPhase() = default;
927
Class() const928 PBXObjectClass PBXResourcesBuildPhase::Class() const {
929 return PBXResourcesBuildPhaseClass;
930 }
931
Name() const932 std::string PBXResourcesBuildPhase::Name() const {
933 return "Resources";
934 }
935
Print(std::ostream & out,unsigned indent) const936 void PBXResourcesBuildPhase::Print(std::ostream& out, unsigned indent) const {
937 const std::string indent_str(indent, '\t');
938 const IndentRules rules = {false, indent + 1};
939 out << indent_str << Reference() << " = {\n";
940 PrintProperty(out, rules, "isa", ToString(Class()));
941 PrintProperty(out, rules, "buildActionMask", 0x7fffffffu);
942 PrintProperty(out, rules, "files", files_);
943 PrintProperty(out, rules, "runOnlyForDeploymentPostprocessing", 0u);
944 out << indent_str << "};\n";
945 }
946
947 // PBXShellScriptBuildPhase ---------------------------------------------------
948
PBXShellScriptBuildPhase(const std::string & name,const std::string & shell_script)949 PBXShellScriptBuildPhase::PBXShellScriptBuildPhase(
950 const std::string& name,
951 const std::string& shell_script)
952 : name_("Action \"Compile and copy " + name + " via ninja\""),
953 shell_script_(shell_script) {}
954
955 PBXShellScriptBuildPhase::~PBXShellScriptBuildPhase() = default;
956
Class() const957 PBXObjectClass PBXShellScriptBuildPhase::Class() const {
958 return PBXShellScriptBuildPhaseClass;
959 }
960
Name() const961 std::string PBXShellScriptBuildPhase::Name() const {
962 return name_;
963 }
964
Print(std::ostream & out,unsigned indent) const965 void PBXShellScriptBuildPhase::Print(std::ostream& out, unsigned indent) const {
966 const std::string indent_str(indent, '\t');
967 const IndentRules rules = {false, indent + 1};
968 out << indent_str << Reference() << " = {\n";
969 PrintProperty(out, rules, "isa", ToString(Class()));
970 PrintProperty(out, rules, "buildActionMask", 0x7fffffffu);
971 PrintProperty(out, rules, "files", files_);
972 PrintProperty(out, rules, "inputPaths", EmptyPBXObjectVector());
973 PrintProperty(out, rules, "name", name_);
974 PrintProperty(out, rules, "outputPaths", EmptyPBXObjectVector());
975 PrintProperty(out, rules, "runOnlyForDeploymentPostprocessing", 0u);
976 PrintProperty(out, rules, "shellPath", "/bin/sh");
977 PrintProperty(out, rules, "shellScript", shell_script_);
978 PrintProperty(out, rules, "showEnvVarsInLog", 0u);
979 out << indent_str << "};\n";
980 }
981
982 // PBXSourcesBuildPhase -------------------------------------------------------
983
984 PBXSourcesBuildPhase::PBXSourcesBuildPhase() = default;
985
986 PBXSourcesBuildPhase::~PBXSourcesBuildPhase() = default;
987
Class() const988 PBXObjectClass PBXSourcesBuildPhase::Class() const {
989 return PBXSourcesBuildPhaseClass;
990 }
991
Name() const992 std::string PBXSourcesBuildPhase::Name() const {
993 return "Sources";
994 }
995
Print(std::ostream & out,unsigned indent) const996 void PBXSourcesBuildPhase::Print(std::ostream& out, unsigned indent) const {
997 const std::string indent_str(indent, '\t');
998 const IndentRules rules = {false, indent + 1};
999 out << indent_str << Reference() << " = {\n";
1000 PrintProperty(out, rules, "isa", ToString(Class()));
1001 PrintProperty(out, rules, "buildActionMask", 0x7fffffffu);
1002 PrintProperty(out, rules, "files", files_);
1003 PrintProperty(out, rules, "runOnlyForDeploymentPostprocessing", 0u);
1004 out << indent_str << "};\n";
1005 }
1006
PBXTargetDependency(const PBXTarget * target,std::unique_ptr<PBXContainerItemProxy> container_item_proxy)1007 PBXTargetDependency::PBXTargetDependency(
1008 const PBXTarget* target,
1009 std::unique_ptr<PBXContainerItemProxy> container_item_proxy)
1010 : target_(target), container_item_proxy_(std::move(container_item_proxy)) {}
1011
1012 PBXTargetDependency::~PBXTargetDependency() = default;
1013
Class() const1014 PBXObjectClass PBXTargetDependency::Class() const {
1015 return PBXTargetDependencyClass;
1016 }
1017
Name() const1018 std::string PBXTargetDependency::Name() const {
1019 return "PBXTargetDependency";
1020 }
1021
Visit(PBXObjectVisitor & visitor)1022 void PBXTargetDependency::Visit(PBXObjectVisitor& visitor) {
1023 PBXObject::Visit(visitor);
1024 container_item_proxy_->Visit(visitor);
1025 }
1026
Visit(PBXObjectVisitorConst & visitor) const1027 void PBXTargetDependency::Visit(PBXObjectVisitorConst& visitor) const {
1028 PBXObject::Visit(visitor);
1029 container_item_proxy_->Visit(visitor);
1030 }
1031
Print(std::ostream & out,unsigned indent) const1032 void PBXTargetDependency::Print(std::ostream& out, unsigned indent) const {
1033 const std::string indent_str(indent, '\t');
1034 const IndentRules rules = {false, indent + 1};
1035 out << indent_str << Reference() << " = {\n";
1036 PrintProperty(out, rules, "isa", ToString(Class()));
1037 PrintProperty(out, rules, "target", target_);
1038 PrintProperty(out, rules, "targetProxy", container_item_proxy_);
1039 out << indent_str << "};\n";
1040 }
1041
1042 // XCBuildConfiguration -------------------------------------------------------
1043
XCBuildConfiguration(const std::string & name,const PBXAttributes & attributes)1044 XCBuildConfiguration::XCBuildConfiguration(const std::string& name,
1045 const PBXAttributes& attributes)
1046 : attributes_(attributes), name_(name) {}
1047
1048 XCBuildConfiguration::~XCBuildConfiguration() = default;
1049
Class() const1050 PBXObjectClass XCBuildConfiguration::Class() const {
1051 return XCBuildConfigurationClass;
1052 }
1053
Name() const1054 std::string XCBuildConfiguration::Name() const {
1055 return name_;
1056 }
1057
Print(std::ostream & out,unsigned indent) const1058 void XCBuildConfiguration::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, "buildSettings", attributes_);
1064 PrintProperty(out, rules, "name", name_);
1065 out << indent_str << "};\n";
1066 }
1067
1068 // XCConfigurationList --------------------------------------------------------
1069
XCConfigurationList(const std::string & name,const PBXAttributes & attributes,const PBXObject * owner_reference)1070 XCConfigurationList::XCConfigurationList(const std::string& name,
1071 const PBXAttributes& attributes,
1072 const PBXObject* owner_reference)
1073 : owner_reference_(owner_reference) {
1074 DCHECK(owner_reference_);
1075 configurations_.push_back(
1076 std::make_unique<XCBuildConfiguration>(name, attributes));
1077 }
1078
1079 XCConfigurationList::~XCConfigurationList() = default;
1080
Class() const1081 PBXObjectClass XCConfigurationList::Class() const {
1082 return XCConfigurationListClass;
1083 }
1084
Name() const1085 std::string XCConfigurationList::Name() const {
1086 std::stringstream buffer;
1087 buffer << "Build configuration list for "
1088 << ToString(owner_reference_->Class()) << " \""
1089 << owner_reference_->Name() << "\"";
1090 return buffer.str();
1091 }
1092
Visit(PBXObjectVisitor & visitor)1093 void XCConfigurationList::Visit(PBXObjectVisitor& visitor) {
1094 PBXObject::Visit(visitor);
1095 for (const auto& configuration : configurations_) {
1096 configuration->Visit(visitor);
1097 }
1098 }
1099
Visit(PBXObjectVisitorConst & visitor) const1100 void XCConfigurationList::Visit(PBXObjectVisitorConst& visitor) const {
1101 PBXObject::Visit(visitor);
1102 for (const auto& configuration : configurations_) {
1103 configuration->Visit(visitor);
1104 }
1105 }
1106
Print(std::ostream & out,unsigned indent) const1107 void XCConfigurationList::Print(std::ostream& out, unsigned indent) const {
1108 const std::string indent_str(indent, '\t');
1109 const IndentRules rules = {false, indent + 1};
1110 out << indent_str << Reference() << " = {\n";
1111 PrintProperty(out, rules, "isa", ToString(Class()));
1112 PrintProperty(out, rules, "buildConfigurations", configurations_);
1113 PrintProperty(out, rules, "defaultConfigurationIsVisible", 1u);
1114 PrintProperty(out, rules, "defaultConfigurationName",
1115 configurations_[0]->Name());
1116 out << indent_str << "};\n";
1117 }
1118