switching to python3 for creating Xcode project files from bakefile

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
This commit is contained in:
Stefan Csomor
2021-11-22 08:15:16 +01:00
parent 7a03d5fe9b
commit bdbece5d66
2 changed files with 314 additions and 190 deletions

View File

@@ -17,23 +17,12 @@ 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 not testFixStage:
if len(sys.argv) < 2:
print USAGE
sys.exit(1)
projectFile = sys.argv[1] + "/project.pbxproj"
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
@@ -45,9 +34,10 @@ def toUuid(name):
id = "%024X" % (int(id, 16) + 1)
return id
def insertBuildFileEntry(filePath, fileRefId):
global strIn
print "\tInsert PBXBuildFile for '%s'..." % filePath,
print("\tInsert PBXBuildFile for '%s'..." % filePath)
matchBuildFileSection = re.search("/\* Begin PBXBuildFile section \*/\n", strIn)
dirName, fileName = os.path.split(filePath)
@@ -55,17 +45,18 @@ def insertBuildFileEntry(filePath, fileRefId):
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)
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"
print("OK")
return id
def insertFileRefEntry(filePath, id=0):
global strIn
print "\tInsert PBXFileReference for '%s'..." % filePath,
print("\tInsert PBXFileReference for '%s'..." % filePath)
matchFileRefSection = re.search("/\* Begin PBXFileReference section \*/\n", strIn)
dirName, fileName = os.path.split(filePath)
@@ -73,16 +64,17 @@ def insertFileRefEntry(filePath, 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)
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"
print("OK")
return id
def insertSourcesBuildPhaseEntry(id, fileName, insertBeforeFileName, startSearchPos=0):
global strIn
print "\tInsert PBXSourcesBuildPhase for '%s'..." % fileName,
print("\tInsert PBXSourcesBuildPhase for '%s'..." % fileName)
matchBuildPhase = re.compile(".+ /\* " + insertBeforeFileName + " in Sources \*/,") \
.search(strIn, startSearchPos)
@@ -91,9 +83,10 @@ def insertSourcesBuildPhaseEntry(id, fileName, insertBeforeFileName, startSearch
+ insert \
+ strIn[matchBuildPhase.start():]
print "OK"
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/"
@@ -157,8 +150,29 @@ strTest = \
/* End PBXSourcesBuildPhase section */"""
if testFixStage:
strIn = strTest
# 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)
@@ -171,15 +185,15 @@ while matchLine:
if line.endswith("};"):
path1 = matchLine.group('path1')
path2 = matchLine.group('path2')
print "Correcting mixed paths '%s' and '%s' at '%s':" % (path1, path2, line)
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...",
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"
print("OK")
# insert corrected path1 entry in PBXFileReference
fileRefId1 = insertFileRefEntry(path1)
@@ -191,7 +205,6 @@ while matchLine:
insertFileRefEntry(path2Corrected, fileRefId2)
buildPhaseId = {}
# insert a PBXBuildFile entry, 1 for each target
# path2 already has correct PBXBuildFile entries
@@ -199,7 +212,6 @@ while matchLine:
for i in range(0, targetCount):
buildPhaseId[i] = insertBuildFileEntry(path1, fileRefId1)
fileName1 = os.path.split(path1)[1]
dir2, fileName2 = os.path.split(path2)
@@ -208,42 +220,36 @@ while matchLine:
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) :
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)),
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"
print("OK")
break
elif line.endswith("*/ = {"):
print "Delete invalid PBXGroup starting at '%s'..." % line,
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"
print("OK")
elif line.endswith(" */,"):
print "Delete invalid PBXGroup child '%s'..." % line,
print("Delete invalid PBXGroup child '%s'..." % line)
strIn = strIn[:matchLine.start()] + strIn[matchLine.end() + 1:]
print "OK"
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 = {}
@@ -268,13 +274,22 @@ for s in dict:
assert (not s[0] in idDict)
idDict[s[0]] = toUuid(s[1])
# replace all found identifiers with the new ones
def repl(match):
return idDict[match.group(0)]
strOut = re.sub(idMask, repl, strIn)
return strOut
fout = open(projectFile, "w")
fout.write(strOut)
fout.close()
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)

109
build/osx/makeprojects.py Normal file
View File

@@ -0,0 +1,109 @@
#!/usr/bin/python
import sys
import os
import shutil
import xml.etree.ElementTree as ET
from pbxproj import XcodeProject
from pbxproj.pbxextensions import ProjectFiles
ProjectFiles._FILE_TYPES['.cxx'] = ('sourcecode.cpp.cpp', 'PBXSourcesBuildPhase')
from fix_xcode_ids import processFile
bklfiles = ["../bakefiles/files.bkl", "../bakefiles/zlib.bkl", "../bakefiles/regex.bkl", "../bakefiles/tiff.bkl",
"../bakefiles/png.bkl", "../bakefiles/jpeg.bkl", "../bakefiles/scintilla.bkl", "../bakefiles/expat.bkl"]
nodes = [
# xcode group, entries[], targets []
["base", ["$(BASE_SRC)"], ["dynamic", "static", "base"]],
["base", ["$(BASE_AND_GUI_SRC)"], ["dynamic", "static", "base", "core"]],
["core", ["$(CORE_SRC)"], ["dynamic", "static", "core"]],
["net", ["$(NET_SRC)"], ["dynamic", "static", "net"]],
["adv", ["$(ADVANCED_SRC)"], ["dynamic", "static", "adv"]],
["webview", ["$(WEBVIEW_SRC)"], ["dynamic", "static", "webview"]],
["media", ["$(MEDIA_SRC)"], ["dynamic", "static", "media"]],
["html", ["$(HTML_SRC)"], ["dynamic", "static", "html"]],
["xrc", ["$(XRC_SRC)"], ["dynamic", "static", "xrc"]],
["qa", ["$(QA_SRC)"], ["dynamic", "static", "qa"]],
["xml", ["$(XML_SRC)"], ["dynamic", "static", "xml"]],
["opengl", ["$(OPENGL_SRC)"], ["dynamic", "static", "gl"]],
["aui", ["$(AUI_SRC)"], ["dynamic", "static", "aui"]],
["ribbon", ["$(RIBBON_SRC)"], ["dynamic", "static", "ribbon"]],
["propgrid", ["$(PROPGRID_SRC)"], ["dynamic", "static", "propgrid"]],
["richtext", ["$(RICHTEXT_SRC)"], ["dynamic", "static", "richttext"]],
["stc", ["$(STC_SRC)"], ["dynamic", "static", "stc"]],
["libzlib", ["$(wxzlib)"], ["dynamic", "static", "wxzlib"]],
["libtiff", ["$(wxtiff)"], ["dynamic", "static", "wxtiff"]],
["libjpeg", ["$(wxjpeg)"], ["dynamic", "static", "wxjpeg"]],
["libpng", ["$(wxpng)"], ["dynamic", "static", "wxpng"]],
["libregex", ["$(wxregex)"], ["dynamic", "static", "wxregex"]],
["libscintilla", ["$(wxscintilla)"], ["dynamic", "static", "wxscintilla"]],
["libexpat", ["$(wxexpat)"], ["dynamic", "static", "wxexpat"]]
]
def addNode(project, groupName, entries, fileGroups, targets):
group = project.get_or_create_group(groupName)
for entry in entries:
if entry.startswith("$("):
varname = entry[2:-1]
addNode(project, groupName, fileGroups[varname], fileGroups, targets)
else:
project.add_file("../../"+entry, parent=group, target_name=targets)
def populateProject(projectfile, fileGroups, nodes):
project = XcodeProject.load(projectfile)
for node in nodes:
groupName = node[0]
entries = node[1]
targets = node[2]
addNode(project, groupName, entries, fileGroups, targets)
project.save()
def parseSources(theName, xmlNode, conditions, fileGroups):
files = xmlNode.text
for ifs in xmlNode.findall("if"):
condition = ifs.attrib['cond']
if condition in conditions:
files += ifs.text
fileList = files.split() if files != None else []
fileGroups[theName] = fileList
def parseFile(bklFile, conditions, fileGroups):
tree = ET.parse(os.path.join(osxBuildFolder, bklFile))
for elem in tree.iter():
if elem.tag == 'set':
theName = elem.attrib['var']
parseSources(theName, elem, conditions, fileGroups)
elif elem.tag == 'lib':
theName = elem.attrib['id']
parseSources(theName, elem.find("sources"), conditions, fileGroups)
def readFilesList(bklFileList, conditions):
fileGroups = {}
for bklFile in bklFileList:
parseFile(bklFile, conditions, fileGroups)
return fileGroups
def makeProject(projectName, conditions):
# make new copy from template
template = os.path.join(osxBuildFolder, projectName + "_in.xcodeproj")
projectFile = os.path.join(osxBuildFolder, projectName + ".xcodeproj")
if os.path.exists(projectFile):
shutil.rmtree(projectFile)
shutil.copytree(template, projectFile)
# read file list from bkls
fileGroups = readFilesList(bklfiles, conditions)
# create xcode project
populateProject(projectFile + "/project.pbxproj", fileGroups, nodes)
processFile(projectFile + "/project.pbxproj")
osxBuildFolder = os.getcwd()
makeProject("wxcocoa", ["PLATFORM_MACOSX=='1'", "TOOLKIT=='OSX_COCOA'", "WXUNIV=='0'", "USE_GUI=='1' and WXUNIV=='0'"])
makeProject("wxiphone", ["PLATFORM_MACOSX=='1'", "TOOLKIT=='OSX_IPHONE'", "WXUNIV=='0'", "USE_GUI=='1' and WXUNIV=='0'"])