""" By using the class Instant a Python extension module can be created at runtime. For the user, it behaves somewhat like an inline module, except you have to import the module manually. The code can be either C or C++, but like when programming C or C++, it has to be inside a function or a similar C/C++ construct. A simple example: (see test1.py) >>> from Instant import inline >>> add_func = inline(\"double add(double a, double b){ return a+b; }\") >>> print "The sum of 3 and 4.5 is ", add_func(3, 4.5) """ import os, sys,re import commands import string import md5 VERBOSE = 0 class Instant: # Default values: def __init__(self): """ Instant constructor """ self.code = """ void f() { printf("No code supplied!\\n"); }""" self.gen_setup = 1 self.module = 'instant_swig_module' self.swigopts = '-I.' self.init_code = ' //Code for initialisation here' self.system_headers = [] self.local_headers = [] self.wrap_headers = [] self.sources = [] self.include_dirs = ['-I.'] self.libraries = [] self.library_dirs = [] self.cppargs = '' self.object_files = [] self.arrays = [] self.additional_definitions = "" self.additional_declarations = "" def parse_args(self, dict): """ A method for parsing arguments. """ for key in dict.keys(): if key == 'code': self.code = dict[key] elif key == 'module': self.module = dict[key] elif key == 'swigopts': self.swigopts = dict[key] elif key == 'init_code': self.init_code = dict[key] elif key == 'sources': self.sources = dict[key] elif key == 'system_headers': self.system_headers = dict[key] elif key == 'local_headers': self.local_headers = dict[key] elif key == 'wrap_headers': self.wrap_headers = dict[key] elif key == 'include_dirs': self.include_dirs = dict[key] elif key == 'libraries': self.libraries = dict[key] elif key == 'library_dirs': self.library_dirs = dict[key] elif key == 'cppargs': self.cppargs = dict[key] elif key == 'object_files': self.object_files = dict[key] elif key == 'arrays': self.arrays = dict[key] elif key == 'additional_definitions': self.additional_definitions = dict[key] elif key == 'additional_declarations': self.additional_declarations = dict[key] self.makefile_name = self.module+".mak" self.logfile_name = self.module+".log" self.ifile_name = self.module+".i" # sources has to be put in different variables, # depending on the file-suffix (.c or .cpp). self.srcs = [] self.cppsrcs = [] for file in self.sources: if file.endswith('.cpp'): self.cppsrcs.append(file) elif file.endswith('.c'): self.srcs.append(file) else: print 'Source files must have \'.c\' or \'.cpp\' suffix!' print 'This is not the case for',file return 1 # Parsing of argurments detected errors return 0 # all ok def create_extension(self, **args): """ Call this function to instantly create an extension module. SWIG is used to generate code that can be compiled and used as an ordinary Python module. Arguments: ========== - B{code}: - A Python string containing C or C++ function, class, .... - B{module}: - The name you want for the module (Default is 'instant_swig_module'.). String. - B{swigopts}: - Options to swig, for instance C{-lpointers.i} to include the SWIG pointers.i library. String. - B{init_code}: - Code that should be executed when the Instant extension is imported. String. - B{system_headers}: - A list of system header files required by the Instant code. - B{local_headers}: - A list of local header files required by the Instant code. - B{wrap_headers}: - A list of local header files that should be wrapped by SWIG. - B{include_dirs}: - A list of directories to search for header files. - B{sources}: - A list of source files to compile and link with the extension. - B{cppargs}: - Flags like C{-D}, C{-U}, etc. String. - B{libraries}: - A list of libraries needed by the Instant extension. - B{library_dirs}: - A list of directories to search for libraries (C{-l}). - B{object_files}: - If you want to compile the files yourself. NOT YET SUPPORTED. - B{arrays}: - A list of the C arrays to be made from NumPy arrays. """ if self.parse_args(args): print 'Nothing done!' return # self.debug() if not os.path.isdir(self.module): os.mkdir(self.module) os.chdir(self.module) f = open("__init__.py", 'w') f.write("from %s import *"% self.module) self.generate_Interfacefile() if self.check_md5sum(): return 1 if sys.platform=='win32': null='nul' else: null='/dev/null' if (os.system("swig -version 2 > %s" % null ) == 0 ): if ( not self.gen_setup ): self.generate_Makefile() if os.path.isfile(self.makefile_name): os.system("make -f "+self.makefile_name+" clean") os.system("make -f "+self.makefile_name+" &> "+self.logfile_name) if VERBOSE == 9: os.remove(self.logfile_name) else: self.generate_setup() os.system("python " + self.module + "_setup.py build_ext") os.system("python " + self.module + "_setup.py install --install-platlib=.") # print "Module name is \'"+self.module+"\'" else: raise RuntimeError, "Could not find swig!\nYou can download swig from http://www.swig.org" def debug(self): """ print out all instance variable """ print 'DEBUG CODE:' print 'code',self.code print 'module',self.module print 'swigopts',self.swigopts print 'init_code',self.init_code print 'system_headers',self.system_headers print 'local_headers',self.local_headers print 'wrap_headers',self.wrap_headers print 'include_dirs',self.include_dirs print 'sources',self.sources print 'srcs',self.srcs print 'cppsrcs',self.cppsrcs print 'cppargs',self.cppargs def clean(self): """ Clean up files the current session. """ if ( not gen_setup ) : for file in [self.module+".log", self.module+".log", self.module+".i", self.module+".mak", self.module+".py", self.module+".pyc", "_"+self.module+".so"]: if os.path.isfile(file): os.remove(file) def generate_Interfacefile(self): """ Use this function to generate a SWIG interface file. To generate an interface file it uses the following class-variables: - code - ifile_name (The SWIG input file) - init_code (Code to put in the init section of the interface file) - system_headers (A list of system headers with declarations needed) - local_headers (A list of local headers with declarations needed) - wrap_headers (A list of local headers that will be wrapped by SWIG) """ if VERBOSE > 0: print "\nGenerating interface file \'"+ self.ifile_name +"\':" func_name = self.code[:self.code.index(')')+1] # create typemaps typemaps = "" if (len(self.arrays) > 0): for a in self.arrays: # 1 dimentional arrays, ie. vectors if (len(a) == 2): typemap = """ %%typemap(in) (int %(n)s,double* %(array)s){ if (!PyArray_Check($input)) { PyErr_SetString(PyExc_TypeError, "Not a NumPy array"); return NULL; ; } PyArrayObject* pyarray; pyarray = (PyArrayObject*)$input; $1 = pyarray->dimensions[0]; $2 = (double*)pyarray->data; } """ % { 'n' : a[0] , 'array' : a[1] } typemaps += typemap # n dimentional arrays, ie. matrices and tensors elif (len(a) == 3): typemap = """ %%typemap(in) (int %(n)s,int* %(ptv)s,double* %(array)s){ if (!PyArray_Check($input)) { PyErr_SetString(PyExc_TypeError, "Not a NumPy array"); return NULL; ; } PyArrayObject* pyarray; pyarray = (PyArrayObject*)$input; $1 = pyarray->nd; $2 = pyarray->dimensions; $3 = (double*)pyarray->data; } """ % { 'n' : a[0] , 'ptv' : a[1], 'array' : a[2] } typemaps += typemap self.system_headers_code = "\n".join(['#include <%s>' % header for header in self.system_headers]) self.local_headers_code = "\n".join(['#include "%s"' % header for header in self.local_headers]) self.wrap_headers_code1 = "\n".join(['#include "%s"' % header for header in self.wrap_headers]) self.wrap_headers_code2 = "\n".join(['%%include "%s"' % header for header in self.wrap_headers]) self.typemaps = typemaps interface_string = """ %%module (directors="1") %(module)s %%feature("director"); %%{ #include %(additional_definitions)s %(system_headers_code)s %(local_headers_code)s %(wrap_headers_code1)s %(code)s %%} %%feature("autodoc", "1"); %%init%%{ %(init_code)s %%} %(additional_definitions)s %(additional_declarations)s %(wrap_headers_code2)s %(typemaps)s %(code)s; """ % vars(self) f = open(self.ifile_name, 'w') f.write(interface_string) f.close() if VERBOSE > 0: print '... Done' return func_name[func_name.rindex(' ')+1:func_name.index('(')] def getmd5sumfiles(self, filenames): ''' get the md5 value of filename modified based on Python24\Tools\Scripts\md5sum.py ''' print filenames for filename in filenames: # print "Adding file ", filename, "to md5 sum " try: fp = open(filename, 'rb') except IOError, msg: sys.stderr.write('%s: Can\'t open: %s\n' % (filename, msg)) return None m = md5.new() try: while 1: data = fp.read() if not data: break m.update(data) except IOError, msg: sys.stderr.write('%s: I/O error: %s\n' % (filename, msg)) return None fp.close() return '%s %s\n' % (m.hexdigest().upper(), filename) def writemd5sumfile(self, filenames, md5out=sys.stdout): result=self.getmd5sumfiles(filenames) try: fp = open(md5out, 'w') except IOError, msg: sys.stderr.write('%s: Can\'t open: %s\n' % (filename, msg)) fp.write(result) fp.close() def check_md5sum(self): """ Check if the md5sum of the generated interface file has changed since the last time the module was compiled. If it has changed then recompilation is necessary. """ md5sum_files = [] md5sum_files.append(self.ifile_name) for i in self.sources : md5sum_files.append(i) for i in self.wrap_headers : md5sum_files.append(i) for i in self.local_headers: md5sum_files.append(i) print md5sum_files if (os.path.isfile(self.module+".md5")): current_md5sum = self.getmd5sumfiles(md5sum_files ) file = open(self.module + ".md5") last_md5sum = file.readline() if ( current_md5sum == last_md5sum) : return 1 else: self.writemd5sumfile(md5sum_files, self.module + ".md5") return 0 else: self.writemd5sumfile(md5sum_files, self.module + ".md5") return 0 return 0; # def check_md5sum(self): # """ # Check if the md5sum of the generated interface file has changed since the last # time the module was compiled. If it has changed then recompilation is necessary. # """ # if (os.path.isfile(self.module+".md5")): # pipe = os.popen("md5sum " + self.ifile_name) # current_md5sum = pipe.readline() # file = open(self.module + ".md5") # last_md5sum = file.readline() # if ( current_md5sum == last_md5sum) : return 1 # else: # os.system("md5sum " + self.ifile_name + " > " + self.module + ".md5") # return 0 # # # else: # os.system("md5sum " + self.ifile_name + " > " + self.module + ".md5") # return 0 # # return 0; def generate_setup(self): """ Generates a setup.py file """ self.cppsrcs.append( "%s_wrap.cxx" %self.module ) f = open(self.module+'_setup.py', 'w') f.write(""" import os from distutils.core import setup, Extension name = '%s' swig_cmd ='swig -python -c++ %s %s' os.system(swig_cmd) sources = %s setup(name = '%s', ext_modules = [Extension('_' + '%s', sources, include_dirs=%s, library_dirs=%s, libraries=%s)]) """ % (self.module, self.swigopts, self.ifile_name, self.cppsrcs, self.module, self.module, self.include_dirs, self.library_dirs, self.libraries )) f.close() def generate_Makefile(self): """ Generates a project dependent Makefile, which includes and uses SWIG's own Makefile to create an extension module of the supplied C/C++ code. """ f = open(self.makefile_name, 'w') f.write(""" LIBS = %s LDPATH = FLAGS = %s SWIG = swig SWIGOPT = %s INTERFACE = %s TARGET = %s INCLUDES = SWIGMAKEFILE = $(SWIGSRC)/Examples/Makefile python:: $(MAKE) -f '$(SWIGMAKEFILE)' INTERFACE='$(INTERFACE)' \\ SWIG='$(SWIG)' SWIGOPT='$(SWIGOPT)' \\ SRCS='%s' \\ CPPSRCS='%s' \\ INCLUDES='$(INCLUDES) %s' \\ LIBS='$(LIBS) %s' \\ CFLAGS='$(CFLAGS) $(FLAGS)' \\ TARGET='$(TARGET)' \\ python_cpp clean:: rm -f *_wrap* _%s.so *.o $(OBJ_FILES) *~ """ % (list2str(self.libraries), self.cppargs, self.swigopts, self.ifile_name, self.module, list2str(self.srcs), list2str(self.cppsrcs), list2str(self.include_dirs), list2str(self.library_dirs), self.module )) f.close() if VERBOSE > 0: print 'Makefile', self.makefile_name, 'generated' # convert list values to string def list2str(list): s = str(list) for c in ['[', ']', ',', '\'']: s = s.replace(c, '') return s def create_extension(**args): """ This is a small wrapper around the create_extension function in Instant. Call this function to instantly create an extension module. SWIG is used to generate code that can be compiled and used as an ordinary Python module. Arguments: ========== - B{code}: - A Python string containing C or C++ function, class, .... - B{module}: - The name you want for the module (Default is 'instant_swig_module'.). String. - B{swigopts}: - Options to swig, for instance C{-lpointers.i} to include the SWIG pointers.i library. String. - B{init_code}: - Code that should be executed when the Instant extension is imported. String. - B{system_headers}: - A list of system header files required by the Instant code. - B{local_headers}: - A list of local header files required by the Instant code. - B{wrap_headers}: - A list of local header files that will be wrapped by SWIG. - B{include_dirs}: - A list of directories to search for header files. - B{sources}: - A list of source files to compile and link with the extension. - B{cppargs}: - Flags like C{-D}, C{-U}, etc. String. - B{libraries}: - A list of libraries needed by the Instant extension. - B{library_dirs}: - A list of directories to search for libraries (C{-l}). - B{object_files}: - If you want to compile the files yourself. NOT YET SUPPORTED. - B{arrays}: - A list of the C arrays to be made from NumPy arrays. """ ext = Instant() ext.create_extension(**args) def inline(c_code): """ This is a short wrapper around the create_extention function in Instant. It creates an extension module given that the input is a valid C function. It is only possible to inline one C function each time. Usage: >>> from Instant import inline >>> add_func = inline("double add(double a, double b){ return a+b; }") >>> print "The sum of 3 and 4.5 is ", add_func(3, 4.5) """ ext = Instant() func = c_code[:c_code.index('(')] ret, func_name = func.split() ext.create_extension(code=c_code, module="inline_ext") exec("from inline_ext import %s as func_name"% func_name) return func_name def inline_with_numpy(c_code, **args_dict): """ This is a short wrapper around the create_extention function in Instant. It creates an extension module given that the input is a valid C function. It is only possible to inline one C function each time. The difference between this function and the inline function is that C-arrays can be used. The following example illustrates that. Usage: >>> import numpy >>> import time >>> from Instant import inline_with_numpy >>> c_code = \"\"\" double sum (int n1, double* array1){ double tmp = 0.0; for (int i=0; i>> sum_func = inline_with_numpy(c_code, arrays = [['n1', 'array1']]) >>> a = numpy.arange(10000000); a = numpy.sin(a) >>> sum_func(a) """ ext = Instant() func = c_code[:c_code.index('(')] ret, func_name = func.split() import numpy ext.create_extension(code=c_code, module="inline_ext_numpy", system_headers=["arrayobject.h"], cppargs='-O3', include_dirs= ["%s/numpy"% numpy.get_include()], init_code='import_array();', arrays = args_dict["arrays"]) exec("from inline_ext_numpy import %s as func_name"% func_name) return func_name def inline_with_numeric(c_code, **args_dict): """ This is a short wrapper around the create_extention function in Instant. It creates an extension module given that the input is a valid C function. It is only possible to inline one C function each time. The difference between this function and the inline function is that C-arrays can be used. The following example illustrates that. Usage: >>> import numpy >>> import time >>> from Instant import inline_with_numeric >>> c_code = \"\"\" double sum (int n1, double* array1){ double tmp = 0.0; for (int i=0; i>> sum_func = inline_with_numeric(c_code, arrays = [['n1', 'array1']]) >>> a = numpy.arange(10000000); a = numpy.sin(a) >>> sum_func(a) """ ext = Instant() func = c_code[:c_code.index('(')] ret, func_name = func.split() ext.create_extension(code=c_code, module="inline_ext_numeric", system_headers=["arrayobject.h"], cppargs='-O3', include_dirs= [[sys.prefix + "/include/python" + sys.version[:3] + "/Numeric", sys.prefix + "/include" + "/Numeric"][sys.platform=='win32']], init_code='import_array();', arrays = args_dict["arrays"]) exec("from inline_ext_numeric import %s as func_name"% func_name) return func_name def inline_with_numarray(c_code, **args_dict): """ This is a short wrapper around the create_extention function in Instant. It creates an extension module given that the input is a valid C function. It is only possible to inline one C function each time. The difference between this function and the inline function is that C-arrays can be used. The following example illustrates that. Usage: >>> import numarray >>> import time >>> from Instant import inline_with_numarray >>> c_code = \"\"\" double sum (int n1, double* array1){ double tmp = 0.0; for (int i=0; i>> sum_func = inline_with_numarray(c_code, arrays = [['n1', 'array1']]) >>> a = numarray.arange(10000000); a = numarray.sin(a) >>> sum_func(a) """ ext = Instant() func = c_code[:c_code.index('(')] ret, func_name = func.split() ext.create_extension(code=c_code, module="inline_ext_numarray", system_headers=["arrayobject.h"], cppargs='-O3', include_dirs= [[sys.prefix + "/include/python" + sys.version[:3] + "/numarray", sys.prefix + "/include" + "/numarray"][sys.platform=='win32']], init_code='import_array();', arrays = args_dict["arrays"]) exec("from inline_ext_numarray import %s as func_name"% func_name) return func_name def header_and_libs_from_pkgconfig(*packages): """ This function returns list of include files, flags, libraries and library directories obtain from a pkgconfig file. The usage is: (includes, flags, libraries, libdirs) = header_and_libs_from_pkgconfig(list_of_packages) """ includes = [] flags = [] libs = [] libdirs = [] for pack in packages: # print commands.getstatusoutput("pkg-config --exists %s " % pack) if commands.getstatusoutput("pkg-config --exists %s " % pack)[0] == 0: tmp = string.split(commands.getoutput("pkg-config --cflags-only-I %s " % pack )) for i in tmp: includes.append(i[2:]) tmp = string.split(commands.getoutput("pkg-config --cflags-only-other %s " % pack )) for i in tmp: flags.append(i) tmp = string.split(commands.getoutput("pkg-config --libs-only-l %s " % pack )) for i in tmp: libs.append(i[2:]) tmp = string.split(commands.getoutput("pkg-config --libs-only-L %s " % pack )) for i in tmp: libdirs.append(i[2:]) else: raise OSError, "The pkg-config file %s does not exist" % pack return (includes,flags,libs, libdirs)