bdbece5d66
moving fix_xcode_ids to python 3 and make it importable, the current AppleScript dictionary from Xcode makes it impossible to use it for our purpose
296 lines
11 KiB
Python
Executable File
296 lines
11 KiB
Python
Executable File
#!/usr/bin/python
|
|
|
|
###############################################################################
|
|
# Name: build/osx/fix_xcode_ids.py
|
|
# Author: Dimitri Schoolwerth
|
|
# Created: 2010-09-08
|
|
# Copyright: (c) 2010 wxWidgets team
|
|
# 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"""
|
|
|
|
# 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 = \"<group>\"; };\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 = "<group>"; };
|
|
/* 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 = "<group>";
|
|
};
|
|
95DE8B841238EE1000B43069 /* src/html */ = {
|
|
isa = PBXGroup;
|
|
children = (
|
|
95DE8B851238EE1000B43069 /* chm.cpp */,
|
|
95DE8BAD1238EE1800B43069 /* m_hline.cpp */,
|
|
);
|
|
name = src/html;
|
|
sourceTree = "<group>";
|
|
};
|
|
|
|
95DE8BA91238EE1700B43069 /* src/html/m_dflist.cppsrc/html */ = {
|
|
isa = PBXGroup;
|
|
children = (
|
|
95DE8BAA1238EE1700B43069 /* m_fonts.cpp */,
|
|
);
|
|
name = src/html/m_dflist.cppsrc/html;
|
|
sourceTree = "<group>";
|
|
};
|
|
/* 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 */"""
|
|
|
|
|
|
# replace all found identifiers with the new ones
|
|
def repl(match):
|
|
return idDict[match.group(0)]
|
|
|
|
|
|
def processFile(projectFile):
|
|
global strIn
|
|
|
|
fin = open(projectFile, "r")
|
|
strIn = fin.read()
|
|
fin.close()
|
|
|
|
strOut = processContent()
|
|
|
|
fout = open(projectFile, "w")
|
|
fout.write(strOut)
|
|
fout.close()
|
|
|
|
|
|
def processContent():
|
|
global strIn
|
|
global idDict
|
|
|
|
rc = re.compile(".+ (?P<path1>[\w/.]+(\.cpp|\.cxx|\.c))(?P<path2>\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 = "<group>"; };
|
|
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)
|
|
|
|
# key = original ID found in project
|
|
# value = ID it will be replaced by
|
|
idDict = {}
|
|
|
|
# some of the strings to match to find definitions of Xcode IDs:
|
|
|
|
# from PBXBuildFile section:
|
|
# 0123456789ABCDEF01234567 /* filename.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FEDCBA9876543210FEDCBA98 /* filename.cpp */; };
|
|
|
|
# from PBXFileReference section:
|
|
# FEDCBA9876543210FEDCBA98 /* filename.cpp */ = {isa = PBXFileReference; lastKnownFileType = file; name = any.cpp; path = ../../src/common/filename.cpp; sourceTree = "<group>"; };
|
|
|
|
# from remaining sections:
|
|
# 890123456789ABCDEF012345 /* Name */ = {
|
|
|
|
# Capture the first comment between /* and */ (file/section name) as a group
|
|
rc = re.compile("\s+(" + idMask + ") /\* (.+) \*/ = {.*$", re.MULTILINE)
|
|
dict = rc.findall(strIn)
|
|
|
|
for s in dict:
|
|
# s[0] is the original ID, s[1] is the name
|
|
assert (not s[0] in idDict)
|
|
idDict[s[0]] = toUuid(s[1])
|
|
|
|
strOut = re.sub(idMask, repl, strIn)
|
|
return strOut
|
|
|
|
|
|
if __name__ == '__main__':
|
|
if not testFixStage:
|
|
if len(sys.argv) < 2:
|
|
print(USAGE)
|
|
sys.exit(1)
|
|
processFile(sys.argv[1] + "/project.pbxproj")
|
|
else:
|
|
strIn = strTest
|
|
print("------------------------------------------")
|
|
print(strIn)
|
|
strOut = processContent()
|
|
print("------------------------------------------")
|
|
print(strOut)
|
|
|
|
exit(1)
|