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