diff options
author | phajdan.jr@chromium.org <phajdan.jr@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-05-26 16:27:02 +0000 |
---|---|---|
committer | phajdan.jr@chromium.org <phajdan.jr@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-05-26 16:27:02 +0000 |
commit | afab2753b934b145bb6901243435d2d67cd33cb7 (patch) | |
tree | 21680753727a30e4526fd6a5ad1a9335f5ed7db9 | |
parent | d4c189468cb4f5b5ae44c6da1df02f504c8345fa (diff) | |
download | chromium_src-afab2753b934b145bb6901243435d2d67cd33cb7.zip chromium_src-afab2753b934b145bb6901243435d2d67cd33cb7.tar.gz chromium_src-afab2753b934b145bb6901243435d2d67cd33cb7.tar.bz2 |
Cleanup: remove xcodebodge tool.
It's no longer needed. Now we have gyp, and Xcode project files
are generated by gyp.
TEST=none
BUG=none
Review URL: http://codereview.chromium.org/2167003
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@48278 0039d316-1c4b-4281-b951-d872f2087c98
-rwxr-xr-x | tools/xcodebodge/xcodebodge.py | 1274 |
1 files changed, 0 insertions, 1274 deletions
diff --git a/tools/xcodebodge/xcodebodge.py b/tools/xcodebodge/xcodebodge.py deleted file mode 100755 index f4beabb9..0000000 --- a/tools/xcodebodge/xcodebodge.py +++ /dev/null @@ -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() |