Chris@87: """ Modified version of build_ext that handles fortran source files. Chris@87: Chris@87: """ Chris@87: from __future__ import division, absolute_import, print_function Chris@87: Chris@87: import os Chris@87: import sys Chris@87: from glob import glob Chris@87: Chris@87: from distutils.dep_util import newer_group Chris@87: from distutils.command.build_ext import build_ext as old_build_ext Chris@87: from distutils.errors import DistutilsFileError, DistutilsSetupError,\ Chris@87: DistutilsError Chris@87: from distutils.file_util import copy_file Chris@87: Chris@87: from numpy.distutils import log Chris@87: from numpy.distutils.exec_command import exec_command Chris@87: from numpy.distutils.system_info import combine_paths Chris@87: from numpy.distutils.misc_util import filter_sources, has_f_sources, \ Chris@87: has_cxx_sources, get_ext_source_files, \ Chris@87: get_numpy_include_dirs, is_sequence, get_build_architecture, \ Chris@87: msvc_version Chris@87: from numpy.distutils.command.config_compiler import show_fortran_compilers Chris@87: Chris@87: try: Chris@87: set Chris@87: except NameError: Chris@87: from sets import Set as set Chris@87: Chris@87: class build_ext (old_build_ext): Chris@87: Chris@87: description = "build C/C++/F extensions (compile/link to build directory)" Chris@87: Chris@87: user_options = old_build_ext.user_options + [ Chris@87: ('fcompiler=', None, Chris@87: "specify the Fortran compiler type"), Chris@87: ] Chris@87: Chris@87: help_options = old_build_ext.help_options + [ Chris@87: ('help-fcompiler', None, "list available Fortran compilers", Chris@87: show_fortran_compilers), Chris@87: ] Chris@87: Chris@87: def initialize_options(self): Chris@87: old_build_ext.initialize_options(self) Chris@87: self.fcompiler = None Chris@87: Chris@87: def finalize_options(self): Chris@87: incl_dirs = self.include_dirs Chris@87: old_build_ext.finalize_options(self) Chris@87: if incl_dirs is not None: Chris@87: self.include_dirs.extend(self.distribution.include_dirs or []) Chris@87: Chris@87: def run(self): Chris@87: if not self.extensions: Chris@87: return Chris@87: Chris@87: # Make sure that extension sources are complete. Chris@87: self.run_command('build_src') Chris@87: Chris@87: if self.distribution.has_c_libraries(): Chris@87: if self.inplace: Chris@87: if self.distribution.have_run.get('build_clib'): Chris@87: log.warn('build_clib already run, it is too late to ' \ Chris@87: 'ensure in-place build of build_clib') Chris@87: build_clib = self.distribution.get_command_obj('build_clib') Chris@87: else: Chris@87: build_clib = self.distribution.get_command_obj('build_clib') Chris@87: build_clib.inplace = 1 Chris@87: build_clib.ensure_finalized() Chris@87: build_clib.run() Chris@87: self.distribution.have_run['build_clib'] = 1 Chris@87: Chris@87: else: Chris@87: self.run_command('build_clib') Chris@87: build_clib = self.get_finalized_command('build_clib') Chris@87: self.library_dirs.append(build_clib.build_clib) Chris@87: else: Chris@87: build_clib = None Chris@87: Chris@87: # Not including C libraries to the list of Chris@87: # extension libraries automatically to prevent Chris@87: # bogus linking commands. Extensions must Chris@87: # explicitly specify the C libraries that they use. Chris@87: Chris@87: from distutils.ccompiler import new_compiler Chris@87: from numpy.distutils.fcompiler import new_fcompiler Chris@87: Chris@87: compiler_type = self.compiler Chris@87: # Initialize C compiler: Chris@87: self.compiler = new_compiler(compiler=compiler_type, Chris@87: verbose=self.verbose, Chris@87: dry_run=self.dry_run, Chris@87: force=self.force) Chris@87: self.compiler.customize(self.distribution) Chris@87: self.compiler.customize_cmd(self) Chris@87: self.compiler.show_customization() Chris@87: Chris@87: # Create mapping of libraries built by build_clib: Chris@87: clibs = {} Chris@87: if build_clib is not None: Chris@87: for libname, build_info in build_clib.libraries or []: Chris@87: if libname in clibs and clibs[libname] != build_info: Chris@87: log.warn('library %r defined more than once,'\ Chris@87: ' overwriting build_info\n%s... \nwith\n%s...' \ Chris@87: % (libname, repr(clibs[libname])[:300], repr(build_info)[:300])) Chris@87: clibs[libname] = build_info Chris@87: # .. and distribution libraries: Chris@87: for libname, build_info in self.distribution.libraries or []: Chris@87: if libname in clibs: Chris@87: # build_clib libraries have a precedence before distribution ones Chris@87: continue Chris@87: clibs[libname] = build_info Chris@87: Chris@87: # Determine if C++/Fortran 77/Fortran 90 compilers are needed. Chris@87: # Update extension libraries, library_dirs, and macros. Chris@87: all_languages = set() Chris@87: for ext in self.extensions: Chris@87: ext_languages = set() Chris@87: c_libs = [] Chris@87: c_lib_dirs = [] Chris@87: macros = [] Chris@87: for libname in ext.libraries: Chris@87: if libname in clibs: Chris@87: binfo = clibs[libname] Chris@87: c_libs += binfo.get('libraries', []) Chris@87: c_lib_dirs += binfo.get('library_dirs', []) Chris@87: for m in binfo.get('macros', []): Chris@87: if m not in macros: Chris@87: macros.append(m) Chris@87: Chris@87: for l in clibs.get(libname, {}).get('source_languages', []): Chris@87: ext_languages.add(l) Chris@87: if c_libs: Chris@87: new_c_libs = ext.libraries + c_libs Chris@87: log.info('updating extension %r libraries from %r to %r' Chris@87: % (ext.name, ext.libraries, new_c_libs)) Chris@87: ext.libraries = new_c_libs Chris@87: ext.library_dirs = ext.library_dirs + c_lib_dirs Chris@87: if macros: Chris@87: log.info('extending extension %r defined_macros with %r' Chris@87: % (ext.name, macros)) Chris@87: ext.define_macros = ext.define_macros + macros Chris@87: Chris@87: # determine extension languages Chris@87: if has_f_sources(ext.sources): Chris@87: ext_languages.add('f77') Chris@87: if has_cxx_sources(ext.sources): Chris@87: ext_languages.add('c++') Chris@87: l = ext.language or self.compiler.detect_language(ext.sources) Chris@87: if l: Chris@87: ext_languages.add(l) Chris@87: # reset language attribute for choosing proper linker Chris@87: if 'c++' in ext_languages: Chris@87: ext_language = 'c++' Chris@87: elif 'f90' in ext_languages: Chris@87: ext_language = 'f90' Chris@87: elif 'f77' in ext_languages: Chris@87: ext_language = 'f77' Chris@87: else: Chris@87: ext_language = 'c' # default Chris@87: if l and l != ext_language and ext.language: Chris@87: log.warn('resetting extension %r language from %r to %r.' % Chris@87: (ext.name, l, ext_language)) Chris@87: ext.language = ext_language Chris@87: # global language Chris@87: all_languages.update(ext_languages) Chris@87: Chris@87: need_f90_compiler = 'f90' in all_languages Chris@87: need_f77_compiler = 'f77' in all_languages Chris@87: need_cxx_compiler = 'c++' in all_languages Chris@87: Chris@87: # Initialize C++ compiler: Chris@87: if need_cxx_compiler: Chris@87: self._cxx_compiler = new_compiler(compiler=compiler_type, Chris@87: verbose=self.verbose, Chris@87: dry_run=self.dry_run, Chris@87: force=self.force) Chris@87: compiler = self._cxx_compiler Chris@87: compiler.customize(self.distribution, need_cxx=need_cxx_compiler) Chris@87: compiler.customize_cmd(self) Chris@87: compiler.show_customization() Chris@87: self._cxx_compiler = compiler.cxx_compiler() Chris@87: else: Chris@87: self._cxx_compiler = None Chris@87: Chris@87: # Initialize Fortran 77 compiler: Chris@87: if need_f77_compiler: Chris@87: ctype = self.fcompiler Chris@87: self._f77_compiler = new_fcompiler(compiler=self.fcompiler, Chris@87: verbose=self.verbose, Chris@87: dry_run=self.dry_run, Chris@87: force=self.force, Chris@87: requiref90=False, Chris@87: c_compiler=self.compiler) Chris@87: fcompiler = self._f77_compiler Chris@87: if fcompiler: Chris@87: ctype = fcompiler.compiler_type Chris@87: fcompiler.customize(self.distribution) Chris@87: if fcompiler and fcompiler.get_version(): Chris@87: fcompiler.customize_cmd(self) Chris@87: fcompiler.show_customization() Chris@87: else: Chris@87: self.warn('f77_compiler=%s is not available.' % Chris@87: (ctype)) Chris@87: self._f77_compiler = None Chris@87: else: Chris@87: self._f77_compiler = None Chris@87: Chris@87: # Initialize Fortran 90 compiler: Chris@87: if need_f90_compiler: Chris@87: ctype = self.fcompiler Chris@87: self._f90_compiler = new_fcompiler(compiler=self.fcompiler, Chris@87: verbose=self.verbose, Chris@87: dry_run=self.dry_run, Chris@87: force=self.force, Chris@87: requiref90=True, Chris@87: c_compiler = self.compiler) Chris@87: fcompiler = self._f90_compiler Chris@87: if fcompiler: Chris@87: ctype = fcompiler.compiler_type Chris@87: fcompiler.customize(self.distribution) Chris@87: if fcompiler and fcompiler.get_version(): Chris@87: fcompiler.customize_cmd(self) Chris@87: fcompiler.show_customization() Chris@87: else: Chris@87: self.warn('f90_compiler=%s is not available.' % Chris@87: (ctype)) Chris@87: self._f90_compiler = None Chris@87: else: Chris@87: self._f90_compiler = None Chris@87: Chris@87: # Build extensions Chris@87: self.build_extensions() Chris@87: Chris@87: Chris@87: def swig_sources(self, sources): Chris@87: # Do nothing. Swig sources have beed handled in build_src command. Chris@87: return sources Chris@87: Chris@87: def build_extension(self, ext): Chris@87: sources = ext.sources Chris@87: if sources is None or not is_sequence(sources): Chris@87: raise DistutilsSetupError( Chris@87: ("in 'ext_modules' option (extension '%s'), " + Chris@87: "'sources' must be present and must be " + Chris@87: "a list of source filenames") % ext.name) Chris@87: sources = list(sources) Chris@87: Chris@87: if not sources: Chris@87: return Chris@87: Chris@87: fullname = self.get_ext_fullname(ext.name) Chris@87: if self.inplace: Chris@87: modpath = fullname.split('.') Chris@87: package = '.'.join(modpath[0:-1]) Chris@87: base = modpath[-1] Chris@87: build_py = self.get_finalized_command('build_py') Chris@87: package_dir = build_py.get_package_dir(package) Chris@87: ext_filename = os.path.join(package_dir, Chris@87: self.get_ext_filename(base)) Chris@87: else: Chris@87: ext_filename = os.path.join(self.build_lib, Chris@87: self.get_ext_filename(fullname)) Chris@87: depends = sources + ext.depends Chris@87: Chris@87: if not (self.force or newer_group(depends, ext_filename, 'newer')): Chris@87: log.debug("skipping '%s' extension (up-to-date)", ext.name) Chris@87: return Chris@87: else: Chris@87: log.info("building '%s' extension", ext.name) Chris@87: Chris@87: extra_args = ext.extra_compile_args or [] Chris@87: macros = ext.define_macros[:] Chris@87: for undef in ext.undef_macros: Chris@87: macros.append((undef,)) Chris@87: Chris@87: c_sources, cxx_sources, f_sources, fmodule_sources = \ Chris@87: filter_sources(ext.sources) Chris@87: Chris@87: Chris@87: Chris@87: if self.compiler.compiler_type=='msvc': Chris@87: if cxx_sources: Chris@87: # Needed to compile kiva.agg._agg extension. Chris@87: extra_args.append('/Zm1000') Chris@87: # this hack works around the msvc compiler attributes Chris@87: # problem, msvc uses its own convention :( Chris@87: c_sources += cxx_sources Chris@87: cxx_sources = [] Chris@87: Chris@87: # Set Fortran/C++ compilers for compilation and linking. Chris@87: if ext.language=='f90': Chris@87: fcompiler = self._f90_compiler Chris@87: elif ext.language=='f77': Chris@87: fcompiler = self._f77_compiler Chris@87: else: # in case ext.language is c++, for instance Chris@87: fcompiler = self._f90_compiler or self._f77_compiler Chris@87: if fcompiler is not None: Chris@87: fcompiler.extra_f77_compile_args = (ext.extra_f77_compile_args or []) if hasattr(ext, 'extra_f77_compile_args') else [] Chris@87: fcompiler.extra_f90_compile_args = (ext.extra_f90_compile_args or []) if hasattr(ext, 'extra_f90_compile_args') else [] Chris@87: cxx_compiler = self._cxx_compiler Chris@87: Chris@87: # check for the availability of required compilers Chris@87: if cxx_sources and cxx_compiler is None: Chris@87: raise DistutilsError("extension %r has C++ sources" \ Chris@87: "but no C++ compiler found" % (ext.name)) Chris@87: if (f_sources or fmodule_sources) and fcompiler is None: Chris@87: raise DistutilsError("extension %r has Fortran sources " \ Chris@87: "but no Fortran compiler found" % (ext.name)) Chris@87: if ext.language in ['f77', 'f90'] and fcompiler is None: Chris@87: self.warn("extension %r has Fortran libraries " \ Chris@87: "but no Fortran linker found, using default linker" % (ext.name)) Chris@87: if ext.language=='c++' and cxx_compiler is None: Chris@87: self.warn("extension %r has C++ libraries " \ Chris@87: "but no C++ linker found, using default linker" % (ext.name)) Chris@87: Chris@87: kws = {'depends':ext.depends} Chris@87: output_dir = self.build_temp Chris@87: Chris@87: include_dirs = ext.include_dirs + get_numpy_include_dirs() Chris@87: Chris@87: c_objects = [] Chris@87: if c_sources: Chris@87: log.info("compiling C sources") Chris@87: c_objects = self.compiler.compile(c_sources, Chris@87: output_dir=output_dir, Chris@87: macros=macros, Chris@87: include_dirs=include_dirs, Chris@87: debug=self.debug, Chris@87: extra_postargs=extra_args, Chris@87: **kws) Chris@87: Chris@87: if cxx_sources: Chris@87: log.info("compiling C++ sources") Chris@87: c_objects += cxx_compiler.compile(cxx_sources, Chris@87: output_dir=output_dir, Chris@87: macros=macros, Chris@87: include_dirs=include_dirs, Chris@87: debug=self.debug, Chris@87: extra_postargs=extra_args, Chris@87: **kws) Chris@87: Chris@87: extra_postargs = [] Chris@87: f_objects = [] Chris@87: if fmodule_sources: Chris@87: log.info("compiling Fortran 90 module sources") Chris@87: module_dirs = ext.module_dirs[:] Chris@87: module_build_dir = os.path.join( Chris@87: self.build_temp, os.path.dirname( Chris@87: self.get_ext_filename(fullname))) Chris@87: Chris@87: self.mkpath(module_build_dir) Chris@87: if fcompiler.module_dir_switch is None: Chris@87: existing_modules = glob('*.mod') Chris@87: extra_postargs += fcompiler.module_options( Chris@87: module_dirs, module_build_dir) Chris@87: f_objects += fcompiler.compile(fmodule_sources, Chris@87: output_dir=self.build_temp, Chris@87: macros=macros, Chris@87: include_dirs=include_dirs, Chris@87: debug=self.debug, Chris@87: extra_postargs=extra_postargs, Chris@87: depends=ext.depends) Chris@87: Chris@87: if fcompiler.module_dir_switch is None: Chris@87: for f in glob('*.mod'): Chris@87: if f in existing_modules: Chris@87: continue Chris@87: t = os.path.join(module_build_dir, f) Chris@87: if os.path.abspath(f)==os.path.abspath(t): Chris@87: continue Chris@87: if os.path.isfile(t): Chris@87: os.remove(t) Chris@87: try: Chris@87: self.move_file(f, module_build_dir) Chris@87: except DistutilsFileError: Chris@87: log.warn('failed to move %r to %r' % Chris@87: (f, module_build_dir)) Chris@87: if f_sources: Chris@87: log.info("compiling Fortran sources") Chris@87: f_objects += fcompiler.compile(f_sources, Chris@87: output_dir=self.build_temp, Chris@87: macros=macros, Chris@87: include_dirs=include_dirs, Chris@87: debug=self.debug, Chris@87: extra_postargs=extra_postargs, Chris@87: depends=ext.depends) Chris@87: Chris@87: objects = c_objects + f_objects Chris@87: Chris@87: if ext.extra_objects: Chris@87: objects.extend(ext.extra_objects) Chris@87: extra_args = ext.extra_link_args or [] Chris@87: libraries = self.get_libraries(ext)[:] Chris@87: library_dirs = ext.library_dirs[:] Chris@87: Chris@87: linker = self.compiler.link_shared_object Chris@87: # Always use system linker when using MSVC compiler. Chris@87: if self.compiler.compiler_type=='msvc': Chris@87: # expand libraries with fcompiler libraries as we are Chris@87: # not using fcompiler linker Chris@87: self._libs_with_msvc_and_fortran(fcompiler, libraries, library_dirs) Chris@87: Chris@87: elif ext.language in ['f77', 'f90'] and fcompiler is not None: Chris@87: linker = fcompiler.link_shared_object Chris@87: if ext.language=='c++' and cxx_compiler is not None: Chris@87: linker = cxx_compiler.link_shared_object Chris@87: Chris@87: if sys.version[:3]>='2.3': Chris@87: kws = {'target_lang':ext.language} Chris@87: else: Chris@87: kws = {} Chris@87: Chris@87: linker(objects, ext_filename, Chris@87: libraries=libraries, Chris@87: library_dirs=library_dirs, Chris@87: runtime_library_dirs=ext.runtime_library_dirs, Chris@87: extra_postargs=extra_args, Chris@87: export_symbols=self.get_export_symbols(ext), Chris@87: debug=self.debug, Chris@87: build_temp=self.build_temp,**kws) Chris@87: Chris@87: def _add_dummy_mingwex_sym(self, c_sources): Chris@87: build_src = self.get_finalized_command("build_src").build_src Chris@87: build_clib = self.get_finalized_command("build_clib").build_clib Chris@87: objects = self.compiler.compile([os.path.join(build_src, Chris@87: "gfortran_vs2003_hack.c")], Chris@87: output_dir=self.build_temp) Chris@87: self.compiler.create_static_lib(objects, "_gfortran_workaround", output_dir=build_clib, debug=self.debug) Chris@87: Chris@87: def _libs_with_msvc_and_fortran(self, fcompiler, c_libraries, Chris@87: c_library_dirs): Chris@87: if fcompiler is None: return Chris@87: Chris@87: for libname in c_libraries: Chris@87: if libname.startswith('msvc'): continue Chris@87: fileexists = False Chris@87: for libdir in c_library_dirs or []: Chris@87: libfile = os.path.join(libdir, '%s.lib' % (libname)) Chris@87: if os.path.isfile(libfile): Chris@87: fileexists = True Chris@87: break Chris@87: if fileexists: continue Chris@87: # make g77-compiled static libs available to MSVC Chris@87: fileexists = False Chris@87: for libdir in c_library_dirs: Chris@87: libfile = os.path.join(libdir, 'lib%s.a' % (libname)) Chris@87: if os.path.isfile(libfile): Chris@87: # copy libname.a file to name.lib so that MSVC linker Chris@87: # can find it Chris@87: libfile2 = os.path.join(self.build_temp, libname + '.lib') Chris@87: copy_file(libfile, libfile2) Chris@87: if self.build_temp not in c_library_dirs: Chris@87: c_library_dirs.append(self.build_temp) Chris@87: fileexists = True Chris@87: break Chris@87: if fileexists: continue Chris@87: log.warn('could not find library %r in directories %s' Chris@87: % (libname, c_library_dirs)) Chris@87: Chris@87: # Always use system linker when using MSVC compiler. Chris@87: f_lib_dirs = [] Chris@87: for dir in fcompiler.library_dirs: Chris@87: # correct path when compiling in Cygwin but with normal Win Chris@87: # Python Chris@87: if dir.startswith('/usr/lib'): Chris@87: s, o = exec_command(['cygpath', '-w', dir], use_tee=False) Chris@87: if not s: Chris@87: dir = o Chris@87: f_lib_dirs.append(dir) Chris@87: c_library_dirs.extend(f_lib_dirs) Chris@87: Chris@87: # make g77-compiled static libs available to MSVC Chris@87: for lib in fcompiler.libraries: Chris@87: if not lib.startswith('msvc'): Chris@87: c_libraries.append(lib) Chris@87: p = combine_paths(f_lib_dirs, 'lib' + lib + '.a') Chris@87: if p: Chris@87: dst_name = os.path.join(self.build_temp, lib + '.lib') Chris@87: if not os.path.isfile(dst_name): Chris@87: copy_file(p[0], dst_name) Chris@87: if self.build_temp not in c_library_dirs: Chris@87: c_library_dirs.append(self.build_temp) Chris@87: Chris@87: def get_source_files (self): Chris@87: self.check_extensions_list(self.extensions) Chris@87: filenames = [] Chris@87: for ext in self.extensions: Chris@87: filenames.extend(get_ext_source_files(ext)) Chris@87: return filenames Chris@87: Chris@87: def get_outputs (self): Chris@87: self.check_extensions_list(self.extensions) Chris@87: Chris@87: outputs = [] Chris@87: for ext in self.extensions: Chris@87: if not ext.sources: Chris@87: continue Chris@87: fullname = self.get_ext_fullname(ext.name) Chris@87: outputs.append(os.path.join(self.build_lib, Chris@87: self.get_ext_filename(fullname))) Chris@87: return outputs