# Copyright (c) 2003
#   Riverbank Computing Limited <info@riverbankcomputing.co.uk>
#
# This is the build script for SIP.  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 glob
import tempfile
import shutil
import py_compile
import string


# Define the globals.
qtDir = None
qtIncDir = None
qtLibDir = None
qtVersion = 0
qtLib = None
platPySiteDir = None
platPyIncDir = None
platPyLibDir = None
platPyLib = None
platBinDir = None
modDir = None
sipIncDir = None
sipBinDir = None
sipSipDir = None
platMake = None
platCopy = None
platQTDIRName = None
pyFullVers = None
pyVersNr = None
makefileGen = None
makeBin = None
proPatches = {}
makefilePatches = {}
tempBuildDir = None
usingTmake = 0
enableOldPython = 0
debugMode = "release"
licType = None
gccFlags = []
blx_config = ["dll"]


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] [-b dir] [-d dir] [-e dir] [-f gccflag] [-g prog] [-i dir] [-l Qt-library] [-m prog] [-p dir] [-q dir] [-r dir] [-t dir] [-u] [-v dir] [-w] [-x]" % (script())
    print "where:"
    print "    -h             display this help message"
    print "    -b dir         where the SIP code generator will be installed [default %s]" % (platBinDir)
    print "    -d dir         where the SIP module will be installed [default %s]" % (modDir)
    print "    -e dir         where the SIP header files will be installed [default %s]" % (sipIncDir)
    print "    -f gccflag     additional GCC flag, eg. -fno-exceptions"
    print "    -g prog        the name of the Makefile generator"
    print "    -i dir         the directory containing the Qt header files [default %s%sinclude]" % (platQTDIRName,os.sep)
    print "    -l Qt-library  explicitly specify the type of Qt library, either qt, qt-mt, qt-mtedu or qte"
    print "    -m prog        the name of the Make program [default %s]" % (platMake)
    print "    -q dir         the Qt base directory [default %s]" % (platQTDIRName)
    print "    -r dir         the directory containing the Qt library [default %s%slib]" % (platQTDIRName,os.sep)
    print "    -t dir         the directory containing the Python library directories [default %s]" % (platPyLibDir)
    print "    -u             build with debugging symbols"
    print "    -v dir         where .sip files are normally installed [default %s]" % (sipSipDir)
    print "    -w             enable the use of Python 1.5.x under Windows"
    print "    -x             disable Qt support"

    sys.exit(rcode)


#<BootstrapCode>
import sys
import os
import types
import re
import time


_script = None


def script():
    """Return the basename of the build/installation script.
    """
    return _script


def _set_script():
    """Internal: Remember the basename of the build/install script and check
    it is in the current directory.
    """
    global _script

    # Only check once.
    if _script is None:
        _script = os.path.basename(sys.argv[0])

        # Check we are in the top level directory.
        if not os.access(_script,os.F_OK):
            error("This program must be run from the top level directory of the package, ie. the directory containing this program.")


def _patch_file(name,pdict):
    """Internal: Apply the patches to a file.

    name is the name of the file.
    pdict is the patch dictionary.
    """
    fi = open(name + ".in","r")
    fo = open(name,"w")

    for line in fi.readlines():
        # Patches come in two forms.  In the first, the key is the pattern to
        # be replaced and the value is the replacement text.  In the second,
        # the key is a unique identifier of the patch and the value is a two
        # element list.  The first element is the compiled pattern, and the
        # second element is the replacement text.  When we find the first type
        # we convert it to the second type.
        for p in pdict.keys():
            v = pdict[p]

            if type(v) == types.ListType:
                pattern, repl = v
            else:
                pattern = re.compile(p,re.M)
                repl = v
                pdict[p] = [pattern, repl]

            line = re.sub(pattern,repl,line)

        fo.write(line)
    
    fi.close()
    fo.close()


def _create_config(cfgfile,pdict):
    """Internal: Create a configuration module based on a template file and
    bootstrap code in this file.

    cfgfile is the name of the file to create.
    pdict is the patch dictionary.
    """
    inform("Creating the %s configuration module." % (cfgfile))
    _patch_file(cfgfile,pdict)

    # Append the bootstrap code from this file.
    fi = open(_script,"r")
    fo = open(cfgfile,"a")

    bootcode = 0

    bootstart = "#<BootstrapCode>"
    bootend = "#</BootstrapCode>"

    for line in fi.readlines():
        if bootcode:
            if line[:len(bootend)] == bootend:
                bootcode = 0
            else:
                fo.write(line)
        else:
            if line[:len(bootstart)] == bootstart:
                bootcode = 1
                fo.write("\n\n")

    fi.close()
    fo.close()


def _create_makefile(profile,mfg,pdict,icmds = None):
    """Internal: Create a Makefile based on a project file template.

    profile is the name of the project file template without the trailing
    ".in".
    mfg is the Makefile generator.
    pdict is the patch dictionary.
    icmds is the list of install commands.
    """
    _patch_file(profile,pdict)

    # Make sure that the Makefile has a later timestamp than the project file.
    time.sleep(1)

    runProgram(mfg,['-o', 'Makefile.in', profile])

    _patch_file("Makefile",makefilePatches)


def _format(mess,delin = 1):
    """Internal: Format a message by inserting line breaks at appropriate
    places.

    mess is the text of the message.
    delin is non-zero if the text should be deliniated.

    Return the formatted message.
    """
    margin = 78
    curs = 0

    if delin:
        msg = "*" * margin + "\n"
    else:
        msg = ""

    for w in string.split(mess):
        l = len(w)
        if curs != 0 and curs + l > margin:
            msg = msg + "\n"
            curs = 0

        if curs > 0:
            msg = msg + " "
            curs = curs + 1

        msg = msg + w
        curs = curs + l

    msg = msg + "\n"

    if delin:
        msg = msg + ("*" * margin) + "\n"

    return msg


def error(mess):
    """Display an error message and exit.

    mess is the text of the message.
    """
    sys.stderr.write(_format("Error: %s" % (mess)))
    sys.exit(1)


def inform(mess,delin = 1):
    """Display an information message.

    mess is the text of the message.
    delin is non-zero if the text should be deliniated.
    """
    sys.stdout.write(_format(mess,delin))
#</BootstrapCode>


def initGlobals():
    """Sets the values of globals that need more than a simple assignment.
    This is called before the command line is parsed to set defaults for the
    help, and afterwards to reflect the command line.
    """
    global pyFullVers, pyVersNr

    pyFullVers = string.split(sys.version)[0]

    vl = string.split(re.findall("[0-9.]*",pyFullVers)[0],".")
    major = vl[0]
    minor = vl[1]

    try:
        bug = vl[2]
    except IndexError:
        bug = 0

    pyVers = major + "." + minor
    pyVersNr = (int(major) << 16) + (int(minor) << 8) + int(bug)

    global platMake, platCopy, platPySiteDir, platPyIncDir, platPyLib
    global platQTDIRName, platBinDir, platPyLibDir

    if sys.platform == "win32":
        if platMake is None:
            platMake = "nmake"

        platCopy = "copy /y"

        if platPyLibDir is None:
            platPyLibDir = sys.prefix

        if pyVersNr < 0x020200:
            platPySiteDir = platPyLibDir + "\\Lib"
        else:
            platPySiteDir = platPyLibDir + "\\Lib\\site-packages"

        platPyIncDir = sys.prefix + "\\include"
        platQTDIRName = "%QTDIR%"

        if platBinDir is None:
            platBinDir = sys.exec_prefix

        global debugMode

        if debugMode == "debug":
            dbg = "_d"
        else:
            dbg = ""

        platPyLib = platPyLibDir + "\\libs\\python%s%s%s.lib" % (major,minor,dbg)

        platSipDir = sys.prefix + "\\sip"
    else:
        if platMake is None:
            platMake = "make"

        platCopy = "cp"

        if platPyLibDir is None:
            platPyLibDir = sys.prefix + "/lib/python" + pyVers

        if pyVersNr < 0x020000:
            platPySiteDir = platPyLibDir
        else:
            platPySiteDir = platPyLibDir + "/site-packages"

        platPyIncDir = sys.prefix + "/include/python" + pyVers
        platQTDIRName = "$QTDIR"

        if platBinDir is None:
            platBinDir = "/usr/local/bin"

        platSipDir = "/usr/local/share/sip"

    global modDir, sipIncDir, sipBinDir, sipSipDir

    if modDir is None:
        modDir = platPySiteDir

    if sipIncDir is None:
        sipIncDir = platPyIncDir

    if sipBinDir is None:
        sipBinDir = platBinDir

    if sipSipDir is None:
        sipSipDir = platSipDir


def escape(s):
    """Escape all the backslashes (usually Windows directory separators) in a
    string so that they don't confuse regular expression substitutions.  Also
    enclose in double quotes if the string contains spaces.

    s is the string to escape.

    Returns the escaped string.
    """
    s = string.replace(s,"\\","\\\\")

    if string.find(s," ") >= 0:
        s = "\"" + s + "\""

    return s


def isProgram(f):
    """Return non-zero if a filename refers to an executable file.
    """
    return os.access(f,os.X_OK) and not os.path.isdir(f)


def searchPath(prog,xtradir = None):
    """Search the PATH for a program and return it's absolute pathname.

    prog is the name of the program.
    xtradir is the name of an optional extra directory to search as well.
    """
    if sys.platform == "win32":
        prog = prog + ".exe"

    # Create the list of directories to search.
    try:
        path = os.environ["PATH"]
    except KeyError:
        path = os.defpath

    dlist = string.split(path,os.pathsep)

    if xtradir:
        dlist.insert(0,xtradir)

    # Create the list of files to check.
    flist = []

    for d in dlist:
        flist.append(os.path.abspath(os.path.join(d,prog)))

    # Search for the file.
    for f in flist:
        if isProgram(f):
            return f

    # Nothing found.
    return None


def isQtLibrary(dir,lib):
    """See if a Qt library appears in a particular directory.

    dir is the name of the directory.
    lib is the name of the library.

    Returns the basename of the library file that was found or None if nothing
    was found.
    """
    if sys.platform == "win32":
        lpatt = "\\" + lib + "[0-9]*.lib"
    else:
        lpatt = "/lib" + lib + ".*"

    l = glob.glob(dir + lpatt)

    if len(l) == 0:
        return None

    return os.path.basename(l[0])


def copyToFile(name,text):
    """Copy a string to a file.

    name is the name of the file.
    text is the contents to copy to the file.
    """
    f = open(name,"w")
    f.write(text)
    f.close()


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:
            error("Unable to create temporary directory.")

        newdir = pushDir(tempBuildDir)

        copyToFile("qttest.pro.in",
"""TEMPLATE = app
TARGET = qttest
CONFIG += @BLX_CONFIG@ console warn_off
INCLUDEPATH = @BL_INCLUDEPATH@
DEFINES = @BL_DEFINES@
SOURCES = qttest.cpp
""")

        # Create a dummy source file to suppress a qmake warning.
        copyToFile("qttest.cpp","")

        buildMakefile("qttest.pro")
    else:
        popDir(olddir)
        newdir = None

        shutil.rmtree(tempBuildDir,1)

    return newdir


def checkQtDirAndVersion():
    """Check that Qt can be found and what its version is.
    """
    global qtVersion, qtDir

    if qtVersion < 0:
        return

    if qtDir is None:
        try:
            qtDir = os.environ["QTDIR"]
        except KeyError:
            error("Please set the name of the Qt base directory, either by using the -q argument or setting the QTDIR environment variable.")
    else:
        os.environ["QTDIR"] = qtDir

    # For lib template project files.
    makefilePatches["QTDIR_LIB"] = [re.compile("(^CC)",re.M), "QTDIR    = " + escape(qtDir) + "\n\\1"]

    # For subdir template project files.
    makefilePatches["QTDIR_SUBDIR"] = [re.compile("(^MAKEFILE)",re.M), "QTDIR    = " + escape(qtDir) + "\n\\1"]

    if not os.access(qtDir,os.F_OK):
        error("The %s Qt base directory does not seem to exist. Use the -q argument or the QTDIR environment variable to set the correct directory or use the -x argument to disable Qt support." % (qtDir))

    inform("%s is the Qt base directory." % (qtDir))

    # Check that the Qt header files have been installed.
    global qtIncDir

    if qtIncDir:
        qtincdir = qtIncDir
    else:
        qtincdir = qtDir + os.sep + "include"

    qglobal = qtincdir + os.sep + "qglobal.h"

    if not os.access(qglobal,os.F_OK):
        error("qglobal.h could not be found in %s. Use the -i argument to explicitly specify the correct directory." % (qtincdir))

    inform("%s contains qglobal.h." % (qtincdir))

    # Get the Qt version number.
    qtversstr = ""
    f = open(qglobal)
    l = f.readline()

    while l:
        wl = string.split(l)
        if len(wl) == 3 and wl[0] == "#define":
            if wl[1] == "QT_VERSION":
                qtv = wl[2]

                if qtv[0:2] == "0x":
                    qtVersion = string.atoi(qtv,16)
                else:
                    qtVersion = int(qtv)
                    maj = qtVersion / 100
                    min = (qtVersion % 100) / 10
                    bug = qtVersion % 10
                    qtVersion = (maj << 16) + (min << 8) + bug

            if wl[1] == "QT_VERSION_STR":
                qtversstr = wl[2][1:-1]

            if qtversstr != "" and qtVersion > 0:
                break

        l = f.readline()

    f.close()

    if qtversstr == "":
        error("The Qt version number could not be determined by parsing %s." % (qglobal))

    # Prevent the Makefiles recreating themselves.  Only seems to affect Qt
    # v3.1.0 and later.
    if qtVersion >= 0x030100:
        makefilePatches["FORCE"] = [re.compile(" FORCE",re.M), ""]

    # Try and work out which Qt edition it is.
    qtedition = ""
    qteditionstr = ""

    if qtVersion >= 0x030000:
        qconfig = qtincdir + os.sep + "qconfig.h"

        if not os.access(qconfig,os.F_OK):
            error("qconfig.h could not be found in %s." % (qtincdir))

        f = open(qconfig)
        l = f.readline()

        while l:
            wl = string.split(l)
            if len(wl) == 3 and wl[0] == "#define" and wl[1] == "QT_PRODUCT_LICENSE":
                    qtedition = wl[2][4:-1]
                    break

            l = f.readline()

        f.close()

        if qtedition == "":
            error("The Qt edition could not be determined by parsing %s." % (qconfig))

        qteditionstr = qtedition + " edition "

    inform("Qt %s %sis being used." % (qtversstr,qteditionstr))

    # Check the licenses are compatible.
    if qtedition:
        if (qtedition == "free" and licType != "GPL") or \
           (qtedition != "free" and licType == "GPL"):
            error("This version of SIP and the %s edition of Qt have incompatible licenses." % (qtedition))


def checkQtLibrary():
    """Check which Qt library is to be used.
    """
    global qtLib

    if qtLibDir:
        qtlibdir = qtLibDir
    else:
        qtlibdir = qtDir + os.sep + "lib"

    if qtLib is None:
        mtlib = isQtLibrary(qtlibdir,"qt-mt")
        stlib = isQtLibrary(qtlibdir,"qt")
        edlib = isQtLibrary(qtlibdir,"qt-mtedu")
        emlib = isQtLibrary(qtlibdir,"qte")

        nrlibs = 0
        names = ""

        if stlib:
            nrlibs = nrlibs + 1
            names = names + ", qt"
            qtLib = "qt"
            lib = stlib

        if mtlib:
            nrlibs = nrlibs + 1
            names = names + ", qt-mt"
            qtLib = "qt-mt"
            lib = mtlib

        if edlib:
            nrlibs = nrlibs + 1
            names = names + ", qt-mtedu"
            qtLib = "qt-mtedu"
            lib = edlib

        if emlib:
            nrlibs = nrlibs + 1
            names = names + ", qte"
            qtLib = "qte"
            lib = emlib

        if nrlibs == 0:
            error("No Qt libraries could be found in %s." % (qtlibdir))

        if nrlibs > 1:
            error("These Qt libraries were found: %s. Use the -l argument to explicitly specify which you want to use." % (names[2:]))

        inform("The %s Qt library was found." % (qtLib))
    else:
        lib = isQtLibrary(qtlibdir,qtLib)

        if not lib:
            error("The %s Qt library could not be found in %s." % (qtLib,qtlibdir))

    if qtLib in ("qt-mt", "qt-mtedu"):
        blx_config.append("thread")
        inform("Qt thread support is enabled.")
    else:
        inform("Qt thread support is disabled.")

    # tmake under Windows doesn't know the version of the Qt library, so we
    # have to patch it.
    if sys.platform == "win32" and usingTmake:
        makefilePatches["qt.lib"] = lib


def installChecks():
    """Carry out basic checks about the installation.
    """
    global licType

    try:
        import license
        licType = license.LicenseType
        licname = license.LicenseName
    except:
        licType = "GPL"
        licname = "GNU General Public License"

    inform("This is the %s version of SIP and is licensed under the %s." % (licType,licname),0)

    print
    print "Type 'L' to view the license."
    print "Type 'yes' to accept the terms of the license."
    print "Type 'no' to decline the terms of the license."
    print

    while 1:
        try:
            resp = raw_input("Do you accept the terms of the license? ")
        except:
            resp = ""

        resp = string.lower(string.strip(resp))

        if resp == "yes":
            break

        if resp == "no":
            sys.exit(0)

        if resp == "l":
            os.system("more LICENSE")

    proPatches["@BLX_SIP_LICENSE@"] = licType

    inform("Building the %s version of SIP 3.7 for Python %s on %s." % (licType,pyFullVers,sys.platform))

    # We can't be sure that we can run programs under Windows (because it might
    # be 95 or 98) with Python 1.5.x and get the return codes.  So we force the
    # user to explicitly say that's what they want to do.
    if sys.platform == "win32" and pyVersNr < 0x010600:
        if not enableOldPython:
            error("You are using Python %s under Windows. If you are running Windows95 or Windows98 then there may be problems trying to configure PyQt. It is recomended that you upgrade to Python 1.6 or later. Use the -w argument to suppress this check.\n" % (pyFullVers))

    # Check the installation directory is valid.
    if not os.access(modDir,os.F_OK):
        error("The %s SIP module destination directory does not seem to exist. Use the -d argument to set the correct directory." % (modDir))

    proPatches["@BL_SIP_MODDIR@"] = escape(modDir)
    proPatches["@BLX_DEF_MODDIR@"] = escape(platPySiteDir)

    inform("%s is the SIP module installation directory." % (modDir))

    # Check that the Python header files have been installed.
    if not os.access(platPyIncDir + "/Python.h",os.F_OK):
        error("Python.h could not be found in %s. Make sure Python is fully installed, including any development packages." % (platPyIncDir))

    inform("%s contains Python.h." % (platPyIncDir))

    # Check that the Python library can be found.
    if platPyLib:
        if not os.access(platPyLib,os.F_OK):
            error("%s could not be found. Make sure Python is fully installed, including any development packages." % (platPyLib))

        proPatches["@BL_PY_LIB@"] = escape(platPyLib)

        inform("%s is installed." % (platPyLib))

    # Check the Qt installation.
    checkQtDirAndVersion()

    # Look for the Makefile generator.
    checkMakefileGenerator()

    if sys.platform == "win32":
        blx_libs = escape(modDir) + "\\libsip.lib " + escape(platPyLib)
    else:
        blx_libs = "-L" + escape(modDir) + " -lsip"

    proPatches["@BLX_LIBS@"] = blx_libs

    # tmake needs the current directory explicitly included (but we don't yet
    # know if we are using qmake or tmake).
    bl_includepath = [".", escape(platPyIncDir)]
    blx_includepath = [".", escape(sipIncDir)]

    if sipIncDir != platPyIncDir:
        blx_includepath.append(escape(platPyIncDir))

    bl_defines = []
    blx_defines = []

    if qtVersion >= 0:
        checkQtLibrary()

        if qtIncDir:
            bl_includepath.append(escape(qtIncDir))
            blx_includepath.append(escape(qtIncDir))

        blx_config.append("qt")

        bl_defines.append("SIP_QT_SUPPORT")

        if usingTmake and sys.platform == "win32":
            bl_defines.append("QT_DLL")
            blx_defines.append("QT_DLL")

    proPatches["@BL_DEFINES@"] = string.join(bl_defines)
    proPatches["@BLX_DEFINES@"] = string.join(blx_defines)
    proPatches["@BL_INCLUDEPATH@"] = string.join(bl_includepath)
    proPatches["@BLX_INCLUDEPATH@"] = string.join(blx_includepath)

    # Look for Make.
    global makeBin

    if makeBin is None:
        makeBin = searchPath(platMake)
    elif not isProgram(makeBin):
        makeBin = None

    if makeBin is None:
        error("A make program could not be found. Use the -m argument to set the correct program.")

    # For lib template project files.
    makefilePatches["MAKE_LIB"] = [re.compile("(^CC)",re.M), "MAKE     = " + escape(makeBin) + "\n\\1"]

    # For subdir template project files.
    makefilePatches["MAKE_SUBDIR"] = [re.compile("(^MAKEFILE)",re.M), "MAKE     = " + escape(makeBin) + "\n\\1"]

    inform("%s will be used as the make program." % (makeBin))


def checkMakefileGenerator():
    """Check that the Makefile generator can be found:
    """
    global makefileGen, usingTmake

    if makefileGen is None:
        mfg = "tmake"

        if qtVersion >= 0:
            xtradir = qtDir + "/bin"

            if qtVersion >= 0x030000:
                mfg = "qmake"
        else:
            xtradir = None

        makefileGen = searchPath(mfg,xtradir)
    elif not isProgram(makefileGen):
        mfg = makefileGen
        makefileGen = None

    if makefileGen is None:
        error("The Makefile generator %s could not be found. Use the -g argument to set the correct program." % (mfg))

    if os.path.basename(makefileGen) in ("tmake", "tmake.exe"):
        usingTmake = 1

        # Make sure that references to tmake use the correct absolute pathname.
        makefilePatches["TMAKE1"] = [re.compile("(^TMAKE[ \t]*=).*$",re.M), "\\1 " + escape(makefileGen)]
        makefilePatches["TMAKE2"] = [re.compile("^\ttmake(.*)$",re.M), "\t" + escape(makefileGen) + "\\1"]

        # Add the install target for Makefiles generated from the subdirs
        # template.
        makefilePatches["TMAKE3"] = [re.compile("^(clean release debug):$",re.M), "\\1 install:"]

        # Remove the invocation of the tmake_all target.
        makefilePatches["TMAKE4"] = [re.compile(" tmake_all",re.M), ""]
    else:
        # Make sure that references to qmake use the correct absolute pathname.
        makefilePatches["QMAKE1"] = [re.compile("(^QMAKE[ \t]*=).*$",re.M), "\\1 " + escape(makefileGen)]

        # Remove the invocation of the qmake_all target.
        makefilePatches["QMAKE2"] = [re.compile(" qmake_all",re.M), ""]

    inform("%s will be used to generate Makefiles." % (makefileGen))

    proPatches["@BLI_MFGBIN@"] = escape(makefileGen)

    # If it is tmake then make sure TMAKEPATH is set.
    if usingTmake:
        try:
            os.environ["TMAKEPATH"]
        except:
            error("tmake requires that the TMAKEPATH environment variable is properly set.")


def tuneCompiler():
    """Tune the compiler flags.
    """
    if len(gccFlags) > 0:
        subst = "\\1"

        for f in gccFlags:
            subst = subst + " -f" + f

        subst = subst + "\n"

        makefilePatches["CXXFLAGS"] = [re.compile("(^CXXFLAGS.*)$",re.M), subst]


def pushDir(newdir):
    """Change to a new directory and return the old one.

    newdir is the new directory.
    """
    olddir = os.getcwd()
    os.chdir(newdir)

    return olddir


def popDir(olddir):
    """Change back to a previous directory.

    olddir is the old directory.
    """
    os.chdir(olddir)


def runProgram(prog,argv = [],fatal = 1):
    """Execute a program.

    prog is the name of the program.
    argv is the optional list of program arguments.
    fatal is non-zero if an error is considered fatal.

    Returns the error code.
    """
    # Use the basename to avoid problems with pathnames containing spaces.
    argv.insert(0,os.path.basename(prog))

    try:
        rc = os.spawnv(os.P_WAIT,prog,argv)
    except AttributeError:
        rc = os.system(string.join(argv))
    except:
        raise

    if rc != 0 and fatal:
        error("%s failed with an exit code of %d." % (prog,rc))

    return rc


def runMake(target = None,fatal = 1):
    """Run make.

    target is the optional make target.
    fatal is non-zero if an error is considered fatal.

    Returns the error code.
    """
    if target is None:
        argv = []
    else:
        argv = [target]

    # As the tests are quite quick, make sure that we don't get timestamp
    # problems.
    time.sleep(1)

    return runProgram(makeBin,argv,fatal)


def buildMakefile(pro):
    """Run qmake or tmake to create a Makefile from a project file.

    pro is the name of the project file.
    """
    _patch_file(pro,proPatches)

    # Make sure that the Makefile has a later timestamp than the project file.
    time.sleep(1)

    runProgram(makefileGen,['-o', 'Makefile.in', pro])

    _patch_file("Makefile",makefilePatches)


def fixInstallTarget(ismod = 0):
    """Fix the install target for a Makefile.

    ismod is non-zero if the Makefile is for the SIP module.
    """
    f = open("Makefile","a")
    f.write("\ninstall:\n")

    if ismod:
        f.write("\t-%s sip.h %s\n" % (platCopy,escape(sipIncDir)))
        f.write("\t-%s sipQt.h %s\n" % (platCopy,escape(sipIncDir)))

    f.close()


def main(argv):
    """The main function of the script.

    argv is the list of command line arguments.
    """
    import getopt

    # Parse the command line.
    _set_script()

    initGlobals()

    try:
        optlist, args = getopt.getopt(argv[1:],"hb:d:e:f:g:i:l:m:q:r:t:uwx")
    except getopt.GetoptError:
        usage()

    explicitMake = 0

    for opt, arg in optlist:
        if opt == "-h":
            usage(0)
        elif opt == "-b":
            global sipBinDir
            sipBinDir = arg
        elif opt == "-d":
            global modDir
            modDir = arg
        elif opt == "-e":
            global sipIncDir
            sipIncDir = arg
        elif opt == "-f":
            global gccFlags
            gccFlags.append(arg)
        elif opt == "-g":
            global makefileGen
            makefileGen = arg
        elif opt == "-i":
            global qtIncDir
            qtIncDir = arg
        elif opt == "-l":
            global qtLib

            if arg in ("qt", "qt-mt", "qt-mtedu", "qte"):
                qtLib = arg
            else:
                usage()
        elif opt == "-m":
            global makeBin
            makeBin = arg
            explicitMake = 1
        elif opt == "-q":
            global qtDir
            qtDir = arg
        elif opt == "-r":
            global qtLibDir
            qtLibDir = arg
        elif opt == "-t":
            global platPyLibDir
            platPyLibDir = arg
        elif opt == "-u":
            global debugMode
            debugMode = "debug"
        elif opt == "-v":
            global sipSipDir
            sipSipDir = arg
        elif opt == "-w":
            global enableOldPython
            enableOldPython = 1
        elif opt == "-x":
            global qtVersion
            qtVersion = -1

    initGlobals()

    installChecks()
    maindir = mkTempBuildDir()
    tuneCompiler()
    mkTempBuildDir(maindir)

    # Some more project file patches.
    proPatches["@BLX_DEF_BINDIR@"] = escape(platBinDir)
    proPatches["@BL_SIP_BINDIR@"] = escape(sipBinDir)

    blx_sip_bin = sipBinDir + os.sep + "sip"

    if sys.platform == "win32":
        blx_sip_bin = blx_sip_bin + ".exe"

    proPatches["@BLX_SIP_BIN@"] = escape(blx_sip_bin)

    target = "sip"

    if sys.platform == "win32":
        target = "libsip"
    else:
        target = "sip"

    proPatches["@BL_TARGET@"] = target

    blx_config.append(debugMode)
    proPatches["@BLX_CONFIG@"] = string.join(blx_config)

    proPatches["@BLX_DEF_SIPDIR@"] = escape(sipSipDir)

    proPatches["@BLX_SIP_VERSION@"] = "0x030700"
    proPatches["@BLX_PY_VERSION@"] = "0x%06x" % (pyVersNr)

    if qtVersion <= 0:
        qtv = "0"
    else:
        qtv = "0x%06x" % (qtVersion)

    proPatches["@BLX_QT_VERSION@"] = qtv

    inform("Creating SIP code generator Makefile.")
    olddir = pushDir("sipgen")
    buildMakefile("sipgen.pro")
    fixInstallTarget()
    popDir(olddir)

    inform("Creating SIP module Makefile.")
    olddir = pushDir("siplib")
    buildMakefile("siplib.pro")
    fixInstallTarget(1)
    popDir(olddir)

    # Generate the top-level Makefile.
    inform("Creating top level Makefile.")
    copyToFile("sip.pro.in","TEMPLATE = subdirs\nSUBDIRS = sipgen siplib\n")
    buildMakefile("sip.pro")

    # Create the configuration module.
    #_create_config("sipconfig.py",proPatches)

    # Tell the user what to do next.
    if explicitMake:
        mk = makeBin
    else:
        (mk,ignore) = os.path.splitext(os.path.basename(makeBin))

    inform("The configuration of SIP for your system is now complete. To compile and install SIP run \"%s\" and \"%s install\" with appropriate user privileges." % (mk,mk))


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
