diff --git a/build/osx/fix_xcode_ids.py b/build/osx/fix_xcode_ids.py index 23dbe250dd..d80e3028f9 100755 --- a/build/osx/fix_xcode_ids.py +++ b/build/osx/fix_xcode_ids.py @@ -9,26 +9,242 @@ # Licence: wxWindows licence ############################################################################### +testFixStage = False + +import os 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""" -if len(sys.argv) < 2: - print USAGE - sys.exit(1) +if not testFixStage: + if len(sys.argv) < 2: + print USAGE + sys.exit(1) -projectFile = sys.argv[1] + "/project.pbxproj" + projectFile = sys.argv[1] + "/project.pbxproj" + fin = open(projectFile, "r") + strIn = fin.read() + fin.close() -fin = open(projectFile, "r") -strIn = fin.read() -fin.close() # Xcode identifiers (IDs) consist of 24 hexadecimal digits idMask = "[A-Fa-f0-9]{24}" +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 "\tInsert 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 "\tInsert 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 = \"\"; };\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 "\tInsert 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 */ + 95DE8BAB1238EE1800B43069 /* m_fonts.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 95DE8BAA1238EE1700B43069 /* m_fonts.cpp */; }; + 95DE8BAC1238EE1800B43069 /* m_fonts.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 95DE8BAA1238EE1700B43069 /* m_fonts.cpp */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 95DE8BAA1238EE1700B43069 /* m_fonts.cpp */ = {isa = PBXFileReference; lastKnownFileType = file; name = m_fonts.cpp; path = ../../src/html/m_dflist.cppsrc/html/m_fonts.cpp; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXGroup section */ + 95DE8B831238EE1000B43069 /* html */ = { + isa = PBXGroup; + children = ( + 95DE8B841238EE1000B43069 /* src/html */, + 95DE8BA91238EE1700B43069 /* src/html/m_dflist.cppsrc/html */, + 95DE8BCE1238EE1F00B43069 /* src/generic */, + ); + name = html; + sourceTree = ""; + }; + 95DE8B841238EE1000B43069 /* src/html */ = { + isa = PBXGroup; + children = ( + 95DE8B851238EE1000B43069 /* chm.cpp */, + 95DE8BAD1238EE1800B43069 /* m_hline.cpp */, + ); + name = src/html; + sourceTree = ""; + }; + + 95DE8BA91238EE1700B43069 /* src/html/m_dflist.cppsrc/html */ = { + isa = PBXGroup; + children = ( + 95DE8BAA1238EE1700B43069 /* m_fonts.cpp */, + ); + name = src/html/m_dflist.cppsrc/html; + sourceTree = ""; + }; +/* End PBXGroup section */ + + +/* Begin PBXSourcesBuildPhase section */ + 404BEE5E10EC83280080E2B8 /* Sources */ = { + files = ( + 95DE8BAC1238EE1800B43069 /* m_fonts.cpp in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D2AAC0C405546C1D00DB518D /* Sources */ = { + files = ( + 95DE8BAB1238EE1800B43069 /* m_fonts.cpp in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + +/* End PBXSourcesBuildPhase section */""" + +if testFixStage: + strIn = strTest + +rc = re.compile(".+ (?P[\w/.]+(\.cpp|\.cxx|\.c))(?P\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 = ""; }; + 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 "\tDelete 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 "\tInsert 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) + + # key = original ID found in project # value = ID it will be replaced by idDict = {} @@ -48,22 +264,10 @@ idDict = {} rc = re.compile("\s+(" + idMask + ") /\* (.+) \*/ = {.*$", re.MULTILINE) dict = rc.findall(strIn) -# convert a name to an identifier for Xcode -def toUuid(name): - from uuid import uuid3, UUID - return uuid3(UUID("349f853c-91f8-4eba-b9b9-5e9f882e693c"), name).hex[:24].upper() - for s in dict: # s[0] is the original ID, s[1] is the name - newId = toUuid(s[1]) - # Some names can appear twice or even more (depending on number of - # targets), make them unique - while newId in idDict.values() : - # [2:-1] to skip prepended 0x and trailing L - newId = hex(int(newId, 16) + 1)[2:-1].upper() - assert(not s[0] in idDict) - idDict[s[0]] = newId + idDict[s[0]] = toUuid(s[1]) # replace all found identifiers with the new ones