Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(140)

Unified Diff: tools/xcodebodge/xcodebodge.py

Issue 2167003: Cleanup: remove xcodebodge tool.... (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src/
Patch Set: Created 10 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « no previous file | no next file » | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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()
« no previous file with comments | « no previous file | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698