| Index: tools/xcodebodge/xcodebodge.py
|
| ===================================================================
|
| --- tools/xcodebodge/xcodebodge.py (revision 48139)
|
| +++ tools/xcodebodge/xcodebodge.py (working copy)
|
| @@ -1,1274 +0,0 @@
|
| -#!/usr/bin/python
|
| -# Copyright (c) 2008 The Chromium Authors. All rights reserved.
|
| -# Use of this source code is governed by a BSD-style license that can be
|
| -# found in the LICENSE file.
|
| -"""
|
| -Commandline modification of Xcode project files
|
| -"""
|
| -
|
| -import sys
|
| -import os
|
| -import optparse
|
| -import re
|
| -import tempfile
|
| -import random
|
| -import subprocess
|
| -
|
| -random.seed() # Seed the generator
|
| -
|
| -
|
| -# All known project build path source tree path reference types
|
| -PBX_VALID_SOURCE_TREE_TYPES = ('"<group>"',
|
| - 'SOURCE_ROOT',
|
| - '"<absolute>"',
|
| - 'BUILT_PRODUCTS_DIR',
|
| - 'DEVELOPER_DIR',
|
| - 'SDKROOT',
|
| - 'CONFIGURATION_TEMP_DIR')
|
| -# Paths with some characters appear quoted
|
| -QUOTE_PATH_RE = re.compile('\s|-|\+')
|
| -
|
| -# Supported Xcode file types
|
| -EXTENSION_TO_XCODE_FILETYPE = {
|
| - '.h' : 'sourcecode.c.h',
|
| - '.c' : 'sourcecode.c.c',
|
| - '.cpp' : 'sourcecode.cpp.cpp',
|
| - '.cc' : 'sourcecode.cpp.cpp',
|
| - '.cxx' : 'sourcecode.cpp.cpp',
|
| - '.m' : 'sourcecode.c.objc',
|
| - '.mm' : 'sourcecode.c.objcpp',
|
| -}
|
| -
|
| -# File types that can be added to a Sources phase
|
| -SOURCES_XCODE_FILETYPES = ( 'sourcecode.c.c',
|
| - 'sourcecode.cpp.cpp',
|
| - 'sourcecode.c.objc',
|
| - 'sourcecode.c.objcpp' )
|
| -
|
| -# Avoid inserting source files into these common Xcode group names. Because
|
| -# Xcode allows any names for these groups this list cannot be authoritative,
|
| -# but these are common names in the Xcode templates.
|
| -NON_SOURCE_GROUP_NAMES = ( 'Frameworks',
|
| - 'Resources',
|
| - 'Products',
|
| - 'Derived Sources',
|
| - 'Configurations',
|
| - 'Documentation',
|
| - 'Frameworks and Libraries',
|
| - 'External Frameworks and Libraries',
|
| - 'Libraries' )
|
| -
|
| -
|
| -def NewUUID():
|
| - """Create a new random Xcode UUID"""
|
| - __pychecker__ = 'unusednames=i'
|
| - elements = []
|
| - for i in range(24):
|
| - elements.append(hex(random.randint(0, 15))[-1].upper())
|
| - return ''.join(elements)
|
| -
|
| -def CygwinPathClean(path):
|
| - """Folks use Cygwin shells with standard Win32 Python which can't handle
|
| - Cygwin paths. Run everything through cygpath if we can (conveniently
|
| - cygpath does the right thing with normal Win32 paths).
|
| - """
|
| - # Look for Unix-like path with Win32 Python
|
| - if sys.platform == 'win32' and path.startswith('/'):
|
| - cygproc = subprocess.Popen(('cygpath', '-a', '-w', path),
|
| - stdout=subprocess.PIPE)
|
| - (stdout_content, stderr_content) = cygproc.communicate()
|
| - return stdout_content.rstrip()
|
| - # Convert all paths to cygpaths if we're using cygwin python
|
| - if sys.platform == 'cygwin':
|
| - cygproc = subprocess.Popen(('cygpath', '-a', '-u', path),
|
| - stdout=subprocess.PIPE)
|
| - (stdout_content, stderr_content) = cygproc.communicate()
|
| - return stdout_content.rstrip()
|
| - # Fallthrough for all other cases
|
| - return path
|
| -
|
| -class XcodeProject(object):
|
| - """Class for reading/writing Xcode project files.
|
| - This is not a general parser or representation. It is restricted to just
|
| - the Xcode internal objects we need.
|
| -
|
| - Args:
|
| - path: Absolute path to Xcode project file (including project.pbxproj
|
| - filename)
|
| - Attributes:
|
| - path: Full path to the project.pbxproj file
|
| - name: Project name (wrapper directory basename without extension)
|
| - source_root_path: Absolute path for Xcode's SOURCE_ROOT
|
| - """
|
| -
|
| - EXPECTED_PROJECT_HEADER_RE = re.compile(
|
| - r'^// !\$\*UTF8\*\$!\n' \
|
| - '\{\n' \
|
| - '\tarchiveVersion = 1;\n' \
|
| - '\tclasses = \{\n' \
|
| - '\t\};\n' \
|
| - '\tobjectVersion = \d+;\n' \
|
| - '\tobjects = \{\n' \
|
| - '\n')
|
| - SECTION_BEGIN_RE = re.compile(r'^/\* Begin (.*) section \*/\n$')
|
| - SECTION_END_RE = re.compile(r'^/\* End (.*) section \*/\n$')
|
| - PROJECT_ROOT_OBJECT_RE = re.compile(
|
| - r'^\trootObject = ([0-9A-F]{24}) /\* Project object \*/;\n$')
|
| -
|
| - def __init__(self, path):
|
| - self.path = path
|
| - self.name = os.path.splitext(os.path.basename(os.path.dirname(path)))[0]
|
| -
|
| - # Load project. Ideally we would use plistlib, but sadly that only reads
|
| - # XML plists. A real parser with pyparsing
|
| - # (http://pyparsing.wikispaces.com/) might be another option, but for now
|
| - # we'll do the simple (?!?) thing.
|
| - project_fh = open(self.path, 'rU')
|
| - self._raw_content = project_fh.readlines()
|
| - project_fh.close()
|
| -
|
| - # Store and check header
|
| - if len(self._raw_content) < 8:
|
| - print >> sys.stderr, ''.join(self._raw_content)
|
| - raise RuntimeError('XcodeProject file "%s" too short' % path)
|
| - self._header = tuple(self._raw_content[:8])
|
| - if not self.__class__.EXPECTED_PROJECT_HEADER_RE.match(''.join(self._header)):
|
| - print >> sys.stderr, ''.join(self._header)
|
| - raise RuntimeError('XcodeProject file "%s" wrong header' % path)
|
| -
|
| - # Find and store tail (some projects have additional whitespace at end)
|
| - self._tail = []
|
| - for tail_line in reversed(self._raw_content):
|
| - self._tail.insert(0, tail_line)
|
| - if tail_line == '\t};\n': break
|
| -
|
| - # Ugly ugly project parsing, turn each commented section into a separate
|
| - # set of objects. For types we don't have a custom representation for,
|
| - # store the raw lines.
|
| - self._section_order = []
|
| - self._sections = {}
|
| - parse_line_no = len(self._header)
|
| - while parse_line_no < (len(self._raw_content) - len(self._tail)):
|
| - section_header_match = self.__class__.SECTION_BEGIN_RE.match(
|
| - self._raw_content[parse_line_no])
|
| - # Loop to next section header
|
| - if not section_header_match:
|
| - parse_line_no += 1
|
| - continue
|
| -
|
| - section = section_header_match.group(1)
|
| - self._section_order.append(section)
|
| - self._sections[section] = []
|
| -
|
| - # Advance to first line of the section
|
| - parse_line_no += 1
|
| -
|
| - # Read in the section, using custom classes where we need them
|
| - section_end_match = self.__class__.SECTION_END_RE.match(
|
| - self._raw_content[parse_line_no])
|
| - while not section_end_match:
|
| - # Unhandled lines
|
| - content = self._raw_content[parse_line_no]
|
| - # Sections we can parse line-by-line
|
| - if section in ('PBXBuildFile', 'PBXFileReference'):
|
| - content = eval('%s.FromContent(content)' % section)
|
| - # Multiline sections
|
| - elif section in ('PBXGroup', 'PBXVariantGroup', 'PBXProject',
|
| - 'PBXNativeTarget', 'PBXSourcesBuildPhase'):
|
| - # Accumulate lines
|
| - content_lines = []
|
| - while 1:
|
| - content_lines.append(content)
|
| - if content == '\t\t};\n': break
|
| - parse_line_no += 1
|
| - content = self._raw_content[parse_line_no]
|
| - content = eval('%s.FromContent(content_lines)' % section)
|
| -
|
| - self._sections[section].append(content)
|
| - parse_line_no += 1
|
| - section_end_match = self.__class__.SECTION_END_RE.match(
|
| - self._raw_content[parse_line_no])
|
| - # Validate section end
|
| - if section_header_match.group(1) != section:
|
| - raise RuntimeError(
|
| - 'XcodeProject parse, section "%s" ended inside section "%s"' %
|
| - (section_end_match.group(1), section))
|
| - # Back around parse loop
|
| -
|
| - # Sanity overall group structure
|
| - if (not self._sections.has_key('PBXProject') or
|
| - len(self._sections['PBXProject']) != 1):
|
| - raise RuntimeError('PBXProject section insane')
|
| - root_obj_parsed = self.__class__.PROJECT_ROOT_OBJECT_RE.match(
|
| - self._tail[1])
|
| - if not root_obj_parsed:
|
| - raise RuntimeError('XcodeProject unable to parse project root object:\n%s'
|
| - % self._tail[1])
|
| - if root_obj_parsed.group(1) != self._sections['PBXProject'][0].uuid:
|
| - raise RuntimeError('XcodeProject root object does not match PBXProject')
|
| - self._root_group_uuid = self._sections['PBXProject'][0].main_group_uuid
|
| -
|
| - # Source root
|
| - self.source_root_path = os.path.abspath(
|
| - os.path.join(
|
| - # Directory that contains the project package
|
| - os.path.dirname(os.path.dirname(path)),
|
| - # Any relative path
|
| - self._sections['PBXProject'][0].project_root))
|
| -
|
| - # Build the absolute paths of the groups with these helpers
|
| - def GroupAbsRealPath(*elements):
|
| - return os.path.abspath(os.path.realpath(os.path.join(*elements)))
|
| - def GroupPathRecurse(group, parent_path):
|
| - descend = False
|
| - if group.source_tree == '"<absolute>"':
|
| - group.abs_path = GroupAbsRealPath(group.path)
|
| - descend = True
|
| - elif group.source_tree == '"<group>"':
|
| - if group.path:
|
| - group.abs_path = GroupAbsRealPath(parent_path, group.path)
|
| - else:
|
| - group.abs_path = parent_path
|
| - descend = True
|
| - elif group.source_tree == 'SOURCE_ROOT':
|
| - if group.path:
|
| - group.abs_path = GroupAbsRealPath(self.source_root_path, group.path)
|
| - else:
|
| - group.abs_path = GroupAbsRealPath(self.source_root_path)
|
| - descend = True
|
| - if descend:
|
| - for child_uuid in group.child_uuids:
|
| - # Try a group first
|
| - found_uuid = False
|
| - for other_group in self._sections['PBXGroup']:
|
| - if other_group.uuid == child_uuid:
|
| - found_uuid = True
|
| - GroupPathRecurse(other_group, group.abs_path)
|
| - break
|
| - if self._sections.has_key('PBXVariantGroup'):
|
| - for other_group in self._sections['PBXVariantGroup']:
|
| - if other_group.uuid == child_uuid:
|
| - found_uuid = True
|
| - GroupPathRecurse(other_group, group.abs_path)
|
| - break
|
| - if not found_uuid:
|
| - for file_ref in self._sections['PBXFileReference']:
|
| - if file_ref.uuid == child_uuid:
|
| - found_uuid = True
|
| - if file_ref.source_tree == '"<absolute>"':
|
| - file_ref.abs_path = GroupAbsRealPath(file_ref.path)
|
| - elif group.source_tree == '"<group>"':
|
| - file_ref.abs_path = GroupAbsRealPath(group.abs_path,
|
| - file_ref.path)
|
| - elif group.source_tree == 'SOURCE_ROOT':
|
| - file_ref.abs_path = GroupAbsRealPath(self.source_root_path,
|
| - file_ref.path)
|
| - break
|
| - if not found_uuid:
|
| - raise RuntimeError('XcodeProject group descent failed to find %s' %
|
| - child_uuid)
|
| - self._root_group = None
|
| - for group in self._sections['PBXGroup']:
|
| - if group.uuid == self._root_group_uuid:
|
| - self._root_group = group
|
| - GroupPathRecurse(group, self.source_root_path)
|
| - if not self._root_group:
|
| - raise RuntimeError('XcodeProject failed to find root group by UUID')
|
| -
|
| - def FileContent(self):
|
| - """Generate and return the project file content as a list of lines"""
|
| - content = []
|
| - content.extend(self._header[:-1])
|
| - for section in self._section_order:
|
| - content.append('\n/* Begin %s section */\n' % section)
|
| - for section_content in self._sections[section]:
|
| - content.append(str(section_content))
|
| - content.append('/* End %s section */\n' % section)
|
| - content.extend(self._tail)
|
| - return content
|
| -
|
| - def Update(self):
|
| - """Rewrite the project file in place with all updated metadata"""
|
| - __pychecker__ = 'no-deprecated'
|
| - # Not concerned with temp_path security here, just needed a unique name
|
| - temp_path = tempfile.mktemp(dir=os.path.dirname(self.path))
|
| - outfile = open(temp_path, 'w')
|
| - outfile.writelines(self.FileContent())
|
| - outfile.close()
|
| - # Rename is weird on Win32, see the docs,
|
| - os.unlink(self.path)
|
| - os.rename(temp_path, self.path)
|
| -
|
| - def NativeTargets(self):
|
| - """Obtain all PBXNativeTarget instances for this project
|
| -
|
| - Returns:
|
| - List of PBXNativeTarget instances
|
| - """
|
| - if self._sections.has_key('PBXNativeTarget'):
|
| - return self._sections['PBXNativeTarget']
|
| - else:
|
| - return []
|
| -
|
| - def NativeTargetForName(self, name):
|
| - """Obtain the target with a given name.
|
| -
|
| - Args:
|
| - name: Target name
|
| -
|
| - Returns:
|
| - PBXNativeTarget instance or None
|
| - """
|
| - for target in self.NativeTargets():
|
| - if target.name == name:
|
| - return target
|
| - return None
|
| -
|
| - def FileReferences(self):
|
| - """Obtain all PBXFileReference instances for this project
|
| -
|
| - Returns:
|
| - List of PBXFileReference instances
|
| - """
|
| - return self._sections['PBXFileReference']
|
| -
|
| - def SourcesBuildPhaseForTarget(self, target):
|
| - """Obtain the PBXSourcesBuildPhase instance for a target. Xcode allows
|
| - only one PBXSourcesBuildPhase per target and each target has a unique
|
| - PBXSourcesBuildPhase.
|
| -
|
| - Args:
|
| - target: PBXNativeTarget instance
|
| -
|
| - Returns:
|
| - PBXSourcesBuildPhase instance
|
| - """
|
| - sources_uuid = None
|
| - for i in range(len(target.build_phase_names)):
|
| - if target.build_phase_names[i] == 'Sources':
|
| - sources_uuid = target.build_phase_uuids[i]
|
| - break
|
| - if not sources_uuid:
|
| - raise RuntimeError('Missing PBXSourcesBuildPhase for target "%s"' %
|
| - target.name)
|
| - for sources_phase in self._sections['PBXSourcesBuildPhase']:
|
| - if sources_phase.uuid == sources_uuid:
|
| - return sources_phase
|
| - raise RuntimeError('Missing PBXSourcesBuildPhase for UUID "%s"' %
|
| - sources_uuid)
|
| -
|
| - def BuildFileForUUID(self, uuid):
|
| - """Look up a PBXBuildFile by UUID
|
| -
|
| - Args:
|
| - uuid: UUID of the PBXBuildFile to find
|
| -
|
| - Raises:
|
| - RuntimeError if no PBXBuildFile exists for |uuid|
|
| -
|
| - Returns:
|
| - PBXBuildFile instance
|
| - """
|
| - for build_file in self._sections['PBXBuildFile']:
|
| - if build_file.uuid == uuid:
|
| - return build_file
|
| - raise RuntimeError('Missing PBXBuildFile for UUID "%s"' % uuid)
|
| -
|
| - def FileReferenceForUUID(self, uuid):
|
| - """Look up a PBXFileReference by UUID
|
| -
|
| - Args:
|
| - uuid: UUID of the PBXFileReference to find
|
| -
|
| - Raises:
|
| - RuntimeError if no PBXFileReference exists for |uuid|
|
| -
|
| - Returns:
|
| - PBXFileReference instance
|
| - """
|
| - for file_ref in self._sections['PBXFileReference']:
|
| - if file_ref.uuid == uuid:
|
| - return file_ref
|
| - raise RuntimeError('Missing PBXFileReference for UUID "%s"' % uuid)
|
| -
|
| - def RemoveSourceFileReference(self, file_ref):
|
| - """Remove a source file's PBXFileReference from the project, cleaning up all
|
| - PBXGroup and PBXBuildFile references to that PBXFileReference and
|
| - furthermore, removing any PBXBuildFiles from all PBXNativeTarget source
|
| - lists.
|
| -
|
| - Args:
|
| - file_ref: PBXFileReference instance
|
| -
|
| - Raises:
|
| - RuntimeError if |file_ref| is not a source file reference in PBXBuildFile
|
| - """
|
| - self._sections['PBXFileReference'].remove(file_ref)
|
| - # Clean up build files
|
| - removed_build_files = []
|
| - for build_file in self._sections['PBXBuildFile']:
|
| - if build_file.file_ref_uuid == file_ref.uuid:
|
| - if build_file.type != 'Sources':
|
| - raise RuntimeError('Removing PBXBuildFile not of "Sources" type')
|
| - removed_build_files.append(build_file)
|
| - removed_build_file_uuids = []
|
| - for build_file in removed_build_files:
|
| - removed_build_file_uuids.append(build_file.uuid)
|
| - self._sections['PBXBuildFile'].remove(build_file)
|
| - # Clean up source references to the removed build files
|
| - for source_phase in self._sections['PBXSourcesBuildPhase']:
|
| - removal_indexes = []
|
| - for i in range(len(source_phase.file_uuids)):
|
| - if source_phase.file_uuids[i] in removed_build_file_uuids:
|
| - removal_indexes.append(i)
|
| - for removal_index in removal_indexes:
|
| - del source_phase.file_uuids[removal_index]
|
| - del source_phase.file_names[removal_index]
|
| - # Clean up group references
|
| - for group in self._sections['PBXGroup']:
|
| - removal_indexes = []
|
| - for i in range(len(group.child_uuids)):
|
| - if group.child_uuids[i] == file_ref.uuid:
|
| - removal_indexes.append(i)
|
| - for removal_index in removal_indexes:
|
| - del group.child_uuids[removal_index]
|
| - del group.child_names[removal_index]
|
| -
|
| - def RelativeSourceRootPath(self, abs_path):
|
| - """Convert a path to one relative to the project's SOURCE_ROOT if possible.
|
| - Generally this follows Xcode semantics, that is, a path is only converted
|
| - if it is a subpath of SOURCE_ROOT.
|
| -
|
| - Args:
|
| - abs_path: Absolute path to convert
|
| -
|
| - Returns:
|
| - String SOURCE_ROOT relative path if possible or None if not relative
|
| - to SOURCE_ROOT.
|
| - """
|
| - if abs_path.startswith(self.source_root_path + os.path.sep):
|
| - return abs_path[len(self.source_root_path + os.path.sep):]
|
| - else:
|
| - # Try to construct a relative path (bodged from ActiveState recipe
|
| - # 302594 since we can't assume Python 2.5 with os.path.relpath()
|
| - source_root_parts = self.source_root_path.split(os.path.sep)
|
| - target_parts = abs_path.split(os.path.sep)
|
| - # Guard against drive changes on Win32 and cygwin
|
| - if sys.platform == 'win32' and source_root_parts[0] <> target_parts[0]:
|
| - return None
|
| - if sys.platform == 'cygwin' and source_root_parts[2] <> target_parts[2]:
|
| - return None
|
| - for i in range(min(len(source_root_parts), len(target_parts))):
|
| - if source_root_parts[i] <> target_parts[i]: break
|
| - else:
|
| - i += 1
|
| - rel_parts = [os.path.pardir] * (len(source_root_parts) - i)
|
| - rel_parts.extend(target_parts[i:])
|
| - return os.path.join(*rel_parts)
|
| -
|
| - def RelativeGroupPath(self, abs_path):
|
| - """Convert a path to a group-relative path if possible
|
| -
|
| - Args:
|
| - abs_path: Absolute path to convert
|
| -
|
| - Returns:
|
| - Parent PBXGroup instance if possible or None
|
| - """
|
| - needed_path = os.path.dirname(abs_path)
|
| - possible_groups = [ g for g in self._sections['PBXGroup']
|
| - if g.abs_path == needed_path and
|
| - not g.name in NON_SOURCE_GROUP_NAMES ]
|
| - if len(possible_groups) < 1:
|
| - return None
|
| - elif len(possible_groups) == 1:
|
| - return possible_groups[0]
|
| - # Multiple groups match, try to find the best using some simple
|
| - # heuristics. Does only one group contain source?
|
| - groups_with_source = []
|
| - for group in possible_groups:
|
| - for child_uuid in group.child_uuids:
|
| - try:
|
| - self.FileReferenceForUUID(child_uuid)
|
| - except RuntimeError:
|
| - pass
|
| - else:
|
| - groups_with_source.append(group)
|
| - break
|
| - if len(groups_with_source) == 1:
|
| - return groups_with_source[0]
|
| - # Is only one _not_ the root group?
|
| - non_root_groups = [ g for g in possible_groups
|
| - if g is not self._root_group ]
|
| - if len(non_root_groups) == 1:
|
| - return non_root_groups[0]
|
| - # Best guess
|
| - if len(non_root_groups):
|
| - return non_root_groups[0]
|
| - elif len(groups_with_source):
|
| - return groups_with_source[0]
|
| - else:
|
| - return possible_groups[0]
|
| -
|
| - def AddSourceFile(self, path):
|
| - """Add a source file to the project, attempting to position it
|
| - in the GUI group hierarchy reasonably.
|
| -
|
| - NOTE: Adding a source file does not add it to any targets
|
| -
|
| - Args:
|
| - path: Absolute path to the file to add
|
| -
|
| - Returns:
|
| - PBXFileReference instance for the newly added source.
|
| - """
|
| - # Guess at file type
|
| - root, extension = os.path.splitext(path)
|
| - if EXTENSION_TO_XCODE_FILETYPE.has_key(extension):
|
| - source_type = EXTENSION_TO_XCODE_FILETYPE[extension]
|
| - else:
|
| - raise RuntimeError('Unknown source file extension "%s"' % extension)
|
| -
|
| - # Is group-relative possible for an existing group?
|
| - parent_group = self.RelativeGroupPath(os.path.abspath(path))
|
| - if parent_group:
|
| - new_file_ref = PBXFileReference(NewUUID(),
|
| - os.path.basename(path),
|
| - source_type,
|
| - None,
|
| - os.path.basename(path),
|
| - '"<group>"',
|
| - None)
|
| - # Chrome tries to keep its lists name sorted, try to match
|
| - i = 0
|
| - while i < len(parent_group.child_uuids):
|
| - # Only files are sorted, they keep groups at the top
|
| - try:
|
| - self.FileReferenceForUUID(parent_group.child_uuids[i])
|
| - if new_file_ref.name.lower() < parent_group.child_names[i].lower():
|
| - break
|
| - except RuntimeError:
|
| - pass # Must be a child group
|
| - i += 1
|
| - parent_group.child_names.insert(i, new_file_ref.name)
|
| - parent_group.child_uuids.insert(i, new_file_ref.uuid)
|
| - # Add file ref uuid sorted
|
| - self._sections['PBXFileReference'].append(new_file_ref)
|
| - self._sections['PBXFileReference'].sort(cmp=lambda x,y: cmp(x.uuid, y.uuid))
|
| - return new_file_ref
|
| -
|
| - # Group-relative failed, how about SOURCE_ROOT relative in the main group
|
| - src_rel_path = self.RelativeSourceRootPath(os.path.abspath(path))
|
| - if src_rel_path:
|
| - src_rel_path = src_rel_path.replace('\\', '/') # Convert to Unix
|
| - new_file_ref = PBXFileReference(NewUUID(),
|
| - os.path.basename(path),
|
| - source_type,
|
| - None,
|
| - src_rel_path,
|
| - 'SOURCE_ROOT',
|
| - None)
|
| - self._root_group.child_uuids.append(new_file_ref.uuid)
|
| - self._root_group.child_names.append(new_file_ref.name)
|
| - # Add file ref uuid sorted
|
| - self._sections['PBXFileReference'].append(new_file_ref)
|
| - self._sections['PBXFileReference'].sort(cmp=lambda x,y: cmp(x.uuid, y.uuid))
|
| - return new_file_ref
|
| -
|
| - # Win to Unix absolute paths probably not practical
|
| - raise RuntimeError('Could not construct group or source PBXFileReference '
|
| - 'for path "%s"' % path)
|
| -
|
| - def AddSourceFileToSourcesBuildPhase(self, source_ref, source_phase):
|
| - """Add a PBXFileReference to a PBXSourcesBuildPhase, creating a new
|
| - PBXBuildFile as needed.
|
| -
|
| - Args:
|
| - source_ref: PBXFileReference instance appropriate for use in
|
| - PBXSourcesBuildPhase
|
| - source_phase: PBXSourcesBuildPhase instance
|
| - """
|
| - # Prevent duplication
|
| - for source_uuid in source_phase.file_uuids:
|
| - build_file = self.BuildFileForUUID(source_uuid)
|
| - if build_file.file_ref_uuid == source_ref.uuid:
|
| - return
|
| - # Create PBXBuildFile
|
| - new_build_file = PBXBuildFile(NewUUID(),
|
| - source_ref.name,
|
| - 'Sources',
|
| - source_ref.uuid,
|
| - '')
|
| - # Add to build file list (uuid sorted)
|
| - self._sections['PBXBuildFile'].append(new_build_file)
|
| - self._sections['PBXBuildFile'].sort(cmp=lambda x,y: cmp(x.uuid, y.uuid))
|
| - # Add to sources phase list (name sorted)
|
| - i = 0
|
| - while i < len(source_phase.file_names):
|
| - if source_ref.name.lower() < source_phase.file_names[i].lower():
|
| - break
|
| - i += 1
|
| - source_phase.file_names.insert(i, new_build_file.name)
|
| - source_phase.file_uuids.insert(i, new_build_file.uuid)
|
| -
|
| -
|
| -class PBXProject(object):
|
| - """Class for PBXProject data section of an Xcode project file.
|
| -
|
| - Attributes:
|
| - uuid: Project UUID
|
| - main_group_uuid: UUID of the top-level PBXGroup
|
| - project_root: Relative path from project file wrapper to source_root_path
|
| - """
|
| -
|
| - PBXPROJECT_HEADER_RE = re.compile(
|
| - r'^\t\t([0-9A-F]{24}) /\* Project object \*/ = {\n$')
|
| - PBXPROJECT_MAIN_GROUP_RE = re.compile(
|
| - r'^\t\t\tmainGroup = ([0-9A-F]{24})(?: /\* .* \*/)?;\n$')
|
| - PBXPROJECT_ROOT_RE = re.compile(
|
| - r'^\t\t\tprojectRoot = (.*);\n$')
|
| -
|
| - @classmethod
|
| - def FromContent(klass, content_lines):
|
| - header_parsed = klass.PBXPROJECT_HEADER_RE.match(content_lines[0])
|
| - if not header_parsed:
|
| - raise RuntimeError('PBXProject unable to parse header content:\n%s'
|
| - % content_lines[0])
|
| - main_group_uuid = None
|
| - project_root = ''
|
| - for content_line in content_lines:
|
| - group_parsed = klass.PBXPROJECT_MAIN_GROUP_RE.match(content_line)
|
| - if group_parsed:
|
| - main_group_uuid = group_parsed.group(1)
|
| - root_parsed = klass.PBXPROJECT_ROOT_RE.match(content_line)
|
| - if root_parsed:
|
| - project_root = root_parsed.group(1)
|
| - if project_root.startswith('"'):
|
| - project_root = project_root[1:-1]
|
| - if not main_group_uuid:
|
| - raise RuntimeError('PBXProject missing main group')
|
| - return klass(content_lines, header_parsed.group(1),
|
| - main_group_uuid, project_root)
|
| -
|
| - def __init__(self, raw_lines, uuid, main_group_uuid, project_root):
|
| - self.uuid = uuid
|
| - self._raw_lines = raw_lines
|
| - self.main_group_uuid = main_group_uuid
|
| - self.project_root = project_root
|
| -
|
| - def __str__(self):
|
| - return ''.join(self._raw_lines)
|
| -
|
| -
|
| -class PBXBuildFile(object):
|
| - """Class for PBXBuildFile data from an Xcode project file.
|
| -
|
| - Attributes:
|
| - uuid: UUID for this instance
|
| - name: Basename of the build file
|
| - type: 'Sources' or 'Frameworks'
|
| - file_ref_uuid: UUID of the PBXFileReference for this file
|
| - """
|
| -
|
| - PBXBUILDFILE_LINE_RE = re.compile(
|
| - r'^\t\t([0-9A-F]{24}) /\* (.+) in (.+) \*/ = '
|
| - '{isa = PBXBuildFile; fileRef = ([0-9A-F]{24}) /\* (.+) \*/; (.*)};\n$')
|
| -
|
| - @classmethod
|
| - def FromContent(klass, content_line):
|
| - parsed = klass.PBXBUILDFILE_LINE_RE.match(content_line)
|
| - if not parsed:
|
| - raise RuntimeError('PBXBuildFile unable to parse content:\n%s'
|
| - % content_line)
|
| - if parsed.group(2) != parsed.group(5):
|
| - raise RuntimeError('PBXBuildFile name mismatch "%s" vs "%s"' %
|
| - (parsed.group(2), parsed.group(5)))
|
| - if not parsed.group(3) in ('Sources', 'Frameworks',
|
| - 'Resources', 'CopyFiles',
|
| - 'Headers', 'Copy Into Framework',
|
| - 'Rez', 'Copy Generated Headers'):
|
| - raise RuntimeError('PBXBuildFile unknown type "%s"' % parsed.group(3))
|
| - return klass(parsed.group(1), parsed.group(2), parsed.group(3),
|
| - parsed.group(4), parsed.group(6))
|
| -
|
| - def __init__(self, uuid, name, type, file_ref_uuid, raw_extras):
|
| - self.uuid = uuid
|
| - self.name = name
|
| - self.type = type
|
| - self.file_ref_uuid = file_ref_uuid
|
| - self._raw_extras = raw_extras
|
| -
|
| - def __str__(self):
|
| - return '\t\t%s /* %s in %s */ = ' \
|
| - '{isa = PBXBuildFile; fileRef = %s /* %s */; %s};\n' % (
|
| - self.uuid, self.name, self.type, self.file_ref_uuid, self.name,
|
| - self._raw_extras)
|
| -
|
| -
|
| -class PBXFileReference(object):
|
| - """Class for PBXFileReference data from an Xcode project file.
|
| -
|
| - Attributes:
|
| - uuid: UUID for this instance
|
| - name: Basename of the file
|
| - file_type: current active file type (explicit or assumed)
|
| - path: source_tree relative path (or absolute if source_tree is absolute)
|
| - source_tree: Source tree type (see PBX_VALID_SOURCE_TREE_TYPES)
|
| - abs_path: Absolute path to the file
|
| - """
|
| - PBXFILEREFERENCE_HEADER_RE = re.compile(
|
| - r'^\t\t([0-9A-F]{24}) /\* (.+) \*/ = {isa = PBXFileReference; ')
|
| - PBXFILEREFERENCE_FILETYPE_RE = re.compile(
|
| - r' (lastKnownFileType|explicitFileType) = ([^\;]+); ')
|
| - PBXFILEREFERENCE_PATH_RE = re.compile(r' path = ([^\;]+); ')
|
| - PBXFILEREFERENCE_SOURCETREE_RE = re.compile(r' sourceTree = ([^\;]+); ')
|
| -
|
| - @classmethod
|
| - def FromContent(klass, content_line):
|
| - header_parsed = klass.PBXFILEREFERENCE_HEADER_RE.match(content_line)
|
| - if not header_parsed:
|
| - raise RuntimeError('PBXFileReference unable to parse header content:\n%s'
|
| - % content_line)
|
| - type_parsed = klass.PBXFILEREFERENCE_FILETYPE_RE.search(content_line)
|
| - if not type_parsed:
|
| - raise RuntimeError('PBXFileReference unable to parse type content:\n%s'
|
| - % content_line)
|
| - if type_parsed.group(1) == 'lastKnownFileType':
|
| - last_known_type = type_parsed.group(2)
|
| - explicit_type = None
|
| - else:
|
| - last_known_type = None
|
| - explicit_type = type_parsed.group(2)
|
| - path_parsed = klass.PBXFILEREFERENCE_PATH_RE.search(content_line)
|
| - if not path_parsed:
|
| - raise RuntimeError('PBXFileReference unable to parse path content:\n%s'
|
| - % content_line)
|
| - tree_parsed = klass.PBXFILEREFERENCE_SOURCETREE_RE.search(content_line)
|
| - if not tree_parsed:
|
| - raise RuntimeError(
|
| - 'PBXFileReference unable to parse source tree content:\n%s'
|
| - % content_line)
|
| - return klass(header_parsed.group(1), header_parsed.group(2),
|
| - last_known_type, explicit_type, path_parsed.group(1),
|
| - tree_parsed.group(1), content_line)
|
| -
|
| - def __init__(self, uuid, name, last_known_file_type, explicit_file_type,
|
| - path, source_tree, raw_line):
|
| - self.uuid = uuid
|
| - self.name = name
|
| - self._last_known_file_type = last_known_file_type
|
| - self._explicit_file_type = explicit_file_type
|
| - if explicit_file_type:
|
| - self.file_type = explicit_file_type
|
| - else:
|
| - self.file_type = last_known_file_type
|
| - self.path = path
|
| - self.source_tree = source_tree
|
| - self.abs_path = None
|
| - self._raw_line = raw_line
|
| -
|
| - def __str__(self):
|
| - # Raw available?
|
| - if self._raw_line: return self._raw_line
|
| - # Construct our own
|
| - if self._last_known_file_type:
|
| - print_file_type = 'lastKnownFileType = %s; ' % self._last_known_file_type
|
| - elif self._explicit_file_type:
|
| - print_file_type = 'explicitFileType = %s; ' % self._explicit_file_type
|
| - else:
|
| - raise RuntimeError('No known file type for stringification')
|
| - name_attribute = ''
|
| - if self.name != self.path:
|
| - name_attribute = 'name = %s; ' % self.name
|
| - print_path = self.path
|
| - if QUOTE_PATH_RE.search(print_path):
|
| - print_path = '"%s"' % print_path
|
| - return '\t\t%s /* %s */ = ' \
|
| - '{isa = PBXFileReference; ' \
|
| - 'fileEncoding = 4; ' \
|
| - '%s' \
|
| - '%s' \
|
| - 'path = %s; sourceTree = %s; };\n' % (
|
| - self.uuid, self.name, print_file_type,
|
| - name_attribute, print_path, self.source_tree)
|
| -
|
| -
|
| -class PBXGroup(object):
|
| - """Class for PBXGroup data from an Xcode project file.
|
| -
|
| - Attributes:
|
| - uuid: UUID for this instance
|
| - name: Group (folder) name
|
| - path: source_tree relative path (or absolute if source_tree is absolute)
|
| - source_tree: Source tree type (see PBX_VALID_SOURCE_TREE_TYPES)
|
| - abs_path: Absolute path to the group
|
| - child_uuids: Ordered list of PBXFileReference UUIDs
|
| - child_names: Ordered list of PBXFileReference names
|
| - """
|
| -
|
| - PBXGROUP_HEADER_RE = re.compile(r'^\t\t([0-9A-F]{24}) (?:/\* .* \*/ )?= {\n$')
|
| - PBXGROUP_FIELD_RE = re.compile(r'^\t\t\t(.*) = (.*);\n$')
|
| - PBXGROUP_CHILD_RE = re.compile(r'^\t\t\t\t([0-9A-F]{24}) /\* (.*) \*/,\n$')
|
| -
|
| - @classmethod
|
| - def FromContent(klass, content_lines):
|
| - # Header line
|
| - header_parsed = klass.PBXGROUP_HEADER_RE.match(content_lines[0])
|
| - if not header_parsed:
|
| - raise RuntimeError('PBXGroup unable to parse header content:\n%s'
|
| - % content_lines[0])
|
| - name = None
|
| - path = ''
|
| - source_tree = None
|
| - tab_width = None
|
| - uses_tabs = None
|
| - indent_width = None
|
| - child_uuids = []
|
| - child_names = []
|
| - # Parse line by line
|
| - content_line_no = 0
|
| - while 1:
|
| - content_line_no += 1
|
| - content_line = content_lines[content_line_no]
|
| - if content_line == '\t\t};\n': break
|
| - if content_line == '\t\t\tisa = PBXGroup;\n': continue
|
| - if content_line == '\t\t\tisa = PBXVariantGroup;\n': continue
|
| - # Child groups
|
| - if content_line == '\t\t\tchildren = (\n':
|
| - content_line_no += 1
|
| - content_line = content_lines[content_line_no]
|
| - while content_line != '\t\t\t);\n':
|
| - child_parsed = klass.PBXGROUP_CHILD_RE.match(content_line)
|
| - if not child_parsed:
|
| - raise RuntimeError('PBXGroup unable to parse child content:\n%s'
|
| - % content_line)
|
| - child_uuids.append(child_parsed.group(1))
|
| - child_names.append(child_parsed.group(2))
|
| - content_line_no += 1
|
| - content_line = content_lines[content_line_no]
|
| - continue # Back to top of loop on end of children
|
| - # Other fields
|
| - field_parsed = klass.PBXGROUP_FIELD_RE.match(content_line)
|
| - if not field_parsed:
|
| - raise RuntimeError('PBXGroup unable to parse field content:\n%s'
|
| - % content_line)
|
| - if field_parsed.group(1) == 'name':
|
| - name = field_parsed.group(2)
|
| - elif field_parsed.group(1) == 'path':
|
| - path = field_parsed.group(2)
|
| - elif field_parsed.group(1) == 'sourceTree':
|
| - if not field_parsed.group(2) in PBX_VALID_SOURCE_TREE_TYPES:
|
| - raise RuntimeError('PBXGroup unknown source tree type "%s"'
|
| - % field_parsed.group(2))
|
| - source_tree = field_parsed.group(2)
|
| - elif field_parsed.group(1) == 'tabWidth':
|
| - tab_width = field_parsed.group(2)
|
| - elif field_parsed.group(1) == 'usesTabs':
|
| - uses_tabs = field_parsed.group(2)
|
| - elif field_parsed.group(1) == 'indentWidth':
|
| - indent_width = field_parsed.group(2)
|
| - else:
|
| - raise RuntimeError('PBXGroup unknown field "%s"'
|
| - % field_parsed.group(1))
|
| - if path and path.startswith('"'):
|
| - path = path[1:-1]
|
| - if name and name.startswith('"'):
|
| - name = name[1:-1]
|
| - return klass(header_parsed.group(1), name, path, source_tree, child_uuids,
|
| - child_names, tab_width, uses_tabs, indent_width)
|
| -
|
| - def __init__(self, uuid, name, path, source_tree, child_uuids, child_names,
|
| - tab_width, uses_tabs, indent_width):
|
| - self.uuid = uuid
|
| - self.name = name
|
| - self.path = path
|
| - self.source_tree = source_tree
|
| - self.child_uuids = child_uuids
|
| - self.child_names = child_names
|
| - self.abs_path = None
|
| - # Semantically I'm not sure these aren't an error, but they
|
| - # appear in some projects
|
| - self._tab_width = tab_width
|
| - self._uses_tabs = uses_tabs
|
| - self._indent_width = indent_width
|
| -
|
| - def __str__(self):
|
| - if self.name:
|
| - header_comment = '/* %s */ ' % self.name
|
| - elif self.path:
|
| - header_comment = '/* %s */ ' % self.path
|
| - else:
|
| - header_comment = ''
|
| - if self.name:
|
| - if QUOTE_PATH_RE.search(self.name):
|
| - name_attribute = '\t\t\tname = "%s";\n' % self.name
|
| - else:
|
| - name_attribute = '\t\t\tname = %s;\n' % self.name
|
| - else:
|
| - name_attribute = ''
|
| - if self.path:
|
| - if QUOTE_PATH_RE.search(self.path):
|
| - path_attribute = '\t\t\tpath = "%s";\n' % self.path
|
| - else:
|
| - path_attribute = '\t\t\tpath = %s;\n' % self.path
|
| - else:
|
| - path_attribute = ''
|
| - child_lines = []
|
| - for x in range(len(self.child_uuids)):
|
| - child_lines.append('\t\t\t\t%s /* %s */,\n' %
|
| - (self.child_uuids[x], self.child_names[x]))
|
| - children = ''.join(child_lines)
|
| - tab_width_attribute = ''
|
| - if self._tab_width:
|
| - tab_width_attribute = '\t\t\ttabWidth = %s;\n' % self._tab_width
|
| - uses_tabs_attribute = ''
|
| - if self._uses_tabs:
|
| - uses_tabs_attribute = '\t\t\tusesTabs = %s;\n' % self._uses_tabs
|
| - indent_width_attribute = ''
|
| - if self._indent_width:
|
| - indent_width_attribute = '\t\t\tindentWidth = %s;\n' % self._indent_width
|
| - return '\t\t%s %s= {\n' \
|
| - '\t\t\tisa = %s;\n' \
|
| - '\t\t\tchildren = (\n' \
|
| - '%s' \
|
| - '\t\t\t);\n' \
|
| - '%s' \
|
| - '%s' \
|
| - '%s' \
|
| - '\t\t\tsourceTree = %s;\n' \
|
| - '%s' \
|
| - '%s' \
|
| - '\t\t};\n' % (
|
| - self.uuid, header_comment,
|
| - self.__class__.__name__,
|
| - children,
|
| - indent_width_attribute,
|
| - name_attribute,
|
| - path_attribute, self.source_tree,
|
| - tab_width_attribute, uses_tabs_attribute)
|
| -
|
| -
|
| -class PBXVariantGroup(PBXGroup):
|
| - pass
|
| -
|
| -
|
| -class PBXNativeTarget(object):
|
| - """Class for PBXNativeTarget data from an Xcode project file.
|
| -
|
| - Attributes:
|
| - name: Target name
|
| - build_phase_uuids: Ordered list of build phase UUIDs
|
| - build_phase_names: Ordered list of build phase names
|
| -
|
| - NOTE: We do not have wrapper classes for all build phase data types!
|
| - """
|
| -
|
| - PBXNATIVETARGET_HEADER_RE = re.compile(
|
| - r'^\t\t([0-9A-F]{24}) /\* (.*) \*/ = {\n$')
|
| - PBXNATIVETARGET_BUILD_PHASE_RE = re.compile(
|
| - r'^\t\t\t\t([0-9A-F]{24}) /\* (.*) \*/,\n$')
|
| -
|
| - @classmethod
|
| - def FromContent(klass, content_lines):
|
| - header_parsed = klass.PBXNATIVETARGET_HEADER_RE.match(content_lines[0])
|
| - if not header_parsed:
|
| - raise RuntimeError('PBXNativeTarget unable to parse header content:\n%s'
|
| - % content_lines[0])
|
| - build_phase_uuids = []
|
| - build_phase_names = []
|
| - content_line_no = 0
|
| - while 1:
|
| - content_line_no += 1
|
| - content_line = content_lines[content_line_no]
|
| - if content_line == '\t\t};\n': break
|
| - if content_line == '\t\t\tisa = PBXNativeTarget;\n': continue
|
| - # Build phases groups
|
| - if content_line == '\t\t\tbuildPhases = (\n':
|
| - content_line_no += 1
|
| - content_line = content_lines[content_line_no]
|
| - while content_line != '\t\t\t);\n':
|
| - phase_parsed = klass.PBXNATIVETARGET_BUILD_PHASE_RE.match(
|
| - content_line)
|
| - if not phase_parsed:
|
| - raise RuntimeError(
|
| - 'PBXNativeTarget unable to parse build phase content:\n%s'
|
| - % content_line)
|
| - build_phase_uuids.append(phase_parsed.group(1))
|
| - build_phase_names.append(phase_parsed.group(2))
|
| - content_line_no += 1
|
| - content_line = content_lines[content_line_no]
|
| - break # Don't care about the rest of the content
|
| - return klass(content_lines, header_parsed.group(2), build_phase_uuids,
|
| - build_phase_names)
|
| -
|
| - def __init__(self, raw_lines, name, build_phase_uuids, build_phase_names):
|
| - self._raw_lines = raw_lines
|
| - self.name = name
|
| - self.build_phase_uuids = build_phase_uuids
|
| - self.build_phase_names = build_phase_names
|
| -
|
| - def __str__(self):
|
| - return ''.join(self._raw_lines)
|
| -
|
| -
|
| -class PBXSourcesBuildPhase(object):
|
| - """Class for PBXSourcesBuildPhase data from an Xcode project file.
|
| -
|
| - Attributes:
|
| - uuid: UUID for this instance
|
| - build_action_mask: Xcode magic mask constant
|
| - run_only_for_deployment_postprocessing: deployment postprocess flag
|
| - file_uuids: Ordered list of PBXBuildFile UUIDs
|
| - file_names: Ordered list of PBXBuildFile names (basename)
|
| - """
|
| -
|
| - PBXSOURCESBUILDPHASE_HEADER_RE = re.compile(
|
| - r'^\t\t([0-9A-F]{24}) /\* Sources \*/ = {\n$')
|
| - PBXSOURCESBUILDPHASE_FIELD_RE = re.compile(r'^\t\t\t(.*) = (.*);\n$')
|
| - PBXSOURCESBUILDPHASE_FILE_RE = re.compile(
|
| - r'^\t\t\t\t([0-9A-F]{24}) /\* (.*) in Sources \*/,\n$')
|
| -
|
| - @classmethod
|
| - def FromContent(klass, content_lines):
|
| - header_parsed = klass.PBXSOURCESBUILDPHASE_HEADER_RE.match(content_lines[0])
|
| - if not header_parsed:
|
| - raise RuntimeError(
|
| - 'PBXSourcesBuildPhase unable to parse header content:\n%s'
|
| - % content_lines[0])
|
| - # Parse line by line
|
| - build_action_mask = None
|
| - run_only_for_deployment_postprocessing = None
|
| - file_uuids = []
|
| - file_names = []
|
| - content_line_no = 0
|
| - while 1:
|
| - content_line_no += 1
|
| - content_line = content_lines[content_line_no]
|
| - if content_line == '\t\t};\n': break
|
| - if content_line == '\t\t\tisa = PBXSourcesBuildPhase;\n': continue
|
| - # Files
|
| - if content_line == '\t\t\tfiles = (\n':
|
| - content_line_no += 1
|
| - content_line = content_lines[content_line_no]
|
| - while content_line != '\t\t\t);\n':
|
| - file_parsed = klass.PBXSOURCESBUILDPHASE_FILE_RE.match(content_line)
|
| - if not file_parsed:
|
| - raise RuntimeError(
|
| - 'PBXSourcesBuildPhase unable to parse file content:\n%s'
|
| - % content_line)
|
| - file_uuids.append(file_parsed.group(1))
|
| - file_names.append(file_parsed.group(2))
|
| - content_line_no += 1
|
| - content_line = content_lines[content_line_no]
|
| - continue # Back to top of loop on end of files list
|
| - # Other fields
|
| - field_parsed = klass.PBXSOURCESBUILDPHASE_FIELD_RE.match(content_line)
|
| - if not field_parsed:
|
| - raise RuntimeError(
|
| - 'PBXSourcesBuildPhase unable to parse field content:\n%s'
|
| - % content_line)
|
| - if field_parsed.group(1) == 'buildActionMask':
|
| - build_action_mask = field_parsed.group(2)
|
| - elif field_parsed.group(1) == 'runOnlyForDeploymentPostprocessing':
|
| - run_only_for_deployment_postprocessing = field_parsed.group(2)
|
| - else:
|
| - raise RuntimeError('PBXSourcesBuildPhase unknown field "%s"'
|
| - % field_parsed.group(1))
|
| - return klass(header_parsed.group(1), build_action_mask,
|
| - run_only_for_deployment_postprocessing,
|
| - file_uuids, file_names)
|
| -
|
| - def __init__(self, uuid, build_action_mask,
|
| - run_only_for_deployment_postprocessing,
|
| - file_uuids, file_names):
|
| - self.uuid = uuid
|
| - self.build_action_mask = build_action_mask
|
| - self.run_only_for_deployment_postprocessing = \
|
| - run_only_for_deployment_postprocessing
|
| - self.file_uuids = file_uuids
|
| - self.file_names = file_names
|
| -
|
| - def __str__(self):
|
| - file_lines = []
|
| - for x in range(len(self.file_uuids)):
|
| - file_lines.append('\t\t\t\t%s /* %s in Sources */,\n' %
|
| - (self.file_uuids[x], self.file_names[x]))
|
| - files = ''.join(file_lines)
|
| - return '\t\t%s /* Sources */ = {\n' \
|
| - '\t\t\tisa = PBXSourcesBuildPhase;\n' \
|
| - '\t\t\tbuildActionMask = %s;\n' \
|
| - '\t\t\tfiles = (\n' \
|
| - '%s' \
|
| - '\t\t\t);\n' \
|
| - '\t\t\trunOnlyForDeploymentPostprocessing = %s;\n' \
|
| - '\t\t};\n' % (
|
| - self.uuid, self.build_action_mask, files,
|
| - self.run_only_for_deployment_postprocessing)
|
| -
|
| -
|
| -def Usage(optparse):
|
| - optparse.print_help()
|
| - print '\n' \
|
| - 'Commands:\n' \
|
| - ' list_native_targets: List Xcode "native" (source compilation)\n' \
|
| - ' targets by name.\n' \
|
| - ' list_target_sources: List project-relative source files in the\n' \
|
| - ' specified Xcode "native" target.\n' \
|
| - ' remove_source [sourcefile ...]: Remove the specified source files\n' \
|
| - ' from every target in the project (target is ignored).\n' \
|
| - ' add_source [sourcefile ...]: Add the specified source files\n' \
|
| - ' to the specified target.\n'
|
| - sys.exit(2)
|
| -
|
| -
|
| -def Main():
|
| - # Use argument structure like xcodebuild commandline
|
| - option_parser = optparse.OptionParser(
|
| - usage='usage: %prog -p projectname [ -t targetname ] ' \
|
| - '<command> [...]',
|
| - add_help_option=False)
|
| - option_parser.add_option(
|
| - '-h', '--help', action='store_true', dest='help',
|
| - default=False, help=optparse.SUPPRESS_HELP)
|
| - option_parser.add_option(
|
| - '-p', '--project', action='store', type='string',
|
| - dest='project', metavar='projectname',
|
| - help='Manipulate the project specified by projectname.')
|
| - option_parser.add_option(
|
| - '-t', '--target', action='store', type='string',
|
| - dest='target', metavar='targetname',
|
| - help='Manipulate the target specified by targetname.')
|
| - (options, args) = option_parser.parse_args()
|
| -
|
| - # Since we have more elaborate commands, handle help
|
| - if options.help:
|
| - Usage(option_parser)
|
| -
|
| - # Xcode project file
|
| - if not options.project:
|
| - option_parser.error('Xcode project file must be specified.')
|
| - project_path = os.path.abspath(CygwinPathClean(options.project))
|
| - if project_path.endswith('.xcodeproj'):
|
| - project_path = os.path.join(project_path, 'project.pbxproj')
|
| - if not project_path.endswith(os.sep + 'project.pbxproj'):
|
| - option_parser.error('Invalid Xcode project file path \"%s\"' % project_path)
|
| - if not os.path.exists(project_path):
|
| - option_parser.error('Missing Xcode project file \"%s\"' % project_path)
|
| -
|
| - # Construct project object
|
| - project = XcodeProject(project_path)
|
| -
|
| - # Switch on command
|
| -
|
| - # List native target names (default command)
|
| - if len(args) < 1 or args[0] == 'list_native_targets':
|
| - # Ape xcodebuild output
|
| - target_names = []
|
| - for target in project.NativeTargets():
|
| - target_names.append(target.name)
|
| - print 'Information about project "%s"\n Native Targets:\n %s' % (
|
| - project.name,
|
| - '\n '.join(target_names))
|
| -
|
| - if len(args) < 1:
|
| - # Be friendly and print some hints for further actions.
|
| - print
|
| - print 'To add or remove files from given target, run:'
|
| - print '\txcodebodge.py -p <project> -t <target> add_source <file_name>'
|
| - print '\txcodebodge.py -p <project> -t <target> remove_source <file_name>'
|
| -
|
| - # List files in a native target
|
| - elif args[0] == 'list_target_sources':
|
| - if len(args) != 1:
|
| - option_parser.error('list_target_sources takes no arguments')
|
| - if not options.target:
|
| - option_parser.error('list_target_sources requires a target')
|
| - # Validate target and get list of files
|
| - target = project.NativeTargetForName(options.target)
|
| - if not target:
|
| - option_parser.error('No native target named "%s"' % options.target)
|
| - sources_phase = project.SourcesBuildPhaseForTarget(target)
|
| - target_files = []
|
| - for source_uuid in sources_phase.file_uuids:
|
| - build_file = project.BuildFileForUUID(source_uuid)
|
| - file_ref = project.FileReferenceForUUID(build_file.file_ref_uuid)
|
| - pretty_path = project.RelativeSourceRootPath(file_ref.abs_path)
|
| - if pretty_path:
|
| - target_files.append(pretty_path)
|
| - else:
|
| - target_files.append(file_ref.abs_path)
|
| - # Ape xcodebuild output
|
| - print 'Information about project "%s" target "%s"\n' \
|
| - ' Files:\n %s' % (project.name, options.target,
|
| - '\n '.join(target_files))
|
| -
|
| - # Remove source files
|
| - elif args[0] == 'remove_source':
|
| - if len(args) < 2:
|
| - option_parser.error('remove_source needs one or more source files')
|
| - if options.target:
|
| - option_parser.error(
|
| - 'remove_source does not support removal from a single target')
|
| - for source_path in args[1:]:
|
| - source_path = CygwinPathClean(source_path)
|
| - found = False
|
| - for file_ref in project.FileReferences():
|
| - # Try undecorated path, abs_path and our prettified paths
|
| - if (file_ref.path == source_path or (
|
| - file_ref.abs_path and (
|
| - file_ref.abs_path == os.path.abspath(source_path) or
|
| - project.RelativeSourceRootPath(file_ref.abs_path) == source_path))):
|
| - # Found a matching file ref, remove it
|
| - found = True
|
| - project.RemoveSourceFileReference(file_ref)
|
| - if not found:
|
| - option_parser.error('No matching source file "%s"' % source_path)
|
| - project.Update()
|
| -
|
| - # Add source files
|
| - elif args[0] == 'add_source':
|
| - if len(args) < 2:
|
| - option_parser.error('add_source needs one or more source files')
|
| - if not options.target:
|
| - option_parser.error('add_source requires a target')
|
| - # Look for the target we want to add too.
|
| - target = project.NativeTargetForName(options.target)
|
| - if not target:
|
| - option_parser.error('No native target named "%s"' % options.target)
|
| - # Get the sources build phase
|
| - sources_phase = project.SourcesBuildPhaseForTarget(target)
|
| - # Loop new sources
|
| - for source_path in args[1:]:
|
| - source_path = CygwinPathClean(source_path)
|
| - if not os.path.exists(os.path.abspath(source_path)):
|
| - option_parser.error('File "%s" not found' % source_path)
|
| - # Don't generate duplicate file references if we don't need them
|
| - source_ref = None
|
| - for file_ref in project.FileReferences():
|
| - # Try undecorated path, abs_path and our prettified paths
|
| - if (file_ref.path == source_path or (
|
| - file_ref.abs_path and (
|
| - file_ref.abs_path == os.path.abspath(source_path) or
|
| - project.RelativeSourceRootPath(file_ref.abs_path) == source_path))):
|
| - source_ref = file_ref
|
| - break
|
| - if not source_ref:
|
| - # Create a new source file ref
|
| - source_ref = project.AddSourceFile(os.path.abspath(source_path))
|
| - # Add the new source file reference to the target if its a safe type
|
| - if source_ref.file_type in SOURCES_XCODE_FILETYPES:
|
| - project.AddSourceFileToSourcesBuildPhase(source_ref, sources_phase)
|
| - project.Update()
|
| -
|
| - # Private sanity check. On an unmodified project make sure our output is
|
| - # the same as the input
|
| - elif args[0] == 'parse_sanity':
|
| - if ''.join(project.FileContent()) != ''.join(project._raw_content):
|
| - option_parser.error('Project rewrite sanity fail "%s"' % project.path)
|
| -
|
| - else:
|
| - Usage(option_parser)
|
| -
|
| -
|
| -if __name__ == '__main__':
|
| - Main()
|
|
|