diff options
Diffstat (limited to 'build.py')
-rw-r--r-- | build.py | 730 |
1 files changed, 730 insertions, 0 deletions
diff --git a/build.py b/build.py new file mode 100644 index 0000000..11e4a0c --- /dev/null +++ b/build.py @@ -0,0 +1,730 @@ +# Copyright (c) 2007 +# Riverbank Computing Limited <info@riverbankcomputing.co.uk> +# +# This file is part of PyQt. +# +# This copy of PyQt is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the Free +# Software Foundation; either version 2, or (at your option) any later +# version. +# +# PyQt is supplied in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +# details. +# +# You should have received a copy of the GNU General Public License along with +# PyQt; see the file LICENSE. If not, write to the Free Software Foundation, +# Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +# This is the build script for PyQt. It should be run in the top level +# directory of the source distribution and by the Python interpreter for which +# it is being built. It uses either qmake or tmake to do the hard work of +# generating the platform specific Makefiles. + + +import sys +import os +import glob +import tempfile +import shutil +import py_compile +import compileall +import string + + +# Get the SIP configuration. +try: + import sipconfig +except: + print "Unable to import the sipconfig module. Please make sure you have" + print "SIP v3.9 or later installed." + sys.exit(1) + +config = sipconfig.SIPConfig("PyQt 3.18.1") + + +# Initialise the globals. +sipMajorVersion = config.sip_version >> 16 +sciIncDir = config.qt_inc_dir +sciLibDir = config.qt_lib_dir +sciLib = None +sciVersion = None +binDir = config.default_bin_dir +modDir = config.default_mod_dir +sipDir = config.default_sip_dir +buildModules = ["qt"] +tempBuildDir = None +catCppFiles = 0 +catSplit = 1 +qpeTag = None +trace = 0 +releaseGIL = 0 + + +def usage(rcode = 2): + """Display a usage message and exit. + + rcode is the return code passed back to the calling process. + """ + print "Usage:" + print " %s [-h] [-a version] [-b dir] [-c] [-d dir] [-g] [-j #] [-n dir] [-o dir] [-r] [-v dir]" % sipconfig.script() + print "where:" + print " -h display this help message" + print " -a tag explicitly enable the qtpe module" + print " -b dir where pyuic and pylupdate will be installed [default %s]" % config.default_bin_dir + print " -c concatenate each module's C++ source files" + print " -d dir where the PyQt modules will be installed [default %s]" % config.default_mod_dir + print " -g always release the GIL (SIP v3.x behaviour)" + print " -j # split the concatenated C++ source files into # pieces [default 1]" + print " -n dir the directory containing the QScintilla header files [default %s]" % config.qt_inc_dir + print " -o dir the directory containing the QScintilla library [default %s]" % config.qt_lib_dir + print " -r generate code with tracing enabled [default disabled]" + print " -v dir where the PyQt .sip files will be installed [default %s]" % config.default_sip_dir + + sys.exit(rcode) + + +def mkTempBuildDir(olddir=None): + """Create a temporary build directory for a console application called + qttest, complete with patched Makefile. The global tempBuildDir is set to + the name of the directory. The temporary directory becomes the current + directory. + + olddir is None if the directory should be created, otherwise it is deleted. + + Returns the name of the previous current directory. + """ + global tempBuildDir + + if olddir is None: + tempBuildDir = tempfile.mktemp() + + try: + os.mkdir(tempBuildDir) + except: + sipconfig.error("Unable to create temporary directory.") + + prevdir = sipconfig.push_dir(tempBuildDir) + + sipconfig.copy_to_file("qttest.pro.in", +"""TEMPLATE = app +TARGET = qttest +CONFIG += console warn_off @TEST_OPENGL@ @BLX_CONFIG_APP@ +INCLUDEPATH = @BLX_INCLUDEPATH@ @TEST_QSCINTILLA_INC@ +DEFINES = @BLX_DEFINES@ +SOURCES = qttest.cpp +LIBS += @TEST_QUI_LIB@ @TEST_QSCINTILLA_LIB@ +""") + + # Disable OpenGL, qui and QScintilla support by default. + config.patches["@TEST_OPENGL@"] = "" + config.patches["@TEST_QUI_LIB@"] = "" + config.patches["@TEST_QSCINTILLA_INC@"] = "" + config.patches["@TEST_QSCINTILLA_LIB@"] = "" + + # Create a dummy source file to suppress a qmake warning. + sipconfig.copy_to_file("qttest.cpp", "") + + config.create_makefile("qttest.pro") + else: + sipconfig.pop_dir(olddir) + prevdir = None + + shutil.rmtree(tempBuildDir, 1) + + return prevdir + + +def tryModule(maindir, mname, incfile, ctor): + """See if a PyQt module should be built and update the buildModules list + accordingly. + + maindir is the directory containing this script. + mname is the name of the PyQt module. + incfile is the C++ header file that defines the class being used for the + test. + ctor is the constructor of the class being used for the test. + """ + # Check for the existence of the module .sip file. + msip = os.path.join(maindir, "sip", mname, mname + "mod.sip") + + if not os.access(msip, os.F_OK): + return + + sipconfig.copy_to_file("qttest.cpp", +"""#include <%s> + +int main(int argc,char **argv) +{ + new %s; +} +""" % (incfile, ctor)) + + if sipconfig.run_make(None,0) == 0: + buildModules.append(mname) + sipconfig.inform("The %s module will be built." % mname) + else: + sipconfig.inform("The %s module will not be built." % mname) + + sipconfig.run_make("clean") + + +def checkQScintilla(): + """See if QScintilla can be found and what its version is. + """ + # Find the QScintilla header files. + sciglobal = os.path.join(sciIncDir, "qextscintillaglobal.h") + + if os.access(sciglobal,os.F_OK): + config.patches["@PYQT_QSCINTILLA_INC@"] = sciIncDir + + sipconfig.inform("%s contains qextscintillaglobal.h." % (sciIncDir)) + + # Get the QScintilla version number. + global sciVersion + + sciVersion, sciversstr = sipconfig.read_version(sciglobal, "QScintilla", "QSCINTILLA_VERSION", "QSCINTILLA_VERSION_STR") + + sipconfig.inform("QScintilla %s is being used." % (sciversstr)) + + # If we find a snapshot then set the version number to 0 as a special + # case. + if sciversstr[:8] == "snapshot": + sciVersion = 0 + + # Find the QScintilla library. + if sys.platform == "win32": + lpatt = "qscintilla.lib" + else: + lpatt = "libqscintilla.*" + + if len(glob.glob(os.path.join(sciLibDir, lpatt))): + sipconfig.inform("%s contains the QScintilla library." % sciLibDir) + + global sciLib + + if sys.platform == "win32": + sciLib = sipconfig.escape(os.path.join(sciLibDir, "qscintilla.lib")) + else: + sciLib = sipconfig.escape("-L" + sciLibDir) + " -lqscintilla" + + config.patches["@PYQT_QSCINTILLA_LIB@"] = sciLib + else: + sipconfig.inform("The QScintilla library could not be found in %s and so the qtext module will not be built. If QScintilla is installed then use the -o argument to explicitly specify the correct directory." % (sciLibDir)) + + sciVersion = -1 + else: + sipconfig.inform("qextscintillaglobal.h could not be found in %s and so the qtext module will not be built. If QScintilla is installed then use the -n argument to explicitly specify the correct directory." % sciIncDir) + + sciVersion = -1 + + +def moduleChecks(maindir): + """See which PyQt modules to build. + """ + sipconfig.inform("Checking which additional PyQt modules to build."); + + tryModule(maindir,"qtcanvas", "qcanvas.h", "QCanvas()") + tryModule(maindir,"qtnetwork", "qsocket.h", "QSocket()") + tryModule(maindir,"qttable", "qtable.h", "QTable()") + tryModule(maindir,"qtxml", "qdom.h", "QDomImplementation()") + + if config.qt_version >= 0x030000: + tryModule(maindir,"qtsql", "qsql.h", "QSql()") + + # We need a different Makefile for the qtgl module. + config.patches["@TEST_OPENGL@"] = "opengl" + config.create_makefile("qttest.pro") + + tryModule(maindir,"qtgl", "qgl.h", "QGLWidget()") + + # Put things back. + config.patches["@TEST_OPENGL@"] = "" + config.create_makefile("qttest.pro") + + # Check for the qui library. + if config.qt_version >= 0x030000: + if sys.platform == "win32": + quilib = r"$(QTDIR)\lib\qui.lib" + else: + quilib = "-lqui" + + config.patches["@PYQT_QUI_LIB@"] = quilib + + config.patches["@TEST_QUI_LIB@"] = quilib + config.create_makefile("qttest.pro") + + tryModule(maindir,"qtui", "qwidgetfactory.h", "QWidgetFactory()") + + # Put things back. + config.patches["@TEST_QUI_LIB@"] = "" + config.create_makefile("qttest.pro") + + # Check for the QScintilla library. + if sciVersion >= 0: + config.patches["@TEST_QSCINTILLA_INC@"] = sciIncDir + config.patches["@TEST_QSCINTILLA_LIB@"] = sciLib + config.create_makefile("qttest.pro") + + tryModule(maindir,"qtext", "qextscintillabase.h", "QextScintillaBase()") + + # Put things back. + config.patches["@TEST_QSCINTILLA_INC@"] = "" + config.patches["@TEST_QSCINTILLA_LIB@"] = "" + config.create_makefile("qttest.pro") + + +def generateFeatures(featfile): + """Generate the header file describing the Qt features that are enabled if + it doesn't already exist. (If it already exists then we are probably cross + compiling and generated the file through other means.) + + featfile is the name of the features file. + """ + if os.access(featfile,os.F_OK): + sipconfig.inform("Using existing features file.") + return + + sipconfig.inform("Generating the features file.") + + # The features that a given Qt configuration may or may not support. Note + # that STYLE_WINDOWSXP requires special handling. + flist = ["ACTION", "CLIPBOARD", "CODECS", "COLORDIALOG", "DATASTREAM", + "DIAL", "DNS", "DOM", "DRAGANDDROP", "ICONVIEW", "IMAGE_TEXT", + "INPUTDIALOG", "FILEDIALOG", "FONTDATABASE", "FONTDIALOG", + "MESSAGEBOX", "MIMECLIPBOARD", "NETWORKPROTOCOL", "PICTURE", + "PRINTDIALOG", "PRINTER", "PROGRESSDIALOG", "PROPERTIES", + "SEMIMODAL", "SIZEGRIP", "SOUND", "SPLITTER", "STYLE_CDE", + "STYLE_INTERLACE", "STYLE_MOTIF", "STYLE_MOTIFPLUS", + "STYLE_PLATINUM", "STYLE_SGI", "STYLE_WINDOWS", "TABDIALOG", + "TABLE", "TABLEVIEW", "TRANSFORMATIONS", "TRANSLATION", "WIZARD", + "WORKSPACE"] + + # Generate the program which will generate the features file. + f = open("qttest.cpp","w") + + # Escape the backslashes so that the name can be embedded in a C++ string. + ffstr = string.replace(featfile, "\\", "\\\\") + + f.write( +"""#include <stdio.h> +#include <qglobal.h> +#include <qapplication.h> + +int main(int argc,char **argv) +{ + FILE *fp; + QApplication app(argc,argv,0); + + if ((fp = fopen("%s","w")) == NULL) + { + printf("Unable to create '%s'\\n"); + return 1; + } + +#if !defined(QT_THREAD_SUPPORT) + fprintf(fp,"-x Qt_THREAD_SUPPORT\\n"); +#endif + +#if (defined(Q_OS_WIN32) || defined(Q_OS_WIN64)) && QT_VERSION >= 0x030000 + if (qWinVersion() != Qt::WV_XP) + fprintf(fp,"-x Qt_STYLE_WINDOWSXP\\n"); +#endif +""" % (ffstr, ffstr)) + + for feat in flist: + f.write( +""" +#if defined(QT_NO_%s) + fprintf(fp,"-x Qt_%s\\n"); +#endif +""" % (feat, feat)) + + f.write( +""" + fclose(fp); + + return 0; +} +""") + + f.close() + + sipconfig.run_make() + sipconfig.run_program(os.path.join(os.getcwd(), "qttest")) + sipconfig.run_make("clean") + + sipconfig.inform("Generated the features file.") + + +def generateSource(mname, plattag, qttag, xtrtag): + """Generate the C++ source code for a particular PyQt module. + + mname is the name of the module. + plattag is the SIP tag for the platform. + qttag is the SIP tag for the Qt version. + xtrtag is an optional extra SIP tag. + """ + sipconfig.inform("Generating the C++ source for the %s module." % mname) + + try: + shutil.rmtree(mname) + except: + pass + + try: + os.mkdir(mname) + except: + sipconfig.error("Unable to create the %s directory." % mname) + + pro = mname + ".pro" + + argv = ["-t", plattag, + "-t", qttag, + "-z", "features", + "-I", "sip", + "-m", mname + "/" + pro + ".in", + "-c", mname, + "sip/" + mname + "/" + mname + "mod.sip"] + + if xtrtag: + argv.insert(0,xtrtag) + argv.insert(0,"-t") + + if trace: + argv.insert(0,"-r") + + if releaseGIL: + argv.insert(0,"-g") + + sipconfig.run_program(config.sip_bin, argv) + + # Generate the Makefile. + sipconfig.inform("Generating the Makefile for the %s module." % mname) + + olddir = sipconfig.push_dir(mname) + + if catCppFiles: + sipconfig.cat_source_files(mname, catSplit) + + config.create_makefile(pro, mname) + + icmds = [] + + if sipMajorVersion == 3: + icmds.append(("copy", mname + ".py", modDir)) + icmds.append(("copy", mname + ".pyc", modDir)) + + config.add_install_target(icmds) + + if sipMajorVersion == 3: + # Compile the Python part of the module. + pyname = mname + ".py" + + sipconfig.inform("Compiling %s." % (pyname)) + py_compile.compile(pyname) + + sipconfig.pop_dir(olddir) + + +def versionToTag(vers, tags, desc): + """Convert a version number to a tag. + + vers is the version number. + tags is the dictionary of tags keyed by version number. + desc is the descriptive name of the package. + + Returns the corresponding tag. + """ + tag = None + + vl = tags.keys() + vl.sort() + + # For a snapshot use the latest tag. + if vers == 0: + tag = tags[vl[-1]] + else: + for v in vl: + if vers < v: + tag = tags[v] + break + + if tag is None: + sipconfig.error("Invalid %s version: 0x%06x." % (desc, vers)) + + return tag + + +def main(argv): + """The main function of the script. + + argv is the list of command line arguments. + """ + import getopt + + # Parse the command line. + try: + optlist, args = getopt.getopt(argv[1:],"ha:b:cd:gj:n:o:rv:") + except getopt.GetoptError: + usage() + + for opt, arg in optlist: + if opt == "-h": + usage(0) + elif opt == "-a": + global qpeTag + qpeTag = arg + elif opt == "-b": + global binDir + binDir = arg + elif opt == "-c": + global catCppFiles + catCppFiles = 1 + elif opt == "-d": + global modDir + modDir = arg + elif opt == "-g": + global releaseGIL + releaseGIL = 1 + elif opt == "-j": + global catSplit + + try: + catSplit = int(arg) + except: + catSplit = 0 + + if catSplit < 1: + usage() + elif opt == "-n": + global sciIncDir + sciIncDir = arg + elif opt == "-o": + global sciLibDir + sciLibDir = arg + elif opt == "-r": + global trace + trace = 1 + elif opt == "-v": + global sipDir + sipDir = arg + + # Confirm the license. + sipconfig.confirm_license() + + # If there should be a license file then check it is where it should be. + if config.license_file: + if os.access(os.path.join("sip", config.license_file), os.F_OK): + sipconfig.inform("Found the license file %s.\n" % config.license_file) + else: + sipconfig.error("Please copy the license file %s to the sip directory.\n" % config.license_file) + + # Check the Qt version. + if config.qt_version == 0: + sipconfig.error("SIP has been built with Qt support disabled.\n") + + # Early versions of Qt for the Mac didn't include everything. Rather than + # maintain these in the future we just mandate a later version. + if sys.platform == "darwin" and config.qt_version < 0x030100: + sipconfig.error("PyQt for MacOS/X requires Qt v3.1.0 or later.\n") + + # Check the installation directory is valid and add it as a patch. + if not os.access(modDir,os.F_OK): + sipconfig.error("The %s PyQt destination directory does not seem to exist. Use the -d argument to set the correct directory." % (modDir)) + + config.patches["@PYQT_MODDIR@"] = sipconfig.escape(modDir) + + sipconfig.inform("%s is the PyQt installation directory." % (modDir)) + + # Enable warnings for SIP v4 generated code. + if sipMajorVersion >= 4: + warn = "warn_on" + else: + warn = "warn_off" + + config.patches["@PYQT_WARN@"] = warn + + # Create patches to allow some modules to link against others. + if sipMajorVersion >= 4: + modlink = "" + elif sys.platform == "win32": + modlink = sipconfig.escape(os.path.join(modDir, "libqtc.lib")) + else: + modlink = sipconfig.escape("-L" + modDir) + " -lqtcmodule" + + config.patches["@PYQT_QT_MODULE@"] = modlink + + if sipMajorVersion >= 4: + modlink = "" + elif sys.platform == "win32": + modlink = sipconfig.escape(os.path.join(modDir, "libqttablec.lib")) + " " + sipconfig.escape(os.path.join(modDir, "libqtc.lib")) + else: + modlink = sipconfig.escape("-L" + modDir) + " -lqttablecmodule -lqtcmodule" + + config.patches["@PYQT_QTTABLE_MODULE@"] = modlink + + # The professional edition needs special handling if XML support is needed. + if config.qt_edition == "professional": + rbprof = "rbprof" + else: + rbprof = "" + + config.patches["@PYQT_RBPROF@"] = rbprof + + # Link in the qassistantclient library for Qt v3.1+. + qaclib = "" + + if config.qt_version >= 0x030100: + if sys.platform == "win32": + qaclib = r"$(QTDIR)\lib\qassistantclient.lib" + else: + qaclib = "-lqassistantclient" + + config.patches["@PYQT_QASSISTANTCLIENT_LIB@"] = qaclib + + # Check for QScintilla. + if config.qt_version >= 0x030000: + checkQScintilla() + + # Create a build directory that we can compile test programs. + maindir = mkTempBuildDir() + + # Check what additional modules to build. + if config.qt_version >= 0x020000: + moduleChecks(maindir) + + # Work out the platform and Qt version tags to pass to SIP to generate the + # code we need. + if config.qt_lib == "qte": + plattag = "WS_QWS" + elif sys.platform == "win32": + plattag = "WS_WIN" + elif sys.platform == "darwin": + plattag = "WS_MACX" + else: + plattag = "WS_X11" + + qttags = { + 0x020000: "Qt_1_43", + 0x020100: "Qt_2_00", + 0x020200: "Qt_2_1_0", + 0x020300: "Qt_2_2_0", + 0x020301: "Qt_2_3_0", + 0x030000: "Qt_2_3_1", + 0x030001: "Qt_3_0_0", + 0x030002: "Qt_3_0_1", + 0x030004: "Qt_3_0_2", + 0x030005: "Qt_3_0_4", + 0x030006: "Qt_3_0_5", + 0x030100: "Qt_3_0_6", + 0x030101: "Qt_3_1_0", + 0x030102: "Qt_3_1_1", + 0x030200: "Qt_3_1_2", + 0x030300: "Qt_3_2_0", + 0x040000: "Qt_3_3_0" + } + + qttag = versionToTag(config.qt_version, qttags, "Qt") + + # Work out the QScintilla tag. + if sciVersion >= 0: + scitags = { + 0x010100: "QScintilla_1_0", + 0x010200: "QScintilla_1_1", + 0x020000: "QScintilla_1_2" + } + + scitag = versionToTag(sciVersion, scitags, "QScintilla") + else: + scitag = None + + # Generate the features file. + generateFeatures(os.path.join(maindir, "features")) + + # We don't need the temporary build directory anymore. + mkTempBuildDir(maindir) + + subdirs = [] + for mname in buildModules: + if mname == "qtext": + xtratag = scitag + else: + xtratag = None + + generateSource(mname, plattag, qttag, xtratag) + subdirs.append(mname) + + # We handle the qtpe module explicitly rather than auto-detect. This is + # because it does things a bit differently and I'm too lazy to deal with it + # properly at the moment. + if qpeTag: + generateSource("qtpe", plattag, qttag, qpeTag) + subdirs.append("qtpe") + + # Install the .sip files. + sipconfig.inform("Creating Makefile for .sip files.") + olddir = sipconfig.push_dir("sip") + sipconfig.copy_to_file("Makefile", "all:\n") + + icmds = [] + + for mname in buildModules: + dstdir = os.path.join(sipDir, mname) + + icmds.append(("mkdir", dstdir, None)) + + for sf in os.listdir(os.path.join(olddir, "sip", mname)): + icmds.append(("copy", os.path.join(mname, sf), os.path.join(dstdir, sf))) + + config.add_install_target(icmds) + sipconfig.pop_dir(olddir) + subdirs.append("sip") + + # See which version of pyuic to build. + config.patches["@PYQT_BINDIR@"] = sipconfig.escape(binDir) + + if config.qt_version >= 0x030000: + sipconfig.inform("Creating Makefile for pyuic3.") + subdirs.append("pyuic3") + olddir = sipconfig.push_dir("pyuic3") + elif config.qt_version >= 0x020000: + sipconfig.inform("Creating Makefile for pyuic2.") + subdirs.append("pyuic2") + olddir = sipconfig.push_dir("pyuic2") + + config.create_makefile("pyuic.pro", []) + sipconfig.pop_dir(olddir) + + # Build pylupdate if Qt v3.0 or later. + if config.qt_version >= 0x030000: + sipconfig.inform("Creating Makefile for pylupdate3.") + subdirs.append("pylupdate3") + olddir = sipconfig.push_dir("pylupdate3") + + config.create_makefile("pylupdate.pro", []) + sipconfig.pop_dir(olddir) + + # Generate the top-level Makefile. + sipconfig.inform("Creating top level Makefile.") + sipconfig.copy_to_file("PyQt.pro.in", "TEMPLATE = subdirs\nSUBDIRS = " + string.join(subdirs) + "\n") + config.create_makefile("PyQt.pro") + + # Tell the user what to do next. + msg = "The build of the PyQt source code for your system is now complete. To compile and install PyQt run \"%s\" and \"%s install\" with appropriate user privileges." % (config.make_bin, config.make_bin) + + sipconfig.inform(msg) + + +if __name__ == "__main__": + try: + main(sys.argv) + except SystemExit: + raise + except: + print \ +"""An internal error occured. Please report all the output from the program, +including the following traceback, to support@riverbankcomputing.co.uk. +""" + raise |