]> git.saurik.com Git - wxWidgets.git/blob - build/osx/fix_xcode_ids.py
carbon sdk must be 10.7 max, 10.8 is not supported anymore
[wxWidgets.git] / build / osx / fix_xcode_ids.py
1 #!/usr/bin/python
2
3 ###############################################################################
4 # Name: build/osx/fix_xcode_ids.py
5 # Author: Dimitri Schoolwerth
6 # Created: 2010-09-08
7 # Copyright: (c) 2010 wxWidgets team
8 # Licence: wxWindows licence
9 ###############################################################################
10
11 testFixStage = False
12
13 import os
14 import sys
15 import re
16
17 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"
18 Usage: fix_xcode_ids xcode_proj_dir"""
19
20 if not testFixStage:
21 if len(sys.argv) < 2:
22 print USAGE
23 sys.exit(1)
24
25 projectFile = sys.argv[1] + "/project.pbxproj"
26 fin = open(projectFile, "r")
27 strIn = fin.read()
28 fin.close()
29
30
31
32 # Xcode identifiers (IDs) consist of 24 hexadecimal digits
33 idMask = "[A-Fa-f0-9]{24}"
34
35 idDict = {}
36
37 # convert a name to an identifier for Xcode
38 def toUuid(name):
39 from uuid import uuid3, UUID
40 id = uuid3(UUID("349f853c-91f8-4eba-b9b9-5e9f882e693c"), name).hex[:24].upper()
41
42 # Some names can appear twice or even more (depending on number of
43 # targets), make them unique
44 while id in idDict.values() :
45 id = "%024X" % (int(id, 16) + 1)
46 return id
47
48 def insertBuildFileEntry(filePath, fileRefId):
49 global strIn
50 print "\tInsert PBXBuildFile for '%s'..." % filePath,
51
52 matchBuildFileSection = re.search("/\* Begin PBXBuildFile section \*/\n", strIn)
53 dirName, fileName = os.path.split(filePath)
54
55 fileInSources = fileName + " in Sources"
56 id = toUuid(fileInSources)
57 idDict[id] = id
58 insert = "\t\t%s /* %s */ = {isa = PBXBuildFile; fileRef = %s /* %s */; };\n" % (id, fileInSources, fileRefId, fileName)
59
60 strIn = strIn[:matchBuildFileSection.end()] + insert + strIn[matchBuildFileSection.end():]
61
62 print "OK"
63 return id
64
65
66 def insertFileRefEntry(filePath, id = 0):
67 global strIn
68 print "\tInsert PBXFileReference for '%s'..." % filePath,
69
70 matchFileRefSection = re.search("/\* Begin PBXFileReference section \*/\n", strIn)
71 dirName, fileName = os.path.split(filePath)
72 if id == 0:
73 id = toUuid(fileName)
74 idDict[id] = id
75
76 insert = "\t\t%s /* %s */ = {isa = PBXFileReference; lastKnownFileType = file; name = %s; path = %s; sourceTree = \"<group>\"; };\n" % (id, fileName, fileName, filePath)
77 strIn = strIn[:matchFileRefSection.end()] + insert + strIn[matchFileRefSection.end():]
78
79 print "OK"
80 return id
81
82
83 def insertSourcesBuildPhaseEntry(id, fileName, insertBeforeFileName, startSearchPos = 0):
84 global strIn
85 print "\tInsert PBXSourcesBuildPhase for '%s'..." % fileName,
86
87 matchBuildPhase = re.compile(".+ /\* " + insertBeforeFileName + " in Sources \*/,") \
88 .search(strIn, startSearchPos)
89 insert = "\t\t\t\t%s /* %s in Sources */,\n" % (id, fileName)
90 strIn = strIn[:matchBuildPhase.start()] \
91 + insert \
92 + strIn[matchBuildPhase.start():]
93
94 print "OK"
95 return matchBuildPhase.start() + len(insert) + len(matchBuildPhase.group(0))
96
97 # Detect and fix errors in the project file that might have been introduced.
98 # Sometimes two source files are concatenated. These are spottable by
99 # looking for patterns such as "filename.cppsrc/html/"
100 # Following is a stripped Xcode project containing several problems that
101 # are solved after finding the error.
102 strTest = \
103 """/* Begin PBXBuildFile section */
104 95DE8BAB1238EE1800B43069 /* m_fonts.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 95DE8BAA1238EE1700B43069 /* m_fonts.cpp */; };
105 95DE8BAC1238EE1800B43069 /* m_fonts.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 95DE8BAA1238EE1700B43069 /* m_fonts.cpp */; };
106 /* End PBXBuildFile section */
107
108 /* Begin PBXFileReference section */
109 95DE8BAA1238EE1700B43069 /* m_fonts.cpp */ = {isa = PBXFileReference; lastKnownFileType = file; name = m_fonts.cpp; path = ../../src/html/m_dflist.cppsrc/html/m_fonts.cpp; sourceTree = "<group>"; };
110 /* End PBXFileReference section */
111
112 /* Begin PBXGroup section */
113 95DE8B831238EE1000B43069 /* html */ = {
114 isa = PBXGroup;
115 children = (
116 95DE8B841238EE1000B43069 /* src/html */,
117 95DE8BA91238EE1700B43069 /* src/html/m_dflist.cppsrc/html */,
118 95DE8BCE1238EE1F00B43069 /* src/generic */,
119 );
120 name = html;
121 sourceTree = "<group>";
122 };
123 95DE8B841238EE1000B43069 /* src/html */ = {
124 isa = PBXGroup;
125 children = (
126 95DE8B851238EE1000B43069 /* chm.cpp */,
127 95DE8BAD1238EE1800B43069 /* m_hline.cpp */,
128 );
129 name = src/html;
130 sourceTree = "<group>";
131 };
132
133 95DE8BA91238EE1700B43069 /* src/html/m_dflist.cppsrc/html */ = {
134 isa = PBXGroup;
135 children = (
136 95DE8BAA1238EE1700B43069 /* m_fonts.cpp */,
137 );
138 name = src/html/m_dflist.cppsrc/html;
139 sourceTree = "<group>";
140 };
141 /* End PBXGroup section */
142
143
144 /* Begin PBXSourcesBuildPhase section */
145 404BEE5E10EC83280080E2B8 /* Sources */ = {
146 files = (
147 95DE8BAC1238EE1800B43069 /* m_fonts.cpp in Sources */,
148 );
149 runOnlyForDeploymentPostprocessing = 0;
150 };
151 D2AAC0C405546C1D00DB518D /* Sources */ = {
152 files = (
153 95DE8BAB1238EE1800B43069 /* m_fonts.cpp in Sources */,
154 );
155 runOnlyForDeploymentPostprocessing = 0;
156 };
157
158 /* End PBXSourcesBuildPhase section */"""
159
160 if testFixStage:
161 strIn = strTest
162
163 rc = re.compile(".+ (?P<path1>[\w/.]+(\.cpp|\.cxx|\.c))(?P<path2>\w+/[\w/.]+).+")
164 matchLine = rc.search(strIn)
165 while matchLine:
166 line = matchLine.group(0)
167
168 # is it a line from the PBXFileReference section containing 2 mixed paths?
169 # example:
170 # FEDCBA9876543210FEDCBA98 /* file2.cpp */ = {isa = PBXFileReference; lastKnownFileType = file; name = file2.cpp; path = ../../src/html/file1.cppsrc/html/file2.cpp; sourceTree = "<group>"; };
171 if line.endswith("};") :
172 path1 = matchLine.group('path1')
173 path2 = matchLine.group('path2')
174 print "Correcting mixed paths '%s' and '%s' at '%s':" % (path1, path2, line)
175 # if so, make note of the ID used (belongs to path2), remove the line
176 # and split the 2 paths inserting 2 new entries inside PBXFileReference
177 fileRefId2 = re.search(idMask, line).group(0)
178
179 print "\tDelete the offending PBXFileReference line...",
180 # delete the PBXFileReference line that was found and which contains 2 mixed paths
181 strIn = strIn[:matchLine.start()] + strIn[matchLine.end()+1:]
182 print "OK"
183
184 # insert corrected path1 entry in PBXFileReference
185 fileRefId1 = insertFileRefEntry(path1)
186
187 # do the same for path2 (which already had a ID)
188 path2Corrected = path2
189 if path2Corrected.startswith('src') :
190 path2Corrected = '../../' + path2Corrected
191
192 insertFileRefEntry(path2Corrected, fileRefId2)
193
194
195 buildPhaseId = {}
196 # insert a PBXBuildFile entry, 1 for each target
197 # path2 already has correct PBXBuildFile entries
198 targetCount = strIn.count("isa = PBXSourcesBuildPhase")
199 for i in range(0, targetCount):
200 buildPhaseId[i] = insertBuildFileEntry(path1, fileRefId1)
201
202
203 fileName1 = os.path.split(path1)[1]
204 dir2, fileName2 = os.path.split(path2)
205
206 # refer to each PBXBuildFile in each PBXSourcesBuildPhase
207 startSearchIndex = 0
208 for i in range(0, targetCount):
209 startSearchIndex = insertSourcesBuildPhaseEntry(buildPhaseId[i], fileName1, fileName2, startSearchIndex)
210
211
212 # insert both paths in the group they belong to
213 matchGroupStart = re.search("/\* %s \*/ = {" % dir2, strIn)
214 endGroupIndex = strIn.find("};", matchGroupStart.start())
215
216 for matchGroupLine in re.compile(".+" + idMask + " /\* (.+) \*/,").finditer(strIn, matchGroupStart.start(), endGroupIndex) :
217 if matchGroupLine.group(1) > fileName1:
218 print "\tInsert paths in PBXGroup '%s', just before '%s'..." % (dir2, matchGroupLine.group(1)),
219 strIn = strIn[:matchGroupLine.start()] \
220 + "\t\t\t\t%s /* %s */,\n" % (fileRefId1, fileName1) \
221 + "\t\t\t\t%s /* %s */,\n" % (fileRefId2, fileName2) \
222 + strIn[matchGroupLine.start():]
223 print "OK"
224
225 break
226
227 elif line.endswith("*/ = {") :
228 print "Delete invalid PBXGroup starting at '%s'..." % line,
229 find = "};\n"
230 endGroupIndex = strIn.find(find, matchLine.start()) + len(find)
231 strIn = strIn[:matchLine.start()] + strIn[endGroupIndex:]
232 print "OK"
233
234 elif line.endswith(" */,") :
235 print "Delete invalid PBXGroup child '%s'..." % line,
236 strIn = strIn[:matchLine.start()] + strIn[matchLine.end()+1:]
237 print "OK"
238
239 matchLine = rc.search(strIn)
240
241 if testFixStage:
242 print "------------------------------------------"
243 print strIn
244 exit(1)
245
246
247 # key = original ID found in project
248 # value = ID it will be replaced by
249 idDict = {}
250
251 # some of the strings to match to find definitions of Xcode IDs:
252
253 # from PBXBuildFile section:
254 # 0123456789ABCDEF01234567 /* filename.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FEDCBA9876543210FEDCBA98 /* filename.cpp */; };
255
256 # from PBXFileReference section:
257 # FEDCBA9876543210FEDCBA98 /* filename.cpp */ = {isa = PBXFileReference; lastKnownFileType = file; name = any.cpp; path = ../../src/common/filename.cpp; sourceTree = "<group>"; };
258
259 # from remaining sections:
260 # 890123456789ABCDEF012345 /* Name */ = {
261
262 # Capture the first comment between /* and */ (file/section name) as a group
263 rc = re.compile("\s+(" + idMask + ") /\* (.+) \*/ = {.*$", re.MULTILINE)
264 dict = rc.findall(strIn)
265
266 for s in dict:
267 # s[0] is the original ID, s[1] is the name
268 assert(not s[0] in idDict)
269 idDict[s[0]] = toUuid(s[1])
270
271
272 # replace all found identifiers with the new ones
273 def repl(match):
274 return idDict[match.group(0)]
275
276 strOut = re.sub(idMask, repl, strIn)
277
278 fout = open(projectFile, "w")
279 fout.write(strOut)
280 fout.close()