Files
pymssql/setup.py

414 lines
13 KiB
Python
Executable File

#!/usr/bin/env python
#
# setup.py
#
# Copyright (C) 2009 Damien Churchill <[email protected]>
#
# This program 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 3, or (at your option)
# any later version.
#
# This program is distributed 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 this program. If not, write to:
# The Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor
# Boston, MA 02110-1301, USA.
#
import contextlib
import os
import os.path as osp
import sys
import platform
# Hack to prevent stupid TypeError: 'NoneType' object is not callable error on
# exit of python setup.py test in multiprocessing/util.py _exit_function when
# running python setup.py test (see
# http://www.eby-sarna.com/pipermail/peak/2010-May/003357.html)
try:
import multiprocessing
except ImportError:
pass
sys.path.append(osp.join(osp.dirname(__file__), '.pyrex'))
try:
from setuptools import setup, Extension
from setuptools.command.develop import develop as STDevelopCmd
except ImportError:
import ez_setup
ez_setup.use_setuptools()
from setuptools import setup, Extension
from setuptools.command.develop import develop as STDevelopCmd
# Work around Setuptools' broken (Cython-unaware) monkeypatching
# to support Pyrex. This monkeypatching makes the Cython step get skipped if
# using setuptools (not distribute) and Pyrex is not installed.
# (from https://github.com/sampsyo/pylastfp/blob/master/setup.py)
# http://code.google.com/p/pymssql/issues/detail?id=14
try:
import setuptools.dist
except ImportError:
pass
else:
Extension.__init__ = setuptools.dist._get_unpatched(setuptools.extension.Extension).__init__
ROOT = osp.abspath(osp.dirname(__file__))
def fpath(*parts):
"""Return fully qualified path for parts, e.g. fpath('a', 'b') -> '<this dir>/a/b'"""
return osp.join(ROOT, *parts)
have_c_files = osp.exists(fpath('_mssql.c')) and osp.exists(fpath('pymssql.c'))
from distutils import log
from distutils.cmd import Command
from distutils.command.clean import clean as _clean
from distutils import ccompiler
if have_c_files:
from distutils.command.build_ext import build_ext as _build_ext
else:
#
# Force `setup_requires` stuff like Cython to be installed before proceeding
#
from setuptools.dist import Distribution
Distribution(dict(setup_requires='Cython>=0.19.1'))
from Cython.Distutils import build_ext as _build_ext
import struct
def extract_version():
with open(osp.join(ROOT, 'pymssql_version.h')) as f:
content = f.read()
# Parse file content that looks like this:
# #define PYMSSQL_VERSION "2.0.1"
version = content.split()[2].replace('"', '')
return version
@contextlib.contextmanager
def stdchannel_redirected(stdchannel, dest_filename):
"""
A context manager to temporarily redirect stdout or stderr
e.g.:
with stdchannel_redirected(sys.stderr, os.devnull):
...
"""
try:
oldstdchannel = os.dup(stdchannel.fileno())
dest_file = open(dest_filename, 'w')
os.dup2(dest_file.fileno(), stdchannel.fileno())
yield
finally:
if oldstdchannel is not None:
os.dup2(oldstdchannel, stdchannel.fileno())
if dest_file is not None:
dest_file.close()
def add_dir_if_exists(filtered_dirs, *dirs):
for d in dirs:
if osp.exists(d):
filtered_dirs.append(d)
compiler = ccompiler.new_compiler()
_extra_compile_args = [
'-DMSDBLIB'
]
WINDOWS = False
SYSTEM = platform.system()
print("setup.py: platform.system() => %r" % SYSTEM)
print("setup.py: platform.architecture() => %r" % (platform.architecture(),))
print("setup.py: platform.linux_distribution() => %r" % (platform.linux_distribution(),))
print("setup.py: platform.libc_ver() => %r" % (platform.libc_ver(),))
# 32 bit or 64 bit system?
BITNESS = struct.calcsize("P") * 8
include_dirs = []
library_dirs = []
if sys.platform == 'win32':
WINDOWS = True
else:
FREETDS = None
if sys.platform == 'darwin':
FREETDS = fpath('freetds', 'darwin_%s' % BITNESS)
print("""setup.py: Detected Darwin/Mac OS X.
You can install FreeTDS with Homebrew or MacPorts, or by downloading
and compiling it yourself.
Homebrew (http://brew.sh/)
--------------------------
brew install freetds
MacPorts (http://www.macports.org/)
-----------------------------------
sudo port install freetds
""")
if not os.getenv('PYMSSQL_DONT_BUILD_WITH_BUNDLED_FREETDS'):
if SYSTEM == 'Linux':
FREETDS = fpath('freetds', 'nix_%s' % BITNESS)
elif SYSTEM == 'FreeBSD':
print("""setup.py: Detected FreeBSD.
For FreeBSD, you can install FreeTDS with FreeBSD Ports or by downloading
and compiling it yourself.
""")
if FREETDS and osp.exists(FREETDS) and os.getenv('PYMSSQL_BUILD_WITH_BUNDLED_FREETDS'):
print('setup.py: Using bundled FreeTDS in %s' % FREETDS)
include_dirs.append(osp.join(FREETDS, 'include'))
library_dirs.append(osp.join(FREETDS, 'lib'))
else:
print('setup.py: Not using bundled FreeTDS')
libraries = ['sybdb']
with stdchannel_redirected(sys.stderr, os.devnull):
if compiler.has_function('clock_gettime', libraries=['rt']):
libraries.append('rt')
usr_local = '/usr/local'
if osp.exists(usr_local):
add_dir_if_exists(
include_dirs,
osp.join(usr_local, 'include'),
osp.join(usr_local, 'include/freetds'),
osp.join(usr_local, 'freetds/include')
)
add_dir_if_exists(
library_dirs,
osp.join(usr_local, 'lib'),
osp.join(usr_local, 'lib/freetds'),
osp.join(usr_local, 'freetds/lib')
)
if sys.platform == 'darwin':
fink = '/sw'
if osp.exists(fink):
add_dir_if_exists(include_dirs, osp.join(fink, 'include'))
add_dir_if_exists(library_dirs, osp.join(fink, 'lib'))
macports = '/opt/local'
if osp.exists(macports):
# some mac ports paths
add_dir_if_exists(
include_dirs,
osp.join(macports, 'include'),
osp.join(macports, 'include/freetds'),
osp.join(macports, 'freetds/include')
)
add_dir_if_exists(
library_dirs,
osp.join(macports, 'lib'),
osp.join(macports, 'lib/freetds'),
osp.join(macports, 'freetds/lib')
)
if sys.platform != 'win32':
# Windows uses a different piece of code to detect these
print('setup.py: include_dirs = %r' % include_dirs)
print('setup.py: library_dirs = %r' % library_dirs)
class build_ext(_build_ext):
"""
Subclass the Cython build_ext command so it:
* Can handle different C compilers on Windows
* Links in the libraries we collected
"""
def build_extensions(self):
global library_dirs, include_dirs, libraries
if WINDOWS:
# Detect the compiler so we can specify the correct command line switches
# and libraries
from distutils.cygwinccompiler import Mingw32CCompiler
extra_cc_args = []
if isinstance(self.compiler, Mingw32CCompiler):
# Compiler is Mingw32
freetds_dir = 'ming'
extra_cc_args = [
'-Wl,-allow-multiple-definition',
'-Wl,-subsystem,windows-mthreads',
'-mwindows',
'-Wl,--strip-all'
]
libraries = [
'libiconv', 'iconv',
'sybdb',
'ws2_32', 'wsock32', 'kernel32',
]
else:
# Assume compiler is Visual Studio
freetds_dir = 'vs2008'
libraries = [
'db-lib', 'tds',
'ws2_32', 'wsock32', 'kernel32', 'shell32',
]
FREETDS = fpath('freetds', '{0}_{1}'.format(freetds_dir, BITNESS))
for e in self.extensions:
e.extra_compile_args.extend(extra_cc_args)
e.libraries.extend(libraries)
e.include_dirs.append(osp.join(FREETDS, 'include'))
e.library_dirs.append(osp.join(FREETDS, 'lib'))
else:
for e in self.extensions:
e.libraries.extend(libraries)
_build_ext.build_extensions(self)
class clean(_clean):
"""
Subclass clean so it removes all the Cython generated files.
"""
def run(self):
_clean.run(self)
for ext in self.distribution.ext_modules:
cy_sources = [osp.splitext(s)[0] for s in ext.sources]
for cy_source in cy_sources:
# .so/.pyd files are created in place when using 'develop'
for ext in ('.c', '.so', '.pyd'):
generated = cy_source + ext
if osp.exists(generated):
log.info('removing %s', generated)
os.remove(generated)
class release(Command):
"""
Setuptools command to run all the required commands to perform
a release. This acts differently depending on the platform it
is being run on.
"""
description = "Run all the commands required for a release."
user_options = []
def initialize_options(self):
pass
def finalize_options(self):
pass
def run(self):
if WINDOWS:
self.release_windows()
else:
self.release_unix()
def release_windows(self):
# generate windows source distributions
sdist = self.distribution.get_command_obj('sdist')
sdist.formats = 'zip'
sdist.ensure_finalized()
sdist.run()
# generate a windows egg
self.run_command('bdist_egg')
# generate windows installers
bdist = self.reinitialize_command('bdist')
bdist.formats = 'zip,wininst'
bdist.ensure_finalized()
bdist.run()
def release_unix(self):
# generate linux source distributions
sdist = self.distribution.get_command_obj('sdist')
sdist.formats = 'gztar,bztar'
sdist.ensure_finalized()
sdist.run()
class DevelopCmd(STDevelopCmd):
def run(self):
# add in the nose plugin only when we are using the develop command
self.distribution.entry_points['nose.plugins'] = ['pymssql_config = tests.nose_plugin:ConfigPlugin']
STDevelopCmd.run(self)
def ext_modules():
if have_c_files:
source_extension = 'c'
else:
source_extension = 'pyx'
return [
Extension('_mssql', ['_mssql.%s' % source_extension],
extra_compile_args = _extra_compile_args,
include_dirs = include_dirs,
library_dirs = library_dirs
),
Extension('pymssql', ['pymssql.%s' % source_extension],
extra_compile_args = _extra_compile_args,
include_dirs = include_dirs,
library_dirs = library_dirs
),
]
setup(
name = 'pymssql',
version = extract_version(),
description = 'DB-API interface to Microsoft SQL Server for Python. (new Cython-based version)',
long_description = open('README.rst').read() +"\n\n" + open('ChangeLog_highlights.rst').read(),
author = 'Damien Churchill',
author_email = '[email protected]',
maintainer = 'pymssql Google Group',
maintainer_email = '[email protected]',
license = 'LGPL',
platforms = 'any',
keywords = ['mssql', 'SQL Server', 'database', 'DB-API'],
url = 'http://pymssql.org',
cmdclass = {
'build_ext': build_ext,
'clean': clean,
'release': release,
'develop': DevelopCmd
},
classifiers=[
"Development Status :: 5 - Production/Stable",
"Intended Audience :: Developers",
"License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)",
"Programming Language :: Python",
"Programming Language :: Python :: 2.6",
"Programming Language :: Python :: 2.7",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.2",
"Programming Language :: Python :: 3.3",
"Programming Language :: Python :: 3.4",
"Programming Language :: Python :: Implementation :: CPython",
"Topic :: Database",
"Topic :: Database :: Database Engines/Servers",
"Topic :: Software Development :: Libraries :: Python Modules",
"Operating System :: Microsoft :: Windows",
"Operating System :: POSIX",
"Operating System :: Unix",
],
zip_safe = False,
setup_requires=['setuptools_git'],
tests_require=['nose', 'unittest2'],
test_suite='nose.collector',
ext_modules = ext_modules(),
# don't remove this, otherwise the customization above in DevelopCmd
# will break. You can safely add to it though, if needed.
entry_points = {}
)