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         'xib':         'file.xib',
   1516         'y':           'sourcecode.yacc',
   1517       }
   1518 
   1519       prop_map = {
   1520         'dart':        'explicitFileType',
   1521         'gyp':         'explicitFileType',
   1522         'gypi':        'explicitFileType',
   1523       }
   1524 
   1525       if is_dir:
   1526         file_type = 'folder'
   1527         prop_name = 'lastKnownFileType'
   1528       else:
   1529         basename = posixpath.basename(self._properties['path'])
   1530         (root, ext) = posixpath.splitext(basename)
   1531         # Check the map using a lowercase extension.
   1532         # TODO(mark): Maybe it should try with the original case first and fall
   1533         # back to lowercase, in case there are any instances where case
   1534         # matters.  There currently aren't.
   1535         if ext != '':
   1536           ext = ext[1:].lower()
   1537 
   1538         # TODO(mark): "text" is the default value, but "file" is appropriate
   1539         # for unrecognized files not containing text.  Xcode seems to choose
   1540         # based on content.
   1541         file_type = extension_map.get(ext, 'text')
   1542         prop_name = prop_map.get(ext, 'lastKnownFileType')
   1543 
   1544       self._properties[prop_name] = file_type
   1545 
   1546 
   1547 class PBXVariantGroup(PBXGroup, XCFileLikeElement):
   1548   """PBXVariantGroup is used by Xcode to represent localizations."""
   1549   # No additions to the schema relative to PBXGroup.
   1550   pass
   1551 
   1552 
   1553 # PBXReferenceProxy is also an XCFileLikeElement subclass.  It is defined below
   1554 # because it uses PBXContainerItemProxy, defined below.
   1555 
   1556 
   1557 class XCBuildConfiguration(XCObject):
   1558   _schema = XCObject._schema.copy()
   1559   _schema.update({
   1560     'baseConfigurationReference': [0, PBXFileReference, 0, 0],
   1561     'buildSettings':              [0, dict, 0, 1, {}],
   1562     'name':                       [0, str,  0, 1],
   1563   })
   1564 
   1565   def HasBuildSetting(self, key):
   1566     return key in self._properties['buildSettings']
   1567 
   1568   def GetBuildSetting(self, key):
   1569     return self._properties['buildSettings'][key]
   1570 
   1571   def SetBuildSetting(self, key, value):
   1572     # TODO(mark): If a list, copy?
   1573     self._properties['buildSettings'][key] = value
   1574 
   1575   def AppendBuildSetting(self, key, value):
   1576     if not key in self._properties['buildSettings']:
   1577       self._properties['buildSettings'][key] = []
   1578     self._properties['buildSettings'][key].append(value)
   1579 
   1580   def DelBuildSetting(self, key):
   1581     if key in self._properties['buildSettings']:
   1582       del self._properties['buildSettings'][key]
   1583 
   1584   def SetBaseConfiguration(self, value):
   1585     self._properties['baseConfigurationReference'] = value
   1586 
   1587 class XCConfigurationList(XCObject):
   1588   # _configs is the default list of configurations.
   1589   _configs = [ XCBuildConfiguration({'name': 'Debug'}),
   1590                XCBuildConfiguration({'name': 'Release'}) ]
   1591 
   1592   _schema = XCObject._schema.copy()
   1593   _schema.update({
   1594     'buildConfigurations':           [1, XCBuildConfiguration, 1, 1, _configs],
   1595     'defaultConfigurationIsVisible': [0, int,                  0, 1, 1],
   1596     'defaultConfigurationName':      [0, str,                  0, 1, 'Release'],
   1597   })
   1598 
   1599   def Name(self):
   1600     return 'Build configuration list for ' + \
   1601            self.parent.__class__.__name__ + ' "' + self.parent.Name() + '"'
   1602 
   1603   def ConfigurationNamed(self, name):
   1604     """Convenience accessor to obtain an XCBuildConfiguration by name."""
   1605     for configuration in self._properties['buildConfigurations']:
   1606       if configuration._properties['name'] == name:
   1607         return configuration
   1608 
   1609     raise KeyError, name
   1610 
   1611   def DefaultConfiguration(self):
   1612     """Convenience accessor to obtain the default XCBuildConfiguration."""
   1613     return self.ConfigurationNamed(self._properties['defaultConfigurationName'])
   1614 
   1615   def HasBuildSetting(self, key):
   1616     """Determines the state of a build setting in all XCBuildConfiguration
   1617     child objects.
   1618 
   1619     If all child objects have key in their build settings, and the value is the
   1620     same in all child objects, returns 1.
   1621 
   1622     If no child objects have the key in their build settings, returns 0.
   1623 
   1624     If some, but not all, child objects have the key in their build settings,
   1625     or if any children have different values for the key, returns -1.
   1626     """
   1627 
   1628     has = None
   1629     value = None
   1630     for configuration in self._properties['buildConfigurations']:
   1631       configuration_has = configuration.HasBuildSetting(key)
   1632       if has is None:
   1633         has = configuration_has
   1634       elif has != configuration_has:
   1635         return -1
   1636 
   1637       if configuration_has:
   1638         configuration_value = configuration.GetBuildSetting(key)
   1639         if value is None:
   1640           value = configuration_value
   1641         elif value != configuration_value:
   1642           return -1
   1643 
   1644     if not has:
   1645       return 0
   1646 
   1647     return 1
   1648 
   1649   def GetBuildSetting(self, key):
   1650     """Gets the build setting for key.
   1651 
   1652     All child XCConfiguration objects must have the same value set for the
   1653     setting, or a ValueError will be raised.
   1654     """
   1655 
   1656     # TODO(mark): This is wrong for build settings that are lists.  The list
   1657     # contents should be compared (and a list copy returned?)
   1658 
   1659     value = None
   1660     for configuration in self._properties['buildConfigurations']:
   1661       configuration_value = configuration.GetBuildSetting(key)
   1662       if value is None:
   1663         value = configuration_value
   1664       else:
   1665         if value != configuration_value:
   1666           raise ValueError, 'Variant values for ' + key
   1667 
   1668     return value
   1669 
   1670   def SetBuildSetting(self, key, value):
   1671     """Sets the build setting for key to value in all child
   1672     XCBuildConfiguration objects.
   1673     """
   1674 
   1675     for configuration in self._properties['buildConfigurations']:
   1676       configuration.SetBuildSetting(key, value)
   1677 
   1678   def AppendBuildSetting(self, key, value):
   1679     """Appends value to the build setting for key, which is treated as a list,
   1680     in all child XCBuildConfiguration objects.
   1681     """
   1682 
   1683     for configuration in self._properties['buildConfigurations']:
   1684       configuration.AppendBuildSetting(key, value)
   1685 
   1686   def DelBuildSetting(self, key):
   1687     """Deletes the build setting key from all child XCBuildConfiguration
   1688     objects.
   1689     """
   1690 
   1691     for configuration in self._properties['buildConfigurations']:
   1692       configuration.DelBuildSetting(key)
   1693 
   1694   def SetBaseConfiguration(self, value):
   1695     """Sets the build configuration in all child XCBuildConfiguration objects.
   1696     """
   1697 
   1698     for configuration in self._properties['buildConfigurations']:
   1699       configuration.SetBaseConfiguration(value)
   1700 
   1701 
   1702 class PBXBuildFile(XCObject):
   1703   _schema = XCObject._schema.copy()
   1704   _schema.update({
   1705     'fileRef':  [0, XCFileLikeElement, 0, 1],
   1706     'settings': [0, str,               0, 0],  # hack, it's a dict
   1707   })
   1708 
   1709   # Weird output rules for PBXBuildFile.
   1710   _should_print_single_line = True
   1711   _encode_transforms = XCObject._alternate_encode_transforms
   1712 
   1713   def Name(self):
   1714     # Example: "main.cc in Sources"
   1715     return self._properties['fileRef'].Name() + ' in ' + self.parent.Name()
   1716 
   1717   def Hashables(self):
   1718     # super
   1719     hashables = XCObject.Hashables(self)
   1720 
   1721     # It is not sufficient to just rely on Name() to get the
   1722     # XCFileLikeElement's name, because that is not a complete pathname.
   1723     # PathHashables returns hashables unique enough that no two
   1724     # PBXBuildFiles should wind up with the same set of hashables, unless
   1725     # someone adds the same file multiple times to the same target.  That
   1726     # would be considered invalid anyway.
   1727     hashables.extend(self._properties['fileRef'].PathHashables())
   1728 
   1729     return hashables
   1730 
   1731 
   1732 class XCBuildPhase(XCObject):
   1733   """Abstract base for build phase classes.  Not represented in a project
   1734   file.
   1735 
   1736   Attributes:
   1737     _files_by_path: A dict mapping each path of a child in the files list by
   1738       path (keys) to the corresponding PBXBuildFile children (values).
   1739     _files_by_xcfilelikeelement: A dict mapping each XCFileLikeElement (keys)
   1740       to the corresponding PBXBuildFile children (values).
   1741   """
   1742 
   1743   # TODO(mark): Some build phase types, like PBXShellScriptBuildPhase, don't
   1744   # actually have a "files" list.  XCBuildPhase should not have "files" but
   1745   # another abstract subclass of it should provide this, and concrete build
   1746   # phase types that do have "files" lists should be derived from that new
   1747   # abstract subclass.  XCBuildPhase should only provide buildActionMask and
   1748   # runOnlyForDeploymentPostprocessing, and not files or the various
   1749   # file-related methods and attributes.
   1750 
   1751   _schema = XCObject._schema.copy()
   1752   _schema.update({
   1753     'buildActionMask':                    [0, int,          0, 1, 0x7fffffff],
   1754     'files':                              [1, PBXBuildFile, 1, 1, []],
   1755     'runOnlyForDeploymentPostprocessing': [0, int,          0, 1, 0],
   1756   })
   1757 
   1758   def __init__(self, properties=None, id=None, parent=None):
   1759     # super
   1760     XCObject.__init__(self, properties, id, parent)
   1761 
   1762     self._files_by_path = {}
   1763     self._files_by_xcfilelikeelement = {}
   1764     for pbxbuildfile in self._properties.get('files', []):
   1765       self._AddBuildFileToDicts(pbxbuildfile)
   1766 
   1767   def FileGroup(self, path):
   1768     # Subclasses must override this by returning a two-element tuple.  The
   1769     # first item in the tuple should be the PBXGroup to which "path" should be
   1770     # added, either as a child or deeper descendant.  The second item should
   1771     # be a boolean indicating whether files should be added into hierarchical
   1772     # groups or one single flat group.
   1773     raise NotImplementedError, \
   1774           self.__class__.__name__ + ' must implement FileGroup'
   1775 
   1776   def _AddPathToDict(self, pbxbuildfile, path):
   1777     """Adds path to the dict tracking paths belonging to this build phase.
   1778 
   1779     If the path is already a member of this build phase, raises an exception.
   1780     """
   1781 
   1782     if path in self._files_by_path:
   1783       raise ValueError, 'Found multiple build files with path ' + path
   1784     self._files_by_path[path] = pbxbuildfile
   1785 
   1786   def _AddBuildFileToDicts(self, pbxbuildfile, path=None):
   1787     """Maintains the _files_by_path and _files_by_xcfilelikeelement dicts.
   1788 
   1789     If path is specified, then it is the path that is being added to the
   1790     phase, and pbxbuildfile must contain either a PBXFileReference directly
   1791     referencing that path, or it must contain a PBXVariantGroup that itself
   1792     contains a PBXFileReference referencing the path.
   1793 
   1794     If path is not specified, either the PBXFileReference's path or the paths
   1795     of all children of the PBXVariantGroup are taken as being added to the
   1796     phase.
   1797 
   1798     If the path is already present in the phase, raises an exception.
   1799 
   1800     If the PBXFileReference or PBXVariantGroup referenced by pbxbuildfile
   1801     are already present in the phase, referenced by a different PBXBuildFile
   1802     object, raises an exception.  This does not raise an exception when
   1803     a PBXFileReference or PBXVariantGroup reappear and are referenced by the
   1804     same PBXBuildFile that has already introduced them, because in the case
   1805     of PBXVariantGroup objects, they may correspond to multiple paths that are
   1806     not all added simultaneously.  When this situation occurs, the path needs
   1807     to be added to _files_by_path, but nothing needs to change in
   1808     _files_by_xcfilelikeelement, and the caller should have avoided adding
   1809     the PBXBuildFile if it is already present in the list of children.
   1810     """
   1811 
   1812     xcfilelikeelement = pbxbuildfile._properties['fileRef']
   1813 
   1814     paths = []
   1815     if path != None:
   1816       # It's best when the caller provides the path.
   1817       if isinstance(xcfilelikeelement, PBXVariantGroup):
   1818         paths.append(path)
   1819     else:
   1820       # If the caller didn't provide a path, there can be either multiple
   1821       # paths (PBXVariantGroup) or one.
   1822       if isinstance(xcfilelikeelement, PBXVariantGroup):
   1823         for variant in xcfilelikeelement._properties['children']:
   1824           paths.append(variant.FullPath())
   1825       else:
   1826         paths.append(xcfilelikeelement.FullPath())
   1827 
   1828     # Add the paths first, because if something's going to raise, the
   1829     # messages provided by _AddPathToDict are more useful owing to its
   1830     # having access to a real pathname and not just an object's Name().
   1831     for a_path in paths:
   1832       self._AddPathToDict(pbxbuildfile, a_path)
   1833 
   1834     # If another PBXBuildFile references this XCFileLikeElement, there's a
   1835     # problem.
   1836     if xcfilelikeelement in self._files_by_xcfilelikeelement and \
   1837        self._files_by_xcfilelikeelement[xcfilelikeelement] != pbxbuildfile:
   1838       raise ValueError, 'Found multiple build files for ' + \
   1839                         xcfilelikeelement.Name()
   1840     self._files_by_xcfilelikeelement[xcfilelikeelement] = pbxbuildfile
   1841 
   1842   def AppendBuildFile(self, pbxbuildfile, path=None):
   1843     # Callers should use this instead of calling
   1844     # AppendProperty('files', pbxbuildfile) directly because this function
   1845     # maintains the object's dicts.  Better yet, callers can just call AddFile
   1846     # with a pathname and not worry about building their own PBXBuildFile
   1847     # objects.
   1848     self.AppendProperty('files', pbxbuildfile)
   1849     self._AddBuildFileToDicts(pbxbuildfile, path)
   1850 
   1851   def AddFile(self, path, settings=None):
   1852     (file_group, hierarchical) = self.FileGroup(path)
   1853     file_ref = file_group.AddOrGetFileByPath(path, hierarchical)
   1854 
   1855     if file_ref in self._files_by_xcfilelikeelement and \
   1856        isinstance(file_ref, PBXVariantGroup):
   1857       # There's already a PBXBuildFile in this phase corresponding to the
   1858       # PBXVariantGroup.  path just provides a new variant that belongs to
   1859       # the group.  Add the path to the dict.
   1860       pbxbuildfile = self._files_by_xcfilelikeelement[file_ref]
   1861       self._AddBuildFileToDicts(pbxbuildfile, path)
   1862     else:
   1863       # Add a new PBXBuildFile to get file_ref into the phase.
   1864       if settings is None:
   1865         pbxbuildfile = PBXBuildFile({'fileRef': file_ref})
   1866       else:
   1867         pbxbuildfile = PBXBuildFile({'fileRef': file_ref, 'settings': settings})
   1868       self.AppendBuildFile(pbxbuildfile, path)
   1869 
   1870 
   1871 class PBXHeadersBuildPhase(XCBuildPhase):
   1872   # No additions to the schema relative to XCBuildPhase.
   1873 
   1874   def Name(self):
   1875     return 'Headers'
   1876 
   1877   def FileGroup(self, path):
   1878     return self.PBXProjectAncestor().RootGroupForPath(path)
   1879 
   1880 
   1881 class PBXResourcesBuildPhase(XCBuildPhase):
   1882   # No additions to the schema relative to XCBuildPhase.
   1883 
   1884   def Name(self):
   1885     return 'Resources'
   1886 
   1887   def FileGroup(self, path):
   1888     return self.PBXProjectAncestor().RootGroupForPath(path)
   1889 
   1890 
   1891 class PBXSourcesBuildPhase(XCBuildPhase):
   1892   # No additions to the schema relative to XCBuildPhase.
   1893 
   1894   def Name(self):
   1895     return 'Sources'
   1896 
   1897   def FileGroup(self, path):
   1898     return self.PBXProjectAncestor().RootGroupForPath(path)
   1899 
   1900 
   1901 class PBXFrameworksBuildPhase(XCBuildPhase):
   1902   # No additions to the schema relative to XCBuildPhase.
   1903 
   1904   def Name(self):
   1905     return 'Frameworks'
   1906 
   1907   def FileGroup(self, path):
   1908     (root, ext) = posixpath.splitext(path)
   1909     if ext != '':
   1910       ext = ext[1:].lower()
   1911     if ext == 'o':
   1912       # .o files are added to Xcode Frameworks phases, but conceptually aren't
   1913       # frameworks, they're more like sources or intermediates. Redirect them
   1914       # to show up in one of those other groups.
   1915       return self.PBXProjectAncestor().RootGroupForPath(path)
   1916     else:
   1917       return (self.PBXProjectAncestor().FrameworksGroup(), False)
   1918 
   1919 
   1920 class PBXShellScriptBuildPhase(XCBuildPhase):
   1921   _schema = XCBuildPhase._schema.copy()
   1922   _schema.update({
   1923     'inputPaths':       [1, str, 0, 1, []],
   1924     'name':             [0, str, 0, 0],
   1925     'outputPaths':      [1, str, 0, 1, []],
   1926     'shellPath':        [0, str, 0, 1, '/bin/sh'],
   1927     'shellScript':      [0, str, 0, 1],
   1928     'showEnvVarsInLog': [0, int, 0, 0],
   1929   })
   1930 
   1931   def Name(self):
   1932     if 'name' in self._properties:
   1933       return self._properties['name']
   1934 
   1935     return 'ShellScript'
   1936 
   1937 
   1938 class PBXCopyFilesBuildPhase(XCBuildPhase):
   1939   _schema = XCBuildPhase._schema.copy()
   1940   _schema.update({
   1941     'dstPath':          [0, str, 0, 1],
   1942     'dstSubfolderSpec': [0, int, 0, 1],
   1943     'name':             [0, str, 0, 0],
   1944   })
   1945 
   1946   # path_tree_re matches "$(DIR)/path" or just "$(DIR)".  Match group 1 is
   1947   # "DIR", match group 3 is "path" or None.
   1948   path_tree_re = re.compile('^\\$\\((.*)\\)(/(.*)|)$')
   1949 
   1950   # path_tree_to_subfolder maps names of Xcode variables to the associated
   1951   # dstSubfolderSpec property value used in a PBXCopyFilesBuildPhase object.
   1952   path_tree_to_subfolder = {
   1953     'BUILT_PRODUCTS_DIR': 16,  # Products Directory
   1954     # Other types that can be chosen via the Xcode UI.
   1955     # TODO(mark): Map Xcode variable names to these.
   1956     # : 1,  # Wrapper
   1957     # : 6,  # Executables: 6
   1958     # : 7,  # Resources
   1959     # : 15,  # Java Resources
   1960     # : 10,  # Frameworks
   1961     # : 11,  # Shared Frameworks
   1962     # : 12,  # Shared Support
   1963     # : 13,  # PlugIns
   1964   }
   1965 
   1966   def Name(self):
   1967     if 'name' in self._properties:
   1968       return self._properties['name']
   1969 
   1970     return 'CopyFiles'
   1971 
   1972   def FileGroup(self, path):
   1973     return self.PBXProjectAncestor().RootGroupForPath(path)
   1974 
   1975   def SetDestination(self, path):
   1976     """Set the dstSubfolderSpec and dstPath properties from path.
   1977 
   1978     path may be specified in the same notation used for XCHierarchicalElements,
   1979     specifically, "$(DIR)/path".
   1980     """
   1981 
   1982     path_tree_match = self.path_tree_re.search(path)
   1983     if path_tree_match:
   1984       # Everything else needs to be relative to an Xcode variable.
   1985       path_tree = path_tree_match.group(1)
   1986       relative_path = path_tree_match.group(3)
   1987 
   1988       if path_tree in self.path_tree_to_subfolder:
   1989         subfolder = self.path_tree_to_subfolder[path_tree]
   1990         if relative_path is None:
   1991           relative_path = ''
   1992       else:
   1993         # The path starts with an unrecognized Xcode variable
   1994         # name like $(SRCROOT).  Xcode will still handle this
   1995         # as an "absolute path" that starts with the variable.
   1996         subfolder = 0
   1997         relative_path = path
   1998     elif path.startswith('/'):
   1999       # Special case.  Absolute paths are in dstSubfolderSpec 0.
   2000       subfolder = 0
   2001       relative_path = path[1:]
   2002     else:
   2003       raise ValueError, 'Can\'t use path %s in a %s' % \
   2004                         (path, self.__class__.__name__)
   2005 
   2006     self._properties['dstPath'] = relative_path
   2007     self._properties['dstSubfolderSpec'] = subfolder
   2008 
   2009 
   2010 class PBXBuildRule(XCObject):
   2011   _schema = XCObject._schema.copy()
   2012   _schema.update({
   2013     'compilerSpec': [0, str, 0, 1],
   2014     'filePatterns': [0, str, 0, 0],
   2015     'fileType':     [0, str, 0, 1],
   2016     'isEditable':   [0, int, 0, 1, 1],
   2017     'outputFiles':  [1, str, 0, 1, []],
   2018     'script':       [0, str, 0, 0],
   2019   })
   2020 
   2021   def Name(self):
   2022     # Not very inspired, but it's what Xcode uses.
   2023     return self.__class__.__name__
   2024 
   2025   def Hashables(self):
   2026     # super
   2027     hashables = XCObject.Hashables(self)
   2028 
   2029     # Use the hashables of the weak objects that this object refers to.
   2030     hashables.append(self._properties['fileType'])
   2031     if 'filePatterns' in self._properties:
   2032       hashables.append(self._properties['filePatterns'])
   2033     return hashables
   2034 
   2035 
   2036 class PBXContainerItemProxy(XCObject):
   2037   # When referencing an item in this project file, containerPortal is the
   2038   # PBXProject root object of this project file.  When referencing an item in
   2039   # another project file, containerPortal is a PBXFileReference identifying
   2040   # the other project file.
   2041   #
   2042   # When serving as a proxy to an XCTarget (in this project file or another),
   2043   # proxyType is 1.  When serving as a proxy to a PBXFileReference (in another
   2044   # project file), proxyType is 2.  Type 2 is used for references to the
   2045   # producs of the other project file's targets.
   2046   #
   2047   # Xcode is weird about remoteGlobalIDString.  Usually, it's printed without
   2048   # a comment, indicating that it's tracked internally simply as a string, but
   2049   # sometimes it's printed with a comment (usually when the object is initially
   2050   # created), indicating that it's tracked as a project file object at least
   2051   # sometimes.  This module always tracks it as an object, but contains a hack
   2052   # to prevent it from printing the comment in the project file output.  See
   2053   # _XCKVPrint.
   2054   _schema = XCObject._schema.copy()
   2055   _schema.update({
   2056     'containerPortal':      [0, XCContainerPortal, 0, 1],
   2057     'proxyType':            [0, int,               0, 1],
   2058     'remoteGlobalIDString': [0, XCRemoteObject,    0, 1],
   2059     'remoteInfo':           [0, str,               0, 1],
   2060   })
   2061 
   2062   def __repr__(self):
   2063     props = self._properties
   2064     name = '%s.gyp:%s' % (props['containerPortal'].Name(), props['remoteInfo'])
   2065     return '<%s %r at 0x%x>' % (self.__class__.__name__, name, id(self))
   2066 
   2067   def Name(self):
   2068     # Admittedly not the best name, but it's what Xcode uses.
   2069     return self.__class__.__name__
   2070 
   2071   def Hashables(self):
   2072     # super
   2073     hashables = XCObject.Hashables(self)
   2074 
   2075     # Use the hashables of the weak objects that this object refers to.
   2076     hashables.extend(self._properties['containerPortal'].Hashables())
   2077     hashables.extend(self._properties['remoteGlobalIDString'].Hashables())
   2078     return hashables
   2079 
   2080 
   2081 class PBXTargetDependency(XCObject):
   2082   # The "target" property accepts an XCTarget object, and obviously not
   2083   # NoneType.  But XCTarget is defined below, so it can't be put into the
   2084   # schema yet.  The definition of PBXTargetDependency can't be moved below
   2085   # XCTarget because XCTarget's own schema references PBXTargetDependency.
   2086   # Python doesn't deal well with this circular relationship, and doesn't have
   2087   # a real way to do forward declarations.  To work around, the type of
   2088   # the "target" property is reset below, after XCTarget is defined.
   2089   #
   2090   # At least one of "name" and "target" is required.
   2091   _schema = XCObject._schema.copy()
   2092   _schema.update({
   2093     'name':        [0, str,                   0, 0],
   2094     'target':      [0, None.__class__,        0, 0],
   2095     'targetProxy': [0, PBXContainerItemProxy, 1, 1],
   2096   })
   2097 
   2098   def __repr__(self):
   2099     name = self._properties.get('name') or self._properties['target'].Name()
   2100     return '<%s %r at 0x%x>' % (self.__class__.__name__, name, id(self))
   2101 
   2102   def Name(self):
   2103     # Admittedly not the best name, but it's what Xcode uses.
   2104     return self.__class__.__name__
   2105 
   2106   def Hashables(self):
   2107     # super
   2108     hashables = XCObject.Hashables(self)
   2109 
   2110     # Use the hashables of the weak objects that this object refers to.
   2111     hashables.extend(self._properties['targetProxy'].Hashables())
   2112     return hashables
   2113 
   2114 
   2115 class PBXReferenceProxy(XCFileLikeElement):
   2116   _schema = XCFileLikeElement._schema.copy()
   2117   _schema.update({
   2118     'fileType':  [0, str,                   0, 1],
   2119     'path':      [0, str,                   0, 1],
   2120     'remoteRef': [0, PBXContainerItemProxy, 1, 1],
   2121   })
   2122 
   2123 
   2124 class XCTarget(XCRemoteObject):
   2125   # An XCTarget is really just an XCObject, the XCRemoteObject thing is just
   2126   # to allow PBXProject to be used in the remoteGlobalIDString property of
   2127   # PBXContainerItemProxy.
   2128   #
   2129   # Setting a "name" property at instantiation may also affect "productName",
   2130   # which may in turn affect the "PRODUCT_NAME" build setting in children of
   2131   # "buildConfigurationList".  See __init__ below.
   2132   _schema = XCRemoteObject._schema.copy()
   2133   _schema.update({
   2134     'buildConfigurationList': [0, XCConfigurationList, 1, 1,
   2135                                XCConfigurationList()],
   2136     'buildPhases':            [1, XCBuildPhase,        1, 1, []],
   2137     'dependencies':           [1, PBXTargetDependency, 1, 1, []],
   2138     'name':                   [0, str,                 0, 1],
   2139     'productName':            [0, str,                 0, 1],
   2140   })
   2141 
   2142   def __init__(self, properties=None, id=None, parent=None,
   2143                force_outdir=None, force_prefix=None, force_extension=None):
   2144     # super
   2145     XCRemoteObject.__init__(self, properties, id, parent)
   2146 
   2147     # Set up additional defaults not expressed in the schema.  If a "name"
   2148     # property was supplied, set "productName" if it is not present.  Also set
   2149     # the "PRODUCT_NAME" build setting in each configuration, but only if
   2150     # the setting is not present in any build configuration.
   2151     if 'name' in self._properties:
   2152       if not 'productName' in self._properties:
   2153         self.SetProperty('productName', self._properties['name'])
   2154 
   2155     if 'productName' in self._properties:
   2156       if 'buildConfigurationList' in self._properties:
   2157         configs = self._properties['buildConfigurationList']
   2158         if configs.HasBuildSetting('PRODUCT_NAME') == 0:
   2159           configs.SetBuildSetting('PRODUCT_NAME',
   2160                                   self._properties['productName'])
   2161 
   2162   def AddDependency(self, other):
   2163     pbxproject = self.PBXProjectAncestor()
   2164     other_pbxproject = other.PBXProjectAncestor()
   2165     if pbxproject == other_pbxproject:
   2166       # Add a dependency to another target in the same project file.
   2167       container = PBXContainerItemProxy({'containerPortal':      pbxproject,
   2168                                          'proxyType':            1,
   2169                                          'remoteGlobalIDString': other,
   2170                                          'remoteInfo':           other.Name()})
   2171       dependency = PBXTargetDependency({'target':      other,
   2172                                         'targetProxy': container})
   2173       self.AppendProperty('dependencies', dependency)
   2174     else:
   2175       # Add a dependency to a target in a different project file.
   2176       other_project_ref = \
   2177           pbxproject.AddOrGetProjectReference(other_pbxproject)[1]
   2178       container = PBXContainerItemProxy({
   2179             'containerPortal':      other_project_ref,
   2180             'proxyType':            1,
   2181             'remoteGlobalIDString': other,
   2182             'remoteInfo':           other.Name(),
   2183           })
   2184       dependency = PBXTargetDependency({'name':        other.Name(),
   2185                                         'targetProxy': container})
   2186       self.AppendProperty('dependencies', dependency)
   2187 
   2188   # Proxy all of these through to the build configuration list.
   2189 
   2190   def ConfigurationNamed(self, name):
   2191     return self._properties['buildConfigurationList'].ConfigurationNamed(name)
   2192 
   2193   def DefaultConfiguration(self):
   2194     return self._properties['buildConfigurationList'].DefaultConfiguration()
   2195 
   2196   def HasBuildSetting(self, key):
   2197     return self._properties['buildConfigurationList'].HasBuildSetting(key)
   2198 
   2199   def GetBuildSetting(self, key):
   2200     return self._properties['buildConfigurationList'].GetBuildSetting(key)
   2201 
   2202   def SetBuildSetting(self, key, value):
   2203     return self._properties['buildConfigurationList'].SetBuildSetting(key, \
   2204                                                                       value)
   2205 
   2206   def AppendBuildSetting(self, key, value):
   2207     return self._properties['buildConfigurationList'].AppendBuildSetting(key, \
   2208                                                                          value)
   2209 
   2210   def DelBuildSetting(self, key):
   2211     return self._properties['buildConfigurationList'].DelBuildSetting(key)
   2212 
   2213 
   2214 # Redefine the type of the "target" property.  See PBXTargetDependency._schema
   2215 # above.
   2216 PBXTargetDependency._schema['target'][1] = XCTarget
   2217 
   2218 
   2219 class PBXNativeTarget(XCTarget):
   2220   # buildPhases is overridden in the schema to be able to set defaults.
   2221   #
   2222   # NOTE: Contrary to most objects, it is advisable to set parent when
   2223   # constructing PBXNativeTarget.  A parent of an XCTarget must be a PBXProject
   2224   # object.  A parent reference is required for a PBXNativeTarget during
   2225   # construction to be able to set up the target defaults for productReference,
   2226   # because a PBXBuildFile object must be created for the target and it must
   2227   # be added to the PBXProject's mainGroup hierarchy.
   2228   _schema = XCTarget._schema.copy()
   2229   _schema.update({
   2230     'buildPhases':      [1, XCBuildPhase,     1, 1,
   2231                          [PBXSourcesBuildPhase(), PBXFrameworksBuildPhase()]],
   2232     'buildRules':       [1, PBXBuildRule,     1, 1, []],
   2233     'productReference': [0, PBXFileReference, 0, 1],
   2234     'productType':      [0, str,              0, 1],
   2235   })
   2236 
   2237   # Mapping from Xcode product-types to settings.  The settings are:
   2238   #  filetype : used for explicitFileType in the project file
   2239   #  prefix : the prefix for the file name
   2240   #  suffix : the suffix for the filen ame
   2241   _product_filetypes = {
   2242     'com.apple.product-type.application':       ['wrapper.application',
   2243                                                  '', '.app'],
   2244     'com.apple.product-type.bundle':            ['wrapper.cfbundle',
   2245                                                  '', '.bundle'],
   2246     'com.apple.product-type.framework':         ['wrapper.framework',
   2247                                                  '', '.framework'],
   2248     'com.apple.product-type.library.dynamic':   ['compiled.mach-o.dylib',
   2249                                                  'lib', '.dylib'],
   2250     'com.apple.product-type.library.static':    ['archive.ar',
   2251                                                  'lib', '.a'],
   2252     'com.apple.product-type.tool':              ['compiled.mach-o.executable',
   2253                                                  '', ''],
   2254     'com.apple.product-type.bundle.unit-test':  ['wrapper.cfbundle',
   2255                                                  '', '.xctest'],
   2256     'com.googlecode.gyp.xcode.bundle':          ['compiled.mach-o.dylib',
   2257                                                  '', '.so'],
   2258   }
   2259 
   2260   def __init__(self, properties=None, id=None, parent=None,
   2261                force_outdir=None, force_prefix=None, force_extension=None):
   2262     # super
   2263     XCTarget.__init__(self, properties, id, parent)
   2264 
   2265     if 'productName' in self._properties and \
   2266        'productType' in self._properties and \
   2267        not 'productReference' in self._properties and \
   2268        self._properties['productType'] in self._product_filetypes:
   2269       products_group = None
   2270       pbxproject = self.PBXProjectAncestor()
   2271       if pbxproject != None:
   2272         products_group = pbxproject.ProductsGroup()
   2273 
   2274       if products_group != None:
   2275         (filetype, prefix, suffix) = \
   2276             self._product_filetypes[self._properties['productType']]
   2277         # Xcode does not have a distinct type for loadable modules that are
   2278         # pure BSD targets (not in a bundle wrapper). GYP allows such modules
   2279         # to be specified by setting a target type to loadable_module without
   2280         # having mac_bundle set. These are mapped to the pseudo-product type
   2281         # com.googlecode.gyp.xcode.bundle.
   2282         #
   2283         # By picking up this special type and converting it to a dynamic
   2284         # library (com.apple.product-type.library.dynamic) with fix-ups,
   2285         # single-file loadable modules can be produced.
   2286         #
   2287         # MACH_O_TYPE is changed to mh_bundle to produce the proper file type
   2288         # (as opposed to mh_dylib). In order for linking to succeed,
   2289         # DYLIB_CURRENT_VERSION and DYLIB_COMPATIBILITY_VERSION must be
   2290         # cleared. They are meaningless for type mh_bundle.
   2291         #
   2292         # Finally, the .so extension is forcibly applied over the default
   2293         # (.dylib), unless another forced extension is already selected.
   2294         # .dylib is plainly wrong, and .bundle is used by loadable_modules in
   2295         # bundle wrappers (com.apple.product-type.bundle). .so seems an odd
   2296         # choice because it's used as the extension on many other systems that
   2297         # don't distinguish between linkable shared libraries and non-linkable
   2298         # loadable modules, but there's precedent: Python loadable modules on
   2299         # Mac OS X use an .so extension.
   2300         if self._properties['productType'] == 'com.googlecode.gyp.xcode.bundle':
   2301           self._properties['productType'] = \
   2302               'com.apple.product-type.library.dynamic'
   2303           self.SetBuildSetting('MACH_O_TYPE', 'mh_bundle')
   2304           self.SetBuildSetting('DYLIB_CURRENT_VERSION', '')
   2305           self.SetBuildSetting('DYLIB_COMPATIBILITY_VERSION', '')
   2306           if force_extension is None:
   2307             force_extension = suffix[1:]
   2308 
   2309         if self._properties['productType'] == \
   2310            'com.apple.product-type-bundle.unit.test':
   2311           if force_extension is None:
   2312             force_extension = suffix[1:]
   2313 
   2314         if force_extension is not None:
   2315           # If it's a wrapper (bundle), set WRAPPER_EXTENSION.
   2316           if filetype.startswith('wrapper.'):
   2317             self.SetBuildSetting('WRAPPER_EXTENSION', force_extension)
   2318           else:
   2319             # Extension override.
   2320             suffix = '.' + force_extension
   2321             self.SetBuildSetting('EXECUTABLE_EXTENSION', force_extension)
   2322 
   2323           if filetype.startswith('compiled.mach-o.executable'):
   2324             product_name = self._properties['productName']
   2325             product_name += suffix
   2326             suffix = ''
   2327             self.SetProperty('productName', product_name)
   2328             self.SetBuildSetting('PRODUCT_NAME', product_name)
   2329 
   2330         # Xcode handles most prefixes based on the target type, however there
   2331         # are exceptions.  If a "BSD Dynamic Library" target is added in the
   2332         # Xcode UI, Xcode sets EXECUTABLE_PREFIX.  This check duplicates that
   2333         # behavior.
   2334         if force_prefix is not None:
   2335           prefix = force_prefix
   2336         if filetype.startswith('wrapper.'):
   2337           self.SetBuildSetting('WRAPPER_PREFIX', prefix)
   2338         else:
   2339           self.SetBuildSetting('EXECUTABLE_PREFIX', prefix)
   2340 
   2341         if force_outdir is not None:
   2342           self.SetBuildSetting('TARGET_BUILD_DIR', force_outdir)
   2343 
   2344         # TODO(tvl): Remove the below hack.
   2345         #    http://code.google.com/p/gyp/issues/detail?id=122
   2346 
   2347         # Some targets include the prefix in the target_name.  These targets
   2348         # really should just add a product_name setting that doesn't include
   2349         # the prefix.  For example:
   2350         #  target_name = 'libevent', product_name = 'event'
   2351         # This check cleans up for them.
   2352         product_name = self._properties['productName']
   2353         prefix_len = len(prefix)
   2354         if prefix_len and (product_name[:prefix_len] == prefix):
   2355           product_name = product_name[prefix_len:]
   2356           self.SetProperty('productName', product_name)
   2357           self.SetBuildSetting('PRODUCT_NAME', product_name)
   2358 
   2359         ref_props = {
   2360           'explicitFileType': filetype,
   2361           'includeInIndex':   0,
   2362           'path':             prefix + product_name + suffix,
   2363           'sourceTree':       'BUILT_PRODUCTS_DIR',
   2364         }
   2365         file_ref = PBXFileReference(ref_props)
   2366         products_group.AppendChild(file_ref)
   2367         self.SetProperty('productReference', file_ref)
   2368 
   2369   def GetBuildPhaseByType(self, type):
   2370     if not 'buildPhases' in self._properties:
   2371       return None
   2372 
   2373     the_phase = None
   2374     for phase in self._properties['buildPhases']:
   2375       if isinstance(phase, type):
   2376         # Some phases may be present in multiples in a well-formed project file,
   2377         # but phases like PBXSourcesBuildPhase may only be present singly, and
   2378         # this function is intended as an aid to GetBuildPhaseByType.  Loop
   2379         # over the entire list of phases and assert if more than one of the
   2380         # desired type is found.
   2381         assert the_phase is None
   2382         the_phase = phase
   2383 
   2384     return the_phase
   2385 
   2386   def HeadersPhase(self):
   2387     headers_phase = self.GetBuildPhaseByType(PBXHeadersBuildPhase)
   2388     if headers_phase is None:
   2389       headers_phase = PBXHeadersBuildPhase()
   2390 
   2391       # The headers phase should come before the resources, sources, and
   2392       # frameworks phases, if any.
   2393       insert_at = len(self._properties['buildPhases'])
   2394       for index in xrange(0, len(self._properties['buildPhases'])):
   2395         phase = self._properties['buildPhases'][index]
   2396         if isinstance(phase, PBXResourcesBuildPhase) or \
   2397            isinstance(phase, PBXSourcesBuildPhase) or \
   2398            isinstance(phase, PBXFrameworksBuildPhase):
   2399           insert_at = index
   2400           break
   2401 
   2402       self._properties['buildPhases'].insert(insert_at, headers_phase)
   2403       headers_phase.parent = self
   2404 
   2405     return headers_phase
   2406 
   2407   def ResourcesPhase(self):
   2408     resources_phase = self.GetBuildPhaseByType(PBXResourcesBuildPhase)
   2409     if resources_phase is None:
   2410       resources_phase = PBXResourcesBuildPhase()
   2411 
   2412       # The resources phase should come before the sources and frameworks
   2413       # phases, if any.
   2414       insert_at = len(self._properties['buildPhases'])
   2415       for index in xrange(0, len(self._properties['buildPhases'])):
   2416         phase = self._properties['buildPhases'][index]
   2417         if isinstance(phase, PBXSourcesBuildPhase) or \
   2418            isinstance(phase, PBXFrameworksBuildPhase):
   2419           insert_at = index
   2420           break
   2421 
   2422       self._properties['buildPhases'].insert(insert_at, resources_phase)
   2423       resources_phase.parent = self
   2424 
   2425     return resources_phase
   2426 
   2427   def SourcesPhase(self):
   2428     sources_phase = self.GetBuildPhaseByType(PBXSourcesBuildPhase)
   2429     if sources_phase is None:
   2430       sources_phase = PBXSourcesBuildPhase()
   2431       self.AppendProperty('buildPhases', sources_phase)
   2432 
   2433     return sources_phase
   2434 
   2435   def FrameworksPhase(self):
   2436     frameworks_phase = self.GetBuildPhaseByType(PBXFrameworksBuildPhase)
   2437     if frameworks_phase is None:
   2438       frameworks_phase = PBXFrameworksBuildPhase()
   2439       self.AppendProperty('buildPhases', frameworks_phase)
   2440 
   2441     return frameworks_phase
   2442 
   2443   def AddDependency(self, other):
   2444     # super
   2445     XCTarget.AddDependency(self, other)
   2446 
   2447     static_library_type = 'com.apple.product-type.library.static'
   2448     shared_library_type = 'com.apple.product-type.library.dynamic'
   2449     framework_type = 'com.apple.product-type.framework'
   2450     if isinstance(other, PBXNativeTarget) and \
   2451        'productType' in self._properties and \
   2452        self._properties['productType'] != static_library_type and \
   2453        'productType' in other._properties and \
   2454        (other._properties['productType'] == static_library_type or \
   2455         ((other._properties['productType'] == shared_library_type or \
   2456           other._properties['productType'] == framework_type) and \
   2457          ((not other.HasBuildSetting('MACH_O_TYPE')) or
   2458           other.GetBuildSetting('MACH_O_TYPE') != 'mh_bundle'))):
   2459 
   2460       file_ref = other.GetProperty('productReference')
   2461 
   2462       pbxproject = self.PBXProjectAncestor()
   2463       other_pbxproject = other.PBXProjectAncestor()
   2464       if pbxproject != other_pbxproject:
   2465         other_project_product_group = \
   2466             pbxproject.AddOrGetProjectReference(other_pbxproject)[0]
   2467         file_ref = other_project_product_group.GetChildByRemoteObject(file_ref)
   2468 
   2469       self.FrameworksPhase().AppendProperty('files',
   2470                                             PBXBuildFile({'fileRef': file_ref}))
   2471 
   2472 
   2473 class PBXAggregateTarget(XCTarget):
   2474   pass
   2475 
   2476 
   2477 class PBXProject(XCContainerPortal):
   2478   # A PBXProject is really just an XCObject, the XCContainerPortal thing is
   2479   # just to allow PBXProject to be used in the containerPortal property of
   2480   # PBXContainerItemProxy.
   2481   """
   2482 
   2483   Attributes:
   2484     path: "sample.xcodeproj".  TODO(mark) Document me!
   2485     _other_pbxprojects: A dictionary, keyed by other PBXProject objects.  Each
   2486                         value is a reference to the dict in the
   2487                         projectReferences list associated with the keyed
   2488                         PBXProject.
   2489   """
   2490 
   2491   _schema = XCContainerPortal._schema.copy()
   2492   _schema.update({
   2493     'attributes':             [0, dict,                0, 0],
   2494     'buildConfigurationList': [0, XCConfigurationList, 1, 1,
   2495                                XCConfigurationList()],
   2496     'compatibilityVersion':   [0, str,                 0, 1, 'Xcode 3.2'],
   2497     'hasScannedForEncodings': [0, int,                 0, 1, 1],
   2498     'mainGroup':              [0, PBXGroup,            1, 1, PBXGroup()],
   2499     'projectDirPath':         [0, str,                 0, 1, ''],
   2500     'projectReferences':      [1, dict,                0, 0],
   2501     'projectRoot':            [0, str,                 0, 1, ''],
   2502     'targets':                [1, XCTarget,            1, 1, []],
   2503   })
   2504 
   2505   def __init__(self, properties=None, id=None, parent=None, path=None):
   2506     self.path = path
   2507     self._other_pbxprojects = {}
   2508     # super
   2509     return XCContainerPortal.__init__(self, properties, id, parent)
   2510 
   2511   def Name(self):
   2512     name = self.path
   2513     if name[-10:] == '.xcodeproj':
   2514       name = name[:-10]
   2515     return posixpath.basename(name)
   2516 
   2517   def Path(self):
   2518     return self.path
   2519 
   2520   def Comment(self):
   2521     return 'Project object'
   2522 
   2523   def Children(self):
   2524     # super
   2525     children = XCContainerPortal.Children(self)
   2526 
   2527     # Add children that the schema doesn't know about.  Maybe there's a more
   2528     # elegant way around this, but this is the only case where we need to own
   2529     # objects in a dictionary (that is itself in a list), and three lines for
   2530     # a one-off isn't that big a deal.
   2531     if 'projectReferences' in self._properties:
   2532       for reference in self._properties['projectReferences']:
   2533         children.append(reference['ProductGroup'])
   2534 
   2535     return children
   2536 
   2537   def PBXProjectAncestor(self):
   2538     return self
   2539 
   2540   def _GroupByName(self, name):
   2541     if not 'mainGroup' in self._properties:
   2542       self.SetProperty('mainGroup', PBXGroup())
   2543 
   2544     main_group = self._properties['mainGroup']
   2545     group = main_group.GetChildByName(name)
   2546     if group is None:
   2547       group = PBXGroup({'name': name})
   2548       main_group.AppendChild(group)
   2549 
   2550     return group
   2551 
   2552   # SourceGroup and ProductsGroup are created by default in Xcode's own
   2553   # templates.
   2554   def SourceGroup(self):
   2555     return self._GroupByName('Source')
   2556 
   2557   def ProductsGroup(self):
   2558     return self._GroupByName('Products')
   2559 
   2560   # IntermediatesGroup is used to collect source-like files that are generated
   2561   # by rules or script phases and are placed in intermediate directories such
   2562   # as DerivedSources.
   2563   def IntermediatesGroup(self):
   2564     return self._GroupByName('Intermediates')
   2565 
   2566   # FrameworksGroup and ProjectsGroup are top-level groups used to collect
   2567   # frameworks and projects.
   2568   def FrameworksGroup(self):
   2569     return self._GroupByName('Frameworks')
   2570 
   2571   def ProjectsGroup(self):
   2572     return self._GroupByName('Projects')
   2573 
   2574   def RootGroupForPath(self, path):
   2575     """Returns a PBXGroup child of this object to which path should be added.
   2576 
   2577     This method is intended to choose between SourceGroup and
   2578     IntermediatesGroup on the basis of whether path is present in a source
   2579     directory or an intermediates directory.  For the purposes of this
   2580     determination, any path located within a derived file directory such as
   2581     PROJECT_DERIVED_FILE_DIR is treated as being in an intermediates
   2582     directory.
   2583 
   2584     The returned value is a two-element tuple.  The first element is the
   2585     PBXGroup, and the second element specifies whether that group should be
   2586     organized hierarchically (True) or as a single flat list (False).
   2587     """
   2588 
   2589     # TODO(mark): make this a class variable and bind to self on call?
   2590     # Also, this list is nowhere near exhaustive.
   2591     # INTERMEDIATE_DIR and SHARED_INTERMEDIATE_DIR are used by
   2592     # gyp.generator.xcode.  There should probably be some way for that module
   2593     # to push the names in, rather than having to hard-code them here.
   2594     source_tree_groups = {
   2595       'DERIVED_FILE_DIR':         (self.IntermediatesGroup, True),
   2596       'INTERMEDIATE_DIR':         (self.IntermediatesGroup, True),
   2597       'PROJECT_DERIVED_FILE_DIR': (self.IntermediatesGroup, True),
   2598       'SHARED_INTERMEDIATE_DIR':  (self.IntermediatesGroup, True),
   2599     }
   2600 
   2601     (source_tree, path) = SourceTreeAndPathFromPath(path)
   2602     if source_tree != None and source_tree in source_tree_groups:
   2603       (group_func, hierarchical) = source_tree_groups[source_tree]
   2604       group = group_func()
   2605       return (group, hierarchical)
   2606 
   2607     # TODO(mark): make additional choices based on file extension.
   2608 
   2609     return (self.SourceGroup(), True)
   2610 
   2611   def AddOrGetFileInRootGroup(self, path):
   2612     """Returns a PBXFileReference corresponding to path in the correct group
   2613     according to RootGroupForPath's heuristics.
   2614 
   2615     If an existing PBXFileReference for path exists, it will be returned.
   2616     Otherwise, one will be created and returned.
   2617     """
   2618 
   2619     (group, hierarchical) = self.RootGroupForPath(path)
   2620     return group.AddOrGetFileByPath(path, hierarchical)
   2621 
   2622   def RootGroupsTakeOverOnlyChildren(self, recurse=False):
   2623     """Calls TakeOverOnlyChild for all groups in the main group."""
   2624 
   2625     for group in self._properties['mainGroup']._properties['children']:
   2626       if isinstance(group, PBXGroup):
   2627         group.TakeOverOnlyChild(recurse)
   2628 
   2629   def SortGroups(self):
   2630     # Sort the children of the mainGroup (like "Source" and "Products")
   2631     # according to their defined order.
   2632     self._properties['mainGroup']._properties['children'] = \
   2633         sorted(self._properties['mainGroup']._properties['children'],
   2634                cmp=lambda x,y: x.CompareRootGroup(y))
   2635 
   2636     # Sort everything else by putting group before files, and going
   2637     # alphabetically by name within sections of groups and files.  SortGroup
   2638     # is recursive.
   2639     for group in self._properties['mainGroup']._properties['children']:
   2640       if not isinstance(group, PBXGroup):
   2641         continue
   2642 
   2643       if group.Name() == 'Products':
   2644         # The Products group is a special case.  Instead of sorting
   2645         # alphabetically, sort things in the order of the targets that
   2646         # produce the products.  To do this, just build up a new list of
   2647         # products based on the targets.
   2648         products = []
   2649         for target in self._properties['targets']:
   2650           if not isinstance(target, PBXNativeTarget):
   2651             continue
   2652           product = target._properties['productReference']
   2653           # Make sure that the product is already in the products group.
   2654           assert product in group._properties['children']
   2655           products.append(product)
   2656 
   2657         # Make sure that this process doesn't miss anything that was already
   2658         # in the products group.
   2659         assert len(products) == len(group._properties['children'])
   2660         group._properties['children'] = products
   2661       else:
   2662         group.SortGroup()
   2663 
   2664   def AddOrGetProjectReference(self, other_pbxproject):
   2665     """Add a reference to another project file (via PBXProject object) to this
   2666     one.
   2667 
   2668     Returns [ProductGroup, ProjectRef].  ProductGroup is a PBXGroup object in
   2669     this project file that contains a PBXReferenceProxy object for each
   2670     product of each PBXNativeTarget in the other project file.  ProjectRef is
   2671     a PBXFileReference to the other project file.
   2672 
   2673     If this project file already references the other project file, the
   2674     existing ProductGroup and ProjectRef are returned.  The ProductGroup will
   2675     still be updated if necessary.
   2676     """
   2677 
   2678     if not 'projectReferences' in self._properties:
   2679       self._properties['projectReferences'] = []
   2680 
   2681     product_group = None
   2682     project_ref = None
   2683 
   2684     if not other_pbxproject in self._other_pbxprojects:
   2685       # This project file isn't yet linked to the other one.  Establish the
   2686       # link.
   2687       product_group = PBXGroup({'name': 'Products'})
   2688 
   2689       # ProductGroup is strong.
   2690       product_group.parent = self
   2691 
   2692       # There's nothing unique about this PBXGroup, and if left alone, it will
   2693       # wind up with the same set of hashables as all other PBXGroup objects
   2694       # owned by the projectReferences list.  Add the hashables of the
   2695       # remote PBXProject that it's related to.
   2696       product_group._hashables.extend(other_pbxproject.Hashables())
   2697 
   2698       # The other project reports its path as relative to the same directory
   2699       # that this project's path is relative to.  The other project's path
   2700       # is not necessarily already relative to this project.  Figure out the
   2701       # pathname that this project needs to use to refer to the other one.
   2702       this_path = posixpath.dirname(self.Path())
   2703       projectDirPath = self.GetProperty('projectDirPath')
   2704       if projectDirPath:
   2705         if posixpath.isabs(projectDirPath[0]):
   2706           this_path = projectDirPath
   2707         else:
   2708           this_path = posixpath.join(this_path, projectDirPath)
   2709       other_path = gyp.common.RelativePath(other_pbxproject.Path(), this_path)
   2710 
   2711       # ProjectRef is weak (it's owned by the mainGroup hierarchy).
   2712       project_ref = PBXFileReference({
   2713             'lastKnownFileType': 'wrapper.pb-project',
   2714             'path':              other_path,
   2715             'sourceTree':        'SOURCE_ROOT',
   2716           })
   2717       self.ProjectsGroup().AppendChild(project_ref)
   2718 
   2719       ref_dict = {'ProductGroup': product_group, 'ProjectRef': project_ref}
   2720       self._other_pbxprojects[other_pbxproject] = ref_dict
   2721       self.AppendProperty('projectReferences', ref_dict)
   2722 
   2723       # Xcode seems to sort this list case-insensitively
   2724       self._properties['projectReferences'] = \
   2725           sorted(self._properties['projectReferences'], cmp=lambda x,y:
   2726                  cmp(x['ProjectRef'].Name().lower(),
   2727                      y['ProjectRef'].Name().lower()))
   2728     else:
   2729       # The link already exists.  Pull out the relevnt data.
   2730       project_ref_dict = self._other_pbxprojects[other_pbxproject]
   2731       product_group = project_ref_dict['ProductGroup']
   2732       project_ref = project_ref_dict['ProjectRef']
   2733 
   2734     self._SetUpProductReferences(other_pbxproject, product_group, project_ref)
   2735 
   2736     return [product_group, project_ref]
   2737 
   2738   def _SetUpProductReferences(self, other_pbxproject, product_group,
   2739                               project_ref):
   2740     # TODO(mark): This only adds references to products in other_pbxproject
   2741     # when they don't exist in this pbxproject.  Perhaps it should also
   2742     # remove references from this pbxproject that are no longer present in
   2743     # other_pbxproject.  Perhaps it should update various properties if they
   2744     # change.
   2745     for target in other_pbxproject._properties['targets']:
   2746       if not isinstance(target, PBXNativeTarget):
   2747         continue
   2748 
   2749       other_fileref = target._properties['productReference']
   2750       if product_group.GetChildByRemoteObject(other_fileref) is None:
   2751         # Xcode sets remoteInfo to the name of the target and not the name
   2752         # of its product, despite this proxy being a reference to the product.
   2753         container_item = PBXContainerItemProxy({
   2754               'containerPortal':      project_ref,
   2755               'proxyType':            2,
   2756               'remoteGlobalIDString': other_fileref,
   2757               'remoteInfo':           target.Name()
   2758             })
   2759         # TODO(mark): Does sourceTree get copied straight over from the other
   2760         # project?  Can the other project ever have lastKnownFileType here
   2761         # instead of explicitFileType?  (Use it if so?)  Can path ever be
   2762         # unset?  (I don't think so.)  Can other_fileref have name set, and
   2763         # does it impact the PBXReferenceProxy if so?  These are the questions
   2764         # that perhaps will be answered one day.
   2765         reference_proxy = PBXReferenceProxy({
   2766               'fileType':   other_fileref._properties['explicitFileType'],
   2767               'path':       other_fileref._properties['path'],
   2768               'sourceTree': other_fileref._properties['sourceTree'],
   2769               'remoteRef':  container_item,
   2770             })
   2771 
   2772         product_group.AppendChild(reference_proxy)
   2773 
   2774   def SortRemoteProductReferences(self):
   2775     # For each remote project file, sort the associated ProductGroup in the
   2776     # same order that the targets are sorted in the remote project file.  This
   2777     # is the sort order used by Xcode.
   2778 
   2779     def CompareProducts(x, y, remote_products):
   2780       # x and y are PBXReferenceProxy objects.  Go through their associated
   2781       # PBXContainerItem to get the remote PBXFileReference, which will be
   2782       # present in the remote_products list.
   2783       x_remote = x._properties['remoteRef']._properties['remoteGlobalIDString']
   2784       y_remote = y._properties['remoteRef']._properties['remoteGlobalIDString']
   2785       x_index = remote_products.index(x_remote)
   2786       y_index = remote_products.index(y_remote)
   2787 
   2788       # Use the order of each remote PBXFileReference in remote_products to
   2789       # determine the sort order.
   2790       return cmp(x_index, y_index)
   2791 
   2792     for other_pbxproject, ref_dict in self._other_pbxprojects.iteritems():
   2793       # Build up a list of products in the remote project file, ordered the
   2794       # same as the targets that produce them.
   2795       remote_products = []
   2796       for target in other_pbxproject._properties['targets']:
   2797         if not isinstance(target, PBXNativeTarget):
   2798           continue
   2799         remote_products.append(target._properties['productReference'])
   2800 
   2801       # Sort the PBXReferenceProxy children according to the list of remote
   2802       # products.
   2803       product_group = ref_dict['ProductGroup']
   2804       product_group._properties['children'] = sorted(
   2805           product_group._properties['children'],
   2806           cmp=lambda x, y: CompareProducts(x, y, remote_products))
   2807 
   2808 
   2809 class XCProjectFile(XCObject):
   2810   _schema = XCObject._schema.copy()
   2811   _schema.update({
   2812     'archiveVersion': [0, int,        0, 1, 1],
   2813     'classes':        [0, dict,       0, 1, {}],
   2814     'objectVersion':  [0, int,        0, 1, 45],
   2815     'rootObject':     [0, PBXProject, 1, 1],
   2816   })
   2817 
   2818   def SetXcodeVersion(self, version):
   2819     version_to_object_version = {
   2820       '2.4': 45,
   2821       '3.0': 45,
   2822       '3.1': 45,
   2823       '3.2': 46,
   2824     }
   2825     if not version in version_to_object_version:
   2826       supported_str = ', '.join(sorted(version_to_object_version.keys()))
   2827       raise Exception(
   2828           'Unsupported Xcode version %s (supported: %s)' %
   2829           ( version, supported_str ) )
   2830     compatibility_version = 'Xcode %s' % version
   2831     self._properties['rootObject'].SetProperty('compatibilityVersion',
   2832                                                compatibility_version)
   2833     self.SetProperty('objectVersion', version_to_object_version[version]);
   2834 
   2835   def ComputeIDs(self, recursive=True, overwrite=True, hash=None):
   2836     # Although XCProjectFile is implemented here as an XCObject, it's not a
   2837     # proper object in the Xcode sense, and it certainly doesn't have its own
   2838     # ID.  Pass through an attempt to update IDs to the real root object.
   2839     if recursive:
   2840       self._properties['rootObject'].ComputeIDs(recursive, overwrite, hash)
   2841 
   2842   def Print(self, file=sys.stdout):
   2843     self.VerifyHasRequiredProperties()
   2844 
   2845     # Add the special "objects" property, which will be caught and handled
   2846     # separately during printing.  This structure allows a fairly standard
   2847     # loop do the normal printing.
   2848     self._properties['objects'] = {}
   2849     self._XCPrint(file, 0, '// !$*UTF8*$!\n')
   2850     if self._should_print_single_line:
   2851       self._XCPrint(file, 0, '{ ')
   2852     else:
   2853       self._XCPrint(file, 0, '{\n')
   2854     for property, value in sorted(self._properties.iteritems(),
   2855                                   cmp=lambda x, y: cmp(x, y)):
   2856       if property == 'objects':
   2857         self._PrintObjects(file)
   2858       else:
   2859         self._XCKVPrint(file, 1, property, value)
   2860     self._XCPrint(file, 0, '}\n')
   2861     del self._properties['objects']
   2862 
   2863   def _PrintObjects(self, file):
   2864     if self._should_print_single_line:
   2865       self._XCPrint(file, 0, 'objects = {')
   2866     else:
   2867       self._XCPrint(file, 1, 'objects = {\n')
   2868 
   2869     objects_by_class = {}
   2870     for object in self.Descendants():
   2871       if object == self:
   2872         continue
   2873       class_name = object.__class__.__name__
   2874       if not class_name in objects_by_class:
   2875         objects_by_class[class_name] = []
   2876       objects_by_class[class_name].append(object)
   2877 
   2878     for class_name in sorted(objects_by_class):
   2879       self._XCPrint(file, 0, '\n')
   2880       self._XCPrint(file, 0, '/* Begin ' + class_name + ' section */\n')
   2881       for object in sorted(objects_by_class[class_name],
   2882                            cmp=lambda x, y: cmp(x.id, y.id)):
   2883         object.Print(file)
   2884       self._XCPrint(file, 0, '/* End ' + class_name + ' section */\n')
   2885 
   2886     if self._should_print_single_line:
   2887       self._XCPrint(file, 0, '}; ')
   2888     else:
   2889       self._XCPrint(file, 1, '};\n')
   2890