Last modified 10 years ago Last modified on 02/25/09 20:43:21

Python and distutils on the BG/P

Python for the front end

Building python for the front end is pretty straightforward. Here is an example build for Python 2.6.1:

$ ../Python-2.6.1/configure --prefix=/home/norris/soft --enable-shared \
    --disable-ipv6 --without-threads CC=gcc LDFLAGS= CXX=g++ LINKCC=gcc
$ make
$ make install

Unfortunately, a regular Python distribution is pretty much useless for cross-compiling, in particular the distutils module contains almost no support for either IBM compilers or cross-compilation where the Python used to build the code is different from the runtime Python.

I added a module to Lib/distutils in the Python source tree, called, which started out as a copy of


Contains the MPIXLCCompiler class, a subclass of CCompiler that handles
the IBM cross-compilation on Blue Gene/P.
  * macros defined with -Dname[=value]
  * macros undefined with -Uname
  * include search directories specified with -Idir
  * libraries specified with -lllib
  * library search directories specified with -Ldir
  * compile handled by 'cc' (or similar) executable with -c option:
    compiles .c to .o
  * link static library handled by 'ar' command (possibly with 'ranlib')
  * link shared library handled by 'mpixlc -G -nostaticlink'

__revision__ = "$Id: 65012 2009-02-25 13:24:06Z boyana.norris $"

import os, sys
from types import StringType, NoneType

from distutils import sysconfig
from distutils.dep_util import newer
from distutils.ccompiler import \
     CCompiler, gen_preprocess_options, gen_lib_options
from distutils.errors import \
     DistutilsExecError, CompileError, LibError, LinkError
from distutils import log

class MPIXLCCompiler(CCompiler):

    compiler_type = 'bgp'
    #compiler_class['mpixlc'] = ('mpixlccompiler', 'MPIXLCCompiler', "Blue Gene/P Cross Compiler")

    # These are used by CCompiler in two places: the constructor sets
    # instance attributes 'preprocessor', 'compiler', etc. from them, and
    # 'set_executable()' allows any of these to be set.  The defaults here
    # are pretty generic; they will probably have to be set by an outsider
    # (eg. using information discovered by the sysconfig about building
    # Python extensions).
    executables = {'preprocessor' : ["gcc -E"],
                   'compiler'     : ["mpixlc_r","-qsuppress=1506-1108","-qalias=noansi","-qrtti=all"],
                   'compiler_so'  : ["mpixlc_r","-qsuppress=1506-1108","-qalias=noansi","-qrtti=all","-qpic","-DPIC"],
                   'compiler_cxx' : ["mpixlcxx_r"],
                   'linker_so'    : ["mpixlc_r"],
                   'linker_exe'   : ["mpixlc_r"],
                   'linker_flags' : ["--shared","-dy","-qnostaticlink"],
                   'archiver'     : ["ar", "-cr"],
                   'ranlib'       : None,

    # Needed for the filename generation methods provided by the base
    # class, CCompiler.  NB. whoever instantiates/uses a particular
    # UnixCCompiler instance should set 'shared_lib_ext' -- we set a
    # reasonable common default here, but it's not necessarily used on all
    # Unices!

    src_extensions = [".c",".C",".cc",".cxx",".cpp",".m"]
    obj_extension = ".o"
    static_lib_extension = ".a"
    shared_lib_extension = ".so"
    static_lib_format = shared_lib_format = "lib%s%s"
    if sys.platform == "cygwin":
        exe_extension = ".exe"

    def preprocess(self, source,
                   output_file=None, macros=None, include_dirs=None,
                   extra_preargs=None, extra_postargs=None):
        if '/bgsys/drivers/ppcfloor/gnu-linux/include/python2.5' not in include_dirs:
        ignore, macros, include_dirs_ignore = \
            self._fix_compile_args(None, macros, include_dirs)
        pp_opts = gen_preprocess_options(macros, include_dirs)
        pp_args = self.preprocessor + pp_opts
        if output_file:
            pp_args.extend(['-o', output_file])
        if extra_preargs:
            pp_args[:0] = extra_preargs
        if extra_postargs:

        # We need to preprocess: either we're being forced to, or we're
        # generating output to stdout, or there's a target output file and
        # the source file is newer than the target (or the target doesn't
        # exist).
        if self.force or output_file is None or newer(source, output_file):
            if output_file:
            except DistutilsExecError, msg:
                raise CompileError, msg

    def _compile(self, obj, src, ext, cc_args, extra_postargs, pp_opts):
        compiler_so = self.compiler_so
            self.spawn(compiler_so + cc_args + [src, '-o', obj] +
        except DistutilsExecError, msg:
            raise CompileError, msg

    def create_static_jib(self, objects, output_libname,
                          output_dir=None, debug=0, target_lang=None):
        objects, output_dir = self._fix_object_args(objects, output_dir)

        output_filename = \
            self.library_filename(output_libname, output_dir=output_dir)

        if self._need_link(objects, output_filename):
            self.spawn(self.archiver +
                       [output_filename] +
                       objects + self.objects)

            # Not many Unices required ranlib anymore -- SunOS 4.x is, I
            # think the only major Unix that does.  Maybe we need some
            # platform intelligence here to skip ranlib if it's not
            # needed -- or maybe Python's configure script took care of
            # it for us, hence the check for leading colon.
            if self.ranlib:
                    self.spawn(self.ranlib + [output_filename])
                except DistutilsExecError, msg:
                    raise LibError, msg
            log.debug("skipping %s (up-to-date)", output_filename)

    def link(self, target_desc, objects,
             output_filename, output_dir=None, libraries=None,
             library_dirs=None, runtime_library_dirs=None,
             export_symbols=None, debug=0, extra_preargs=None,
             extra_postargs=None, build_temp=None, target_lang=None):

        objects, output_dir = self._fix_object_args(objects, output_dir)

        # These are the "standard" locations of the compute-node libraries on the bgp:
        if '/bgsys/drivers/ppcfloor/gnu-linux/lib' not in self.library_dirs:
        if '/bgsys/drivers/ppcfloor/gnu-linux/lib' not in self.runtime_library_dirs:
        libraries, library_dirs, runtime_library_dirs = \
           self._fix_lib_args(libraries, library_dirs, runtime_library_dirs)
        if 'python2.5' not in self.libraries:

        lib_opts = gen_lib_options(self, library_dirs, runtime_library_dirs,

        for opt in lib_opts[:]:
            if opt.startswith('-lpython'): lib_opts.remove(opt)

        if type(output_dir) not in (StringType, NoneType):
            raise TypeError, "'output_dir' must be a string or None"
        if output_dir is not None:
            output_filename = os.path.join(output_dir, output_filename)

        if self._need_link(objects, output_filename):
            ld_args = (objects + self.linker_flags +  self.objects +
                       lib_opts + ['-o', output_filename])
            if debug:
                ld_args[:0] = ['-g']
            if extra_preargs:
                ld_args[:0] = extra_preargs
            if extra_postargs:
                if target_desc == CCompiler.EXECUTABLE:
                    linker = self.linker_exe[:]
                    linker = self.linker_so[:]
                if target_lang == "c++" and self.compiler_cxx:
                    # skip over environment variable settings if /usr/bin/env
                    # is used to set up the linker's environment.
                    # This is needed on OSX. Note: this assumes that the
                    # normal and C++ compiler have the same environment
                    # settings.
                    i = 0
                    if os.path.basename(linker[0]) == "env":
                        i = 1
                        while '=' in linker[i]:
                            i = i + 1

                    linker[i] = self.compiler_cxx[i]

                #print linker, ld_args
                self.spawn(linker + ld_args)
            except DistutilsExecError, msg:
                raise LinkError, msg
            log.debug("skipping %s (up-to-date)", output_filename)

    # -- Miscellaneous methods -----------------------------------------
    # These are all used by the 'gen_lib_options() function, in

    def library_dir_option(self, dir):
        return "-L" + dir

    def runtime_library_dir_option(self, dir):
        return "-R" + dir

    def library_option(self, lib):
        return "-l" + lib

    def find_library_file(self, dirs, lib, debug=0):
        shared_f = self.library_filename(lib, lib_type='shared')
        static_f = self.library_filename(lib, lib_type='static')

        for dir in dirs:
            shared = os.path.join(dir, shared_f)
            static = os.path.join(dir, static_f)
            # We're second-guessing the linker here, with not much hard
            # data to go on: GCC seems to prefer the shared library, so I'm
            # assuming that *all* Unix C compilers do.  And of course I'm
            # ignoring even GCC's "-static" option.  So sue me.
            if os.path.exists(shared):
                return shared
            elif os.path.exists(static):
                return static

        # Oops, didn't find it in *any* of 'dirs'
        return None

Then, I had to modify slightly to make distutils aware of the new compiler (it's a bit dumb about finding modules dynamically). Basically I added the last entry to the compiler_class dictionary:

compiler_class = { 'unix':    ('unixccompiler', 'UnixCCompiler',
                               "standard UNIX-style compiler"),
                   'msvc':    ('msvccompiler', 'MSVCCompiler',
                               "Microsoft Visual C++"),
                   'cygwin':  ('cygwinccompiler', 'CygwinCCompiler',
                               "Cygwin port of GNU C Compiler for Win32"),
                   'mingw32': ('cygwinccompiler', 'Mingw32CCompiler',
                               "Mingw32 port of GNU C Compiler for Win32"),
                   'bcpp':    ('bcppcompiler', 'BCPPCompiler',
                               "Borland C++ Compiler"),
                   'mwerks':  ('mwerkscompiler', 'MWerksCompiler',
                               "MetroWerks CodeWarrior"),
                   'emx':     ('emxccompiler', 'EMXCCompiler',
                               "EMX port of GNU C Compiler for OS/2"),
                   'mpixlc':  ('mpixlccompiler', 'MPIXLCCompiler', 
                               "Blue Gene/P Cross Compiler")

The Python patched in that manner can be installed and used to cross-compile using the IBM compilers and the compute-node version of Python (which doesn't have to be the same version as the one used for building, e.g., I built with Python 2.6.1 while the compute-node version is 2.5).