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