Add version check for MSVC 10
[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 # RCS-Id: $Id$
8 # Copyright: (c) 2010 wxWidgets team
9 # Licence: wxWindows licence
10 ###############################################################################
11
12 testFixStage = False
13
14 import os
15 import sys
16 import re
17
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"""
20
21 if not testFixStage:
22 if len(sys.argv) < 2:
23 print USAGE
24 sys.exit(1)
25
26 projectFile = sys.argv[1] + "/project.pbxproj"
27 fin = open(projectFile, "r")
28 strIn = fin.read()
29 fin.close()
30
31
32
33 # Xcode identifiers (IDs) consist of 24 hexadecimal digits
34 idMask = "[A-Fa-f0-9]{24}"
35
36 idDict = {}
37
38 # convert a name to an identifier for Xcode
39 def toUuid(name):
40 from uuid import uuid3, UUID
41 id = uuid3(UUID("349f853c-91f8-4eba-b9b9-5e9f882e693c"), name).hex[:24].upper()
42
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)
47 return id
48
49 def insertBuildFileEntry(filePath, fileRefId):
50 global strIn
51 print "\tInsert PBXBuildFile for '%s'..." % filePath,
52
53 matchBuildFileSection = re.search("/\* Begin PBXBuildFile section \*/\n", strIn)
54 dirName, fileName = os.path.split(filePath)
55
56 fileInSources = fileName + " in Sources"
57 id = toUuid(fileInSources)
58 idDict[id] = id
59 insert = "\t\t%s /* %s */ = {isa = PBXBuildFile; fileRef = %s /* %s */; };\n" % (id, fileInSources, fileRefId, fileName)
60
61 strIn = strIn[:matchBuildFileSection.end()] + insert + strIn[matchBuildFileSection.end():]
62
63 print "OK"
64 return id
65
66
67 def insertFileRefEntry(filePath, id = 0):
68 global strIn
69 print "\tInsert PBXFileReference for '%s'..." % filePath,
70
71 matchFileRefSection = re.search("/\* Begin PBXFileReference section \*/\n", strIn)
72 dirName, fileName = os.path.split(filePath)
73 if id == 0:
74 id = toUuid(fileName)
75 idDict[id] = id
76
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():]
79
80 print "OK"
81 return id
82
83
84 def insertSourcesBuildPhaseEntry(id, fileName, insertBeforeFileName, startSearchPos = 0):
85 global strIn
86 print "\tInsert PBXSourcesBuildPhase for '%s'..." % fileName,
87
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()] \
92 + insert \
93 + strIn[matchBuildPhase.start():]
94
95 print "OK"
96 return matchBuildPhase.start() + len(insert) + len(matchBuildPhase.group(0))
97
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.
103 strTest = \
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 */
108
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 */
112
113 /* Begin PBXGroup section */
114 95DE8B831238EE1000B43069 /* html */ = {
115 isa = PBXGroup;
116 children = (
117 95DE8B841238EE1000B43069 /* src/html */,
118 95DE8BA91238EE1700B43069 /* src/html/m_dflist.cppsrc/html */,
119 95DE8BCE1238EE1F00B43069 /* src/generic */,
120 );
121 name = html;
122 sourceTree = "<group>";
123 };
124 95DE8B841238EE1000B43069 /* src/html */ = {
125 isa = PBXGroup;
126 children = (
127 95DE8B851238EE1000B43069 /* chm.cpp */,
128 95DE8BAD1238EE1800B43069 /* m_hline.cpp */,
129 );
130 name = src/html;
131 sourceTree = "<group>";
132 };
133
134 95DE8BA91238EE1700B43069 /* src/html/m_dflist.cppsrc/html */ = {
135 isa = PBXGroup;
136 children = (
137 95DE8BAA1238EE1700B43069 /* m_fonts.cpp */,
138 );
139 name = src/html/m_dflist.cppsrc/html;
140 sourceTree = "<group>";
141 };
142 /* End PBXGroup section */
143
144
145 /* Begin PBXSourcesBuildPhase section */
146 404BEE5E10EC83280080E2B8 /* Sources */ = {
147 files = (
148 95DE8BAC1238EE1800B43069 /* m_fonts.cpp in Sources */,
149 );
150 runOnlyForDeploymentPostprocessing = 0;
151 };
152 D2AAC0C405546C1D00DB518D /* Sources */ = {
153 files = (
154 95DE8BAB1238EE1800B43069 /* m_fonts.cpp in Sources */,
155 );
156 runOnlyForDeploymentPostprocessing = 0;
157 };
158
159 /* End PBXSourcesBuildPhase section */"""
160
161 if testFixStage:
162 strIn = strTest
163
164 rc = re.compile(".+ (?P<path1>[\w/.]+(\.cpp|\.cxx|\.c))(?P<path2>\w+/[\w/.]+).+")
165 matchLine = rc.search(strIn)
166 while matchLine:
167 line = matchLine.group(0)
168
169 # is it a line from the PBXFileReference section containing 2 mixed paths?
170 # example:
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)
179
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:]
183 print "OK"
184
185 # insert corrected path1 entry in PBXFileReference
186 fileRefId1 = insertFileRefEntry(path1)
187
188 # do the same for path2 (which already had a ID)
189 path2Corrected = path2
190 if path2Corrected.startswith('src') :
191 path2Corrected = '../../' + path2Corrected
192
193 insertFileRefEntry(path2Corrected, fileRefId2)
194
195
196 buildPhaseId = {}
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)
202
203
204 fileName1 = os.path.split(path1)[1]
205 dir2, fileName2 = os.path.split(path2)
206
207 # refer to each PBXBuildFile in each PBXSourcesBuildPhase
208 startSearchIndex = 0
209 for i in range(0, targetCount):
210 startSearchIndex = insertSourcesBuildPhaseEntry(buildPhaseId[i], fileName1, fileName2, startSearchIndex)
211
212
213 # insert both paths in the group they belong to
214 matchGroupStart = re.search("/\* %s \*/ = {" % dir2, strIn)
215 endGroupIndex = strIn.find("};", matchGroupStart.start())
216
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():]
224 print "OK"
225
226 break
227
228 elif line.endswith("*/ = {") :
229 print "Delete invalid PBXGroup starting at '%s'..." % line,
230 find = "};\n"
231 endGroupIndex = strIn.find(find, matchLine.start()) + len(find)
232 strIn = strIn[:matchLine.start()] + strIn[endGroupIndex:]
233 print "OK"
234
235 elif line.endswith(" */,") :
236 print "Delete invalid PBXGroup child '%s'..." % line,
237 strIn = strIn[:matchLine.start()] + strIn[matchLine.end()+1:]
238 print "OK"
239
240 matchLine = rc.search(strIn)
241
242 if testFixStage:
243 print "------------------------------------------"
244 print strIn
245 exit(1)
246
247
248 # key = original ID found in project
249 # value = ID it will be replaced by
250 idDict = {}
251
252 # some of the strings to match to find definitions of Xcode IDs:
253
254 # from PBXBuildFile section:
255 # 0123456789ABCDEF01234567 /* filename.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FEDCBA9876543210FEDCBA98 /* filename.cpp */; };
256
257 # from PBXFileReference section:
258 # FEDCBA9876543210FEDCBA98 /* filename.cpp */ = {isa = PBXFileReference; lastKnownFileType = file; name = any.cpp; path = ../../src/common/filename.cpp; sourceTree = "<group>"; };
259
260 # from remaining sections:
261 # 890123456789ABCDEF012345 /* Name */ = {
262
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)
266
267 for s in dict:
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])
271
272
273 # replace all found identifiers with the new ones
274 def repl(match):
275 return idDict[match.group(0)]
276
277 strOut = re.sub(idMask, repl, strIn)
278
279 fout = open(projectFile, "w")
280 fout.write(strOut)
281 fout.close()