175
175
# Used by SourceTreeAndPathFromPath
176
_path_leading_variable = re.compile('^\$\((.*?)\)(/(.*))?$')
176
_path_leading_variable = re.compile(r'^\$\((.*?)\)(/(.*))?$')
178
178
def SourceTreeAndPathFromPath(input_path):
179
179
"""Given input_path, returns a tuple with sourceTree and path values.
196
196
return (source_tree, output_path)
198
198
def ConvertVariablesToShellSyntax(input_string):
199
return re.sub('\$\((.*?)\)', '${\\1}', input_string)
199
return re.sub(r'\$\((.*?)\)', '${\\1}', input_string)
201
201
class XCObject(object):
202
202
"""The abstract base of all class types used in Xcode project files.
341
341
elif isinstance(value, dict):
342
342
# dicts are never strong.
344
raise TypeError, 'Strong dict for key ' + key + ' in ' + \
345
self.__class__.__name__
344
raise TypeError('Strong dict for key ' + key + ' in ' + \
345
self.__class__.__name__)
347
347
that._properties[key] = value.copy()
349
raise TypeError, 'Unexpected type ' + value.__class__.__name__ + \
350
' for key ' + key + ' in ' + self.__class__.__name__
349
raise TypeError('Unexpected type ' + value.__class__.__name__ + \
350
' for key ' + key + ' in ' + self.__class__.__name__)
366
366
('name' in self._schema and self._schema['name'][3]):
367
367
return self._properties['name']
369
raise NotImplementedError, \
370
self.__class__.__name__ + ' must implement Name'
369
raise NotImplementedError(self.__class__.__name__ + ' must implement Name')
372
371
def Comment(self):
373
372
"""Return a comment string for the object.
466
465
for descendant in descendants:
467
466
if descendant.id in ids:
468
467
other = ids[descendant.id]
470
469
'Duplicate ID %s, objects "%s" and "%s" in "%s"' % \
471
470
(descendant.id, str(descendant._properties),
472
str(other._properties), self._properties['rootObject'].Name())
471
str(other._properties), self._properties['rootObject'].Name()))
473
472
ids[descendant.id] = descendant
475
474
def Children(self):
756
755
for property, value in properties.iteritems():
757
756
# Make sure the property is in the schema.
758
757
if not property in self._schema:
759
raise KeyError, property + ' not in ' + self.__class__.__name__
758
raise KeyError(property + ' not in ' + self.__class__.__name__)
761
760
# Make sure the property conforms to the schema.
762
761
(is_list, property_type, is_strong) = self._schema[property][0:3]
764
763
if value.__class__ != list:
766
765
property + ' of ' + self.__class__.__name__ + \
767
' must be list, not ' + value.__class__.__name__
766
' must be list, not ' + value.__class__.__name__)
768
767
for item in value:
769
768
if not isinstance(item, property_type) and \
770
769
not (item.__class__ == unicode and property_type == str):
771
770
# Accept unicode where str is specified. str is treated as
774
773
'item of ' + property + ' of ' + self.__class__.__name__ + \
775
774
' must be ' + property_type.__name__ + ', not ' + \
776
item.__class__.__name__
775
item.__class__.__name__)
777
776
elif not isinstance(value, property_type) and \
778
777
not (value.__class__ == unicode and property_type == str):
779
778
# Accept unicode where str is specified. str is treated as
782
781
property + ' of ' + self.__class__.__name__ + ' must be ' + \
783
property_type.__name__ + ', not ' + value.__class__.__name__
782
property_type.__name__ + ', not ' + value.__class__.__name__)
785
784
# Checks passed, perform the assignment.
804
803
elif isinstance(value, dict):
805
804
self._properties[property] = value.copy()
807
raise TypeError, "Don't know how to copy a " + \
808
value.__class__.__name__ + ' object for ' + \
809
property + ' in ' + self.__class__.__name__
806
raise TypeError("Don't know how to copy a " + \
807
value.__class__.__name__ + ' object for ' + \
808
property + ' in ' + self.__class__.__name__)
811
810
self._properties[property] = value
838
837
# Schema validation.
839
838
if not key in self._schema:
840
raise KeyError, key + ' not in ' + self.__class__.__name__
839
raise KeyError(key + ' not in ' + self.__class__.__name__)
842
841
(is_list, property_type, is_strong) = self._schema[key][0:3]
844
raise TypeError, key + ' of ' + self.__class__.__name__ + ' must be list'
843
raise TypeError(key + ' of ' + self.__class__.__name__ + ' must be list')
845
844
if not isinstance(value, property_type):
846
raise TypeError, 'item of ' + key + ' of ' + self.__class__.__name__ + \
847
' must be ' + property_type.__name__ + ', not ' + \
848
value.__class__.__name__
845
raise TypeError('item of ' + key + ' of ' + self.__class__.__name__ + \
846
' must be ' + property_type.__name__ + ', not ' + \
847
value.__class__.__name__)
850
849
# If the property doesn't exist yet, create a new empty list to receive the
869
868
for property, attributes in self._schema.iteritems():
870
869
(is_list, property_type, is_strong, is_required) = attributes[0:4]
871
870
if is_required and not property in self._properties:
872
raise KeyError, self.__class__.__name__ + ' requires ' + property
871
raise KeyError(self.__class__.__name__ + ' requires ' + property)
874
873
def _SetDefaultsFromSchema(self):
875
874
"""Assign object default values according to the schema. This will not
1143
1142
child_path = child.PathFromSourceTreeAndPath()
1145
1144
if child_path in self._children_by_path:
1146
raise ValueError, 'Found multiple children with path ' + child_path
1145
raise ValueError('Found multiple children with path ' + child_path)
1147
1146
self._children_by_path[child_path] = child
1149
1148
if isinstance(child, PBXVariantGroup):
1150
1149
child_name = child._properties.get('name', None)
1151
1150
key = (child_name, child_path)
1152
1151
if key in self._variant_children_by_name_and_path:
1153
raise ValueError, 'Found multiple PBXVariantGroup children with ' + \
1154
'name ' + str(child_name) + ' and path ' + \
1152
raise ValueError('Found multiple PBXVariantGroup children with ' + \
1153
'name ' + str(child_name) + ' and path ' + \
1156
1155
self._variant_children_by_name_and_path[key] = child
1158
1157
def AppendChild(self, child):
1483
1482
'cpp': 'sourcecode.cpp.cpp',
1484
1483
'css': 'text.css',
1485
1484
'cxx': 'sourcecode.cpp.cpp',
1485
'dart': 'sourcecode',
1486
1486
'dylib': 'compiled.mach-o.dylib',
1487
1487
'framework': 'wrapper.framework',
1488
'gyp': 'sourcecode',
1489
'gypi': 'sourcecode',
1488
1490
'h': 'sourcecode.c.h',
1489
1491
'hxx': 'sourcecode.cpp.h',
1490
1492
'icns': 'image.icns',
1505
1507
's': 'sourcecode.asm',
1506
1508
'storyboard': 'file.storyboard',
1507
1509
'strings': 'text.plist.strings',
1510
'swift': 'sourcecode.swift',
1512
'xcassets': 'folder.assetcatalog',
1509
1513
'xcconfig': 'text.xcconfig',
1510
1514
'xcdatamodel': 'wrapper.xcdatamodel',
1515
'xcdatamodeld':'wrapper.xcdatamodeld',
1511
1516
'xib': 'file.xib',
1512
1517
'y': 'sourcecode.yacc',
1521
'dart': 'explicitFileType',
1522
'gyp': 'explicitFileType',
1523
'gypi': 'explicitFileType',
1516
1527
file_type = 'folder'
1528
prop_name = 'lastKnownFileType'
1518
1530
basename = posixpath.basename(self._properties['path'])
1519
1531
(root, ext) = posixpath.splitext(basename)
1528
1540
# for unrecognized files not containing text. Xcode seems to choose
1529
1541
# based on content.
1530
1542
file_type = extension_map.get(ext, 'text')
1543
prop_name = prop_map.get(ext, 'lastKnownFileType')
1532
self._properties['lastKnownFileType'] = file_type
1545
self._properties[prop_name] = file_type
1535
1548
class PBXVariantGroup(PBXGroup, XCFileLikeElement):
1758
1771
# added, either as a child or deeper descendant. The second item should
1759
1772
# be a boolean indicating whether files should be added into hierarchical
1760
1773
# groups or one single flat group.
1761
raise NotImplementedError, \
1762
self.__class__.__name__ + ' must implement FileGroup'
1774
raise NotImplementedError(
1775
self.__class__.__name__ + ' must implement FileGroup')
1764
1777
def _AddPathToDict(self, pbxbuildfile, path):
1765
1778
"""Adds path to the dict tracking paths belonging to this build phase.
1770
1783
if path in self._files_by_path:
1771
raise ValueError, 'Found multiple build files with path ' + path
1784
raise ValueError('Found multiple build files with path ' + path)
1772
1785
self._files_by_path[path] = pbxbuildfile
1774
1787
def _AddBuildFileToDicts(self, pbxbuildfile, path=None):
1824
1837
if xcfilelikeelement in self._files_by_xcfilelikeelement and \
1825
1838
self._files_by_xcfilelikeelement[xcfilelikeelement] != pbxbuildfile:
1826
raise ValueError, 'Found multiple build files for ' + \
1827
xcfilelikeelement.Name()
1839
raise ValueError('Found multiple build files for ' + \
1840
xcfilelikeelement.Name())
1828
1841
self._files_by_xcfilelikeelement[xcfilelikeelement] = pbxbuildfile
1830
1843
def AppendBuildFile(self, pbxbuildfile, path=None):
1989
2002
relative_path = path[1:]
1991
raise ValueError, 'Can\'t use path %s in a %s' % \
1992
(path, self.__class__.__name__)
2004
raise ValueError('Can\'t use path %s in a %s' % \
2005
(path, self.__class__.__name__))
1994
2007
self._properties['dstPath'] = relative_path
1995
2008
self._properties['dstSubfolderSpec'] = subfolder
2225
2238
# Mapping from Xcode product-types to settings. The settings are:
2226
2239
# filetype : used for explicitFileType in the project file
2227
2240
# prefix : the prefix for the file name
2228
# suffix : the suffix for the filen ame
2241
# suffix : the suffix for the file name
2229
2242
_product_filetypes = {
2230
'com.apple.product-type.application': ['wrapper.application',
2232
'com.apple.product-type.bundle': ['wrapper.cfbundle',
2234
'com.apple.product-type.framework': ['wrapper.framework',
2236
'com.apple.product-type.library.dynamic': ['compiled.mach-o.dylib',
2238
'com.apple.product-type.library.static': ['archive.ar',
2240
'com.apple.product-type.tool': ['compiled.mach-o.executable',
2242
'com.googlecode.gyp.xcode.bundle': ['compiled.mach-o.dylib',
2243
'com.apple.product-type.application': ['wrapper.application',
2245
'com.apple.product-type.application.watchapp': ['wrapper.application',
2247
'com.apple.product-type.watchkit-extension': ['wrapper.app-extension',
2249
'com.apple.product-type.app-extension': ['wrapper.app-extension',
2251
'com.apple.product-type.bundle': ['wrapper.cfbundle',
2253
'com.apple.product-type.framework': ['wrapper.framework',
2255
'com.apple.product-type.library.dynamic': ['compiled.mach-o.dylib',
2257
'com.apple.product-type.library.static': ['archive.ar',
2259
'com.apple.product-type.tool': ['compiled.mach-o.executable',
2261
'com.apple.product-type.bundle.unit-test': ['wrapper.cfbundle',
2263
'com.googlecode.gyp.xcode.bundle': ['compiled.mach-o.dylib',
2246
2267
def __init__(self, properties=None, id=None, parent=None,
2292
2313
if force_extension is None:
2293
2314
force_extension = suffix[1:]
2316
if self._properties['productType'] == \
2317
'com.apple.product-type-bundle.unit.test':
2318
if force_extension is None:
2319
force_extension = suffix[1:]
2295
2321
if force_extension is not None:
2296
2322
# If it's a wrapper (bundle), set WRAPPER_EXTENSION.
2323
# Extension override.
2324
suffix = '.' + force_extension
2297
2325
if filetype.startswith('wrapper.'):
2298
2326
self.SetBuildSetting('WRAPPER_EXTENSION', force_extension)
2300
# Extension override.
2301
suffix = '.' + force_extension
2302
2328
self.SetBuildSetting('EXECUTABLE_EXTENSION', force_extension)
2304
2330
if filetype.startswith('compiled.mach-o.executable'):
2715
2741
self._SetUpProductReferences(other_pbxproject, product_group, project_ref)
2743
inherit_unique_symroot = self._AllSymrootsUnique(other_pbxproject, False)
2744
targets = other_pbxproject.GetProperty('targets')
2745
if all(self._AllSymrootsUnique(t, inherit_unique_symroot) for t in targets):
2746
dir_path = project_ref._properties['path']
2747
product_group._hashables.extend(dir_path)
2717
2749
return [product_group, project_ref]
2751
def _AllSymrootsUnique(self, target, inherit_unique_symroot):
2752
# Returns True if all configurations have a unique 'SYMROOT' attribute.
2753
# The value of inherit_unique_symroot decides, if a configuration is assumed
2754
# to inherit a unique 'SYMROOT' attribute from its parent, if it doesn't
2755
# define an explicit value for 'SYMROOT'.
2756
symroots = self._DefinedSymroots(target)
2757
for s in self._DefinedSymroots(target):
2758
if (s is not None and not self._IsUniqueSymrootForTarget(s) or
2759
s is None and not inherit_unique_symroot):
2761
return True if symroots else inherit_unique_symroot
2763
def _DefinedSymroots(self, target):
2764
# Returns all values for the 'SYMROOT' attribute defined in all
2765
# configurations for this target. If any configuration doesn't define the
2766
# 'SYMROOT' attribute, None is added to the returned set. If all
2767
# configurations don't define the 'SYMROOT' attribute, an empty set is
2769
config_list = target.GetProperty('buildConfigurationList')
2771
for config in config_list.GetProperty('buildConfigurations'):
2772
setting = config.GetProperty('buildSettings')
2773
if 'SYMROOT' in setting:
2774
symroots.add(setting['SYMROOT'])
2777
if len(symroots) == 1 and None in symroots:
2781
def _IsUniqueSymrootForTarget(self, symroot):
2782
# This method returns True if all configurations in target contain a
2783
# 'SYMROOT' attribute that is unique for the given target. A value is
2784
# unique, if the Xcode macro '$SRCROOT' appears in it in any form.
2785
uniquifier = ['$SRCROOT', '$(SRCROOT)']
2786
if any(x in symroot for x in uniquifier):
2719
2790
def _SetUpProductReferences(self, other_pbxproject, product_group,
2721
2792
# TODO(mark): This only adds references to products in other_pbxproject
2784
2855
product_group = ref_dict['ProductGroup']
2785
2856
product_group._properties['children'] = sorted(
2786
2857
product_group._properties['children'],
2787
cmp=lambda x, y: CompareProducts(x, y, remote_products))
2858
cmp=lambda x, y, rp=remote_products: CompareProducts(x, y, rp))
2790
2861
class XCProjectFile(XCObject):
2792
2863
_schema.update({
2793
2864
'archiveVersion': [0, int, 0, 1, 1],
2794
2865
'classes': [0, dict, 0, 1, {}],
2795
'objectVersion': [0, int, 0, 1, 45],
2866
'objectVersion': [0, int, 0, 1, 46],
2796
2867
'rootObject': [0, PBXProject, 1, 1],
2799
def SetXcodeVersion(self, version):
2800
version_to_object_version = {
2806
if not version in version_to_object_version:
2807
supported_str = ', '.join(sorted(version_to_object_version.keys()))
2809
'Unsupported Xcode version %s (supported: %s)' %
2810
( version, supported_str ) )
2811
compatibility_version = 'Xcode %s' % version
2812
self._properties['rootObject'].SetProperty('compatibilityVersion',
2813
compatibility_version)
2814
self.SetProperty('objectVersion', version_to_object_version[version]);
2816
2870
def ComputeIDs(self, recursive=True, overwrite=True, hash=None):
2817
2871
# Although XCProjectFile is implemented here as an XCObject, it's not a
2818
2872
# proper object in the Xcode sense, and it certainly doesn't have its own