3 ###############################################################################
4 # Name: build/osx/fix_xcode_ids.py
5 # Author: Dimitri Schoolwerth
8 # Copyright: (c) 2010 wxWidgets team
9 # Licence: wxWindows licence
10 ###############################################################################
18 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"
19 Usage: fix_xcode_ids xcode_proj_dir"""
26 projectFile
= sys
.argv
[1] + "/project.pbxproj"
27 fin
= open(projectFile
, "r")
33 # Xcode identifiers (IDs) consist of 24 hexadecimal digits
34 idMask
= "[A-Fa-f0-9]{24}"
38 # convert a name to an identifier for Xcode
40 from uuid
import uuid3
, UUID
41 id = uuid3(UUID("349f853c-91f8-4eba-b9b9-5e9f882e693c"), name
).hex[:24].upper()
43 # Some names can appear twice or even more (depending on number of
44 # targets), make them unique
45 while id in idDict
.values() :
46 id = "%024X" % (int(id, 16) + 1)
49 def insertBuildFileEntry(filePath
, fileRefId
):
51 print "\tInsert PBXBuildFile for '%s'..." % filePath
,
53 matchBuildFileSection
= re
.search("/\* Begin PBXBuildFile section \*/\n", strIn
)
54 dirName
, fileName
= os
.path
.split(filePath
)
56 fileInSources
= fileName
+ " in Sources"
57 id = toUuid(fileInSources
)
59 insert
= "\t\t%s /* %s */ = {isa = PBXBuildFile; fileRef = %s /* %s */; };\n" % (id, fileInSources
, fileRefId
, fileName
)
61 strIn
= strIn
[:matchBuildFileSection
.end()] + insert
+ strIn
[matchBuildFileSection
.end():]
67 def insertFileRefEntry(filePath
, id = 0):
69 print "\tInsert PBXFileReference for '%s'..." % filePath
,
71 matchFileRefSection
= re
.search("/\* Begin PBXFileReference section \*/\n", strIn
)
72 dirName
, fileName
= os
.path
.split(filePath
)
77 insert
= "\t\t%s /* %s */ = {isa = PBXFileReference; lastKnownFileType = file; name = %s; path = %s; sourceTree = \"<group>\"; };\n" % (id, fileName
, fileName
, filePath
)
78 strIn
= strIn
[:matchFileRefSection
.end()] + insert
+ strIn
[matchFileRefSection
.end():]
84 def insertSourcesBuildPhaseEntry(id, fileName
, insertBeforeFileName
, startSearchPos
= 0):
86 print "\tInsert PBXSourcesBuildPhase for '%s'..." % fileName
,
88 matchBuildPhase
= re
.compile(".+ /\* " + insertBeforeFileName
+ " in Sources \*/,") \
89 .search(strIn
, startSearchPos
)
90 insert
= "\t\t\t\t%s /* %s in Sources */,\n" % (id, fileName
)
91 strIn
= strIn
[:matchBuildPhase
.start()] \
93 + strIn
[matchBuildPhase
.start():]
96 return matchBuildPhase
.start() + len(insert
) + len(matchBuildPhase
.group(0))
98 # Detect and fix errors in the project file that might have been introduced.
99 # Sometimes two source files are concatenated. These are spottable by
100 # looking for patterns such as "filename.cppsrc/html/"
101 # Following is a stripped Xcode project containing several problems that
102 # are solved after finding the error.
104 """/* Begin PBXBuildFile section */
105 95DE8BAB1238EE1800B43069 /* m_fonts.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 95DE8BAA1238EE1700B43069 /* m_fonts.cpp */; };
106 95DE8BAC1238EE1800B43069 /* m_fonts.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 95DE8BAA1238EE1700B43069 /* m_fonts.cpp */; };
107 /* End PBXBuildFile section */
109 /* Begin PBXFileReference section */
110 95DE8BAA1238EE1700B43069 /* m_fonts.cpp */ = {isa = PBXFileReference; lastKnownFileType = file; name = m_fonts.cpp; path = ../../src/html/m_dflist.cppsrc/html/m_fonts.cpp; sourceTree = "<group>"; };
111 /* End PBXFileReference section */
113 /* Begin PBXGroup section */
114 95DE8B831238EE1000B43069 /* html */ = {
117 95DE8B841238EE1000B43069 /* src/html */,
118 95DE8BA91238EE1700B43069 /* src/html/m_dflist.cppsrc/html */,
119 95DE8BCE1238EE1F00B43069 /* src/generic */,
122 sourceTree = "<group>";
124 95DE8B841238EE1000B43069 /* src/html */ = {
127 95DE8B851238EE1000B43069 /* chm.cpp */,
128 95DE8BAD1238EE1800B43069 /* m_hline.cpp */,
131 sourceTree = "<group>";
134 95DE8BA91238EE1700B43069 /* src/html/m_dflist.cppsrc/html */ = {
137 95DE8BAA1238EE1700B43069 /* m_fonts.cpp */,
139 name = src/html/m_dflist.cppsrc/html;
140 sourceTree = "<group>";
142 /* End PBXGroup section */
145 /* Begin PBXSourcesBuildPhase section */
146 404BEE5E10EC83280080E2B8 /* Sources */ = {
148 95DE8BAC1238EE1800B43069 /* m_fonts.cpp in Sources */,
150 runOnlyForDeploymentPostprocessing = 0;
152 D2AAC0C405546C1D00DB518D /* Sources */ = {
154 95DE8BAB1238EE1800B43069 /* m_fonts.cpp in Sources */,
156 runOnlyForDeploymentPostprocessing = 0;
159 /* End PBXSourcesBuildPhase section */"""
164 rc
= re
.compile(".+ (?P<path1>[\w/.]+(\.cpp|\.cxx|\.c))(?P<path2>\w+/[\w/.]+).+")
165 matchLine
= rc
.search(strIn
)
167 line
= matchLine
.group(0)
169 # is it a line from the PBXFileReference section containing 2 mixed paths?
171 # FEDCBA9876543210FEDCBA98 /* file2.cpp */ = {isa = PBXFileReference; lastKnownFileType = file; name = file2.cpp; path = ../../src/html/file1.cppsrc/html/file2.cpp; sourceTree = "<group>"; };
172 if line
.endswith("};") :
173 path1
= matchLine
.group('path1')
174 path2
= matchLine
.group('path2')
175 print "Correcting mixed paths '%s' and '%s' at '%s':" % (path1
, path2
, line
)
176 # if so, make note of the ID used (belongs to path2), remove the line
177 # and split the 2 paths inserting 2 new entries inside PBXFileReference
178 fileRefId2
= re
.search(idMask
, line
).group(0)
180 print "\tDelete the offending PBXFileReference line...",
181 # delete the PBXFileReference line that was found and which contains 2 mixed paths
182 strIn
= strIn
[:matchLine
.start()] + strIn
[matchLine
.end()+1:]
185 # insert corrected path1 entry in PBXFileReference
186 fileRefId1
= insertFileRefEntry(path1
)
188 # do the same for path2 (which already had a ID)
189 path2Corrected
= path2
190 if path2Corrected
.startswith('src') :
191 path2Corrected
= '../../' + path2Corrected
193 insertFileRefEntry(path2Corrected
, fileRefId2
)
197 # insert a PBXBuildFile entry, 1 for each target
198 # path2 already has correct PBXBuildFile entries
199 targetCount
= strIn
.count("isa = PBXSourcesBuildPhase")
200 for i
in range(0, targetCount
):
201 buildPhaseId
[i
] = insertBuildFileEntry(path1
, fileRefId1
)
204 fileName1
= os
.path
.split(path1
)[1]
205 dir2
, fileName2
= os
.path
.split(path2
)
207 # refer to each PBXBuildFile in each PBXSourcesBuildPhase
209 for i
in range(0, targetCount
):
210 startSearchIndex
= insertSourcesBuildPhaseEntry(buildPhaseId
[i
], fileName1
, fileName2
, startSearchIndex
)
213 # insert both paths in the group they belong to
214 matchGroupStart
= re
.search("/\* %s \*/ = {" % dir2
, strIn
)
215 endGroupIndex
= strIn
.find("};", matchGroupStart
.start())
217 for matchGroupLine
in re
.compile(".+" + idMask
+ " /\* (.+) \*/,").finditer(strIn
, matchGroupStart
.start(), endGroupIndex
) :
218 if matchGroupLine
.group(1) > fileName1
:
219 print "\tInsert paths in PBXGroup '%s', just before '%s'..." % (dir2
, matchGroupLine
.group(1)),
220 strIn
= strIn
[:matchGroupLine
.start()] \
221 + "\t\t\t\t%s /* %s */,\n" % (fileRefId1
, fileName1
) \
222 + "\t\t\t\t%s /* %s */,\n" % (fileRefId2
, fileName2
) \
223 + strIn
[matchGroupLine
.start():]
228 elif line
.endswith("*/ = {") :
229 print "Delete invalid PBXGroup starting at '%s'..." % line
,
231 endGroupIndex
= strIn
.find(find
, matchLine
.start()) + len(find
)
232 strIn
= strIn
[:matchLine
.start()] + strIn
[endGroupIndex
:]
235 elif line
.endswith(" */,") :
236 print "Delete invalid PBXGroup child '%s'..." % line
,
237 strIn
= strIn
[:matchLine
.start()] + strIn
[matchLine
.end()+1:]
240 matchLine
= rc
.search(strIn
)
243 print "------------------------------------------"
248 # key = original ID found in project
249 # value = ID it will be replaced by
252 # some of the strings to match to find definitions of Xcode IDs:
254 # from PBXBuildFile section:
255 # 0123456789ABCDEF01234567 /* filename.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FEDCBA9876543210FEDCBA98 /* filename.cpp */; };
257 # from PBXFileReference section:
258 # FEDCBA9876543210FEDCBA98 /* filename.cpp */ = {isa = PBXFileReference; lastKnownFileType = file; name = any.cpp; path = ../../src/common/filename.cpp; sourceTree = "<group>"; };
260 # from remaining sections:
261 # 890123456789ABCDEF012345 /* Name */ = {
263 # Capture the first comment between /* and */ (file/section name) as a group
264 rc
= re
.compile("\s+(" + idMask
+ ") /\* (.+) \*/ = {.*$", re
.MULTILINE
)
265 dict = rc
.findall(strIn
)
268 # s[0] is the original ID, s[1] is the name
269 assert(not s
[0] in idDict
)
270 idDict
[s
[0]] = toUuid(s
[1])
273 # replace all found identifiers with the new ones
275 return idDict
[match
.group(0)]
277 strOut
= re
.sub(idMask
, repl
, strIn
)
279 fout
= open(projectFile
, "w")