#!/usr/local/bin/python2.1 """ Distutils Extensions needed for the mx Extensions. Copyright (c) 1997-2000, Marc-Andre Lemburg; mailto:mal@lemburg.com Copyright (c) 2000-2004, eGenix.com Software GmbH; mailto:info@egenix.com See the documentation for further information on copyrights, or contact the author. All Rights Reserved. """ # # History: # 2.1.2: added wide Unicode support # 2.1.1: added support for Python 2.3 way of finding MSVC paths # 2.1.0: added bdist_zope, support for classifiers # 2.0.0: reworked the include and lib path logic; factored out the # compiler preparation # 1.9.0: added new include and lib path logic; added bdist_zope command # Older revisions are not documented. # import string, types, glob, os, sys, re from distutils.errors import \ DistutilsError, DistutilsExecError, CompileError, CCompilerError from distutils.core import setup, Extension, Command from distutils.sysconfig import \ get_config_h_filename, parse_config_h, customize_compiler, \ get_python_inc, get_config_vars from distutils.msvccompiler import MSVCCompiler from distutils.util import execute, get_platform from distutils.dir_util import remove_tree, mkpath from distutils.spawn import spawn, find_executable from distutils.dist import Distribution from distutils.command.config import config from distutils.command.build import build from distutils.command.build_ext import build_ext from distutils.command.build_clib import build_clib from distutils.command.build_py import build_py from distutils.command.bdist_rpm import bdist_rpm from distutils.command.bdist_dumb import bdist_dumb from distutils.command.install import install from distutils.command.install_data import install_data import distutils.archive_util # distutils extensions if sys.version[:3] >= '2.0': try: from distutils.command import bdist_ppm except ImportError: bdist_ppm = None try: from distutils.command import GenPPD except ImportError: GenPPD = None else: bdist_ppm = None GenPPD = None ### Globals __version__ = '2.1.1' _debug = 0 # # Helpers # def find_file(filename, paths, pattern=None): """ Look for a file in the directories defined in the list paths. If pattern is given, the found files are additionally checked to include the given RE search pattern. Pattern matching is done case-insensitive per default. Returns the directory where the file can be found or None in case it was not found. """ if _debug: print 'looking for %s ...' % filename for dir in paths: pathname = os.path.join(dir, filename) if os.path.exists(pathname): if pattern: data = open(pathname).read() if not re.search(pattern, data, re.I): data = None if _debug: print ' %s: found, but not matched' % dir continue data = None if _debug: print ' %s: found and matched' % dir return dir elif _debug: print ' %s: not found' % dir if _debug: print 'not found' return None def add_dir(dir, pathlist, index=-1): if dir not in pathlist and \ os.path.isdir(dir): if index < 0: index = index + len(pathlist) + 1 pathlist.insert(index, dir) def py_version(unicode_aware=1, include_patchlevel=0): """ Return the Python version as short string. If unicode_aware is true (default), the function also tests whether a UCS2 or UCS4 built is running and modifies the version accordingly. If include_patchlevel is true (default is false), the patch level is also included in the version string. """ if include_patchlevel: version = sys.version[:5] else: version = sys.version[:3] if unicode_aware and version > '2.0': try: unichr(100000) except ValueError: pass else: # UCS4 build version = version + 'ucs4' return version def check_zope_product_version(version, version_txt): """ Check whether the version string version matches the version data in the Zope product version.txt file version_txt. """ data = open(version_txt, 'r').read().strip() return data[-len(version):] == version def mx_customize_compiler(compiler): # Allow environment variables to override settings in the # Python Makefile on Unix; this overrides # distutils.sysconfig.customize_compiler() if compiler.compiler_type == "unix": # Note: BASECFLAGS is new in Python 2.3; thanks to # David M. Cooke for pointing this out to us. cc, opt, ccshared, basecflags, ldshared, so = \ get_config_vars('CC', 'OPT', 'BASECFLAGS', 'CCSHARED', 'LDSHARED', 'SO') cc = os.environ.get('CC', cc or '') opt = os.environ.get('OPT', opt or '') basecflags = os.environ.get('BASECFLAGS', basecflags or '') ccshared = os.environ.get('CCSHARED', ccshared or '') ldshared = os.environ.get('LDSHARED', ldshared or '') so = os.environ.get('SO', so or '') compiler.set_executables( preprocessor='%s -E' % cc, compiler='%s %s %s' % (cc, opt, basecflags), compiler_so='%s %s %s %s' % (cc, opt, ccshared, basecflags), linker_so=ldshared, linker_exe=cc) compiler.shared_lib_extension = so else: customize_compiler(compiler) compression_programs = { 'gzip': ('.gz', '-f9'), 'bzip2': ('.bz2', 'f9'), 'compress': ('.Z', '-f'), } def mx_make_tarball(base_name, base_dir, compression='gzip', verbose=0, dry_run=0, tar_options='-chf'): # Much like archive_util.make_tarball, except that this version # dereferences symbolic links. tar_archive = base_name + '.tar' # Create the directory for the archive mkpath(os.path.dirname(tar_archive), dry_run=dry_run) # Create the archive cmd = ['tar', tar_options, tar_archive, base_dir] spawn(cmd, verbose=verbose, dry_run=dry_run) # Compress if that's needed if compression: try: ext, options = compression_programs[compression] except KeyError: raise ValueError, 'unknown compression program: %s' % compress cmd = [compression, options, tar_archive] spawn(cmd, verbose=verbose, dry_run=dry_run) tar_archive = tar_archive + ext return tar_archive # Register our version of make_tarball() distutils.archive_util.ARCHIVE_FORMATS.update({ 'gztar': (mx_make_tarball, [('compression', 'gzip')], 'gzipped tar-file'), 'bztar': (mx_make_tarball, [('compression', 'bzip2')], 'bzip2ed tar-file'), 'ztar': (mx_make_tarball, [('compression', 'compress')], 'compressed tar file'), 'tar': (mx_make_tarball, [('compression', None)], 'tar file'), }) def build_path(dirs): """ Builds a path list from a list of directories/paths. The dirs list may contain shell variable references and user dir references. These will get expanded automatically. Non-existing shell variables are replaced with an empty string. Path entries will get expanded to single directory entries. Empty string entries are removed from the list. """ try: expandvars = os.path.expandvars except AttributeError: expandvars = None try: expanduser = os.path.expanduser except AttributeError: expanduser = None path = [] for i in range(len(dirs)): dir = dirs[i] if expanduser is not None: dir = expanduser(dir) if expandvars is not None: dir = expandvars(dir) if '$' in dir: dir = string.join(re.split(r'\$\w+|\{[^}]*\}', dir), '') dir = string.strip(dir) if os.pathsep in dir: path.extend(string.split(dir, os.pathsep)) elif dir: path.append(dir) # empty entries are omitted return path def verify_path(path): """ Verify the directories in path for existence and their directory nature. Also removes duplicates from the list. """ d = {} l = [] for dir in path: if os.path.exists(dir) and \ os.path.isdir(dir): if not d.has_key(dir): d[dir] = 1 l.append(dir) path[:] = l def get_msvc_paths(): """ Return a tuple (libpath, inclpath) defining the search paths for library files and include files that the MS VC++ compiler uses per default. Both entries are lists of directories. Only available on Windows platforms with installed compiler. """ try: # distutils versions prior to the one that came with Python 2.3 from distutils.msvccompiler import get_devstudio_versions, get_msvc_paths msvc_versions = get_devstudio_versions() if msvc_versions: msvc_version = msvc_versions[0] # choose most recent one inclpath = get_msvc_paths('include', msvc_version) libpath = get_msvc_paths('lib', msvc_version) else: libpath = [] inclpath = [] except ImportError: # distutils Python 2.3 and later from distutils.msvccompiler import MSVCCompiler try: msvccompiler = MSVCCompiler() inclpath = msvccompiler.get_msvc_paths('include') libpath = msvccompiler.get_msvc_paths('library') except Exception, why: print '*** Problem:', why libpath = [] inclpath = [] msvccompiler = None return libpath, inclpath # # Search paths # # Standard system directories which are automatically scanned by the # compiler and linker for include files and libraries. LIB and INCLUDE # are environment variables used on Windows platforms, other platforms # may have different names. STDLIBPATH = build_path(['/usr/lib', '/opt/lib', '$LIB']) STDINCLPATH = build_path(['/usr/include', '/opt/include', '$INCLUDE']) # Add additional dirs from Windows registry if available if sys.platform[:3] == 'win': libpath, inclpath = get_msvc_paths() STDLIBPATH.extend(libpath) STDINCLPATH.extend(inclpath) verify_path(STDLIBPATH) verify_path(STDINCLPATH) # Default paths for additional library and include file search (in # addition to the standard system directories above); these are always # added to the compile and link commands by mxSetup per default. LIBPATH = build_path(['/usr/local/lib', os.path.join(sys.prefix, 'lib')]) INCLPATH = build_path(['/usr/local/include', os.path.join(sys.prefix, 'include')]) verify_path(LIBPATH) verify_path(INCLPATH) if _debug: print 'mxSetup found these paths:' print ' lib path:', LIBPATH print ' include path:', INCLPATH # Additional paths to scan in order to find third party libs and # headers; these are used by mx_autoconf.find_*_file() APIs. FINDLIBPATH = build_path([]) FINDINCLPATH = build_path([]) verify_path(FINDLIBPATH) verify_path(FINDINCLPATH) if 0: # Work-around for quirk on Solaris which happens to be a common # problem when compiling things with GCC: there's a non-GCC stdarg.h # header file in /usr/include which gets picked up by GCC instead of # its own compiler specific one, so we remove /usr/include from # INCLPATH in that situation. if sys.platform == 'sunos5' and \ string.find(sys.version, 'GCC'): if os.path.exists('/usr/include/stdarg.h'): INCLPATH.remove('/usr/include') if _debug: print 'mxSetup will additionally scan these paths during autoconf:' print ' lib path:', FINDLIBPATH print ' include path:', FINDINCLPATH # # Mixin helpers # class CompilerSupportMixin: """ Compiler support mixin which makes sure that the .compiler attribute is properly setup. """ # Internal flag prepared_compiler = 0 def prepare_compiler(self): if self.prepared_compiler: return # Setup .compiler instance if self.compiler is None: self._check_compiler() compiler = self.compiler # Work around a bug in distutils <= 1.0.3 if compiler.exe_extension is None: compiler.exe_extension = '' # Make sure we have a typical setup for directory # searches for dir in LIBPATH: add_dir(dir, compiler.library_dirs) for dir in INCLPATH: add_dir(dir, compiler.include_dirs) # Customize the compiler according to system settings mx_customize_compiler(self.compiler) self.prepared_compiler = 1 # # mx Auto-Configuration command # class mx_autoconf(CompilerSupportMixin, config): """ Auto-configuration class which adds some extra configuration settings to the packages. """ # Command description description = "auto-configuration build step (for internal use only)" # Command line options user_options = config.user_options + [ ('enable-debugging', None, 'compile with debugging support'), ] # User option defaults enable_debugging = 0 # C APIs to check: (C API name, list of header files to include) api_checks = (('strftime', ['time.h']), ('strptime', ['time.h']), ('timegm', ['time.h']), ) def initialize_options(self): config.initialize_options(self) self.noisy = 0 self.dump_source = 0 def finalize_options(self): config.finalize_options(self) def prepare_compiler(self): if self.prepared_compiler: return # Setup .compiler instance if not self.compiler: self._check_compiler() mx_customize_compiler(self.compiler) compiler = self.compiler # Work around a bug in distutils <= 1.0.3 if compiler.exe_extension is None: compiler.exe_extension = '' # Make sure we have a typical setup for directory # searches; also see mx_Extension.prepared_compiler() for dir in LIBPATH: add_dir(dir, compiler.library_dirs) for dir in INCLPATH: add_dir(dir, compiler.include_dirs) self.prepared_compiler = 1 def run(self): # Setup .compiler instance self.prepare_compiler() # Add some static #defines which should not hurt self.compiler.define_macro('_GNU_SOURCE', '1') # Parse [py]config.h config_h = get_config_h_filename() try: configfile = open(config_h) except IOError,why: self.warn('could not open %s file' % config_h) configuration = {} else: configuration = parse_config_h(configfile) configfile.close() # Tweak configuration a little for some platforms if sys.platform[:5] == 'win32': configuration['HAVE_STRFTIME'] = 1 # Build lists of #defines and #undefs define = [] undef = [] # Check APIs for apiname, includefiles in self.api_checks: macro_name = 'HAVE_' + string.upper(apiname) if not configuration.has_key(macro_name): if self.check_function(apiname, includefiles): define.append((macro_name, '1')) else: undef.append(macro_name) # Compiler tests if not configuration.has_key('BAD_STATIC_FORWARD'): if self.check_bad_staticforward(): define.append(('BAD_STATIC_FORWARD', '1')) # Enable debugging support if self.enable_debugging: define.append(('MAL_DEBUG', None)) self.announce('macros to define: %s' % define) self.announce('macros to undefine: %s' % undef) # Reinitialize build_ext command with extra defines build_ext = self.distribution.reinitialize_command('build_ext') build_ext.ensure_finalized() # We set these here, since distutils 1.0.2 introduced a # new incompatible way to process .define and .undef if build_ext.define: #print repr(build_ext.define) if type(build_ext.define) is types.StringType: # distutils < 1.0.2 needs this: l = string.split(build_ext.define, ',') build_ext.define = map(lambda symbol: (symbol, '1'), l) build_ext.define = build_ext.define + define else: build_ext.define = define if build_ext.undef: #print repr(build_ext.undef) if type(build_ext.undef) is types.StringType: # distutils < 1.0.2 needs this: build_ext.undef = string.split(build_ext.undef, ',') build_ext.undef = build_ext.undef + undef else: build_ext.undef = undef self.announce('updated build_ext with autoconf setup') def check_compiler(self, sourcecode, headers=None, include_dirs=None, libraries=None, library_dirs=None): """ Check whether sourcecode compiles and links with the current compiler and link environment. For documentation of the other arguments see the base class' .try_link(). """ self.prepare_compiler() return self.try_link(sourcecode, headers, include_dirs, libraries, library_dirs) def check_bad_staticforward(self): """ Check whether the compiler does not supports forward declaring static arrays. For documentation of the other arguments see the base class' .try_link(). """ body = """ typedef struct _mxstruct {int a; int b;} mxstruct; staticforward mxstruct mxarray[]; statichere mxstruct mxarray[] = {{0,2},{3,4},}; int main(void) {return mxarray[0].a;} """ self.prepare_compiler() return not self.try_compile(body, headers=('Python.h',), include_dirs=[get_python_inc()]) def check_function(self, function, headers=None, include_dirs=None, libraries=None, library_dirs=None, prototype=0, call=0): """ Check whether function is available in the given compile and link environment. If prototype is true, a function prototype is included in the test. If call is true, a function call is generated (rather than just a reference of the function symbol). For documentation of the other arguments see the base class' .try_link(). """ body = [] if prototype: body.append("int %s (void);" % function) body.append("int main (void) {") if call: body.append(" %s();" % function) else: body.append(" %s;" % function) body.append("return 0; }") body = string.join(body, "\n") + "\n" return self.check_compiler(body, headers, include_dirs, libraries, library_dirs) def check_library(self, library, library_dirs=None, headers=None, include_dirs=None, other_libraries=[]): """ Check whether we can link against the given library. For documentation of the other arguments see the base class' .try_link(). """ body = "int main (void) { return 0; }" return self.check_compiler(body, headers, include_dirs, [library]+other_libraries, library_dirs) def find_include_file(self, filename, paths, pattern=None): """ Find an include file of the given name. The search path is determined by the paths parameter, the compiler's .include_dirs attribute and the STDINCLPATH and FINDINCLPATH globals. The search is done in this order. """ self.prepare_compiler() paths = (paths + self.compiler.include_dirs + STDINCLPATH + FINDINCLPATH) verify_path(paths) if _debug: print 'INCLPATH', paths return find_file(filename, paths, pattern) def find_library_file(self, libname, paths, pattern=None, lib_types=('shared', 'static')): """ Find a library of the given name. The search path is determined by the paths parameter, the compiler's .library_dirs attribute and the STDLIBPATH and FINDLIBPATH globals. The search is done in this order. Shared libraries are prefered over static ones if both types are given in lib_types. """ self.prepare_compiler() paths = (paths + self.compiler.library_dirs + STDLIBPATH + FINDLIBPATH) verify_path(paths) if _debug: print 'LIBPATH', paths # Try to first find a shared library to use and revert # to a static one, if no shared lib can be found for lib_type in lib_types: filename = self.compiler.library_filename(libname, lib_type=lib_type) dir = find_file(filename, paths, pattern) if dir is not None: return dir return None # # mx MSVC Compiler extension # # We want some extra options for the MSVCCompiler, so we add them # here. This is an awful hack, but there seems to be no other way to # subclass a standard distutils C compiler class... if sys.version[:3] < '2.4': # VC6 MSVC_COMPILER_FLAGS = ['/O2', '/Gf', '/GB', '/GD', '/Ob2'] else: # VC7 MSVC_COMPILER_FLAGS = ['/O2', '/GF', '/GB', '/Ob2'] # remember old __init__ old_MSVCCompiler__init__ = MSVCCompiler.__init__ def mx_msvccompiler__init__(self, *args, **kws): apply(old_MSVCCompiler__init__, (self,) + args, kws) self.compile_options.extend(MSVC_COMPILER_FLAGS) # "Install" new __init__ MSVCCompiler.__init__ = mx_msvccompiler__init__ # # mx Distribution class # class mx_Distribution(Distribution): """ Distribution class which knows about our distutils extensions. """ # List of UnixLibrary instances unixlibs = None # Add classifiers dummy options if needed if 'classifiers' not in Distribution.display_options: display_options = Distribution.display_options + [ ('classifiers', None, "print the list of classifiers (not yet supported)"), ] display_option_names = Distribution.display_option_names + [ 'classifiers' ] def has_unixlibs(self): return self.unixlibs and len(self.unixlibs) > 0 # # mx Extension class # class mx_Extension(Extension): """ Extension class which allows specifying whether the extension is required to build or optional. """ # Is this Extension required to build or can we issue a Warning in # case it fails to build and continue with the remaining build # process ? required = 1 # List of optional libaries (libname, list of header files to # check) to include in the link step; the availability of these is # tested prior to compiling the extension. If found, the symbol # HAVE__LIB is defined and the library included in # the list of libraries to link against. optional_libraries = () # List of needed include files in form of tuples (filename, # [dir1, dir2,...], pattern); see mx_autoconf.find_file() # for details needed_includes = () # List of needed include files in form of tuples (libname, # [dir1, dir2,...], pattern); see mx_autoconf.find_library_file() # for details needed_libraries = () # Library types to check (for needed libraries) lib_types = ('shared', 'static') # Data files needed by this extension (these are only # installed if building the extension succeeded) data_files = () # Python packages needed by this extension (these are only # installed if building the extension succeeded) packages = () # Building success marker successfully_built = 0 def __init__(self, *args, **kws): for attr in ('required', 'lib_types', 'optional_libraries', 'needed_includes', 'needed_libraries', 'data_files', 'packages'): if kws.has_key(attr): setattr(self, attr, kws[attr]) del kws[attr] else: value = getattr(self, attr) if type(value) == type(()): setattr(self, attr, list(value)) apply(Extension.__init__, (self,) + args, kws) # # mx Build command # class mx_build(build): """ build command which knows about our distutils extensions. """ def has_unixlibs(self): return self.distribution.has_unixlibs() if len(build.sub_commands) > 4: raise DistutilsError, 'incompatible distutils version !' sub_commands = [('build_clib', build.has_c_libraries), ('build_unixlib', has_unixlibs), ('mx_autoconf', build.has_ext_modules), ('build_ext', build.has_ext_modules), ('build_py', build.has_pure_modules), ('build_scripts', build.has_scripts), ] # # mx Build C Lib command # class mx_build_clib(build_clib): """ build_clib command which builds the libs using separate temp dirs """ def build_library(self, lib_name, build_info): # Build each extension in its own subdir of build_temp (to # avoid accidental sharing of object files between extensions # having the same name, e.g. mxODBC.o). build_temp_base = self.build_temp self.build_temp = os.path.join(build_temp_base, lib_name) try: # # This is mostly identical to the original build_clib command. # sources = build_info.get('sources') if sources is None or \ type(sources) not in (types.ListType, types.TupleType): raise DistutilsSetupError, \ ("in 'libraries' option (library '%s'), " + "'sources' must be present and must be " + "a list of source filenames") % lib_name sources = list(sources) self.announce("building '%s' library" % lib_name) # First, compile the source code to object files in the # library directory. (This should probably change to # putting object files in a temporary build directory.) macros = build_info.get('macros') include_dirs = build_info.get('include_dirs') objects = self.compiler.compile(sources, output_dir=self.build_temp, macros=macros, include_dirs=include_dirs, debug=self.debug) # Now "link" the object files together into a static library. # (On Unix at least, this isn't really linking -- it just # builds an archive. Whatever.) self.compiler.create_static_lib(objects, lib_name, output_dir=self.build_clib, debug=self.debug) finally: # Cleanup local changes to the configuration self.build_temp = build_temp_base def build_libraries(self, libraries): for (lib_name, build_info) in libraries: self.build_library(lib_name, build_info) # # mx Build Extensions command # class mx_build_ext(CompilerSupportMixin, build_ext): """ build_ext command which runs mx_autoconf command before trying to build anything. """ user_options = build_ext.user_options + [ ('disable-build=', None, 'disable building an optional extensions (comma separated list of ' 'dotted package names); default is to try building all'), ('enable-build=', None, 'if given, only these optional extensions are built (comma separated ' 'list of dotted package names)'), ] # mx_autoconf command object (finalized and run) autoconf = None # Default values for command line options disable_build = None enable_build = None def finalize_options(self): build_ext.finalize_options(self) if self.disable_build is None: self.disable_build = () elif type(self.disable_build) is types.StringType: self.disable_build = map(string.strip, string.split(self.disable_build, ',')) if type(self.enable_build) is types.StringType: self.enable_build = map(string.strip, string.split(self.enable_build, ',')) def run(self): # Add unixlibs install-dirs to library_dirs, so that linking # against them becomes easy if self.distribution.has_unixlibs(): build_unixlib = self.get_finalized_command('build_unixlib') paths, libs = build_unixlib.get_unixlib_lib_options() # Libraries have to be linked against by defining them # in the mx_Extension() setup, otherwise, all extensions # get linked against all Unix libs that were built... #self.libraries[:0] = libs self.library_dirs[:0] = paths # Assure that mx_autoconf has been run and store a reference # in .autoconf self.run_command('mx_autoconf') self.autoconf = self.get_finalized_command('mx_autoconf') # Now, continue with the standard build process build_ext.run(self) def build_extensions(self): # Make sure the compiler is setup correctly self.prepare_compiler() # Make sure that any autoconf actions use the same compiler # settings as we do (the .compiler is set up in build_ext.run() # just before calling .build_extensions()) self.autoconf.compiler = self.compiler # Build the extensions self.check_extensions_list(self.extensions) for ext in self.extensions: self.build_extension(ext) # Cleanup .extensions list (remove entries which did not build correctly) l = [] for ext in self.extensions: if not isinstance(ext, mx_Extension): l.append(ext) else: if ext.successfully_built: l.append(ext) self.extensions = l if _debug: print 'extensions:', repr(self.extensions) self.announce('') def build_extension(self, ext): required = not hasattr(ext, 'required') or ext.required self.announce('') self.announce('building extension "%s" %s' % (ext.name, required * '(required)' or '(optional)')) # Optional extension building can be adjusted via command line options if not required: if self.enable_build is not None and \ ext.name not in self.enable_build: self.announce('skipped -- build not enabled by command line option') return elif ext.name in self.disable_build: self.announce('skipped -- build disabled by command line option') return # Search for include files if hasattr(ext, 'needed_includes') and \ ext.needed_includes: self.announce('looking for header files needed by extension ' '"%s"' % ext.name) for filename, dirs, pattern in ext.needed_includes: dir = self.autoconf.find_include_file(filename, dirs, pattern) if dir is not None: self.announce('found needed include file "%s" ' 'in directory %s' % (filename, dir)) if dir not in ext.include_dirs and \ dir not in STDINCLPATH and \ dir not in INCLPATH: ext.include_dirs.append(dir) else: if required: raise CompileError, \ 'could not find needed header file "%s"' % \ filename else: self.announce( '*** WARNING: Building of extension ' '"%s" failed: needed include file "%s" ' 'not found' % (ext.name, filename)) return # Search for libraries if hasattr(ext, 'needed_libraries') and \ ext.needed_libraries: self.announce('looking for libraries needed by extension ' '"%s"' % ext.name) for libname, dirs, pattern in ext.needed_libraries: dir = self.autoconf.find_library_file(libname, dirs, pattern, ext.lib_types) if dir is not None: self.announce('found needed library "%s" ' 'in directory %s' % (libname, dir)) if dir not in ext.library_dirs and \ dir not in STDLIBPATH and \ dir not in LIBPATH: ext.library_dirs.append(dir) if libname not in ext.libraries: ext.libraries.append(libname) else: if required: raise CompileError, \ 'could not find needed library "%s"' % \ libname else: self.announce( '*** WARNING: Building of extension ' '"%s" failed: needed library "%s" ' 'not found' % (ext.name, libname)) return # Build each extension in its own subdir of build_temp (to # avoid accidental sharing of object files between extensions # having the same name, e.g. mxODBC.o). build_temp_base = self.build_temp extpath = string.join(string.split(ext.name, '.'), os.sep) self.build_temp = os.path.join(build_temp_base, extpath) # Check for availability of optional libs which can be used # by the extension; note: this step includes building small # object files to test for the availability of the libraries if hasattr(ext, 'optional_libraries') and \ ext.optional_libraries: self.announce("checking for optional libraries") for libname, headerfiles in ext.optional_libraries: if self.autoconf.check_library(libname, headers=headerfiles): symbol = 'HAVE_%s_LIB' % string.upper(libname) self.announce("found optional library '%s'" " -- defining %s" % (libname, symbol)) ext.libraries.append(libname) ext.define_macros.append((symbol, '1')) else: self.announce("could not find optional library '%s'" " -- omitting it" % libname) if _debug: print 'Include dirs:', repr(ext.include_dirs + self.compiler.include_dirs) print 'Libary dirs:', repr(ext.library_dirs + self.compiler.library_dirs) print 'Libaries:', repr(ext.libraries) print 'Macros:', repr(ext.define_macros) # Build the extension successfully_built = 0 try: # Skip extensions which cannot be built if the required # option is given and set to false. required = not hasattr(ext, 'required') or ext.required if required: build_ext.build_extension(self, ext) successfully_built = 1 else: try: build_ext.build_extension(self, ext) except (CCompilerError, DistutilsError), why: self.announce( '*** WARNING: Building of extension "%s" ' 'failed: %s' % (ext.name, sys.exc_info()[1])) else: successfully_built = 1 finally: # Cleanup local changes to the configuration self.build_temp = build_temp_base # Processing for successfully built extensions if successfully_built: # Add Python packages needed by this extension if hasattr(ext, 'packages'): self.distribution.packages.extend(ext.packages) # Add data files needed by this extension if hasattr(ext, 'data_files'): self.distribution.data_files.extend(ext.data_files) # Mark as having been built successfully ext.successfully_built = 1 # # mx Build Python command # class mx_build_py(build_py): """ build_py command which also allows removing Python source code after the byte-code compile process. """ user_options = build_py.user_options + [ ('without-source', None, "only include Python byte-code"), ] boolean_options = build_py.boolean_options + ['without-source'] # Omit source files ? without_source = 0 def run(self): if self.without_source: # --without-source implies byte-code --compile if not self.compile and \ not self.optimize: self.compile = 1 # Build the Python code build_py.run(self) # Optionally remove source code if self.without_source: self.announce('removing Python source files (--without-source)') verbose = self.verbose dry_run = self.dry_run for file in build_py.get_outputs(self, include_bytecode=0): # Only process .py files if file[-3:] != '.py': continue # Remove source code execute(os.remove, (file,), "removing %s" % file, verbose=verbose, dry_run=dry_run) # Remove .pyc files (if not requested) if not self.compile: filename = file + "c" if os.path.isfile(filename): execute(os.remove, (filename,), "removing %s" % filename, verbose=verbose, dry_run=dry_run) # Remove .pyo files (if not requested) if self.optimize == 0: filename = file + "o" if os.path.isfile(filename): execute(os.remove, (filename,), "removing %s" % filename, verbose=verbose, dry_run=dry_run) def get_outputs(self, include_bytecode=1): if not self.without_source or \ not include_bytecode: return build_py.get_outputs(self, include_bytecode) # Remove source code files from outputs files = [] for file in build_py.get_outputs(self, include_bytecode=1): if file[-3:] == '.py' or \ (not self.compile and file[-4:] == '.pyc') or \ (not self.optimize and file[-4:] == '.pyo'): continue files.append(file) return files # # mx Build Unix Libs command # class UnixLibrary: """ Container for library configuration data. """ # Name of the library libname = '' # Source tree where the library lives sourcetree = '' # List of library files and where to install them in the # build tree libfiles = None # Name of the configure script configure = 'configure' # Configure options configure_options = None # Make options make_options = None def __init__(self, libname, sourcetree, libfiles, configure=None, configure_options=None, make_options=None): self.libname = libname self.sourcetree = sourcetree # Check for 2-tuples... for libfile, targetdir in libfiles: pass self.libfiles = libfiles # Optional settings if configure: self.configure = configure if configure_options: self.configure_options = configure_options else: self.configure_options = [] if make_options: self.make_options = make_options else: self.make_options = [] def get(self, option, alternative=None): return getattr(self, option, alternative) class mx_build_unixlib(Command): """ This command compiles external libs using the standard Unix procedure for this: ./configure make """ description = "build Unix libraries used by Python extensions" # make program to use make = None user_options = [ ('build-lib=', 'b', "directory to store built Unix libraries in"), ('build-temp=', 't', "directory to build Unix libraries to"), ('make=', None, "make program to use"), ('makefile=', None, "makefile to use"), ('force', 'f', "forcibly reconfigure"), ] boolean_options = ['force'] def initialize_options(self): self.build_lib = None self.build_temp = None self.make = None self.makefile = None self.force = 0 def finalize_options(self): self.set_undefined_options('build', ('verbose', 'verbose'), ('build_lib', 'build_lib'), ('build_temp', 'build_temp') ) if self.make is None: self.make = 'make' if self.makefile is None: self.makefile = 'Makefile' # For debugging we are always in very verbose mode... self.verbose = 2 def run_script(self, script, options=[]): if options: l = [] for k, v in options: if v is not None: l.append('%s=%s' % (k, v)) else: l.append(k) script = script + ' ' + string.join(l, ' ') if self.verbose > 1: self.announce('executing script %s' % repr(script)) if self.dry_run: return 0 try: rc = os.system(script) except DistutilsExecError, msg: raise CompileError, msg return rc def run_configure(self, options=[], dir=None, configure='configure'): """ Run the configure script using options is given. Options must be a list of tuples (optionname, optionvalue). If an option should not have a value, passing None as optionvalue will have the effect of using the option without value. dir can be given to have the configure script execute in that directory instead of the current one. """ cmd = './%s' % configure if dir: cmd = 'cd %s; ' % dir + cmd if self.verbose: self.announce('running %s in %s' % (configure, dir or '.')) rc = self.run_script(cmd, options) if rc != 0: raise CompileError, 'configure script failed' def run_make(self, targets=[], dir=None, make='make', options=[]): """ Run the make command for the given targets. Targets must be a list of valid Makefile targets. dir can be given to have the make program execute in that directory instead of the current one. """ cmd = '%s' % make if targets: cmd = cmd + ' ' + string.join(targets, ' ') if dir: cmd = 'cd %s; ' % dir + cmd if self.verbose: self.announce('running %s in %s' % (make, dir or '.')) rc = self.run_script(cmd, options) if rc != 0: raise CompileError, 'make failed' def build_unixlib(self, unixlib): # Build each lib in its own subdir of build_temp (to # avoid accidental sharing of object files) build_temp_base = self.build_temp libpath = unixlib.libname self.build_temp = os.path.join(build_temp_base, libpath) try: # Get options configure = unixlib.configure configure_options = unixlib.configure_options make_options = unixlib.make_options sourcetree = unixlib.sourcetree buildtree = os.path.join(self.build_temp, sourcetree) libfiles = unixlib.libfiles if not libfiles: raise DistutilsError, \ 'no libfiles defined for unixlib "%s"' % \ unixlib.name if self.verbose: self.announce('building C lib %s in %s' % (unixlib.libname, buildtree)) # Prepare build if self.verbose: self.announce('preparing build of %s' % unixlib.libname) self.mkpath(buildtree) self.copy_tree(sourcetree, buildtree) # Run configure to build the Makefile if not os.path.exists(os.path.join(buildtree, self.makefile)) or \ self.force: self.run_configure(configure_options, dir=buildtree, configure=configure) elif self.verbose: self.announce("skipping configure: " "%s is already configured" % unixlib.libname) # Run make self.run_make(dir=buildtree, make=self.make, options=make_options) # Copy libs to destinations for sourcefile, destination in libfiles: if not destination: continue sourcefile = os.path.join(self.build_temp, sourcefile) destination = os.path.join(self.build_lib, destination) if not os.path.exists(sourcefile): raise CompileError, \ 'library "%s" failed to build' % sourcefile self.mkpath(destination) self.copy_file(sourcefile, destination) finally: # Cleanup local changes to the configuration self.build_temp = build_temp_base def build_unixlibs(self, unixlibs): for unixlib in unixlibs: self.build_unixlib(unixlib) def get_unixlib_lib_options(self): libs = [] paths = [] for unixlib in self.distribution.unixlibs: for sourcefile, destination in unixlib.libfiles: if not destination: # direct linking sourcefile = os.path.join(self.build_temp, sourcefile) libs.append(sourcefile) else: # linking via link path and lib name sourcefile = os.path.basename(sourcefile) libname = os.path.splitext(sourcefile)[0] if libname[:3] == 'lib': libname = libname[3:] libs.append(libname) destination = os.path.join(self.build_lib, destination) paths.append(destination) #print paths, libs return paths, libs def run(self): if not self.distribution.unixlibs: return self.build_unixlibs(self.distribution.unixlibs) # # mx Install command # class mx_install(install): """ We want install_data to default to install_purelib if it is not given. """ def finalize_options(self): fix_install_data = (self.install_data is None) install.finalize_options(self) if fix_install_data: self.install_data = self.install_purelib # Hack needed for bdist_wininst def ensure_finalized(self): install.ensure_finalized(self) if self.install_data[-5:] == '\\DATA': self.install_data = self.install_data[:-5] + '\\PURELIB' # # mx Install Data command # class mx_install_data(install_data): """ Rework the install_data command to something more useful. """ def finalize_options(self): if self.install_dir is None: installobj = self.distribution.get_command_obj('install') self.install_dir = installobj.install_data if _debug: print 'Installing data files to %s' % self.install_dir install_data.finalize_options(self) def run (self): if not self.dry_run: self.mkpath(self.install_dir) data_files = self.get_inputs() for entry in data_files: if type(entry) == types.StringType: if 1 or os.name != 'posix': # Unix- to platform-convention conversion entry = string.join(string.split(entry, '/'), os.sep) filenames = glob.glob(entry) for filename in filenames: dst = os.path.join(self.install_dir, filename) dstdir = os.path.split(dst)[0] if not self.dry_run: self.mkpath(dstdir) outfile = self.copy_file(filename, dst)[0] else: outfile = dst self.outfiles.append(outfile) else: raise ValueError, 'tuples in data_files not supported' # # mx Uninstall command # # Credits are due to Thomas Heller for the idea and the initial code # base for this command (he posted a different version to # distutils-sig@python.org) in 02/2001. # class mx_uninstall(Command): description = "uninstall the package files and directories" user_options = [] def initialize_options(self): pass def finalize_options(self): pass def run(self): # Execute build self.announce('determining installation files') self.announce('(re)building package') savevalue = self.distribution.dry_run self.distribution.dry_run = 0 self.run_command('build') # Execute install in dry-run mode self.announce('dry-run package install') self.distribution.dry_run = 1 self.run_command('install') self.distribution.dry_run = savevalue build = self.get_finalized_command('build') install = self.get_finalized_command('install') # Remove all installed files self.announce("removing files") dirs = {} filenames = install.get_outputs() for filename in filenames: if not os.path.isabs(filename): raise DistutilsError,\ 'filename %s from .get_output() not absolute' % \ filename if os.path.isfile(filename): self.announce("removing %s" % filename) if not self.dry_run: try: os.remove(filename) except OSError, details: self.warn("Could not remove file: %s" % details) dir = os.path.split(filename)[0] if not dirs.has_key(dir): dirs[dir] = 1 if os.path.splitext(filename)[1] == '.py': # Remove byte-code files as well try: os.remove(filename + 'c') except OSError: pass try: os.remove(filename + 'o') except OSError: pass elif os.path.isdir(filename): # This functionality is currently not being used by distutils if not dirs.has_key(dir): dirs[filename] = 1 elif not os.path.splitext(filename)[1] in ('.pyo', '.pyc'): self.announce("skipping removal of %s (not found)" % filename) # Remove the installation directories self.announce("removing directories") dirs = dirs.keys() dirs.sort(); dirs.reverse() # sort descending for dir in dirs: self.announce("removing directory %s" % dir) if not self.dry_run: try: os.rmdir(dir) except OSError, details: self.warn("could not remove directory: %s" % details) # # mx RPM distribution command # class mx_bdist_rpm(bdist_rpm): """ bdist_rpm command which allows passing in distutils options. """ user_options = bdist_rpm.user_options + [ ('distutils-build-options=', None, 'extra distutils build options to use before the "build" command'), ('distutils-install-options=', None, 'extra distutils install options to use after the "install" command'), ] # Defaults distutils_build_options = None distutils_install_options = None def finalize_options(self): bdist_rpm.finalize_options(self) if self.distutils_build_options is None: self.distutils_build_options = '' if self.distutils_install_options is None: self.distutils_install_options = '' def _make_spec_file(self): # Generate .spec file l = bdist_rpm._make_spec_file(self) # Insert into build command i = l.index('%build') buildcmd = l[i + 1] inspos = string.find(l[i + 1], ' build') if inspos >= 0: l[i + 1] = '%s %s %s' % (buildcmd[:inspos], self.distutils_build_options, buildcmd[inspos:]) else: raise DistutilsError, \ 'could not insert distutils command in RPM build command' # Insert into install command i = l.index('%install') installcmd = l[i + 1] inspos = string.find(l[i + 1], ' install') if inspos >= 0: l[i + 1] = '%s %s %s %s' % (installcmd[:inspos], self.distutils_build_options, installcmd[inspos:], self.distutils_install_options) else: raise DistutilsError, \ 'could not insert distutils command in RPM install command' return l # # mx in-place binary distribution command # class mx_bdist_inplace(bdist_dumb): """ Build binary Zope product distribution. """ # Path prefix to use in the distribution (all files will be placed # under this prefix) dist_prefix = '' user_options = bdist_dumb.user_options + [ ('dist-prefix=', None, 'path prefix the binary distribution with'), ] def finalize_options(self): # Default to ZIP as format on all platforms if self.format is None: self.format = 'zip' # Default to -py on all platforms if self.plat_name is None: self.plat_name = '%s-py%s' % (get_platform(), py_version()) bdist_dumb.finalize_options(self) # Hack to reuse bdist_dumb for our purposes; .run() calls # reinitialize_command() with 'install' as command. def reinitialize_command(self, command, reinit_subcommands=0): cmdobj = bdist_dumb.reinitialize_command(self, command, reinit_subcommands) if command == 'install': cmdobj.install_lib = self.dist_prefix cmdobj.install_data = self.dist_prefix return cmdobj # # mx Zope binary distribution command # class mx_bdist_zope(mx_bdist_inplace): """ Build binary Zope product distribution. """ # Path prefix to use in the distribution (all files will be placed # under this prefix) dist_prefix = 'lib/python' ### def run_setup(configurations): """ Run distutils setup. The parameters passed to setup() are extracted from the list of modules, classes or instances given in configurations. Names with leading underscore are removed from the parameters. Parameters which are not strings, lists or tuples are removed as well. Configurations which occur later in the configurations list override settings of configurations earlier in the list. """ # Build parameter dictionary kws = {} for configuration in configurations: kws.update(vars(configuration)) for name, value in kws.items(): if name[:1] == '_' or \ type(value) not in (types.StringType, types.ListType, types.TupleType): del kws[name] #if type(value) is types.FunctionType: # kws[name] = value() # Add setup extensions kws['distclass'] = mx_Distribution extensions = {'build': mx_build, 'build_unixlib': mx_build_unixlib, 'mx_autoconf': mx_autoconf, 'build_ext': mx_build_ext, 'build_clib': mx_build_clib, 'build_py': mx_build_py, 'install': mx_install, 'install_data': mx_install_data, 'uninstall': mx_uninstall, 'bdist_rpm': mx_bdist_rpm, 'bdist_zope': mx_bdist_zope, 'bdist_inplace': mx_bdist_inplace, } if bdist_ppm is not None: extensions['bdist_ppm'] = bdist_ppm.bdist_ppm if GenPPD is not None: extensions['GenPPD'] = GenPPD.GenPPD kws['cmdclass'] = extensions # Invoke distutils setup apply(setup, (), kws)