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 {"xib", "file.xib"},
141 {"y", "sourcecode.yacc"},
142 };
143
GetSourceType(const std::string_view & ext)144 const char* GetSourceType(const std::string_view& ext) {
145 for (size_t i = 0; i < std::size(kSourceTypeForExt); ++i) {
146 if (kSourceTypeForExt[i].ext == ext)
147 return kSourceTypeForExt[i].source_type;
148 }
149
150 return "text";
151 }
152
HasExplicitFileType(const std::string_view & ext)153 bool HasExplicitFileType(const std::string_view& ext) {
154 return ext == "dart";
155 }
156
IsSourceFileForIndexing(const std::string_view & ext)157 bool IsSourceFileForIndexing(const std::string_view& ext) {
158 return ext == "c" || ext == "cc" || ext == "cpp" || ext == "cxx" ||
159 ext == "m" || ext == "mm";
160 }
161
PrintValue(std::ostream & out,IndentRules rules,unsigned value)162 void PrintValue(std::ostream& out, IndentRules rules, unsigned value) {
163 out << value;
164 }
165
PrintValue(std::ostream & out,IndentRules rules,const char * value)166 void PrintValue(std::ostream& out, IndentRules rules, const char* value) {
167 out << EncodeString(value);
168 }
169
PrintValue(std::ostream & out,IndentRules rules,const std::string & value)170 void PrintValue(std::ostream& out,
171 IndentRules rules,
172 const std::string& value) {
173 out << EncodeString(value);
174 }
175
PrintValue(std::ostream & out,IndentRules rules,const PBXObject * value)176 void PrintValue(std::ostream& out, IndentRules rules, const PBXObject* value) {
177 out << value->Reference();
178 }
179
180 template <typename ObjectClass>
PrintValue(std::ostream & out,IndentRules rules,const std::unique_ptr<ObjectClass> & value)181 void PrintValue(std::ostream& out,
182 IndentRules rules,
183 const std::unique_ptr<ObjectClass>& value) {
184 PrintValue(out, rules, value.get());
185 }
186
187 template <typename ValueType>
PrintValue(std::ostream & out,IndentRules rules,const std::vector<ValueType> & values)188 void PrintValue(std::ostream& out,
189 IndentRules rules,
190 const std::vector<ValueType>& values) {
191 IndentRules sub_rule{rules.one_line, rules.level + 1};
192 out << "(" << (rules.one_line ? " " : "\n");
193 for (const auto& value : values) {
194 if (!sub_rule.one_line)
195 out << std::string(sub_rule.level, '\t');
196
197 PrintValue(out, sub_rule, value);
198 out << "," << (rules.one_line ? " " : "\n");
199 }
200
201 if (!rules.one_line && rules.level)
202 out << std::string(rules.level, '\t');
203 out << ")";
204 }
205
206 template <typename ValueType>
PrintValue(std::ostream & out,IndentRules rules,const std::map<std::string,ValueType> & values)207 void PrintValue(std::ostream& out,
208 IndentRules rules,
209 const std::map<std::string, ValueType>& values) {
210 IndentRules sub_rule{rules.one_line, rules.level + 1};
211 out << "{" << (rules.one_line ? " " : "\n");
212 for (const auto& pair : values) {
213 if (!sub_rule.one_line)
214 out << std::string(sub_rule.level, '\t');
215
216 out << pair.first << " = ";
217 PrintValue(out, sub_rule, pair.second);
218 out << ";" << (rules.one_line ? " " : "\n");
219 }
220
221 if (!rules.one_line && rules.level)
222 out << std::string(rules.level, '\t');
223 out << "}";
224 }
225
226 template <typename ValueType>
PrintProperty(std::ostream & out,IndentRules rules,const char * name,ValueType && value)227 void PrintProperty(std::ostream& out,
228 IndentRules rules,
229 const char* name,
230 ValueType&& value) {
231 if (!rules.one_line && rules.level)
232 out << std::string(rules.level, '\t');
233
234 out << name << " = ";
235 PrintValue(out, rules, std::forward<ValueType>(value));
236 out << ";" << (rules.one_line ? " " : "\n");
237 }
238 } // namespace
239
240 // PBXObjectClass -------------------------------------------------------------
241
ToString(PBXObjectClass cls)242 const char* ToString(PBXObjectClass cls) {
243 switch (cls) {
244 case PBXAggregateTargetClass:
245 return "PBXAggregateTarget";
246 case PBXBuildFileClass:
247 return "PBXBuildFile";
248 case PBXContainerItemProxyClass:
249 return "PBXContainerItemProxy";
250 case PBXFileReferenceClass:
251 return "PBXFileReference";
252 case PBXFrameworksBuildPhaseClass:
253 return "PBXFrameworksBuildPhase";
254 case PBXGroupClass:
255 return "PBXGroup";
256 case PBXNativeTargetClass:
257 return "PBXNativeTarget";
258 case PBXProjectClass:
259 return "PBXProject";
260 case PBXShellScriptBuildPhaseClass:
261 return "PBXShellScriptBuildPhase";
262 case PBXSourcesBuildPhaseClass:
263 return "PBXSourcesBuildPhase";
264 case PBXTargetDependencyClass:
265 return "PBXTargetDependency";
266 case XCBuildConfigurationClass:
267 return "XCBuildConfiguration";
268 case XCConfigurationListClass:
269 return "XCConfigurationList";
270 }
271 NOTREACHED();
272 return nullptr;
273 }
274
275 // PBXObjectVisitor -----------------------------------------------------------
276
277 PBXObjectVisitor::PBXObjectVisitor() = default;
278
279 PBXObjectVisitor::~PBXObjectVisitor() = default;
280
281 // PBXObject ------------------------------------------------------------------
282
283 PBXObject::PBXObject() = default;
284
285 PBXObject::~PBXObject() = default;
286
SetId(const std::string & id)287 void PBXObject::SetId(const std::string& id) {
288 DCHECK(id_.empty());
289 DCHECK(!id.empty());
290 id_.assign(id);
291 }
292
Reference() const293 std::string PBXObject::Reference() const {
294 std::string comment = Comment();
295 if (comment.empty())
296 return id_;
297
298 return id_ + " /* " + comment + " */";
299 }
300
Comment() const301 std::string PBXObject::Comment() const {
302 return Name();
303 }
304
Visit(PBXObjectVisitor & visitor)305 void PBXObject::Visit(PBXObjectVisitor& visitor) {
306 visitor.Visit(this);
307 }
308
309 // PBXBuildPhase --------------------------------------------------------------
310
311 PBXBuildPhase::PBXBuildPhase() = default;
312
313 PBXBuildPhase::~PBXBuildPhase() = default;
314
315 // PBXTarget ------------------------------------------------------------------
316
PBXTarget(const std::string & name,const std::string & shell_script,const std::string & config_name,const PBXAttributes & attributes)317 PBXTarget::PBXTarget(const std::string& name,
318 const std::string& shell_script,
319 const std::string& config_name,
320 const PBXAttributes& attributes)
321 : configurations_(new XCConfigurationList(config_name, attributes, this)),
322 name_(name) {
323 if (!shell_script.empty()) {
324 build_phases_.push_back(
325 std::make_unique<PBXShellScriptBuildPhase>(name, shell_script));
326 }
327 }
328
329 PBXTarget::~PBXTarget() = default;
330
AddDependency(std::unique_ptr<PBXTargetDependency> dependency)331 void PBXTarget::AddDependency(std::unique_ptr<PBXTargetDependency> dependency) {
332 DCHECK(dependency);
333 dependencies_.push_back(std::move(dependency));
334 }
335
Name() const336 std::string PBXTarget::Name() const {
337 return name_;
338 }
339
Visit(PBXObjectVisitor & visitor)340 void PBXTarget::Visit(PBXObjectVisitor& visitor) {
341 PBXObject::Visit(visitor);
342 configurations_->Visit(visitor);
343 for (const auto& dependency : dependencies_)
344 dependency->Visit(visitor);
345 for (const auto& build_phase : build_phases_)
346 build_phase->Visit(visitor);
347 }
348
349 // PBXAggregateTarget ---------------------------------------------------------
350
PBXAggregateTarget(const std::string & name,const std::string & shell_script,const std::string & config_name,const PBXAttributes & attributes)351 PBXAggregateTarget::PBXAggregateTarget(const std::string& name,
352 const std::string& shell_script,
353 const std::string& config_name,
354 const PBXAttributes& attributes)
355 : PBXTarget(name, shell_script, config_name, attributes) {}
356
357 PBXAggregateTarget::~PBXAggregateTarget() = default;
358
Class() const359 PBXObjectClass PBXAggregateTarget::Class() const {
360 return PBXAggregateTargetClass;
361 }
362
Print(std::ostream & out,unsigned indent) const363 void PBXAggregateTarget::Print(std::ostream& out, unsigned indent) const {
364 const std::string indent_str(indent, '\t');
365 const IndentRules rules = {false, indent + 1};
366 out << indent_str << Reference() << " = {\n";
367 PrintProperty(out, rules, "isa", ToString(Class()));
368 PrintProperty(out, rules, "buildConfigurationList", configurations_);
369 PrintProperty(out, rules, "buildPhases", build_phases_);
370 PrintProperty(out, rules, "dependencies", EmptyPBXObjectVector());
371 PrintProperty(out, rules, "name", name_);
372 PrintProperty(out, rules, "productName", name_);
373 out << indent_str << "};\n";
374 }
375
376 // PBXBuildFile ---------------------------------------------------------------
377
PBXBuildFile(const PBXFileReference * file_reference,const PBXSourcesBuildPhase * build_phase,const CompilerFlags compiler_flag)378 PBXBuildFile::PBXBuildFile(const PBXFileReference* file_reference,
379 const PBXSourcesBuildPhase* build_phase,
380 const CompilerFlags compiler_flag)
381 : file_reference_(file_reference),
382 build_phase_(build_phase),
383 compiler_flag_(compiler_flag) {
384 DCHECK(file_reference_);
385 DCHECK(build_phase_);
386 }
387
388 PBXBuildFile::~PBXBuildFile() = default;
389
Class() const390 PBXObjectClass PBXBuildFile::Class() const {
391 return PBXBuildFileClass;
392 }
393
Name() const394 std::string PBXBuildFile::Name() const {
395 return file_reference_->Name() + " in " + build_phase_->Name();
396 }
397
Print(std::ostream & out,unsigned indent) const398 void PBXBuildFile::Print(std::ostream& out, unsigned indent) const {
399 const std::string indent_str(indent, '\t');
400 const IndentRules rules = {true, 0};
401 out << indent_str << Reference() << " = {";
402 PrintProperty(out, rules, "isa", ToString(Class()));
403 PrintProperty(out, rules, "fileRef", file_reference_);
404 if (compiler_flag_ == CompilerFlags::HELP) {
405 std::map<std::string, std::string> settings = {
406 {"COMPILER_FLAGS", "--help"},
407 };
408 PrintProperty(out, rules, "settings", settings);
409 }
410 out << "};\n";
411 }
412
413 // PBXContainerItemProxy ------------------------------------------------------
PBXContainerItemProxy(const PBXProject * project,const PBXTarget * target)414 PBXContainerItemProxy::PBXContainerItemProxy(const PBXProject* project,
415 const PBXTarget* target)
416 : project_(project), target_(target) {}
417
418 PBXContainerItemProxy::~PBXContainerItemProxy() = default;
419
Class() const420 PBXObjectClass PBXContainerItemProxy::Class() const {
421 return PBXContainerItemProxyClass;
422 }
423
Visit(PBXObjectVisitor & visitor)424 void PBXContainerItemProxy::Visit(PBXObjectVisitor& visitor) {
425 PBXObject::Visit(visitor);
426 }
427
Name() const428 std::string PBXContainerItemProxy::Name() const {
429 return "PBXContainerItemProxy";
430 }
431
Print(std::ostream & out,unsigned indent) const432 void PBXContainerItemProxy::Print(std::ostream& out, unsigned indent) const {
433 const std::string indent_str(indent, '\t');
434 const IndentRules rules = {true, 0};
435 out << indent_str << Reference() << " = {";
436 PrintProperty(out, rules, "isa", ToString(Class()));
437 PrintProperty(out, rules, "containerPortal", project_);
438 PrintProperty(out, rules, "proxyType", 1u);
439 PrintProperty(out, rules, "remoteGlobalIDString", target_);
440 PrintProperty(out, rules, "remoteInfo", target_->Name());
441 out << indent_str << "};\n";
442 }
443
444 // PBXFileReference -----------------------------------------------------------
445
PBXFileReference(const std::string & name,const std::string & path,const std::string & type)446 PBXFileReference::PBXFileReference(const std::string& name,
447 const std::string& path,
448 const std::string& type)
449 : name_(name), path_(path), type_(type) {}
450
451 PBXFileReference::~PBXFileReference() = default;
452
Class() const453 PBXObjectClass PBXFileReference::Class() const {
454 return PBXFileReferenceClass;
455 }
456
Name() const457 std::string PBXFileReference::Name() const {
458 return name_;
459 }
460
Print(std::ostream & out,unsigned indent) const461 void PBXFileReference::Print(std::ostream& out, unsigned indent) const {
462 const std::string indent_str(indent, '\t');
463 const IndentRules rules = {true, 0};
464 out << indent_str << Reference() << " = {";
465 PrintProperty(out, rules, "isa", ToString(Class()));
466
467 if (!type_.empty()) {
468 PrintProperty(out, rules, "explicitFileType", type_);
469 PrintProperty(out, rules, "includeInIndex", 0u);
470 } else {
471 std::string_view ext = FindExtension(&name_);
472 if (HasExplicitFileType(ext))
473 PrintProperty(out, rules, "explicitFileType", GetSourceType(ext));
474 else
475 PrintProperty(out, rules, "lastKnownFileType", GetSourceType(ext));
476 }
477
478 if (!name_.empty())
479 PrintProperty(out, rules, "name", name_);
480
481 DCHECK(!path_.empty());
482 PrintProperty(out, rules, "path", path_);
483 PrintProperty(out, rules, "sourceTree",
484 type_.empty() ? "<group>" : "BUILT_PRODUCTS_DIR");
485 out << "};\n";
486 }
487
488 // PBXFrameworksBuildPhase ----------------------------------------------------
489
490 PBXFrameworksBuildPhase::PBXFrameworksBuildPhase() = default;
491
492 PBXFrameworksBuildPhase::~PBXFrameworksBuildPhase() = default;
493
Class() const494 PBXObjectClass PBXFrameworksBuildPhase::Class() const {
495 return PBXFrameworksBuildPhaseClass;
496 }
497
Name() const498 std::string PBXFrameworksBuildPhase::Name() const {
499 return "Frameworks";
500 }
501
Print(std::ostream & out,unsigned indent) const502 void PBXFrameworksBuildPhase::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, "buildActionMask", 0x7fffffffu);
508 PrintProperty(out, rules, "files", EmptyPBXObjectVector());
509 PrintProperty(out, rules, "runOnlyForDeploymentPostprocessing", 0u);
510 out << indent_str << "};\n";
511 }
512
513 // PBXGroup -------------------------------------------------------------------
514
PBXGroup(const std::string & path,const std::string & name)515 PBXGroup::PBXGroup(const std::string& path, const std::string& name)
516 : name_(name), path_(path) {}
517
518 PBXGroup::~PBXGroup() = default;
519
AddChild(std::unique_ptr<PBXObject> child)520 PBXObject* PBXGroup::AddChild(std::unique_ptr<PBXObject> child) {
521 DCHECK(child);
522 children_.push_back(std::move(child));
523 return children_.back().get();
524 }
525
AddSourceFile(const std::string & navigator_path,const std::string & source_path)526 PBXFileReference* PBXGroup::AddSourceFile(const std::string& navigator_path,
527 const std::string& source_path) {
528 DCHECK(!navigator_path.empty());
529 DCHECK(!source_path.empty());
530 std::string::size_type sep = navigator_path.find("/");
531 if (sep == std::string::npos) {
532 // Prevent same file reference being created and added multiple times.
533 for (const auto& child : children_) {
534 if (child->Class() != PBXFileReferenceClass)
535 continue;
536
537 PBXFileReference* child_as_file_reference =
538 static_cast<PBXFileReference*>(child.get());
539 if (child_as_file_reference->Name() == navigator_path &&
540 child_as_file_reference->path() == source_path) {
541 return child_as_file_reference;
542 }
543 }
544
545 children_.push_back(std::make_unique<PBXFileReference>(
546 navigator_path, source_path, std::string()));
547 return static_cast<PBXFileReference*>(children_.back().get());
548 }
549
550 PBXGroup* group = nullptr;
551 std::string_view component(navigator_path.data(), sep);
552 for (const auto& child : children_) {
553 if (child->Class() != PBXGroupClass)
554 continue;
555
556 PBXGroup* child_as_group = static_cast<PBXGroup*>(child.get());
557 if (child_as_group->name_ == component) {
558 group = child_as_group;
559 break;
560 }
561 }
562
563 if (!group) {
564 children_.push_back(std::make_unique<PBXGroup>(std::string(component),
565 std::string(component)));
566 group = static_cast<PBXGroup*>(children_.back().get());
567 }
568
569 DCHECK(group);
570 DCHECK(group->name_ == component);
571 return group->AddSourceFile(navigator_path.substr(sep + 1), source_path);
572 }
573
Class() const574 PBXObjectClass PBXGroup::Class() const {
575 return PBXGroupClass;
576 }
577
Name() const578 std::string PBXGroup::Name() const {
579 if (!name_.empty())
580 return name_;
581 if (!path_.empty())
582 return path_;
583 return std::string();
584 }
585
Visit(PBXObjectVisitor & visitor)586 void PBXGroup::Visit(PBXObjectVisitor& visitor) {
587 PBXObject::Visit(visitor);
588 for (const auto& child : children_) {
589 child->Visit(visitor);
590 }
591 }
592
Print(std::ostream & out,unsigned indent) const593 void PBXGroup::Print(std::ostream& out, unsigned indent) const {
594 const std::string indent_str(indent, '\t');
595 const IndentRules rules = {false, indent + 1};
596 out << indent_str << Reference() << " = {\n";
597 PrintProperty(out, rules, "isa", ToString(Class()));
598 PrintProperty(out, rules, "children", children_);
599 if (!name_.empty())
600 PrintProperty(out, rules, "name", name_);
601 if (is_source_ && !path_.empty())
602 PrintProperty(out, rules, "path", path_);
603 PrintProperty(out, rules, "sourceTree", "<group>");
604 out << indent_str << "};\n";
605 }
606
607 // PBXNativeTarget ------------------------------------------------------------
608
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)609 PBXNativeTarget::PBXNativeTarget(const std::string& name,
610 const std::string& shell_script,
611 const std::string& config_name,
612 const PBXAttributes& attributes,
613 const std::string& product_type,
614 const std::string& product_name,
615 const PBXFileReference* product_reference)
616 : PBXTarget(name, shell_script, config_name, attributes),
617 product_reference_(product_reference),
618 product_type_(product_type),
619 product_name_(product_name) {
620 DCHECK(product_reference_);
621 build_phases_.push_back(std::make_unique<PBXSourcesBuildPhase>());
622 source_build_phase_ =
623 static_cast<PBXSourcesBuildPhase*>(build_phases_.back().get());
624
625 build_phases_.push_back(std::make_unique<PBXFrameworksBuildPhase>());
626 }
627
628 PBXNativeTarget::~PBXNativeTarget() = default;
629
AddFileForIndexing(const PBXFileReference * file_reference,const CompilerFlags compiler_flag)630 void PBXNativeTarget::AddFileForIndexing(const PBXFileReference* file_reference,
631 const CompilerFlags compiler_flag) {
632 DCHECK(file_reference);
633 source_build_phase_->AddBuildFile(std::make_unique<PBXBuildFile>(
634 file_reference, source_build_phase_, compiler_flag));
635 }
636
Class() const637 PBXObjectClass PBXNativeTarget::Class() const {
638 return PBXNativeTargetClass;
639 }
640
Print(std::ostream & out,unsigned indent) const641 void PBXNativeTarget::Print(std::ostream& out, unsigned indent) const {
642 const std::string indent_str(indent, '\t');
643 const IndentRules rules = {false, indent + 1};
644 out << indent_str << Reference() << " = {\n";
645 PrintProperty(out, rules, "isa", ToString(Class()));
646 PrintProperty(out, rules, "buildConfigurationList", configurations_);
647 PrintProperty(out, rules, "buildPhases", build_phases_);
648 PrintProperty(out, rules, "buildRules", EmptyPBXObjectVector());
649 PrintProperty(out, rules, "dependencies", dependencies_);
650 PrintProperty(out, rules, "name", name_);
651 PrintProperty(out, rules, "productName", product_name_);
652 PrintProperty(out, rules, "productReference", product_reference_);
653 PrintProperty(out, rules, "productType", product_type_);
654 out << indent_str << "};\n";
655 }
656
657 // PBXProject -----------------------------------------------------------------
658
PBXProject(const std::string & name,const std::string & config_name,const std::string & source_path,const PBXAttributes & attributes)659 PBXProject::PBXProject(const std::string& name,
660 const std::string& config_name,
661 const std::string& source_path,
662 const PBXAttributes& attributes)
663 : name_(name), config_name_(config_name), target_for_indexing_(nullptr) {
664 attributes_["BuildIndependentTargetsInParallel"] = "YES";
665
666 main_group_.reset(new PBXGroup);
667 sources_ = static_cast<PBXGroup*>(
668 main_group_->AddChild(std::make_unique<PBXGroup>(source_path, "Source")));
669 sources_->set_is_source(true);
670 products_ = static_cast<PBXGroup*>(main_group_->AddChild(
671 std::make_unique<PBXGroup>(std::string(), "Product")));
672 main_group_->AddChild(std::make_unique<PBXGroup>(std::string(), "Build"));
673
674 configurations_.reset(new XCConfigurationList(config_name, attributes, this));
675 }
676
677 PBXProject::~PBXProject() = default;
678
AddSourceFileToIndexingTarget(const std::string & navigator_path,const std::string & source_path,const CompilerFlags compiler_flag)679 void PBXProject::AddSourceFileToIndexingTarget(
680 const std::string& navigator_path,
681 const std::string& source_path,
682 const CompilerFlags compiler_flag) {
683 if (!target_for_indexing_) {
684 AddIndexingTarget();
685 }
686 AddSourceFile(navigator_path, source_path, compiler_flag,
687 target_for_indexing_);
688 }
689
AddSourceFile(const std::string & navigator_path,const std::string & source_path,const CompilerFlags compiler_flag,PBXNativeTarget * target)690 void PBXProject::AddSourceFile(const std::string& navigator_path,
691 const std::string& source_path,
692 const CompilerFlags compiler_flag,
693 PBXNativeTarget* target) {
694 PBXFileReference* file_reference =
695 sources_->AddSourceFile(navigator_path, source_path);
696 std::string_view ext = FindExtension(&source_path);
697 if (!IsSourceFileForIndexing(ext))
698 return;
699
700 DCHECK(target);
701 target->AddFileForIndexing(file_reference, compiler_flag);
702 }
703
AddAggregateTarget(const std::string & name,const std::string & shell_script)704 void PBXProject::AddAggregateTarget(const std::string& name,
705 const std::string& shell_script) {
706 PBXAttributes attributes;
707 attributes["CODE_SIGNING_REQUIRED"] = "NO";
708 attributes["CONFIGURATION_BUILD_DIR"] = ".";
709 attributes["PRODUCT_NAME"] = name;
710
711 targets_.push_back(std::make_unique<PBXAggregateTarget>(
712 name, shell_script, config_name_, attributes));
713 }
714
AddIndexingTarget()715 void PBXProject::AddIndexingTarget() {
716 DCHECK(!target_for_indexing_);
717 PBXAttributes attributes;
718 attributes["EXECUTABLE_PREFIX"] = "";
719 attributes["HEADER_SEARCH_PATHS"] = sources_->path();
720 attributes["PRODUCT_NAME"] = "sources";
721
722 PBXFileReference* product_reference = static_cast<PBXFileReference*>(
723 products_->AddChild(std::make_unique<PBXFileReference>(
724 std::string(), "sources", "compiled.mach-o.executable")));
725
726 const char product_type[] = "com.apple.product-type.tool";
727 targets_.push_back(std::make_unique<PBXNativeTarget>(
728 "sources", std::string(), config_name_, attributes, product_type,
729 "sources", product_reference));
730 target_for_indexing_ = static_cast<PBXNativeTarget*>(targets_.back().get());
731 }
732
AddNativeTarget(const std::string & name,const std::string & type,const std::string & output_name,const std::string & output_type,const std::string & shell_script,const PBXAttributes & extra_attributes)733 PBXNativeTarget* PBXProject::AddNativeTarget(
734 const std::string& name,
735 const std::string& type,
736 const std::string& output_name,
737 const std::string& output_type,
738 const std::string& shell_script,
739 const PBXAttributes& extra_attributes) {
740 std::string_view ext = FindExtension(&output_name);
741 PBXFileReference* product = static_cast<PBXFileReference*>(
742 products_->AddChild(std::make_unique<PBXFileReference>(
743 std::string(), output_name,
744 type.empty() ? GetSourceType(ext) : type)));
745
746 // Per Xcode build settings documentation: Product Name (PRODUCT_NAME) should
747 // the basename of the product generated by the target.
748 // Therefore, take the basename of output name without file extension as the
749 // "PRODUCT_NAME".
750 size_t basename_offset = FindFilenameOffset(output_name);
751 std::string output_basename = basename_offset != std::string::npos
752 ? output_name.substr(basename_offset)
753 : output_name;
754 size_t ext_offset = FindExtensionOffset(output_basename);
755 std::string product_name = ext_offset != std::string::npos
756 ? output_basename.substr(0, ext_offset - 1)
757 : output_basename;
758
759 PBXAttributes attributes = extra_attributes;
760 attributes["CODE_SIGNING_REQUIRED"] = "NO";
761 attributes["CONFIGURATION_BUILD_DIR"] = ".";
762 attributes["PRODUCT_NAME"] = product_name;
763
764 targets_.push_back(std::make_unique<PBXNativeTarget>(
765 name, shell_script, config_name_, attributes, output_type, product_name,
766 product));
767 return static_cast<PBXNativeTarget*>(targets_.back().get());
768 }
769
SetProjectDirPath(const std::string & project_dir_path)770 void PBXProject::SetProjectDirPath(const std::string& project_dir_path) {
771 DCHECK(!project_dir_path.empty());
772 project_dir_path_.assign(project_dir_path);
773 }
774
SetProjectRoot(const std::string & project_root)775 void PBXProject::SetProjectRoot(const std::string& project_root) {
776 DCHECK(!project_root.empty());
777 project_root_.assign(project_root);
778 }
779
AddTarget(std::unique_ptr<PBXTarget> target)780 void PBXProject::AddTarget(std::unique_ptr<PBXTarget> target) {
781 DCHECK(target);
782 targets_.push_back(std::move(target));
783 }
784
Class() const785 PBXObjectClass PBXProject::Class() const {
786 return PBXProjectClass;
787 }
788
Name() const789 std::string PBXProject::Name() const {
790 return name_;
791 }
792
Comment() const793 std::string PBXProject::Comment() const {
794 return "Project object";
795 }
796
Visit(PBXObjectVisitor & visitor)797 void PBXProject::Visit(PBXObjectVisitor& visitor) {
798 PBXObject::Visit(visitor);
799 configurations_->Visit(visitor);
800 main_group_->Visit(visitor);
801 for (const auto& target : targets_) {
802 target->Visit(visitor);
803 }
804 }
805
Print(std::ostream & out,unsigned indent) const806 void PBXProject::Print(std::ostream& out, unsigned indent) const {
807 const std::string indent_str(indent, '\t');
808 const IndentRules rules = {false, indent + 1};
809 out << indent_str << Reference() << " = {\n";
810 PrintProperty(out, rules, "isa", ToString(Class()));
811 PrintProperty(out, rules, "attributes", attributes_);
812 PrintProperty(out, rules, "buildConfigurationList", configurations_);
813 PrintProperty(out, rules, "compatibilityVersion", "Xcode 3.2");
814 PrintProperty(out, rules, "developmentRegion", "English");
815 PrintProperty(out, rules, "hasScannedForEncodings", 1u);
816 PrintProperty(out, rules, "knownRegions", std::vector<std::string>({"en"}));
817 PrintProperty(out, rules, "mainGroup", main_group_);
818 PrintProperty(out, rules, "projectDirPath", project_dir_path_);
819 PrintProperty(out, rules, "projectRoot", project_root_);
820 PrintProperty(out, rules, "targets", targets_);
821 out << indent_str << "};\n";
822 }
823
824 // PBXShellScriptBuildPhase ---------------------------------------------------
825
PBXShellScriptBuildPhase(const std::string & name,const std::string & shell_script)826 PBXShellScriptBuildPhase::PBXShellScriptBuildPhase(
827 const std::string& name,
828 const std::string& shell_script)
829 : name_("Action \"Compile and copy " + name + " via ninja\""),
830 shell_script_(shell_script) {}
831
832 PBXShellScriptBuildPhase::~PBXShellScriptBuildPhase() = default;
833
Class() const834 PBXObjectClass PBXShellScriptBuildPhase::Class() const {
835 return PBXShellScriptBuildPhaseClass;
836 }
837
Name() const838 std::string PBXShellScriptBuildPhase::Name() const {
839 return name_;
840 }
841
Print(std::ostream & out,unsigned indent) const842 void PBXShellScriptBuildPhase::Print(std::ostream& out, unsigned indent) const {
843 const std::string indent_str(indent, '\t');
844 const IndentRules rules = {false, indent + 1};
845 out << indent_str << Reference() << " = {\n";
846 PrintProperty(out, rules, "isa", ToString(Class()));
847 PrintProperty(out, rules, "buildActionMask", 0x7fffffffu);
848 PrintProperty(out, rules, "files", EmptyPBXObjectVector());
849 PrintProperty(out, rules, "inputPaths", EmptyPBXObjectVector());
850 PrintProperty(out, rules, "name", name_);
851 PrintProperty(out, rules, "outputPaths", EmptyPBXObjectVector());
852 PrintProperty(out, rules, "runOnlyForDeploymentPostprocessing", 0u);
853 PrintProperty(out, rules, "shellPath", "/bin/sh");
854 PrintProperty(out, rules, "shellScript", shell_script_);
855 PrintProperty(out, rules, "showEnvVarsInLog", 0u);
856 out << indent_str << "};\n";
857 }
858
859 // PBXSourcesBuildPhase -------------------------------------------------------
860
861 PBXSourcesBuildPhase::PBXSourcesBuildPhase() = default;
862
863 PBXSourcesBuildPhase::~PBXSourcesBuildPhase() = default;
864
AddBuildFile(std::unique_ptr<PBXBuildFile> build_file)865 void PBXSourcesBuildPhase::AddBuildFile(
866 std::unique_ptr<PBXBuildFile> build_file) {
867 files_.push_back(std::move(build_file));
868 }
869
Class() const870 PBXObjectClass PBXSourcesBuildPhase::Class() const {
871 return PBXSourcesBuildPhaseClass;
872 }
873
Name() const874 std::string PBXSourcesBuildPhase::Name() const {
875 return "Sources";
876 }
877
Visit(PBXObjectVisitor & visitor)878 void PBXSourcesBuildPhase::Visit(PBXObjectVisitor& visitor) {
879 PBXBuildPhase::Visit(visitor);
880 for (const auto& file : files_) {
881 file->Visit(visitor);
882 }
883 }
884
Print(std::ostream & out,unsigned indent) const885 void PBXSourcesBuildPhase::Print(std::ostream& out, unsigned indent) const {
886 const std::string indent_str(indent, '\t');
887 const IndentRules rules = {false, indent + 1};
888 out << indent_str << Reference() << " = {\n";
889 PrintProperty(out, rules, "isa", ToString(Class()));
890 PrintProperty(out, rules, "buildActionMask", 0x7fffffffu);
891 PrintProperty(out, rules, "files", files_);
892 PrintProperty(out, rules, "runOnlyForDeploymentPostprocessing", 0u);
893 out << indent_str << "};\n";
894 }
895
PBXTargetDependency(const PBXTarget * target,std::unique_ptr<PBXContainerItemProxy> container_item_proxy)896 PBXTargetDependency::PBXTargetDependency(
897 const PBXTarget* target,
898 std::unique_ptr<PBXContainerItemProxy> container_item_proxy)
899 : target_(target), container_item_proxy_(std::move(container_item_proxy)) {}
900
901 PBXTargetDependency::~PBXTargetDependency() = default;
902
Class() const903 PBXObjectClass PBXTargetDependency::Class() const {
904 return PBXTargetDependencyClass;
905 }
Name() const906 std::string PBXTargetDependency::Name() const {
907 return "PBXTargetDependency";
908 }
Visit(PBXObjectVisitor & visitor)909 void PBXTargetDependency::Visit(PBXObjectVisitor& visitor) {
910 PBXObject::Visit(visitor);
911 container_item_proxy_->Visit(visitor);
912 }
Print(std::ostream & out,unsigned indent) const913 void PBXTargetDependency::Print(std::ostream& out, unsigned indent) const {
914 const std::string indent_str(indent, '\t');
915 const IndentRules rules = {false, indent + 1};
916 out << indent_str << Reference() << " = {\n";
917 PrintProperty(out, rules, "isa", ToString(Class()));
918 PrintProperty(out, rules, "target", target_);
919 PrintProperty(out, rules, "targetProxy", container_item_proxy_);
920 out << indent_str << "};\n";
921 }
922
923 // XCBuildConfiguration -------------------------------------------------------
924
XCBuildConfiguration(const std::string & name,const PBXAttributes & attributes)925 XCBuildConfiguration::XCBuildConfiguration(const std::string& name,
926 const PBXAttributes& attributes)
927 : attributes_(attributes), name_(name) {}
928
929 XCBuildConfiguration::~XCBuildConfiguration() = default;
930
Class() const931 PBXObjectClass XCBuildConfiguration::Class() const {
932 return XCBuildConfigurationClass;
933 }
934
Name() const935 std::string XCBuildConfiguration::Name() const {
936 return name_;
937 }
938
Print(std::ostream & out,unsigned indent) const939 void XCBuildConfiguration::Print(std::ostream& out, unsigned indent) const {
940 const std::string indent_str(indent, '\t');
941 const IndentRules rules = {false, indent + 1};
942 out << indent_str << Reference() << " = {\n";
943 PrintProperty(out, rules, "isa", ToString(Class()));
944 PrintProperty(out, rules, "buildSettings", attributes_);
945 PrintProperty(out, rules, "name", name_);
946 out << indent_str << "};\n";
947 }
948
949 // XCConfigurationList --------------------------------------------------------
950
XCConfigurationList(const std::string & name,const PBXAttributes & attributes,const PBXObject * owner_reference)951 XCConfigurationList::XCConfigurationList(const std::string& name,
952 const PBXAttributes& attributes,
953 const PBXObject* owner_reference)
954 : owner_reference_(owner_reference) {
955 DCHECK(owner_reference_);
956 configurations_.push_back(
957 std::make_unique<XCBuildConfiguration>(name, attributes));
958 }
959
960 XCConfigurationList::~XCConfigurationList() = default;
961
Class() const962 PBXObjectClass XCConfigurationList::Class() const {
963 return XCConfigurationListClass;
964 }
965
Name() const966 std::string XCConfigurationList::Name() const {
967 std::stringstream buffer;
968 buffer << "Build configuration list for "
969 << ToString(owner_reference_->Class()) << " \""
970 << owner_reference_->Name() << "\"";
971 return buffer.str();
972 }
973
Visit(PBXObjectVisitor & visitor)974 void XCConfigurationList::Visit(PBXObjectVisitor& visitor) {
975 PBXObject::Visit(visitor);
976 for (const auto& configuration : configurations_) {
977 configuration->Visit(visitor);
978 }
979 }
980
Print(std::ostream & out,unsigned indent) const981 void XCConfigurationList::Print(std::ostream& out, unsigned indent) const {
982 const std::string indent_str(indent, '\t');
983 const IndentRules rules = {false, indent + 1};
984 out << indent_str << Reference() << " = {\n";
985 PrintProperty(out, rules, "isa", ToString(Class()));
986 PrintProperty(out, rules, "buildConfigurations", configurations_);
987 PrintProperty(out, rules, "defaultConfigurationIsVisible", 1u);
988 PrintProperty(out, rules, "defaultConfigurationName",
989 configurations_[0]->Name());
990 out << indent_str << "};\n";
991 }
992