346 lines
12 KiB
Python
346 lines
12 KiB
Python
#!/usr/bin/env python2
|
|
import distutils.sysconfig
|
|
import getopt
|
|
import glob
|
|
import os
|
|
import platform
|
|
import shutil
|
|
import subprocess
|
|
import stat
|
|
import sys
|
|
|
|
# ScintillaEditPy can only be built against Python 2.x so fail if Python 3.x
|
|
assert sys.version_info < (3,0), "sepbuild.py requires Python 2.x"
|
|
|
|
sys.path.append(os.path.join("..", "ScintillaEdit"))
|
|
import WidgetGen
|
|
|
|
scintillaDirectory = "../.."
|
|
scintillaScriptsDirectory = os.path.join(scintillaDirectory, "scripts")
|
|
sys.path.append(scintillaScriptsDirectory)
|
|
from FileGenerator import GenerateFile
|
|
|
|
# Decide up front which platform, treat anything other than Windows or OS X as Linux
|
|
PLAT_WINDOWS = platform.system() == "Windows"
|
|
PLAT_DARWIN = platform.system() == "Darwin"
|
|
PLAT_LINUX = not (PLAT_DARWIN or PLAT_WINDOWS)
|
|
|
|
def IsFileNewer(name1, name2):
|
|
""" Returns whether file with name1 is newer than file with name2. Returns 1
|
|
if name2 doesn't exist. """
|
|
|
|
if not os.path.exists(name1):
|
|
return 0
|
|
|
|
if not os.path.exists(name2):
|
|
return 1
|
|
|
|
mod_time1 = os.stat(name1)[stat.ST_MTIME]
|
|
mod_time2 = os.stat(name2)[stat.ST_MTIME]
|
|
return (mod_time1 > mod_time2)
|
|
|
|
def textFromRun(args):
|
|
proc = subprocess.Popen(args, shell=isinstance(args, str), stdout=subprocess.PIPE)
|
|
(stdoutdata, stderrdata) = proc.communicate()
|
|
if proc.returncode:
|
|
print("Warning - failed to run '" + " ".join(args) + "'")
|
|
raise OSError(proc.returncode)
|
|
return stdoutdata
|
|
|
|
def runProgram(args, exitOnFailure):
|
|
print(" ".join(args))
|
|
retcode = subprocess.call(" ".join(args), shell=True, stderr=subprocess.STDOUT)
|
|
if retcode:
|
|
print("Failed in " + " ".join(args) + " return code = " + str(retcode))
|
|
if exitOnFailure:
|
|
sys.exit()
|
|
|
|
def usage():
|
|
print("sepbuild.py [-h|--help][-c|--clean][-u|--underscore-names]")
|
|
print("")
|
|
print("Generate PySide wappers and build them.")
|
|
print("")
|
|
print("options:")
|
|
print("")
|
|
print("-c --clean remove all object and generated files")
|
|
print("-b --pyside-base Location of the PySide+Qt4 sandbox to use")
|
|
print("-h --help display this text")
|
|
print("-d --debug=yes|no force debug build (or non-debug build)")
|
|
print("-u --underscore-names use method_names consistent with GTK+ standards")
|
|
|
|
modifyFunctionElement = """ <modify-function signature="%s">%s
|
|
</modify-function>"""
|
|
|
|
injectCode = """
|
|
<inject-code class="target" position="beginning">%s
|
|
</inject-code>"""
|
|
|
|
injectCheckN = """
|
|
if (!cppArg%d) {
|
|
PyErr_SetString(PyExc_ValueError, "Null string argument");
|
|
return 0;
|
|
}"""
|
|
|
|
def methodSignature(name, v, options):
|
|
argTypes = ""
|
|
p1Type = WidgetGen.cppAlias(v["Param1Type"])
|
|
if p1Type == "int":
|
|
p1Type = "sptr_t"
|
|
if p1Type:
|
|
argTypes = argTypes + p1Type
|
|
p2Type = WidgetGen.cppAlias(v["Param2Type"])
|
|
if p2Type == "int":
|
|
p2Type = "sptr_t"
|
|
if p2Type and v["Param2Type"] != "stringresult":
|
|
if p1Type:
|
|
argTypes = argTypes + ", "
|
|
argTypes = argTypes + p2Type
|
|
methodName = WidgetGen.normalisedName(name, options, v["FeatureType"])
|
|
constDeclarator = " const" if v["FeatureType"] == "get" else ""
|
|
return methodName + "(" + argTypes + ")" + constDeclarator
|
|
|
|
def printTypeSystemFile(f, options):
|
|
out = []
|
|
for name in f.order:
|
|
v = f.features[name]
|
|
if v["Category"] != "Deprecated":
|
|
feat = v["FeatureType"]
|
|
if feat in ["fun", "get", "set"]:
|
|
checks = ""
|
|
if v["Param1Type"] == "string":
|
|
checks = checks + (injectCheckN % 0)
|
|
if v["Param2Type"] == "string":
|
|
if v["Param1Type"] == "": # Only arg 2 -> treat as first
|
|
checks = checks + (injectCheckN % 0)
|
|
else:
|
|
checks = checks + (injectCheckN % 1)
|
|
if checks:
|
|
inject = injectCode % checks
|
|
out.append(modifyFunctionElement % (methodSignature(name, v, options), inject))
|
|
#if v["Param1Type"] == "string":
|
|
# out.append("<string-xml>" + name + "</string-xml>\n")
|
|
return out
|
|
|
|
def doubleBackSlashes(s):
|
|
# Quote backslashes so qmake does not produce warnings
|
|
return s.replace("\\", "\\\\")
|
|
|
|
class SepBuilder:
|
|
def __init__(self):
|
|
# Discover configuration parameters
|
|
self.ScintillaEditIncludes = [".", "../ScintillaEdit", "../ScintillaEditBase", "../../include"]
|
|
if PLAT_WINDOWS:
|
|
self.MakeCommand = "nmake"
|
|
self.MakeTarget = "release"
|
|
else:
|
|
self.MakeCommand = "make"
|
|
self.MakeTarget = ""
|
|
|
|
if PLAT_DARWIN:
|
|
self.QMakeOptions = "-spec macx-g++"
|
|
else:
|
|
self.QMakeOptions = ""
|
|
|
|
# Default to debug build if running in a debug build interpreter
|
|
self.DebugBuild = hasattr(sys, 'getobjects')
|
|
|
|
# Python
|
|
self.PyVersion = "%d.%d" % sys.version_info[:2]
|
|
self.PyVersionSuffix = distutils.sysconfig.get_config_var("VERSION")
|
|
self.PyIncludes = distutils.sysconfig.get_python_inc()
|
|
self.PyPrefix = distutils.sysconfig.get_config_var("prefix")
|
|
self.PyLibDir = distutils.sysconfig.get_config_var(
|
|
("LIBDEST" if sys.platform == 'win32' else "LIBDIR"))
|
|
|
|
# Scintilla
|
|
with open("../../version.txt") as f:
|
|
version = f.read()
|
|
self.ScintillaVersion = version[0] + '.' + version[1] + '.' + version[2]
|
|
|
|
# Find out what qmake is called
|
|
self.QMakeCommand = "qmake"
|
|
if not PLAT_WINDOWS:
|
|
# On Unix qmake may not be present but qmake-qt4 may be so check
|
|
pathToQMake = textFromRun("which qmake-qt4 || which qmake").rstrip()
|
|
self.QMakeCommand = os.path.basename(pathToQMake)
|
|
|
|
# Qt default location from qmake
|
|
self._SetQtIncludeBase(textFromRun(self.QMakeCommand + " -query QT_INSTALL_HEADERS").rstrip())
|
|
|
|
# PySide default location
|
|
# No standard for installing PySide development headers and libs on Windows so
|
|
# choose /usr to be like Linux
|
|
self._setPySideBase('\\usr' if PLAT_WINDOWS else '/usr')
|
|
|
|
self.ProInclude = "sepbuild.pri"
|
|
|
|
self.qtStyleInterface = True
|
|
|
|
def _setPySideBase(self, base):
|
|
|
|
self.PySideBase = base
|
|
def _try_pkgconfig(var, package, *relpath):
|
|
try:
|
|
return textFromRun(["pkg-config", "--variable=" + var, package]).rstrip()
|
|
except OSError:
|
|
return os.path.join(self.PySideBase, *relpath)
|
|
self.PySideTypeSystem = _try_pkgconfig("typesystemdir", "pyside",
|
|
"share", "PySide", "typesystems")
|
|
self.PySideIncludeBase = _try_pkgconfig("includedir", "pyside",
|
|
"include", "PySide")
|
|
self.ShibokenIncludeBase = _try_pkgconfig("includedir", "shiboken",
|
|
"include", "shiboken")
|
|
self.PySideIncludes = [
|
|
self.ShibokenIncludeBase,
|
|
self.PySideIncludeBase,
|
|
os.path.join(self.PySideIncludeBase, "QtCore"),
|
|
os.path.join(self.PySideIncludeBase, "QtGui")]
|
|
|
|
self.PySideLibDir = _try_pkgconfig("libdir", "pyside", "lib")
|
|
self.ShibokenLibDir = _try_pkgconfig("libdir", "shiboken", "lib")
|
|
self.AllIncludes = os.pathsep.join(self.QtIncludes + self.ScintillaEditIncludes + self.PySideIncludes)
|
|
|
|
self.ShibokenGenerator = "shiboken"
|
|
# Is this still needed? It doesn't work with latest shiboken sources
|
|
#if PLAT_DARWIN:
|
|
# # On OS X, can not automatically find Shiboken dylib so provide a full path
|
|
# self.ShibokenGenerator = os.path.join(self.PySideLibDir, "generatorrunner", "shiboken")
|
|
|
|
def generateAPI(self, args):
|
|
os.chdir(os.path.join("..", "ScintillaEdit"))
|
|
if not self.qtStyleInterface:
|
|
args.insert(0, '--underscore-names')
|
|
WidgetGen.main(args)
|
|
f = WidgetGen.readInterface(False)
|
|
os.chdir(os.path.join("..", "ScintillaEditPy"))
|
|
options = {"qtStyle": self.qtStyleInterface}
|
|
GenerateFile("typesystem_ScintillaEdit.xml.template", "typesystem_ScintillaEdit.xml",
|
|
"<!-- ", True, printTypeSystemFile(f, options))
|
|
|
|
def runGenerator(self):
|
|
generatorrunner = "shiboken"
|
|
for name in ('shiboken', 'generatorrunner'):
|
|
if PLAT_WINDOWS:
|
|
name += '.exe'
|
|
name = os.path.join(self.PySideBase, "bin", name)
|
|
if os.path.exists(name):
|
|
generatorrunner = name
|
|
break
|
|
|
|
args = [
|
|
generatorrunner,
|
|
"--generator-set=" + self.ShibokenGenerator,
|
|
"global.h ",
|
|
"--avoid-protected-hack",
|
|
"--enable-pyside-extensions",
|
|
"--include-paths=" + self.AllIncludes,
|
|
"--typesystem-paths=" + self.PySideTypeSystem,
|
|
"--output-directory=.",
|
|
"typesystem_ScintillaEdit.xml"]
|
|
print(" ".join(args))
|
|
retcode = subprocess.call(" ".join(args), shell=True, stderr=subprocess.STDOUT)
|
|
if retcode:
|
|
print("Error - failed in generatorrunner " + str(retcode))
|
|
sys.exit()
|
|
|
|
def writeVariables(self):
|
|
# Write variables needed into file to be included from project so it does not have to discover much
|
|
with open(self.ProInclude, "w") as f:
|
|
f.write("SCINTILLA_VERSION=" + self.ScintillaVersion + "\n")
|
|
f.write("PY_VERSION=" + self.PyVersion + "\n")
|
|
f.write("PY_VERSION_SUFFIX=" + self.PyVersionSuffix + "\n")
|
|
f.write("PY_PREFIX=" + doubleBackSlashes(self.PyPrefix) + "\n")
|
|
f.write("PY_INCLUDES=" + doubleBackSlashes(self.PyIncludes) + "\n")
|
|
f.write("PY_LIBDIR=" + doubleBackSlashes(self.PyLibDir) + "\n")
|
|
f.write("PYSIDE_INCLUDES=" + doubleBackSlashes(self.PySideIncludeBase) + "\n")
|
|
f.write("PYSIDE_LIB=" + doubleBackSlashes(self.PySideLibDir) + "\n")
|
|
f.write("SHIBOKEN_INCLUDES=" + doubleBackSlashes(self.ShibokenIncludeBase) + "\n")
|
|
f.write("SHIBOKEN_LIB=" + doubleBackSlashes(self.ShibokenLibDir) + "\n")
|
|
if self.DebugBuild:
|
|
f.write("CONFIG += debug\n")
|
|
else:
|
|
f.write("CONFIG += release\n")
|
|
|
|
def make(self):
|
|
runProgram([self.QMakeCommand, self.QMakeOptions], exitOnFailure=True)
|
|
runProgram([self.MakeCommand, self.MakeTarget], exitOnFailure=True)
|
|
|
|
def cleanEverything(self):
|
|
self.generateAPI(["--clean"])
|
|
runProgram([self.MakeCommand, "distclean"], exitOnFailure=False)
|
|
filesToRemove = [self.ProInclude, "typesystem_ScintillaEdit.xml",
|
|
"../../bin/ScintillaEditPy.so", "../../bin/ScintillaConstants.py"]
|
|
for file in filesToRemove:
|
|
try:
|
|
os.remove(file)
|
|
except OSError:
|
|
pass
|
|
for logFile in glob.glob("*.log"):
|
|
try:
|
|
os.remove(logFile)
|
|
except OSError:
|
|
pass
|
|
shutil.rmtree("debug", ignore_errors=True)
|
|
shutil.rmtree("release", ignore_errors=True)
|
|
shutil.rmtree("ScintillaEditPy", ignore_errors=True)
|
|
|
|
def buildEverything(self):
|
|
cleanGenerated = False
|
|
opts, args = getopt.getopt(sys.argv[1:], "hcdub",
|
|
["help", "clean", "debug=",
|
|
"underscore-names", "pyside-base="])
|
|
for opt, arg in opts:
|
|
if opt in ("-h", "--help"):
|
|
usage()
|
|
sys.exit()
|
|
elif opt in ("-c", "--clean"):
|
|
cleanGenerated = True
|
|
elif opt in ("-d", "--debug"):
|
|
self.DebugBuild = (arg == '' or arg.lower() == 'yes')
|
|
if self.DebugBuild and sys.platform == 'win32':
|
|
self.MakeTarget = 'debug'
|
|
elif opt in ("-b", '--pyside-base'):
|
|
self._SetQtIncludeBase(os.path.join(os.path.normpath(arg), 'include'))
|
|
self._setPySideBase(os.path.normpath(arg))
|
|
elif opt in ("-u", "--underscore-names"):
|
|
self.qtStyleInterface = False
|
|
|
|
if cleanGenerated:
|
|
self.cleanEverything()
|
|
else:
|
|
self.writeVariables()
|
|
self.generateAPI([""])
|
|
self.runGenerator()
|
|
self.make()
|
|
self.copyScintillaConstants()
|
|
|
|
def copyScintillaConstants(self):
|
|
|
|
orig = 'ScintillaConstants.py'
|
|
dest = '../../bin/' + orig
|
|
if IsFileNewer(dest, orig):
|
|
return
|
|
|
|
f = open(orig, 'r')
|
|
contents = f.read()
|
|
f.close()
|
|
|
|
f = open(dest, 'w')
|
|
f.write(contents)
|
|
f.close()
|
|
|
|
def _SetQtIncludeBase(self, base):
|
|
|
|
self.QtIncludeBase = base
|
|
self.QtIncludes = [self.QtIncludeBase] + [os.path.join(self.QtIncludeBase, sub) for sub in ["QtCore", "QtGui"]]
|
|
# Set path so correct qmake is found
|
|
path = os.environ.get('PATH', '').split(os.pathsep)
|
|
qt_bin_dir = os.path.join(os.path.dirname(base), 'bin')
|
|
if qt_bin_dir not in path:
|
|
path.insert(0, qt_bin_dir)
|
|
os.environ['PATH'] = os.pathsep.join(path)
|
|
|
|
if __name__ == "__main__":
|
|
sepBuild = SepBuilder()
|
|
sepBuild.buildEverything()
|