Hello Cython Developers,

I wanted to announce "Cpyx", a module I've been working on off and on since 2006 that I use to automatically compile and also inline Cython code in my work (mostly because I like to do everything in one step).

This is more-or-less a prototype, but it works for me on Windows, Mac, and Ubuntu, so I thought I'd share!

I know it has similar goals to pyx_import, but I think the two are quite compilementary... (and I couldn't figure out how to get numpy support in pyx_import when it came out...)

My main hope for this is that it can give people a starting point for using manual compilation/distutils on their system (it is very verbose by default) and that it can automatically inline code with numpy support!

If you find it useful, I think it is almost mature enough to be included in cython, and if not I certainly enjoy using it!

In any case, I'd love some feedback.

Thanks for all the hard work you all are doing with Greg's brainchild!
-David Mashburn
# Author: David Mashburn
# Created July 2006
# Last Modified December 11, 2009
# License: ??? (Apache 2) -- whatever is easiest for cython folks...

# This module is for the automatic compilation (and also inlining) of
# Pyrex / Cython code...
# It can use distutils or manual compilation with gcc (or another compiler)
# It can work with a single existing C source and automatically compile it as well
# It has been tested on Windows, Mac, and Ubuntu Linux

# That said, I make no guarantees that it will work as expected!
# Numpy support is automatically enabled for the non-distutils version...

# Unless the printCmds option is set to False, the script will output every action taken
# and command run

# My main goal for this is to aid people in learning how to compile cython code
# on their system, and give them a starting point so they can tweak what they want...

# My other goal is to automate the Cython compile process so I can do everything in
# one step after getting it set up :)

# I really like the inline feature a lot for testing!
# And try it with PySlices, the latest incarnation of the wxPython shell, PyCrust! (Shameless plug...)

import os
import sys
import glob
import random
import numpy
import SetEnvironVars

# Making this work in Vista...
# Download the latest mingw (5.x.x):
# add C:\MinGW\bin to the PATH environment variable

# Should work with latest MingW on Windows 7...

# Making this work on Mac...
# Download Xcode from the apple developer site (create a login) and install it:
# http://connect.apple.com

# Sample output for Cpyx on Windows:
# Pieces:
# gcc -c -IC:/Python25/include PyrexExample.c -o PyrexExample.o
# gcc -shared PyrexExample.o -LC:/Python25/libs -lpython25 -o PyrexExample.pyd
# All-in-one:
# gcc -shared PyrexExample.c -IC:/Python25/include -LC:/Python25/libs -lpython25 -o PyrexExample.pyd
# All-in-one with linking dll...
# gcc -shared numpyTest.c -IC:/Python25/include -LC:/Python25/libs -LC:/Users/mashbudn/Programming/Python/Pyx -lpython25 -lnumpyTestC -o numpyTest.pyd

myPythonDir=os.environ['MYPYTHON']
myPyrexDir=os.environ['MYPYREX']
globalUseCython=True

if sys.platform=='win32':
    pyrexcName='"' + 'C:\\Python25\\Scripts\\pyrexc.py' + '"' # Full path to the Pyrex compiler script
    cythonName='"' + 'C:\\Python25\\Scripts\\cython.py' + '"' # Full path to the Cython compiler script
    pythonName='C:\\Python25\\python.exe' # Full path to python.exe
    sitePackages='C:\\Python25\\Lib\\site-packages'
    pythonInclude='C:/Python25/include'
    pythonLibs='C:/Python25/libs'
elif sys.platform=='darwin':
    pyrexcName='"' + '/Library/Frameworks/Python.framework/Versions/5.1.1/bin/pyrexc' + '"' # Full path to the Pyrex compiler script
    cythonName='"' + '/Library/Frameworks/Python.framework/Versions/5.1.1/bin/cython' + '"' # Full path to the Cython compiler script
    pythonName='/Library/Frameworks/Python.framework/Versions/5.1.1/bin/python' # Full path to python.exe
    sitePackages='/Library/Frameworks/Python.framework/Versions/5.1.1/lib/python2.5/site-packages'
    pythonInclude='/Library/Frameworks/Python.framework/Versions/5.1.1/include'
    pythonLibs='/Library/Frameworks/Python.framework/Versions/5.1.1/lib/python2.5/config/' # contains libpython2.5.so
elif sys.platform=='linux2':
    pyrexcName='"' + '/usr/bin/pyrexc' + '"' # Full path to the Pyrex compiler script
    cythonName='"' + '/usr/bin/cython' + '"' # Full path to the Cython compiler script
    pythonName='/usr/bin/python2.5' # Full path to python.exe
    sitePackages='/usr/lib/python2.5/site-packages'
    pythonInclude='/usr/include/python2.5'
    pythonLibs='/usr/lib' # contains libpython2.5.so
else:
    print 'Platform "' + sys.platform + '" not supported yet'

# New way to find numpy's arrayobject.h to include
arrayobjecthPath = os.path.join(numpy.get_include(),'numpy','arrayobject.h')
arrayObjectDir = numpy.get_include()

def Cdll(cNameIn='',printCmds=True,gccOptions=''):
    cwd=os.getcwd()
    
    (cPath,cName)=os.path.split(cNameIn) # input path and input file name
    if cPath=='':
        cPath=myPyrexDir # directory used for all Pyrex stuff
    dllPath=cPath
    
    stripName=(os.path.splitext(cName))[0] # input file name without extension
    
    if sys.platform=='win32':      dllName='"' + os.path.join(dllPath,stripName+'.dll') + '"'
    elif sys.platform=='darwin':   dllName='"' + os.path.join(dllPath,'lib'+stripName+'.so') + '"'
    elif sys.platform=='linux2':   dllName='"' + os.path.join(dllPath,'lib'+stripName+'.so') + '"'
    else:                          print 'Platform "' + sys.platform + '" not supported yet'
    
    cName='"' + os.path.join(cPath,stripName+'.c') + '"' # redefine cName
    hName='"' + os.path.join(cPath,stripName+'.h') + '"'
    oName='"' + os.path.join(dllPath,stripName+'.o') + '"'
    
    os.chdir(cPath)
    
    cmd=' '.join(['gcc',gccOptions,'-fPIC','-c',cName,'-o',stripName+'.o'])
    if printCmds:
        print '\n', cmd
    os.system(cmd)
    
    cmd=' '.join(['gcc','-shared','-o',dllName,oName])
    if printCmds:
        print '\n', cmd
    os.system(cmd)
    
    os.chdir(cwd)

def Cpyx(pyxNameIn='PyrexExample.pyx',useDistutils=False,useCython=globalUseCython,gccOptions='',printCmds=True):
    cwd=os.getcwd()
    
    (pyxPath,pyxName)=os.path.split(pyxNameIn) # input path and input file name
    
    if pyxPath=='':
        pydPath=mainDir=myPyrexDir # directory used for all Pyrex stuff
    else:
        pydPath=mainDir=pyxPath
    
    pyxStrip=(os.path.splitext(pyxName))[0] # input file name without extension
    
    extName='"' + pyxStrip + '"'
    pyxName='"' + os.path.join(mainDir,pyxStrip+'.pyx') + '"' # Full path to the PYX file (must be in Python/Pyx folder) redefine pyxName
    pyx2cName='"' + os.path.join(pydPath,pyxStrip+'.c') + '"' # Full path to the C file to be created
    pydName='"' + os.path.join(pydPath,pyxStrip+'.pyd') + '"' # Full path to the PYD file to be created
    soName='"' + os.path.join(pydPath,pyxStrip+'.so') + '"' # Full path to the lib*.so file to be created
    setupName='"' + os.path.join(pydPath,'setup.py') + '"' # Full path of the Setup File to be created
    
    # run the main pyrex command to make the C file
    pyxCompiler = cythonName if useCython else pyrexcName
    cmd=' '.join([pythonName,pyxCompiler,pyxName,'-o',pyx2cName])
    if printCmds:
        print '\n', cmd
    os.system(cmd)
    
    if useDistutils:
        
        #write setup.py which will make a PYD file that can be imported
        setupText="""### This file is setup.py ###
from distutils.core import setup 
from distutils.extension import Extension 
from Pyrex.Distutils import build_ext 

setup( 
  name = 'Lock module', 
  ext_modules=[ 
    Extension(""" + extName + ', [' + pyxName.replace('\\','\\\\') + ']' + """),
  ], 
  cmdclass = {'build_ext': build_ext} 
)"""
        
        if printCmds:
            print 'Write Stuff to ', setupName[1:-1]
        fid = open(setupName[1:-1],'w') # [1:-1] removes quotes
        fid.write(setupText)
        fid.close()
        
        # run setup.py
        
        os.chdir(mainDir)
        
        if sys.platform=='win32':        cmd=' '.join([pythonName,setupName,'build_ext','--compiler=mingw32','--inplace'])
        elif sys.platform=='darwin':     cmd=' '.join([pythonName,setupName,'build_ext','--inplace'])
        elif sys.platform=='linux2':     cmd=' '.join([pythonName,setupName,'build_ext','--inplace'])
        else:                            print 'Platform "' + sys.platform + '" not supported yet'
    
    else:
        if sys.platform=='win32':        cmd=' '.join(['gcc',gccOptions,'-fPIC','-shared',pyx2cName,'-I'+pythonInclude,'-L'+pythonLibs,'-lpython25','-o',pydName])
        elif sys.platform=='darwin':     cmd=' '.join(['gcc',gccOptions,'-fno-strict-aliasing','-Wno-long-double','-no-cpp-precomp','-mno-fused-madd','-fno-common',
                                                       '-dynamic','-DNDEBUG','-g','-O3','-bundle','-undefined dynamic_lookup','-I'+pythonInclude,
                                                       '-I'+pythonInclude+'/python2.5','-I'+arrayObjectDir,'-L'+pythonLibs,'-L/usr/local/lib',pyx2cName,'-o',soName])
        elif sys.platform=='linux2':     cmd=' '.join(['gcc',gccOptions,'-fPIC','-shared',pyx2cName,'-I'+pythonInclude,'-L'+pythonLibs,'-lpython2.5','-o',soName])
        else:                            print 'Platform "' + sys.platform + '" not supported yet'
    
    if printCmds:
        print '\n', cmd
    os.system(cmd)
    
    os.chdir(cwd)

def CpyxLib(pyxNameIn='PyrexExample.pyx',cNameIn='CTestC.c',recompile=True,useDistutils=False,useCython=globalUseCython,gccOptions='',printCmds=True):
    cwd=os.getcwd()
    
    (pyxPath,pyxName)=os.path.split(pyxNameIn) # input path and input file name
    (cPath,cName)=os.path.split(cNameIn) # input path and input file name
    
    if cPath=='':
        dllPath=cPath=myPyrexDir # directory used for all Pyrex stuff
    
    if pyxPath=='':
        pydPath=mainDir=myPyrexDir # directory used for all Pyrex stuff
    else:
        pydPath=mainDir=pyxPath
    
    pyxStrip=(os.path.splitext(pyxName))[0] # input file name without extension
    cStrip=(os.path.splitext(cName))[0] # input file name without extension
    
    extName='"' + pyxStrip + '"'
    pyxName='"' + os.path.join(mainDir,pyxStrip+'.pyx') + '"' # Full path to the PYX file (must be in Python\\Pyrex folder)
    
    if sys.platform=='win32':
        dllName='"' + os.path.join(dllPath,cStrip+'.dll') + '"'
        libName='"' + os.path.join(dllPath,cStrip) + '"'
        library_dirs_txt=''
    elif sys.platform=='darwin':
        dllName='"' + os.path.join(dllPath,'lib'+cStrip+'.so') + '"'
        libName='"' + cStrip + '"'
        library_dirs_txt="""
    library_dirs=[""" + '"' + pydPath.replace('\\','\\\\') + '"' + """],
    runtime_library_dirs=[""" + '"' + pydPath.replace('\\','\\\\') + '"' + """],"""
    elif sys.platform=='linux2':
        dllName='"' + os.path.join(dllPath,'lib'+cStrip+'.so') + '"'
        libName='"' + cStrip + '"'
        library_dirs_txt="""
    library_dirs=[""" + '"' + pydPath.replace('\\','\\\\') + '"' + """],
    runtime_library_dirs=[""" + '"' + pydPath.replace('\\','\\\\') + '"' + """],"""
    else:
        print 'Platform "' + sys.platform + '" not supported yet'
    
    cName='"' + os.path.join(cPath,cStrip+'.c') + '"' # redefine cName
    hName='"' + os.path.join(cPath,cStrip+'.h') + '"'
    oName='"' + os.path.join(dllPath,cStrip+'.o') + '"'
    
    os.chdir(cPath)
    
    pyx2cName='"' + os.path.join(pydPath,pyxStrip+'.c') + '"' # Full path to the C file to be created
    setupName='"' + os.path.join(pydPath,'setup.py') + '"' # Full path of the Setup File to be created
    pydName='"' + os.path.join(pydPath,pyxStrip+'.pyd') + '"' # Full path to the PYD file to be created
    soName='"' + os.path.join(pydPath,pyxStrip+'.so') + '"' # Full path to the lib*.so file to be created
    
    # compile the DLL needed for the link to the C file
    if recompile:
        Cdll(cName[1:-1],printCmds=printCmds,gccOptions=gccOptions) # [1:-1] to remove the quotes
    
    # run the main pyrex command to make the C file
    pyxCompiler = cythonName if useCython else pyrexcName
    cmd=' '.join([pythonName,pyxCompiler,pyxName,'-o',pyx2cName])
    if printCmds:
        print '\n', cmd
    os.system(cmd)
    
    if useDistutils:
        #write setup.py which will make a PYD file that can be imported
        setupText="""### This file is setup.py ###
from distutils.core import setup 
from distutils.extension import Extension 
from Pyrex.Distutils import build_ext 

setup( 
  name = 'Lock module', 
  ext_modules=[ 
    Extension(""" + extName + ', [' + pyxName.replace('\\','\\\\') + """],"""+library_dirs_txt+"""
    libraries=[""" + libName.replace('\\','\\\\') + ']' + """),
  ], 
  cmdclass = {'build_ext': build_ext} 
)"""
        if printCmds:
            print 'Write Stuff to ', setupName[1:-1]
        fid = open(setupName[1:-1],'w') # [1:-1] removes quotes
        fid.write(setupText)
        fid.close()
        
        # run setup.py
        os.chdir(mainDir)
        
        if sys.platform=='win32':        cmd=' '.join([pythonName,setupName,'build_ext','--compiler=mingw32','--inplace'])
        elif sys.platform=='darwin':     cmd=' '.join([pythonName,setupName,'build_ext','--inplace'])
        elif sys.platform=='linux2':     cmd=' '.join([pythonName,setupName,'build_ext','--inplace'])
        else:                            print 'Platform "' + sys.platform + '" not supported yet'
    else:
        if sys.platform=='win32':        cmd=' '.join(['gcc',gccOptions,'-fPIC','-shared',pyx2cName,'-I'+pythonInclude,'-L'+pythonLibs,'-L'+cPath,'-Wl,-R'+cPath,'-lpython25','-l'+cStrip,'-o',pydName])
        elif sys.platform=='darwin':     cmd=' '.join(['gcc',gccOptions,'-fno-strict-aliasing','-Wno-long-double','-no-cpp-precomp','-mno-fused-madd','-fno-common',
                                                       '-dynamic','-DNDEBUG','-g','-O3','-bundle','-undefined dynamic_lookup','-I'+pythonInclude,
                                                       '-I'+pythonInclude+'/python2.5','-I'+arrayObjectDir,'-L'+pythonLibs,'-L/usr/local/lib','-L'+cPath,'-Wl,-R'+cPath,
                                                       '-l'+cStrip,pyx2cName,'-o',soName])
        elif sys.platform=='linux2':     cmd=' '.join(['gcc',gccOptions,'-fPIC','-shared',pyx2cName,'-I'+pythonInclude,'-L'+pythonLibs,'-L'+cPath,'-Wl,-R'+cPath,'-lpython2.5','-l'+cStrip,'-o',soName])
        else:                            print 'Platform "' + sys.platform + '" not supported yet'
    
    if printCmds:
        print '\n', cmd
    os.system(cmd)
    
    os.chdir(cwd)

#Shamelessly steal the idea used by scipy.weave.inline but for Pyrex/Cython instead...
# In order to be able to import *, have to use exec in the calling module...
def PyrexInline(code,cleanUp=False,useDistutils=False,useCython=False,gccOptions='',printCmds=True):
    '''PyrexInline returns a string that is an import statement to the temporary cython module'''+ \
    '''Use this like: exec(PyrexInline(r"""<somecode>""",<options>))'''
    
    testCode=r"""
cdef extern from "stdio.h":
    ctypedef struct FILE

    FILE * stdout
    int printf(char *format,...)
    int fflush( FILE *stream )

def PyrexPrint(mystring):
    printf(mystring)
    fflush(stdout)

PyrexPrint('HelloWorld!')
"""
    tmpPath=os.path.expanduser('~/.Cpyx_tmp')
    if not os.path.isdir(tmpPath):
        os.mkdir(tmpPath)
    if tmpPath not in sys.path:
        sys.path.append(tmpPath)
    if cleanUp:
        CleanTmp()
    
    # Ensure you always get a new module!
    # This means there is no reason to "reload"
    # Also means memory gets majorly eaten up!
    # Can't have everything!
    moduleName='Pyrex'+str(random.randint(0,1e18))
    file=os.path.join(tmpPath,moduleName+'.pyx')
    
    fid=open(file,'w')
    fid.write(code)
    fid.close()
    
    Cpyx(file,useDistutils=useDistutils,useCython=useCython,gccOptions=gccOptions,printCmds=printCmds)
    
    #cmd="""import """+moduleName+""" as LoadPyrexInline"""
    cmd="""from """+moduleName+""" import *"""
    if printCmds:
        print cmd
    return cmd

# Create a dummy function that defaults to using Cython instead for clarity...
def CythonInline(code,cleanUp=False,useDistutils=False,useCython=True,gccOptions='',printCmds=True):
    return PyrexInline(code,cleanUp=cleanUp,useDistutils=useDistutils,useCython=useCython,gccOptions=gccOptions,printCmds=printCmds)

def CleanTmp():
    tmpPath=os.path.expanduser('~/.Cpyx_tmp')
    for i in glob.glob(os.path.join(tmpPath,'*')):
        os.remove(i)
_______________________________________________
Cython-dev mailing list
[email protected]
http://codespeak.net/mailman/listinfo/cython-dev

Reply via email to