# I'll be the first to admit that the code in this build script is an aesthetic
# atrocity, but it's a build script, so I don't care.

from __future__ import nested_scopes # Python 2.1 compatibility

import ConfigParser, os, os.path, re, shutil, sys, types
import distutils.core
import distutils.ccompiler
import distutils.sysconfig

class BuildError(Exception): pass

def doCommand(cmd, header='COMMAND EXECUTION ERROR'):
    print '\t' + cmd
    taskOutStream = os.popen(cmd)
    taskOutput = taskOutStream.read()
    taskRetCode = taskOutStream.close()
    if taskRetCode is not None:
        raise BuildError('\n%s\n  Command [%s] died with error:\n[%s]\n'
            % (header, cmd, taskOutput)
          )
    return taskOutput

def doLibConvCmd(cmd):
    return doCommand(cmd, LIBCONVERSION_ERROR_HEADER)

def determineWindowsSystemDir():
    if sys.platform.lower() == 'cygwin':
        return doCommand('cygpath --sysdir')[:-1] # Trailing newline.
    else:
        # (normal win32)
        # If I were willing to introduce a win32all dependency into this build
        # script, this function would be replaced by win32api.GetSystemDirectory.
        winDir = os.environ.get('SYSTEMROOT', os.environ.get('WINDIR', 'C:\\Windows'))
        winSysDir = os.path.join(winDir, 'system32')
        if not os.path.isdir(winSysDir):
            winSysDir = os.path.join(winDir, 'system')
        return winSysDir

# Be careful about changing these messages; the build documentation refers to them.
PYTHON_SYSTEM_ERROR_HEADER = 'PYTHON SYSTEM ERROR:'
COMPILER_CONFIGURATION_ERROR_HEADER = 'COMPILER CONFIGURATION ERROR:'
KIDB_DISTRIBUTION_ERROR_HEADER = 'KINTERBASDB DISTRIBUTION ERROR:'
LIBCONVERSION_ERROR_HEADER = 'LIBRARY CONVERSION ERROR:'
AUTODETECTION_ERROR_HEADER = 'LIBRARY AUTODETECTION ERROR:'
MANUAL_SPECIFICATION_ERROR_HEADER = 'LIBRARY MANUAL SPECIFICATION ERROR:'

DISTUTILS_URL = 'http://www.python.org/sigs/distutils-sig/distutils.html'

VERSION_FILE = 'version.txt'
CONFIG_FILE = 'setup.cfg'

PYTHON_VERSION_THRESHOLD = (2,1)
if sys.version_info[:2] < PYTHON_VERSION_THRESHOLD:
    sys.stderr.write(
        '%s\n'
        '  Beginning with kinterbasdb 3.1, kinterbasdb'
        ' no longer officially supports\n  versions of Python prior to %s.\n\n'
        % (PYTHON_SYSTEM_ERROR_HEADER, PYTHON_VERSION_THRESHOLD)
      )
    sys.exit(-1)

DEBUG = int(os.environ.get('KINTERBASDB_DEBUG', 0))

# Module name and version number:
kinterbasdb_name = 'kinterbasdb'
# Retrive the kinterbasdb version number from a central text file for the sake
# of maintainability:
try:
    kinterbasdb_version = open(VERSION_FILE).read().strip()
except IOError:
    raise BuildError(
        "\n%s\n"
        " File 'version.txt' is missing; cannot determine kinterbasdb"
        " version."
        % KIDB_DISTRIBUTION_ERROR_HEADER
      )

argJam = ' '.join(sys.argv[1:]).lower()

# These config parameters are user-specifiable via setup.cfg:
CHECKED_BUILD = 0

ENABLE_FIELD_PRECISION_DETERMINATION = 1

DATABASE_IS_FIREBIRD = None
DATABASE_HOME_DIR = None
DATABASE_INCLUDE_DIR = None
DATABASE_LIB_DIR = None
DATABASE_LIB_NAME = None

# These config parameters are not drawn from setup.cfg:
CUSTOM_PREPROCESSOR_DEFS = []
PLATFORM_SPECIFIC_INCLUDE_DIRS = []
PLATFORM_SPECIFIC_LIB_DIRS = []
PLATFORM_SPECIFIC_LIB_NAMES = []
PLATFORM_SPECIFIC_EXTRA_COMPILER_ARGS = []
PLATFORM_SPECIFIC_EXTRA_LINKER_ARGS = []

# See if the user manually specified various build options in the setup config
# file.  If so, skip autodetection for the options that the user has specified.
config = ConfigParser.ConfigParser()
config.read(CONFIG_FILE)

if config.has_section('manual_config'):
    def _boolConfOpt(name):
        if config.has_option('manual_config', name):
            return config.getboolean('manual_config', name)
        else:
            return 0

    CHECKED_BUILD = _boolConfOpt('checked_build')

    ENABLE_FIELD_PRECISION_DETERMINATION = _boolConfOpt('enable_field_precision_determination')

    DATABASE_IS_FIREBIRD = _boolConfOpt('database_is_firebird')

    if config.has_option('manual_config', 'database_home_dir'):
        DATABASE_HOME_DIR = config.get('manual_config', 'database_home_dir')
    if config.has_option('manual_config', 'database_include_dir'):
        DATABASE_INCLUDE_DIR = config.get('manual_config', 'database_include_dir')
    if config.has_option('manual_config', 'database_lib_dir'):
        DATABASE_LIB_DIR = config.get('manual_config', 'database_lib_dir')
    if config.has_option('manual_config', 'database_lib_name'):
        DATABASE_LIB_NAME = config.get('manual_config', 'database_lib_name')

if DEBUG:
    print "*** CONFIG OPTIONS SPECIFIED IN %s SECTION 'manual_config' ***" % CONFIG_FILE
    for key in config.options('manual_config'):
        print '%s:' % (key)
        print '    %s' % (config.get('manual_config', key))

ALL_AUTODETECT_OPTIONS_MANUALLY_SPECIFIED = (
        DATABASE_IS_FIREBIRD is not None
    and DATABASE_HOME_DIR
    and DATABASE_INCLUDE_DIR
    and DATABASE_LIB_DIR
    and DATABASE_LIB_NAME
  )


def verifyAutodetectedDatabaseIncludeDir():
    if not os.path.exists(DATABASE_INCLUDE_DIR):
        sys.stderr.write(
            "%s\n"
            "  Cannot autodetect the database header file directory.\n"
            "  (Tried %s)\n"
            "  Try specifying the 'database_include_dir' option in\n"
            "  the 'manual_config' section of the setup config file (%s).\n"
            % (AUTODETECTION_ERROR_HEADER, DATABASE_INCLUDE_DIR, CONFIG_FILE)
          )
        sys.exit(1)

def verifyUserSpecifiedDatabaseIncludeDir():
    if not os.path.exists(DATABASE_INCLUDE_DIR):
        sys.stderr.write(
            "%s\n"
            "  The user-specified database header file directory\n"
            "    %s\n"
            "  does not exist.\n"
            "  Try modifying the 'database_include_dir' option in\n"
            "  the 'manual_config' section of the setup config file (%s),\n"
            "  or comment out that option to force this script to\n"
            "  to autodetect it.\n"
            % (MANUAL_SPECIFICATION_ERROR_HEADER, DATABASE_INCLUDE_DIR, CONFIG_FILE)
          )
        sys.exit(1)

def verifyAutodetectedDatabaseLibraryDir():
    if not os.path.exists(DATABASE_LIB_DIR):
        sys.stderr.write(
            "%s\n"
            "  Cannot autodetect the database lib directory.\n"
            "  (Tried %s)\n"
            "  Try specifying the 'database_include_dir' option in\n"
            "  the 'manual_config' section of the setup config file (%s).\n"
            % (AUTODETECTION_ERROR_HEADER, DATABASE_LIB_DIR, CONFIG_FILE)
          )
        sys.exit(1)

def verifyUserSpecifiedDatabaseLibraryDir():
    if not os.path.exists(DATABASE_LIB_DIR):
        sys.stderr.write(
            "%s\n"
            "  The user-specified database lib directory\n"
            "    %s\n"
            "  does not exist.\n"
            "  Try modifying the 'database_lib_dir' option in\n"
            "  the 'manual_config' section of the setup config file (%s),\n"
            "  or comment out that option to force this script to\n"
            "  to autodetect it.\n"
            % (MANUAL_SPECIFICATION_ERROR_HEADER, DATABASE_LIB_DIR, CONFIG_FILE)
          )
        sys.exit(1)

def findSpacelessDirName(d):
    # On Windows, try to find the spaceless version of the provided directory
    # name.
    # This function was added on 2002.03.14 as part of an ugly hack to
    # surmount a bug in the distutils package.

    # Sometime distutils causes None to be fed to this function.
    if not d:
        return d
    d = os.path.normpath(os.path.abspath(d))
    if ' ' not in d:
        return d

    # If d doesn't exist, its short equivalent can't be determined.
    # However, for the purposes of this program (which is solely for
    # convenience anyway) it's better just to risk feeding the
    # compiler/linker a path with a space in it than it is to raise
    # an exception when there's still a *chance* of success.
    if not os.path.isdir(d):
        return d

    try:
        import win32api
        return os.path.normcase(win32api.GetShortPathName(d))
    except ImportError:
        # Since win32api is not available, we'll revert to a lame,
        # manual approximation of GetShortPathName.
        pass

    ds = d.split(os.sep) # Split into components.
    if DEBUG: print 'ds is', ds
    ds[0] = ds[0] + '\\' # Add slash back onto drive designation so that
                         # it's like c:\ rather than just c:

    dsNoSpaces = [] # Will contain a version of the directory components
                    # with all spaces removed.
    for x in range(len(ds)):
        dir = ds[x]
        if DEBUG: print 'dir is', dir

        if ' ' not in dir:
            shortDir = dir
        else:
            fullDir = apply(os.path.join, ds[:x + 1])
            if DEBUG: print 'fullDir is', fullDir

            # Must deal with names like 'abc de' that have their space
            # before the sixth character.
            dirNoSpaces = dir.replace(' ', '')
            if len(dirNoSpaces) < 6:
                shortDirBase = dirNoSpaces
            else:
                shortDirBase = dirNoSpaces[:6]

            # Search for shortDirBase~i until we find it.
            shortDir = None
            i = 1
            while i < 9: # This code doesn't handle situations where there are
                         # more than nine directories with the same prefix.
                maybeShortDir = '%s~%d' % (shortDirBase, i)
                fullMaybeShortDir = os.path.join(
                    os.path.dirname(fullDir), maybeShortDir)
                if not os.path.isdir(fullMaybeShortDir):
                    continue

                # There follows a *really* lame approximation of
                # os.path.samefile, which is not available on Windows.
                if os.listdir(fullMaybeShortDir) == os.listdir(fullDir):
                    shortDir = maybeShortDir
                    break

                i = i + 1

            if shortDir is None:
                raise Exception('Unable to find shortened version of'
                    ' directory named %s' % d
                  )

        dsNoSpaces.append(shortDir)

    if DEBUG:
        print 'dsNoSpaces is', dsNoSpaces

    return os.path.normcase(apply(os.path.join, dsNoSpaces))


# Perform generic compilation parameter setup, then switch to platform-
# specific.

origWorkingDir = os.path.abspath(os.curdir)

# Autodetect Python directory info.
if DEBUG:
    print '*** PYTHON SETTINGS ***'

pythonHomeDir = sys.exec_prefix
pythonPkgDir = distutils.sysconfig.get_python_lib()

if DEBUG:
    print '\tPython home dir:', pythonHomeDir
    print '\tPython package dir:', pythonPkgDir


# Begin platform-specific compilation parameter setup:
osIsWindows = sys.platform.lower().startswith('win')

compilerIsMinGW = 0
compilerIsGCC = 0

if osIsWindows:
    ALL_AUTODETECT_WINDOWS_REGISTRY_OPTIONS_MANUALLY_SPECIFIED = (
            DATABASE_HOME_DIR
        and DATABASE_INCLUDE_DIR
        and DATABASE_LIB_DIR
        and DATABASE_LIB_NAME
      )

    CUSTOM_PREPROCESSOR_DEFS.append( ('WIN32', None) )

    pyVersionSuffix = ''.join( [str(n) for n in sys.version_info[:2]] )
    pyLibName = 'python%s.lib' % pyVersionSuffix

    # 2003.03.28: Accomodate source dists of Python on Windows (give the
    # PCBuild\pythonVV.lib file (if any) precedence over the
    # libs\pythonVV.lib file (if any)):
    pyLibsDir = os.path.join(pythonHomeDir, 'PCbuild')
    if not os.path.exists(os.path.join(pyLibsDir, pyLibName)):
        pyLibsDir = os.path.join(pythonHomeDir, 'libs')

    pyConventionalLibPath = os.path.join(pyLibsDir, pyLibName)

    # If this is a source distribution of Python, add a couple of necessary
    # include and lib directories.
    pcbuildDir = os.path.join(
        os.path.dirname(distutils.sysconfig.get_python_inc()), 'PCBuild'
      )
    if os.path.exists(pcbuildDir):
        PLATFORM_SPECIFIC_LIB_DIRS.append(pcbuildDir)

        pySrcDistExtraIncludeDir = os.path.join(
            os.path.dirname(distutils.sysconfig.get_python_inc()), 'PC'
          )
        PLATFORM_SPECIFIC_INCLUDE_DIRS.append(pySrcDistExtraIncludeDir)

    # Verify the various directories (such as include and library dirs) that
    # will be used during compilation.

    # Open the registry in preparation for reading various installation
    # directories from it.
    try:
        import _winreg
    except ImportError:
        # If the user has manually specified all of the options that would
        # require registry access to autodetect, we can proceed despite the
        # lack of _winreg.
        if not ALL_AUTODETECT_WINDOWS_REGISTRY_OPTIONS_MANUALLY_SPECIFIED:
            sys.stderr.write(
                "%s\n"
                "  Cannot import the standard package '_winreg'.\n"
                "  _winreg did not join the standard library until\n"
                "  Python 2.0.  If you are using a source distribution\n"
                "  of Python 2.0 or later, you may have simply forgotten\n"
                "  to compile the _winreg C extension.\n"
                "  You can get around the lack of _winreg by manually\n"
                "  specifying all of the configuration options in the\n"
                "  'manual_config' section of the setup config file (%s)."
                % (AUTODETECTION_ERROR_HEADER, CONFIG_FILE)
              )
            sys.exit(1)

    if not ALL_AUTODETECT_WINDOWS_REGISTRY_OPTIONS_MANUALLY_SPECIFIED:
        try:
            r = _winreg.ConnectRegistry(None, _winreg.HKEY_LOCAL_MACHINE)
        except WindowsError, e:
            sys.stderr.write(
                "%s\n"
                "  Unable to connect to the HKEY_LOCAL_MACHINE section of\n"
                "  the Windows registry.\n"
                "  The specific error encountered is:\n"
                "  %s"
                % (AUTODETECTION_ERROR_HEADER, str(e))
              )
            sys.exit(1)

    if DEBUG:
        print '*** DATABASE SETTINGS ***'

    # Autodetect database directory info if the user did not specify it.
    if not DATABASE_HOME_DIR:
        def findDatabaseHomeDir(databaseInfoKey, databaseHomeValueName):
            databaseCurrentVersionKey = _winreg.OpenKey(r, databaseInfoKey)
            try:
                return _winreg.QueryValueEx(
                    databaseCurrentVersionKey,
                    databaseHomeValueName
                  )[0]
            finally:
                _winreg.CloseKey(databaseCurrentVersionKey)

        # Try to find Firebird first; revert to Interbase only if necessary.
        try:
            try:
                # 2003.11.10: Firebird 1.5 RC7 changed the registry structure.
                DATABASE_HOME_DIR = findDatabaseHomeDir(
                    r'SOFTWARE\Firebird Project\Firebird Server\Instances',
                    'DefaultInstance'
                  )
                DATABASE_IS_FIREBIRD = 1
            except WindowsError:
                try:
                    # Firebird 1.0-Firebird 1.5 RC6:
                    DATABASE_HOME_DIR = findDatabaseHomeDir(
                        r'SOFTWARE\FirebirdSQL\Firebird\CurrentVersion',
                        'RootDirectory'
                      )
                    DATABASE_IS_FIREBIRD = 1
                except WindowsError:
                    # Revert to Interbase.
                    DATABASE_IS_FIREBIRD = 0
                    DATABASE_HOME_DIR = findDatabaseHomeDir(
                        r'SOFTWARE\Borland\InterBase\CurrentVersion',
                        'RootDirectory'
                      )
        except WindowsError, e:
            sys.stderr.write(
                "%s\n"
                "  Unable to retrieve database directory from the Windows"
                " registry.\n"
                "  Try specifying the 'database_home_dir' option in the\n"
                "  'manual_config' section of the setup config file (%s).\n"
                "  The specific error encountered is:\n"
                "  %s"
                % (AUTODETECTION_ERROR_HEADER, CONFIG_FILE, str(e))
              )
            sys.exit(1)

    if DATABASE_INCLUDE_DIR:
        verifyUserSpecifiedDatabaseIncludeDir()
        databaseSDKDir = os.path.dirname(DATABASE_INCLUDE_DIR)
    else:
        databaseSDKDir = os.path.join(DATABASE_HOME_DIR, 'SDK')
        if DATABASE_IS_FIREBIRD or not os.path.exists(databaseSDKDir):
            databaseSDKDir = DATABASE_HOME_DIR

        DATABASE_INCLUDE_DIR = os.path.join(databaseSDKDir, 'include')

        if DEBUG:
            print (
                '\tDATABASE_INCLUDE_DIR exists at\n\t  %s: %d'
                % (DATABASE_INCLUDE_DIR, os.path.exists(DATABASE_INCLUDE_DIR))
              )

        verifyAutodetectedDatabaseIncludeDir()

    if DATABASE_LIB_DIR:
        verifyUserSpecifiedDatabaseLibraryDir()
    else:
        DATABASE_LIB_DIR = os.path.join(databaseSDKDir, 'lib')

        verifyAutodetectedDatabaseLibraryDir()

    # Perform compiler-specific setup.
    if not DATABASE_LIB_NAME:
        DATABASE_LIB_NAME = 'gds32'

    # I should discover a way to ask distutils what compiler it's
    # configured to use--the current code would only detect a compiler
    # change via the command line.  I've looked at the compiler subpackage
    # of distutils, and can't find any such facility (though it can be
    # queried for the system's default compiler).
    customCompilerName = 'msvc'

    match = re.search(r'--compiler=(?P<cname>\S+)', argJam)
    if match:
        customCompilerName = match.group('cname')
    else:
        match = re.search(r'-c\s*(?P<cname>\S+)', argJam)
        if match:
            customCompilerName = match.group('cname')

    compilerIsMinGW = customCompilerName.lower().startswith('mingw')

    if customCompilerName == 'msvc':
        # 2004.11.05: SF 1056684:
        # If MSVC is assumed to be the compiler (which it is, on Windows,
        # unless the user explicitly indicates otherwise), but we're not
        # actually compiling (as when this script is invoked with
        # 'setup.py install --skip-build'), skip the registry lookups that'll
        # break if MSVC is not installed.
        if argJam.find('--skip-build') == -1:
            # 2004.10.24:
            # Autodetect support files for "Microsoft Visual C++ Toolkit 2003":
            if sys.version_info[:2] >= (2,4):
                directoriesKey = _winreg.OpenKey(r,
                    r'SOFTWARE\Microsoft\MicrosoftSDK\Directories'
                  )
                try:
                    windowsSDKDir = _winreg.QueryValueEx(
                        directoriesKey,
                        'Install Dir'
                      )[0]
                finally:
                    _winreg.CloseKey(directoriesKey)

                windowsSDKLibDir = os.path.join(windowsSDKDir, 'Lib')
                PLATFORM_SPECIFIC_LIB_DIRS.append(windowsSDKLibDir)
            # End "Microsoft Visual C++ Toolkit 2003" support code.
            else:
                # 2004.10.28: Better support for building with VStudio 6 when
                # "Register Environment Variables" was not selected during the
                # installation process (vcvars32.bat doesn't quite paper over
                # all of the differences).
                vcKey = _winreg.OpenKey(r,
                    r'SOFTWARE\Microsoft\DevStudio\6.0\Products\Microsoft Visual C++'
                  )
                try:
                    vcDir = _winreg.QueryValueEx(vcKey, 'ProductDir')[0]
                finally:
                    _winreg.CloseKey(vcKey)

                PLATFORM_SPECIFIC_INCLUDE_DIRS.append(os.path.join(vcDir, 'Include'))
                PLATFORM_SPECIFIC_LIB_DIRS.append(os.path.join(vcDir, 'Lib'))


        if not DATABASE_IS_FIREBIRD:
            DATABASE_LIB_DIR = os.path.join(databaseSDKDir, r'lib_ms')
            if not os.path.exists(DATABASE_LIB_DIR):
                DATABASE_LIB_DIR = os.path.join(databaseSDKDir, r'lib')
        if not DATABASE_LIB_NAME or DATABASE_LIB_NAME == 'gds32':
            DATABASE_LIB_NAME = 'gds32_ms'
    elif customCompilerName == 'bcpp':
        print '  *** BCPP LIBRARY GENERATION : begin ***'
        COMPILER_EXE_NAME = 'bcc32.exe'

        # Try to find the home directory of the Borland compiler by searching
        # each directory listed in the PATH.
        # If I were willing to depend on win32all, win32api.FindExecutable
        # would replace this code.
        osPath = os.environ['PATH'].split(os.pathsep)
        bccHome = None
        for dir in osPath:
            if os.path.exists(os.path.join(dir, COMPILER_EXE_NAME)):
                bccHome = os.path.split(dir)[0]
                break
        else:
            # Couldn't find it.
            sys.stderr.write(
                "%s\n"
                "  Unable to find the home directory of the Borland"
                " compiler.\n"
                "  You must add the 'bin' subdirectory of the"
                " compiler's\n"
                "  home directory to your PATH.\n"
                "  One way to do this is to type a command of the"
                " format\n"
                "    SET PATH=%%PATH%%;c:\\EXAMPLE_DIR\\bin\n"
                "  in the same command prompt you use to run the"
                " kinterbasdb setup script."
                % (COMPILER_CONFIGURATION_ERROR_HEADER,)
              )
            sys.exit(1)

        # Override the default behavior of distutils.bcppcompiler.BCPPCompiler
        # in order to force it to issue the correct command.
        from distutils.bcppcompiler import BCPPCompiler
        def _makeDirNameSpacless(kwargs, argName):
            x = kwargs.get(argName, None)
            if x is None:
                return
            elif isinstance(x, types.StringType):
                kwargs[argName] = findSpacelessDirName(x)
            else: # sequence of strings
                kwargs[argName] = [ findSpacelessDirName(d) for d in x ]

        class BCPP_UGLY_Hack(BCPPCompiler):
            def compile (self, *args, **kwargs):
                bccIncludePreargDir = findSpacelessDirName(r'%s\include' % bccHome)
                bccLibPreargDir = findSpacelessDirName(r'%s\lib' % bccHome)
                bccLibPSDKPreargDir = findSpacelessDirName(r'%s\lib\psdk' % bccHome)

                kwargs['extra_preargs'] = (
                    [
                      r'-I"%s"' % bccIncludePreargDir,
                      r'-L"%s"' % bccLibPreargDir,
                      r'-L"%s"' % bccLibPSDKPreargDir
                    ]
                    + kwargs.get('extra_preargs', [])
                  )
                for argName in ('output_dir', 'include_dirs'):
                    _makeDirNameSpacless(kwargs, argName)

                return BCPPCompiler.compile(self, *args, **kwargs)


            def link (self, *args, **kwargs):
                ilinkLibPreargDir = findSpacelessDirName(r'%s\lib' % bccHome)
                ilinkLibPSDKPreargDir = findSpacelessDirName(r'%s\lib\psdk' % bccHome)

                myPreArgs = [
                    r'/L"%s"' % ilinkLibPreargDir,
                    r'/L"%s"' % ilinkLibPSDKPreargDir
                  ]
                if not kwargs.has_key('extra_preargs'):
                    argsAsList = list(args)
                    argsAsList[9] = myPreArgs # see distuils.ccompiler.py
                    args = tuple(argsAsList)
                else:
                    kwargs['extra_preargs'] = myPreArgs + kwargs.get['extra_preargs']

                for argName in (
                    'output_dir', 'library_dirs',
                    'runtime_library_dirs', 'build_temp'
                  ):
                    _makeDirNameSpacless(kwargs, argName)

                return BCPPCompiler.link(self, *args, **kwargs)


        # Force distutils to use our BCPP_UGLY_Hack class rather than the
        # default BCPPCompiler class.
        compilerSetupTuple = distutils.ccompiler.compiler_class['bcpp']
        import distutils.bcppcompiler
        distutils.bcppcompiler.BCPP_UGLY_Hack = BCPP_UGLY_Hack
        distutils.ccompiler.compiler_class['bcpp'] = (
            compilerSetupTuple[0], 'BCPP_UGLY_Hack', compilerSetupTuple[2]
          )

        # Use the Borland command-line library conversion tool coff2omf to
        # create a Borland-compiler-compatible library file,
        # "pythonVV_bcpp.lib", from the standard "pythonVV.lib".
        libName = os.path.join(pyLibsDir, 'python%s_bcpp.lib' % pyVersionSuffix)
        if not os.path.exists(libName):
            print 'setup.py is trying to create %s' % libName
            coff2omfCommand = ('coff2omf %s %s' % (pyConventionalLibPath, libName))

            os.system(coff2omfCommand)
            # Do this test instead of checking the return value of
            # os.system, which will not reliably indicate an error
            # condition on Win9x.
            if not os.path.exists(libName):
                sys.stderr.write(
                    "%s\n"
                    "  Unable to create a Borland-compatible Python"
                    " library file using the\n"
                    "  coff2omf utility.\n"
                    "  Tried command:\n"
                    "    %s"
                    % (COMPILER_CONFIGURATION_ERROR_HEADER, coff2omfCommand)
                  )
                sys.exit(1)
        assert os.path.isfile(libName)
        print '  *** BCPP LIBRARY GENERATION : end ***'
    elif compilerIsMinGW: # 2003.08.05:
        print '  *** MINGW LIBRARY GENERATION : begin ***'
        # Use the MinGW tools pexports and dlltool to create a GCC-compatible
        # library file for Python.
        # Firebird 1.5 already includes a suitable library file (fbclient_ms.lib).
        if DATABASE_LIB_NAME != 'fbclient_ms':
            if DATABASE_LIB_NAME is not None:
                print (
                    '\tIgnoring your "%s" library name setting in favor of\n'
                    '\t  "fbclient_ms", which is the proper choice for MinGW.'
                    % DATABASE_LIB_NAME
                  )
            DATABASE_LIB_NAME = 'fbclient_ms'

        winSysDir = determineWindowsSystemDir()

        # Python (pythonVV.lib -> libpythonVV.a):
        pyDLL = 'python%s.dll' % pyVersionSuffix
        pyDLLPathPossibilies = [os.path.join(d, pyDLL) for d in
            (pythonHomeDir, pcbuildDir, winSysDir)
          ]
        for pyDLLPath in pyDLLPathPossibilies:
            if os.path.isfile(pyDLLPath):
                break
        else:
            raise BuildError("""\n%s\n  Can't find Python DLL "%s"."""
                % (LIBCONVERSION_ERROR_HEADER, pyDLL)
              )

        libName = 'libpython%s.a' % pyVersionSuffix
        libUltimateDest = os.path.join(pyLibsDir, libName)
        defFilename = 'python%s.def' % pyVersionSuffix
        if os.path.isfile(libUltimateDest):
            print ('\tMinGW-compatible Python library already exists at:\n\t  %s'
                % libUltimateDest
              )
        else:
            print (
                '\n\tsetup.py is trying to create MinGW-compatible Python'
                ' library at:\n'
                '\t  "%s"'
                % libUltimateDest
              )
            os.chdir(os.path.dirname(pyDLLPath))
            try:
                doLibConvCmd('pexports %s > %s' % (pyDLL, defFilename))
                doLibConvCmd(
                    'dlltool --dllname %s --def %s --output-lib %s'
                    % (pyDLL, defFilename, libName)
                  )
                os.remove(defFilename)
                # With source builds of some versions of Python, the Python DLL
                # is located in the same directory that distutils declares to
                # be the "library directory", so the generated library file
                # shouldn't be moved.
                if os.path.dirname(libUltimateDest).lower() != os.path.abspath(os.curdir).lower():
                    shutil.copyfile(libName, libUltimateDest)
                    os.remove(libName)
            finally:
                os.chdir(origWorkingDir)

        assert os.path.isfile(libUltimateDest)
        print '  *** MINGW LIBRARY GENERATION : end ***\n'

    if DEBUG:
        print '\tDATABASE_LIB_DIR exists at\n\t  %s: %d' \
            % (DATABASE_LIB_DIR, os.path.exists(DATABASE_LIB_DIR))
        print '\tDATABASE_LIB_NAME is\n\t  %s' % DATABASE_LIB_NAME

elif sys.platform.lower() == 'cygwin': # 2003.08.05:
    print '  *** CYGWIN LIBRARY GENERATION : begin ***'
    if DATABASE_LIB_NAME != 'fbclient':
        if DATABASE_LIB_NAME is not None:
            print (
                '\tIgnoring your "%s" library name setting in favor of\n'
                '\t  "fbclient", which is the proper choice for Cygwin.'
                % DATABASE_LIB_NAME
              )
        DATABASE_LIB_NAME = 'fbclient'

    winSysDir = determineWindowsSystemDir()

    # 2003.11.10: Switched to FB 1.5 RC7+ reg structure.
    # baseRegLoc = '/proc/registry/HKEY_LOCAL_MACHINE/SOFTWARE/FirebirdSQL/Firebird/CurrentVersion'
    regInstLoc = (
        '/proc/registry/HKEY_LOCAL_MACHINE/SOFTWARE/Firebird Project'
        '/Firebird Server/Instances/DefaultInstance'
      )
    # Read the location of Firebird from the Windows registry.
    try:
        fbDir = doLibConvCmd('cat "%s"' % regInstLoc)[:-1] # Trailing null byte.
    except BuildError:
        raise BuildError("\n%s\n  Windows registry settings for Firebird 1.5"
            " were not found.  Try running Firebird's instreg.exe utility."
            " (Firebird 1.5 before RC7, Firebird 1.0, and Interbase are not"
            " supported.)"
            % AUTODETECTION_ERROR_HEADER
          )

    if fbDir.endswith('\\'):
        fbDir = fbDir[:-1] # Trailing backslash.
    fbDir = fbDir.replace('\\', '/')
    fbDir = doLibConvCmd('cygpath --unix %s' % fbDir)[:-1] # Trailing null byte.

    if DATABASE_INCLUDE_DIR:
        verifyUserSpecifiedDatabaseIncludeDir()
    else:
        DATABASE_INCLUDE_DIR = os.path.join(fbDir, 'include')
        verifyAutodetectedDatabaseIncludeDir()

    libUltimateDest = '/usr/lib/libfbclient.a'
    libUltimateDir, libName = os.path.split(libUltimateDest)
    if os.path.isfile(libUltimateDest):
        print ('\tCygwin-compatible Firebird library already exists at:\n\t  %s'
            % libUltimateDest
          )
    else:
        print (
            '\n\tsetup.py is trying to create cygwin-compatible Firebird'
            ' library at:\n'
            '\t  "%s"'
            % libUltimateDest
          )

        fbClientLibFilename = os.path.join(fbDir, 'lib', 'fbclient_ms.lib')

        origCurDir = os.path.abspath(os.curdir)
        os.chdir(winSysDir)
        try:
            # Create def file containing symbols from fbclient_ms.lib.
            doLibConvCmd(
                '''(echo EXPORTS; nm %s  | grep " T _"'''
                ''' | sed 's/.* T _//g' | sed 's/@.*$//g')'''
                ''' > fbclient.def'''
                % fbClientLibFilename
              )
            # End lame pexports substitute.

            # Create lib file from DLL and the just-generated def file.
            doLibConvCmd(
                'dlltool --dllname fbclient.dll --def fbclient.def --output-lib %s'
                % libName
              )
            os.remove('fbclient.def')
            # Move the lib file to a location where cygwin-GCC's linker will
            # find it.
            shutil.copyfile(libName, libUltimateDest)
            os.remove(libName)
        finally:
            os.chdir(origCurDir)

    assert os.path.isfile(libUltimateDest)
    print '  *** CYGWIN LIBRARY GENERATION : end ***\n'

elif sys.platform.lower() == 'darwin': # Based on Patch 909886 (Piet van oostrum)
    PLATFORM_SPECIFIC_EXTRA_LINKER_ARGS.extend(['-framework', 'Firebird'])
    # Don't override the include dir specified in setup.cfg, if any:
    if not DATABASE_INCLUDE_DIR:
        DATABASE_INCLUDE_DIR = '/Library/Frameworks/Firebird.framework/Headers'

else: # not win32, cygwin, or darwin
    # If the platform isn't Linux, issue a warning.
    if not sys.platform.lower().startswith('linux'):
        sys.stderr.write("Warning:  The kinterbasdb setup code was not"
            " specifically written to support your platform (%s), and"
            " may not work properly.\n"
            % sys.platform
          )

    # Is libcrypt necessary on all POSIX OSes, or just Linux?
    # Until someone informs me otherwise, I'll assume all.
    if os.name == 'posix':
        PLATFORM_SPECIFIC_LIB_NAMES.append('crypt')

    # Verify the various directories (such as include and library dirs) that
    # will be used during compilation.

    # Assumption:
    # This is a Unix-like OS, where a proper installation routine would have
    # placed the database [header, library] files in system-wide dirs.
    # We have no way of knowing beyond the shadow of a doubt whether that
    # has happened (as opposed to the situation on Windows, where we can
    # consult the registry to determine where a binary installer placed its
    # files), so we'll just let the compiler complain if it can't find the
    # [header, library] files.
    # If, on the other hand, the user manually specified the directories, we
    # verify that they exist before invoking the compiler.

    if DATABASE_INCLUDE_DIR: # the user manually specified it
        verifyUserSpecifiedDatabaseIncludeDir()

    if DATABASE_LIB_DIR: # the user manually specified it
        verifyUserSpecifiedDatabaseLibraryDir()

    # 2003.04.12:
    # On FreeBSD 4, the header and library files apparently are not made
    # visible by default.
    # This script attempts to "autodetect" an installation at the default
    # location, but only if:
    # - no DATABASE_HOME_DIR has been manually specified
    # - the default installation directory actually exists
    #
    # This "autodetection" will probably work for some other Unixes as well.
    if not DATABASE_HOME_DIR:
        DEFAULT_FREEBSD_HOME_DIR = '/usr/local/firebird'
        if os.path.isdir(DEFAULT_FREEBSD_HOME_DIR):
            DATABASE_HOME_DIR = DEFAULT_FREEBSD_HOME_DIR
            if not DATABASE_INCLUDE_DIR:
                DATABASE_INCLUDE_DIR = os.path.join(DATABASE_HOME_DIR, 'include')
            if not DATABASE_LIB_DIR:
                DATABASE_LIB_DIR = os.path.join(DATABASE_HOME_DIR, 'lib')

    if not DATABASE_LIB_NAME:
        # 2003.07.29:  If the user hasn't specified the name of the database
        # library, this script will now guess its way from the most recent
        # known library back to the oldest, most conservative option.
        # The goal of this smarter probing is to allow kinterbasdb to build out
        # of the box with Firebird 1.5, without *requiring* the user to modify
        # setup.cfg to specify the correct library name.
        #
        # YYY: This isn't the most proper way to probe for libraries using
        # distutils, but I must admit that propriety isn't my highest priority
        # in this setup script.
        import distutils.command.config as cmd_conf
        import distutils.dist as dist_dist

        class _ConfigUglyHack(cmd_conf.config):
            # _ConfigUglyHack circumvents a distutils problem brought to light
            # on Unix by this script's abuse of the distutils.
            def try_link(self, *args, **kwargs):
                self.compiler.exe_extension = '' # ('' rather than None)
                return cmd_conf.config.try_link(self, *args, **kwargs)

        cfg = _ConfigUglyHack(dist_dist.Distribution())
        for possibleLib in ('fbclient', 'fbembed'):
            if cfg.check_lib(possibleLib):
                DATABASE_LIB_NAME = possibleLib
                break
        else:
            DATABASE_LIB_NAME = 'gds'

# On any non-Windows platform, assume that GCC is the compiler:
compilerIsGCC = ((compilerIsMinGW or not osIsWindows) and 1) or 0

if compilerIsGCC:
    # By default, distutils includes the -fno-strict-aliasing flag on *nix-GCC,
    # but not on MinGW-GCC.
    PLATFORM_SPECIFIC_EXTRA_COMPILER_ARGS.append('-fno-strict-aliasing')

# Now finished with platform-specific compilation parameter setup.

# Create a list of all INCLUDE dirs to be passed to setup():
allIncludeDirs = []

# Add Python include directory:
allIncludeDirs.append(distutils.sysconfig.get_python_inc())

if len(PLATFORM_SPECIFIC_INCLUDE_DIRS) > 0:
    allIncludeDirs.extend(PLATFORM_SPECIFIC_INCLUDE_DIRS)

if DATABASE_INCLUDE_DIR:
    allIncludeDirs.append(DATABASE_INCLUDE_DIR)

# Create a list of all LIB names to be passed to setup():
allLibNames = []
if DATABASE_LIB_NAME:
    allLibNames.append(DATABASE_LIB_NAME)
allLibNames.extend(PLATFORM_SPECIFIC_LIB_NAMES)

# Create a list of all LIB directories to be passed to setup():
allLibDirs = []
if len(PLATFORM_SPECIFIC_LIB_DIRS) > 0:
    allLibDirs.extend(PLATFORM_SPECIFIC_LIB_DIRS)

if DATABASE_LIB_DIR:
    allLibDirs.append(DATABASE_LIB_DIR)

# Create a list of all macro definitions that must be passed to distutils.
allMacroDefs = []

if len(CUSTOM_PREPROCESSOR_DEFS) > 0:
    allMacroDefs.extend(CUSTOM_PREPROCESSOR_DEFS)

# Create a list of all extra options to pass to the compiler, linker.
allExtraCompilerArgs = []

if CHECKED_BUILD:
    if osIsWindows and customCompilerName == 'msvc':
        checkEnabler = '/UNDEBUG'
    else:
        checkEnabler = '-UNDEBUG'
    allExtraCompilerArgs.append(checkEnabler)

if ENABLE_FIELD_PRECISION_DETERMINATION:
    allMacroDefs.append(('DETERMINE_FIELD_PRECISION', '1'))

if len(PLATFORM_SPECIFIC_EXTRA_COMPILER_ARGS) > 0:
    allExtraCompilerArgs.extend(PLATFORM_SPECIFIC_EXTRA_COMPILER_ARGS)

allExtraLinkerArgs = []
if len(PLATFORM_SPECIFIC_EXTRA_LINKER_ARGS) > 0:
    allExtraLinkerArgs.extend(PLATFORM_SPECIFIC_EXTRA_LINKER_ARGS)

extensionModules = [
    distutils.core.Extension( "kinterbasdb._kinterbasdb",
        sources=["_kinterbasdb.c", "_kievents.c"],
        libraries=allLibNames,
        include_dirs=allIncludeDirs,
        library_dirs=allLibDirs,
        define_macros=allMacroDefs,
        extra_compile_args=allExtraCompilerArgs,
        extra_link_args=allExtraLinkerArgs
      ),
  ]


allPythonModules = [
    'kinterbasdb.__init__',
    'kinterbasdb.k_exceptions',

    'kinterbasdb.typeconv_naked',
    'kinterbasdb.typeconv_backcompat',
    'kinterbasdb.typeconv_23plus',

    'kinterbasdb.typeconv_fixed_stdlib',
    'kinterbasdb.typeconv_fixed_fixedpoint',

    'kinterbasdb.typeconv_datetime_naked',
    'kinterbasdb.typeconv_datetime_stdlib',
    'kinterbasdb.typeconv_datetime_mx',

    'kinterbasdb.typeconv_text_unicode',

    # Python 2.1 compatibility hacks:
    'kinterbasdb.typeconv_util_isinstance',
    'kinterbasdb._py21incompat',
  ]

if kinterbasdb_version >= '3.2':
    allPythonModules.extend([
        # 2004.11.10: Python 2.4 decimal support:
        'kinterbasdb.typeconv_24plus',
        'kinterbasdb.typeconv_fixed_decimal',
      ])

# 2003.02.18:
# Build somewhat differently if we're dealing with an IB version before 6.0.
# (Innocent until proven guilty.)
isIBLessThan_6_0 = 0
for incDir in allIncludeDirs:
    headerFilename = os.path.join(incDir, 'ibase.h')
    if os.path.exists(headerFilename):
        # Using the isc_decode_sql_time symbol as the detector is kinda arbitrary.
        if open(headerFilename).read().find('isc_decode_sql_time') == -1:
            isIBLessThan_6_0 = 1
            break

if isIBLessThan_6_0:
    print >> sys.stderr, \
        'WARNING:  Not building the kinterbasdb._services module because' \
        ' IB 5.5 does not support it.'
else:
    # Only include the services module if dealing with >= IB 6.0.
    allPythonModules.append('kinterbasdb.services')
    extensionModules.append(
        distutils.core.Extension( "kinterbasdb._kiservices",
            sources=["_kiservices.c"],
            libraries=allLibNames,
            include_dirs=allIncludeDirs,
            library_dirs=allLibDirs,
            define_macros=allMacroDefs,
            extra_compile_args=allExtraCompilerArgs,
            extra_link_args=allExtraLinkerArgs
          )
      )

# Now we've detected and verified all compilation parameters, and are ready to
# compile.

if DEBUG:
    print '*** SETTINGS DETECTION PHASE COMPLETE; READY FOR BUILD ***'
    print ("\tThe DEBUG flag is enabled, so the setup script stops\n"
           "\t  before actually invoking the distutils setup procedure."
        )
    sys.exit(0)

# The MEAT:
distutils.core.setup(
    name=kinterbasdb_name,
    version=kinterbasdb_version,
    author='''Originally by Alexander Kuznetsov ;
                   rewritten and expanded by David Rushby
                   with contributions from several others
                   (see docs/license.txt for details).''',
    author_email='woodsplitter@rocketmail.com',
    url='http://kinterbasdb.sourceforge.net',
    description='Python DB API 2.0 extension for Firebird and Interbase',
    long_description=
        'kinterbasdb allows Python to access the Firebird and Interbase\n'
        'relational databases according to the interface defined by the\n'
        'Python Database API Specification version 2.0.',
    license='see docs/license.txt',

    package_dir={'kinterbasdb': os.curdir},

    py_modules=allPythonModules,

    ext_modules=extensionModules,
    data_files = [
      # documentation:
      (
        # This path will be interpreted by distutils as being relative to
        # sys.exec_prefix.
        ((osIsWindows and 'Lib') or 'lib/python'+sys.version[:3])
          + '/site-packages/kinterbasdb/docs',
        [
          'docs/index.html',
          'docs/installation-source.html',
          'docs/installation-binary.html',
          'docs/usage.html',
          'docs/changelog.txt',
          'docs/license.txt',
          'docs/Python-DB-API-2.0.html',
          'docs/links.html',
          'docs/global.css',
          'docs/w3c.png',
        ]
      )
    ]
  )
