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