~ubuntu-branches/ubuntu/oneiric/mozc/oneiric

« back to all changes in this revision

Viewing changes to third_party/gyp/pylib/gyp/xcodeproj_file.py

  • Committer: Bazaar Package Importer
  • Author(s): Nobuhiro Iwamatsu
  • Date: 2010-07-14 03:26:47 UTC
  • mfrom: (1.1.1 upstream)
  • Revision ID: james.westby@ubuntu.com-20100714032647-13qjisj6m8cm8jdx
Tags: 0.12.410.102-1
* New upstream release (Closes: #588971).
  - Add mozc-server, mozc-utils-gui and scim-mozc packages.
* Update debian/rules.
  Add --gypdir option to build_mozc.py.
* Update debian/control.
  - Bumped standards-version to 3.9.0.
  - Update description.
* Add mozc icon (Closes: #588972).
* Add patch which revises issue 18.
  ibus_mozc_issue18.patch
* kFreeBSD build support.
  support_kfreebsd.patch

Show diffs side-by-side

added added

removed removed

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