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