Saturday, May 14, 2016

Python source distribution with fortran dependencies

This post is intended make crystal clear how to distribute python packages which need to interface with a large fortran library. If you haven't already, you should familiarize yourself with the basics of using numpy.distutils with f2py [f2py-distutils documentation].

Are you back now - and fully understood the docs? Good.

Ideally, one can use f2py directives to eliminate the need for a signature file. What follows below will describe how to coax numpy.distutils into building the shared library WITHOUT having to create a signature file.


A Simple Example

Suppose you have the following Code structure (files and modules are 1 to 1 in this example):

src/
 |--pysrc/
 |    |--main.py
 |--forsrc/
 |    |--interface.f08
 |    |--codelogic.f08
 |--setup.py

codelogic.f08 may contain all sorts of derived types which f2py can't really handle. The interface.f08 file should contain subroutines and functions that contain ONLY arrays and default types (real, complex, integer, etc) within their message signatures (inputs and outputs). So we really want f2py to generate a shared library which accesses the interface module, but ignores the codelogic module.

The magic that makes this happen all occurs within the Extension object, inside an option called f2py_opts. Creating the following will do the trick:
    f2py_opts = [':','forsrc/interface.f08']
So within setup.py there will be an Extension object defined as:
    forlib = Extension(name='my_fort_lib',
                       sources=['forsrc/interface.f08',
                                'forsrc/codelogic.f08'],
                       f2py_opts=[':','forsrc/interface.f08'])
Toss this into your setup() function, and you should be able to
  import my_fort_lib
inside of main.py


A Deeper Look

Consider the following Extension:
  forlib = Extension(name='my_fort_lib',
                     sources=['forsrc/interface.f08',
                              'forsrc/codelogic.f08'],
                     extra_f90_compile_args=['-ggdb',-gbacktrace],
                     libraries=['m'],
                     f2py_opts=[':',
                                'forsrc/interface.f08',
                                'skip:',
                                'foo', 'bar'])
Here's a description of each keyword and what options were provided:

  •   name='my_fort_lib' -> The name of the shared library will be such that python can import my_fort_lib
  •   sources=[...] -> the fortran source files
  •   extra_f90_compile_args=[...] -> options passed to the compiler. -ggdb and -gbacktrace enable debugging and backtrace flags for the gcc compilers
  •   libraries -> libraries the linker needs to link against. m is the standard math library
  •   f2py_opts -> the options to pass to f2py
    • ':' -> the following items in the list become the only source files f2py uses when constructing the library. This terminates when another option is found in the list.
    • 'skip:' -> the following items in the list are skipped when constructing the library. These should be subroutine and function names.
    • 'foo', 'bar' -> subroutine names not exposed to the shared library available to python