From f3526d06303f6ddbd3ebf43191f979021a6f2fcc Mon Sep 17 00:00:00 2001 From: Steve Lacy Date: Mon, 6 Jun 2011 14:01:52 -0700 Subject: [PATCH 01/10] Refactoring of the pg_config detection code in setup.py Pull all state and path searching into it's own class. --- setup.py | 268 +++++++++++++++++++++++++++++-------------------------- 1 file changed, 140 insertions(+), 128 deletions(-) diff --git a/setup.py b/setup.py index f155119d..86a75d6a 100644 --- a/setup.py +++ b/setup.py @@ -45,7 +45,6 @@ Operating System :: Unix # Note: The setup.py must be compatible with both Python 2 and 3 import os -import os.path import sys import re import subprocess @@ -54,7 +53,6 @@ from distutils.errors import DistutilsFileError from distutils.command.build_ext import build_ext from distutils.sysconfig import get_python_inc from distutils.ccompiler import get_default_compiler -from distutils.dep_util import newer_group from distutils.util import get_platform try: from distutils.msvc9compiler import MSVCCompiler @@ -85,21 +83,137 @@ version_flags = ['dt', 'dec'] PLATFORM_IS_WINDOWS = sys.platform.lower().startswith('win') -def get_pg_config(kind, pg_config): - try: - p = subprocess.Popen([pg_config, "--" + kind], - stdin=subprocess.PIPE, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - except OSError: - raise Warning("Unable to find 'pg_config' file in '%s'" % pg_config) - p.stdin.close() - r = p.stdout.readline().strip() - if not r: - raise Warning(p.stderr.readline()) - if not isinstance(r, str): - r = r.decode('ascii') - return r + +class PostgresConfig(): + def __init__(self): + self.pg_config_exe = self.autodetect_pg_config_path() + if self.pg_config_exe is None: + sys.stderr.write("""\ +Error: pg_config executable not found. + +Please add the directory containing pg_config to the PATH +or specify the full executable path with the option: + + python setup.py build_ext --pg-config /path/to/pg_config build ... + +or with the pg_config option in 'setup.cfg'. +""") + sys.exit(1) + + def query(self, attr_name): + """Spawn the pg_config executable, querying for the given config + name, and return the printed value, sanitized. """ + try: + pg_config_process = subprocess.Popen( + [self.pg_config_exe, "--" + attr_name], + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + except OSError: + raise Warning("Unable to find 'pg_config' file in '%s'" % + self.pg_config_exe) + pg_config_process.stdin.close() + result = pg_config_process.stdout.readline().strip() + if not result: + raise Warning(pg_config_process.stderr.readline()) + if not isinstance(result, str): + result = result.decode('ascii') + return result + + def autodetect_pg_config_path(self): + """Find and return the path to the pg_config executable.""" + if PLATFORM_IS_WINDOWS: + return self.autodetect_pg_config_path_windows() + else: + return self.autodetect_pg_config_path_posix() + + def autodetect_pg_config_path_posix(self): + """Return pg_config from the current PATH""" + exename = 'pg_config' + for dir in os.environ['PATH'].split(os.pathsep): + fn = os.path.join(dir, exename) + if os.path.isfile(fn): + return fn + return None + + def autodetect_pg_config_path_windows(self): + """Attempt several different ways of finding the pg_config + executable on Windows, and return its full path, if found.""" + # Find the first PostgreSQL installation listed in the registry and + # return the full path to its pg_config utility. + # + # This autodetection is performed *only* if the following conditions + # hold: + # + # 1) The pg_config utility is not already available on the PATH: + if os.popen('pg_config').close() is None: # .close()->None == success + return None + # 2) The user has not specified any of the following settings in + # setup.cfg: + # - pg_config + # - include_dirs + # - library_dirs + for setting_name in ('pg_config', 'include_dirs', 'library_dirs'): + try: + val = parser.get('build_ext', setting_name) + except configparser.NoOptionError: + pass + else: + if val.strip() != '': + return None + # end of guard conditions + + try: + import winreg + except ImportError: + import _winreg as winreg + + pg_inst_base_dir = None + pg_config_path = None + + reg = winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) + try: + pg_inst_list_key = winreg.OpenKey(reg, + 'SOFTWARE\\PostgreSQL\\Installations' + ) + except EnvironmentError: + pg_inst_list_key = None + + if pg_inst_list_key is not None: + try: + # Determine the name of the first subkey, if any: + try: + first_sub_key_name = winreg.EnumKey(pg_inst_list_key, 0) + except EnvironmentError: + first_sub_key_name = None + + if first_sub_key_name is not None: + pg_first_inst_key = winreg.OpenKey(reg, + 'SOFTWARE\\PostgreSQL\\Installations\\' + + first_sub_key_name + ) + try: + pg_inst_base_dir = winreg.QueryValueEx( + pg_first_inst_key, 'Base Directory' + )[0] + finally: + winreg.CloseKey(pg_first_inst_key) + finally: + winreg.CloseKey(pg_inst_list_key) + + if pg_inst_base_dir and os.path.exists(pg_inst_base_dir): + pg_config_path = os.path.join(pg_inst_base_dir, 'bin', + 'pg_config.exe' + ) + # Support unicode paths, if this version of Python provides the + # necessary infrastructure: + if sys.version_info[0] < 3 \ + and hasattr(sys, 'getfilesystemencoding'): + pg_config_path = pg_config_path.encode( + sys.getfilesystemencoding()) + + return pg_config_path + class psycopg_build_ext(build_ext): """Conditionally complement the setup.cfg options file. @@ -126,6 +240,10 @@ class psycopg_build_ext(build_ext): boolean_options = build_ext.boolean_options[:] boolean_options.extend(('use-pydatetime', 'have-ssl', 'static-libpq')) + def __init__(self, *args, **kwargs): + build_ext.__init__(self, *args, **kwargs) + self.pg_config = PostgresConfig() + def initialize_options(self): build_ext.initialize_options(self) self.use_pg_dll = 1 @@ -134,7 +252,6 @@ class psycopg_build_ext(build_ext): self.use_pydatetime = 1 self.have_ssl = have_ssl self.static_libpq = static_libpq - self.pg_config = None def get_compiler(self): """Return the name of the C compiler used to compile extensions. @@ -153,9 +270,6 @@ class psycopg_build_ext(build_ext): name = get_default_compiler() return name - def get_pg_config(self, kind): - return get_pg_config(kind, self.pg_config) - def get_export_symbols(self, ext): # Fix MSVC seeing two of the same export symbols. if self.get_compiler().lower().startswith('msvc'): @@ -248,38 +362,24 @@ class psycopg_build_ext(build_ext): def finalize_options(self): """Complete the build system configuation.""" build_ext.finalize_options(self) - if self.pg_config is None: - self.pg_config = self.autodetect_pg_config_path() - if self.pg_config is None: - sys.stderr.write("""\ -Error: pg_config executable not found. - -Please add the directory containing pg_config to the PATH -or specify the full executable path with the option: - - python setup.py build_ext --pg-config /path/to/pg_config build ... - -or with the pg_config option in 'setup.cfg'. -""") - sys.exit(1) self.include_dirs.append(".") if self.static_libpq: if not self.link_objects: self.link_objects = [] self.link_objects.append( - os.path.join(self.get_pg_config("libdir"), "libpq.a")) + os.path.join(self.pg_config.query("libdir"), "libpq.a")) else: self.libraries.append("pq") try: - self.library_dirs.append(self.get_pg_config("libdir")) - self.include_dirs.append(self.get_pg_config("includedir")) - self.include_dirs.append(self.get_pg_config("includedir-server")) + self.library_dirs.append(self.pg_config.query("libdir")) + self.include_dirs.append(self.pg_config.query("includedir")) + self.include_dirs.append(self.pg_config.query("includedir-server")) try: # Here we take a conservative approach: we suppose that # *at least* PostgreSQL 7.4 is available (this is the only # 7.x series supported by psycopg 2) - pgversion = self.get_pg_config("version").split()[1] + pgversion = self.pg_config.query("version").split()[1] except: pgversion = "7.4.0" @@ -305,94 +405,6 @@ or with the pg_config option in 'setup.cfg'. if hasattr(self, "finalize_" + sys.platform): getattr(self, "finalize_" + sys.platform)() - def autodetect_pg_config_path(self): - if PLATFORM_IS_WINDOWS: - return self.autodetect_pg_config_path_windows() - else: - return self.autodetect_pg_config_path_posix() - - def autodetect_pg_config_path_posix(self): - exename = 'pg_config' - for dir in os.environ['PATH'].split(os.pathsep): - fn = os.path.join(dir, exename) - if os.path.isfile(fn): - return fn - - def autodetect_pg_config_path_windows(self): - # Find the first PostgreSQL installation listed in the registry and - # return the full path to its pg_config utility. - # - # This autodetection is performed *only* if the following conditions - # hold: - # - # 1) The pg_config utility is not already available on the PATH: - if os.popen('pg_config').close() is None: # .close()->None == success - return None - # 2) The user has not specified any of the following settings in - # setup.cfg: - # - pg_config - # - include_dirs - # - library_dirs - for settingName in ('pg_config', 'include_dirs', 'library_dirs'): - try: - val = parser.get('build_ext', settingName) - except configparser.NoOptionError: - pass - else: - if val.strip() != '': - return None - # end of guard conditions - - try: - import winreg - except ImportError: - import _winreg as winreg - - pg_inst_base_dir = None - pg_config_path = None - - reg = winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) - try: - pg_inst_list_key = winreg.OpenKey(reg, - 'SOFTWARE\\PostgreSQL\\Installations' - ) - except EnvironmentError: - pg_inst_list_key = None - - if pg_inst_list_key is not None: - try: - # Determine the name of the first subkey, if any: - try: - first_sub_key_name = winreg.EnumKey(pg_inst_list_key, 0) - except EnvironmentError: - first_sub_key_name = None - - if first_sub_key_name is not None: - pg_first_inst_key = winreg.OpenKey(reg, - 'SOFTWARE\\PostgreSQL\\Installations\\' - + first_sub_key_name - ) - try: - pg_inst_base_dir = winreg.QueryValueEx( - pg_first_inst_key, 'Base Directory' - )[0] - finally: - winreg.CloseKey(pg_first_inst_key) - finally: - winreg.CloseKey(pg_inst_list_key) - - if pg_inst_base_dir and os.path.exists(pg_inst_base_dir): - pg_config_path = os.path.join(pg_inst_base_dir, 'bin', - 'pg_config.exe' - ) - # Support unicode paths, if this version of Python provides the - # necessary infrastructure: - if sys.version_info[0] < 3 \ - and hasattr(sys, 'getfilesystemencoding'): - pg_config_path = pg_config_path.encode( - sys.getfilesystemencoding()) - - return pg_config_path # let's start with macro definitions (the ones not already in setup.cfg) define_macros = [] From c826446ff839f2a4d0d9d0327b57dfd973f19ff7 Mon Sep 17 00:00:00 2001 From: Steve Lacy Date: Mon, 6 Jun 2011 14:19:27 -0700 Subject: [PATCH 02/10] Clean up a bunch of lint from pylint/pyflakes/pep8 checking - Don't override global variable name "ext" (use "extension" as function argument names) - Improve function naming (get_compiler -> get_compiler_name) - Other misc operator spacing and 80-column violation cleanup. - Remove unneeded import (DistUtilsFileError) --- setup.py | 93 ++++++++++++++++++++++++++++++-------------------------- 1 file changed, 50 insertions(+), 43 deletions(-) diff --git a/setup.py b/setup.py index 86a75d6a..033e57b6 100644 --- a/setup.py +++ b/setup.py @@ -49,7 +49,6 @@ import sys import re import subprocess from distutils.core import setup, Extension -from distutils.errors import DistutilsFileError from distutils.command.build_ext import build_ext from distutils.sysconfig import get_python_inc from distutils.ccompiler import get_default_compiler @@ -130,10 +129,10 @@ or with the pg_config option in 'setup.cfg'. def autodetect_pg_config_path_posix(self): """Return pg_config from the current PATH""" exename = 'pg_config' - for dir in os.environ['PATH'].split(os.pathsep): - fn = os.path.join(dir, exename) - if os.path.isfile(fn): - return fn + for dir_name in os.environ['PATH'].split(os.pathsep): + fullpath = os.path.join(dir_name, exename) + if os.path.isfile(fullpath): + return fullpath return None def autodetect_pg_config_path_windows(self): @@ -253,7 +252,7 @@ class psycopg_build_ext(build_ext): self.have_ssl = have_ssl self.static_libpq = static_libpq - def get_compiler(self): + def get_compiler_name(self): """Return the name of the C compiler used to compile extensions. If a compiler was not explicitly set (on the command line, for @@ -270,15 +269,15 @@ class psycopg_build_ext(build_ext): name = get_default_compiler() return name - def get_export_symbols(self, ext): + def get_export_symbols(self, extension): # Fix MSVC seeing two of the same export symbols. - if self.get_compiler().lower().startswith('msvc'): + if self.get_compiler_name().lower().startswith('msvc'): return [] else: - return build_ext.get_export_symbols(self, ext) + return build_ext.get_export_symbols(self, extension) - def build_extension(self, ext): - build_ext.build_extension(self, ext) + def build_extension(self, extension): + build_ext.build_extension(self, extension) # For Python versions that use MSVC compiler 2008, re-insert the # manifest into the resulting .pyd file. @@ -288,19 +287,21 @@ class psycopg_build_ext(build_ext): manifest = '_psycopg.vc9.x86.manifest' if platform == 'win-amd64': manifest = '_psycopg.vc9.amd64.manifest' - self.compiler.spawn(['mt.exe', '-nologo', '-manifest', - os.path.join('psycopg', manifest), - '-outputresource:%s;2' % (os.path.join(self.build_lib, 'psycopg2', '_psycopg.pyd'))]) + self.compiler.spawn( + ['mt.exe', '-nologo', '-manifest', + os.path.join('psycopg', manifest), + '-outputresource:%s;2' % ( + os.path.join(self.build_lib, + 'psycopg2', '_psycopg.pyd'))]) def finalize_win32(self): """Finalize build system configuration on win32 platform.""" - import struct sysVer = sys.version_info[:2] # Add compiler-specific arguments: extra_compiler_args = [] - compiler_name = self.get_compiler().lower() + compiler_name = self.get_compiler_name().lower() compiler_is_msvc = compiler_name.startswith('msvc') compiler_is_mingw = compiler_name.startswith('mingw') if compiler_is_mingw: @@ -315,18 +316,18 @@ class psycopg_build_ext(build_ext): extra_compiler_args.append('-fno-strict-aliasing') # Force correct C runtime library linkage: - if sysVer <= (2,3): + if sysVer <= (2, 3): # Yes: 'msvcr60', rather than 'msvcrt', is the correct value # on the line below: self.libraries.append('msvcr60') - elif sysVer in ((2,4), (2,5)): + elif sysVer in ((2, 4), (2, 5)): self.libraries.append('msvcr71') # Beyond Python 2.5, we take our chances on the default C runtime # library, because we don't know what compiler those future # versions of Python will use. - for exten in ext: # ext is a global list of Extension objects - exten.extra_compile_args.extend(extra_compiler_args) + for extension in ext: # ext is a global list of Extension objects + extension.extra_compile_args.extend(extra_compiler_args) # End of add-compiler-specific arguments section. self.libraries.append("ws2_32") @@ -356,8 +357,9 @@ class psycopg_build_ext(build_ext): def finalize_linux2(self): """Finalize build system configuration on GNU/Linux platform.""" # tell piro that GCC is fine and dandy, but not so MS compilers - for ext in self.extensions: - ext.extra_compile_args.append('-Wdeclaration-after-statement') + for extension in self.extensions: + extension.extra_compile_args.append( + '-Wdeclaration-after-statement') def finalize_options(self): """Complete the build system configuation.""" @@ -365,7 +367,8 @@ class psycopg_build_ext(build_ext): self.include_dirs.append(".") if self.static_libpq: - if not self.link_objects: self.link_objects = [] + if not self.link_objects: + self.link_objects = [] self.link_objects.append( os.path.join(self.pg_config.query("libdir"), "libpq.a")) else: @@ -383,7 +386,8 @@ class psycopg_build_ext(build_ext): except: pgversion = "7.4.0" - verre = re.compile(r"(\d+)\.(\d+)(?:(?:\.(\d+))|(devel|(alpha|beta|rc)\d+))") + verre = re.compile( + r"(\d+)\.(\d+)(?:(?:\.(\d+))|(devel|(alpha|beta|rc)\d+))") m = verre.match(pgversion) if m: pgmajor, pgminor, pgpatch = m.group(1, 2, 3) @@ -398,7 +402,7 @@ class psycopg_build_ext(build_ext): define_macros.append(("PG_VERSION_HEX", "0x%02X%02X%02X" % (int(pgmajor), int(pgminor), int(pgpatch)))) except Warning: - w = sys.exc_info()[1] # work around py 2/3 different syntax + w = sys.exc_info()[1] # work around py 2/3 different syntax sys.stderr.write("Error: %s\n" % w) sys.exit(1) @@ -411,7 +415,8 @@ define_macros = [] include_dirs = [] # gather information to build the extension module -ext = [] ; data_files = [] +ext = [] +data_files = [] # sources @@ -464,7 +469,7 @@ else: if os.path.exists(mxincludedir): # Build the support for mx: we will check at runtime if it can be imported include_dirs.append(mxincludedir) - define_macros.append(('HAVE_MXDATETIME','1')) + define_macros.append(('HAVE_MXDATETIME', '1')) sources.append('adapter_mxdatetime.c') depends.extend(['adapter_mxdatetime.h', 'typecast_mxdatetime.c']) have_mxdatetime = True @@ -472,18 +477,21 @@ if os.path.exists(mxincludedir): # now decide which package will be the default for date/time typecasts if have_pydatetime and (use_pydatetime or not have_mxdatetime): - define_macros.append(('PSYCOPG_DEFAULT_PYDATETIME','1')) + define_macros.append(('PSYCOPG_DEFAULT_PYDATETIME', '1')) elif have_mxdatetime: - define_macros.append(('PSYCOPG_DEFAULT_MXDATETIME','1')) + define_macros.append(('PSYCOPG_DEFAULT_MXDATETIME', '1')) else: - def e(msg): - sys.stderr.write("error: " + msg + "\n") - e("psycopg requires a datetime module:") - e(" mx.DateTime module not found") - e(" python datetime module not found") - e("Note that psycopg needs the module headers and not just the module") - e("itself. If you installed Python or mx.DateTime from a binary package") - e("you probably need to install its companion -dev or -devel package.") + error_message = """\ +psycopg requires a datetime module: + mx.DateTime module not found + python datetime module not found + +Note that psycopg needs the module headers and not just the module +itself. If you installed Python or mx.DateTime from a binary package +you probably need to install its companion -dev or -devel package.""" + + for line in error_message.split("\n"): + sys.stderr.write("error: " + line) sys.exit(1) # generate a nice version string to avoid confusion when users report bugs @@ -497,9 +505,9 @@ else: PSYCOPG_VERSION_EX = PSYCOPG_VERSION if not PLATFORM_IS_WINDOWS: - define_macros.append(('PSYCOPG_VERSION', '"'+PSYCOPG_VERSION_EX+'"')) + define_macros.append(('PSYCOPG_VERSION', '"' + PSYCOPG_VERSION_EX + '"')) else: - define_macros.append(('PSYCOPG_VERSION', '\\"'+PSYCOPG_VERSION_EX+'\\"')) + define_macros.append(('PSYCOPG_VERSION', '\\"' + PSYCOPG_VERSION_EX + '\\"')) if parser.has_option('build_ext', 'have_ssl'): have_ssl = int(parser.get('build_ext', 'have_ssl')) @@ -537,17 +545,16 @@ setup(name="psycopg2", author="Federico Di Gregorio", author_email="fog@initd.org", url="http://initd.org/psycopg/", - download_url = download_url, + download_url=download_url, license="GPL with exceptions or ZPL", - platforms = ["any"], + platforms=["any"], description=__doc__.split("\n")[0], long_description="\n".join(__doc__.split("\n")[2:]), classifiers=[x for x in classifiers.split("\n") if x], data_files=data_files, - package_dir={'psycopg2':'lib', 'psycopg2.tests': 'tests'}, + package_dir={'psycopg2': 'lib', 'psycopg2.tests': 'tests'}, packages=['psycopg2', 'psycopg2.tests'], cmdclass={ 'build_ext': psycopg_build_ext, 'build_py': build_py, }, ext_modules=ext) - From ef1891539633a867bd7d2fb389bf0603af0c4410 Mon Sep 17 00:00:00 2001 From: Steve Lacy Date: Mon, 6 Jun 2011 14:30:47 -0700 Subject: [PATCH 03/10] Unify the way the MSVC compiler is detected And do it only once in __init__ instead of different ways and in different places. --- setup.py | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/setup.py b/setup.py index 033e57b6..8a7a4137 100644 --- a/setup.py +++ b/setup.py @@ -53,10 +53,7 @@ from distutils.command.build_ext import build_ext from distutils.sysconfig import get_python_inc from distutils.ccompiler import get_default_compiler from distutils.util import get_platform -try: - from distutils.msvc9compiler import MSVCCompiler -except ImportError: - MSVCCompiler = None + try: from distutils.command.build_py import build_py_2to3 as build_py except ImportError: @@ -242,6 +239,9 @@ class psycopg_build_ext(build_ext): def __init__(self, *args, **kwargs): build_ext.__init__(self, *args, **kwargs) self.pg_config = PostgresConfig() + compiler_name = self.get_compiler_name().lower() + self.compiler_is_msvc = compiler_name.startswith('msvc') + self.compiler_is_mingw = compiler_name.startswith('mingw') def initialize_options(self): build_ext.initialize_options(self) @@ -271,7 +271,7 @@ class psycopg_build_ext(build_ext): def get_export_symbols(self, extension): # Fix MSVC seeing two of the same export symbols. - if self.get_compiler_name().lower().startswith('msvc'): + if self.compiler_is_msvc: return [] else: return build_ext.get_export_symbols(self, extension) @@ -281,7 +281,7 @@ class psycopg_build_ext(build_ext): # For Python versions that use MSVC compiler 2008, re-insert the # manifest into the resulting .pyd file. - if MSVCCompiler and isinstance(self.compiler, MSVCCompiler): + if self.compiler_is_msvc: platform = get_platform() # Default to the x86 manifest manifest = '_psycopg.vc9.x86.manifest' @@ -301,10 +301,7 @@ class psycopg_build_ext(build_ext): # Add compiler-specific arguments: extra_compiler_args = [] - compiler_name = self.get_compiler_name().lower() - compiler_is_msvc = compiler_name.startswith('msvc') - compiler_is_mingw = compiler_name.startswith('mingw') - if compiler_is_mingw: + if self.compiler_is_mingw: # Default MinGW compilation of Python extensions on Windows uses # only -O: extra_compiler_args.append('-O3') @@ -332,7 +329,7 @@ class psycopg_build_ext(build_ext): self.libraries.append("ws2_32") self.libraries.append("advapi32") - if compiler_is_msvc: + if self.compiler_is_msvc: # MSVC requires an explicit "libpq" self.libraries.remove("pq") self.libraries.append("secur32") From 9a7aee3d05fa0df73a2e6bf792b6c3f2bb2a81b9 Mon Sep 17 00:00:00 2001 From: Daniele Varrazzo Date: Tue, 7 Jun 2011 08:58:54 +0100 Subject: [PATCH 04/10] Fixed compatibility problem in setup for Python 2.4 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 8a7a4137..ae4d297c 100644 --- a/setup.py +++ b/setup.py @@ -80,7 +80,7 @@ version_flags = ['dt', 'dec'] PLATFORM_IS_WINDOWS = sys.platform.lower().startswith('win') -class PostgresConfig(): +class PostgresConfig: def __init__(self): self.pg_config_exe = self.autodetect_pg_config_path() if self.pg_config_exe is None: From 57a6cf3144d3278a4a7f3a5fc0553975a4013764 Mon Sep 17 00:00:00 2001 From: Steve Lacy Date: Tue, 7 Jun 2011 09:50:52 -0700 Subject: [PATCH 05/10] Code to find an executable on the current PATH refactored --- setup.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/setup.py b/setup.py index ae4d297c..277be61c 100644 --- a/setup.py +++ b/setup.py @@ -116,6 +116,15 @@ or with the pg_config option in 'setup.cfg'. result = result.decode('ascii') return result + def find_on_path(self, exename, path_directories=None): + if not path_directories: + path_directories = os.environ['PATH'].split(os.pathsep) + for dir_name in path_directories: + fullpath = os.path.join(dir_name, exename) + if os.path.isfile(fullpath): + return fullpath + return None + def autodetect_pg_config_path(self): """Find and return the path to the pg_config executable.""" if PLATFORM_IS_WINDOWS: @@ -125,12 +134,7 @@ or with the pg_config option in 'setup.cfg'. def autodetect_pg_config_path_posix(self): """Return pg_config from the current PATH""" - exename = 'pg_config' - for dir_name in os.environ['PATH'].split(os.pathsep): - fullpath = os.path.join(dir_name, exename) - if os.path.isfile(fullpath): - return fullpath - return None + return self.find_on_path('pg_config') def autodetect_pg_config_path_windows(self): """Attempt several different ways of finding the pg_config From 46dc7e66f806c8b36fba1d721a0abcd50bcf8a1d Mon Sep 17 00:00:00 2001 From: Steve Lacy Date: Tue, 7 Jun 2011 10:53:10 -0700 Subject: [PATCH 06/10] Fix pg_config commandline option broken in a previous change - Make sure to declare self.pg_config in initialize_options. - Don't declare PostgresConfig in __init__, as its scope is limited. - Pass build_ext instance to PostgresConfig to avoid having to call the option parser directly. --- setup.py | 37 ++++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/setup.py b/setup.py index 277be61c..6aa4f68b 100644 --- a/setup.py +++ b/setup.py @@ -81,8 +81,11 @@ PLATFORM_IS_WINDOWS = sys.platform.lower().startswith('win') class PostgresConfig: - def __init__(self): - self.pg_config_exe = self.autodetect_pg_config_path() + def __init__(self, build_ext): + self.build_ext = build_ext + self.pg_config_exe = self.build_ext.pg_config + if not self.pg_config_exe: + self.pg_config_exe = self.autodetect_pg_config_path() if self.pg_config_exe is None: sys.stderr.write("""\ Error: pg_config executable not found. @@ -148,19 +151,17 @@ or with the pg_config option in 'setup.cfg'. # 1) The pg_config utility is not already available on the PATH: if os.popen('pg_config').close() is None: # .close()->None == success return None + # 2) The user has not specified any of the following settings in # setup.cfg: # - pg_config # - include_dirs # - library_dirs - for setting_name in ('pg_config', 'include_dirs', 'library_dirs'): - try: - val = parser.get('build_ext', setting_name) - except configparser.NoOptionError: - pass - else: - if val.strip() != '': - return None + + if (self.build_ext.pg_config + or self.build_ext.include_dirs + or self.build_ext.library_dirs): + return None # end of guard conditions try: @@ -242,7 +243,6 @@ class psycopg_build_ext(build_ext): def __init__(self, *args, **kwargs): build_ext.__init__(self, *args, **kwargs) - self.pg_config = PostgresConfig() compiler_name = self.get_compiler_name().lower() self.compiler_is_msvc = compiler_name.startswith('msvc') self.compiler_is_mingw = compiler_name.startswith('mingw') @@ -255,6 +255,7 @@ class psycopg_build_ext(build_ext): self.use_pydatetime = 1 self.have_ssl = have_ssl self.static_libpq = static_libpq + self.pg_config = None def get_compiler_name(self): """Return the name of the C compiler used to compile extensions. @@ -366,24 +367,26 @@ class psycopg_build_ext(build_ext): """Complete the build system configuation.""" build_ext.finalize_options(self) + pg_config_helper = PostgresConfig(self) + self.include_dirs.append(".") if self.static_libpq: - if not self.link_objects: + if not hasattr(self, 'link_objects'): self.link_objects = [] self.link_objects.append( - os.path.join(self.pg_config.query("libdir"), "libpq.a")) + os.path.join(pg_config_helper.query("libdir"), "libpq.a")) else: self.libraries.append("pq") try: - self.library_dirs.append(self.pg_config.query("libdir")) - self.include_dirs.append(self.pg_config.query("includedir")) - self.include_dirs.append(self.pg_config.query("includedir-server")) + self.library_dirs.append(pg_config_helper.query("libdir")) + self.include_dirs.append(pg_config_helper.query("includedir")) + self.include_dirs.append(pg_config_helper.query("includedir-server")) try: # Here we take a conservative approach: we suppose that # *at least* PostgreSQL 7.4 is available (this is the only # 7.x series supported by psycopg 2) - pgversion = self.pg_config.query("version").split()[1] + pgversion = pg_config_helper.query("version").split()[1] except: pgversion = "7.4.0" From 575afa2e0e0e98de996c8ac6108cd6e15f3b0ff4 Mon Sep 17 00:00:00 2001 From: Steve Lacy Date: Tue, 7 Jun 2011 11:25:59 -0700 Subject: [PATCH 07/10] Properly detect pg_config.exe on Windows. I'm fairly certain this is correct, submitting so I can pull on my Windows box and do some testing there. --- setup.py | 84 ++++++++++++++++++++++++-------------------------------- 1 file changed, 36 insertions(+), 48 deletions(-) diff --git a/setup.py b/setup.py index 6aa4f68b..bd05a04a 100644 --- a/setup.py +++ b/setup.py @@ -133,37 +133,22 @@ or with the pg_config option in 'setup.cfg'. if PLATFORM_IS_WINDOWS: return self.autodetect_pg_config_path_windows() else: - return self.autodetect_pg_config_path_posix() - - def autodetect_pg_config_path_posix(self): - """Return pg_config from the current PATH""" - return self.find_on_path('pg_config') + return self.find_on_path('pg_config') def autodetect_pg_config_path_windows(self): """Attempt several different ways of finding the pg_config executable on Windows, and return its full path, if found.""" - # Find the first PostgreSQL installation listed in the registry and - # return the full path to its pg_config utility. - # - # This autodetection is performed *only* if the following conditions - # hold: - # - # 1) The pg_config utility is not already available on the PATH: - if os.popen('pg_config').close() is None: # .close()->None == success - return None - # 2) The user has not specified any of the following settings in - # setup.cfg: - # - pg_config - # - include_dirs - # - library_dirs + # This code only runs if they have not specified a pg_config option + # in the config file or via the commandline. - if (self.build_ext.pg_config - or self.build_ext.include_dirs - or self.build_ext.library_dirs): - return None - # end of guard conditions + # First, check for pg_config.exe on the PATH, and use that if found. + pg_config_exe = self.find_on_path('pg_config.exe') + if pg_config_exe: + return pg_config_exe + # Now, try looking in the Windows Registry to find a PostgreSQL + # installation, and infer the path from that. try: import winreg except ImportError: @@ -180,32 +165,35 @@ or with the pg_config option in 'setup.cfg'. except EnvironmentError: pg_inst_list_key = None - if pg_inst_list_key is not None: - try: - # Determine the name of the first subkey, if any: - try: - first_sub_key_name = winreg.EnumKey(pg_inst_list_key, 0) - except EnvironmentError: - first_sub_key_name = None + if not pg_inst_list_key: + # No PostgreSQL installation, as best as we can tell. + return None - if first_sub_key_name is not None: - pg_first_inst_key = winreg.OpenKey(reg, - 'SOFTWARE\\PostgreSQL\\Installations\\' - + first_sub_key_name - ) - try: - pg_inst_base_dir = winreg.QueryValueEx( - pg_first_inst_key, 'Base Directory' - )[0] - finally: - winreg.CloseKey(pg_first_inst_key) - finally: - winreg.CloseKey(pg_inst_list_key) + + try: + # Determine the name of the first subkey, if any: + try: + first_sub_key_name = winreg.EnumKey(pg_inst_list_key, 0) + except EnvironmentError: + first_sub_key_name = None + + if first_sub_key_name is not None: + pg_first_inst_key = winreg.OpenKey(reg, + 'SOFTWARE\\PostgreSQL\\Installations\\' + + first_sub_key_name + ) + try: + pg_inst_base_dir = winreg.QueryValueEx( + pg_first_inst_key, 'Base Directory' + )[0] + finally: + winreg.CloseKey(pg_first_inst_key) + finally: + winreg.CloseKey(pg_inst_list_key) if pg_inst_base_dir and os.path.exists(pg_inst_base_dir): - pg_config_path = os.path.join(pg_inst_base_dir, 'bin', - 'pg_config.exe' - ) + pg_config_path = os.path.join( + pg_inst_base_dir, 'bin', 'pg_config.exe') # Support unicode paths, if this version of Python provides the # necessary infrastructure: if sys.version_info[0] < 3 \ @@ -285,7 +273,7 @@ class psycopg_build_ext(build_ext): build_ext.build_extension(self, extension) # For Python versions that use MSVC compiler 2008, re-insert the - # manifest into the resulting .pyd file. + # manifest into the resulting .pyd file. if self.compiler_is_msvc: platform = get_platform() # Default to the x86 manifest From d0b97feab3101f32a95a558be1f6b02ef0960347 Mon Sep 17 00:00:00 2001 From: Daniele Varrazzo Date: Tue, 7 Jun 2011 22:15:37 +0100 Subject: [PATCH 08/10] More cleanup in pg_config detection from Windows registry --- setup.py | 59 ++++++++++++++++++++++++++++---------------------------- 1 file changed, 29 insertions(+), 30 deletions(-) diff --git a/setup.py b/setup.py index bd05a04a..9dee671a 100644 --- a/setup.py +++ b/setup.py @@ -149,57 +149,56 @@ or with the pg_config option in 'setup.cfg'. # Now, try looking in the Windows Registry to find a PostgreSQL # installation, and infer the path from that. + pg_config_exe = self._get_pg_config_from_registry() + if pg_config_exe: + return pg_config_exe + + return None + + def _get_pg_config_from_registry(self): try: import winreg except ImportError: import _winreg as winreg - pg_inst_base_dir = None - pg_config_path = None - reg = winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) try: pg_inst_list_key = winreg.OpenKey(reg, - 'SOFTWARE\\PostgreSQL\\Installations' - ) + 'SOFTWARE\\PostgreSQL\\Installations') except EnvironmentError: - pg_inst_list_key = None - - if not pg_inst_list_key: # No PostgreSQL installation, as best as we can tell. return None - try: # Determine the name of the first subkey, if any: try: first_sub_key_name = winreg.EnumKey(pg_inst_list_key, 0) except EnvironmentError: - first_sub_key_name = None + return None + + pg_first_inst_key = winreg.OpenKey(reg, + 'SOFTWARE\\PostgreSQL\\Installations\\' + + first_sub_key_name) + try: + pg_inst_base_dir = winreg.QueryValueEx( + pg_first_inst_key, 'Base Directory')[0] + finally: + winreg.CloseKey(pg_first_inst_key) - if first_sub_key_name is not None: - pg_first_inst_key = winreg.OpenKey(reg, - 'SOFTWARE\\PostgreSQL\\Installations\\' - + first_sub_key_name - ) - try: - pg_inst_base_dir = winreg.QueryValueEx( - pg_first_inst_key, 'Base Directory' - )[0] - finally: - winreg.CloseKey(pg_first_inst_key) finally: winreg.CloseKey(pg_inst_list_key) - if pg_inst_base_dir and os.path.exists(pg_inst_base_dir): - pg_config_path = os.path.join( - pg_inst_base_dir, 'bin', 'pg_config.exe') - # Support unicode paths, if this version of Python provides the - # necessary infrastructure: - if sys.version_info[0] < 3 \ - and hasattr(sys, 'getfilesystemencoding'): - pg_config_path = pg_config_path.encode( - sys.getfilesystemencoding()) + pg_config_path = os.path.join( + pg_inst_base_dir, 'bin', 'pg_config.exe') + if not os.path.exists(pg_config_path): + return None + + # Support unicode paths, if this version of Python provides the + # necessary infrastructure: + if sys.version_info[0] < 3 \ + and hasattr(sys, 'getfilesystemencoding'): + pg_config_path = pg_config_path.encode( + sys.getfilesystemencoding()) return pg_config_path From dc92161dda754d0bf8a5017c5ab3728e9981a891 Mon Sep 17 00:00:00 2001 From: Daniele Varrazzo Date: Tue, 7 Jun 2011 23:28:17 +0100 Subject: [PATCH 09/10] Delay detection of the compiler in setup.py At init time, build_ext is not configured, so neither the --compiler option nor settings in setup.cfg/distutil.cfg is effective. Steve, I told you distutils was a mess :) --- setup.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/setup.py b/setup.py index 9dee671a..4840e585 100644 --- a/setup.py +++ b/setup.py @@ -230,9 +230,6 @@ class psycopg_build_ext(build_ext): def __init__(self, *args, **kwargs): build_ext.__init__(self, *args, **kwargs) - compiler_name = self.get_compiler_name().lower() - self.compiler_is_msvc = compiler_name.startswith('msvc') - self.compiler_is_mingw = compiler_name.startswith('mingw') def initialize_options(self): build_ext.initialize_options(self) @@ -244,6 +241,12 @@ class psycopg_build_ext(build_ext): self.static_libpq = static_libpq self.pg_config = None + def compiler_is_msvc(self): + return self.get_compiler_name().lower().startswith('msvc') + + def compiler_is_mingw(self): + return self.get_compiler_name().lower().startswith('mingw') + def get_compiler_name(self): """Return the name of the C compiler used to compile extensions. @@ -263,7 +266,7 @@ class psycopg_build_ext(build_ext): def get_export_symbols(self, extension): # Fix MSVC seeing two of the same export symbols. - if self.compiler_is_msvc: + if self.compiler_is_msvc(): return [] else: return build_ext.get_export_symbols(self, extension) @@ -273,7 +276,7 @@ class psycopg_build_ext(build_ext): # For Python versions that use MSVC compiler 2008, re-insert the # manifest into the resulting .pyd file. - if self.compiler_is_msvc: + if self.compiler_is_msvc(): platform = get_platform() # Default to the x86 manifest manifest = '_psycopg.vc9.x86.manifest' @@ -293,7 +296,7 @@ class psycopg_build_ext(build_ext): # Add compiler-specific arguments: extra_compiler_args = [] - if self.compiler_is_mingw: + if self.compiler_is_mingw(): # Default MinGW compilation of Python extensions on Windows uses # only -O: extra_compiler_args.append('-O3') @@ -321,7 +324,7 @@ class psycopg_build_ext(build_ext): self.libraries.append("ws2_32") self.libraries.append("advapi32") - if self.compiler_is_msvc: + if self.compiler_is_msvc(): # MSVC requires an explicit "libpq" self.libraries.remove("pq") self.libraries.append("secur32") From 7b017e794455ea8953b3aa2c87998a2def0bb0d7 Mon Sep 17 00:00:00 2001 From: Daniele Varrazzo Date: Tue, 7 Jun 2011 23:58:37 +0100 Subject: [PATCH 10/10] Mention Steve and his work in the NEWS file That's Steve's Job! :D --- NEWS | 2 ++ 1 file changed, 2 insertions(+) diff --git a/NEWS b/NEWS index 7f0070cd..09669107 100644 --- a/NEWS +++ b/NEWS @@ -18,6 +18,8 @@ What's new in psycopg 2.4.2 - Trying to execute concurrent operations on the same connection through concurrent green thread results in an error instead of a deadlock. + - Fixed detection of pg_config on Window. Report and fix, plus some + long needed setup.py cleanup by Steve Lacy: thanks! What's new in psycopg 2.4.1