2010-09-08 13:11:15 -04:00
#!/usr/bin/python
###############################################################################
# Name: build/osx/fix_xcode_ids.py
# Author: Dimitri Schoolwerth
# Created: 2010-09-08
# Copyright: (c) 2010 wxWidgets team
# Licence: wxWindows licence
###############################################################################
2010-09-15 18:13:40 -04:00
testFixStage = False
import os
2010-09-08 13:11:15 -04:00
import sys
import re
USAGE = """ fix_xcode_ids - Modifies an Xcode project in-place to use the same identifiers (based on name) instead of being different on each regeneration "
Usage : fix_xcode_ids xcode_proj_dir """
2010-09-15 18:13:40 -04:00
if not testFixStage :
if len ( sys . argv ) < 2 :
print USAGE
sys . exit ( 1 )
2010-09-08 13:11:15 -04:00
2010-09-15 18:13:40 -04:00
projectFile = sys . argv [ 1 ] + " /project.pbxproj "
fin = open ( projectFile , " r " )
strIn = fin . read ( )
fin . close ( )
2010-09-08 13:11:15 -04:00
# Xcode identifiers (IDs) consist of 24 hexadecimal digits
idMask = " [A-Fa-f0-9] {24} "
2010-09-15 18:13:40 -04:00
idDict = { }
# convert a name to an identifier for Xcode
def toUuid ( name ) :
from uuid import uuid3 , UUID
id = uuid3 ( UUID ( " 349f853c-91f8-4eba-b9b9-5e9f882e693c " ) , name ) . hex [ : 24 ] . upper ( )
# Some names can appear twice or even more (depending on number of
# targets), make them unique
while id in idDict . values ( ) :
id = " %024X " % ( int ( id , 16 ) + 1 )
return id
def insertBuildFileEntry ( filePath , fileRefId ) :
global strIn
print " \t Insert PBXBuildFile for ' %s ' ... " % filePath ,
matchBuildFileSection = re . search ( " / \ * Begin PBXBuildFile section \ */ \n " , strIn )
dirName , fileName = os . path . split ( filePath )
fileInSources = fileName + " in Sources "
id = toUuid ( fileInSources )
idDict [ id ] = id
insert = " \t \t %s /* %s */ = { isa = PBXBuildFile; fileRef = %s /* %s */; }; \n " % ( id , fileInSources , fileRefId , fileName )
strIn = strIn [ : matchBuildFileSection . end ( ) ] + insert + strIn [ matchBuildFileSection . end ( ) : ]
print " OK "
return id
def insertFileRefEntry ( filePath , id = 0 ) :
global strIn
print " \t Insert PBXFileReference for ' %s ' ... " % filePath ,
matchFileRefSection = re . search ( " / \ * Begin PBXFileReference section \ */ \n " , strIn )
dirName , fileName = os . path . split ( filePath )
if id == 0 :
id = toUuid ( fileName )
idDict [ id ] = id
insert = " \t \t %s /* %s */ = { isa = PBXFileReference; lastKnownFileType = file; name = %s ; path = %s ; sourceTree = \" <group> \" ; }; \n " % ( id , fileName , fileName , filePath )
strIn = strIn [ : matchFileRefSection . end ( ) ] + insert + strIn [ matchFileRefSection . end ( ) : ]
print " OK "
return id
def insertSourcesBuildPhaseEntry ( id , fileName , insertBeforeFileName , startSearchPos = 0 ) :
global strIn
print " \t Insert PBXSourcesBuildPhase for ' %s ' ... " % fileName ,
matchBuildPhase = re . compile ( " .+ / \ * " + insertBeforeFileName + " in Sources \ */, " ) \
. search ( strIn , startSearchPos )
insert = " \t \t \t \t %s /* %s in Sources */, \n " % ( id , fileName )
strIn = strIn [ : matchBuildPhase . start ( ) ] \
+ insert \
+ strIn [ matchBuildPhase . start ( ) : ]
print " OK "
return matchBuildPhase . start ( ) + len ( insert ) + len ( matchBuildPhase . group ( 0 ) )
# Detect and fix errors in the project file that might have been introduced.
# Sometimes two source files are concatenated. These are spottable by
# looking for patterns such as "filename.cppsrc/html/"
# Following is a stripped Xcode project containing several problems that
# are solved after finding the error.
strTest = \
""" /* Begin PBXBuildFile section */
95 DE8BAB1238EE1800B43069 / * m_fonts . cpp in Sources * / = { isa = PBXBuildFile ; fileRef = 95 DE8BAA1238EE1700B43069 / * m_fonts . cpp * / ; } ;
95 DE8BAC1238EE1800B43069 / * m_fonts . cpp in Sources * / = { isa = PBXBuildFile ; fileRef = 95 DE8BAA1238EE1700B43069 / * m_fonts . cpp * / ; } ;
/ * End PBXBuildFile section * /
/ * Begin PBXFileReference section * /
95 DE8BAA1238EE1700B43069 / * m_fonts . cpp * / = { isa = PBXFileReference ; lastKnownFileType = file ; name = m_fonts . cpp ; path = . . / . . / src / html / m_dflist . cppsrc / html / m_fonts . cpp ; sourceTree = " <group> " ; } ;
/ * End PBXFileReference section * /
/ * Begin PBXGroup section * /
95 DE8B831238EE1000B43069 / * html * / = {
isa = PBXGroup ;
children = (
95 DE8B841238EE1000B43069 / * src / html * / ,
95 DE8BA91238EE1700B43069 / * src / html / m_dflist . cppsrc / html * / ,
95 DE8BCE1238EE1F00B43069 / * src / generic * / ,
) ;
name = html ;
sourceTree = " <group> " ;
} ;
95 DE8B841238EE1000B43069 / * src / html * / = {
isa = PBXGroup ;
children = (
95 DE8B851238EE1000B43069 / * chm . cpp * / ,
95 DE8BAD1238EE1800B43069 / * m_hline . cpp * / ,
) ;
name = src / html ;
sourceTree = " <group> " ;
} ;
95 DE8BA91238EE1700B43069 / * src / html / m_dflist . cppsrc / html * / = {
isa = PBXGroup ;
children = (
95 DE8BAA1238EE1700B43069 / * m_fonts . cpp * / ,
) ;
name = src / html / m_dflist . cppsrc / html ;
sourceTree = " <group> " ;
} ;
/ * End PBXGroup section * /
/ * Begin PBXSourcesBuildPhase section * /
404 BEE5E10EC83280080E2B8 / * Sources * / = {
files = (
95 DE8BAC1238EE1800B43069 / * m_fonts . cpp in Sources * / ,
) ;
runOnlyForDeploymentPostprocessing = 0 ;
} ;
D2AAC0C405546C1D00DB518D / * Sources * / = {
files = (
95 DE8BAB1238EE1800B43069 / * m_fonts . cpp in Sources * / ,
) ;
runOnlyForDeploymentPostprocessing = 0 ;
} ;
/ * End PBXSourcesBuildPhase section * / """
if testFixStage :
strIn = strTest
rc = re . compile ( " .+ (?P<path1>[ \ w/.]+( \ .cpp| \ .cxx| \ .c))(?P<path2> \ w+/[ \ w/.]+).+ " )
matchLine = rc . search ( strIn )
while matchLine :
line = matchLine . group ( 0 )
# is it a line from the PBXFileReference section containing 2 mixed paths?
# example:
# FEDCBA9876543210FEDCBA98 /* file2.cpp */ = {isa = PBXFileReference; lastKnownFileType = file; name = file2.cpp; path = ../../src/html/file1.cppsrc/html/file2.cpp; sourceTree = "<group>"; };
if line . endswith ( " }; " ) :
path1 = matchLine . group ( ' path1 ' )
path2 = matchLine . group ( ' path2 ' )
print " Correcting mixed paths ' %s ' and ' %s ' at ' %s ' : " % ( path1 , path2 , line )
# if so, make note of the ID used (belongs to path2), remove the line
# and split the 2 paths inserting 2 new entries inside PBXFileReference
fileRefId2 = re . search ( idMask , line ) . group ( 0 )
print " \t Delete the offending PBXFileReference line... " ,
# delete the PBXFileReference line that was found and which contains 2 mixed paths
strIn = strIn [ : matchLine . start ( ) ] + strIn [ matchLine . end ( ) + 1 : ]
print " OK "
# insert corrected path1 entry in PBXFileReference
fileRefId1 = insertFileRefEntry ( path1 )
# do the same for path2 (which already had a ID)
path2Corrected = path2
if path2Corrected . startswith ( ' src ' ) :
path2Corrected = ' ../../ ' + path2Corrected
insertFileRefEntry ( path2Corrected , fileRefId2 )
buildPhaseId = { }
# insert a PBXBuildFile entry, 1 for each target
# path2 already has correct PBXBuildFile entries
targetCount = strIn . count ( " isa = PBXSourcesBuildPhase " )
for i in range ( 0 , targetCount ) :
buildPhaseId [ i ] = insertBuildFileEntry ( path1 , fileRefId1 )
fileName1 = os . path . split ( path1 ) [ 1 ]
dir2 , fileName2 = os . path . split ( path2 )
# refer to each PBXBuildFile in each PBXSourcesBuildPhase
startSearchIndex = 0
for i in range ( 0 , targetCount ) :
startSearchIndex = insertSourcesBuildPhaseEntry ( buildPhaseId [ i ] , fileName1 , fileName2 , startSearchIndex )
# insert both paths in the group they belong to
matchGroupStart = re . search ( " / \ * %s \ */ = { " % dir2 , strIn )
endGroupIndex = strIn . find ( " }; " , matchGroupStart . start ( ) )
for matchGroupLine in re . compile ( " .+ " + idMask + " / \ * (.+) \ */, " ) . finditer ( strIn , matchGroupStart . start ( ) , endGroupIndex ) :
if matchGroupLine . group ( 1 ) > fileName1 :
print " \t Insert paths in PBXGroup ' %s ' , just before ' %s ' ... " % ( dir2 , matchGroupLine . group ( 1 ) ) ,
strIn = strIn [ : matchGroupLine . start ( ) ] \
+ " \t \t \t \t %s /* %s */, \n " % ( fileRefId1 , fileName1 ) \
+ " \t \t \t \t %s /* %s */, \n " % ( fileRefId2 , fileName2 ) \
+ strIn [ matchGroupLine . start ( ) : ]
print " OK "
break
elif line . endswith ( " */ = { " ) :
print " Delete invalid PBXGroup starting at ' %s ' ... " % line ,
find = " }; \n "
endGroupIndex = strIn . find ( find , matchLine . start ( ) ) + len ( find )
strIn = strIn [ : matchLine . start ( ) ] + strIn [ endGroupIndex : ]
print " OK "
elif line . endswith ( " */, " ) :
print " Delete invalid PBXGroup child ' %s ' ... " % line ,
strIn = strIn [ : matchLine . start ( ) ] + strIn [ matchLine . end ( ) + 1 : ]
print " OK "
matchLine = rc . search ( strIn )
if testFixStage :
print " ------------------------------------------ "
print strIn
exit ( 1 )
2010-09-08 13:11:15 -04:00
# key = original ID found in project
# value = ID it will be replaced by
idDict = { }
# some of the strings to match to find definitions of Xcode IDs:
# from PBXBuildFile section:
# 0123456789ABCDEF01234567 /* filename.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FEDCBA9876543210FEDCBA98 /* filename.cpp */; };
# from PBXFileReference section:
# FEDCBA9876543210FEDCBA98 /* filename.cpp */ = {isa = PBXFileReference; lastKnownFileType = file; name = any.cpp; path = ../../src/common/filename.cpp; sourceTree = "<group>"; };
# from remaining sections:
# 890123456789ABCDEF012345 /* Name */ = {
# Capture the first comment between /* and */ (file/section name) as a group
rc = re . compile ( " \ s+( " + idMask + " ) / \ * (.+) \ */ = { .*$ " , re . MULTILINE )
dict = rc . findall ( strIn )
for s in dict :
# s[0] is the original ID, s[1] is the name
assert ( not s [ 0 ] in idDict )
2010-09-15 18:13:40 -04:00
idDict [ s [ 0 ] ] = toUuid ( s [ 1 ] )
2010-09-08 13:11:15 -04:00
# replace all found identifiers with the new ones
def repl ( match ) :
return idDict [ match . group ( 0 ) ]
strOut = re . sub ( idMask , repl , strIn )
fout = open ( projectFile , " w " )
fout . write ( strOut )
fout . close ( )