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