]>
Commit | Line | Data |
---|---|---|
59719014 DS |
1 | #!/usr/bin/python |
2 | ||
3 | ############################################################################### | |
4 | # Name: build/osx/fix_xcode_ids.py | |
5 | # Author: Dimitri Schoolwerth | |
6 | # Created: 2010-09-08 | |
59719014 DS |
7 | # Copyright: (c) 2010 wxWidgets team |
8 | # Licence: wxWindows licence | |
9 | ############################################################################### | |
10 | ||
09a8cab7 DS |
11 | testFixStage = False |
12 | ||
13 | import os | |
59719014 DS |
14 | import sys |
15 | import re | |
59719014 DS |
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 | ||
09a8cab7 DS |
20 | if not testFixStage: |
21 | if len(sys.argv) < 2: | |
22 | print USAGE | |
23 | sys.exit(1) | |
59719014 | 24 | |
09a8cab7 DS |
25 | projectFile = sys.argv[1] + "/project.pbxproj" |
26 | fin = open(projectFile, "r") | |
27 | strIn = fin.read() | |
28 | fin.close() | |
59719014 | 29 | |
59719014 DS |
30 | |
31 | ||
32 | # Xcode identifiers (IDs) consist of 24 hexadecimal digits | |
33 | idMask = "[A-Fa-f0-9]{24}" | |
34 | ||
09a8cab7 DS |
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 | ||
59719014 DS |
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 | ||
59719014 DS |
266 | for s in dict: |
267 | # s[0] is the original ID, s[1] is the name | |
59719014 | 268 | assert(not s[0] in idDict) |
09a8cab7 | 269 | idDict[s[0]] = toUuid(s[1]) |
59719014 DS |
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() |