1 # Copyright (c) 2012 Google Inc. 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.
5 """Xcode project file generator.
7 This module is both an Xcode project file generator and a documentation of the
8 Xcode project file format. Knowledge of the project file format was gained
9 based on extensive experience with Xcode, and by making changes to projects in
10 Xcode.app and observing the resultant changes in the associated project files.
14 The generator targets the file format as written by Xcode 3.2 (specifically,
15 3.2.6), but past experience has taught that the format has not changed
16 significantly in the past several years, and future versions of Xcode are able
17 to read older project files.
19 Xcode project files are "bundled": the project "file" from an end-user's
20 perspective is actually a directory with an ".xcodeproj" extension. The
21 project file from this module's perspective is actually a file inside this
22 directory, always named "project.pbxproj". This file contains a complete
23 description of the project and is all that is needed to use the xcodeproj.
24 Other files contained in the xcodeproj directory are simply used to store
25 per-user settings, such as the state of various UI elements in the Xcode
28 The project.pbxproj file is a property list, stored in a format almost
29 identical to the NeXTstep property list format. The file is able to carry
30 Unicode data, and is encoded in UTF-8. The root element in the property list
31 is a dictionary that contains several properties of minimal interest, and two
32 properties of immense interest. The most important property is a dictionary
33 named "objects". The entire structure of the project is represented by the
34 children of this property. The objects dictionary is keyed by unique 96-bit
35 values represented by 24 uppercase hexadecimal characters. Each value in the
36 objects dictionary is itself a dictionary, describing an individual object.
38 Each object in the dictionary is a member of a class, which is identified by
39 the "isa" property of each object. A variety of classes are represented in a
40 project file. Objects can refer to other objects by ID, using the 24-character
41 hexadecimal object key. A project's objects form a tree, with a root object
42 of class PBXProject at the root. As an example, the PBXProject object serves
43 as parent to an XCConfigurationList object defining the build configurations
44 used in the project, a PBXGroup object serving as a container for all files
45 referenced in the project, and a list of target objects, each of which defines
46 a target in the project. There are several different types of target object,
47 such as PBXNativeTarget and PBXAggregateTarget. In this module, this
48 relationship is expressed by having each target type derive from an abstract
51 The project.pbxproj file's root dictionary also contains a property, sibling to
52 the "objects" dictionary, named "rootObject". The value of rootObject is a
53 24-character object key referring to the root PBXProject object in the
56 In Xcode, every file used as input to a target or produced as a final product
57 of a target must appear somewhere in the hierarchy rooted at the PBXGroup
58 object referenced by the PBXProject's mainGroup property. A PBXGroup is
59 generally represented as a folder in the Xcode application. PBXGroups can
60 contain other PBXGroups as well as PBXFileReferences, which are pointers to
63 Each XCTarget contains a list of build phases, represented in this module by
64 the abstract base XCBuildPhase. Examples of concrete XCBuildPhase derivations
65 are PBXSourcesBuildPhase and PBXFrameworksBuildPhase, which correspond to the
66 "Compile Sources" and "Link Binary With Libraries" phases displayed in the
67 Xcode application. Files used as input to these phases (for example, source
68 files in the former case and libraries and frameworks in the latter) are
69 represented by PBXBuildFile objects, referenced by elements of "files" lists
70 in XCTarget objects. Each PBXBuildFile object refers to a PBXBuildFile
71 object as a "weak" reference: it does not "own" the PBXBuildFile, which is
72 owned by the root object's mainGroup or a descendant group. In most cases, the
73 layer of indirection between an XCBuildPhase and a PBXFileReference via a
74 PBXBuildFile appears extraneous, but there's actually one reason for this:
75 file-specific compiler flags are added to the PBXBuildFile object so as to
76 allow a single file to be a member of multiple targets while having distinct
77 compiler flags for each. These flags can be modified in the Xcode applciation
78 in the "Build" tab of a File Info window.
80 When a project is open in the Xcode application, Xcode will rewrite it. As
81 such, this module is careful to adhere to the formatting used by Xcode, to
82 avoid insignificant changes appearing in the file when it is used in the
83 Xcode application. This will keep version control repositories happy, and
84 makes it possible to compare a project file used in Xcode to one generated by
85 this module to determine if any significant changes were made in the
88 Xcode has its own way of assigning 24-character identifiers to each object,
89 which is not duplicated here. Because the identifier only is only generated
90 once, when an object is created, and is then left unchanged, there is no need
91 to attempt to duplicate Xcode's behavior in this area. The generator is free
92 to select any identifier, even at random, to refer to the objects it creates,
93 and Xcode will retain those identifiers and use them when subsequently
94 rewriting the project file. However, the generator would choose new random
95 identifiers each time the project files are generated, leading to difficulties
96 comparing "used" project files to "pristine" ones produced by this module,
97 and causing the appearance of changes as every object identifier is changed
98 when updated projects are checked in to a version control repository. To
99 mitigate this problem, this module chooses identifiers in a more deterministic
100 way, by hashing a description of each object as well as its parent and ancestor
101 objects. This strategy should result in minimal "shift" in IDs as successive
102 generations of project files are produced.
106 This module introduces several classes, all derived from the XCObject class.
107 Nearly all of the "brains" are built into the XCObject class, which understands
108 how to create and modify objects, maintain the proper tree structure, compute
109 identifiers, and print objects. For the most part, classes derived from
110 XCObject need only provide a _schema class object, a dictionary that
111 expresses what properties objects of the class may contain.
113 Given this structure, it's possible to build a minimal project file by creating
114 objects of the appropriate types and making the proper connections:
116 config_list = XCConfigurationList()
118 project = PBXProject({'buildConfigurationList': config_list,
121 With the project object set up, it can be added to an XCProjectFile object.
122 XCProjectFile is a pseudo-class in the sense that it is a concrete XCObject
123 subclass that does not actually correspond to a class type found in a project
124 file. Rather, it is used to represent the project file's root dictionary.
125 Printing an XCProjectFile will print the entire project file, including the
126 full "objects" dictionary.
128 project_file = XCProjectFile({'rootObject': project})
129 project_file.ComputeIDs()
132 Xcode project files are always encoded in UTF-8. This module will accept
133 strings of either the str class or the unicode class. Strings of class str
134 are assumed to already be encoded in UTF-8. Obviously, if you're just using
135 ASCII, you won't encounter difficulties because ASCII is a UTF-8 subset.
136 Strings of class unicode are handled properly and encoded in UTF-8 when
137 a project file is output.
146 # hashlib is supplied as of Python 2.5 as the replacement interface for sha
147 # and other secure hashes. In 2.6, sha is deprecated. Import hashlib if
148 # available, avoiding a deprecation warning under 2.6. Import sha otherwise,
149 # preserving 2.4 compatibility.
152 _new_sha1 = hashlib.sha1
158 # See XCObject._EncodeString. This pattern is used to determine when a string
159 # can be printed unquoted. Strings that match this pattern may be printed
160 # unquoted. Strings that do not match must be quoted and may be further
161 # transformed to be properly encoded. Note that this expression matches the
162 # characters listed with "+", for 1 or more occurrences: if a string is empty,
163 # it must not match this pattern, because it needs to be encoded as "".
164 _unquoted = re.compile('^[A-Za-z0-9$./_]+$')
166 # Strings that match this pattern are quoted regardless of what _unquoted says.
167 # Oddly, Xcode will quote any string with a run of three or more underscores.
168 _quoted = re.compile('___')
170 # This pattern should match any character that needs to be escaped by
171 # XCObject._EncodeString. See that function.
172 _escaped = re.compile('[\\\\"]|[\x00-\x1f]')
175 # Used by SourceTreeAndPathFromPath
176 _path_leading_variable = re.compile(r'^\$\((.*?)\)(/(.*))?$')
178 def SourceTreeAndPathFromPath(input_path):
179 """Given input_path, returns a tuple with sourceTree and path values.
182 input_path (source_tree, output_path)
183 '$(VAR)/path' ('VAR', 'path')
184 '$(VAR)' ('VAR', None)
185 'path' (None, 'path')
188 source_group_match = _path_leading_variable.match(input_path)
189 if source_group_match:
190 source_tree = source_group_match.group(1)
191 output_path = source_group_match.group(3) # This may be None.
194 output_path = input_path
196 return (source_tree, output_path)
198 def ConvertVariablesToShellSyntax(input_string):
199 return re.sub(r'\$\((.*?)\)', '${\\1}', input_string)
201 class XCObject(object):
202 """The abstract base of all class types used in Xcode project files.
205 _schema: A dictionary defining the properties of this class. The keys to
206 _schema are string property keys as used in project files. Values
207 are a list of four or five elements:
208 [ is_list, property_type, is_strong, is_required, default ]
209 is_list: True if the property described is a list, as opposed
211 property_type: The type to use as the value of the property,
212 or if is_list is True, the type to use for each
213 element of the value's list. property_type must
214 be an XCObject subclass, or one of the built-in
215 types str, int, or dict.
216 is_strong: If property_type is an XCObject subclass, is_strong
217 is True to assert that this class "owns," or serves
218 as parent, to the property value (or, if is_list is
219 True, values). is_strong must be False if
220 property_type is not an XCObject subclass.
221 is_required: True if the property is required for the class.
222 Note that is_required being True does not preclude
223 an empty string ("", in the case of property_type
224 str) or list ([], in the case of is_list True) from
225 being set for the property.
226 default: Optional. If is_requried is True, default may be set
227 to provide a default value for objects that do not supply
228 their own value. If is_required is True and default
229 is not provided, users of the class must supply their own
230 value for the property.
231 Note that although the values of the array are expressed in
232 boolean terms, subclasses provide values as integers to conserve
234 _should_print_single_line: False in XCObject. Subclasses whose objects
235 should be written to the project file in the
236 alternate single-line format, such as
237 PBXFileReference and PBXBuildFile, should
239 _encode_transforms: Used by _EncodeString to encode unprintable characters.
240 The index into this list is the ordinal of the
241 character to transform; each value is a string
242 used to represent the character in the output. XCObject
243 provides an _encode_transforms list suitable for most
245 _alternate_encode_transforms: Provided for subclasses that wish to use
246 the alternate encoding rules. Xcode seems
247 to use these rules when printing objects in
248 single-line format. Subclasses that desire
249 this behavior should set _encode_transforms
250 to _alternate_encode_transforms.
251 _hashables: A list of XCObject subclasses that can be hashed by ComputeIDs
252 to construct this object's ID. Most classes that need custom
253 hashing behavior should do it by overriding Hashables,
254 but in some cases an object's parent may wish to push a
255 hashable value into its child, and it can do so by appending
258 id: The object's identifier, a 24-character uppercase hexadecimal string.
259 Usually, objects being created should not set id until the entire
260 project file structure is built. At that point, UpdateIDs() should
261 be called on the root object to assign deterministic values for id to
262 each object in the tree.
263 parent: The object's parent. This is set by a parent XCObject when a child
264 object is added to it.
265 _properties: The object's property dictionary. An object's properties are
266 described by its class' _schema variable.
270 _should_print_single_line = False
273 _encode_transforms = []
276 _encode_transforms.append('\\U%04x' % i)
278 _encode_transforms[7] = '\\a'
279 _encode_transforms[8] = '\\b'
280 _encode_transforms[9] = '\\t'
281 _encode_transforms[10] = '\\n'
282 _encode_transforms[11] = '\\v'
283 _encode_transforms[12] = '\\f'
284 _encode_transforms[13] = '\\n'
286 _alternate_encode_transforms = list(_encode_transforms)
287 _alternate_encode_transforms[9] = chr(9)
288 _alternate_encode_transforms[10] = chr(10)
289 _alternate_encode_transforms[11] = chr(11)
291 def __init__(self, properties=None, id=None, parent=None):
294 self._properties = {}
296 self._SetDefaultsFromSchema()
297 self.UpdateProperties(properties)
302 except NotImplementedError:
303 return '<%s at 0x%x>' % (self.__class__.__name__, id(self))
304 return '<%s %r at 0x%x>' % (self.__class__.__name__, name, id(self))
307 """Make a copy of this object.
309 The new object will have its own copy of lists and dicts. Any XCObject
310 objects owned by this object (marked "strong") will be copied in the
311 new object, even those found in lists. If this object has any weak
312 references to other XCObjects, the same references are added to the new
313 object without making a copy.
316 that = self.__class__(id=self.id, parent=self.parent)
317 for key, value in self._properties.iteritems():
318 is_strong = self._schema[key][2]
320 if isinstance(value, XCObject):
322 new_value = value.Copy()
323 new_value.parent = that
324 that._properties[key] = new_value
326 that._properties[key] = value
327 elif isinstance(value, str) or isinstance(value, unicode) or \
328 isinstance(value, int):
329 that._properties[key] = value
330 elif isinstance(value, list):
332 # If is_strong is True, each element is an XCObject, so it's safe to
334 that._properties[key] = []
336 new_item = item.Copy()
337 new_item.parent = that
338 that._properties[key].append(new_item)
340 that._properties[key] = value[:]
341 elif isinstance(value, dict):
342 # dicts are never strong.
344 raise TypeError('Strong dict for key ' + key + ' in ' + \
345 self.__class__.__name__)
347 that._properties[key] = value.copy()
349 raise TypeError('Unexpected type ' + value.__class__.__name__ + \
350 ' for key ' + key + ' in ' + self.__class__.__name__)
355 """Return the name corresponding to an object.
357 Not all objects necessarily need to be nameable, and not all that do have
358 a "name" property. Override as needed.
361 # If the schema indicates that "name" is required, try to access the
362 # property even if it doesn't exist. This will result in a KeyError
363 # being raised for the property that should be present, which seems more
364 # appropriate than NotImplementedError in this case.
365 if 'name' in self._properties or \
366 ('name' in self._schema and self._schema['name'][3]):
367 return self._properties['name']
369 raise NotImplementedError(self.__class__.__name__ + ' must implement Name')
372 """Return a comment string for the object.
374 Most objects just use their name as the comment, but PBXProject uses
377 The returned comment is not escaped and does not have any comment marker
378 strings applied to it.
384 hashables = [self.__class__.__name__]
388 hashables.append(name)
390 hashables.extend(self._hashables)
394 def HashablesForChild(self):
397 def ComputeIDs(self, recursive=True, overwrite=True, seed_hash=None):
398 """Set "id" properties deterministically.
400 An object's "id" property is set based on a hash of its class type and
401 name, as well as the class type and name of all ancestor objects. As
402 such, it is only advisable to call ComputeIDs once an entire project file
405 If recursive is True, recurse into all descendant objects and update their
408 If overwrite is True, any existing value set in the "id" property will be
412 def _HashUpdate(hash, data):
413 """Update hash with data's length and contents.
415 If the hash were updated only with the value of data, it would be
416 possible for clowns to induce collisions by manipulating the names of
417 their objects. By adding the length, it's exceedingly less likely that
418 ID collisions will be encountered, intentionally or not.
421 hash.update(struct.pack('>i', len(data)))
424 if seed_hash is None:
425 seed_hash = _new_sha1()
427 hash = seed_hash.copy()
429 hashables = self.Hashables()
430 assert len(hashables) > 0
431 for hashable in hashables:
432 _HashUpdate(hash, hashable)
435 hashables_for_child = self.HashablesForChild()
436 if hashables_for_child is None:
439 assert len(hashables_for_child) > 0
440 child_hash = seed_hash.copy()
441 for hashable in hashables_for_child:
442 _HashUpdate(child_hash, hashable)
444 for child in self.Children():
445 child.ComputeIDs(recursive, overwrite, child_hash)
447 if overwrite or self.id is None:
448 # Xcode IDs are only 96 bits (24 hex characters), but a SHA-1 digest is
449 # is 160 bits. Instead of throwing out 64 bits of the digest, xor them
450 # into the portion that gets used.
451 assert hash.digest_size % 4 == 0
452 digest_int_count = hash.digest_size / 4
453 digest_ints = struct.unpack('>' + 'I' * digest_int_count, hash.digest())
455 for index in xrange(0, digest_int_count):
456 id_ints[index % 3] ^= digest_ints[index]
457 self.id = '%08X%08X%08X' % tuple(id_ints)
459 def EnsureNoIDCollisions(self):
460 """Verifies that no two objects have the same ID. Checks all descendants.
464 descendants = self.Descendants()
465 for descendant in descendants:
466 if descendant.id in ids:
467 other = ids[descendant.id]
469 'Duplicate ID %s, objects "%s" and "%s" in "%s"' % \
470 (descendant.id, str(descendant._properties),
471 str(other._properties), self._properties['rootObject'].Name()))
472 ids[descendant.id] = descendant
475 """Returns a list of all of this object's owned (strong) children."""
478 for property, attributes in self._schema.iteritems():
479 (is_list, property_type, is_strong) = attributes[0:3]
480 if is_strong and property in self._properties:
482 children.append(self._properties[property])
484 children.extend(self._properties[property])
487 def Descendants(self):
488 """Returns a list of all of this object's descendants, including this
492 children = self.Children()
494 for child in children:
495 descendants.extend(child.Descendants())
498 def PBXProjectAncestor(self):
499 # The base case for recursion is defined at PBXProject.PBXProjectAncestor.
501 return self.parent.PBXProjectAncestor()
504 def _EncodeComment(self, comment):
505 """Encodes a comment to be placed in the project file output, mimicing
509 # This mimics Xcode behavior by wrapping the comment in "/*" and "*/". If
510 # the string already contains a "*/", it is turned into "(*)/". This keeps
511 # the file writer from outputting something that would be treated as the
512 # end of a comment in the middle of something intended to be entirely a
515 return '/* ' + comment.replace('*/', '(*)/') + ' */'
517 def _EncodeTransform(self, match):
518 # This function works closely with _EncodeString. It will only be called
519 # by re.sub with match.group(0) containing a character matched by the
520 # the _escaped expression.
521 char = match.group(0)
523 # Backslashes (\) and quotation marks (") are always replaced with a
524 # backslash-escaped version of the same. Everything else gets its
525 # replacement from the class' _encode_transforms array.
530 return self._encode_transforms[ord(char)]
532 def _EncodeString(self, value):
533 """Encodes a string to be placed in the project file output, mimicing
537 # Use quotation marks when any character outside of the range A-Z, a-z, 0-9,
538 # $ (dollar sign), . (period), and _ (underscore) is present. Also use
539 # quotation marks to represent empty strings.
541 # Escape " (double-quote) and \ (backslash) by preceding them with a
544 # Some characters below the printable ASCII range are encoded specially:
545 # 7 ^G BEL is encoded as "\a"
546 # 8 ^H BS is encoded as "\b"
547 # 11 ^K VT is encoded as "\v"
548 # 12 ^L NP is encoded as "\f"
549 # 127 ^? DEL is passed through as-is without escaping
550 # - In PBXFileReference and PBXBuildFile objects:
551 # 9 ^I HT is passed through as-is without escaping
552 # 10 ^J NL is passed through as-is without escaping
553 # 13 ^M CR is passed through as-is without escaping
554 # - In other objects:
555 # 9 ^I HT is encoded as "\t"
556 # 10 ^J NL is encoded as "\n"
557 # 13 ^M CR is encoded as "\n" rendering it indistinguishable from
559 # All other characters within the ASCII control character range (0 through
560 # 31 inclusive) are encoded as "\U001f" referring to the Unicode code point
561 # in hexadecimal. For example, character 14 (^N SO) is encoded as "\U000e".
562 # Characters above the ASCII range are passed through to the output encoded
563 # as UTF-8 without any escaping. These mappings are contained in the
564 # class' _encode_transforms list.
566 if _unquoted.search(value) and not _quoted.search(value):
569 return '"' + _escaped.sub(self._EncodeTransform, value) + '"'
571 def _XCPrint(self, file, tabs, line):
572 file.write('\t' * tabs + line)
574 def _XCPrintableValue(self, tabs, value, flatten_list=False):
575 """Returns a representation of value that may be printed in a project file,
576 mimicing Xcode's behavior.
578 _XCPrintableValue can handle str and int values, XCObjects (which are
579 made printable by returning their id property), and list and dict objects
580 composed of any of the above types. When printing a list or dict, and
581 _should_print_single_line is False, the tabs parameter is used to determine
582 how much to indent the lines corresponding to the items in the list or
585 If flatten_list is True, single-element lists will be transformed into
592 if self._should_print_single_line:
598 element_tabs = '\t' * (tabs + 1)
599 end_tabs = '\t' * tabs
601 if isinstance(value, XCObject):
602 printable += value.id
603 comment = value.Comment()
604 elif isinstance(value, str):
605 printable += self._EncodeString(value)
606 elif isinstance(value, unicode):
607 printable += self._EncodeString(value.encode('utf-8'))
608 elif isinstance(value, int):
609 printable += str(value)
610 elif isinstance(value, list):
611 if flatten_list and len(value) <= 1:
613 printable += self._EncodeString('')
615 printable += self._EncodeString(value[0])
617 printable = '(' + sep
619 printable += element_tabs + \
620 self._XCPrintableValue(tabs + 1, item, flatten_list) + \
622 printable += end_tabs + ')'
623 elif isinstance(value, dict):
624 printable = '{' + sep
625 for item_key, item_value in sorted(value.iteritems()):
626 printable += element_tabs + \
627 self._XCPrintableValue(tabs + 1, item_key, flatten_list) + ' = ' + \
628 self._XCPrintableValue(tabs + 1, item_value, flatten_list) + ';' + \
630 printable += end_tabs + '}'
632 raise TypeError("Can't make " + value.__class__.__name__ + ' printable')
635 printable += ' ' + self._EncodeComment(comment)
639 def _XCKVPrint(self, file, tabs, key, value):
640 """Prints a key and value, members of an XCObject's _properties dictionary,
643 tabs is an int identifying the indentation level. If the class'
644 _should_print_single_line variable is True, tabs is ignored and the
645 key-value pair will be followed by a space insead of a newline.
648 if self._should_print_single_line:
652 printable = '\t' * tabs
655 # Xcode usually prints remoteGlobalIDString values in PBXContainerItemProxy
656 # objects without comments. Sometimes it prints them with comments, but
657 # the majority of the time, it doesn't. To avoid unnecessary changes to
658 # the project file after Xcode opens it, don't write comments for
659 # remoteGlobalIDString. This is a sucky hack and it would certainly be
660 # cleaner to extend the schema to indicate whether or not a comment should
661 # be printed, but since this is the only case where the problem occurs and
662 # Xcode itself can't seem to make up its mind, the hack will suffice.
664 # Also see PBXContainerItemProxy._schema['remoteGlobalIDString'].
665 if key == 'remoteGlobalIDString' and isinstance(self,
666 PBXContainerItemProxy):
667 value_to_print = value.id
669 value_to_print = value
671 # PBXBuildFile's settings property is represented in the output as a dict,
672 # but a hack here has it represented as a string. Arrange to strip off the
673 # quotes so that it shows up in the output as expected.
674 if key == 'settings' and isinstance(self, PBXBuildFile):
675 strip_value_quotes = True
677 strip_value_quotes = False
679 # In another one-off, let's set flatten_list on buildSettings properties
680 # of XCBuildConfiguration objects, because that's how Xcode treats them.
681 if key == 'buildSettings' and isinstance(self, XCBuildConfiguration):
687 printable_key = self._XCPrintableValue(tabs, key, flatten_list)
688 printable_value = self._XCPrintableValue(tabs, value_to_print,
690 if strip_value_quotes and len(printable_value) > 1 and \
691 printable_value[0] == '"' and printable_value[-1] == '"':
692 printable_value = printable_value[1:-1]
693 printable += printable_key + ' = ' + printable_value + ';' + after_kv
695 gyp.common.ExceptionAppend(e,
696 'while printing key "%s"' % key)
699 self._XCPrint(file, 0, printable)
701 def Print(self, file=sys.stdout):
702 """Prints a reprentation of this object to file, adhering to Xcode output
706 self.VerifyHasRequiredProperties()
708 if self._should_print_single_line:
709 # When printing an object in a single line, Xcode doesn't put any space
710 # between the beginning of a dictionary (or presumably a list) and the
711 # first contained item, so you wind up with snippets like
712 # ...CDEF = {isa = PBXFileReference; fileRef = 0123...
713 # If it were me, I would have put a space in there after the opening
714 # curly, but I guess this is just another one of those inconsistencies
715 # between how Xcode prints PBXFileReference and PBXBuildFile objects as
716 # compared to other objects. Mimic Xcode's behavior here by using an
717 # empty string for sep.
724 # Start the object. For example, '\t\tPBXProject = {\n'.
725 self._XCPrint(file, 2, self._XCPrintableValue(2, self) + ' = {' + sep)
727 # "isa" isn't in the _properties dictionary, it's an intrinsic property
728 # of the class which the object belongs to. Xcode always outputs "isa"
729 # as the first element of an object dictionary.
730 self._XCKVPrint(file, 3, 'isa', self.__class__.__name__)
732 # The remaining elements of an object dictionary are sorted alphabetically.
733 for property, value in sorted(self._properties.iteritems()):
734 self._XCKVPrint(file, 3, property, value)
737 self._XCPrint(file, end_tabs, '};\n')
739 def UpdateProperties(self, properties, do_copy=False):
740 """Merge the supplied properties into the _properties dictionary.
742 The input properties must adhere to the class schema or a KeyError or
743 TypeError exception will be raised. If adding an object of an XCObject
744 subclass and the schema indicates a strong relationship, the object's
745 parent will be set to this object.
747 If do_copy is True, then lists, dicts, strong-owned XCObjects, and
748 strong-owned XCObjects in lists will be copied instead of having their
752 if properties is None:
755 for property, value in properties.iteritems():
756 # Make sure the property is in the schema.
757 if not property in self._schema:
758 raise KeyError(property + ' not in ' + self.__class__.__name__)
760 # Make sure the property conforms to the schema.
761 (is_list, property_type, is_strong) = self._schema[property][0:3]
763 if value.__class__ != list:
765 property + ' of ' + self.__class__.__name__ + \
766 ' must be list, not ' + value.__class__.__name__)
768 if not isinstance(item, property_type) and \
769 not (item.__class__ == unicode and property_type == str):
770 # Accept unicode where str is specified. str is treated as
773 'item of ' + property + ' of ' + self.__class__.__name__ + \
774 ' must be ' + property_type.__name__ + ', not ' + \
775 item.__class__.__name__)
776 elif not isinstance(value, property_type) and \
777 not (value.__class__ == unicode and property_type == str):
778 # Accept unicode where str is specified. str is treated as
781 property + ' of ' + self.__class__.__name__ + ' must be ' + \
782 property_type.__name__ + ', not ' + value.__class__.__name__)
784 # Checks passed, perform the assignment.
786 if isinstance(value, XCObject):
788 self._properties[property] = value.Copy()
790 self._properties[property] = value
791 elif isinstance(value, str) or isinstance(value, unicode) or \
792 isinstance(value, int):
793 self._properties[property] = value
794 elif isinstance(value, list):
796 # If is_strong is True, each element is an XCObject, so it's safe
798 self._properties[property] = []
800 self._properties[property].append(item.Copy())
802 self._properties[property] = value[:]
803 elif isinstance(value, dict):
804 self._properties[property] = value.copy()
806 raise TypeError("Don't know how to copy a " + \
807 value.__class__.__name__ + ' object for ' + \
808 property + ' in ' + self.__class__.__name__)
810 self._properties[property] = value
812 # Set up the child's back-reference to this object. Don't use |value|
813 # any more because it may not be right if do_copy is true.
816 self._properties[property].parent = self
818 for item in self._properties[property]:
821 def HasProperty(self, key):
822 return key in self._properties
824 def GetProperty(self, key):
825 return self._properties[key]
827 def SetProperty(self, key, value):
828 self.UpdateProperties({key: value})
830 def DelProperty(self, key):
831 if key in self._properties:
832 del self._properties[key]
834 def AppendProperty(self, key, value):
835 # TODO(mark): Support ExtendProperty too (and make this call that)?
838 if not key in self._schema:
839 raise KeyError(key + ' not in ' + self.__class__.__name__)
841 (is_list, property_type, is_strong) = self._schema[key][0:3]
843 raise TypeError(key + ' of ' + self.__class__.__name__ + ' must be list')
844 if not isinstance(value, property_type):
845 raise TypeError('item of ' + key + ' of ' + self.__class__.__name__ + \
846 ' must be ' + property_type.__name__ + ', not ' + \
847 value.__class__.__name__)
849 # If the property doesn't exist yet, create a new empty list to receive the
851 if not key in self._properties:
852 self._properties[key] = []
854 # Set up the ownership link.
859 self._properties[key].append(value)
861 def VerifyHasRequiredProperties(self):
862 """Ensure that all properties identified as required by the schema are
866 # TODO(mark): A stronger verification mechanism is needed. Some
867 # subclasses need to perform validation beyond what the schema can enforce.
868 for property, attributes in self._schema.iteritems():
869 (is_list, property_type, is_strong, is_required) = attributes[0:4]
870 if is_required and not property in self._properties:
871 raise KeyError(self.__class__.__name__ + ' requires ' + property)
873 def _SetDefaultsFromSchema(self):
874 """Assign object default values according to the schema. This will not
875 overwrite properties that have already been set."""
878 for property, attributes in self._schema.iteritems():
879 (is_list, property_type, is_strong, is_required) = attributes[0:4]
880 if is_required and len(attributes) >= 5 and \
881 not property in self._properties:
882 default = attributes[4]
884 defaults[property] = default
886 if len(defaults) > 0:
887 # Use do_copy=True so that each new object gets its own copy of strong
888 # objects, lists, and dicts.
889 self.UpdateProperties(defaults, do_copy=True)
892 class XCHierarchicalElement(XCObject):
893 """Abstract base for PBXGroup and PBXFileReference. Not represented in a
896 # TODO(mark): Do name and path belong here? Probably so.
897 # If path is set and name is not, name may have a default value. Name will
898 # be set to the basename of path, if the basename of path is different from
899 # the full value of path. If path is already just a leaf name, name will
901 _schema = XCObject._schema.copy()
903 'comments': [0, str, 0, 0],
904 'fileEncoding': [0, str, 0, 0],
905 'includeInIndex': [0, int, 0, 0],
906 'indentWidth': [0, int, 0, 0],
907 'lineEnding': [0, int, 0, 0],
908 'sourceTree': [0, str, 0, 1, '<group>'],
909 'tabWidth': [0, int, 0, 0],
910 'usesTabs': [0, int, 0, 0],
911 'wrapsLines': [0, int, 0, 0],
914 def __init__(self, properties=None, id=None, parent=None):
916 XCObject.__init__(self, properties, id, parent)
917 if 'path' in self._properties and not 'name' in self._properties:
918 path = self._properties['path']
919 name = posixpath.basename(path)
920 if name != '' and path != name:
921 self.SetProperty('name', name)
923 if 'path' in self._properties and \
924 (not 'sourceTree' in self._properties or \
925 self._properties['sourceTree'] == '<group>'):
926 # If the pathname begins with an Xcode variable like "$(SDKROOT)/", take
927 # the variable out and make the path be relative to that variable by
928 # assigning the variable name as the sourceTree.
929 (source_tree, path) = SourceTreeAndPathFromPath(self._properties['path'])
930 if source_tree != None:
931 self._properties['sourceTree'] = source_tree
933 self._properties['path'] = path
934 if source_tree != None and path is None and \
935 not 'name' in self._properties:
936 # The path was of the form "$(SDKROOT)" with no path following it.
937 # This object is now relative to that variable, so it has no path
938 # attribute of its own. It does, however, keep a name.
939 del self._properties['path']
940 self._properties['name'] = source_tree
943 if 'name' in self._properties:
944 return self._properties['name']
945 elif 'path' in self._properties:
946 return self._properties['path']
948 # This happens in the case of the root PBXGroup.
952 """Custom hashables for XCHierarchicalElements.
954 XCHierarchicalElements are special. Generally, their hashes shouldn't
955 change if the paths don't change. The normal XCObject implementation of
956 Hashables adds a hashable for each object, which means that if
957 the hierarchical structure changes (possibly due to changes caused when
958 TakeOverOnlyChild runs and encounters slight changes in the hierarchy),
959 the hashes will change. For example, if a project file initially contains
960 a/b/f1 and a/b becomes collapsed into a/b, f1 will have a single parent
961 a/b. If someone later adds a/f2 to the project file, a/b can no longer be
962 collapsed, and f1 winds up with parent b and grandparent a. That would
963 be sufficient to change f1's hash.
965 To counteract this problem, hashables for all XCHierarchicalElements except
966 for the main group (which has neither a name nor a path) are taken to be
967 just the set of path components. Because hashables are inherited from
968 parents, this provides assurance that a/b/f1 has the same set of hashables
969 whether its parent is b or a/b.
971 The main group is a special case. As it is permitted to have no name or
972 path, it is permitted to use the standard XCObject hash mechanism. This
973 is not considered a problem because there can be only one main group.
976 if self == self.PBXProjectAncestor()._properties['mainGroup']:
978 return XCObject.Hashables(self)
982 # Put the name in first, ensuring that if TakeOverOnlyChild collapses
983 # children into a top-level group like "Source", the name always goes
984 # into the list of hashables without interfering with path components.
985 if 'name' in self._properties:
986 # Make it less likely for people to manipulate hashes by following the
987 # pattern of always pushing an object type value onto the list first.
988 hashables.append(self.__class__.__name__ + '.name')
989 hashables.append(self._properties['name'])
991 # NOTE: This still has the problem that if an absolute path is encountered,
992 # including paths with a sourceTree, they'll still inherit their parents'
993 # hashables, even though the paths aren't relative to their parents. This
994 # is not expected to be much of a problem in practice.
995 path = self.PathFromSourceTreeAndPath()
997 components = path.split(posixpath.sep)
998 for component in components:
999 hashables.append(self.__class__.__name__ + '.path')
1000 hashables.append(component)
1002 hashables.extend(self._hashables)
1006 def Compare(self, other):
1007 # Allow comparison of these types. PBXGroup has the highest sort rank;
1008 # PBXVariantGroup is treated as equal to PBXFileReference.
1009 valid_class_types = {
1010 PBXFileReference: 'file',
1012 PBXVariantGroup: 'file',
1014 self_type = valid_class_types[self.__class__]
1015 other_type = valid_class_types[other.__class__]
1017 if self_type == other_type:
1018 # If the two objects are of the same sort rank, compare their names.
1019 return cmp(self.Name(), other.Name())
1021 # Otherwise, sort groups before everything else.
1022 if self_type == 'group':
1026 def CompareRootGroup(self, other):
1027 # This function should be used only to compare direct children of the
1028 # containing PBXProject's mainGroup. These groups should appear in the
1030 # TODO(mark): "Build" is used by gyp.generator.xcode, perhaps the
1031 # generator should have a way of influencing this list rather than having
1032 # to hardcode for the generator here.
1033 order = ['Source', 'Intermediates', 'Projects', 'Frameworks', 'Products',
1036 # If the groups aren't in the listed order, do a name comparison.
1037 # Otherwise, groups in the listed order should come before those that
1039 self_name = self.Name()
1040 other_name = other.Name()
1041 self_in = isinstance(self, PBXGroup) and self_name in order
1042 other_in = isinstance(self, PBXGroup) and other_name in order
1043 if not self_in and not other_in:
1044 return self.Compare(other)
1045 if self_name in order and not other_name in order:
1047 if other_name in order and not self_name in order:
1050 # If both groups are in the listed order, go by the defined order.
1051 self_index = order.index(self_name)
1052 other_index = order.index(other_name)
1053 if self_index < other_index:
1055 if self_index > other_index:
1059 def PathFromSourceTreeAndPath(self):
1060 # Turn the object's sourceTree and path properties into a single flat
1061 # string of a form comparable to the path parameter. If there's a
1062 # sourceTree property other than "<group>", wrap it in $(...) for the
1065 if self._properties['sourceTree'] != '<group>':
1066 components.append('$(' + self._properties['sourceTree'] + ')')
1067 if 'path' in self._properties:
1068 components.append(self._properties['path'])
1070 if len(components) > 0:
1071 return posixpath.join(*components)
1076 # Returns a full path to self relative to the project file, or relative
1077 # to some other source tree. Start with self, and walk up the chain of
1078 # parents prepending their paths, if any, until no more parents are
1079 # available (project-relative path) or until a path relative to some
1080 # source tree is found.
1083 while isinstance(xche, XCHierarchicalElement) and \
1085 (not path.startswith('/') and not path.startswith('$'))):
1086 this_path = xche.PathFromSourceTreeAndPath()
1087 if this_path != None and path != None:
1088 path = posixpath.join(this_path, path)
1089 elif this_path != None:
1096 class PBXGroup(XCHierarchicalElement):
1099 _children_by_path: Maps pathnames of children of this PBXGroup to the
1100 actual child XCHierarchicalElement objects.
1101 _variant_children_by_name_and_path: Maps (name, path) tuples of
1102 PBXVariantGroup children to the actual child PBXVariantGroup objects.
1105 _schema = XCHierarchicalElement._schema.copy()
1107 'children': [1, XCHierarchicalElement, 1, 1, []],
1108 'name': [0, str, 0, 0],
1109 'path': [0, str, 0, 0],
1112 def __init__(self, properties=None, id=None, parent=None):
1114 XCHierarchicalElement.__init__(self, properties, id, parent)
1115 self._children_by_path = {}
1116 self._variant_children_by_name_and_path = {}
1117 for child in self._properties.get('children', []):
1118 self._AddChildToDicts(child)
1120 def Hashables(self):
1122 hashables = XCHierarchicalElement.Hashables(self)
1124 # It is not sufficient to just rely on name and parent to build a unique
1125 # hashable : a node could have two child PBXGroup sharing a common name.
1126 # To add entropy the hashable is enhanced with the names of all its
1128 for child in self._properties.get('children', []):
1129 child_name = child.Name()
1130 if child_name != None:
1131 hashables.append(child_name)
1135 def HashablesForChild(self):
1136 # To avoid a circular reference the hashables used to compute a child id do
1137 # not include the child names.
1138 return XCHierarchicalElement.Hashables(self)
1140 def _AddChildToDicts(self, child):
1141 # Sets up this PBXGroup object's dicts to reference the child properly.
1142 child_path = child.PathFromSourceTreeAndPath()
1144 if child_path in self._children_by_path:
1145 raise ValueError('Found multiple children with path ' + child_path)
1146 self._children_by_path[child_path] = child
1148 if isinstance(child, PBXVariantGroup):
1149 child_name = child._properties.get('name', None)
1150 key = (child_name, child_path)
1151 if key in self._variant_children_by_name_and_path:
1152 raise ValueError('Found multiple PBXVariantGroup children with ' + \
1153 'name ' + str(child_name) + ' and path ' + \
1155 self._variant_children_by_name_and_path[key] = child
1157 def AppendChild(self, child):
1158 # Callers should use this instead of calling
1159 # AppendProperty('children', child) directly because this function
1160 # maintains the group's dicts.
1161 self.AppendProperty('children', child)
1162 self._AddChildToDicts(child)
1164 def GetChildByName(self, name):
1165 # This is not currently optimized with a dict as GetChildByPath is because
1166 # it has few callers. Most callers probably want GetChildByPath. This
1167 # function is only useful to get children that have names but no paths,
1168 # which is rare. The children of the main group ("Source", "Products",
1169 # etc.) is pretty much the only case where this likely to come up.
1171 # TODO(mark): Maybe this should raise an error if more than one child is
1172 # present with the same name.
1173 if not 'children' in self._properties:
1176 for child in self._properties['children']:
1177 if child.Name() == name:
1182 def GetChildByPath(self, path):
1186 if path in self._children_by_path:
1187 return self._children_by_path[path]
1191 def GetChildByRemoteObject(self, remote_object):
1192 # This method is a little bit esoteric. Given a remote_object, which
1193 # should be a PBXFileReference in another project file, this method will
1194 # return this group's PBXReferenceProxy object serving as a local proxy
1195 # for the remote PBXFileReference.
1197 # This function might benefit from a dict optimization as GetChildByPath
1198 # for some workloads, but profiling shows that it's not currently a
1200 if not 'children' in self._properties:
1203 for child in self._properties['children']:
1204 if not isinstance(child, PBXReferenceProxy):
1207 container_proxy = child._properties['remoteRef']
1208 if container_proxy._properties['remoteGlobalIDString'] == remote_object:
1213 def AddOrGetFileByPath(self, path, hierarchical):
1214 """Returns an existing or new file reference corresponding to path.
1216 If hierarchical is True, this method will create or use the necessary
1217 hierarchical group structure corresponding to path. Otherwise, it will
1218 look in and create an item in the current group only.
1220 If an existing matching reference is found, it is returned, otherwise, a
1221 new one will be created, added to the correct group, and returned.
1223 If path identifies a directory by virtue of carrying a trailing slash,
1224 this method returns a PBXFileReference of "folder" type. If path
1225 identifies a variant, by virtue of it identifying a file inside a directory
1226 with an ".lproj" extension, this method returns a PBXVariantGroup
1227 containing the variant named by path, and possibly other variants. For
1228 all other paths, a "normal" PBXFileReference will be returned.
1231 # Adding or getting a directory? Directories end with a trailing slash.
1233 if path.endswith('/'):
1235 path = posixpath.normpath(path)
1239 # Adding or getting a variant? Variants are files inside directories
1240 # with an ".lproj" extension. Xcode uses variants for localization. For
1241 # a variant path/to/Language.lproj/MainMenu.nib, put a variant group named
1242 # MainMenu.nib inside path/to, and give it a variant named Language. In
1243 # this example, grandparent would be set to path/to and parent_root would
1244 # be set to Language.
1246 parent = posixpath.dirname(path)
1247 grandparent = posixpath.dirname(parent)
1248 parent_basename = posixpath.basename(parent)
1249 (parent_root, parent_ext) = posixpath.splitext(parent_basename)
1250 if parent_ext == '.lproj':
1251 variant_name = parent_root
1252 if grandparent == '':
1255 # Putting a directory inside a variant group is not currently supported.
1256 assert not is_dir or variant_name is None
1258 path_split = path.split(posixpath.sep)
1259 if len(path_split) == 1 or \
1260 ((is_dir or variant_name != None) and len(path_split) == 2) or \
1262 # The PBXFileReference or PBXVariantGroup will be added to or gotten from
1263 # this PBXGroup, no recursion necessary.
1264 if variant_name is None:
1265 # Add or get a PBXFileReference.
1266 file_ref = self.GetChildByPath(path)
1267 if file_ref != None:
1268 assert file_ref.__class__ == PBXFileReference
1270 file_ref = PBXFileReference({'path': path})
1271 self.AppendChild(file_ref)
1273 # Add or get a PBXVariantGroup. The variant group name is the same
1274 # as the basename (MainMenu.nib in the example above). grandparent
1275 # specifies the path to the variant group itself, and path_split[-2:]
1276 # is the path of the specific variant relative to its group.
1277 variant_group_name = posixpath.basename(path)
1278 variant_group_ref = self.AddOrGetVariantGroupByNameAndPath(
1279 variant_group_name, grandparent)
1280 variant_path = posixpath.sep.join(path_split[-2:])
1281 variant_ref = variant_group_ref.GetChildByPath(variant_path)
1282 if variant_ref != None:
1283 assert variant_ref.__class__ == PBXFileReference
1285 variant_ref = PBXFileReference({'name': variant_name,
1286 'path': variant_path})
1287 variant_group_ref.AppendChild(variant_ref)
1288 # The caller is interested in the variant group, not the specific
1290 file_ref = variant_group_ref
1293 # Hierarchical recursion. Add or get a PBXGroup corresponding to the
1294 # outermost path component, and then recurse into it, chopping off that
1296 next_dir = path_split[0]
1297 group_ref = self.GetChildByPath(next_dir)
1298 if group_ref != None:
1299 assert group_ref.__class__ == PBXGroup
1301 group_ref = PBXGroup({'path': next_dir})
1302 self.AppendChild(group_ref)
1303 return group_ref.AddOrGetFileByPath(posixpath.sep.join(path_split[1:]),
1306 def AddOrGetVariantGroupByNameAndPath(self, name, path):
1307 """Returns an existing or new PBXVariantGroup for name and path.
1309 If a PBXVariantGroup identified by the name and path arguments is already
1310 present as a child of this object, it is returned. Otherwise, a new
1311 PBXVariantGroup with the correct properties is created, added as a child,
1314 This method will generally be called by AddOrGetFileByPath, which knows
1315 when to create a variant group based on the structure of the pathnames
1320 if key in self._variant_children_by_name_and_path:
1321 variant_group_ref = self._variant_children_by_name_and_path[key]
1322 assert variant_group_ref.__class__ == PBXVariantGroup
1323 return variant_group_ref
1325 variant_group_properties = {'name': name}
1327 variant_group_properties['path'] = path
1328 variant_group_ref = PBXVariantGroup(variant_group_properties)
1329 self.AppendChild(variant_group_ref)
1331 return variant_group_ref
1333 def TakeOverOnlyChild(self, recurse=False):
1334 """If this PBXGroup has only one child and it's also a PBXGroup, take
1335 it over by making all of its children this object's children.
1337 This function will continue to take over only children when those children
1338 are groups. If there are three PBXGroups representing a, b, and c, with
1339 c inside b and b inside a, and a and b have no other children, this will
1340 result in a taking over both b and c, forming a PBXGroup for a/b/c.
1342 If recurse is True, this function will recurse into children and ask them
1343 to collapse themselves by taking over only children as well. Assuming
1344 an example hierarchy with files at a/b/c/d1, a/b/c/d2, and a/b/c/d3/e/f
1345 (d1, d2, and f are files, the rest are groups), recursion will result in
1346 a group for a/b/c containing a group for d3/e.
1349 # At this stage, check that child class types are PBXGroup exactly,
1350 # instead of using isinstance. The only subclass of PBXGroup,
1351 # PBXVariantGroup, should not participate in reparenting in the same way:
1352 # reparenting by merging different object types would be wrong.
1353 while len(self._properties['children']) == 1 and \
1354 self._properties['children'][0].__class__ == PBXGroup:
1355 # Loop to take over the innermost only-child group possible.
1357 child = self._properties['children'][0]
1359 # Assume the child's properties, including its children. Save a copy
1360 # of this object's old properties, because they'll still be needed.
1361 # This object retains its existing id and parent attributes.
1362 old_properties = self._properties
1363 self._properties = child._properties
1364 self._children_by_path = child._children_by_path
1366 if not 'sourceTree' in self._properties or \
1367 self._properties['sourceTree'] == '<group>':
1368 # The child was relative to its parent. Fix up the path. Note that
1369 # children with a sourceTree other than "<group>" are not relative to
1370 # their parents, so no path fix-up is needed in that case.
1371 if 'path' in old_properties:
1372 if 'path' in self._properties:
1373 # Both the original parent and child have paths set.
1374 self._properties['path'] = posixpath.join(old_properties['path'],
1375 self._properties['path'])
1377 # Only the original parent has a path, use it.
1378 self._properties['path'] = old_properties['path']
1379 if 'sourceTree' in old_properties:
1380 # The original parent had a sourceTree set, use it.
1381 self._properties['sourceTree'] = old_properties['sourceTree']
1383 # If the original parent had a name set, keep using it. If the original
1384 # parent didn't have a name but the child did, let the child's name
1385 # live on. If the name attribute seems unnecessary now, get rid of it.
1386 if 'name' in old_properties and old_properties['name'] != None and \
1387 old_properties['name'] != self.Name():
1388 self._properties['name'] = old_properties['name']
1389 if 'name' in self._properties and 'path' in self._properties and \
1390 self._properties['name'] == self._properties['path']:
1391 del self._properties['name']
1393 # Notify all children of their new parent.
1394 for child in self._properties['children']:
1397 # If asked to recurse, recurse.
1399 for child in self._properties['children']:
1400 if child.__class__ == PBXGroup:
1401 child.TakeOverOnlyChild(recurse)
1403 def SortGroup(self):
1404 self._properties['children'] = \
1405 sorted(self._properties['children'], cmp=lambda x,y: x.Compare(y))
1408 for child in self._properties['children']:
1409 if isinstance(child, PBXGroup):
1413 class XCFileLikeElement(XCHierarchicalElement):
1414 # Abstract base for objects that can be used as the fileRef property of
1417 def PathHashables(self):
1418 # A PBXBuildFile that refers to this object will call this method to
1419 # obtain additional hashables specific to this XCFileLikeElement. Don't
1420 # just use this object's hashables, they're not specific and unique enough
1421 # on their own (without access to the parent hashables.) Instead, provide
1422 # hashables that identify this object by path by getting its hashables as
1423 # well as the hashables of ancestor XCHierarchicalElement objects.
1427 while xche != None and isinstance(xche, XCHierarchicalElement):
1428 xche_hashables = xche.Hashables()
1429 for index in xrange(0, len(xche_hashables)):
1430 hashables.insert(index, xche_hashables[index])
1435 class XCContainerPortal(XCObject):
1436 # Abstract base for objects that can be used as the containerPortal property
1437 # of PBXContainerItemProxy.
1441 class XCRemoteObject(XCObject):
1442 # Abstract base for objects that can be used as the remoteGlobalIDString
1443 # property of PBXContainerItemProxy.
1447 class PBXFileReference(XCFileLikeElement, XCContainerPortal, XCRemoteObject):
1448 _schema = XCFileLikeElement._schema.copy()
1450 'explicitFileType': [0, str, 0, 0],
1451 'lastKnownFileType': [0, str, 0, 0],
1452 'name': [0, str, 0, 0],
1453 'path': [0, str, 0, 1],
1456 # Weird output rules for PBXFileReference.
1457 _should_print_single_line = True
1459 _encode_transforms = XCFileLikeElement._alternate_encode_transforms
1461 def __init__(self, properties=None, id=None, parent=None):
1463 XCFileLikeElement.__init__(self, properties, id, parent)
1464 if 'path' in self._properties and self._properties['path'].endswith('/'):
1465 self._properties['path'] = self._properties['path'][:-1]
1470 if 'path' in self._properties and \
1471 not 'lastKnownFileType' in self._properties and \
1472 not 'explicitFileType' in self._properties:
1473 # TODO(mark): This is the replacement for a replacement for a quick hack.
1474 # It is no longer incredibly sucky, but this list needs to be extended.
1477 'app': 'wrapper.application',
1479 'bundle': 'wrapper.cfbundle',
1480 'c': 'sourcecode.c.c',
1481 'cc': 'sourcecode.cpp.cpp',
1482 'cpp': 'sourcecode.cpp.cpp',
1484 'cxx': 'sourcecode.cpp.cpp',
1485 'dart': 'sourcecode',
1486 'dylib': 'compiled.mach-o.dylib',
1487 'framework': 'wrapper.framework',
1488 'gyp': 'sourcecode',
1489 'gypi': 'sourcecode',
1490 'h': 'sourcecode.c.h',
1491 'hxx': 'sourcecode.cpp.h',
1492 'icns': 'image.icns',
1493 'java': 'sourcecode.java',
1494 'js': 'sourcecode.javascript',
1495 'kext': 'wrapper.kext',
1496 'm': 'sourcecode.c.objc',
1497 'mm': 'sourcecode.cpp.objcpp',
1498 'nib': 'wrapper.nib',
1499 'o': 'compiled.mach-o.objfile',
1501 'pl': 'text.script.perl',
1502 'plist': 'text.plist.xml',
1503 'pm': 'text.script.perl',
1505 'py': 'text.script.python',
1506 'r': 'sourcecode.rez',
1507 'rez': 'sourcecode.rez',
1508 's': 'sourcecode.asm',
1509 'storyboard': 'file.storyboard',
1510 'strings': 'text.plist.strings',
1511 'swift': 'sourcecode.swift',
1513 'xcassets': 'folder.assetcatalog',
1514 'xcconfig': 'text.xcconfig',
1515 'xcdatamodel': 'wrapper.xcdatamodel',
1516 'xcdatamodeld':'wrapper.xcdatamodeld',
1518 'y': 'sourcecode.yacc',
1522 'dart': 'explicitFileType',
1523 'gyp': 'explicitFileType',
1524 'gypi': 'explicitFileType',
1528 file_type = 'folder'
1529 prop_name = 'lastKnownFileType'
1531 basename = posixpath.basename(self._properties['path'])
1532 (root, ext) = posixpath.splitext(basename)
1533 # Check the map using a lowercase extension.
1534 # TODO(mark): Maybe it should try with the original case first and fall
1535 # back to lowercase, in case there are any instances where case
1536 # matters. There currently aren't.
1538 ext = ext[1:].lower()
1540 # TODO(mark): "text" is the default value, but "file" is appropriate
1541 # for unrecognized files not containing text. Xcode seems to choose
1543 file_type = extension_map.get(ext, 'text')
1544 prop_name = prop_map.get(ext, 'lastKnownFileType')
1546 self._properties[prop_name] = file_type
1549 class PBXVariantGroup(PBXGroup, XCFileLikeElement):
1550 """PBXVariantGroup is used by Xcode to represent localizations."""
1551 # No additions to the schema relative to PBXGroup.
1555 # PBXReferenceProxy is also an XCFileLikeElement subclass. It is defined below
1556 # because it uses PBXContainerItemProxy, defined below.
1559 class XCBuildConfiguration(XCObject):
1560 _schema = XCObject._schema.copy()
1562 'baseConfigurationReference': [0, PBXFileReference, 0, 0],
1563 'buildSettings': [0, dict, 0, 1, {}],
1564 'name': [0, str, 0, 1],
1567 def HasBuildSetting(self, key):
1568 return key in self._properties['buildSettings']
1570 def GetBuildSetting(self, key):
1571 return self._properties['buildSettings'][key]
1573 def SetBuildSetting(self, key, value):
1574 # TODO(mark): If a list, copy?
1575 self._properties['buildSettings'][key] = value
1577 def AppendBuildSetting(self, key, value):
1578 if not key in self._properties['buildSettings']:
1579 self._properties['buildSettings'][key] = []
1580 self._properties['buildSettings'][key].append(value)
1582 def DelBuildSetting(self, key):
1583 if key in self._properties['buildSettings']:
1584 del self._properties['buildSettings'][key]
1586 def SetBaseConfiguration(self, value):
1587 self._properties['baseConfigurationReference'] = value
1589 class XCConfigurationList(XCObject):
1590 # _configs is the default list of configurations.
1591 _configs = [ XCBuildConfiguration({'name': 'Debug'}),
1592 XCBuildConfiguration({'name': 'Release'}) ]
1594 _schema = XCObject._schema.copy()
1596 'buildConfigurations': [1, XCBuildConfiguration, 1, 1, _configs],
1597 'defaultConfigurationIsVisible': [0, int, 0, 1, 1],
1598 'defaultConfigurationName': [0, str, 0, 1, 'Release'],
1602 return 'Build configuration list for ' + \
1603 self.parent.__class__.__name__ + ' "' + self.parent.Name() + '"'
1605 def ConfigurationNamed(self, name):
1606 """Convenience accessor to obtain an XCBuildConfiguration by name."""
1607 for configuration in self._properties['buildConfigurations']:
1608 if configuration._properties['name'] == name:
1609 return configuration
1611 raise KeyError(name)
1613 def DefaultConfiguration(self):
1614 """Convenience accessor to obtain the default XCBuildConfiguration."""
1615 return self.ConfigurationNamed(self._properties['defaultConfigurationName'])
1617 def HasBuildSetting(self, key):
1618 """Determines the state of a build setting in all XCBuildConfiguration
1621 If all child objects have key in their build settings, and the value is the
1622 same in all child objects, returns 1.
1624 If no child objects have the key in their build settings, returns 0.
1626 If some, but not all, child objects have the key in their build settings,
1627 or if any children have different values for the key, returns -1.
1632 for configuration in self._properties['buildConfigurations']:
1633 configuration_has = configuration.HasBuildSetting(key)
1635 has = configuration_has
1636 elif has != configuration_has:
1639 if configuration_has:
1640 configuration_value = configuration.GetBuildSetting(key)
1642 value = configuration_value
1643 elif value != configuration_value:
1651 def GetBuildSetting(self, key):
1652 """Gets the build setting for key.
1654 All child XCConfiguration objects must have the same value set for the
1655 setting, or a ValueError will be raised.
1658 # TODO(mark): This is wrong for build settings that are lists. The list
1659 # contents should be compared (and a list copy returned?)
1662 for configuration in self._properties['buildConfigurations']:
1663 configuration_value = configuration.GetBuildSetting(key)
1665 value = configuration_value
1667 if value != configuration_value:
1668 raise ValueError('Variant values for ' + key)
1672 def SetBuildSetting(self, key, value):
1673 """Sets the build setting for key to value in all child
1674 XCBuildConfiguration objects.
1677 for configuration in self._properties['buildConfigurations']:
1678 configuration.SetBuildSetting(key, value)
1680 def AppendBuildSetting(self, key, value):
1681 """Appends value to the build setting for key, which is treated as a list,
1682 in all child XCBuildConfiguration objects.
1685 for configuration in self._properties['buildConfigurations']:
1686 configuration.AppendBuildSetting(key, value)
1688 def DelBuildSetting(self, key):
1689 """Deletes the build setting key from all child XCBuildConfiguration
1693 for configuration in self._properties['buildConfigurations']:
1694 configuration.DelBuildSetting(key)
1696 def SetBaseConfiguration(self, value):
1697 """Sets the build configuration in all child XCBuildConfiguration objects.
1700 for configuration in self._properties['buildConfigurations']:
1701 configuration.SetBaseConfiguration(value)
1704 class PBXBuildFile(XCObject):
1705 _schema = XCObject._schema.copy()
1707 'fileRef': [0, XCFileLikeElement, 0, 1],
1708 'settings': [0, str, 0, 0], # hack, it's a dict
1711 # Weird output rules for PBXBuildFile.
1712 _should_print_single_line = True
1713 _encode_transforms = XCObject._alternate_encode_transforms
1716 # Example: "main.cc in Sources"
1717 return self._properties['fileRef'].Name() + ' in ' + self.parent.Name()
1719 def Hashables(self):
1721 hashables = XCObject.Hashables(self)
1723 # It is not sufficient to just rely on Name() to get the
1724 # XCFileLikeElement's name, because that is not a complete pathname.
1725 # PathHashables returns hashables unique enough that no two
1726 # PBXBuildFiles should wind up with the same set of hashables, unless
1727 # someone adds the same file multiple times to the same target. That
1728 # would be considered invalid anyway.
1729 hashables.extend(self._properties['fileRef'].PathHashables())
1734 class XCBuildPhase(XCObject):
1735 """Abstract base for build phase classes. Not represented in a project
1739 _files_by_path: A dict mapping each path of a child in the files list by
1740 path (keys) to the corresponding PBXBuildFile children (values).
1741 _files_by_xcfilelikeelement: A dict mapping each XCFileLikeElement (keys)
1742 to the corresponding PBXBuildFile children (values).
1745 # TODO(mark): Some build phase types, like PBXShellScriptBuildPhase, don't
1746 # actually have a "files" list. XCBuildPhase should not have "files" but
1747 # another abstract subclass of it should provide this, and concrete build
1748 # phase types that do have "files" lists should be derived from that new
1749 # abstract subclass. XCBuildPhase should only provide buildActionMask and
1750 # runOnlyForDeploymentPostprocessing, and not files or the various
1751 # file-related methods and attributes.
1753 _schema = XCObject._schema.copy()
1755 'buildActionMask': [0, int, 0, 1, 0x7fffffff],
1756 'files': [1, PBXBuildFile, 1, 1, []],
1757 'runOnlyForDeploymentPostprocessing': [0, int, 0, 1, 0],
1760 def __init__(self, properties=None, id=None, parent=None):
1762 XCObject.__init__(self, properties, id, parent)
1764 self._files_by_path = {}
1765 self._files_by_xcfilelikeelement = {}
1766 for pbxbuildfile in self._properties.get('files', []):
1767 self._AddBuildFileToDicts(pbxbuildfile)
1769 def FileGroup(self, path):
1770 # Subclasses must override this by returning a two-element tuple. The
1771 # first item in the tuple should be the PBXGroup to which "path" should be
1772 # added, either as a child or deeper descendant. The second item should
1773 # be a boolean indicating whether files should be added into hierarchical
1774 # groups or one single flat group.
1775 raise NotImplementedError(
1776 self.__class__.__name__ + ' must implement FileGroup')
1778 def _AddPathToDict(self, pbxbuildfile, path):
1779 """Adds path to the dict tracking paths belonging to this build phase.
1781 If the path is already a member of this build phase, raises an exception.
1784 if path in self._files_by_path:
1785 raise ValueError('Found multiple build files with path ' + path)
1786 self._files_by_path[path] = pbxbuildfile
1788 def _AddBuildFileToDicts(self, pbxbuildfile, path=None):
1789 """Maintains the _files_by_path and _files_by_xcfilelikeelement dicts.
1791 If path is specified, then it is the path that is being added to the
1792 phase, and pbxbuildfile must contain either a PBXFileReference directly
1793 referencing that path, or it must contain a PBXVariantGroup that itself
1794 contains a PBXFileReference referencing the path.
1796 If path is not specified, either the PBXFileReference's path or the paths
1797 of all children of the PBXVariantGroup are taken as being added to the
1800 If the path is already present in the phase, raises an exception.
1802 If the PBXFileReference or PBXVariantGroup referenced by pbxbuildfile
1803 are already present in the phase, referenced by a different PBXBuildFile
1804 object, raises an exception. This does not raise an exception when
1805 a PBXFileReference or PBXVariantGroup reappear and are referenced by the
1806 same PBXBuildFile that has already introduced them, because in the case
1807 of PBXVariantGroup objects, they may correspond to multiple paths that are
1808 not all added simultaneously. When this situation occurs, the path needs
1809 to be added to _files_by_path, but nothing needs to change in
1810 _files_by_xcfilelikeelement, and the caller should have avoided adding
1811 the PBXBuildFile if it is already present in the list of children.
1814 xcfilelikeelement = pbxbuildfile._properties['fileRef']
1818 # It's best when the caller provides the path.
1819 if isinstance(xcfilelikeelement, PBXVariantGroup):
1822 # If the caller didn't provide a path, there can be either multiple
1823 # paths (PBXVariantGroup) or one.
1824 if isinstance(xcfilelikeelement, PBXVariantGroup):
1825 for variant in xcfilelikeelement._properties['children']:
1826 paths.append(variant.FullPath())
1828 paths.append(xcfilelikeelement.FullPath())
1830 # Add the paths first, because if something's going to raise, the
1831 # messages provided by _AddPathToDict are more useful owing to its
1832 # having access to a real pathname and not just an object's Name().
1833 for a_path in paths:
1834 self._AddPathToDict(pbxbuildfile, a_path)
1836 # If another PBXBuildFile references this XCFileLikeElement, there's a
1838 if xcfilelikeelement in self._files_by_xcfilelikeelement and \
1839 self._files_by_xcfilelikeelement[xcfilelikeelement] != pbxbuildfile:
1840 raise ValueError('Found multiple build files for ' + \
1841 xcfilelikeelement.Name())
1842 self._files_by_xcfilelikeelement[xcfilelikeelement] = pbxbuildfile
1844 def AppendBuildFile(self, pbxbuildfile, path=None):
1845 # Callers should use this instead of calling
1846 # AppendProperty('files', pbxbuildfile) directly because this function
1847 # maintains the object's dicts. Better yet, callers can just call AddFile
1848 # with a pathname and not worry about building their own PBXBuildFile
1850 self.AppendProperty('files', pbxbuildfile)
1851 self._AddBuildFileToDicts(pbxbuildfile, path)
1853 def AddFile(self, path, settings=None):
1854 (file_group, hierarchical) = self.FileGroup(path)
1855 file_ref = file_group.AddOrGetFileByPath(path, hierarchical)
1857 if file_ref in self._files_by_xcfilelikeelement and \
1858 isinstance(file_ref, PBXVariantGroup):
1859 # There's already a PBXBuildFile in this phase corresponding to the
1860 # PBXVariantGroup. path just provides a new variant that belongs to
1861 # the group. Add the path to the dict.
1862 pbxbuildfile = self._files_by_xcfilelikeelement[file_ref]
1863 self._AddBuildFileToDicts(pbxbuildfile, path)
1865 # Add a new PBXBuildFile to get file_ref into the phase.
1866 if settings is None:
1867 pbxbuildfile = PBXBuildFile({'fileRef': file_ref})
1869 pbxbuildfile = PBXBuildFile({'fileRef': file_ref, 'settings': settings})
1870 self.AppendBuildFile(pbxbuildfile, path)
1873 class PBXHeadersBuildPhase(XCBuildPhase):
1874 # No additions to the schema relative to XCBuildPhase.
1879 def FileGroup(self, path):
1880 return self.PBXProjectAncestor().RootGroupForPath(path)
1883 class PBXResourcesBuildPhase(XCBuildPhase):
1884 # No additions to the schema relative to XCBuildPhase.
1889 def FileGroup(self, path):
1890 return self.PBXProjectAncestor().RootGroupForPath(path)
1893 class PBXSourcesBuildPhase(XCBuildPhase):
1894 # No additions to the schema relative to XCBuildPhase.
1899 def FileGroup(self, path):
1900 return self.PBXProjectAncestor().RootGroupForPath(path)
1903 class PBXFrameworksBuildPhase(XCBuildPhase):
1904 # No additions to the schema relative to XCBuildPhase.
1909 def FileGroup(self, path):
1910 (root, ext) = posixpath.splitext(path)
1912 ext = ext[1:].lower()
1914 # .o files are added to Xcode Frameworks phases, but conceptually aren't
1915 # frameworks, they're more like sources or intermediates. Redirect them
1916 # to show up in one of those other groups.
1917 return self.PBXProjectAncestor().RootGroupForPath(path)
1919 return (self.PBXProjectAncestor().FrameworksGroup(), False)
1922 class PBXShellScriptBuildPhase(XCBuildPhase):
1923 _schema = XCBuildPhase._schema.copy()
1925 'inputPaths': [1, str, 0, 1, []],
1926 'name': [0, str, 0, 0],
1927 'outputPaths': [1, str, 0, 1, []],
1928 'shellPath': [0, str, 0, 1, '/bin/sh'],
1929 'shellScript': [0, str, 0, 1],
1930 'showEnvVarsInLog': [0, int, 0, 0],
1934 if 'name' in self._properties:
1935 return self._properties['name']
1937 return 'ShellScript'
1940 class PBXCopyFilesBuildPhase(XCBuildPhase):
1941 _schema = XCBuildPhase._schema.copy()
1943 'dstPath': [0, str, 0, 1],
1944 'dstSubfolderSpec': [0, int, 0, 1],
1945 'name': [0, str, 0, 0],
1948 # path_tree_re matches "$(DIR)/path" or just "$(DIR)". Match group 1 is
1949 # "DIR", match group 3 is "path" or None.
1950 path_tree_re = re.compile('^\\$\\((.*)\\)(/(.*)|)$')
1952 # path_tree_to_subfolder maps names of Xcode variables to the associated
1953 # dstSubfolderSpec property value used in a PBXCopyFilesBuildPhase object.
1954 path_tree_to_subfolder = {
1955 'BUILT_FRAMEWORKS_DIR': 10, # Frameworks Directory
1956 'BUILT_PRODUCTS_DIR': 16, # Products Directory
1957 # Other types that can be chosen via the Xcode UI.
1958 # TODO(mark): Map Xcode variable names to these.
1960 # : 6, # Executables: 6
1962 # : 15, # Java Resources
1963 # : 11, # Shared Frameworks
1964 # : 12, # Shared Support
1969 if 'name' in self._properties:
1970 return self._properties['name']
1974 def FileGroup(self, path):
1975 return self.PBXProjectAncestor().RootGroupForPath(path)
1977 def SetDestination(self, path):
1978 """Set the dstSubfolderSpec and dstPath properties from path.
1980 path may be specified in the same notation used for XCHierarchicalElements,
1981 specifically, "$(DIR)/path".
1984 path_tree_match = self.path_tree_re.search(path)
1986 # Everything else needs to be relative to an Xcode variable.
1987 path_tree = path_tree_match.group(1)
1988 relative_path = path_tree_match.group(3)
1990 if path_tree in self.path_tree_to_subfolder:
1991 subfolder = self.path_tree_to_subfolder[path_tree]
1992 if relative_path is None:
1995 # The path starts with an unrecognized Xcode variable
1996 # name like $(SRCROOT). Xcode will still handle this
1997 # as an "absolute path" that starts with the variable.
1999 relative_path = path
2000 elif path.startswith('/'):
2001 # Special case. Absolute paths are in dstSubfolderSpec 0.
2003 relative_path = path[1:]
2005 raise ValueError('Can\'t use path %s in a %s' % \
2006 (path, self.__class__.__name__))
2008 self._properties['dstPath'] = relative_path
2009 self._properties['dstSubfolderSpec'] = subfolder
2012 class PBXBuildRule(XCObject):
2013 _schema = XCObject._schema.copy()
2015 'compilerSpec': [0, str, 0, 1],
2016 'filePatterns': [0, str, 0, 0],
2017 'fileType': [0, str, 0, 1],
2018 'isEditable': [0, int, 0, 1, 1],
2019 'outputFiles': [1, str, 0, 1, []],
2020 'script': [0, str, 0, 0],
2024 # Not very inspired, but it's what Xcode uses.
2025 return self.__class__.__name__
2027 def Hashables(self):
2029 hashables = XCObject.Hashables(self)
2031 # Use the hashables of the weak objects that this object refers to.
2032 hashables.append(self._properties['fileType'])
2033 if 'filePatterns' in self._properties:
2034 hashables.append(self._properties['filePatterns'])
2038 class PBXContainerItemProxy(XCObject):
2039 # When referencing an item in this project file, containerPortal is the
2040 # PBXProject root object of this project file. When referencing an item in
2041 # another project file, containerPortal is a PBXFileReference identifying
2042 # the other project file.
2044 # When serving as a proxy to an XCTarget (in this project file or another),
2045 # proxyType is 1. When serving as a proxy to a PBXFileReference (in another
2046 # project file), proxyType is 2. Type 2 is used for references to the
2047 # producs of the other project file's targets.
2049 # Xcode is weird about remoteGlobalIDString. Usually, it's printed without
2050 # a comment, indicating that it's tracked internally simply as a string, but
2051 # sometimes it's printed with a comment (usually when the object is initially
2052 # created), indicating that it's tracked as a project file object at least
2053 # sometimes. This module always tracks it as an object, but contains a hack
2054 # to prevent it from printing the comment in the project file output. See
2056 _schema = XCObject._schema.copy()
2058 'containerPortal': [0, XCContainerPortal, 0, 1],
2059 'proxyType': [0, int, 0, 1],
2060 'remoteGlobalIDString': [0, XCRemoteObject, 0, 1],
2061 'remoteInfo': [0, str, 0, 1],
2065 props = self._properties
2066 name = '%s.gyp:%s' % (props['containerPortal'].Name(), props['remoteInfo'])
2067 return '<%s %r at 0x%x>' % (self.__class__.__name__, name, id(self))
2070 # Admittedly not the best name, but it's what Xcode uses.
2071 return self.__class__.__name__
2073 def Hashables(self):
2075 hashables = XCObject.Hashables(self)
2077 # Use the hashables of the weak objects that this object refers to.
2078 hashables.extend(self._properties['containerPortal'].Hashables())
2079 hashables.extend(self._properties['remoteGlobalIDString'].Hashables())
2083 class PBXTargetDependency(XCObject):
2084 # The "target" property accepts an XCTarget object, and obviously not
2085 # NoneType. But XCTarget is defined below, so it can't be put into the
2086 # schema yet. The definition of PBXTargetDependency can't be moved below
2087 # XCTarget because XCTarget's own schema references PBXTargetDependency.
2088 # Python doesn't deal well with this circular relationship, and doesn't have
2089 # a real way to do forward declarations. To work around, the type of
2090 # the "target" property is reset below, after XCTarget is defined.
2092 # At least one of "name" and "target" is required.
2093 _schema = XCObject._schema.copy()
2095 'name': [0, str, 0, 0],
2096 'target': [0, None.__class__, 0, 0],
2097 'targetProxy': [0, PBXContainerItemProxy, 1, 1],
2101 name = self._properties.get('name') or self._properties['target'].Name()
2102 return '<%s %r at 0x%x>' % (self.__class__.__name__, name, id(self))
2105 # Admittedly not the best name, but it's what Xcode uses.
2106 return self.__class__.__name__
2108 def Hashables(self):
2110 hashables = XCObject.Hashables(self)
2112 # Use the hashables of the weak objects that this object refers to.
2113 hashables.extend(self._properties['targetProxy'].Hashables())
2117 class PBXReferenceProxy(XCFileLikeElement):
2118 _schema = XCFileLikeElement._schema.copy()
2120 'fileType': [0, str, 0, 1],
2121 'path': [0, str, 0, 1],
2122 'remoteRef': [0, PBXContainerItemProxy, 1, 1],
2126 class XCTarget(XCRemoteObject):
2127 # An XCTarget is really just an XCObject, the XCRemoteObject thing is just
2128 # to allow PBXProject to be used in the remoteGlobalIDString property of
2129 # PBXContainerItemProxy.
2131 # Setting a "name" property at instantiation may also affect "productName",
2132 # which may in turn affect the "PRODUCT_NAME" build setting in children of
2133 # "buildConfigurationList". See __init__ below.
2134 _schema = XCRemoteObject._schema.copy()
2136 'buildConfigurationList': [0, XCConfigurationList, 1, 1,
2137 XCConfigurationList()],
2138 'buildPhases': [1, XCBuildPhase, 1, 1, []],
2139 'dependencies': [1, PBXTargetDependency, 1, 1, []],
2140 'name': [0, str, 0, 1],
2141 'productName': [0, str, 0, 1],
2144 def __init__(self, properties=None, id=None, parent=None,
2145 force_outdir=None, force_prefix=None, force_extension=None):
2147 XCRemoteObject.__init__(self, properties, id, parent)
2149 # Set up additional defaults not expressed in the schema. If a "name"
2150 # property was supplied, set "productName" if it is not present. Also set
2151 # the "PRODUCT_NAME" build setting in each configuration, but only if
2152 # the setting is not present in any build configuration.
2153 if 'name' in self._properties:
2154 if not 'productName' in self._properties:
2155 self.SetProperty('productName', self._properties['name'])
2157 if 'productName' in self._properties:
2158 if 'buildConfigurationList' in self._properties:
2159 configs = self._properties['buildConfigurationList']
2160 if configs.HasBuildSetting('PRODUCT_NAME') == 0:
2161 configs.SetBuildSetting('PRODUCT_NAME',
2162 self._properties['productName'])
2164 def AddDependency(self, other):
2165 pbxproject = self.PBXProjectAncestor()
2166 other_pbxproject = other.PBXProjectAncestor()
2167 if pbxproject == other_pbxproject:
2168 # Add a dependency to another target in the same project file.
2169 container = PBXContainerItemProxy({'containerPortal': pbxproject,
2171 'remoteGlobalIDString': other,
2172 'remoteInfo': other.Name()})
2173 dependency = PBXTargetDependency({'target': other,
2174 'targetProxy': container})
2175 self.AppendProperty('dependencies', dependency)
2177 # Add a dependency to a target in a different project file.
2178 other_project_ref = \
2179 pbxproject.AddOrGetProjectReference(other_pbxproject)[1]
2180 container = PBXContainerItemProxy({
2181 'containerPortal': other_project_ref,
2183 'remoteGlobalIDString': other,
2184 'remoteInfo': other.Name(),
2186 dependency = PBXTargetDependency({'name': other.Name(),
2187 'targetProxy': container})
2188 self.AppendProperty('dependencies', dependency)
2190 # Proxy all of these through to the build configuration list.
2192 def ConfigurationNamed(self, name):
2193 return self._properties['buildConfigurationList'].ConfigurationNamed(name)
2195 def DefaultConfiguration(self):
2196 return self._properties['buildConfigurationList'].DefaultConfiguration()
2198 def HasBuildSetting(self, key):
2199 return self._properties['buildConfigurationList'].HasBuildSetting(key)
2201 def GetBuildSetting(self, key):
2202 return self._properties['buildConfigurationList'].GetBuildSetting(key)
2204 def SetBuildSetting(self, key, value):
2205 return self._properties['buildConfigurationList'].SetBuildSetting(key, \
2208 def AppendBuildSetting(self, key, value):
2209 return self._properties['buildConfigurationList'].AppendBuildSetting(key, \
2212 def DelBuildSetting(self, key):
2213 return self._properties['buildConfigurationList'].DelBuildSetting(key)
2216 # Redefine the type of the "target" property. See PBXTargetDependency._schema
2218 PBXTargetDependency._schema['target'][1] = XCTarget
2221 class PBXNativeTarget(XCTarget):
2222 # buildPhases is overridden in the schema to be able to set defaults.
2224 # NOTE: Contrary to most objects, it is advisable to set parent when
2225 # constructing PBXNativeTarget. A parent of an XCTarget must be a PBXProject
2226 # object. A parent reference is required for a PBXNativeTarget during
2227 # construction to be able to set up the target defaults for productReference,
2228 # because a PBXBuildFile object must be created for the target and it must
2229 # be added to the PBXProject's mainGroup hierarchy.
2230 _schema = XCTarget._schema.copy()
2232 'buildPhases': [1, XCBuildPhase, 1, 1,
2233 [PBXSourcesBuildPhase(), PBXFrameworksBuildPhase()]],
2234 'buildRules': [1, PBXBuildRule, 1, 1, []],
2235 'productReference': [0, PBXFileReference, 0, 1],
2236 'productType': [0, str, 0, 1],
2239 # Mapping from Xcode product-types to settings. The settings are:
2240 # filetype : used for explicitFileType in the project file
2241 # prefix : the prefix for the file name
2242 # suffix : the suffix for the file name
2243 _product_filetypes = {
2244 'com.apple.product-type.application': ['wrapper.application',
2246 'com.apple.product-type.application.watchapp': ['wrapper.application',
2248 'com.apple.product-type.watchkit-extension': ['wrapper.app-extension',
2250 'com.apple.product-type.app-extension': ['wrapper.app-extension',
2252 'com.apple.product-type.bundle': ['wrapper.cfbundle',
2254 'com.apple.product-type.framework': ['wrapper.framework',
2256 'com.apple.product-type.library.dynamic': ['compiled.mach-o.dylib',
2258 'com.apple.product-type.library.static': ['archive.ar',
2260 'com.apple.product-type.tool': ['compiled.mach-o.executable',
2262 'com.apple.product-type.bundle.unit-test': ['wrapper.cfbundle',
2264 'com.googlecode.gyp.xcode.bundle': ['compiled.mach-o.dylib',
2266 'com.apple.product-type.kernel-extension': ['wrapper.kext',
2270 def __init__(self, properties=None, id=None, parent=None,
2271 force_outdir=None, force_prefix=None, force_extension=None):
2273 XCTarget.__init__(self, properties, id, parent)
2275 if 'productName' in self._properties and \
2276 'productType' in self._properties and \
2277 not 'productReference' in self._properties and \
2278 self._properties['productType'] in self._product_filetypes:
2279 products_group = None
2280 pbxproject = self.PBXProjectAncestor()
2281 if pbxproject != None:
2282 products_group = pbxproject.ProductsGroup()
2284 if products_group != None:
2285 (filetype, prefix, suffix) = \
2286 self._product_filetypes[self._properties['productType']]
2287 # Xcode does not have a distinct type for loadable modules that are
2288 # pure BSD targets (not in a bundle wrapper). GYP allows such modules
2289 # to be specified by setting a target type to loadable_module without
2290 # having mac_bundle set. These are mapped to the pseudo-product type
2291 # com.googlecode.gyp.xcode.bundle.
2293 # By picking up this special type and converting it to a dynamic
2294 # library (com.apple.product-type.library.dynamic) with fix-ups,
2295 # single-file loadable modules can be produced.
2297 # MACH_O_TYPE is changed to mh_bundle to produce the proper file type
2298 # (as opposed to mh_dylib). In order for linking to succeed,
2299 # DYLIB_CURRENT_VERSION and DYLIB_COMPATIBILITY_VERSION must be
2300 # cleared. They are meaningless for type mh_bundle.
2302 # Finally, the .so extension is forcibly applied over the default
2303 # (.dylib), unless another forced extension is already selected.
2304 # .dylib is plainly wrong, and .bundle is used by loadable_modules in
2305 # bundle wrappers (com.apple.product-type.bundle). .so seems an odd
2306 # choice because it's used as the extension on many other systems that
2307 # don't distinguish between linkable shared libraries and non-linkable
2308 # loadable modules, but there's precedent: Python loadable modules on
2309 # Mac OS X use an .so extension.
2310 if self._properties['productType'] == 'com.googlecode.gyp.xcode.bundle':
2311 self._properties['productType'] = \
2312 'com.apple.product-type.library.dynamic'
2313 self.SetBuildSetting('MACH_O_TYPE', 'mh_bundle')
2314 self.SetBuildSetting('DYLIB_CURRENT_VERSION', '')
2315 self.SetBuildSetting('DYLIB_COMPATIBILITY_VERSION', '')
2316 if force_extension is None:
2317 force_extension = suffix[1:]
2319 if self._properties['productType'] == \
2320 'com.apple.product-type-bundle.unit.test':
2321 if force_extension is None:
2322 force_extension = suffix[1:]
2324 if force_extension is not None:
2325 # If it's a wrapper (bundle), set WRAPPER_EXTENSION.
2326 # Extension override.
2327 suffix = '.' + force_extension
2328 if filetype.startswith('wrapper.'):
2329 self.SetBuildSetting('WRAPPER_EXTENSION', force_extension)
2331 self.SetBuildSetting('EXECUTABLE_EXTENSION', force_extension)
2333 if filetype.startswith('compiled.mach-o.executable'):
2334 product_name = self._properties['productName']
2335 product_name += suffix
2337 self.SetProperty('productName', product_name)
2338 self.SetBuildSetting('PRODUCT_NAME', product_name)
2340 # Xcode handles most prefixes based on the target type, however there
2341 # are exceptions. If a "BSD Dynamic Library" target is added in the
2342 # Xcode UI, Xcode sets EXECUTABLE_PREFIX. This check duplicates that
2344 if force_prefix is not None:
2345 prefix = force_prefix
2346 if filetype.startswith('wrapper.'):
2347 self.SetBuildSetting('WRAPPER_PREFIX', prefix)
2349 self.SetBuildSetting('EXECUTABLE_PREFIX', prefix)
2351 if force_outdir is not None:
2352 self.SetBuildSetting('TARGET_BUILD_DIR', force_outdir)
2354 # TODO(tvl): Remove the below hack.
2355 # http://code.google.com/p/gyp/issues/detail?id=122
2357 # Some targets include the prefix in the target_name. These targets
2358 # really should just add a product_name setting that doesn't include
2359 # the prefix. For example:
2360 # target_name = 'libevent', product_name = 'event'
2361 # This check cleans up for them.
2362 product_name = self._properties['productName']
2363 prefix_len = len(prefix)
2364 if prefix_len and (product_name[:prefix_len] == prefix):
2365 product_name = product_name[prefix_len:]
2366 self.SetProperty('productName', product_name)
2367 self.SetBuildSetting('PRODUCT_NAME', product_name)
2370 'explicitFileType': filetype,
2371 'includeInIndex': 0,
2372 'path': prefix + product_name + suffix,
2373 'sourceTree': 'BUILT_PRODUCTS_DIR',
2375 file_ref = PBXFileReference(ref_props)
2376 products_group.AppendChild(file_ref)
2377 self.SetProperty('productReference', file_ref)
2379 def GetBuildPhaseByType(self, type):
2380 if not 'buildPhases' in self._properties:
2384 for phase in self._properties['buildPhases']:
2385 if isinstance(phase, type):
2386 # Some phases may be present in multiples in a well-formed project file,
2387 # but phases like PBXSourcesBuildPhase may only be present singly, and
2388 # this function is intended as an aid to GetBuildPhaseByType. Loop
2389 # over the entire list of phases and assert if more than one of the
2390 # desired type is found.
2391 assert the_phase is None
2396 def HeadersPhase(self):
2397 headers_phase = self.GetBuildPhaseByType(PBXHeadersBuildPhase)
2398 if headers_phase is None:
2399 headers_phase = PBXHeadersBuildPhase()
2401 # The headers phase should come before the resources, sources, and
2402 # frameworks phases, if any.
2403 insert_at = len(self._properties['buildPhases'])
2404 for index in xrange(0, len(self._properties['buildPhases'])):
2405 phase = self._properties['buildPhases'][index]
2406 if isinstance(phase, PBXResourcesBuildPhase) or \
2407 isinstance(phase, PBXSourcesBuildPhase) or \
2408 isinstance(phase, PBXFrameworksBuildPhase):
2412 self._properties['buildPhases'].insert(insert_at, headers_phase)
2413 headers_phase.parent = self
2415 return headers_phase
2417 def ResourcesPhase(self):
2418 resources_phase = self.GetBuildPhaseByType(PBXResourcesBuildPhase)
2419 if resources_phase is None:
2420 resources_phase = PBXResourcesBuildPhase()
2422 # The resources phase should come before the sources and frameworks
2424 insert_at = len(self._properties['buildPhases'])
2425 for index in xrange(0, len(self._properties['buildPhases'])):
2426 phase = self._properties['buildPhases'][index]
2427 if isinstance(phase, PBXSourcesBuildPhase) or \
2428 isinstance(phase, PBXFrameworksBuildPhase):
2432 self._properties['buildPhases'].insert(insert_at, resources_phase)
2433 resources_phase.parent = self
2435 return resources_phase
2437 def SourcesPhase(self):
2438 sources_phase = self.GetBuildPhaseByType(PBXSourcesBuildPhase)
2439 if sources_phase is None:
2440 sources_phase = PBXSourcesBuildPhase()
2441 self.AppendProperty('buildPhases', sources_phase)
2443 return sources_phase
2445 def FrameworksPhase(self):
2446 frameworks_phase = self.GetBuildPhaseByType(PBXFrameworksBuildPhase)
2447 if frameworks_phase is None:
2448 frameworks_phase = PBXFrameworksBuildPhase()
2449 self.AppendProperty('buildPhases', frameworks_phase)
2451 return frameworks_phase
2453 def AddDependency(self, other):
2455 XCTarget.AddDependency(self, other)
2457 static_library_type = 'com.apple.product-type.library.static'
2458 shared_library_type = 'com.apple.product-type.library.dynamic'
2459 framework_type = 'com.apple.product-type.framework'
2460 if isinstance(other, PBXNativeTarget) and \
2461 'productType' in self._properties and \
2462 self._properties['productType'] != static_library_type and \
2463 'productType' in other._properties and \
2464 (other._properties['productType'] == static_library_type or \
2465 ((other._properties['productType'] == shared_library_type or \
2466 other._properties['productType'] == framework_type) and \
2467 ((not other.HasBuildSetting('MACH_O_TYPE')) or
2468 other.GetBuildSetting('MACH_O_TYPE') != 'mh_bundle'))):
2470 file_ref = other.GetProperty('productReference')
2472 pbxproject = self.PBXProjectAncestor()
2473 other_pbxproject = other.PBXProjectAncestor()
2474 if pbxproject != other_pbxproject:
2475 other_project_product_group = \
2476 pbxproject.AddOrGetProjectReference(other_pbxproject)[0]
2477 file_ref = other_project_product_group.GetChildByRemoteObject(file_ref)
2479 self.FrameworksPhase().AppendProperty('files',
2480 PBXBuildFile({'fileRef': file_ref}))
2483 class PBXAggregateTarget(XCTarget):
2487 class PBXProject(XCContainerPortal):
2488 # A PBXProject is really just an XCObject, the XCContainerPortal thing is
2489 # just to allow PBXProject to be used in the containerPortal property of
2490 # PBXContainerItemProxy.
2494 path: "sample.xcodeproj". TODO(mark) Document me!
2495 _other_pbxprojects: A dictionary, keyed by other PBXProject objects. Each
2496 value is a reference to the dict in the
2497 projectReferences list associated with the keyed
2501 _schema = XCContainerPortal._schema.copy()
2503 'attributes': [0, dict, 0, 0],
2504 'buildConfigurationList': [0, XCConfigurationList, 1, 1,
2505 XCConfigurationList()],
2506 'compatibilityVersion': [0, str, 0, 1, 'Xcode 3.2'],
2507 'hasScannedForEncodings': [0, int, 0, 1, 1],
2508 'mainGroup': [0, PBXGroup, 1, 1, PBXGroup()],
2509 'projectDirPath': [0, str, 0, 1, ''],
2510 'projectReferences': [1, dict, 0, 0],
2511 'projectRoot': [0, str, 0, 1, ''],
2512 'targets': [1, XCTarget, 1, 1, []],
2515 def __init__(self, properties=None, id=None, parent=None, path=None):
2517 self._other_pbxprojects = {}
2519 return XCContainerPortal.__init__(self, properties, id, parent)
2523 if name[-10:] == '.xcodeproj':
2525 return posixpath.basename(name)
2531 return 'Project object'
2535 children = XCContainerPortal.Children(self)
2537 # Add children that the schema doesn't know about. Maybe there's a more
2538 # elegant way around this, but this is the only case where we need to own
2539 # objects in a dictionary (that is itself in a list), and three lines for
2540 # a one-off isn't that big a deal.
2541 if 'projectReferences' in self._properties:
2542 for reference in self._properties['projectReferences']:
2543 children.append(reference['ProductGroup'])
2547 def PBXProjectAncestor(self):
2550 def _GroupByName(self, name):
2551 if not 'mainGroup' in self._properties:
2552 self.SetProperty('mainGroup', PBXGroup())
2554 main_group = self._properties['mainGroup']
2555 group = main_group.GetChildByName(name)
2557 group = PBXGroup({'name': name})
2558 main_group.AppendChild(group)
2562 # SourceGroup and ProductsGroup are created by default in Xcode's own
2564 def SourceGroup(self):
2565 return self._GroupByName('Source')
2567 def ProductsGroup(self):
2568 return self._GroupByName('Products')
2570 # IntermediatesGroup is used to collect source-like files that are generated
2571 # by rules or script phases and are placed in intermediate directories such
2572 # as DerivedSources.
2573 def IntermediatesGroup(self):
2574 return self._GroupByName('Intermediates')
2576 # FrameworksGroup and ProjectsGroup are top-level groups used to collect
2577 # frameworks and projects.
2578 def FrameworksGroup(self):
2579 return self._GroupByName('Frameworks')
2581 def ProjectsGroup(self):
2582 return self._GroupByName('Projects')
2584 def RootGroupForPath(self, path):
2585 """Returns a PBXGroup child of this object to which path should be added.
2587 This method is intended to choose between SourceGroup and
2588 IntermediatesGroup on the basis of whether path is present in a source
2589 directory or an intermediates directory. For the purposes of this
2590 determination, any path located within a derived file directory such as
2591 PROJECT_DERIVED_FILE_DIR is treated as being in an intermediates
2594 The returned value is a two-element tuple. The first element is the
2595 PBXGroup, and the second element specifies whether that group should be
2596 organized hierarchically (True) or as a single flat list (False).
2599 # TODO(mark): make this a class variable and bind to self on call?
2600 # Also, this list is nowhere near exhaustive.
2601 # INTERMEDIATE_DIR and SHARED_INTERMEDIATE_DIR are used by
2602 # gyp.generator.xcode. There should probably be some way for that module
2603 # to push the names in, rather than having to hard-code them here.
2604 source_tree_groups = {
2605 'DERIVED_FILE_DIR': (self.IntermediatesGroup, True),
2606 'INTERMEDIATE_DIR': (self.IntermediatesGroup, True),
2607 'PROJECT_DERIVED_FILE_DIR': (self.IntermediatesGroup, True),
2608 'SHARED_INTERMEDIATE_DIR': (self.IntermediatesGroup, True),
2611 (source_tree, path) = SourceTreeAndPathFromPath(path)
2612 if source_tree != None and source_tree in source_tree_groups:
2613 (group_func, hierarchical) = source_tree_groups[source_tree]
2614 group = group_func()
2615 return (group, hierarchical)
2617 # TODO(mark): make additional choices based on file extension.
2619 return (self.SourceGroup(), True)
2621 def AddOrGetFileInRootGroup(self, path):
2622 """Returns a PBXFileReference corresponding to path in the correct group
2623 according to RootGroupForPath's heuristics.
2625 If an existing PBXFileReference for path exists, it will be returned.
2626 Otherwise, one will be created and returned.
2629 (group, hierarchical) = self.RootGroupForPath(path)
2630 return group.AddOrGetFileByPath(path, hierarchical)
2632 def RootGroupsTakeOverOnlyChildren(self, recurse=False):
2633 """Calls TakeOverOnlyChild for all groups in the main group."""
2635 for group in self._properties['mainGroup']._properties['children']:
2636 if isinstance(group, PBXGroup):
2637 group.TakeOverOnlyChild(recurse)
2639 def SortGroups(self):
2640 # Sort the children of the mainGroup (like "Source" and "Products")
2641 # according to their defined order.
2642 self._properties['mainGroup']._properties['children'] = \
2643 sorted(self._properties['mainGroup']._properties['children'],
2644 cmp=lambda x,y: x.CompareRootGroup(y))
2646 # Sort everything else by putting group before files, and going
2647 # alphabetically by name within sections of groups and files. SortGroup
2649 for group in self._properties['mainGroup']._properties['children']:
2650 if not isinstance(group, PBXGroup):
2653 if group.Name() == 'Products':
2654 # The Products group is a special case. Instead of sorting
2655 # alphabetically, sort things in the order of the targets that
2656 # produce the products. To do this, just build up a new list of
2657 # products based on the targets.
2659 for target in self._properties['targets']:
2660 if not isinstance(target, PBXNativeTarget):
2662 product = target._properties['productReference']
2663 # Make sure that the product is already in the products group.
2664 assert product in group._properties['children']
2665 products.append(product)
2667 # Make sure that this process doesn't miss anything that was already
2668 # in the products group.
2669 assert len(products) == len(group._properties['children'])
2670 group._properties['children'] = products
2674 def AddOrGetProjectReference(self, other_pbxproject):
2675 """Add a reference to another project file (via PBXProject object) to this
2678 Returns [ProductGroup, ProjectRef]. ProductGroup is a PBXGroup object in
2679 this project file that contains a PBXReferenceProxy object for each
2680 product of each PBXNativeTarget in the other project file. ProjectRef is
2681 a PBXFileReference to the other project file.
2683 If this project file already references the other project file, the
2684 existing ProductGroup and ProjectRef are returned. The ProductGroup will
2685 still be updated if necessary.
2688 if not 'projectReferences' in self._properties:
2689 self._properties['projectReferences'] = []
2691 product_group = None
2694 if not other_pbxproject in self._other_pbxprojects:
2695 # This project file isn't yet linked to the other one. Establish the
2697 product_group = PBXGroup({'name': 'Products'})
2699 # ProductGroup is strong.
2700 product_group.parent = self
2702 # There's nothing unique about this PBXGroup, and if left alone, it will
2703 # wind up with the same set of hashables as all other PBXGroup objects
2704 # owned by the projectReferences list. Add the hashables of the
2705 # remote PBXProject that it's related to.
2706 product_group._hashables.extend(other_pbxproject.Hashables())
2708 # The other project reports its path as relative to the same directory
2709 # that this project's path is relative to. The other project's path
2710 # is not necessarily already relative to this project. Figure out the
2711 # pathname that this project needs to use to refer to the other one.
2712 this_path = posixpath.dirname(self.Path())
2713 projectDirPath = self.GetProperty('projectDirPath')
2715 if posixpath.isabs(projectDirPath[0]):
2716 this_path = projectDirPath
2718 this_path = posixpath.join(this_path, projectDirPath)
2719 other_path = gyp.common.RelativePath(other_pbxproject.Path(), this_path)
2721 # ProjectRef is weak (it's owned by the mainGroup hierarchy).
2722 project_ref = PBXFileReference({
2723 'lastKnownFileType': 'wrapper.pb-project',
2725 'sourceTree': 'SOURCE_ROOT',
2727 self.ProjectsGroup().AppendChild(project_ref)
2729 ref_dict = {'ProductGroup': product_group, 'ProjectRef': project_ref}
2730 self._other_pbxprojects[other_pbxproject] = ref_dict
2731 self.AppendProperty('projectReferences', ref_dict)
2733 # Xcode seems to sort this list case-insensitively
2734 self._properties['projectReferences'] = \
2735 sorted(self._properties['projectReferences'], cmp=lambda x,y:
2736 cmp(x['ProjectRef'].Name().lower(),
2737 y['ProjectRef'].Name().lower()))
2739 # The link already exists. Pull out the relevnt data.
2740 project_ref_dict = self._other_pbxprojects[other_pbxproject]
2741 product_group = project_ref_dict['ProductGroup']
2742 project_ref = project_ref_dict['ProjectRef']
2744 self._SetUpProductReferences(other_pbxproject, product_group, project_ref)
2746 inherit_unique_symroot = self._AllSymrootsUnique(other_pbxproject, False)
2747 targets = other_pbxproject.GetProperty('targets')
2748 if all(self._AllSymrootsUnique(t, inherit_unique_symroot) for t in targets):
2749 dir_path = project_ref._properties['path']
2750 product_group._hashables.extend(dir_path)
2752 return [product_group, project_ref]
2754 def _AllSymrootsUnique(self, target, inherit_unique_symroot):
2755 # Returns True if all configurations have a unique 'SYMROOT' attribute.
2756 # The value of inherit_unique_symroot decides, if a configuration is assumed
2757 # to inherit a unique 'SYMROOT' attribute from its parent, if it doesn't
2758 # define an explicit value for 'SYMROOT'.
2759 symroots = self._DefinedSymroots(target)
2760 for s in self._DefinedSymroots(target):
2761 if (s is not None and not self._IsUniqueSymrootForTarget(s) or
2762 s is None and not inherit_unique_symroot):
2764 return True if symroots else inherit_unique_symroot
2766 def _DefinedSymroots(self, target):
2767 # Returns all values for the 'SYMROOT' attribute defined in all
2768 # configurations for this target. If any configuration doesn't define the
2769 # 'SYMROOT' attribute, None is added to the returned set. If all
2770 # configurations don't define the 'SYMROOT' attribute, an empty set is
2772 config_list = target.GetProperty('buildConfigurationList')
2774 for config in config_list.GetProperty('buildConfigurations'):
2775 setting = config.GetProperty('buildSettings')
2776 if 'SYMROOT' in setting:
2777 symroots.add(setting['SYMROOT'])
2780 if len(symroots) == 1 and None in symroots:
2784 def _IsUniqueSymrootForTarget(self, symroot):
2785 # This method returns True if all configurations in target contain a
2786 # 'SYMROOT' attribute that is unique for the given target. A value is
2787 # unique, if the Xcode macro '$SRCROOT' appears in it in any form.
2788 uniquifier = ['$SRCROOT', '$(SRCROOT)']
2789 if any(x in symroot for x in uniquifier):
2793 def _SetUpProductReferences(self, other_pbxproject, product_group,
2795 # TODO(mark): This only adds references to products in other_pbxproject
2796 # when they don't exist in this pbxproject. Perhaps it should also
2797 # remove references from this pbxproject that are no longer present in
2798 # other_pbxproject. Perhaps it should update various properties if they
2800 for target in other_pbxproject._properties['targets']:
2801 if not isinstance(target, PBXNativeTarget):
2804 other_fileref = target._properties['productReference']
2805 if product_group.GetChildByRemoteObject(other_fileref) is None:
2806 # Xcode sets remoteInfo to the name of the target and not the name
2807 # of its product, despite this proxy being a reference to the product.
2808 container_item = PBXContainerItemProxy({
2809 'containerPortal': project_ref,
2811 'remoteGlobalIDString': other_fileref,
2812 'remoteInfo': target.Name()
2814 # TODO(mark): Does sourceTree get copied straight over from the other
2815 # project? Can the other project ever have lastKnownFileType here
2816 # instead of explicitFileType? (Use it if so?) Can path ever be
2817 # unset? (I don't think so.) Can other_fileref have name set, and
2818 # does it impact the PBXReferenceProxy if so? These are the questions
2819 # that perhaps will be answered one day.
2820 reference_proxy = PBXReferenceProxy({
2821 'fileType': other_fileref._properties['explicitFileType'],
2822 'path': other_fileref._properties['path'],
2823 'sourceTree': other_fileref._properties['sourceTree'],
2824 'remoteRef': container_item,
2827 product_group.AppendChild(reference_proxy)
2829 def SortRemoteProductReferences(self):
2830 # For each remote project file, sort the associated ProductGroup in the
2831 # same order that the targets are sorted in the remote project file. This
2832 # is the sort order used by Xcode.
2834 def CompareProducts(x, y, remote_products):
2835 # x and y are PBXReferenceProxy objects. Go through their associated
2836 # PBXContainerItem to get the remote PBXFileReference, which will be
2837 # present in the remote_products list.
2838 x_remote = x._properties['remoteRef']._properties['remoteGlobalIDString']
2839 y_remote = y._properties['remoteRef']._properties['remoteGlobalIDString']
2840 x_index = remote_products.index(x_remote)
2841 y_index = remote_products.index(y_remote)
2843 # Use the order of each remote PBXFileReference in remote_products to
2844 # determine the sort order.
2845 return cmp(x_index, y_index)
2847 for other_pbxproject, ref_dict in self._other_pbxprojects.iteritems():
2848 # Build up a list of products in the remote project file, ordered the
2849 # same as the targets that produce them.
2850 remote_products = []
2851 for target in other_pbxproject._properties['targets']:
2852 if not isinstance(target, PBXNativeTarget):
2854 remote_products.append(target._properties['productReference'])
2856 # Sort the PBXReferenceProxy children according to the list of remote
2858 product_group = ref_dict['ProductGroup']
2859 product_group._properties['children'] = sorted(
2860 product_group._properties['children'],
2861 cmp=lambda x, y, rp=remote_products: CompareProducts(x, y, rp))
2864 class XCProjectFile(XCObject):
2865 _schema = XCObject._schema.copy()
2867 'archiveVersion': [0, int, 0, 1, 1],
2868 'classes': [0, dict, 0, 1, {}],
2869 'objectVersion': [0, int, 0, 1, 46],
2870 'rootObject': [0, PBXProject, 1, 1],
2873 def ComputeIDs(self, recursive=True, overwrite=True, hash=None):
2874 # Although XCProjectFile is implemented here as an XCObject, it's not a
2875 # proper object in the Xcode sense, and it certainly doesn't have its own
2876 # ID. Pass through an attempt to update IDs to the real root object.
2878 self._properties['rootObject'].ComputeIDs(recursive, overwrite, hash)
2880 def Print(self, file=sys.stdout):
2881 self.VerifyHasRequiredProperties()
2883 # Add the special "objects" property, which will be caught and handled
2884 # separately during printing. This structure allows a fairly standard
2885 # loop do the normal printing.
2886 self._properties['objects'] = {}
2887 self._XCPrint(file, 0, '// !$*UTF8*$!\n')
2888 if self._should_print_single_line:
2889 self._XCPrint(file, 0, '{ ')
2891 self._XCPrint(file, 0, '{\n')
2892 for property, value in sorted(self._properties.iteritems(),
2893 cmp=lambda x, y: cmp(x, y)):
2894 if property == 'objects':
2895 self._PrintObjects(file)
2897 self._XCKVPrint(file, 1, property, value)
2898 self._XCPrint(file, 0, '}\n')
2899 del self._properties['objects']
2901 def _PrintObjects(self, file):
2902 if self._should_print_single_line:
2903 self._XCPrint(file, 0, 'objects = {')
2905 self._XCPrint(file, 1, 'objects = {\n')
2907 objects_by_class = {}
2908 for object in self.Descendants():
2911 class_name = object.__class__.__name__
2912 if not class_name in objects_by_class:
2913 objects_by_class[class_name] = []
2914 objects_by_class[class_name].append(object)
2916 for class_name in sorted(objects_by_class):
2917 self._XCPrint(file, 0, '\n')
2918 self._XCPrint(file, 0, '/* Begin ' + class_name + ' section */\n')
2919 for object in sorted(objects_by_class[class_name],
2920 cmp=lambda x, y: cmp(x.id, y.id)):
2922 self._XCPrint(file, 0, '/* End ' + class_name + ' section */\n')
2924 if self._should_print_single_line:
2925 self._XCPrint(file, 0, '}; ')
2927 self._XCPrint(file, 1, '};\n')