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

Side by Side 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 unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « no previous file | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 #!/usr/bin/python
2 # Copyright (c) 2008 The Chromium Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file.
5 """
6 Commandline modification of Xcode project files
7 """
8
9 import sys
10 import os
11 import optparse
12 import re
13 import tempfile
14 import random
15 import subprocess
16
17 random.seed() # Seed the generator
18
19
20 # All known project build path source tree path reference types
21 PBX_VALID_SOURCE_TREE_TYPES = ('"<group>"',
22 'SOURCE_ROOT',
23 '"<absolute>"',
24 'BUILT_PRODUCTS_DIR',
25 'DEVELOPER_DIR',
26 'SDKROOT',
27 'CONFIGURATION_TEMP_DIR')
28 # Paths with some characters appear quoted
29 QUOTE_PATH_RE = re.compile('\s|-|\+')
30
31 # Supported Xcode file types
32 EXTENSION_TO_XCODE_FILETYPE = {
33 '.h' : 'sourcecode.c.h',
34 '.c' : 'sourcecode.c.c',
35 '.cpp' : 'sourcecode.cpp.cpp',
36 '.cc' : 'sourcecode.cpp.cpp',
37 '.cxx' : 'sourcecode.cpp.cpp',
38 '.m' : 'sourcecode.c.objc',
39 '.mm' : 'sourcecode.c.objcpp',
40 }
41
42 # File types that can be added to a Sources phase
43 SOURCES_XCODE_FILETYPES = ( 'sourcecode.c.c',
44 'sourcecode.cpp.cpp',
45 'sourcecode.c.objc',
46 'sourcecode.c.objcpp' )
47
48 # Avoid inserting source files into these common Xcode group names. Because
49 # Xcode allows any names for these groups this list cannot be authoritative,
50 # but these are common names in the Xcode templates.
51 NON_SOURCE_GROUP_NAMES = ( 'Frameworks',
52 'Resources',
53 'Products',
54 'Derived Sources',
55 'Configurations',
56 'Documentation',
57 'Frameworks and Libraries',
58 'External Frameworks and Libraries',
59 'Libraries' )
60
61
62 def NewUUID():
63 """Create a new random Xcode UUID"""
64 __pychecker__ = 'unusednames=i'
65 elements = []
66 for i in range(24):
67 elements.append(hex(random.randint(0, 15))[-1].upper())
68 return ''.join(elements)
69
70 def CygwinPathClean(path):
71 """Folks use Cygwin shells with standard Win32 Python which can't handle
72 Cygwin paths. Run everything through cygpath if we can (conveniently
73 cygpath does the right thing with normal Win32 paths).
74 """
75 # Look for Unix-like path with Win32 Python
76 if sys.platform == 'win32' and path.startswith('/'):
77 cygproc = subprocess.Popen(('cygpath', '-a', '-w', path),
78 stdout=subprocess.PIPE)
79 (stdout_content, stderr_content) = cygproc.communicate()
80 return stdout_content.rstrip()
81 # Convert all paths to cygpaths if we're using cygwin python
82 if sys.platform == 'cygwin':
83 cygproc = subprocess.Popen(('cygpath', '-a', '-u', path),
84 stdout=subprocess.PIPE)
85 (stdout_content, stderr_content) = cygproc.communicate()
86 return stdout_content.rstrip()
87 # Fallthrough for all other cases
88 return path
89
90 class XcodeProject(object):
91 """Class for reading/writing Xcode project files.
92 This is not a general parser or representation. It is restricted to just
93 the Xcode internal objects we need.
94
95 Args:
96 path: Absolute path to Xcode project file (including project.pbxproj
97 filename)
98 Attributes:
99 path: Full path to the project.pbxproj file
100 name: Project name (wrapper directory basename without extension)
101 source_root_path: Absolute path for Xcode's SOURCE_ROOT
102 """
103
104 EXPECTED_PROJECT_HEADER_RE = re.compile(
105 r'^// !\$\*UTF8\*\$!\n' \
106 '\{\n' \
107 '\tarchiveVersion = 1;\n' \
108 '\tclasses = \{\n' \
109 '\t\};\n' \
110 '\tobjectVersion = \d+;\n' \
111 '\tobjects = \{\n' \
112 '\n')
113 SECTION_BEGIN_RE = re.compile(r'^/\* Begin (.*) section \*/\n$')
114 SECTION_END_RE = re.compile(r'^/\* End (.*) section \*/\n$')
115 PROJECT_ROOT_OBJECT_RE = re.compile(
116 r'^\trootObject = ([0-9A-F]{24}) /\* Project object \*/;\n$')
117
118 def __init__(self, path):
119 self.path = path
120 self.name = os.path.splitext(os.path.basename(os.path.dirname(path)))[0]
121
122 # Load project. Ideally we would use plistlib, but sadly that only reads
123 # XML plists. A real parser with pyparsing
124 # (http://pyparsing.wikispaces.com/) might be another option, but for now
125 # we'll do the simple (?!?) thing.
126 project_fh = open(self.path, 'rU')
127 self._raw_content = project_fh.readlines()
128 project_fh.close()
129
130 # Store and check header
131 if len(self._raw_content) < 8:
132 print >> sys.stderr, ''.join(self._raw_content)
133 raise RuntimeError('XcodeProject file "%s" too short' % path)
134 self._header = tuple(self._raw_content[:8])
135 if not self.__class__.EXPECTED_PROJECT_HEADER_RE.match(''.join(self._header) ):
136 print >> sys.stderr, ''.join(self._header)
137 raise RuntimeError('XcodeProject file "%s" wrong header' % path)
138
139 # Find and store tail (some projects have additional whitespace at end)
140 self._tail = []
141 for tail_line in reversed(self._raw_content):
142 self._tail.insert(0, tail_line)
143 if tail_line == '\t};\n': break
144
145 # Ugly ugly project parsing, turn each commented section into a separate
146 # set of objects. For types we don't have a custom representation for,
147 # store the raw lines.
148 self._section_order = []
149 self._sections = {}
150 parse_line_no = len(self._header)
151 while parse_line_no < (len(self._raw_content) - len(self._tail)):
152 section_header_match = self.__class__.SECTION_BEGIN_RE.match(
153 self._raw_content[parse_line_no])
154 # Loop to next section header
155 if not section_header_match:
156 parse_line_no += 1
157 continue
158
159 section = section_header_match.group(1)
160 self._section_order.append(section)
161 self._sections[section] = []
162
163 # Advance to first line of the section
164 parse_line_no += 1
165
166 # Read in the section, using custom classes where we need them
167 section_end_match = self.__class__.SECTION_END_RE.match(
168 self._raw_content[parse_line_no])
169 while not section_end_match:
170 # Unhandled lines
171 content = self._raw_content[parse_line_no]
172 # Sections we can parse line-by-line
173 if section in ('PBXBuildFile', 'PBXFileReference'):
174 content = eval('%s.FromContent(content)' % section)
175 # Multiline sections
176 elif section in ('PBXGroup', 'PBXVariantGroup', 'PBXProject',
177 'PBXNativeTarget', 'PBXSourcesBuildPhase'):
178 # Accumulate lines
179 content_lines = []
180 while 1:
181 content_lines.append(content)
182 if content == '\t\t};\n': break
183 parse_line_no += 1
184 content = self._raw_content[parse_line_no]
185 content = eval('%s.FromContent(content_lines)' % section)
186
187 self._sections[section].append(content)
188 parse_line_no += 1
189 section_end_match = self.__class__.SECTION_END_RE.match(
190 self._raw_content[parse_line_no])
191 # Validate section end
192 if section_header_match.group(1) != section:
193 raise RuntimeError(
194 'XcodeProject parse, section "%s" ended inside section "%s"' %
195 (section_end_match.group(1), section))
196 # Back around parse loop
197
198 # Sanity overall group structure
199 if (not self._sections.has_key('PBXProject') or
200 len(self._sections['PBXProject']) != 1):
201 raise RuntimeError('PBXProject section insane')
202 root_obj_parsed = self.__class__.PROJECT_ROOT_OBJECT_RE.match(
203 self._tail[1])
204 if not root_obj_parsed:
205 raise RuntimeError('XcodeProject unable to parse project root object:\n%s'
206 % self._tail[1])
207 if root_obj_parsed.group(1) != self._sections['PBXProject'][0].uuid:
208 raise RuntimeError('XcodeProject root object does not match PBXProject')
209 self._root_group_uuid = self._sections['PBXProject'][0].main_group_uuid
210
211 # Source root
212 self.source_root_path = os.path.abspath(
213 os.path.join(
214 # Directory that contains the project package
215 os.path.dirname(os.path.dirname(path)),
216 # Any relative path
217 self._sections['PBXProject'][0].project_root))
218
219 # Build the absolute paths of the groups with these helpers
220 def GroupAbsRealPath(*elements):
221 return os.path.abspath(os.path.realpath(os.path.join(*elements)))
222 def GroupPathRecurse(group, parent_path):
223 descend = False
224 if group.source_tree == '"<absolute>"':
225 group.abs_path = GroupAbsRealPath(group.path)
226 descend = True
227 elif group.source_tree == '"<group>"':
228 if group.path:
229 group.abs_path = GroupAbsRealPath(parent_path, group.path)
230 else:
231 group.abs_path = parent_path
232 descend = True
233 elif group.source_tree == 'SOURCE_ROOT':
234 if group.path:
235 group.abs_path = GroupAbsRealPath(self.source_root_path, group.path)
236 else:
237 group.abs_path = GroupAbsRealPath(self.source_root_path)
238 descend = True
239 if descend:
240 for child_uuid in group.child_uuids:
241 # Try a group first
242 found_uuid = False
243 for other_group in self._sections['PBXGroup']:
244 if other_group.uuid == child_uuid:
245 found_uuid = True
246 GroupPathRecurse(other_group, group.abs_path)
247 break
248 if self._sections.has_key('PBXVariantGroup'):
249 for other_group in self._sections['PBXVariantGroup']:
250 if other_group.uuid == child_uuid:
251 found_uuid = True
252 GroupPathRecurse(other_group, group.abs_path)
253 break
254 if not found_uuid:
255 for file_ref in self._sections['PBXFileReference']:
256 if file_ref.uuid == child_uuid:
257 found_uuid = True
258 if file_ref.source_tree == '"<absolute>"':
259 file_ref.abs_path = GroupAbsRealPath(file_ref.path)
260 elif group.source_tree == '"<group>"':
261 file_ref.abs_path = GroupAbsRealPath(group.abs_path,
262 file_ref.path)
263 elif group.source_tree == 'SOURCE_ROOT':
264 file_ref.abs_path = GroupAbsRealPath(self.source_root_path,
265 file_ref.path)
266 break
267 if not found_uuid:
268 raise RuntimeError('XcodeProject group descent failed to find %s' %
269 child_uuid)
270 self._root_group = None
271 for group in self._sections['PBXGroup']:
272 if group.uuid == self._root_group_uuid:
273 self._root_group = group
274 GroupPathRecurse(group, self.source_root_path)
275 if not self._root_group:
276 raise RuntimeError('XcodeProject failed to find root group by UUID')
277
278 def FileContent(self):
279 """Generate and return the project file content as a list of lines"""
280 content = []
281 content.extend(self._header[:-1])
282 for section in self._section_order:
283 content.append('\n/* Begin %s section */\n' % section)
284 for section_content in self._sections[section]:
285 content.append(str(section_content))
286 content.append('/* End %s section */\n' % section)
287 content.extend(self._tail)
288 return content
289
290 def Update(self):
291 """Rewrite the project file in place with all updated metadata"""
292 __pychecker__ = 'no-deprecated'
293 # Not concerned with temp_path security here, just needed a unique name
294 temp_path = tempfile.mktemp(dir=os.path.dirname(self.path))
295 outfile = open(temp_path, 'w')
296 outfile.writelines(self.FileContent())
297 outfile.close()
298 # Rename is weird on Win32, see the docs,
299 os.unlink(self.path)
300 os.rename(temp_path, self.path)
301
302 def NativeTargets(self):
303 """Obtain all PBXNativeTarget instances for this project
304
305 Returns:
306 List of PBXNativeTarget instances
307 """
308 if self._sections.has_key('PBXNativeTarget'):
309 return self._sections['PBXNativeTarget']
310 else:
311 return []
312
313 def NativeTargetForName(self, name):
314 """Obtain the target with a given name.
315
316 Args:
317 name: Target name
318
319 Returns:
320 PBXNativeTarget instance or None
321 """
322 for target in self.NativeTargets():
323 if target.name == name:
324 return target
325 return None
326
327 def FileReferences(self):
328 """Obtain all PBXFileReference instances for this project
329
330 Returns:
331 List of PBXFileReference instances
332 """
333 return self._sections['PBXFileReference']
334
335 def SourcesBuildPhaseForTarget(self, target):
336 """Obtain the PBXSourcesBuildPhase instance for a target. Xcode allows
337 only one PBXSourcesBuildPhase per target and each target has a unique
338 PBXSourcesBuildPhase.
339
340 Args:
341 target: PBXNativeTarget instance
342
343 Returns:
344 PBXSourcesBuildPhase instance
345 """
346 sources_uuid = None
347 for i in range(len(target.build_phase_names)):
348 if target.build_phase_names[i] == 'Sources':
349 sources_uuid = target.build_phase_uuids[i]
350 break
351 if not sources_uuid:
352 raise RuntimeError('Missing PBXSourcesBuildPhase for target "%s"' %
353 target.name)
354 for sources_phase in self._sections['PBXSourcesBuildPhase']:
355 if sources_phase.uuid == sources_uuid:
356 return sources_phase
357 raise RuntimeError('Missing PBXSourcesBuildPhase for UUID "%s"' %
358 sources_uuid)
359
360 def BuildFileForUUID(self, uuid):
361 """Look up a PBXBuildFile by UUID
362
363 Args:
364 uuid: UUID of the PBXBuildFile to find
365
366 Raises:
367 RuntimeError if no PBXBuildFile exists for |uuid|
368
369 Returns:
370 PBXBuildFile instance
371 """
372 for build_file in self._sections['PBXBuildFile']:
373 if build_file.uuid == uuid:
374 return build_file
375 raise RuntimeError('Missing PBXBuildFile for UUID "%s"' % uuid)
376
377 def FileReferenceForUUID(self, uuid):
378 """Look up a PBXFileReference by UUID
379
380 Args:
381 uuid: UUID of the PBXFileReference to find
382
383 Raises:
384 RuntimeError if no PBXFileReference exists for |uuid|
385
386 Returns:
387 PBXFileReference instance
388 """
389 for file_ref in self._sections['PBXFileReference']:
390 if file_ref.uuid == uuid:
391 return file_ref
392 raise RuntimeError('Missing PBXFileReference for UUID "%s"' % uuid)
393
394 def RemoveSourceFileReference(self, file_ref):
395 """Remove a source file's PBXFileReference from the project, cleaning up all
396 PBXGroup and PBXBuildFile references to that PBXFileReference and
397 furthermore, removing any PBXBuildFiles from all PBXNativeTarget source
398 lists.
399
400 Args:
401 file_ref: PBXFileReference instance
402
403 Raises:
404 RuntimeError if |file_ref| is not a source file reference in PBXBuildFile
405 """
406 self._sections['PBXFileReference'].remove(file_ref)
407 # Clean up build files
408 removed_build_files = []
409 for build_file in self._sections['PBXBuildFile']:
410 if build_file.file_ref_uuid == file_ref.uuid:
411 if build_file.type != 'Sources':
412 raise RuntimeError('Removing PBXBuildFile not of "Sources" type')
413 removed_build_files.append(build_file)
414 removed_build_file_uuids = []
415 for build_file in removed_build_files:
416 removed_build_file_uuids.append(build_file.uuid)
417 self._sections['PBXBuildFile'].remove(build_file)
418 # Clean up source references to the removed build files
419 for source_phase in self._sections['PBXSourcesBuildPhase']:
420 removal_indexes = []
421 for i in range(len(source_phase.file_uuids)):
422 if source_phase.file_uuids[i] in removed_build_file_uuids:
423 removal_indexes.append(i)
424 for removal_index in removal_indexes:
425 del source_phase.file_uuids[removal_index]
426 del source_phase.file_names[removal_index]
427 # Clean up group references
428 for group in self._sections['PBXGroup']:
429 removal_indexes = []
430 for i in range(len(group.child_uuids)):
431 if group.child_uuids[i] == file_ref.uuid:
432 removal_indexes.append(i)
433 for removal_index in removal_indexes:
434 del group.child_uuids[removal_index]
435 del group.child_names[removal_index]
436
437 def RelativeSourceRootPath(self, abs_path):
438 """Convert a path to one relative to the project's SOURCE_ROOT if possible.
439 Generally this follows Xcode semantics, that is, a path is only converted
440 if it is a subpath of SOURCE_ROOT.
441
442 Args:
443 abs_path: Absolute path to convert
444
445 Returns:
446 String SOURCE_ROOT relative path if possible or None if not relative
447 to SOURCE_ROOT.
448 """
449 if abs_path.startswith(self.source_root_path + os.path.sep):
450 return abs_path[len(self.source_root_path + os.path.sep):]
451 else:
452 # Try to construct a relative path (bodged from ActiveState recipe
453 # 302594 since we can't assume Python 2.5 with os.path.relpath()
454 source_root_parts = self.source_root_path.split(os.path.sep)
455 target_parts = abs_path.split(os.path.sep)
456 # Guard against drive changes on Win32 and cygwin
457 if sys.platform == 'win32' and source_root_parts[0] <> target_parts[0]:
458 return None
459 if sys.platform == 'cygwin' and source_root_parts[2] <> target_parts[2]:
460 return None
461 for i in range(min(len(source_root_parts), len(target_parts))):
462 if source_root_parts[i] <> target_parts[i]: break
463 else:
464 i += 1
465 rel_parts = [os.path.pardir] * (len(source_root_parts) - i)
466 rel_parts.extend(target_parts[i:])
467 return os.path.join(*rel_parts)
468
469 def RelativeGroupPath(self, abs_path):
470 """Convert a path to a group-relative path if possible
471
472 Args:
473 abs_path: Absolute path to convert
474
475 Returns:
476 Parent PBXGroup instance if possible or None
477 """
478 needed_path = os.path.dirname(abs_path)
479 possible_groups = [ g for g in self._sections['PBXGroup']
480 if g.abs_path == needed_path and
481 not g.name in NON_SOURCE_GROUP_NAMES ]
482 if len(possible_groups) < 1:
483 return None
484 elif len(possible_groups) == 1:
485 return possible_groups[0]
486 # Multiple groups match, try to find the best using some simple
487 # heuristics. Does only one group contain source?
488 groups_with_source = []
489 for group in possible_groups:
490 for child_uuid in group.child_uuids:
491 try:
492 self.FileReferenceForUUID(child_uuid)
493 except RuntimeError:
494 pass
495 else:
496 groups_with_source.append(group)
497 break
498 if len(groups_with_source) == 1:
499 return groups_with_source[0]
500 # Is only one _not_ the root group?
501 non_root_groups = [ g for g in possible_groups
502 if g is not self._root_group ]
503 if len(non_root_groups) == 1:
504 return non_root_groups[0]
505 # Best guess
506 if len(non_root_groups):
507 return non_root_groups[0]
508 elif len(groups_with_source):
509 return groups_with_source[0]
510 else:
511 return possible_groups[0]
512
513 def AddSourceFile(self, path):
514 """Add a source file to the project, attempting to position it
515 in the GUI group hierarchy reasonably.
516
517 NOTE: Adding a source file does not add it to any targets
518
519 Args:
520 path: Absolute path to the file to add
521
522 Returns:
523 PBXFileReference instance for the newly added source.
524 """
525 # Guess at file type
526 root, extension = os.path.splitext(path)
527 if EXTENSION_TO_XCODE_FILETYPE.has_key(extension):
528 source_type = EXTENSION_TO_XCODE_FILETYPE[extension]
529 else:
530 raise RuntimeError('Unknown source file extension "%s"' % extension)
531
532 # Is group-relative possible for an existing group?
533 parent_group = self.RelativeGroupPath(os.path.abspath(path))
534 if parent_group:
535 new_file_ref = PBXFileReference(NewUUID(),
536 os.path.basename(path),
537 source_type,
538 None,
539 os.path.basename(path),
540 '"<group>"',
541 None)
542 # Chrome tries to keep its lists name sorted, try to match
543 i = 0
544 while i < len(parent_group.child_uuids):
545 # Only files are sorted, they keep groups at the top
546 try:
547 self.FileReferenceForUUID(parent_group.child_uuids[i])
548 if new_file_ref.name.lower() < parent_group.child_names[i].lower():
549 break
550 except RuntimeError:
551 pass # Must be a child group
552 i += 1
553 parent_group.child_names.insert(i, new_file_ref.name)
554 parent_group.child_uuids.insert(i, new_file_ref.uuid)
555 # Add file ref uuid sorted
556 self._sections['PBXFileReference'].append(new_file_ref)
557 self._sections['PBXFileReference'].sort(cmp=lambda x,y: cmp(x.uuid, y.uuid ))
558 return new_file_ref
559
560 # Group-relative failed, how about SOURCE_ROOT relative in the main group
561 src_rel_path = self.RelativeSourceRootPath(os.path.abspath(path))
562 if src_rel_path:
563 src_rel_path = src_rel_path.replace('\\', '/') # Convert to Unix
564 new_file_ref = PBXFileReference(NewUUID(),
565 os.path.basename(path),
566 source_type,
567 None,
568 src_rel_path,
569 'SOURCE_ROOT',
570 None)
571 self._root_group.child_uuids.append(new_file_ref.uuid)
572 self._root_group.child_names.append(new_file_ref.name)
573 # Add file ref uuid sorted
574 self._sections['PBXFileReference'].append(new_file_ref)
575 self._sections['PBXFileReference'].sort(cmp=lambda x,y: cmp(x.uuid, y.uuid ))
576 return new_file_ref
577
578 # Win to Unix absolute paths probably not practical
579 raise RuntimeError('Could not construct group or source PBXFileReference '
580 'for path "%s"' % path)
581
582 def AddSourceFileToSourcesBuildPhase(self, source_ref, source_phase):
583 """Add a PBXFileReference to a PBXSourcesBuildPhase, creating a new
584 PBXBuildFile as needed.
585
586 Args:
587 source_ref: PBXFileReference instance appropriate for use in
588 PBXSourcesBuildPhase
589 source_phase: PBXSourcesBuildPhase instance
590 """
591 # Prevent duplication
592 for source_uuid in source_phase.file_uuids:
593 build_file = self.BuildFileForUUID(source_uuid)
594 if build_file.file_ref_uuid == source_ref.uuid:
595 return
596 # Create PBXBuildFile
597 new_build_file = PBXBuildFile(NewUUID(),
598 source_ref.name,
599 'Sources',
600 source_ref.uuid,
601 '')
602 # Add to build file list (uuid sorted)
603 self._sections['PBXBuildFile'].append(new_build_file)
604 self._sections['PBXBuildFile'].sort(cmp=lambda x,y: cmp(x.uuid, y.uuid))
605 # Add to sources phase list (name sorted)
606 i = 0
607 while i < len(source_phase.file_names):
608 if source_ref.name.lower() < source_phase.file_names[i].lower():
609 break
610 i += 1
611 source_phase.file_names.insert(i, new_build_file.name)
612 source_phase.file_uuids.insert(i, new_build_file.uuid)
613
614
615 class PBXProject(object):
616 """Class for PBXProject data section of an Xcode project file.
617
618 Attributes:
619 uuid: Project UUID
620 main_group_uuid: UUID of the top-level PBXGroup
621 project_root: Relative path from project file wrapper to source_root_path
622 """
623
624 PBXPROJECT_HEADER_RE = re.compile(
625 r'^\t\t([0-9A-F]{24}) /\* Project object \*/ = {\n$')
626 PBXPROJECT_MAIN_GROUP_RE = re.compile(
627 r'^\t\t\tmainGroup = ([0-9A-F]{24})(?: /\* .* \*/)?;\n$')
628 PBXPROJECT_ROOT_RE = re.compile(
629 r'^\t\t\tprojectRoot = (.*);\n$')
630
631 @classmethod
632 def FromContent(klass, content_lines):
633 header_parsed = klass.PBXPROJECT_HEADER_RE.match(content_lines[0])
634 if not header_parsed:
635 raise RuntimeError('PBXProject unable to parse header content:\n%s'
636 % content_lines[0])
637 main_group_uuid = None
638 project_root = ''
639 for content_line in content_lines:
640 group_parsed = klass.PBXPROJECT_MAIN_GROUP_RE.match(content_line)
641 if group_parsed:
642 main_group_uuid = group_parsed.group(1)
643 root_parsed = klass.PBXPROJECT_ROOT_RE.match(content_line)
644 if root_parsed:
645 project_root = root_parsed.group(1)
646 if project_root.startswith('"'):
647 project_root = project_root[1:-1]
648 if not main_group_uuid:
649 raise RuntimeError('PBXProject missing main group')
650 return klass(content_lines, header_parsed.group(1),
651 main_group_uuid, project_root)
652
653 def __init__(self, raw_lines, uuid, main_group_uuid, project_root):
654 self.uuid = uuid
655 self._raw_lines = raw_lines
656 self.main_group_uuid = main_group_uuid
657 self.project_root = project_root
658
659 def __str__(self):
660 return ''.join(self._raw_lines)
661
662
663 class PBXBuildFile(object):
664 """Class for PBXBuildFile data from an Xcode project file.
665
666 Attributes:
667 uuid: UUID for this instance
668 name: Basename of the build file
669 type: 'Sources' or 'Frameworks'
670 file_ref_uuid: UUID of the PBXFileReference for this file
671 """
672
673 PBXBUILDFILE_LINE_RE = re.compile(
674 r'^\t\t([0-9A-F]{24}) /\* (.+) in (.+) \*/ = '
675 '{isa = PBXBuildFile; fileRef = ([0-9A-F]{24}) /\* (.+) \*/; (.*)};\n$')
676
677 @classmethod
678 def FromContent(klass, content_line):
679 parsed = klass.PBXBUILDFILE_LINE_RE.match(content_line)
680 if not parsed:
681 raise RuntimeError('PBXBuildFile unable to parse content:\n%s'
682 % content_line)
683 if parsed.group(2) != parsed.group(5):
684 raise RuntimeError('PBXBuildFile name mismatch "%s" vs "%s"' %
685 (parsed.group(2), parsed.group(5)))
686 if not parsed.group(3) in ('Sources', 'Frameworks',
687 'Resources', 'CopyFiles',
688 'Headers', 'Copy Into Framework',
689 'Rez', 'Copy Generated Headers'):
690 raise RuntimeError('PBXBuildFile unknown type "%s"' % parsed.group(3))
691 return klass(parsed.group(1), parsed.group(2), parsed.group(3),
692 parsed.group(4), parsed.group(6))
693
694 def __init__(self, uuid, name, type, file_ref_uuid, raw_extras):
695 self.uuid = uuid
696 self.name = name
697 self.type = type
698 self.file_ref_uuid = file_ref_uuid
699 self._raw_extras = raw_extras
700
701 def __str__(self):
702 return '\t\t%s /* %s in %s */ = ' \
703 '{isa = PBXBuildFile; fileRef = %s /* %s */; %s};\n' % (
704 self.uuid, self.name, self.type, self.file_ref_uuid, self.name,
705 self._raw_extras)
706
707
708 class PBXFileReference(object):
709 """Class for PBXFileReference data from an Xcode project file.
710
711 Attributes:
712 uuid: UUID for this instance
713 name: Basename of the file
714 file_type: current active file type (explicit or assumed)
715 path: source_tree relative path (or absolute if source_tree is absolute)
716 source_tree: Source tree type (see PBX_VALID_SOURCE_TREE_TYPES)
717 abs_path: Absolute path to the file
718 """
719 PBXFILEREFERENCE_HEADER_RE = re.compile(
720 r'^\t\t([0-9A-F]{24}) /\* (.+) \*/ = {isa = PBXFileReference; ')
721 PBXFILEREFERENCE_FILETYPE_RE = re.compile(
722 r' (lastKnownFileType|explicitFileType) = ([^\;]+); ')
723 PBXFILEREFERENCE_PATH_RE = re.compile(r' path = ([^\;]+); ')
724 PBXFILEREFERENCE_SOURCETREE_RE = re.compile(r' sourceTree = ([^\;]+); ')
725
726 @classmethod
727 def FromContent(klass, content_line):
728 header_parsed = klass.PBXFILEREFERENCE_HEADER_RE.match(content_line)
729 if not header_parsed:
730 raise RuntimeError('PBXFileReference unable to parse header content:\n%s'
731 % content_line)
732 type_parsed = klass.PBXFILEREFERENCE_FILETYPE_RE.search(content_line)
733 if not type_parsed:
734 raise RuntimeError('PBXFileReference unable to parse type content:\n%s'
735 % content_line)
736 if type_parsed.group(1) == 'lastKnownFileType':
737 last_known_type = type_parsed.group(2)
738 explicit_type = None
739 else:
740 last_known_type = None
741 explicit_type = type_parsed.group(2)
742 path_parsed = klass.PBXFILEREFERENCE_PATH_RE.search(content_line)
743 if not path_parsed:
744 raise RuntimeError('PBXFileReference unable to parse path content:\n%s'
745 % content_line)
746 tree_parsed = klass.PBXFILEREFERENCE_SOURCETREE_RE.search(content_line)
747 if not tree_parsed:
748 raise RuntimeError(
749 'PBXFileReference unable to parse source tree content:\n%s'
750 % content_line)
751 return klass(header_parsed.group(1), header_parsed.group(2),
752 last_known_type, explicit_type, path_parsed.group(1),
753 tree_parsed.group(1), content_line)
754
755 def __init__(self, uuid, name, last_known_file_type, explicit_file_type,
756 path, source_tree, raw_line):
757 self.uuid = uuid
758 self.name = name
759 self._last_known_file_type = last_known_file_type
760 self._explicit_file_type = explicit_file_type
761 if explicit_file_type:
762 self.file_type = explicit_file_type
763 else:
764 self.file_type = last_known_file_type
765 self.path = path
766 self.source_tree = source_tree
767 self.abs_path = None
768 self._raw_line = raw_line
769
770 def __str__(self):
771 # Raw available?
772 if self._raw_line: return self._raw_line
773 # Construct our own
774 if self._last_known_file_type:
775 print_file_type = 'lastKnownFileType = %s; ' % self._last_known_file_type
776 elif self._explicit_file_type:
777 print_file_type = 'explicitFileType = %s; ' % self._explicit_file_type
778 else:
779 raise RuntimeError('No known file type for stringification')
780 name_attribute = ''
781 if self.name != self.path:
782 name_attribute = 'name = %s; ' % self.name
783 print_path = self.path
784 if QUOTE_PATH_RE.search(print_path):
785 print_path = '"%s"' % print_path
786 return '\t\t%s /* %s */ = ' \
787 '{isa = PBXFileReference; ' \
788 'fileEncoding = 4; ' \
789 '%s' \
790 '%s' \
791 'path = %s; sourceTree = %s; };\n' % (
792 self.uuid, self.name, print_file_type,
793 name_attribute, print_path, self.source_tree)
794
795
796 class PBXGroup(object):
797 """Class for PBXGroup data from an Xcode project file.
798
799 Attributes:
800 uuid: UUID for this instance
801 name: Group (folder) name
802 path: source_tree relative path (or absolute if source_tree is absolute)
803 source_tree: Source tree type (see PBX_VALID_SOURCE_TREE_TYPES)
804 abs_path: Absolute path to the group
805 child_uuids: Ordered list of PBXFileReference UUIDs
806 child_names: Ordered list of PBXFileReference names
807 """
808
809 PBXGROUP_HEADER_RE = re.compile(r'^\t\t([0-9A-F]{24}) (?:/\* .* \*/ )?= {\n$')
810 PBXGROUP_FIELD_RE = re.compile(r'^\t\t\t(.*) = (.*);\n$')
811 PBXGROUP_CHILD_RE = re.compile(r'^\t\t\t\t([0-9A-F]{24}) /\* (.*) \*/,\n$')
812
813 @classmethod
814 def FromContent(klass, content_lines):
815 # Header line
816 header_parsed = klass.PBXGROUP_HEADER_RE.match(content_lines[0])
817 if not header_parsed:
818 raise RuntimeError('PBXGroup unable to parse header content:\n%s'
819 % content_lines[0])
820 name = None
821 path = ''
822 source_tree = None
823 tab_width = None
824 uses_tabs = None
825 indent_width = None
826 child_uuids = []
827 child_names = []
828 # Parse line by line
829 content_line_no = 0
830 while 1:
831 content_line_no += 1
832 content_line = content_lines[content_line_no]
833 if content_line == '\t\t};\n': break
834 if content_line == '\t\t\tisa = PBXGroup;\n': continue
835 if content_line == '\t\t\tisa = PBXVariantGroup;\n': continue
836 # Child groups
837 if content_line == '\t\t\tchildren = (\n':
838 content_line_no += 1
839 content_line = content_lines[content_line_no]
840 while content_line != '\t\t\t);\n':
841 child_parsed = klass.PBXGROUP_CHILD_RE.match(content_line)
842 if not child_parsed:
843 raise RuntimeError('PBXGroup unable to parse child content:\n%s'
844 % content_line)
845 child_uuids.append(child_parsed.group(1))
846 child_names.append(child_parsed.group(2))
847 content_line_no += 1
848 content_line = content_lines[content_line_no]
849 continue # Back to top of loop on end of children
850 # Other fields
851 field_parsed = klass.PBXGROUP_FIELD_RE.match(content_line)
852 if not field_parsed:
853 raise RuntimeError('PBXGroup unable to parse field content:\n%s'
854 % content_line)
855 if field_parsed.group(1) == 'name':
856 name = field_parsed.group(2)
857 elif field_parsed.group(1) == 'path':
858 path = field_parsed.group(2)
859 elif field_parsed.group(1) == 'sourceTree':
860 if not field_parsed.group(2) in PBX_VALID_SOURCE_TREE_TYPES:
861 raise RuntimeError('PBXGroup unknown source tree type "%s"'
862 % field_parsed.group(2))
863 source_tree = field_parsed.group(2)
864 elif field_parsed.group(1) == 'tabWidth':
865 tab_width = field_parsed.group(2)
866 elif field_parsed.group(1) == 'usesTabs':
867 uses_tabs = field_parsed.group(2)
868 elif field_parsed.group(1) == 'indentWidth':
869 indent_width = field_parsed.group(2)
870 else:
871 raise RuntimeError('PBXGroup unknown field "%s"'
872 % field_parsed.group(1))
873 if path and path.startswith('"'):
874 path = path[1:-1]
875 if name and name.startswith('"'):
876 name = name[1:-1]
877 return klass(header_parsed.group(1), name, path, source_tree, child_uuids,
878 child_names, tab_width, uses_tabs, indent_width)
879
880 def __init__(self, uuid, name, path, source_tree, child_uuids, child_names,
881 tab_width, uses_tabs, indent_width):
882 self.uuid = uuid
883 self.name = name
884 self.path = path
885 self.source_tree = source_tree
886 self.child_uuids = child_uuids
887 self.child_names = child_names
888 self.abs_path = None
889 # Semantically I'm not sure these aren't an error, but they
890 # appear in some projects
891 self._tab_width = tab_width
892 self._uses_tabs = uses_tabs
893 self._indent_width = indent_width
894
895 def __str__(self):
896 if self.name:
897 header_comment = '/* %s */ ' % self.name
898 elif self.path:
899 header_comment = '/* %s */ ' % self.path
900 else:
901 header_comment = ''
902 if self.name:
903 if QUOTE_PATH_RE.search(self.name):
904 name_attribute = '\t\t\tname = "%s";\n' % self.name
905 else:
906 name_attribute = '\t\t\tname = %s;\n' % self.name
907 else:
908 name_attribute = ''
909 if self.path:
910 if QUOTE_PATH_RE.search(self.path):
911 path_attribute = '\t\t\tpath = "%s";\n' % self.path
912 else:
913 path_attribute = '\t\t\tpath = %s;\n' % self.path
914 else:
915 path_attribute = ''
916 child_lines = []
917 for x in range(len(self.child_uuids)):
918 child_lines.append('\t\t\t\t%s /* %s */,\n' %
919 (self.child_uuids[x], self.child_names[x]))
920 children = ''.join(child_lines)
921 tab_width_attribute = ''
922 if self._tab_width:
923 tab_width_attribute = '\t\t\ttabWidth = %s;\n' % self._tab_width
924 uses_tabs_attribute = ''
925 if self._uses_tabs:
926 uses_tabs_attribute = '\t\t\tusesTabs = %s;\n' % self._uses_tabs
927 indent_width_attribute = ''
928 if self._indent_width:
929 indent_width_attribute = '\t\t\tindentWidth = %s;\n' % self._indent_width
930 return '\t\t%s %s= {\n' \
931 '\t\t\tisa = %s;\n' \
932 '\t\t\tchildren = (\n' \
933 '%s' \
934 '\t\t\t);\n' \
935 '%s' \
936 '%s' \
937 '%s' \
938 '\t\t\tsourceTree = %s;\n' \
939 '%s' \
940 '%s' \
941 '\t\t};\n' % (
942 self.uuid, header_comment,
943 self.__class__.__name__,
944 children,
945 indent_width_attribute,
946 name_attribute,
947 path_attribute, self.source_tree,
948 tab_width_attribute, uses_tabs_attribute)
949
950
951 class PBXVariantGroup(PBXGroup):
952 pass
953
954
955 class PBXNativeTarget(object):
956 """Class for PBXNativeTarget data from an Xcode project file.
957
958 Attributes:
959 name: Target name
960 build_phase_uuids: Ordered list of build phase UUIDs
961 build_phase_names: Ordered list of build phase names
962
963 NOTE: We do not have wrapper classes for all build phase data types!
964 """
965
966 PBXNATIVETARGET_HEADER_RE = re.compile(
967 r'^\t\t([0-9A-F]{24}) /\* (.*) \*/ = {\n$')
968 PBXNATIVETARGET_BUILD_PHASE_RE = re.compile(
969 r'^\t\t\t\t([0-9A-F]{24}) /\* (.*) \*/,\n$')
970
971 @classmethod
972 def FromContent(klass, content_lines):
973 header_parsed = klass.PBXNATIVETARGET_HEADER_RE.match(content_lines[0])
974 if not header_parsed:
975 raise RuntimeError('PBXNativeTarget unable to parse header content:\n%s'
976 % content_lines[0])
977 build_phase_uuids = []
978 build_phase_names = []
979 content_line_no = 0
980 while 1:
981 content_line_no += 1
982 content_line = content_lines[content_line_no]
983 if content_line == '\t\t};\n': break
984 if content_line == '\t\t\tisa = PBXNativeTarget;\n': continue
985 # Build phases groups
986 if content_line == '\t\t\tbuildPhases = (\n':
987 content_line_no += 1
988 content_line = content_lines[content_line_no]
989 while content_line != '\t\t\t);\n':
990 phase_parsed = klass.PBXNATIVETARGET_BUILD_PHASE_RE.match(
991 content_line)
992 if not phase_parsed:
993 raise RuntimeError(
994 'PBXNativeTarget unable to parse build phase content:\n%s'
995 % content_line)
996 build_phase_uuids.append(phase_parsed.group(1))
997 build_phase_names.append(phase_parsed.group(2))
998 content_line_no += 1
999 content_line = content_lines[content_line_no]
1000 break # Don't care about the rest of the content
1001 return klass(content_lines, header_parsed.group(2), build_phase_uuids,
1002 build_phase_names)
1003
1004 def __init__(self, raw_lines, name, build_phase_uuids, build_phase_names):
1005 self._raw_lines = raw_lines
1006 self.name = name
1007 self.build_phase_uuids = build_phase_uuids
1008 self.build_phase_names = build_phase_names
1009
1010 def __str__(self):
1011 return ''.join(self._raw_lines)
1012
1013
1014 class PBXSourcesBuildPhase(object):
1015 """Class for PBXSourcesBuildPhase data from an Xcode project file.
1016
1017 Attributes:
1018 uuid: UUID for this instance
1019 build_action_mask: Xcode magic mask constant
1020 run_only_for_deployment_postprocessing: deployment postprocess flag
1021 file_uuids: Ordered list of PBXBuildFile UUIDs
1022 file_names: Ordered list of PBXBuildFile names (basename)
1023 """
1024
1025 PBXSOURCESBUILDPHASE_HEADER_RE = re.compile(
1026 r'^\t\t([0-9A-F]{24}) /\* Sources \*/ = {\n$')
1027 PBXSOURCESBUILDPHASE_FIELD_RE = re.compile(r'^\t\t\t(.*) = (.*);\n$')
1028 PBXSOURCESBUILDPHASE_FILE_RE = re.compile(
1029 r'^\t\t\t\t([0-9A-F]{24}) /\* (.*) in Sources \*/,\n$')
1030
1031 @classmethod
1032 def FromContent(klass, content_lines):
1033 header_parsed = klass.PBXSOURCESBUILDPHASE_HEADER_RE.match(content_lines[0])
1034 if not header_parsed:
1035 raise RuntimeError(
1036 'PBXSourcesBuildPhase unable to parse header content:\n%s'
1037 % content_lines[0])
1038 # Parse line by line
1039 build_action_mask = None
1040 run_only_for_deployment_postprocessing = None
1041 file_uuids = []
1042 file_names = []
1043 content_line_no = 0
1044 while 1:
1045 content_line_no += 1
1046 content_line = content_lines[content_line_no]
1047 if content_line == '\t\t};\n': break
1048 if content_line == '\t\t\tisa = PBXSourcesBuildPhase;\n': continue
1049 # Files
1050 if content_line == '\t\t\tfiles = (\n':
1051 content_line_no += 1
1052 content_line = content_lines[content_line_no]
1053 while content_line != '\t\t\t);\n':
1054 file_parsed = klass.PBXSOURCESBUILDPHASE_FILE_RE.match(content_line)
1055 if not file_parsed:
1056 raise RuntimeError(
1057 'PBXSourcesBuildPhase unable to parse file content:\n%s'
1058 % content_line)
1059 file_uuids.append(file_parsed.group(1))
1060 file_names.append(file_parsed.group(2))
1061 content_line_no += 1
1062 content_line = content_lines[content_line_no]
1063 continue # Back to top of loop on end of files list
1064 # Other fields
1065 field_parsed = klass.PBXSOURCESBUILDPHASE_FIELD_RE.match(content_line)
1066 if not field_parsed:
1067 raise RuntimeError(
1068 'PBXSourcesBuildPhase unable to parse field content:\n%s'
1069 % content_line)
1070 if field_parsed.group(1) == 'buildActionMask':
1071 build_action_mask = field_parsed.group(2)
1072 elif field_parsed.group(1) == 'runOnlyForDeploymentPostprocessing':
1073 run_only_for_deployment_postprocessing = field_parsed.group(2)
1074 else:
1075 raise RuntimeError('PBXSourcesBuildPhase unknown field "%s"'
1076 % field_parsed.group(1))
1077 return klass(header_parsed.group(1), build_action_mask,
1078 run_only_for_deployment_postprocessing,
1079 file_uuids, file_names)
1080
1081 def __init__(self, uuid, build_action_mask,
1082 run_only_for_deployment_postprocessing,
1083 file_uuids, file_names):
1084 self.uuid = uuid
1085 self.build_action_mask = build_action_mask
1086 self.run_only_for_deployment_postprocessing = \
1087 run_only_for_deployment_postprocessing
1088 self.file_uuids = file_uuids
1089 self.file_names = file_names
1090
1091 def __str__(self):
1092 file_lines = []
1093 for x in range(len(self.file_uuids)):
1094 file_lines.append('\t\t\t\t%s /* %s in Sources */,\n' %
1095 (self.file_uuids[x], self.file_names[x]))
1096 files = ''.join(file_lines)
1097 return '\t\t%s /* Sources */ = {\n' \
1098 '\t\t\tisa = PBXSourcesBuildPhase;\n' \
1099 '\t\t\tbuildActionMask = %s;\n' \
1100 '\t\t\tfiles = (\n' \
1101 '%s' \
1102 '\t\t\t);\n' \
1103 '\t\t\trunOnlyForDeploymentPostprocessing = %s;\n' \
1104 '\t\t};\n' % (
1105 self.uuid, self.build_action_mask, files,
1106 self.run_only_for_deployment_postprocessing)
1107
1108
1109 def Usage(optparse):
1110 optparse.print_help()
1111 print '\n' \
1112 'Commands:\n' \
1113 ' list_native_targets: List Xcode "native" (source compilation)\n' \
1114 ' targets by name.\n' \
1115 ' list_target_sources: List project-relative source files in the\n' \
1116 ' specified Xcode "native" target.\n' \
1117 ' remove_source [sourcefile ...]: Remove the specified source files\n' \
1118 ' from every target in the project (target is ignored).\n' \
1119 ' add_source [sourcefile ...]: Add the specified source files\n' \
1120 ' to the specified target.\n'
1121 sys.exit(2)
1122
1123
1124 def Main():
1125 # Use argument structure like xcodebuild commandline
1126 option_parser = optparse.OptionParser(
1127 usage='usage: %prog -p projectname [ -t targetname ] ' \
1128 '<command> [...]',
1129 add_help_option=False)
1130 option_parser.add_option(
1131 '-h', '--help', action='store_true', dest='help',
1132 default=False, help=optparse.SUPPRESS_HELP)
1133 option_parser.add_option(
1134 '-p', '--project', action='store', type='string',
1135 dest='project', metavar='projectname',
1136 help='Manipulate the project specified by projectname.')
1137 option_parser.add_option(
1138 '-t', '--target', action='store', type='string',
1139 dest='target', metavar='targetname',
1140 help='Manipulate the target specified by targetname.')
1141 (options, args) = option_parser.parse_args()
1142
1143 # Since we have more elaborate commands, handle help
1144 if options.help:
1145 Usage(option_parser)
1146
1147 # Xcode project file
1148 if not options.project:
1149 option_parser.error('Xcode project file must be specified.')
1150 project_path = os.path.abspath(CygwinPathClean(options.project))
1151 if project_path.endswith('.xcodeproj'):
1152 project_path = os.path.join(project_path, 'project.pbxproj')
1153 if not project_path.endswith(os.sep + 'project.pbxproj'):
1154 option_parser.error('Invalid Xcode project file path \"%s\"' % project_path)
1155 if not os.path.exists(project_path):
1156 option_parser.error('Missing Xcode project file \"%s\"' % project_path)
1157
1158 # Construct project object
1159 project = XcodeProject(project_path)
1160
1161 # Switch on command
1162
1163 # List native target names (default command)
1164 if len(args) < 1 or args[0] == 'list_native_targets':
1165 # Ape xcodebuild output
1166 target_names = []
1167 for target in project.NativeTargets():
1168 target_names.append(target.name)
1169 print 'Information about project "%s"\n Native Targets:\n %s' % (
1170 project.name,
1171 '\n '.join(target_names))
1172
1173 if len(args) < 1:
1174 # Be friendly and print some hints for further actions.
1175 print
1176 print 'To add or remove files from given target, run:'
1177 print '\txcodebodge.py -p <project> -t <target> add_source <file_name>'
1178 print '\txcodebodge.py -p <project> -t <target> remove_source <file_name>'
1179
1180 # List files in a native target
1181 elif args[0] == 'list_target_sources':
1182 if len(args) != 1:
1183 option_parser.error('list_target_sources takes no arguments')
1184 if not options.target:
1185 option_parser.error('list_target_sources requires a target')
1186 # Validate target and get list of files
1187 target = project.NativeTargetForName(options.target)
1188 if not target:
1189 option_parser.error('No native target named "%s"' % options.target)
1190 sources_phase = project.SourcesBuildPhaseForTarget(target)
1191 target_files = []
1192 for source_uuid in sources_phase.file_uuids:
1193 build_file = project.BuildFileForUUID(source_uuid)
1194 file_ref = project.FileReferenceForUUID(build_file.file_ref_uuid)
1195 pretty_path = project.RelativeSourceRootPath(file_ref.abs_path)
1196 if pretty_path:
1197 target_files.append(pretty_path)
1198 else:
1199 target_files.append(file_ref.abs_path)
1200 # Ape xcodebuild output
1201 print 'Information about project "%s" target "%s"\n' \
1202 ' Files:\n %s' % (project.name, options.target,
1203 '\n '.join(target_files))
1204
1205 # Remove source files
1206 elif args[0] == 'remove_source':
1207 if len(args) < 2:
1208 option_parser.error('remove_source needs one or more source files')
1209 if options.target:
1210 option_parser.error(
1211 'remove_source does not support removal from a single target')
1212 for source_path in args[1:]:
1213 source_path = CygwinPathClean(source_path)
1214 found = False
1215 for file_ref in project.FileReferences():
1216 # Try undecorated path, abs_path and our prettified paths
1217 if (file_ref.path == source_path or (
1218 file_ref.abs_path and (
1219 file_ref.abs_path == os.path.abspath(source_path) or
1220 project.RelativeSourceRootPath(file_ref.abs_path) == source_path)) ):
1221 # Found a matching file ref, remove it
1222 found = True
1223 project.RemoveSourceFileReference(file_ref)
1224 if not found:
1225 option_parser.error('No matching source file "%s"' % source_path)
1226 project.Update()
1227
1228 # Add source files
1229 elif args[0] == 'add_source':
1230 if len(args) < 2:
1231 option_parser.error('add_source needs one or more source files')
1232 if not options.target:
1233 option_parser.error('add_source requires a target')
1234 # Look for the target we want to add too.
1235 target = project.NativeTargetForName(options.target)
1236 if not target:
1237 option_parser.error('No native target named "%s"' % options.target)
1238 # Get the sources build phase
1239 sources_phase = project.SourcesBuildPhaseForTarget(target)
1240 # Loop new sources
1241 for source_path in args[1:]:
1242 source_path = CygwinPathClean(source_path)
1243 if not os.path.exists(os.path.abspath(source_path)):
1244 option_parser.error('File "%s" not found' % source_path)
1245 # Don't generate duplicate file references if we don't need them
1246 source_ref = None
1247 for file_ref in project.FileReferences():
1248 # Try undecorated path, abs_path and our prettified paths
1249 if (file_ref.path == source_path or (
1250 file_ref.abs_path and (
1251 file_ref.abs_path == os.path.abspath(source_path) or
1252 project.RelativeSourceRootPath(file_ref.abs_path) == source_path)) ):
1253 source_ref = file_ref
1254 break
1255 if not source_ref:
1256 # Create a new source file ref
1257 source_ref = project.AddSourceFile(os.path.abspath(source_path))
1258 # Add the new source file reference to the target if its a safe type
1259 if source_ref.file_type in SOURCES_XCODE_FILETYPES:
1260 project.AddSourceFileToSourcesBuildPhase(source_ref, sources_phase)
1261 project.Update()
1262
1263 # Private sanity check. On an unmodified project make sure our output is
1264 # the same as the input
1265 elif args[0] == 'parse_sanity':
1266 if ''.join(project.FileContent()) != ''.join(project._raw_content):
1267 option_parser.error('Project rewrite sanity fail "%s"' % project.path)
1268
1269 else:
1270 Usage(option_parser)
1271
1272
1273 if __name__ == '__main__':
1274 Main()
OLDNEW
« 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