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