Home | History | Annotate | Download | only in gyp
      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.
      4 
      5 """Xcode project file generator.
      6 
      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.
     11 
     12 XCODE PROJECT FILES
     13 
     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.
     18 
     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
     26 application.
     27 
     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.
     37 
     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
     49 base named XCTarget.
     50 
     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
     54 objects dictionary.
     55 
     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
     61 actual files.
     62 
     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.
     79 
     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
     86 application.
     87 
     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.
    103 
    104 THIS MODULE
    105 
    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.
    112 
    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:
    115 
    116   config_list = XCConfigurationList()
    117   group = PBXGroup()
    118   project = PBXProject({'buildConfigurationList': config_list,
    119                         'mainGroup': group})
    120 
    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.
    127 
    128   project_file = XCProjectFile({'rootObject': project})
    129   project_file.ComputeIDs()
    130   project_file.Print()
    131 
    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.
    138 """
    139 
    140 import gyp.common
    141 import posixpath
    142 import re
    143 import struct
    144 import sys
    145 
    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.
    150 try:
    151   import hashlib
    152   _new_sha1 = hashlib.sha1
    153 except ImportError:
    154   import sha
    155   _new_sha1 = sha.new
    156 
    157 
    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$./_]+$')
    165 
    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('___')
    169 
    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]')
    173 
    174 
    175 # Used by SourceTreeAndPathFromPath
    176 _path_leading_variable = re.compile('^\$\((.*?)\)(/(.*))?$')
    177 
    178 def SourceTreeAndPathFromPath(input_path):
    179   """Given input_path, returns a tuple with sourceTree and path values.
    180 
    181   Examples:
    182     input_path     (source_tree, output_path)
    183     '$(VAR)/path'  ('VAR', 'path')
    184     '$(VAR)'       ('VAR', None)
    185     'path'         (None, 'path')
    186   """
    187 
    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.
    192   else:
    193     source_tree = None
    194     output_path = input_path
    195 
    196   return (source_tree, output_path)
    197 
    198 def ConvertVariablesToShellSyntax(input_string):
    199   return re.sub('\$\((.*?)\)', '${\\1}', input_string)
    200 
    201 class XCObject(object):
    202   """The abstract base of all class types used in Xcode project files.
    203 
    204   Class variables:
    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
    210                       to a single element.
    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
    233              horizontal space.
    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
    238                                set this to True.
    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
    244                         XCObject subclasses.
    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
    256                 to _hashables.
    257   Attributes:
    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.
    267   """
    268 
    269   _schema = {}
    270   _should_print_single_line = False
    271 
    272   # See _EncodeString.
    273   _encode_transforms = []
    274   i = 0
    275   while i < ord(' '):
    276     _encode_transforms.append('\\U%04x' % i)
    277     i = i + 1
    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'
    285 
    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)
    290 
    291   def __init__(self, properties=None, id=None, parent=None):
    292     self.id = id
    293     self.parent = parent
    294     self._properties = {}
    295     self._hashables = []
    296     self._SetDefaultsFromSchema()
    297     self.UpdateProperties(properties)
    298 
    299   def __repr__(self):
    300     try:
    301       name = self.Name()
    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))
    305 
    306   def Copy(self):
    307     """Make a copy of this object.
    308 
    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.
    314     """
    315 
    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]
    319 
    320       if isinstance(value, XCObject):
    321         if is_strong:
    322           new_value = value.Copy()
    323           new_value.parent = that
    324           that._properties[key] = new_value
    325         else:
    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):
    331         if is_strong:
    332           # If is_strong is True, each element is an XCObject, so it's safe to
    333           # call Copy.
    334           that._properties[key] = []
    335           for item in value:
    336             new_item = item.Copy()
    337             new_item.parent = that
    338             that._properties[key].append(new_item)
    339         else:
    340           that._properties[key] = value[:]
    341       elif isinstance(value, dict):
    342         # dicts are never strong.
    343         if is_strong:
    344           raise TypeError, 'Strong dict for key ' + key + ' in ' + \
    345                            self.__class__.__name__
    346         else:
    347           that._properties[key] = value.copy()
    348       else:
    349         raise TypeError, 'Unexpected type ' + value.__class__.__name__ + \
    350                          ' for key ' + key + ' in ' + self.__class__.__name__
    351 
    352     return that
    353 
    354   def Name(self):
    355     """Return the name corresponding to an object.
    356 
    357     Not all objects necessarily need to be nameable, and not all that do have
    358     a "name" property.  Override as needed.
    359     """
    360 
    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']
    368 
    369     raise NotImplementedError, \
    370           self.__class__.__name__ + ' must implement Name'
    371 
    372   def Comment(self):
    373     """Return a comment string for the object.
    374 
    375     Most objects just use their name as the comment, but PBXProject uses
    376     different values.
    377 
    378     The returned comment is not escaped and does not have any comment marker
    379     strings applied to it.
    380     """
    381 
    382     return self.Name()
    383 
    384   def Hashables(self):
    385     hashables = [self.__class__.__name__]
    386 
    387     name = self.Name()
    388     if name != None:
    389       hashables.append(name)
    390 
    391     hashables.extend(self._hashables)
    392 
    393     return hashables
    394 
    395   def HashablesForChild(self):
    396     return None
    397 
    398   def ComputeIDs(self, recursive=True, overwrite=True, seed_hash=None):
    399     """Set "id" properties deterministically.
    400 
    401     An object's "id" property is set based on a hash of its class type and
    402     name, as well as the class type and name of all ancestor objects.  As
    403     such, it is only advisable to call ComputeIDs once an entire project file
    404     tree is built.
    405 
    406     If recursive is True, recurse into all descendant objects and update their
    407     hashes.
    408 
    409     If overwrite is True, any existing value set in the "id" property will be
    410     replaced.
    411     """
    412 
    413     def _HashUpdate(hash, data):
    414       """Update hash with data's length and contents.
    415 
    416       If the hash were updated only with the value of data, it would be
    417       possible for clowns to induce collisions by manipulating the names of
    418       their objects.  By adding the length, it's exceedingly less likely that
    419       ID collisions will be encountered, intentionally or not.
    420       """
    421 
    422       hash.update(struct.pack('>i', len(data)))
    423       hash.update(data)
    424 
    425     if seed_hash is None:
    426       seed_hash = _new_sha1()
    427 
    428     hash = seed_hash.copy()
    429 
    430     hashables = self.Hashables()
    431     assert len(hashables) > 0
    432     for hashable in hashables:
    433       _HashUpdate(hash, hashable)
    434 
    435     if recursive:
    436       hashables_for_child = self.HashablesForChild()
    437       if hashables_for_child is None:
    438         child_hash = hash
    439       else:
    440         assert len(hashables_for_child) > 0
    441         child_hash = seed_hash.copy()
    442         for hashable in hashables_for_child:
    443           _HashUpdate(child_hash, hashable)
    444 
    445       for child in self.Children():
    446         child.ComputeIDs(recursive, overwrite, child_hash)
    447 
    448     if overwrite or self.id is None:
    449       # Xcode IDs are only 96 bits (24 hex characters), but a SHA-1 digest is
    450       # is 160 bits.  Instead of throwing out 64 bits of the digest, xor them
    451       # into the portion that gets used.
    452       assert hash.digest_size % 4 == 0
    453       digest_int_count = hash.digest_size / 4
    454       digest_ints = struct.unpack('>' + 'I' * digest_int_count, hash.digest())
    455       id_ints = [0, 0, 0]
    456       for index in xrange(0, digest_int_count):
    457         id_ints[index % 3] ^= digest_ints[index]
    458       self.id = '%08X%08X%08X' % tuple(id_ints)
    459 
    460   def EnsureNoIDCollisions(self):
    461     """Verifies that no two objects have the same ID.  Checks all descendants.
    462     """
    463 
    464     ids = {}
    465     descendants = self.Descendants()
    466     for descendant in descendants:
    467       if descendant.id in ids:
    468         other = ids[descendant.id]
    469         raise KeyError, \
    470               'Duplicate ID %s, objects "%s" and "%s" in "%s"' % \
    471               (descendant.id, str(descendant._properties),
    472                str(other._properties), self._properties['rootObject'].Name())
    473       ids[descendant.id] = descendant
    474 
    475   def Children(self):
    476     """Returns a list of all of this object's owned (strong) children."""
    477 
    478     children = []
    479     for property, attributes in self._schema.iteritems():
    480       (is_list, property_type, is_strong) = attributes[0:3]
    481       if is_strong and property in self._properties:
    482         if not is_list:
    483           children.append(self._properties[property])
    484         else:
    485           children.extend(self._properties[property])
    486     return children
    487 
    488   def Descendants(self):
    489     """Returns a list of all of this object's descendants, including this
    490     object.
    491     """
    492 
    493     children = self.Children()
    494     descendants = [self]
    495     for child in children:
    496       descendants.extend(child.Descendants())
    497     return descendants
    498 
    499   def PBXProjectAncestor(self):
    500     # The base case for recursion is defined at PBXProject.PBXProjectAncestor.
    501     if self.parent:
    502       return self.parent.PBXProjectAncestor()
    503     return None
    504 
    505   def _EncodeComment(self, comment):
    506     """Encodes a comment to be placed in the project file output, mimicing
    507     Xcode behavior.
    508     """
    509 
    510     # This mimics Xcode behavior by wrapping the comment in "/*" and "*/".  If
    511     # the string already contains a "*/", it is turned into "(*)/".  This keeps
    512     # the file writer from outputting something that would be treated as the
    513     # end of a comment in the middle of something intended to be entirely a
    514     # comment.
    515 
    516     return '/* ' + comment.replace('*/', '(*)/') + ' */'
    517 
    518   def _EncodeTransform(self, match):
    519     # This function works closely with _EncodeString.  It will only be called
    520     # by re.sub with match.group(0) containing a character matched by the
    521     # the _escaped expression.
    522     char = match.group(0)
    523 
    524     # Backslashes (\) and quotation marks (") are always replaced with a
    525     # backslash-escaped version of the same.  Everything else gets its
    526     # replacement from the class' _encode_transforms array.
    527     if char == '\\':
    528       return '\\\\'
    529     if char == '"':
    530       return '\\"'
    531     return self._encode_transforms[ord(char)]
    532 
    533   def _EncodeString(self, value):
    534     """Encodes a string to be placed in the project file output, mimicing
    535     Xcode behavior.
    536     """
    537 
    538     # Use quotation marks when any character outside of the range A-Z, a-z, 0-9,
    539     # $ (dollar sign), . (period), and _ (underscore) is present.  Also use
    540     # quotation marks to represent empty strings.
    541     #
    542     # Escape " (double-quote) and \ (backslash) by preceding them with a
    543     # backslash.
    544     #
    545     # Some characters below the printable ASCII range are encoded specially:
    546     #     7 ^G BEL is encoded as "\a"
    547     #     8 ^H BS  is encoded as "\b"
    548     #    11 ^K VT  is encoded as "\v"
    549     #    12 ^L NP  is encoded as "\f"
    550     #   127 ^? DEL is passed through as-is without escaping
    551     #  - In PBXFileReference and PBXBuildFile objects:
    552     #     9 ^I HT  is passed through as-is without escaping
    553     #    10 ^J NL  is passed through as-is without escaping
    554     #    13 ^M CR  is passed through as-is without escaping
    555     #  - In other objects:
    556     #     9 ^I HT  is encoded as "\t"
    557     #    10 ^J NL  is encoded as "\n"
    558     #    13 ^M CR  is encoded as "\n" rendering it indistinguishable from
    559     #              10 ^J NL
    560     # All other characters within the ASCII control character range (0 through
    561     # 31 inclusive) are encoded as "\U001f" referring to the Unicode code point
    562     # in hexadecimal.  For example, character 14 (^N SO) is encoded as "\U000e".
    563     # Characters above the ASCII range are passed through to the output encoded
    564     # as UTF-8 without any escaping.  These mappings are contained in the
    565     # class' _encode_transforms list.
    566 
    567     if _unquoted.search(value) and not _quoted.search(value):
    568       return value
    569 
    570     return '"' + _escaped.sub(self._EncodeTransform, value) + '"'
    571 
    572   def _XCPrint(self, file, tabs, line):
    573     file.write('\t' * tabs + line)
    574 
    575   def _XCPrintableValue(self, tabs, value, flatten_list=False):
    576     """Returns a representation of value that may be printed in a project file,
    577     mimicing Xcode's behavior.
    578 
    579     _XCPrintableValue can handle str and int values, XCObjects (which are
    580     made printable by returning their id property), and list and dict objects
    581     composed of any of the above types.  When printing a list or dict, and
    582     _should_print_single_line is False, the tabs parameter is used to determine
    583     how much to indent the lines corresponding to the items in the list or
    584     dict.
    585 
    586     If flatten_list is True, single-element lists will be transformed into
    587     strings.
    588     """
    589 
    590     printable = ''
    591     comment = None
    592 
    593     if self._should_print_single_line:
    594       sep = ' '
    595       element_tabs = ''
    596       end_tabs = ''
    597     else:
    598       sep = '\n'
    599       element_tabs = '\t' * (tabs + 1)
    600       end_tabs = '\t' * tabs
    601 
    602     if isinstance(value, XCObject):
    603       printable += value.id
    604       comment = value.Comment()
    605     elif isinstance(value, str):
    606       printable += self._EncodeString(value)
    607     elif isinstance(value, unicode):
    608       printable += self._EncodeString(value.encode('utf-8'))
    609     elif isinstance(value, int):
    610       printable += str(value)
    611     elif isinstance(value, list):
    612       if flatten_list and len(value) <= 1:
    613         if len(value) == 0:
    614           printable += self._EncodeString('')
    615         else:
    616           printable += self._EncodeString(value[0])
    617       else:
    618         printable = '(' + sep
    619         for item in value:
    620           printable += element_tabs + \
    621                        self._XCPrintableValue(tabs + 1, item, flatten_list) + \
    622                        ',' + sep
    623         printable += end_tabs + ')'
    624     elif isinstance(value, dict):
    625       printable = '{' + sep
    626       for item_key, item_value in sorted(value.iteritems()):
    627         printable += element_tabs + \
    628             self._XCPrintableValue(tabs + 1, item_key, flatten_list) + ' = ' + \
    629             self._XCPrintableValue(tabs + 1, item_value, flatten_list) + ';' + \
    630             sep
    631       printable += end_tabs + '}'
    632     else:
    633       raise TypeError, "Can't make " + value.__class__.__name__ + ' printable'
    634 
    635     if comment != None:
    636       printable += ' ' + self._EncodeComment(comment)
    637 
    638     return printable
    639 
    640   def _XCKVPrint(self, file, tabs, key, value):
    641     """Prints a key and value, members of an XCObject's _properties dictionary,
    642     to file.
    643 
    644     tabs is an int identifying the indentation level.  If the class'
    645     _should_print_single_line variable is True, tabs is ignored and the
    646     key-value pair will be followed by a space insead of a newline.
    647     """
    648 
    649     if self._should_print_single_line:
    650       printable = ''
    651       after_kv = ' '
    652     else:
    653       printable = '\t' * tabs
    654       after_kv = '\n'
    655 
    656     # Xcode usually prints remoteGlobalIDString values in PBXContainerItemProxy
    657     # objects without comments.  Sometimes it prints them with comments, but
    658     # the majority of the time, it doesn't.  To avoid unnecessary changes to
    659     # the project file after Xcode opens it, don't write comments for
    660     # remoteGlobalIDString.  This is a sucky hack and it would certainly be
    661     # cleaner to extend the schema to indicate whether or not a comment should
    662     # be printed, but since this is the only case where the problem occurs and
    663     # Xcode itself can't seem to make up its mind, the hack will suffice.
    664     #
    665     # Also see PBXContainerItemProxy._schema['remoteGlobalIDString'].
    666     if key == 'remoteGlobalIDString' and isinstance(self,
    667                                                     PBXContainerItemProxy):
    668       value_to_print = value.id
    669     else:
    670       value_to_print = value
    671 
    672     # PBXBuildFile's settings property is represented in the output as a dict,
    673     # but a hack here has it represented as a string. Arrange to strip off the
    674     # quotes so that it shows up in the output as expected.
    675     if key == 'settings' and isinstance(self, PBXBuildFile):
    676       strip_value_quotes = True
    677     else:
    678       strip_value_quotes = False
    679 
    680     # In another one-off, let's set flatten_list on buildSettings properties
    681     # of XCBuildConfiguration objects, because that's how Xcode treats them.
    682     if key == 'buildSettings' and isinstance(self, XCBuildConfiguration):
    683       flatten_list = True
    684     else:
    685       flatten_list = False
    686 
    687     try:
    688       printable_key = self._XCPrintableValue(tabs, key, flatten_list)
    689       printable_value = self._XCPrintableValue(tabs, value_to_print,
    690                                                flatten_list)
    691       if strip_value_quotes and len(printable_value) > 1 and \
    692           printable_value[0] == '"' and printable_value[-1] == '"':
    693         printable_value = printable_value[1:-1]
    694       printable += printable_key + ' = ' + printable_value + ';' + after_kv
    695     except TypeError, e:
    696       gyp.common.ExceptionAppend(e,
    697                                  'while printing key "%s"' % key)
    698       raise
    699 
    700     self._XCPrint(file, 0, printable)
    701 
    702   def Print(self, file=sys.stdout):
    703     """Prints a reprentation of this object to file, adhering to Xcode output
    704     formatting.
    705     """
    706 
    707     self.VerifyHasRequiredProperties()
    708 
    709     if self._should_print_single_line:
    710       # When printing an object in a single line, Xcode doesn't put any space
    711       # between the beginning of a dictionary (or presumably a list) and the
    712       # first contained item, so you wind up with snippets like
    713       #   ...CDEF = {isa = PBXFileReference; fileRef = 0123...
    714       # If it were me, I would have put a space in there after the opening
    715       # curly, but I guess this is just another one of those inconsistencies
    716       # between how Xcode prints PBXFileReference and PBXBuildFile objects as
    717       # compared to other objects.  Mimic Xcode's behavior here by using an
    718       # empty string for sep.
    719       sep = ''
    720       end_tabs = 0
    721     else:
    722       sep = '\n'
    723       end_tabs = 2
    724 
    725     # Start the object.  For example, '\t\tPBXProject = {\n'.
    726     self._XCPrint(file, 2, self._XCPrintableValue(2, self) + ' = {' + sep)
    727 
    728     # "isa" isn't in the _properties dictionary, it's an intrinsic property
    729     # of the class which the object belongs to.  Xcode always outputs "isa"
    730     # as the first element of an object dictionary.
    731     self._XCKVPrint(file, 3, 'isa', self.__class__.__name__)
    732 
    733     # The remaining elements of an object dictionary are sorted alphabetically.
    734     for property, value in sorted(self._properties.iteritems()):
    735       self._XCKVPrint(file, 3, property, value)
    736 
    737     # End the object.
    738     self._XCPrint(file, end_tabs, '};\n')
    739 
    740   def UpdateProperties(self, properties, do_copy=False):
    741     """Merge the supplied properties into the _properties dictionary.
    742 
    743     The input properties must adhere to the class schema or a KeyError or
    744     TypeError exception will be raised.  If adding an object of an XCObject
    745     subclass and the schema indicates a strong relationship, the object's
    746     parent will be set to this object.
    747 
    748     If do_copy is True, then lists, dicts, strong-owned XCObjects, and
    749     strong-owned XCObjects in lists will be copied instead of having their
    750     references added.
    751     """
    752 
    753     if properties is None:
    754       return
    755 
    756     for property, value in properties.iteritems():
    757       # Make sure the property is in the schema.
    758       if not property in self._schema:
    759         raise KeyError, property + ' not in ' + self.__class__.__name__
    760 
    761       # Make sure the property conforms to the schema.
    762       (is_list, property_type, is_strong) = self._schema[property][0:3]
    763       if is_list:
    764         if value.__class__ != list:
    765           raise TypeError, \
    766                 property + ' of ' + self.__class__.__name__ + \
    767                 ' must be list, not ' + value.__class__.__name__
    768         for item in value:
    769           if not isinstance(item, property_type) and \
    770              not (item.__class__ == unicode and property_type == str):
    771             # Accept unicode where str is specified.  str is treated as
    772             # UTF-8-encoded.
    773             raise TypeError, \
    774                   'item of ' + property + ' of ' + self.__class__.__name__ + \
    775                   ' must be ' + property_type.__name__ + ', not ' + \
    776                   item.__class__.__name__
    777       elif not isinstance(value, property_type) and \
    778            not (value.__class__ == unicode and property_type == str):
    779         # Accept unicode where str is specified.  str is treated as
    780         # UTF-8-encoded.
    781         raise TypeError, \
    782               property + ' of ' + self.__class__.__name__ + ' must be ' + \
    783               property_type.__name__ + ', not ' + value.__class__.__name__
    784 
    785       # Checks passed, perform the assignment.
    786       if do_copy:
    787         if isinstance(value, XCObject):
    788           if is_strong:
    789             self._properties[property] = value.Copy()
    790           else:
    791             self._properties[property] = value
    792         elif isinstance(value, str) or isinstance(value, unicode) or \
    793              isinstance(value, int):
    794           self._properties[property] = value
    795         elif isinstance(value, list):
    796           if is_strong:
    797             # If is_strong is True, each element is an XCObject, so it's safe
    798             # to call Copy.
    799             self._properties[property] = []
    800             for item in value:
    801               self._properties[property].append(item.Copy())
    802           else:
    803             self._properties[property] = value[:]
    804         elif isinstance(value, dict):
    805           self._properties[property] = value.copy()
    806         else:
    807           raise TypeError, "Don't know how to copy a " + \
    808                            value.__class__.__name__ + ' object for ' + \
    809                            property + ' in ' + self.__class__.__name__
    810       else:
    811         self._properties[property] = value
    812 
    813       # Set up the child's back-reference to this object.  Don't use |value|
    814       # any more because it may not be right if do_copy is true.
    815       if is_strong:
    816         if not is_list:
    817           self._properties[property].parent = self
    818         else:
    819           for item in self._properties[property]:
    820             item.parent = self
    821 
    822   def HasProperty(self, key):
    823     return key in self._properties
    824 
    825   def GetProperty(self, key):
    826     return self._properties[key]
    827 
    828   def SetProperty(self, key, value):
    829     self.UpdateProperties({key: value})
    830 
    831   def DelProperty(self, key):
    832     if key in self._properties:
    833       del self._properties[key]
    834 
    835   def AppendProperty(self, key, value):
    836     # TODO(mark): Support ExtendProperty too (and make this call that)?
    837 
    838     # Schema validation.
    839     if not key in self._schema:
    840       raise KeyError, key + ' not in ' + self.__class__.__name__
    841 
    842     (is_list, property_type, is_strong) = self._schema[key][0:3]
    843     if not is_list:
    844       raise TypeError, key + ' of ' + self.__class__.__name__ + ' must be list'
    845     if not isinstance(value, property_type):
    846       raise TypeError, 'item of ' + key + ' of ' + self.__class__.__name__ + \
    847                        ' must be ' + property_type.__name__ + ', not ' + \
    848                        value.__class__.__name__
    849 
    850     # If the property doesn't exist yet, create a new empty list to receive the
    851     # item.
    852     if not key in self._properties:
    853       self._properties[key] = []
    854 
    855     # Set up the ownership link.
    856     if is_strong:
    857       value.parent = self
    858 
    859     # Store the item.
    860     self._properties[key].append(value)
    861 
    862   def VerifyHasRequiredProperties(self):
    863     """Ensure that all properties identified as required by the schema are
    864     set.
    865     """
    866 
    867     # TODO(mark): A stronger verification mechanism is needed.  Some
    868     # subclasses need to perform validation beyond what the schema can enforce.
    869     for property, attributes in self._schema.iteritems():
    870       (is_list, property_type, is_strong, is_required) = attributes[0:4]
    871       if is_required and not property in self._properties:
    872         raise KeyError, self.__class__.__name__ + ' requires ' + property
    873 
    874   def _SetDefaultsFromSchema(self):
    875     """Assign object default values according to the schema.  This will not
    876     overwrite properties that have already been set."""
    877 
    878     defaults = {}
    879     for property, attributes in self._schema.iteritems():
    880       (is_list, property_type, is_strong, is_required) = attributes[0:4]
    881       if is_required and len(attributes) >= 5 and \
    882           not property in self._properties:
    883         default = attributes[4]
    884 
    885         defaults[property] = default
    886 
    887     if len(defaults) > 0:
    888       # Use do_copy=True so that each new object gets its own copy of strong
    889       # objects, lists, and dicts.
    890       self.UpdateProperties(defaults, do_copy=True)
    891 
    892 
    893 class XCHierarchicalElement(XCObject):
    894   """Abstract base for PBXGroup and PBXFileReference.  Not represented in a
    895   project file."""
    896 
    897   # TODO(mark): Do name and path belong here?  Probably so.
    898   # If path is set and name is not, name may have a default value.  Name will
    899   # be set to the basename of path, if the basename of path is different from
    900   # the full value of path.  If path is already just a leaf name, name will
    901   # not be set.
    902   _schema = XCObject._schema.copy()
    903   _schema.update({
    904     'comments':       [0, str, 0, 0],
    905     'fileEncoding':   [0, str, 0, 0],
    906     'includeInIndex': [0, int, 0, 0],
    907     'indentWidth':    [0, int, 0, 0],
    908     'lineEnding':     [0, int, 0, 0],
    909     'sourceTree':     [0, str, 0, 1, '<group>'],
    910     'tabWidth':       [0, int, 0, 0],
    911     'usesTabs':       [0, int, 0, 0],
    912     'wrapsLines':     [0, int, 0, 0],
    913   })
    914 
    915   def __init__(self, properties=None, id=None, parent=None):
    916     # super
    917     XCObject.__init__(self, properties, id, parent)
    918     if 'path' in self._properties and not 'name' in self._properties:
    919       path = self._properties['path']
    920       name = posixpath.basename(path)
    921       if name != '' and path != name:
    922         self.SetProperty('name', name)
    923 
    924     if 'path' in self._properties and \
    925         (not 'sourceTree' in self._properties or \
    926          self._properties['sourceTree'] == '<group>'):
    927       # If the pathname begins with an Xcode variable like "$(SDKROOT)/", take
    928       # the variable out and make the path be relative to that variable by
    929       # assigning the variable name as the sourceTree.
    930       (source_tree, path) = SourceTreeAndPathFromPath(self._properties['path'])
    931       if source_tree != None:
    932         self._properties['sourceTree'] = source_tree
    933       if path != None:
    934         self._properties['path'] = path
    935       if source_tree != None and path is None and \
    936          not 'name' in self._properties:
    937         # The path was of the form "$(SDKROOT)" with no path following it.
    938         # This object is now relative to that variable, so it has no path
    939         # attribute of its own.  It does, however, keep a name.
    940         del self._properties['path']
    941         self._properties['name'] = source_tree
    942 
    943   def Name(self):
    944     if 'name' in self._properties:
    945       return self._properties['name']
    946     elif 'path' in self._properties:
    947       return self._properties['path']
    948     else:
    949       # This happens in the case of the root PBXGroup.
    950       return None
    951 
    952   def Hashables(self):
    953     """Custom hashables for XCHierarchicalElements.
    954 
    955     XCHierarchicalElements are special.  Generally, their hashes shouldn't
    956     change if the paths don't change.  The normal XCObject implementation of
    957     Hashables adds a hashable for each object, which means that if
    958     the hierarchical structure changes (possibly due to changes caused when
    959     TakeOverOnlyChild runs and encounters slight changes in the hierarchy),
    960     the hashes will change.  For example, if a project file initially contains
    961     a/b/f1 and a/b becomes collapsed into a/b, f1 will have a single parent
    962     a/b.  If someone later adds a/f2 to the project file, a/b can no longer be
    963     collapsed, and f1 winds up with parent b and grandparent a.  That would
    964     be sufficient to change f1's hash.
    965 
    966     To counteract this problem, hashables for all XCHierarchicalElements except
    967     for the main group (which has neither a name nor a path) are taken to be
    968     just the set of path components.  Because hashables are inherited from
    969     parents, this provides assurance that a/b/f1 has the same set of hashables
    970     whether its parent is b or a/b.
    971 
    972     The main group is a special case.  As it is permitted to have no name or
    973     path, it is permitted to use the standard XCObject hash mechanism.  This
    974     is not considered a problem because there can be only one main group.
    975     """
    976 
    977     if self == self.PBXProjectAncestor()._properties['mainGroup']:
    978       # super
    979       return XCObject.Hashables(self)
    980 
    981     hashables = []
    982 
    983     # Put the name in first, ensuring that if TakeOverOnlyChild collapses
    984     # children into a top-level group like "Source", the name always goes
    985     # into the list of hashables without interfering with path components.
    986     if 'name' in self._properties:
    987       # Make it less likely for people to manipulate hashes by following the
    988       # pattern of always pushing an object type value onto the list first.
    989       hashables.append(self.__class__.__name__ + '.name')
    990       hashables.append(self._properties['name'])
    991 
    992     # NOTE: This still has the problem that if an absolute path is encountered,
    993     # including paths with a sourceTree, they'll still inherit their parents'
    994     # hashables, even though the paths aren't relative to their parents.  This
    995     # is not expected to be much of a problem in practice.
    996     path = self.PathFromSourceTreeAndPath()
    997     if path != None:
    998       components = path.split(posixpath.sep)
    999       for component in components:
   1000         hashables.append(self.__class__.__name__ + '.path')
   1001         hashables.append(component)
   1002 
   1003     hashables.extend(self._hashables)
   1004 
   1005     return hashables
   1006 
   1007   def Compare(self, other):
   1008     # Allow comparison of these types.  PBXGroup has the highest sort rank;
   1009     # PBXVariantGroup is treated as equal to PBXFileReference.
   1010     valid_class_types = {
   1011       PBXFileReference: 'file',
   1012       PBXGroup:         'group',
   1013       PBXVariantGroup:  'file',
   1014     }
   1015     self_type = valid_class_types[self.__class__]
   1016     other_type = valid_class_types[other.__class__]
   1017 
   1018     if self_type == other_type:
   1019       # If the two objects are of the same sort rank, compare their names.
   1020       return cmp(self.Name(), other.Name())
   1021 
   1022     # Otherwise, sort groups before everything else.
   1023     if self_type == 'group':
   1024       return -1
   1025     return 1
   1026 
   1027   def CompareRootGroup(self, other):
   1028     # This function should be used only to compare direct children of the
   1029     # containing PBXProject's mainGroup.  These groups should appear in the
   1030     # listed order.
   1031     # TODO(mark): "Build" is used by gyp.generator.xcode, perhaps the
   1032     # generator should have a way of influencing this list rather than having
   1033     # to hardcode for the generator here.
   1034     order = ['Source', 'Intermediates', 'Projects', 'Frameworks', 'Products',
   1035              'Build']
   1036 
   1037     # If the groups aren't in the listed order, do a name comparison.
   1038     # Otherwise, groups in the listed order should come before those that
   1039     # aren't.
   1040     self_name = self.Name()
   1041     other_name = other.Name()
   1042     self_in = isinstance(self, PBXGroup) and self_name in order
   1043     other_in = isinstance(self, PBXGroup) and other_name in order
   1044     if not self_in and not other_in:
   1045       return self.Compare(other)
   1046     if self_name in order and not other_name in order:
   1047       return -1
   1048     if other_name in order and not self_name in order:
   1049       return 1
   1050 
   1051     # If both groups are in the listed order, go by the defined order.
   1052     self_index = order.index(self_name)
   1053     other_index = order.index(other_name)
   1054     if self_index < other_index:
   1055       return -1
   1056     if self_index > other_index:
   1057       return 1
   1058     return 0
   1059 
   1060   def PathFromSourceTreeAndPath(self):
   1061     # Turn the object's sourceTree and path properties into a single flat
   1062     # string of a form comparable to the path parameter.  If there's a
   1063     # sourceTree property other than "<group>", wrap it in $(...) for the
   1064     # comparison.
   1065     components = []
   1066     if self._properties['sourceTree'] != '<group>':
   1067       components.append('$(' + self._properties['sourceTree'] + ')')
   1068     if 'path' in self._properties:
   1069       components.append(self._properties['path'])
   1070 
   1071     if len(components) > 0:
   1072       return posixpath.join(*components)
   1073 
   1074     return None
   1075 
   1076   def FullPath(self):
   1077     # Returns a full path to self relative to the project file, or relative
   1078     # to some other source tree.  Start with self, and walk up the chain of
   1079     # parents prepending their paths, if any, until no more parents are
   1080     # available (project-relative path) or until a path relative to some
   1081     # source tree is found.
   1082     xche = self
   1083     path = None
   1084     while isinstance(xche, XCHierarchicalElement) and \
   1085           (path is None or \
   1086            (not path.startswith('/') and not path.startswith('$'))):
   1087       this_path = xche.PathFromSourceTreeAndPath()
   1088       if this_path != None and path != None:
   1089         path = posixpath.join(this_path, path)
   1090       elif this_path != None:
   1091         path = this_path
   1092       xche = xche.parent
   1093 
   1094     return path
   1095 
   1096 
   1097 class PBXGroup(XCHierarchicalElement):
   1098   """
   1099   Attributes:
   1100     _children_by_path: Maps pathnames of children of this PBXGroup to the
   1101       actual child XCHierarchicalElement objects.
   1102     _variant_children_by_name_and_path: Maps (name, path) tuples of
   1103       PBXVariantGroup children to the actual child PBXVariantGroup objects.
   1104   """
   1105 
   1106   _schema = XCHierarchicalElement._schema.copy()
   1107   _schema.update({
   1108     'children': [1, XCHierarchicalElement, 1, 1, []],
   1109     'name':     [0, str,                   0, 0],
   1110     'path':     [0, str,                   0, 0],
   1111   })
   1112 
   1113   def __init__(self, properties=None, id=None, parent=None):
   1114     # super
   1115     XCHierarchicalElement.__init__(self, properties, id, parent)
   1116     self._children_by_path = {}
   1117     self._variant_children_by_name_and_path = {}
   1118     for child in self._properties.get('children', []):
   1119       self._AddChildToDicts(child)
   1120 
   1121   def Hashables(self):
   1122     # super
   1123     hashables = XCHierarchicalElement.Hashables(self)
   1124 
   1125     # It is not sufficient to just rely on name and parent to build a unique
   1126     # hashable : a node could have two child PBXGroup sharing a common name.
   1127     # To add entropy the hashable is enhanced with the names of all its
   1128     # children.
   1129     for child in self._properties.get('children', []):
   1130       child_name = child.Name()
   1131       if child_name != None:
   1132         hashables.append(child_name)
   1133 
   1134     return hashables
   1135 
   1136   def HashablesForChild(self):
   1137     # To avoid a circular reference the hashables used to compute a child id do
   1138     # not include the child names.
   1139     return XCHierarchicalElement.Hashables(self)
   1140 
   1141   def _AddChildToDicts(self, child):
   1142     # Sets up this PBXGroup object's dicts to reference the child properly.
   1143     child_path = child.PathFromSourceTreeAndPath()
   1144     if child_path:
   1145       if child_path in self._children_by_path:
   1146         raise ValueError, 'Found multiple children with path ' + child_path
   1147       self._children_by_path[child_path] = child
   1148 
   1149     if isinstance(child, PBXVariantGroup):
   1150       child_name = child._properties.get('name', None)
   1151       key = (child_name, child_path)
   1152       if key in self._variant_children_by_name_and_path:
   1153         raise ValueError, 'Found multiple PBXVariantGroup children with ' + \
   1154                           'name ' + str(child_name) + ' and path ' + \
   1155                           str(child_path)
   1156       self._variant_children_by_name_and_path[key] = child
   1157 
   1158   def AppendChild(self, child):
   1159     # Callers should use this instead of calling
   1160     # AppendProperty('children', child) directly because this function
   1161     # maintains the group's dicts.
   1162     self.AppendProperty('children', child)
   1163     self._AddChildToDicts(child)
   1164 
   1165   def GetChildByName(self, name):
   1166     # This is not currently optimized with a dict as GetChildByPath is because
   1167     # it has few callers.  Most callers probably want GetChildByPath.  This
   1168     # function is only useful to get children that have names but no paths,
   1169     # which is rare.  The children of the main group ("Source", "Products",
   1170     # etc.) is pretty much the only case where this likely to come up.
   1171     #
   1172     # TODO(mark): Maybe this should raise an error if more than one child is
   1173     # present with the same name.
   1174     if not 'children' in self._properties:
   1175       return None
   1176 
   1177     for child in self._properties['children']:
   1178       if child.Name() == name:
   1179         return child
   1180 
   1181     return None
   1182 
   1183   def GetChildByPath(self, path):
   1184     if not path:
   1185       return None
   1186 
   1187     if path in self._children_by_path:
   1188       return self._children_by_path[path]
   1189 
   1190     return None
   1191 
   1192   def GetChildByRemoteObject(self, remote_object):
   1193     # This method is a little bit esoteric.  Given a remote_object, which
   1194     # should be a PBXFileReference in another project file, this method will
   1195     # return this group's PBXReferenceProxy object serving as a local proxy
   1196     # for the remote PBXFileReference.
   1197     #
   1198     # This function might benefit from a dict optimization as GetChildByPath
   1199     # for some workloads, but profiling shows that it's not currently a
   1200     # problem.
   1201     if not 'children' in self._properties:
   1202       return None
   1203 
   1204     for child in self._properties['children']:
   1205       if not isinstance(child, PBXReferenceProxy):
   1206         continue
   1207 
   1208       container_proxy = child._properties['remoteRef']
   1209       if container_proxy._properties['remoteGlobalIDString'] == remote_object:
   1210         return child
   1211 
   1212     return None
   1213 
   1214   def AddOrGetFileByPath(self, path, hierarchical):
   1215     """Returns an existing or new file reference corresponding to path.
   1216 
   1217     If hierarchical is True, this method will create or use the necessary
   1218     hierarchical group structure corresponding to path.  Otherwise, it will
   1219     look in and create an item in the current group only.
   1220 
   1221     If an existing matching reference is found, it is returned, otherwise, a
   1222     new one will be created, added to the correct group, and returned.
   1223 
   1224     If path identifies a directory by virtue of carrying a trailing slash,
   1225     this method returns a PBXFileReference of "folder" type.  If path
   1226     identifies a variant, by virtue of it identifying a file inside a directory
   1227     with an ".lproj" extension, this method returns a PBXVariantGroup
   1228     containing the variant named by path, and possibly other variants.  For
   1229     all other paths, a "normal" PBXFileReference will be returned.
   1230     """
   1231 
   1232     # Adding or getting a directory?  Directories end with a trailing slash.
   1233     is_dir = False
   1234     if path.endswith('/'):
   1235       is_dir = True
   1236     path = posixpath.normpath(path)
   1237     if is_dir:
   1238       path = path + '/'
   1239 
   1240     # Adding or getting a variant?  Variants are files inside directories
   1241     # with an ".lproj" extension.  Xcode uses variants for localization.  For
   1242     # a variant path/to/Language.lproj/MainMenu.nib, put a variant group named
   1243     # MainMenu.nib inside path/to, and give it a variant named Language.  In
   1244     # this example, grandparent would be set to path/to and parent_root would
   1245     # be set to Language.
   1246     variant_name = None
   1247     parent = posixpath.dirname(path)
   1248     grandparent = posixpath.dirname(parent)
   1249     parent_basename = posixpath.basename(parent)
   1250     (parent_root, parent_ext) = posixpath.splitext(parent_basename)
   1251     if parent_ext == '.lproj':
   1252       variant_name = parent_root
   1253     if grandparent == '':
   1254       grandparent = None
   1255 
   1256     # Putting a directory inside a variant group is not currently supported.
   1257     assert not is_dir or variant_name is None
   1258 
   1259     path_split = path.split(posixpath.sep)
   1260     if len(path_split) == 1 or \
   1261        ((is_dir or variant_name != None) and len(path_split) == 2) or \
   1262        not hierarchical:
   1263       # The PBXFileReference or PBXVariantGroup will be added to or gotten from
   1264       # this PBXGroup, no recursion necessary.
   1265       if variant_name is None:
   1266         # Add or get a PBXFileReference.
   1267         file_ref = self.GetChildByPath(path)
   1268         if file_ref != None:
   1269           assert file_ref.__class__ == PBXFileReference
   1270         else:
   1271           file_ref = PBXFileReference({'path': path})
   1272           self.AppendChild(file_ref)
   1273       else:
   1274         # Add or get a PBXVariantGroup.  The variant group name is the same
   1275         # as the basename (MainMenu.nib in the example above).  grandparent
   1276         # specifies the path to the variant group itself, and path_split[-2:]
   1277         # is the path of the specific variant relative to its group.
   1278         variant_group_name = posixpath.basename(path)
   1279         variant_group_ref = self.AddOrGetVariantGroupByNameAndPath(
   1280             variant_group_name, grandparent)
   1281         variant_path = posixpath.sep.join(path_split[-2:])
   1282         variant_ref = variant_group_ref.GetChildByPath(variant_path)
   1283         if variant_ref != None:
   1284           assert variant_ref.__class__ == PBXFileReference
   1285         else:
   1286           variant_ref = PBXFileReference({'name': variant_name,
   1287                                           'path': variant_path})
   1288           variant_group_ref.AppendChild(variant_ref)
   1289         # The caller is interested in the variant group, not the specific
   1290         # variant file.
   1291         file_ref = variant_group_ref
   1292       return file_ref
   1293     else:
   1294       # Hierarchical recursion.  Add or get a PBXGroup corresponding to the
   1295       # outermost path component, and then recurse into it, chopping off that
   1296       # path component.
   1297       next_dir = path_split[0]
   1298       group_ref = self.GetChildByPath(next_dir)
   1299       if group_ref != None:
   1300         assert group_ref.__class__ == PBXGroup
   1301       else:
   1302         group_ref = PBXGroup({'path': next_dir})
   1303         self.AppendChild(group_ref)
   1304       return group_ref.AddOrGetFileByPath(posixpath.sep.join(path_split[1:]),
   1305                                           hierarchical)
   1306 
   1307   def AddOrGetVariantGroupByNameAndPath(self, name, path):
   1308     """Returns an existing or new PBXVariantGroup for name and path.
   1309 
   1310     If a PBXVariantGroup identified by the name and path arguments is already
   1311     present as a child of this object, it is returned.  Otherwise, a new
   1312     PBXVariantGroup with the correct properties is created, added as a child,
   1313     and returned.
   1314 
   1315     This method will generally be called by AddOrGetFileByPath, which knows
   1316     when to create a variant group based on the structure of the pathnames
   1317     passed to it.
   1318     """
   1319 
   1320     key = (name, path)
   1321     if key in self._variant_children_by_name_and_path:
   1322       variant_group_ref = self._variant_children_by_name_and_path[key]
   1323       assert variant_group_ref.__class__ == PBXVariantGroup
   1324       return variant_group_ref
   1325 
   1326     variant_group_properties = {'name': name}
   1327     if path != None:
   1328       variant_group_properties['path'] = path
   1329     variant_group_ref = PBXVariantGroup(variant_group_properties)
   1330     self.AppendChild(variant_group_ref)
   1331 
   1332     return variant_group_ref
   1333 
   1334   def TakeOverOnlyChild(self, recurse=False):
   1335     """If this PBXGroup has only one child and it's also a PBXGroup, take
   1336     it over by making all of its children this object's children.
   1337 
   1338     This function will continue to take over only children when those children
   1339     are groups.  If there are three PBXGroups representing a, b, and c, with
   1340     c inside b and b inside a, and a and b have no other children, this will
   1341     result in a taking over both b and c, forming a PBXGroup for a/b/c.
   1342 
   1343     If recurse is True, this function will recurse into children and ask them
   1344     to collapse themselves by taking over only children as well.  Assuming
   1345     an example hierarchy with files at a/b/c/d1, a/b/c/d2, and a/b/c/d3/e/f
   1346     (d1, d2, and f are files, the rest are groups), recursion will result in
   1347     a group for a/b/c containing a group for d3/e.
   1348     """
   1349 
   1350     # At this stage, check that child class types are PBXGroup exactly,
   1351     # instead of using isinstance.  The only subclass of PBXGroup,
   1352     # PBXVariantGroup, should not participate in reparenting in the same way:
   1353     # reparenting by merging different object types would be wrong.
   1354     while len(self._properties['children']) == 1 and \
   1355           self._properties['children'][0].__class__ == PBXGroup:
   1356       # Loop to take over the innermost only-child group possible.
   1357 
   1358       child = self._properties['children'][0]
   1359 
   1360       # Assume the child's properties, including its children.  Save a copy
   1361       # of this object's old properties, because they'll still be needed.
   1362       # This object retains its existing id and parent attributes.
   1363       old_properties = self._properties
   1364       self._properties = child._properties
   1365       self._children_by_path = child._children_by_path
   1366 
   1367       if not 'sourceTree' in self._properties or \
   1368          self._properties['sourceTree'] == '<group>':
   1369         # The child was relative to its parent.  Fix up the path.  Note that
   1370         # children with a sourceTree other than "<group>" are not relative to
   1371         # their parents, so no path fix-up is needed in that case.
   1372         if 'path' in old_properties:
   1373           if 'path' in self._properties:
   1374             # Both the original parent and child have paths set.
   1375             self._properties['path'] = posixpath.join(old_properties['path'],
   1376                                                       self._properties['path'])
   1377           else:
   1378             # Only the original parent has a path, use it.
   1379             self._properties['path'] = old_properties['path']
   1380         if 'sourceTree' in old_properties:
   1381           # The original parent had a sourceTree set, use it.
   1382           self._properties['sourceTree'] = old_properties['sourceTree']
   1383 
   1384       # If the original parent had a name set, keep using it.  If the original
   1385       # parent didn't have a name but the child did, let the child's name
   1386       # live on.  If the name attribute seems unnecessary now, get rid of it.
   1387       if 'name' in old_properties and old_properties['name'] != None and \
   1388          old_properties['name'] != self.Name():
   1389         self._properties['name'] = old_properties['name']
   1390       if 'name' in self._properties and 'path' in self._properties and \
   1391          self._properties['name'] == self._properties['path']:
   1392         del self._properties['name']
   1393 
   1394       # Notify all children of their new parent.
   1395       for child in self._properties['children']:
   1396         child.parent = self
   1397 
   1398     # If asked to recurse, recurse.
   1399     if recurse:
   1400       for child in self._properties['children']:
   1401         if child.__class__ == PBXGroup:
   1402           child.TakeOverOnlyChild(recurse)
   1403 
   1404   def SortGroup(self):
   1405     self._properties['children'] = \
   1406         sorted(self._properties['children'], cmp=lambda x,y: x.Compare(y))
   1407 
   1408     # Recurse.
   1409     for child in self._properties['children']:
   1410       if isinstance(child, PBXGroup):
   1411         child.SortGroup()
   1412 
   1413 
   1414 class XCFileLikeElement(XCHierarchicalElement):
   1415   # Abstract base for objects that can be used as the fileRef property of
   1416   # PBXBuildFile.
   1417 
   1418   def PathHashables(self):
   1419     # A PBXBuildFile that refers to this object will call this method to
   1420     # obtain additional hashables specific to this XCFileLikeElement.  Don't
   1421     # just use this object's hashables, they're not specific and unique enough
   1422     # on their own (without access to the parent hashables.)  Instead, provide
   1423     # hashables that identify this object by path by getting its hashables as
   1424     # well as the hashables of ancestor XCHierarchicalElement objects.
   1425 
   1426     hashables = []
   1427     xche = self
   1428     while xche != None and isinstance(xche, XCHierarchicalElement):
   1429       xche_hashables = xche.Hashables()
   1430       for index in xrange(0, len(xche_hashables)):
   1431         hashables.insert(index, xche_hashables[index])
   1432       xche = xche.parent
   1433     return hashables
   1434 
   1435 
   1436 class XCContainerPortal(XCObject):
   1437   # Abstract base for objects that can be used as the containerPortal property
   1438   # of PBXContainerItemProxy.
   1439   pass
   1440 
   1441 
   1442 class XCRemoteObject(XCObject):
   1443   # Abstract base for objects that can be used as the remoteGlobalIDString
   1444   # property of PBXContainerItemProxy.
   1445   pass
   1446 
   1447 
   1448 class PBXFileReference(XCFileLikeElement, XCContainerPortal, XCRemoteObject):
   1449   _schema = XCFileLikeElement._schema.copy()
   1450   _schema.update({
   1451     'explicitFileType':  [0, str, 0, 0],
   1452     'lastKnownFileType': [0, str, 0, 0],
   1453     'name':              [0, str, 0, 0],
   1454     'path':              [0, str, 0, 1],
   1455   })
   1456 
   1457   # Weird output rules for PBXFileReference.
   1458   _should_print_single_line = True
   1459   # super
   1460   _encode_transforms = XCFileLikeElement._alternate_encode_transforms
   1461 
   1462   def __init__(self, properties=None, id=None, parent=None):
   1463     # super
   1464     XCFileLikeElement.__init__(self, properties, id, parent)
   1465     if 'path' in self._properties and self._properties['path'].endswith('/'):
   1466       self._properties['path'] = self._properties['path'][:-1]
   1467       is_dir = True
   1468     else:
   1469       is_dir = False
   1470 
   1471     if 'path' in self._properties and \
   1472         not 'lastKnownFileType' in self._properties and \
   1473         not 'explicitFileType' in self._properties:
   1474       # TODO(mark): This is the replacement for a replacement for a quick hack.
   1475       # It is no longer incredibly sucky, but this list needs to be extended.
   1476       extension_map = {
   1477         'a':           'archive.ar',
   1478         'app':         'wrapper.application',
   1479         'bdic':        'file',
   1480         'bundle':      'wrapper.cfbundle',
   1481         'c':           'sourcecode.c.c',
   1482         'cc':          'sourcecode.cpp.cpp',
   1483         'cpp':         'sourcecode.cpp.cpp',
   1484         'css':         'text.css',
   1485         'cxx':         'sourcecode.cpp.cpp',
   1486         'dart':        'sourcecode',
   1487         'dylib':       'compiled.mach-o.dylib',
   1488         'framework':   'wrapper.framework',
   1489         'gyp':         'sourcecode',
   1490         'gypi':        'sourcecode',
   1491         'h':           'sourcecode.c.h',
   1492         'hxx':         'sourcecode.cpp.h',
   1493         'icns':        'image.icns',
   1494         'java':        'sourcecode.java',
   1495         'js':          'sourcecode.javascript',
   1496         'm':           'sourcecode.c.objc',
   1497         'mm':          'sourcecode.cpp.objcpp',
   1498         'nib':         'wrapper.nib',
   1499         'o':           'compiled.mach-o.objfile',
   1500         'pdf':         'image.pdf',
   1501         'pl':          'text.script.perl',
   1502         'plist':       'text.plist.xml',
   1503         'pm':          'text.script.perl',
   1504         'png':         'image.png',
   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         'ttf':         'file',
   1512         'xcassets':    'folder.assetcatalog',
   1513         'xcconfig':    'text.xcconfig',
   1514         'xcdatamodel': 'wrapper.xcdatamodel',
   1515         'xcdatamodeld':'wrapper.xcdatamodeld',
   1516         'xib':         'file.xib',
   1517         'y':           'sourcecode.yacc',
   1518       }
   1519 
   1520       prop_map = {
   1521         'dart':        'explicitFileType',
   1522         'gyp':         'explicitFileType',
   1523         'gypi':        'explicitFileType',
   1524       }
   1525 
   1526       if is_dir:
   1527         file_type = 'folder'
   1528         prop_name = 'lastKnownFileType'
   1529       else:
   1530         basename = posixpath.basename(self._properties['path'])
   1531         (root, ext) = posixpath.splitext(basename)
   1532         # Check the map using a lowercase extension.
   1533         # TODO(mark): Maybe it should try with the original case first and fall
   1534         # back to lowercase, in case there are any instances where case
   1535         # matters.  There currently aren't.
   1536         if ext != '':
   1537           ext = ext[1:].lower()
   1538 
   1539         # TODO(mark): "text" is the default value, but "file" is appropriate
   1540         # for unrecognized files not containing text.  Xcode seems to choose
   1541         # based on content.
   1542         file_type = extension_map.get(ext, 'text')
   1543         prop_name = prop_map.get(ext, 'lastKnownFileType')
   1544 
   1545       self._properties[prop_name] = file_type
   1546 
   1547 
   1548 class PBXVariantGroup(PBXGroup, XCFileLikeElement):
   1549   """PBXVariantGroup is used by Xcode to represent localizations."""
   1550   # No additions to the schema relative to PBXGroup.
   1551   pass
   1552 
   1553 
   1554 # PBXReferenceProxy is also an XCFileLikeElement subclass.  It is defined below
   1555 # because it uses PBXContainerItemProxy, defined below.
   1556 
   1557 
   1558 class XCBuildConfiguration(XCObject):
   1559   _schema = XCObject._schema.copy()
   1560   _schema.update({
   1561     'baseConfigurationReference': [0, PBXFileReference, 0, 0],
   1562     'buildSettings':              [0, dict, 0, 1, {}],
   1563     'name':                       [0, str,  0, 1],
   1564   })
   1565 
   1566   def HasBuildSetting(self, key):
   1567     return key in self._properties['buildSettings']
   1568 
   1569   def GetBuildSetting(self, key):
   1570     return self._properties['buildSettings'][key]
   1571 
   1572   def SetBuildSetting(self, key, value):
   1573     # TODO(mark): If a list, copy?
   1574     self._properties['buildSettings'][key] = value
   1575 
   1576   def AppendBuildSetting(self, key, value):
   1577     if not key in self._properties['buildSettings']:
   1578       self._properties['buildSettings'][key] = []
   1579     self._properties['buildSettings'][key].append(value)
   1580 
   1581   def DelBuildSetting(self, key):
   1582     if key in self._properties['buildSettings']:
   1583       del self._properties['buildSettings'][key]
   1584 
   1585   def SetBaseConfiguration(self, value):
   1586     self._properties['baseConfigurationReference'] = value
   1587 
   1588 class XCConfigurationList(XCObject):
   1589   # _configs is the default list of configurations.
   1590   _configs = [ XCBuildConfiguration({'name': 'Debug'}),
   1591                XCBuildConfiguration({'name': 'Release'}) ]
   1592 
   1593   _schema = XCObject._schema.copy()
   1594   _schema.update({
   1595     'buildConfigurations':           [1, XCBuildConfiguration, 1, 1, _configs],
   1596     'defaultConfigurationIsVisible': [0, int,                  0, 1, 1],
   1597     'defaultConfigurationName':      [0, str,                  0, 1, 'Release'],
   1598   })
   1599 
   1600   def Name(self):
   1601     return 'Build configuration list for ' + \
   1602            self.parent.__class__.__name__ + ' "' + self.parent.Name() + '"'
   1603 
   1604   def ConfigurationNamed(self, name):
   1605     """Convenience accessor to obtain an XCBuildConfiguration by name."""
   1606     for configuration in self._properties['buildConfigurations']:
   1607       if configuration._properties['name'] == name:
   1608         return configuration
   1609 
   1610     raise KeyError, name
   1611 
   1612   def DefaultConfiguration(self):
   1613     """Convenience accessor to obtain the default XCBuildConfiguration."""
   1614     return self.ConfigurationNamed(self._properties['defaultConfigurationName'])
   1615 
   1616   def HasBuildSetting(self, key):
   1617     """Determines the state of a build setting in all XCBuildConfiguration
   1618     child objects.
   1619 
   1620     If all child objects have key in their build settings, and the value is the
   1621     same in all child objects, returns 1.
   1622 
   1623     If no child objects have the key in their build settings, returns 0.
   1624 
   1625     If some, but not all, child objects have the key in their build settings,
   1626     or if any children have different values for the key, returns -1.
   1627     """
   1628 
   1629     has = None
   1630     value = None
   1631     for configuration in self._properties['buildConfigurations']:
   1632       configuration_has = configuration.HasBuildSetting(key)
   1633       if has is None:
   1634         has = configuration_has
   1635       elif has != configuration_has:
   1636         return -1
   1637 
   1638       if configuration_has:
   1639         configuration_value = configuration.GetBuildSetting(key)
   1640         if value is None:
   1641           value = configuration_value
   1642         elif value != configuration_value:
   1643           return -1
   1644 
   1645     if not has:
   1646       return 0
   1647 
   1648     return 1
   1649 
   1650   def GetBuildSetting(self, key):
   1651     """Gets the build setting for key.
   1652 
   1653     All child XCConfiguration objects must have the same value set for the
   1654     setting, or a ValueError will be raised.
   1655     """
   1656 
   1657     # TODO(mark): This is wrong for build settings that are lists.  The list
   1658     # contents should be compared (and a list copy returned?)
   1659 
   1660     value = None
   1661     for configuration in self._properties['buildConfigurations']:
   1662       configuration_value = configuration.GetBuildSetting(key)
   1663       if value is None:
   1664         value = configuration_value
   1665       else:
   1666         if value != configuration_value:
   1667           raise ValueError, 'Variant values for ' + key
   1668 
   1669     return value
   1670 
   1671   def SetBuildSetting(self, key, value):
   1672     """Sets the build setting for key to value in all child
   1673     XCBuildConfiguration objects.
   1674     """
   1675 
   1676     for configuration in self._properties['buildConfigurations']:
   1677       configuration.SetBuildSetting(key, value)
   1678 
   1679   def AppendBuildSetting(self, key, value):
   1680     """Appends value to the build setting for key, which is treated as a list,
   1681     in all child XCBuildConfiguration objects.
   1682     """
   1683 
   1684     for configuration in self._properties['buildConfigurations']:
   1685       configuration.AppendBuildSetting(key, value)
   1686 
   1687   def DelBuildSetting(self, key):
   1688     """Deletes the build setting key from all child XCBuildConfiguration
   1689     objects.
   1690     """
   1691 
   1692     for configuration in self._properties['buildConfigurations']:
   1693       configuration.DelBuildSetting(key)
   1694 
   1695   def SetBaseConfiguration(self, value):
   1696     """Sets the build configuration in all child XCBuildConfiguration objects.
   1697     """
   1698 
   1699     for configuration in self._properties['buildConfigurations']:
   1700       configuration.SetBaseConfiguration(value)
   1701 
   1702 
   1703 class PBXBuildFile(XCObject):
   1704   _schema = XCObject._schema.copy()
   1705   _schema.update({
   1706     'fileRef':  [0, XCFileLikeElement, 0, 1],
   1707     'settings': [0, str,               0, 0],  # hack, it's a dict
   1708   })
   1709 
   1710   # Weird output rules for PBXBuildFile.
   1711   _should_print_single_line = True
   1712   _encode_transforms = XCObject._alternate_encode_transforms
   1713 
   1714   def Name(self):
   1715     # Example: "main.cc in Sources"
   1716     return self._properties['fileRef'].Name() + ' in ' + self.parent.Name()
   1717 
   1718   def Hashables(self):
   1719     # super
   1720     hashables = XCObject.Hashables(self)
   1721 
   1722     # It is not sufficient to just rely on Name() to get the
   1723     # XCFileLikeElement's name, because that is not a complete pathname.
   1724     # PathHashables returns hashables unique enough that no two
   1725     # PBXBuildFiles should wind up with the same set of hashables, unless
   1726     # someone adds the same file multiple times to the same target.  That
   1727     # would be considered invalid anyway.
   1728     hashables.extend(self._properties['fileRef'].PathHashables())
   1729 
   1730     return hashables
   1731 
   1732 
   1733 class XCBuildPhase(XCObject):
   1734   """Abstract base for build phase classes.  Not represented in a project
   1735   file.
   1736 
   1737   Attributes:
   1738     _files_by_path: A dict mapping each path of a child in the files list by
   1739       path (keys) to the corresponding PBXBuildFile children (values).
   1740     _files_by_xcfilelikeelement: A dict mapping each XCFileLikeElement (keys)
   1741       to the corresponding PBXBuildFile children (values).
   1742   """
   1743 
   1744   # TODO(mark): Some build phase types, like PBXShellScriptBuildPhase, don't
   1745   # actually have a "files" list.  XCBuildPhase should not have "files" but
   1746   # another abstract subclass of it should provide this, and concrete build
   1747   # phase types that do have "files" lists should be derived from that new
   1748   # abstract subclass.  XCBuildPhase should only provide buildActionMask and
   1749   # runOnlyForDeploymentPostprocessing, and not files or the various
   1750   # file-related methods and attributes.
   1751 
   1752   _schema = XCObject._schema.copy()
   1753   _schema.update({
   1754     'buildActionMask':                    [0, int,          0, 1, 0x7fffffff],
   1755     'files':                              [1, PBXBuildFile, 1, 1, []],
   1756     'runOnlyForDeploymentPostprocessing': [0, int,          0, 1, 0],
   1757   })
   1758 
   1759   def __init__(self, properties=None, id=None, parent=None):
   1760     # super
   1761     XCObject.__init__(self, properties, id, parent)
   1762 
   1763     self._files_by_path = {}
   1764     self._files_by_xcfilelikeelement = {}
   1765     for pbxbuildfile in self._properties.get('files', []):
   1766       self._AddBuildFileToDicts(pbxbuildfile)
   1767 
   1768   def FileGroup(self, path):
   1769     # Subclasses must override this by returning a two-element tuple.  The
   1770     # first item in the tuple should be the PBXGroup to which "path" should be
   1771     # added, either as a child or deeper descendant.  The second item should
   1772     # be a boolean indicating whether files should be added into hierarchical
   1773     # groups or one single flat group.
   1774     raise NotImplementedError, \
   1775           self.__class__.__name__ + ' must implement FileGroup'
   1776 
   1777   def _AddPathToDict(self, pbxbuildfile, path):
   1778     """Adds path to the dict tracking paths belonging to this build phase.
   1779 
   1780     If the path is already a member of this build phase, raises an exception.
   1781     """
   1782 
   1783     if path in self._files_by_path:
   1784       raise ValueError, 'Found multiple build files with path ' + path
   1785     self._files_by_path[path] = pbxbuildfile
   1786 
   1787   def _AddBuildFileToDicts(self, pbxbuildfile, path=None):
   1788     """Maintains the _files_by_path and _files_by_xcfilelikeelement dicts.
   1789 
   1790     If path is specified, then it is the path that is being added to the
   1791     phase, and pbxbuildfile must contain either a PBXFileReference directly
   1792     referencing that path, or it must contain a PBXVariantGroup that itself
   1793     contains a PBXFileReference referencing the path.
   1794 
   1795     If path is not specified, either the PBXFileReference's path or the paths
   1796     of all children of the PBXVariantGroup are taken as being added to the
   1797     phase.
   1798 
   1799     If the path is already present in the phase, raises an exception.
   1800 
   1801     If the PBXFileReference or PBXVariantGroup referenced by pbxbuildfile
   1802     are already present in the phase, referenced by a different PBXBuildFile
   1803     object, raises an exception.  This does not raise an exception when
   1804     a PBXFileReference or PBXVariantGroup reappear and are referenced by the
   1805     same PBXBuildFile that has already introduced them, because in the case
   1806     of PBXVariantGroup objects, they may correspond to multiple paths that are
   1807     not all added simultaneously.  When this situation occurs, the path needs
   1808     to be added to _files_by_path, but nothing needs to change in
   1809     _files_by_xcfilelikeelement, and the caller should have avoided adding
   1810     the PBXBuildFile if it is already present in the list of children.
   1811     """
   1812 
   1813     xcfilelikeelement = pbxbuildfile._properties['fileRef']
   1814 
   1815     paths = []
   1816     if path != None:
   1817       # It's best when the caller provides the path.
   1818       if isinstance(xcfilelikeelement, PBXVariantGroup):
   1819         paths.append(path)
   1820     else:
   1821       # If the caller didn't provide a path, there can be either multiple
   1822       # paths (PBXVariantGroup) or one.
   1823       if isinstance(xcfilelikeelement, PBXVariantGroup):
   1824         for variant in xcfilelikeelement._properties['children']:
   1825           paths.append(variant.FullPath())
   1826       else:
   1827         paths.append(xcfilelikeelement.FullPath())
   1828 
   1829     # Add the paths first, because if something's going to raise, the
   1830     # messages provided by _AddPathToDict are more useful owing to its
   1831     # having access to a real pathname and not just an object's Name().
   1832     for a_path in paths:
   1833       self._AddPathToDict(pbxbuildfile, a_path)
   1834 
   1835     # If another PBXBuildFile references this XCFileLikeElement, there's a
   1836     # problem.
   1837     if xcfilelikeelement in self._files_by_xcfilelikeelement and \
   1838        self._files_by_xcfilelikeelement[xcfilelikeelement] != pbxbuildfile:
   1839       raise ValueError, 'Found multiple build files for ' + \
   1840                         xcfilelikeelement.Name()
   1841     self._files_by_xcfilelikeelement[xcfilelikeelement] = pbxbuildfile
   1842 
   1843   def AppendBuildFile(self, pbxbuildfile, path=None):
   1844     # Callers should use this instead of calling
   1845     # AppendProperty('files', pbxbuildfile) directly because this function
   1846     # maintains the object's dicts.  Better yet, callers can just call AddFile
   1847     # with a pathname and not worry about building their own PBXBuildFile
   1848     # objects.
   1849     self.AppendProperty('files', pbxbuildfile)
   1850     self._AddBuildFileToDicts(pbxbuildfile, path)
   1851 
   1852   def AddFile(self, path, settings=None):
   1853     (file_group, hierarchical) = self.FileGroup(path)
   1854     file_ref = file_group.AddOrGetFileByPath(path, hierarchical)
   1855 
   1856     if file_ref in self._files_by_xcfilelikeelement and \
   1857        isinstance(file_ref, PBXVariantGroup):
   1858       # There's already a PBXBuildFile in this phase corresponding to the
   1859       # PBXVariantGroup.  path just provides a new variant that belongs to
   1860       # the group.  Add the path to the dict.
   1861       pbxbuildfile = self._files_by_xcfilelikeelement[file_ref]
   1862       self._AddBuildFileToDicts(pbxbuildfile, path)
   1863     else:
   1864       # Add a new PBXBuildFile to get file_ref into the phase.
   1865       if settings is None:
   1866         pbxbuildfile = PBXBuildFile({'fileRef': file_ref})
   1867       else:
   1868         pbxbuildfile = PBXBuildFile({'fileRef': file_ref, 'settings': settings})
   1869       self.AppendBuildFile(pbxbuildfile, path)
   1870 
   1871 
   1872 class PBXHeadersBuildPhase(XCBuildPhase):
   1873   # No additions to the schema relative to XCBuildPhase.
   1874 
   1875   def Name(self):
   1876     return 'Headers'
   1877 
   1878   def FileGroup(self, path):
   1879     return self.PBXProjectAncestor().RootGroupForPath(path)
   1880 
   1881 
   1882 class PBXResourcesBuildPhase(XCBuildPhase):
   1883   # No additions to the schema relative to XCBuildPhase.
   1884 
   1885   def Name(self):
   1886     return 'Resources'
   1887 
   1888   def FileGroup(self, path):
   1889     return self.PBXProjectAncestor().RootGroupForPath(path)
   1890 
   1891 
   1892 class PBXSourcesBuildPhase(XCBuildPhase):
   1893   # No additions to the schema relative to XCBuildPhase.
   1894 
   1895   def Name(self):
   1896     return 'Sources'
   1897 
   1898   def FileGroup(self, path):
   1899     return self.PBXProjectAncestor().RootGroupForPath(path)
   1900 
   1901 
   1902 class PBXFrameworksBuildPhase(XCBuildPhase):
   1903   # No additions to the schema relative to XCBuildPhase.
   1904 
   1905   def Name(self):
   1906     return 'Frameworks'
   1907 
   1908   def FileGroup(self, path):
   1909     (root, ext) = posixpath.splitext(path)
   1910     if ext != '':
   1911       ext = ext[1:].lower()
   1912     if ext == 'o':
   1913       # .o files are added to Xcode Frameworks phases, but conceptually aren't
   1914       # frameworks, they're more like sources or intermediates. Redirect them
   1915       # to show up in one of those other groups.
   1916       return self.PBXProjectAncestor().RootGroupForPath(path)
   1917     else:
   1918       return (self.PBXProjectAncestor().FrameworksGroup(), False)
   1919 
   1920 
   1921 class PBXShellScriptBuildPhase(XCBuildPhase):
   1922   _schema = XCBuildPhase._schema.copy()
   1923   _schema.update({
   1924     'inputPaths':       [1, str, 0, 1, []],
   1925     'name':             [0, str, 0, 0],
   1926     'outputPaths':      [1, str, 0, 1, []],
   1927     'shellPath':        [0, str, 0, 1, '/bin/sh'],
   1928     'shellScript':      [0, str, 0, 1],
   1929     'showEnvVarsInLog': [0, int, 0, 0],
   1930   })
   1931 
   1932   def Name(self):
   1933     if 'name' in self._properties:
   1934       return self._properties['name']
   1935 
   1936     return 'ShellScript'
   1937 
   1938 
   1939 class PBXCopyFilesBuildPhase(XCBuildPhase):
   1940   _schema = XCBuildPhase._schema.copy()
   1941   _schema.update({
   1942     'dstPath':          [0, str, 0, 1],
   1943     'dstSubfolderSpec': [0, int, 0, 1],
   1944     'name':             [0, str, 0, 0],
   1945   })
   1946 
   1947   # path_tree_re matches "$(DIR)/path" or just "$(DIR)".  Match group 1 is
   1948   # "DIR", match group 3 is "path" or None.
   1949   path_tree_re = re.compile('^\\$\\((.*)\\)(/(.*)|)$')
   1950 
   1951   # path_tree_to_subfolder maps names of Xcode variables to the associated
   1952   # dstSubfolderSpec property value used in a PBXCopyFilesBuildPhase object.
   1953   path_tree_to_subfolder = {
   1954     'BUILT_PRODUCTS_DIR': 16,  # Products Directory
   1955     # Other types that can be chosen via the Xcode UI.
   1956     # TODO(mark): Map Xcode variable names to these.
   1957     # : 1,  # Wrapper
   1958     # : 6,  # Executables: 6
   1959     # : 7,  # Resources
   1960     # : 15,  # Java Resources
   1961     # : 10,  # Frameworks
   1962     # : 11,  # Shared Frameworks
   1963     # : 12,  # Shared Support
   1964     # : 13,  # PlugIns
   1965   }
   1966 
   1967   def Name(self):
   1968     if 'name' in self._properties:
   1969       return self._properties['name']
   1970 
   1971     return 'CopyFiles'
   1972 
   1973   def FileGroup(self, path):
   1974     return self.PBXProjectAncestor().RootGroupForPath(path)
   1975 
   1976   def SetDestination(self, path):
   1977     """Set the dstSubfolderSpec and dstPath properties from path.
   1978 
   1979     path may be specified in the same notation used for XCHierarchicalElements,
   1980     specifically, "$(DIR)/path".
   1981     """
   1982 
   1983     path_tree_match = self.path_tree_re.search(path)
   1984     if path_tree_match:
   1985       # Everything else needs to be relative to an Xcode variable.
   1986       path_tree = path_tree_match.group(1)
   1987       relative_path = path_tree_match.group(3)
   1988 
   1989       if path_tree in self.path_tree_to_subfolder:
   1990         subfolder = self.path_tree_to_subfolder[path_tree]
   1991         if relative_path is None:
   1992           relative_path = ''
   1993       else:
   1994         # The path starts with an unrecognized Xcode variable
   1995         # name like $(SRCROOT).  Xcode will still handle this
   1996         # as an "absolute path" that starts with the variable.
   1997         subfolder = 0
   1998         relative_path = path
   1999     elif path.startswith('/'):
   2000       # Special case.  Absolute paths are in dstSubfolderSpec 0.
   2001       subfolder = 0
   2002       relative_path = path[1:]
   2003     else:
   2004       raise ValueError, 'Can\'t use path %s in a %s' % \
   2005                         (path, self.__class__.__name__)
   2006 
   2007     self._properties['dstPath'] = relative_path
   2008     self._properties['dstSubfolderSpec'] = subfolder
   2009 
   2010 
   2011 class PBXBuildRule(XCObject):
   2012   _schema = XCObject._schema.copy()
   2013   _schema.update({
   2014     'compilerSpec': [0, str, 0, 1],
   2015     'filePatterns': [0, str, 0, 0],
   2016     'fileType':     [0, str, 0, 1],
   2017     'isEditable':   [0, int, 0, 1, 1],
   2018     'outputFiles':  [1, str, 0, 1, []],
   2019     'script':       [0, str, 0, 0],
   2020   })
   2021 
   2022   def Name(self):
   2023     # Not very inspired, but it's what Xcode uses.
   2024     return self.__class__.__name__
   2025 
   2026   def Hashables(self):
   2027     # super
   2028     hashables = XCObject.Hashables(self)
   2029 
   2030     # Use the hashables of the weak objects that this object refers to.
   2031     hashables.append(self._properties['fileType'])
   2032     if 'filePatterns' in self._properties:
   2033       hashables.append(self._properties['filePatterns'])
   2034     return hashables
   2035 
   2036 
   2037 class PBXContainerItemProxy(XCObject):
   2038   # When referencing an item in this project file, containerPortal is the
   2039   # PBXProject root object of this project file.  When referencing an item in
   2040   # another project file, containerPortal is a PBXFileReference identifying
   2041   # the other project file.
   2042   #
   2043   # When serving as a proxy to an XCTarget (in this project file or another),
   2044   # proxyType is 1.  When serving as a proxy to a PBXFileReference (in another
   2045   # project file), proxyType is 2.  Type 2 is used for references to the
   2046   # producs of the other project file's targets.
   2047   #
   2048   # Xcode is weird about remoteGlobalIDString.  Usually, it's printed without
   2049   # a comment, indicating that it's tracked internally simply as a string, but
   2050   # sometimes it's printed with a comment (usually when the object is initially
   2051   # created), indicating that it's tracked as a project file object at least
   2052   # sometimes.  This module always tracks it as an object, but contains a hack
   2053   # to prevent it from printing the comment in the project file output.  See
   2054   # _XCKVPrint.
   2055   _schema = XCObject._schema.copy()
   2056   _schema.update({
   2057     'containerPortal':      [0, XCContainerPortal, 0, 1],
   2058     'proxyType':            [0, int,               0, 1],
   2059     'remoteGlobalIDString': [0, XCRemoteObject,    0, 1],
   2060     'remoteInfo':           [0, str,               0, 1],
   2061   })
   2062 
   2063   def __repr__(self):
   2064     props = self._properties
   2065     name = '%s.gyp:%s' % (props['containerPortal'].Name(), props['remoteInfo'])
   2066     return '<%s %r at 0x%x>' % (self.__class__.__name__, name, id(self))
   2067 
   2068   def Name(self):
   2069     # Admittedly not the best name, but it's what Xcode uses.
   2070     return self.__class__.__name__
   2071 
   2072   def Hashables(self):
   2073     # super
   2074     hashables = XCObject.Hashables(self)
   2075 
   2076     # Use the hashables of the weak objects that this object refers to.
   2077     hashables.extend(self._properties['containerPortal'].Hashables())
   2078     hashables.extend(self._properties['remoteGlobalIDString'].Hashables())
   2079     return hashables
   2080 
   2081 
   2082 class PBXTargetDependency(XCObject):
   2083   # The "target" property accepts an XCTarget object, and obviously not
   2084   # NoneType.  But XCTarget is defined below, so it can't be put into the
   2085   # schema yet.  The definition of PBXTargetDependency can't be moved below
   2086   # XCTarget because XCTarget's own schema references PBXTargetDependency.
   2087   # Python doesn't deal well with this circular relationship, and doesn't have
   2088   # a real way to do forward declarations.  To work around, the type of
   2089   # the "target" property is reset below, after XCTarget is defined.
   2090   #
   2091   # At least one of "name" and "target" is required.
   2092   _schema = XCObject._schema.copy()
   2093   _schema.update({
   2094     'name':        [0, str,                   0, 0],
   2095     'target':      [0, None.__class__,        0, 0],
   2096     'targetProxy': [0, PBXContainerItemProxy, 1, 1],
   2097   })
   2098 
   2099   def __repr__(self):
   2100     name = self._properties.get('name') or self._properties['target'].Name()
   2101     return '<%s %r at 0x%x>' % (self.__class__.__name__, name, id(self))
   2102 
   2103   def Name(self):
   2104     # Admittedly not the best name, but it's what Xcode uses.
   2105     return self.__class__.__name__
   2106 
   2107   def Hashables(self):
   2108     # super
   2109     hashables = XCObject.Hashables(self)
   2110 
   2111     # Use the hashables of the weak objects that this object refers to.
   2112     hashables.extend(self._properties['targetProxy'].Hashables())
   2113     return hashables
   2114 
   2115 
   2116 class PBXReferenceProxy(XCFileLikeElement):
   2117   _schema = XCFileLikeElement._schema.copy()
   2118   _schema.update({
   2119     'fileType':  [0, str,                   0, 1],
   2120     'path':      [0, str,                   0, 1],
   2121     'remoteRef': [0, PBXContainerItemProxy, 1, 1],
   2122   })
   2123 
   2124 
   2125 class XCTarget(XCRemoteObject):
   2126   # An XCTarget is really just an XCObject, the XCRemoteObject thing is just
   2127   # to allow PBXProject to be used in the remoteGlobalIDString property of
   2128   # PBXContainerItemProxy.
   2129   #
   2130   # Setting a "name" property at instantiation may also affect "productName",
   2131   # which may in turn affect the "PRODUCT_NAME" build setting in children of
   2132   # "buildConfigurationList".  See __init__ below.
   2133   _schema = XCRemoteObject._schema.copy()
   2134   _schema.update({
   2135     'buildConfigurationList': [0, XCConfigurationList, 1, 1,
   2136                                XCConfigurationList()],
   2137     'buildPhases':            [1, XCBuildPhase,        1, 1, []],
   2138     'dependencies':           [1, PBXTargetDependency, 1, 1, []],
   2139     'name':                   [0, str,                 0, 1],
   2140     'productName':            [0, str,                 0, 1],
   2141   })
   2142 
   2143   def __init__(self, properties=None, id=None, parent=None,
   2144                force_outdir=None, force_prefix=None, force_extension=None):
   2145     # super
   2146     XCRemoteObject.__init__(self, properties, id, parent)
   2147 
   2148     # Set up additional defaults not expressed in the schema.  If a "name"
   2149     # property was supplied, set "productName" if it is not present.  Also set
   2150     # the "PRODUCT_NAME" build setting in each configuration, but only if
   2151     # the setting is not present in any build configuration.
   2152     if 'name' in self._properties:
   2153       if not 'productName' in self._properties:
   2154         self.SetProperty('productName', self._properties['name'])
   2155 
   2156     if 'productName' in self._properties:
   2157       if 'buildConfigurationList' in self._properties:
   2158         configs = self._properties['buildConfigurationList']
   2159         if configs.HasBuildSetting('PRODUCT_NAME') == 0:
   2160           configs.SetBuildSetting('PRODUCT_NAME',
   2161                                   self._properties['productName'])
   2162 
   2163   def AddDependency(self, other):
   2164     pbxproject = self.PBXProjectAncestor()
   2165     other_pbxproject = other.PBXProjectAncestor()
   2166     if pbxproject == other_pbxproject:
   2167       # Add a dependency to another target in the same project file.
   2168       container = PBXContainerItemProxy({'containerPortal':      pbxproject,
   2169                                          'proxyType':            1,
   2170                                          'remoteGlobalIDString': other,
   2171                                          'remoteInfo':           other.Name()})
   2172       dependency = PBXTargetDependency({'target':      other,
   2173                                         'targetProxy': container})
   2174       self.AppendProperty('dependencies', dependency)
   2175     else:
   2176       # Add a dependency to a target in a different project file.
   2177       other_project_ref = \
   2178           pbxproject.AddOrGetProjectReference(other_pbxproject)[1]
   2179       container = PBXContainerItemProxy({
   2180             'containerPortal':      other_project_ref,
   2181             'proxyType':            1,
   2182             'remoteGlobalIDString': other,
   2183             'remoteInfo':           other.Name(),
   2184           })
   2185       dependency = PBXTargetDependency({'name':        other.Name(),
   2186                                         'targetProxy': container})
   2187       self.AppendProperty('dependencies', dependency)
   2188 
   2189   # Proxy all of these through to the build configuration list.
   2190 
   2191   def ConfigurationNamed(self, name):
   2192     return self._properties['buildConfigurationList'].ConfigurationNamed(name)
   2193 
   2194   def DefaultConfiguration(self):
   2195     return self._properties['buildConfigurationList'].DefaultConfiguration()
   2196 
   2197   def HasBuildSetting(self, key):
   2198     return self._properties['buildConfigurationList'].HasBuildSetting(key)
   2199 
   2200   def GetBuildSetting(self, key):
   2201     return self._properties['buildConfigurationList'].GetBuildSetting(key)
   2202 
   2203   def SetBuildSetting(self, key, value):
   2204     return self._properties['buildConfigurationList'].SetBuildSetting(key, \
   2205                                                                       value)
   2206 
   2207   def AppendBuildSetting(self, key, value):
   2208     return self._properties['buildConfigurationList'].AppendBuildSetting(key, \
   2209                                                                          value)
   2210 
   2211   def DelBuildSetting(self, key):
   2212     return self._properties['buildConfigurationList'].DelBuildSetting(key)
   2213 
   2214 
   2215 # Redefine the type of the "target" property.  See PBXTargetDependency._schema
   2216 # above.
   2217 PBXTargetDependency._schema['target'][1] = XCTarget
   2218 
   2219 
   2220 class PBXNativeTarget(XCTarget):
   2221   # buildPhases is overridden in the schema to be able to set defaults.
   2222   #
   2223   # NOTE: Contrary to most objects, it is advisable to set parent when
   2224   # constructing PBXNativeTarget.  A parent of an XCTarget must be a PBXProject
   2225   # object.  A parent reference is required for a PBXNativeTarget during
   2226   # construction to be able to set up the target defaults for productReference,
   2227   # because a PBXBuildFile object must be created for the target and it must
   2228   # be added to the PBXProject's mainGroup hierarchy.
   2229   _schema = XCTarget._schema.copy()
   2230   _schema.update({
   2231     'buildPhases':      [1, XCBuildPhase,     1, 1,
   2232                          [PBXSourcesBuildPhase(), PBXFrameworksBuildPhase()]],
   2233     'buildRules':       [1, PBXBuildRule,     1, 1, []],
   2234     'productReference': [0, PBXFileReference, 0, 1],
   2235     'productType':      [0, str,              0, 1],
   2236   })
   2237 
   2238   # Mapping from Xcode product-types to settings.  The settings are:
   2239   #  filetype : used for explicitFileType in the project file
   2240   #  prefix : the prefix for the file name
   2241   #  suffix : the suffix for the file name
   2242   _product_filetypes = {
   2243     'com.apple.product-type.application':       ['wrapper.application',
   2244                                                  '', '.app'],
   2245     'com.apple.product-type.app-extension':     ['wrapper.app-extension',
   2246                                                  '', '.appex'],
   2247     'com.apple.product-type.bundle':            ['wrapper.cfbundle',
   2248                                                  '', '.bundle'],
   2249     'com.apple.product-type.framework':         ['wrapper.framework',
   2250                                                  '', '.framework'],
   2251     'com.apple.product-type.library.dynamic':   ['compiled.mach-o.dylib',
   2252                                                  'lib', '.dylib'],
   2253     'com.apple.product-type.library.static':    ['archive.ar',
   2254                                                  'lib', '.a'],
   2255     'com.apple.product-type.tool':              ['compiled.mach-o.executable',
   2256                                                  '', ''],
   2257     'com.apple.product-type.bundle.unit-test':  ['wrapper.cfbundle',
   2258                                                  '', '.xctest'],
   2259     'com.googlecode.gyp.xcode.bundle':          ['compiled.mach-o.dylib',
   2260                                                  '', '.so'],
   2261   }
   2262 
   2263   def __init__(self, properties=None, id=None, parent=None,
   2264                force_outdir=None, force_prefix=None, force_extension=None):
   2265     # super
   2266     XCTarget.__init__(self, properties, id, parent)
   2267 
   2268     if 'productName' in self._properties and \
   2269        'productType' in self._properties and \
   2270        not 'productReference' in self._properties and \
   2271        self._properties['productType'] in self._product_filetypes:
   2272       products_group = None
   2273       pbxproject = self.PBXProjectAncestor()
   2274       if pbxproject != None:
   2275         products_group = pbxproject.ProductsGroup()
   2276 
   2277       if products_group != None:
   2278         (filetype, prefix, suffix) = \
   2279             self._product_filetypes[self._properties['productType']]
   2280         # Xcode does not have a distinct type for loadable modules that are
   2281         # pure BSD targets (not in a bundle wrapper). GYP allows such modules
   2282         # to be specified by setting a target type to loadable_module without
   2283         # having mac_bundle set. These are mapped to the pseudo-product type
   2284         # com.googlecode.gyp.xcode.bundle.
   2285         #
   2286         # By picking up this special type and converting it to a dynamic
   2287         # library (com.apple.product-type.library.dynamic) with fix-ups,
   2288         # single-file loadable modules can be produced.
   2289         #
   2290         # MACH_O_TYPE is changed to mh_bundle to produce the proper file type
   2291         # (as opposed to mh_dylib). In order for linking to succeed,
   2292         # DYLIB_CURRENT_VERSION and DYLIB_COMPATIBILITY_VERSION must be
   2293         # cleared. They are meaningless for type mh_bundle.
   2294         #
   2295         # Finally, the .so extension is forcibly applied over the default
   2296         # (.dylib), unless another forced extension is already selected.
   2297         # .dylib is plainly wrong, and .bundle is used by loadable_modules in
   2298         # bundle wrappers (com.apple.product-type.bundle). .so seems an odd
   2299         # choice because it's used as the extension on many other systems that
   2300         # don't distinguish between linkable shared libraries and non-linkable
   2301         # loadable modules, but there's precedent: Python loadable modules on
   2302         # Mac OS X use an .so extension.
   2303         if self._properties['productType'] == 'com.googlecode.gyp.xcode.bundle':
   2304           self._properties['productType'] = \
   2305               'com.apple.product-type.library.dynamic'
   2306           self.SetBuildSetting('MACH_O_TYPE', 'mh_bundle')
   2307           self.SetBuildSetting('DYLIB_CURRENT_VERSION', '')
   2308           self.SetBuildSetting('DYLIB_COMPATIBILITY_VERSION', '')
   2309           if force_extension is None:
   2310             force_extension = suffix[1:]
   2311 
   2312         if self._properties['productType'] == \
   2313            'com.apple.product-type-bundle.unit.test':
   2314           if force_extension is None:
   2315             force_extension = suffix[1:]
   2316 
   2317         if force_extension is not None:
   2318           # If it's a wrapper (bundle), set WRAPPER_EXTENSION.
   2319           # Extension override.
   2320           suffix = '.' + force_extension
   2321           if filetype.startswith('wrapper.'):
   2322             self.SetBuildSetting('WRAPPER_EXTENSION', force_extension)
   2323           else:
   2324             self.SetBuildSetting('EXECUTABLE_EXTENSION', force_extension)
   2325 
   2326           if filetype.startswith('compiled.mach-o.executable'):
   2327             product_name = self._properties['productName']
   2328             product_name += suffix
   2329             suffix = ''
   2330             self.SetProperty('productName', product_name)
   2331             self.SetBuildSetting('PRODUCT_NAME', product_name)
   2332 
   2333         # Xcode handles most prefixes based on the target type, however there
   2334         # are exceptions.  If a "BSD Dynamic Library" target is added in the
   2335         # Xcode UI, Xcode sets EXECUTABLE_PREFIX.  This check duplicates that
   2336         # behavior.
   2337         if force_prefix is not None:
   2338           prefix = force_prefix
   2339         if filetype.startswith('wrapper.'):
   2340           self.SetBuildSetting('WRAPPER_PREFIX', prefix)
   2341         else:
   2342           self.SetBuildSetting('EXECUTABLE_PREFIX', prefix)
   2343 
   2344         if force_outdir is not None:
   2345           self.SetBuildSetting('TARGET_BUILD_DIR', force_outdir)
   2346 
   2347         # TODO(tvl): Remove the below hack.
   2348         #    http://code.google.com/p/gyp/issues/detail?id=122
   2349 
   2350         # Some targets include the prefix in the target_name.  These targets
   2351         # really should just add a product_name setting that doesn't include
   2352         # the prefix.  For example:
   2353         #  target_name = 'libevent', product_name = 'event'
   2354         # This check cleans up for them.
   2355         product_name = self._properties['productName']
   2356         prefix_len = len(prefix)
   2357         if prefix_len and (product_name[:prefix_len] == prefix):
   2358           product_name = product_name[prefix_len:]
   2359           self.SetProperty('productName', product_name)
   2360           self.SetBuildSetting('PRODUCT_NAME', product_name)
   2361 
   2362         ref_props = {
   2363           'explicitFileType': filetype,
   2364           'includeInIndex':   0,
   2365           'path':             prefix + product_name + suffix,
   2366           'sourceTree':       'BUILT_PRODUCTS_DIR',
   2367         }
   2368         file_ref = PBXFileReference(ref_props)
   2369         products_group.AppendChild(file_ref)
   2370         self.SetProperty('productReference', file_ref)
   2371 
   2372   def GetBuildPhaseByType(self, type):
   2373     if not 'buildPhases' in self._properties:
   2374       return None
   2375 
   2376     the_phase = None
   2377     for phase in self._properties['buildPhases']:
   2378       if isinstance(phase, type):
   2379         # Some phases may be present in multiples in a well-formed project file,
   2380         # but phases like PBXSourcesBuildPhase may only be present singly, and
   2381         # this function is intended as an aid to GetBuildPhaseByType.  Loop
   2382         # over the entire list of phases and assert if more than one of the
   2383         # desired type is found.
   2384         assert the_phase is None
   2385         the_phase = phase
   2386 
   2387     return the_phase
   2388 
   2389   def HeadersPhase(self):
   2390     headers_phase = self.GetBuildPhaseByType(PBXHeadersBuildPhase)
   2391     if headers_phase is None:
   2392       headers_phase = PBXHeadersBuildPhase()
   2393 
   2394       # The headers phase should come before the resources, sources, and
   2395       # frameworks phases, if any.
   2396       insert_at = len(self._properties['buildPhases'])
   2397       for index in xrange(0, len(self._properties['buildPhases'])):
   2398         phase = self._properties['buildPhases'][index]
   2399         if isinstance(phase, PBXResourcesBuildPhase) or \
   2400            isinstance(phase, PBXSourcesBuildPhase) or \
   2401            isinstance(phase, PBXFrameworksBuildPhase):
   2402           insert_at = index
   2403           break
   2404 
   2405       self._properties['buildPhases'].insert(insert_at, headers_phase)
   2406       headers_phase.parent = self
   2407 
   2408     return headers_phase
   2409 
   2410   def ResourcesPhase(self):
   2411     resources_phase = self.GetBuildPhaseByType(PBXResourcesBuildPhase)
   2412     if resources_phase is None:
   2413       resources_phase = PBXResourcesBuildPhase()
   2414 
   2415       # The resources phase should come before the sources and frameworks
   2416       # phases, if any.
   2417       insert_at = len(self._properties['buildPhases'])
   2418       for index in xrange(0, len(self._properties['buildPhases'])):
   2419         phase = self._properties['buildPhases'][index]
   2420         if isinstance(phase, PBXSourcesBuildPhase) or \
   2421            isinstance(phase, PBXFrameworksBuildPhase):
   2422           insert_at = index
   2423           break
   2424 
   2425       self._properties['buildPhases'].insert(insert_at, resources_phase)
   2426       resources_phase.parent = self
   2427 
   2428     return resources_phase
   2429 
   2430   def SourcesPhase(self):
   2431     sources_phase = self.GetBuildPhaseByType(PBXSourcesBuildPhase)
   2432     if sources_phase is None:
   2433       sources_phase = PBXSourcesBuildPhase()
   2434       self.AppendProperty('buildPhases', sources_phase)
   2435 
   2436     return sources_phase
   2437 
   2438   def FrameworksPhase(self):
   2439     frameworks_phase = self.GetBuildPhaseByType(PBXFrameworksBuildPhase)
   2440     if frameworks_phase is None:
   2441       frameworks_phase = PBXFrameworksBuildPhase()
   2442       self.AppendProperty('buildPhases', frameworks_phase)
   2443 
   2444     return frameworks_phase
   2445 
   2446   def AddDependency(self, other):
   2447     # super
   2448     XCTarget.AddDependency(self, other)
   2449 
   2450     static_library_type = 'com.apple.product-type.library.static'
   2451     shared_library_type = 'com.apple.product-type.library.dynamic'
   2452     framework_type = 'com.apple.product-type.framework'
   2453     if isinstance(other, PBXNativeTarget) and \
   2454        'productType' in self._properties and \
   2455        self._properties['productType'] != static_library_type and \
   2456        'productType' in other._properties and \
   2457        (other._properties['productType'] == static_library_type or \
   2458         ((other._properties['productType'] == shared_library_type or \
   2459           other._properties['productType'] == framework_type) and \
   2460          ((not other.HasBuildSetting('MACH_O_TYPE')) or
   2461           other.GetBuildSetting('MACH_O_TYPE') != 'mh_bundle'))):
   2462 
   2463       file_ref = other.GetProperty('productReference')
   2464 
   2465       pbxproject = self.PBXProjectAncestor()
   2466       other_pbxproject = other.PBXProjectAncestor()
   2467       if pbxproject != other_pbxproject:
   2468         other_project_product_group = \
   2469             pbxproject.AddOrGetProjectReference(other_pbxproject)[0]
   2470         file_ref = other_project_product_group.GetChildByRemoteObject(file_ref)
   2471 
   2472       self.FrameworksPhase().AppendProperty('files',
   2473                                             PBXBuildFile({'fileRef': file_ref}))
   2474 
   2475 
   2476 class PBXAggregateTarget(XCTarget):
   2477   pass
   2478 
   2479 
   2480 class PBXProject(XCContainerPortal):
   2481   # A PBXProject is really just an XCObject, the XCContainerPortal thing is
   2482   # just to allow PBXProject to be used in the containerPortal property of
   2483   # PBXContainerItemProxy.
   2484   """
   2485 
   2486   Attributes:
   2487     path: "sample.xcodeproj".  TODO(mark) Document me!
   2488     _other_pbxprojects: A dictionary, keyed by other PBXProject objects.  Each
   2489                         value is a reference to the dict in the
   2490                         projectReferences list associated with the keyed
   2491                         PBXProject.
   2492   """
   2493 
   2494   _schema = XCContainerPortal._schema.copy()
   2495   _schema.update({
   2496     'attributes':             [0, dict,                0, 0],
   2497     'buildConfigurationList': [0, XCConfigurationList, 1, 1,
   2498                                XCConfigurationList()],
   2499     'compatibilityVersion':   [0, str,                 0, 1, 'Xcode 3.2'],
   2500     'hasScannedForEncodings': [0, int,                 0, 1, 1],
   2501     'mainGroup':              [0, PBXGroup,            1, 1, PBXGroup()],
   2502     'projectDirPath':         [0, str,                 0, 1, ''],
   2503     'projectReferences':      [1, dict,                0, 0],
   2504     'projectRoot':            [0, str,                 0, 1, ''],
   2505     'targets':                [1, XCTarget,            1, 1, []],
   2506   })
   2507 
   2508   def __init__(self, properties=None, id=None, parent=None, path=None):
   2509     self.path = path
   2510     self._other_pbxprojects = {}
   2511     # super
   2512     return XCContainerPortal.__init__(self, properties, id, parent)
   2513 
   2514   def Name(self):
   2515     name = self.path
   2516     if name[-10:] == '.xcodeproj':
   2517       name = name[:-10]
   2518     return posixpath.basename(name)
   2519 
   2520   def Path(self):
   2521     return self.path
   2522 
   2523   def Comment(self):
   2524     return 'Project object'
   2525 
   2526   def Children(self):
   2527     # super
   2528     children = XCContainerPortal.Children(self)
   2529 
   2530     # Add children that the schema doesn't know about.  Maybe there's a more
   2531     # elegant way around this, but this is the only case where we need to own
   2532     # objects in a dictionary (that is itself in a list), and three lines for
   2533     # a one-off isn't that big a deal.
   2534     if 'projectReferences' in self._properties:
   2535       for reference in self._properties['projectReferences']:
   2536         children.append(reference['ProductGroup'])
   2537 
   2538     return children
   2539 
   2540   def PBXProjectAncestor(self):
   2541     return self
   2542 
   2543   def _GroupByName(self, name):
   2544     if not 'mainGroup' in self._properties:
   2545       self.SetProperty('mainGroup', PBXGroup())
   2546 
   2547     main_group = self._properties['mainGroup']
   2548     group = main_group.GetChildByName(name)
   2549     if group is None:
   2550       group = PBXGroup({'name': name})
   2551       main_group.AppendChild(group)
   2552 
   2553     return group
   2554 
   2555   # SourceGroup and ProductsGroup are created by default in Xcode's own
   2556   # templates.
   2557   def SourceGroup(self):
   2558     return self._GroupByName('Source')
   2559 
   2560   def ProductsGroup(self):
   2561     return self._GroupByName('Products')
   2562 
   2563   # IntermediatesGroup is used to collect source-like files that are generated
   2564   # by rules or script phases and are placed in intermediate directories such
   2565   # as DerivedSources.
   2566   def IntermediatesGroup(self):
   2567     return self._GroupByName('Intermediates')
   2568 
   2569   # FrameworksGroup and ProjectsGroup are top-level groups used to collect
   2570   # frameworks and projects.
   2571   def FrameworksGroup(self):
   2572     return self._GroupByName('Frameworks')
   2573 
   2574   def ProjectsGroup(self):
   2575     return self._GroupByName('Projects')
   2576 
   2577   def RootGroupForPath(self, path):
   2578     """Returns a PBXGroup child of this object to which path should be added.
   2579 
   2580     This method is intended to choose between SourceGroup and
   2581     IntermediatesGroup on the basis of whether path is present in a source
   2582     directory or an intermediates directory.  For the purposes of this
   2583     determination, any path located within a derived file directory such as
   2584     PROJECT_DERIVED_FILE_DIR is treated as being in an intermediates
   2585     directory.
   2586 
   2587     The returned value is a two-element tuple.  The first element is the
   2588     PBXGroup, and the second element specifies whether that group should be
   2589     organized hierarchically (True) or as a single flat list (False).
   2590     """
   2591 
   2592     # TODO(mark): make this a class variable and bind to self on call?
   2593     # Also, this list is nowhere near exhaustive.
   2594     # INTERMEDIATE_DIR and SHARED_INTERMEDIATE_DIR are used by
   2595     # gyp.generator.xcode.  There should probably be some way for that module
   2596     # to push the names in, rather than having to hard-code them here.
   2597     source_tree_groups = {
   2598       'DERIVED_FILE_DIR':         (self.IntermediatesGroup, True),
   2599       'INTERMEDIATE_DIR':         (self.IntermediatesGroup, True),
   2600       'PROJECT_DERIVED_FILE_DIR': (self.IntermediatesGroup, True),
   2601       'SHARED_INTERMEDIATE_DIR':  (self.IntermediatesGroup, True),
   2602     }
   2603 
   2604     (source_tree, path) = SourceTreeAndPathFromPath(path)
   2605     if source_tree != None and source_tree in source_tree_groups:
   2606       (group_func, hierarchical) = source_tree_groups[source_tree]
   2607       group = group_func()
   2608       return (group, hierarchical)
   2609 
   2610     # TODO(mark): make additional choices based on file extension.
   2611 
   2612     return (self.SourceGroup(), True)
   2613 
   2614   def AddOrGetFileInRootGroup(self, path):
   2615     """Returns a PBXFileReference corresponding to path in the correct group
   2616     according to RootGroupForPath's heuristics.
   2617 
   2618     If an existing PBXFileReference for path exists, it will be returned.
   2619     Otherwise, one will be created and returned.
   2620     """
   2621 
   2622     (group, hierarchical) = self.RootGroupForPath(path)
   2623     return group.AddOrGetFileByPath(path, hierarchical)
   2624 
   2625   def RootGroupsTakeOverOnlyChildren(self, recurse=False):
   2626     """Calls TakeOverOnlyChild for all groups in the main group."""
   2627 
   2628     for group in self._properties['mainGroup']._properties['children']:
   2629       if isinstance(group, PBXGroup):
   2630         group.TakeOverOnlyChild(recurse)
   2631 
   2632   def SortGroups(self):
   2633     # Sort the children of the mainGroup (like "Source" and "Products")
   2634     # according to their defined order.
   2635     self._properties['mainGroup']._properties['children'] = \
   2636         sorted(self._properties['mainGroup']._properties['children'],
   2637                cmp=lambda x,y: x.CompareRootGroup(y))
   2638 
   2639     # Sort everything else by putting group before files, and going
   2640     # alphabetically by name within sections of groups and files.  SortGroup
   2641     # is recursive.
   2642     for group in self._properties['mainGroup']._properties['children']:
   2643       if not isinstance(group, PBXGroup):
   2644         continue
   2645 
   2646       if group.Name() == 'Products':
   2647         # The Products group is a special case.  Instead of sorting
   2648         # alphabetically, sort things in the order of the targets that
   2649         # produce the products.  To do this, just build up a new list of
   2650         # products based on the targets.
   2651         products = []
   2652         for target in self._properties['targets']:
   2653           if not isinstance(target, PBXNativeTarget):
   2654             continue
   2655           product = target._properties['productReference']
   2656           # Make sure that the product is already in the products group.
   2657           assert product in group._properties['children']
   2658           products.append(product)
   2659 
   2660         # Make sure that this process doesn't miss anything that was already
   2661         # in the products group.
   2662         assert len(products) == len(group._properties['children'])
   2663         group._properties['children'] = products
   2664       else:
   2665         group.SortGroup()
   2666 
   2667   def AddOrGetProjectReference(self, other_pbxproject):
   2668     """Add a reference to another project file (via PBXProject object) to this
   2669     one.
   2670 
   2671     Returns [ProductGroup, ProjectRef].  ProductGroup is a PBXGroup object in
   2672     this project file that contains a PBXReferenceProxy object for each
   2673     product of each PBXNativeTarget in the other project file.  ProjectRef is
   2674     a PBXFileReference to the other project file.
   2675 
   2676     If this project file already references the other project file, the
   2677     existing ProductGroup and ProjectRef are returned.  The ProductGroup will
   2678     still be updated if necessary.
   2679     """
   2680 
   2681     if not 'projectReferences' in self._properties:
   2682       self._properties['projectReferences'] = []
   2683 
   2684     product_group = None
   2685     project_ref = None
   2686 
   2687     if not other_pbxproject in self._other_pbxprojects:
   2688       # This project file isn't yet linked to the other one.  Establish the
   2689       # link.
   2690       product_group = PBXGroup({'name': 'Products'})
   2691 
   2692       # ProductGroup is strong.
   2693       product_group.parent = self
   2694 
   2695       # There's nothing unique about this PBXGroup, and if left alone, it will
   2696       # wind up with the same set of hashables as all other PBXGroup objects
   2697       # owned by the projectReferences list.  Add the hashables of the
   2698       # remote PBXProject that it's related to.
   2699       product_group._hashables.extend(other_pbxproject.Hashables())
   2700 
   2701       # The other project reports its path as relative to the same directory
   2702       # that this project's path is relative to.  The other project's path
   2703       # is not necessarily already relative to this project.  Figure out the
   2704       # pathname that this project needs to use to refer to the other one.
   2705       this_path = posixpath.dirname(self.Path())
   2706       projectDirPath = self.GetProperty('projectDirPath')
   2707       if projectDirPath:
   2708         if posixpath.isabs(projectDirPath[0]):
   2709           this_path = projectDirPath
   2710         else:
   2711           this_path = posixpath.join(this_path, projectDirPath)
   2712       other_path = gyp.common.RelativePath(other_pbxproject.Path(), this_path)
   2713 
   2714       # ProjectRef is weak (it's owned by the mainGroup hierarchy).
   2715       project_ref = PBXFileReference({
   2716             'lastKnownFileType': 'wrapper.pb-project',
   2717             'path':              other_path,
   2718             'sourceTree':        'SOURCE_ROOT',
   2719           })
   2720       self.ProjectsGroup().AppendChild(project_ref)
   2721 
   2722       ref_dict = {'ProductGroup': product_group, 'ProjectRef': project_ref}
   2723       self._other_pbxprojects[other_pbxproject] = ref_dict
   2724       self.AppendProperty('projectReferences', ref_dict)
   2725 
   2726       # Xcode seems to sort this list case-insensitively
   2727       self._properties['projectReferences'] = \
   2728           sorted(self._properties['projectReferences'], cmp=lambda x,y:
   2729                  cmp(x['ProjectRef'].Name().lower(),
   2730                      y['ProjectRef'].Name().lower()))
   2731     else:
   2732       # The link already exists.  Pull out the relevnt data.
   2733       project_ref_dict = self._other_pbxprojects[other_pbxproject]
   2734       product_group = project_ref_dict['ProductGroup']
   2735       project_ref = project_ref_dict['ProjectRef']
   2736 
   2737     self._SetUpProductReferences(other_pbxproject, product_group, project_ref)
   2738 
   2739     return [product_group, project_ref]
   2740 
   2741   def _SetUpProductReferences(self, other_pbxproject, product_group,
   2742                               project_ref):
   2743     # TODO(mark): This only adds references to products in other_pbxproject
   2744     # when they don't exist in this pbxproject.  Perhaps it should also
   2745     # remove references from this pbxproject that are no longer present in
   2746     # other_pbxproject.  Perhaps it should update various properties if they
   2747     # change.
   2748     for target in other_pbxproject._properties['targets']:
   2749       if not isinstance(target, PBXNativeTarget):
   2750         continue
   2751 
   2752       other_fileref = target._properties['productReference']
   2753       if product_group.GetChildByRemoteObject(other_fileref) is None:
   2754         # Xcode sets remoteInfo to the name of the target and not the name
   2755         # of its product, despite this proxy being a reference to the product.
   2756         container_item = PBXContainerItemProxy({
   2757               'containerPortal':      project_ref,
   2758               'proxyType':            2,
   2759               'remoteGlobalIDString': other_fileref,
   2760               'remoteInfo':           target.Name()
   2761             })
   2762         # TODO(mark): Does sourceTree get copied straight over from the other
   2763         # project?  Can the other project ever have lastKnownFileType here
   2764         # instead of explicitFileType?  (Use it if so?)  Can path ever be
   2765         # unset?  (I don't think so.)  Can other_fileref have name set, and
   2766         # does it impact the PBXReferenceProxy if so?  These are the questions
   2767         # that perhaps will be answered one day.
   2768         reference_proxy = PBXReferenceProxy({
   2769               'fileType':   other_fileref._properties['explicitFileType'],
   2770               'path':       other_fileref._properties['path'],
   2771               'sourceTree': other_fileref._properties['sourceTree'],
   2772               'remoteRef':  container_item,
   2773             })
   2774 
   2775         product_group.AppendChild(reference_proxy)
   2776 
   2777   def SortRemoteProductReferences(self):
   2778     # For each remote project file, sort the associated ProductGroup in the
   2779     # same order that the targets are sorted in the remote project file.  This
   2780     # is the sort order used by Xcode.
   2781 
   2782     def CompareProducts(x, y, remote_products):
   2783       # x and y are PBXReferenceProxy objects.  Go through their associated
   2784       # PBXContainerItem to get the remote PBXFileReference, which will be
   2785       # present in the remote_products list.
   2786       x_remote = x._properties['remoteRef']._properties['remoteGlobalIDString']
   2787       y_remote = y._properties['remoteRef']._properties['remoteGlobalIDString']
   2788       x_index = remote_products.index(x_remote)
   2789       y_index = remote_products.index(y_remote)
   2790 
   2791       # Use the order of each remote PBXFileReference in remote_products to
   2792       # determine the sort order.
   2793       return cmp(x_index, y_index)
   2794 
   2795     for other_pbxproject, ref_dict in self._other_pbxprojects.iteritems():
   2796       # Build up a list of products in the remote project file, ordered the
   2797       # same as the targets that produce them.
   2798       remote_products = []
   2799       for target in other_pbxproject._properties['targets']:
   2800         if not isinstance(target, PBXNativeTarget):
   2801           continue
   2802         remote_products.append(target._properties['productReference'])
   2803 
   2804       # Sort the PBXReferenceProxy children according to the list of remote
   2805       # products.
   2806       product_group = ref_dict['ProductGroup']
   2807       product_group._properties['children'] = sorted(
   2808           product_group._properties['children'],
   2809           cmp=lambda x, y: CompareProducts(x, y, remote_products))
   2810 
   2811 
   2812 class XCProjectFile(XCObject):
   2813   _schema = XCObject._schema.copy()
   2814   _schema.update({
   2815     'archiveVersion': [0, int,        0, 1, 1],
   2816     'classes':        [0, dict,       0, 1, {}],
   2817     'objectVersion':  [0, int,        0, 1, 45],
   2818     'rootObject':     [0, PBXProject, 1, 1],
   2819   })
   2820 
   2821   def SetXcodeVersion(self, version):
   2822     version_to_object_version = {
   2823       '2.4': 45,
   2824       '3.0': 45,
   2825       '3.1': 45,
   2826       '3.2': 46,
   2827     }
   2828     if not version in version_to_object_version:
   2829       supported_str = ', '.join(sorted(version_to_object_version.keys()))
   2830       raise Exception(
   2831           'Unsupported Xcode version %s (supported: %s)' %
   2832           ( version, supported_str ) )
   2833     compatibility_version = 'Xcode %s' % version
   2834     self._properties['rootObject'].SetProperty('compatibilityVersion',
   2835                                                compatibility_version)
   2836     self.SetProperty('objectVersion', version_to_object_version[version]);
   2837 
   2838   def ComputeIDs(self, recursive=True, overwrite=True, hash=None):
   2839     # Although XCProjectFile is implemented here as an XCObject, it's not a
   2840     # proper object in the Xcode sense, and it certainly doesn't have its own
   2841     # ID.  Pass through an attempt to update IDs to the real root object.
   2842     if recursive:
   2843       self._properties['rootObject'].ComputeIDs(recursive, overwrite, hash)
   2844 
   2845   def Print(self, file=sys.stdout):
   2846     self.VerifyHasRequiredProperties()
   2847 
   2848     # Add the special "objects" property, which will be caught and handled
   2849     # separately during printing.  This structure allows a fairly standard
   2850     # loop do the normal printing.
   2851     self._properties['objects'] = {}
   2852     self._XCPrint(file, 0, '// !$*UTF8*$!\n')
   2853     if self._should_print_single_line:
   2854       self._XCPrint(file, 0, '{ ')
   2855     else:
   2856       self._XCPrint(file, 0, '{\n')
   2857     for property, value in sorted(self._properties.iteritems(),
   2858                                   cmp=lambda x, y: cmp(x, y)):
   2859       if property == 'objects':
   2860         self._PrintObjects(file)
   2861       else:
   2862         self._XCKVPrint(file, 1, property, value)
   2863     self._XCPrint(file, 0, '}\n')
   2864     del self._properties['objects']
   2865 
   2866   def _PrintObjects(self, file):
   2867     if self._should_print_single_line:
   2868       self._XCPrint(file, 0, 'objects = {')
   2869     else:
   2870       self._XCPrint(file, 1, 'objects = {\n')
   2871 
   2872     objects_by_class = {}
   2873     for object in self.Descendants():
   2874       if object == self:
   2875         continue
   2876       class_name = object.__class__.__name__
   2877       if not class_name in objects_by_class:
   2878         objects_by_class[class_name] = []
   2879       objects_by_class[class_name].append(object)
   2880 
   2881     for class_name in sorted(objects_by_class):
   2882       self._XCPrint(file, 0, '\n')
   2883       self._XCPrint(file, 0, '/* Begin ' + class_name + ' section */\n')
   2884       for object in sorted(objects_by_class[class_name],
   2885                            cmp=lambda x, y: cmp(x.id, y.id)):
   2886         object.Print(file)
   2887       self._XCPrint(file, 0, '/* End ' + class_name + ' section */\n')
   2888 
   2889     if self._should_print_single_line:
   2890       self._XCPrint(file, 0, '}; ')
   2891     else:
   2892       self._XCPrint(file, 1, '};\n')
   2893