773 lines
30 KiB
Python
773 lines
30 KiB
Python
""" Build swig and f2py sources.
|
|
"""
|
|
import os
|
|
import re
|
|
import sys
|
|
import shlex
|
|
import copy
|
|
|
|
from distutils.command import build_ext
|
|
from distutils.dep_util import newer_group, newer
|
|
from distutils.util import get_platform
|
|
from distutils.errors import DistutilsError, DistutilsSetupError
|
|
|
|
|
|
# this import can't be done here, as it uses numpy stuff only available
|
|
# after it's installed
|
|
#import numpy.f2py
|
|
from numpy.distutils import log
|
|
from numpy.distutils.misc_util import (
|
|
fortran_ext_match, appendpath, is_string, is_sequence, get_cmd
|
|
)
|
|
from numpy.distutils.from_template import process_file as process_f_file
|
|
from numpy.distutils.conv_template import process_file as process_c_file
|
|
|
|
def subst_vars(target, source, d):
|
|
"""Substitute any occurrence of @foo@ by d['foo'] from source file into
|
|
target."""
|
|
var = re.compile('@([a-zA-Z_]+)@')
|
|
with open(source, 'r') as fs:
|
|
with open(target, 'w') as ft:
|
|
for l in fs:
|
|
m = var.search(l)
|
|
if m:
|
|
ft.write(l.replace('@%s@' % m.group(1), d[m.group(1)]))
|
|
else:
|
|
ft.write(l)
|
|
|
|
class build_src(build_ext.build_ext):
|
|
|
|
description = "build sources from SWIG, F2PY files or a function"
|
|
|
|
user_options = [
|
|
('build-src=', 'd', "directory to \"build\" sources to"),
|
|
('f2py-opts=', None, "list of f2py command line options"),
|
|
('swig=', None, "path to the SWIG executable"),
|
|
('swig-opts=', None, "list of SWIG command line options"),
|
|
('swig-cpp', None, "make SWIG create C++ files (default is autodetected from sources)"),
|
|
('f2pyflags=', None, "additional flags to f2py (use --f2py-opts= instead)"), # obsolete
|
|
('swigflags=', None, "additional flags to swig (use --swig-opts= instead)"), # obsolete
|
|
('force', 'f', "forcibly build everything (ignore file timestamps)"),
|
|
('inplace', 'i',
|
|
"ignore build-lib and put compiled extensions into the source " +
|
|
"directory alongside your pure Python modules"),
|
|
('verbose-cfg', None,
|
|
"change logging level from WARN to INFO which will show all " +
|
|
"compiler output")
|
|
]
|
|
|
|
boolean_options = ['force', 'inplace', 'verbose-cfg']
|
|
|
|
help_options = []
|
|
|
|
def initialize_options(self):
|
|
self.extensions = None
|
|
self.package = None
|
|
self.py_modules = None
|
|
self.py_modules_dict = None
|
|
self.build_src = None
|
|
self.build_lib = None
|
|
self.build_base = None
|
|
self.force = None
|
|
self.inplace = None
|
|
self.package_dir = None
|
|
self.f2pyflags = None # obsolete
|
|
self.f2py_opts = None
|
|
self.swigflags = None # obsolete
|
|
self.swig_opts = None
|
|
self.swig_cpp = None
|
|
self.swig = None
|
|
self.verbose_cfg = None
|
|
|
|
def finalize_options(self):
|
|
self.set_undefined_options('build',
|
|
('build_base', 'build_base'),
|
|
('build_lib', 'build_lib'),
|
|
('force', 'force'))
|
|
if self.package is None:
|
|
self.package = self.distribution.ext_package
|
|
self.extensions = self.distribution.ext_modules
|
|
self.libraries = self.distribution.libraries or []
|
|
self.py_modules = self.distribution.py_modules or []
|
|
self.data_files = self.distribution.data_files or []
|
|
|
|
if self.build_src is None:
|
|
plat_specifier = ".{}-{}.{}".format(get_platform(), *sys.version_info[:2])
|
|
self.build_src = os.path.join(self.build_base, 'src'+plat_specifier)
|
|
|
|
# py_modules_dict is used in build_py.find_package_modules
|
|
self.py_modules_dict = {}
|
|
|
|
if self.f2pyflags:
|
|
if self.f2py_opts:
|
|
log.warn('ignoring --f2pyflags as --f2py-opts already used')
|
|
else:
|
|
self.f2py_opts = self.f2pyflags
|
|
self.f2pyflags = None
|
|
if self.f2py_opts is None:
|
|
self.f2py_opts = []
|
|
else:
|
|
self.f2py_opts = shlex.split(self.f2py_opts)
|
|
|
|
if self.swigflags:
|
|
if self.swig_opts:
|
|
log.warn('ignoring --swigflags as --swig-opts already used')
|
|
else:
|
|
self.swig_opts = self.swigflags
|
|
self.swigflags = None
|
|
|
|
if self.swig_opts is None:
|
|
self.swig_opts = []
|
|
else:
|
|
self.swig_opts = shlex.split(self.swig_opts)
|
|
|
|
# use options from build_ext command
|
|
build_ext = self.get_finalized_command('build_ext')
|
|
if self.inplace is None:
|
|
self.inplace = build_ext.inplace
|
|
if self.swig_cpp is None:
|
|
self.swig_cpp = build_ext.swig_cpp
|
|
for c in ['swig', 'swig_opt']:
|
|
o = '--'+c.replace('_', '-')
|
|
v = getattr(build_ext, c, None)
|
|
if v:
|
|
if getattr(self, c):
|
|
log.warn('both build_src and build_ext define %s option' % (o))
|
|
else:
|
|
log.info('using "%s=%s" option from build_ext command' % (o, v))
|
|
setattr(self, c, v)
|
|
|
|
def run(self):
|
|
log.info("build_src")
|
|
if not (self.extensions or self.libraries):
|
|
return
|
|
self.build_sources()
|
|
|
|
def build_sources(self):
|
|
|
|
if self.inplace:
|
|
self.get_package_dir = \
|
|
self.get_finalized_command('build_py').get_package_dir
|
|
|
|
self.build_py_modules_sources()
|
|
|
|
for libname_info in self.libraries:
|
|
self.build_library_sources(*libname_info)
|
|
|
|
if self.extensions:
|
|
self.check_extensions_list(self.extensions)
|
|
|
|
for ext in self.extensions:
|
|
self.build_extension_sources(ext)
|
|
|
|
self.build_data_files_sources()
|
|
self.build_npy_pkg_config()
|
|
|
|
def build_data_files_sources(self):
|
|
if not self.data_files:
|
|
return
|
|
log.info('building data_files sources')
|
|
from numpy.distutils.misc_util import get_data_files
|
|
new_data_files = []
|
|
for data in self.data_files:
|
|
if isinstance(data, str):
|
|
new_data_files.append(data)
|
|
elif isinstance(data, tuple):
|
|
d, files = data
|
|
if self.inplace:
|
|
build_dir = self.get_package_dir('.'.join(d.split(os.sep)))
|
|
else:
|
|
build_dir = os.path.join(self.build_src, d)
|
|
funcs = [f for f in files if hasattr(f, '__call__')]
|
|
files = [f for f in files if not hasattr(f, '__call__')]
|
|
for f in funcs:
|
|
if f.__code__.co_argcount==1:
|
|
s = f(build_dir)
|
|
else:
|
|
s = f()
|
|
if s is not None:
|
|
if isinstance(s, list):
|
|
files.extend(s)
|
|
elif isinstance(s, str):
|
|
files.append(s)
|
|
else:
|
|
raise TypeError(repr(s))
|
|
filenames = get_data_files((d, files))
|
|
new_data_files.append((d, filenames))
|
|
else:
|
|
raise TypeError(repr(data))
|
|
self.data_files[:] = new_data_files
|
|
|
|
|
|
def _build_npy_pkg_config(self, info, gd):
|
|
template, install_dir, subst_dict = info
|
|
template_dir = os.path.dirname(template)
|
|
for k, v in gd.items():
|
|
subst_dict[k] = v
|
|
|
|
if self.inplace == 1:
|
|
generated_dir = os.path.join(template_dir, install_dir)
|
|
else:
|
|
generated_dir = os.path.join(self.build_src, template_dir,
|
|
install_dir)
|
|
generated = os.path.basename(os.path.splitext(template)[0])
|
|
generated_path = os.path.join(generated_dir, generated)
|
|
if not os.path.exists(generated_dir):
|
|
os.makedirs(generated_dir)
|
|
|
|
subst_vars(generated_path, template, subst_dict)
|
|
|
|
# Where to install relatively to install prefix
|
|
full_install_dir = os.path.join(template_dir, install_dir)
|
|
return full_install_dir, generated_path
|
|
|
|
def build_npy_pkg_config(self):
|
|
log.info('build_src: building npy-pkg config files')
|
|
|
|
# XXX: another ugly workaround to circumvent distutils brain damage. We
|
|
# need the install prefix here, but finalizing the options of the
|
|
# install command when only building sources cause error. Instead, we
|
|
# copy the install command instance, and finalize the copy so that it
|
|
# does not disrupt how distutils want to do things when with the
|
|
# original install command instance.
|
|
install_cmd = copy.copy(get_cmd('install'))
|
|
if not install_cmd.finalized == 1:
|
|
install_cmd.finalize_options()
|
|
build_npkg = False
|
|
if self.inplace == 1:
|
|
top_prefix = '.'
|
|
build_npkg = True
|
|
elif hasattr(install_cmd, 'install_libbase'):
|
|
top_prefix = install_cmd.install_libbase
|
|
build_npkg = True
|
|
|
|
if build_npkg:
|
|
for pkg, infos in self.distribution.installed_pkg_config.items():
|
|
pkg_path = self.distribution.package_dir[pkg]
|
|
prefix = os.path.join(os.path.abspath(top_prefix), pkg_path)
|
|
d = {'prefix': prefix}
|
|
for info in infos:
|
|
install_dir, generated = self._build_npy_pkg_config(info, d)
|
|
self.distribution.data_files.append((install_dir,
|
|
[generated]))
|
|
|
|
def build_py_modules_sources(self):
|
|
if not self.py_modules:
|
|
return
|
|
log.info('building py_modules sources')
|
|
new_py_modules = []
|
|
for source in self.py_modules:
|
|
if is_sequence(source) and len(source)==3:
|
|
package, module_base, source = source
|
|
if self.inplace:
|
|
build_dir = self.get_package_dir(package)
|
|
else:
|
|
build_dir = os.path.join(self.build_src,
|
|
os.path.join(*package.split('.')))
|
|
if hasattr(source, '__call__'):
|
|
target = os.path.join(build_dir, module_base + '.py')
|
|
source = source(target)
|
|
if source is None:
|
|
continue
|
|
modules = [(package, module_base, source)]
|
|
if package not in self.py_modules_dict:
|
|
self.py_modules_dict[package] = []
|
|
self.py_modules_dict[package] += modules
|
|
else:
|
|
new_py_modules.append(source)
|
|
self.py_modules[:] = new_py_modules
|
|
|
|
def build_library_sources(self, lib_name, build_info):
|
|
sources = list(build_info.get('sources', []))
|
|
|
|
if not sources:
|
|
return
|
|
|
|
log.info('building library "%s" sources' % (lib_name))
|
|
|
|
sources = self.generate_sources(sources, (lib_name, build_info))
|
|
|
|
sources = self.template_sources(sources, (lib_name, build_info))
|
|
|
|
sources, h_files = self.filter_h_files(sources)
|
|
|
|
if h_files:
|
|
log.info('%s - nothing done with h_files = %s',
|
|
self.package, h_files)
|
|
|
|
#for f in h_files:
|
|
# self.distribution.headers.append((lib_name,f))
|
|
|
|
build_info['sources'] = sources
|
|
return
|
|
|
|
def build_extension_sources(self, ext):
|
|
|
|
sources = list(ext.sources)
|
|
|
|
log.info('building extension "%s" sources' % (ext.name))
|
|
|
|
fullname = self.get_ext_fullname(ext.name)
|
|
|
|
modpath = fullname.split('.')
|
|
package = '.'.join(modpath[0:-1])
|
|
|
|
if self.inplace:
|
|
self.ext_target_dir = self.get_package_dir(package)
|
|
|
|
sources = self.generate_sources(sources, ext)
|
|
sources = self.template_sources(sources, ext)
|
|
sources = self.swig_sources(sources, ext)
|
|
sources = self.f2py_sources(sources, ext)
|
|
sources = self.pyrex_sources(sources, ext)
|
|
|
|
sources, py_files = self.filter_py_files(sources)
|
|
|
|
if package not in self.py_modules_dict:
|
|
self.py_modules_dict[package] = []
|
|
modules = []
|
|
for f in py_files:
|
|
module = os.path.splitext(os.path.basename(f))[0]
|
|
modules.append((package, module, f))
|
|
self.py_modules_dict[package] += modules
|
|
|
|
sources, h_files = self.filter_h_files(sources)
|
|
|
|
if h_files:
|
|
log.info('%s - nothing done with h_files = %s',
|
|
package, h_files)
|
|
#for f in h_files:
|
|
# self.distribution.headers.append((package,f))
|
|
|
|
ext.sources = sources
|
|
|
|
def generate_sources(self, sources, extension):
|
|
new_sources = []
|
|
func_sources = []
|
|
for source in sources:
|
|
if is_string(source):
|
|
new_sources.append(source)
|
|
else:
|
|
func_sources.append(source)
|
|
if not func_sources:
|
|
return new_sources
|
|
if self.inplace and not is_sequence(extension):
|
|
build_dir = self.ext_target_dir
|
|
else:
|
|
if is_sequence(extension):
|
|
name = extension[0]
|
|
# if 'include_dirs' not in extension[1]:
|
|
# extension[1]['include_dirs'] = []
|
|
# incl_dirs = extension[1]['include_dirs']
|
|
else:
|
|
name = extension.name
|
|
# incl_dirs = extension.include_dirs
|
|
#if self.build_src not in incl_dirs:
|
|
# incl_dirs.append(self.build_src)
|
|
build_dir = os.path.join(*([self.build_src]
|
|
+name.split('.')[:-1]))
|
|
self.mkpath(build_dir)
|
|
|
|
if self.verbose_cfg:
|
|
new_level = log.INFO
|
|
else:
|
|
new_level = log.WARN
|
|
old_level = log.set_threshold(new_level)
|
|
|
|
for func in func_sources:
|
|
source = func(extension, build_dir)
|
|
if not source:
|
|
continue
|
|
if is_sequence(source):
|
|
[log.info(" adding '%s' to sources." % (s,)) for s in source]
|
|
new_sources.extend(source)
|
|
else:
|
|
log.info(" adding '%s' to sources." % (source,))
|
|
new_sources.append(source)
|
|
log.set_threshold(old_level)
|
|
return new_sources
|
|
|
|
def filter_py_files(self, sources):
|
|
return self.filter_files(sources, ['.py'])
|
|
|
|
def filter_h_files(self, sources):
|
|
return self.filter_files(sources, ['.h', '.hpp', '.inc'])
|
|
|
|
def filter_files(self, sources, exts = []):
|
|
new_sources = []
|
|
files = []
|
|
for source in sources:
|
|
(base, ext) = os.path.splitext(source)
|
|
if ext in exts:
|
|
files.append(source)
|
|
else:
|
|
new_sources.append(source)
|
|
return new_sources, files
|
|
|
|
def template_sources(self, sources, extension):
|
|
new_sources = []
|
|
if is_sequence(extension):
|
|
depends = extension[1].get('depends')
|
|
include_dirs = extension[1].get('include_dirs')
|
|
else:
|
|
depends = extension.depends
|
|
include_dirs = extension.include_dirs
|
|
for source in sources:
|
|
(base, ext) = os.path.splitext(source)
|
|
if ext == '.src': # Template file
|
|
if self.inplace:
|
|
target_dir = os.path.dirname(base)
|
|
else:
|
|
target_dir = appendpath(self.build_src, os.path.dirname(base))
|
|
self.mkpath(target_dir)
|
|
target_file = os.path.join(target_dir, os.path.basename(base))
|
|
if (self.force or newer_group([source] + depends, target_file)):
|
|
if _f_pyf_ext_match(base):
|
|
log.info("from_template:> %s" % (target_file))
|
|
outstr = process_f_file(source)
|
|
else:
|
|
log.info("conv_template:> %s" % (target_file))
|
|
outstr = process_c_file(source)
|
|
with open(target_file, 'w') as fid:
|
|
fid.write(outstr)
|
|
if _header_ext_match(target_file):
|
|
d = os.path.dirname(target_file)
|
|
if d not in include_dirs:
|
|
log.info(" adding '%s' to include_dirs." % (d))
|
|
include_dirs.append(d)
|
|
new_sources.append(target_file)
|
|
else:
|
|
new_sources.append(source)
|
|
return new_sources
|
|
|
|
def pyrex_sources(self, sources, extension):
|
|
"""Pyrex not supported; this remains for Cython support (see below)"""
|
|
new_sources = []
|
|
ext_name = extension.name.split('.')[-1]
|
|
for source in sources:
|
|
(base, ext) = os.path.splitext(source)
|
|
if ext == '.pyx':
|
|
target_file = self.generate_a_pyrex_source(base, ext_name,
|
|
source,
|
|
extension)
|
|
new_sources.append(target_file)
|
|
else:
|
|
new_sources.append(source)
|
|
return new_sources
|
|
|
|
def generate_a_pyrex_source(self, base, ext_name, source, extension):
|
|
"""Pyrex is not supported, but some projects monkeypatch this method.
|
|
|
|
That allows compiling Cython code, see gh-6955.
|
|
This method will remain here for compatibility reasons.
|
|
"""
|
|
return []
|
|
|
|
def f2py_sources(self, sources, extension):
|
|
new_sources = []
|
|
f2py_sources = []
|
|
f_sources = []
|
|
f2py_targets = {}
|
|
target_dirs = []
|
|
ext_name = extension.name.split('.')[-1]
|
|
skip_f2py = 0
|
|
|
|
for source in sources:
|
|
(base, ext) = os.path.splitext(source)
|
|
if ext == '.pyf': # F2PY interface file
|
|
if self.inplace:
|
|
target_dir = os.path.dirname(base)
|
|
else:
|
|
target_dir = appendpath(self.build_src, os.path.dirname(base))
|
|
if os.path.isfile(source):
|
|
name = get_f2py_modulename(source)
|
|
if name != ext_name:
|
|
raise DistutilsSetupError('mismatch of extension names: %s '
|
|
'provides %r but expected %r' % (
|
|
source, name, ext_name))
|
|
target_file = os.path.join(target_dir, name+'module.c')
|
|
else:
|
|
log.debug(' source %s does not exist: skipping f2py\'ing.' \
|
|
% (source))
|
|
name = ext_name
|
|
skip_f2py = 1
|
|
target_file = os.path.join(target_dir, name+'module.c')
|
|
if not os.path.isfile(target_file):
|
|
log.warn(' target %s does not exist:\n '\
|
|
'Assuming %smodule.c was generated with '\
|
|
'"build_src --inplace" command.' \
|
|
% (target_file, name))
|
|
target_dir = os.path.dirname(base)
|
|
target_file = os.path.join(target_dir, name+'module.c')
|
|
if not os.path.isfile(target_file):
|
|
raise DistutilsSetupError("%r missing" % (target_file,))
|
|
log.info(' Yes! Using %r as up-to-date target.' \
|
|
% (target_file))
|
|
target_dirs.append(target_dir)
|
|
f2py_sources.append(source)
|
|
f2py_targets[source] = target_file
|
|
new_sources.append(target_file)
|
|
elif fortran_ext_match(ext):
|
|
f_sources.append(source)
|
|
else:
|
|
new_sources.append(source)
|
|
|
|
if not (f2py_sources or f_sources):
|
|
return new_sources
|
|
|
|
for d in target_dirs:
|
|
self.mkpath(d)
|
|
|
|
f2py_options = extension.f2py_options + self.f2py_opts
|
|
|
|
if self.distribution.libraries:
|
|
for name, build_info in self.distribution.libraries:
|
|
if name in extension.libraries:
|
|
f2py_options.extend(build_info.get('f2py_options', []))
|
|
|
|
log.info("f2py options: %s" % (f2py_options))
|
|
|
|
if f2py_sources:
|
|
if len(f2py_sources) != 1:
|
|
raise DistutilsSetupError(
|
|
'only one .pyf file is allowed per extension module but got'\
|
|
' more: %r' % (f2py_sources,))
|
|
source = f2py_sources[0]
|
|
target_file = f2py_targets[source]
|
|
target_dir = os.path.dirname(target_file) or '.'
|
|
depends = [source] + extension.depends
|
|
if (self.force or newer_group(depends, target_file, 'newer')) \
|
|
and not skip_f2py:
|
|
log.info("f2py: %s" % (source))
|
|
import numpy.f2py
|
|
numpy.f2py.run_main(f2py_options
|
|
+ ['--build-dir', target_dir, source])
|
|
else:
|
|
log.debug(" skipping '%s' f2py interface (up-to-date)" % (source))
|
|
else:
|
|
#XXX TODO: --inplace support for sdist command
|
|
if is_sequence(extension):
|
|
name = extension[0]
|
|
else: name = extension.name
|
|
target_dir = os.path.join(*([self.build_src]
|
|
+name.split('.')[:-1]))
|
|
target_file = os.path.join(target_dir, ext_name + 'module.c')
|
|
new_sources.append(target_file)
|
|
depends = f_sources + extension.depends
|
|
if (self.force or newer_group(depends, target_file, 'newer')) \
|
|
and not skip_f2py:
|
|
log.info("f2py:> %s" % (target_file))
|
|
self.mkpath(target_dir)
|
|
import numpy.f2py
|
|
numpy.f2py.run_main(f2py_options + ['--lower',
|
|
'--build-dir', target_dir]+\
|
|
['-m', ext_name]+f_sources)
|
|
else:
|
|
log.debug(" skipping f2py fortran files for '%s' (up-to-date)"\
|
|
% (target_file))
|
|
|
|
if not os.path.isfile(target_file):
|
|
raise DistutilsError("f2py target file %r not generated" % (target_file,))
|
|
|
|
build_dir = os.path.join(self.build_src, target_dir)
|
|
target_c = os.path.join(build_dir, 'fortranobject.c')
|
|
target_h = os.path.join(build_dir, 'fortranobject.h')
|
|
log.info(" adding '%s' to sources." % (target_c))
|
|
new_sources.append(target_c)
|
|
if build_dir not in extension.include_dirs:
|
|
log.info(" adding '%s' to include_dirs." % (build_dir))
|
|
extension.include_dirs.append(build_dir)
|
|
|
|
if not skip_f2py:
|
|
import numpy.f2py
|
|
d = os.path.dirname(numpy.f2py.__file__)
|
|
source_c = os.path.join(d, 'src', 'fortranobject.c')
|
|
source_h = os.path.join(d, 'src', 'fortranobject.h')
|
|
if newer(source_c, target_c) or newer(source_h, target_h):
|
|
self.mkpath(os.path.dirname(target_c))
|
|
self.copy_file(source_c, target_c)
|
|
self.copy_file(source_h, target_h)
|
|
else:
|
|
if not os.path.isfile(target_c):
|
|
raise DistutilsSetupError("f2py target_c file %r not found" % (target_c,))
|
|
if not os.path.isfile(target_h):
|
|
raise DistutilsSetupError("f2py target_h file %r not found" % (target_h,))
|
|
|
|
for name_ext in ['-f2pywrappers.f', '-f2pywrappers2.f90']:
|
|
filename = os.path.join(target_dir, ext_name + name_ext)
|
|
if os.path.isfile(filename):
|
|
log.info(" adding '%s' to sources." % (filename))
|
|
f_sources.append(filename)
|
|
|
|
return new_sources + f_sources
|
|
|
|
def swig_sources(self, sources, extension):
|
|
# Assuming SWIG 1.3.14 or later. See compatibility note in
|
|
# http://www.swig.org/Doc1.3/Python.html#Python_nn6
|
|
|
|
new_sources = []
|
|
swig_sources = []
|
|
swig_targets = {}
|
|
target_dirs = []
|
|
py_files = [] # swig generated .py files
|
|
target_ext = '.c'
|
|
if '-c++' in extension.swig_opts:
|
|
typ = 'c++'
|
|
is_cpp = True
|
|
extension.swig_opts.remove('-c++')
|
|
elif self.swig_cpp:
|
|
typ = 'c++'
|
|
is_cpp = True
|
|
else:
|
|
typ = None
|
|
is_cpp = False
|
|
skip_swig = 0
|
|
ext_name = extension.name.split('.')[-1]
|
|
|
|
for source in sources:
|
|
(base, ext) = os.path.splitext(source)
|
|
if ext == '.i': # SWIG interface file
|
|
# the code below assumes that the sources list
|
|
# contains not more than one .i SWIG interface file
|
|
if self.inplace:
|
|
target_dir = os.path.dirname(base)
|
|
py_target_dir = self.ext_target_dir
|
|
else:
|
|
target_dir = appendpath(self.build_src, os.path.dirname(base))
|
|
py_target_dir = target_dir
|
|
if os.path.isfile(source):
|
|
name = get_swig_modulename(source)
|
|
if name != ext_name[1:]:
|
|
raise DistutilsSetupError(
|
|
'mismatch of extension names: %s provides %r'
|
|
' but expected %r' % (source, name, ext_name[1:]))
|
|
if typ is None:
|
|
typ = get_swig_target(source)
|
|
is_cpp = typ=='c++'
|
|
else:
|
|
typ2 = get_swig_target(source)
|
|
if typ2 is None:
|
|
log.warn('source %r does not define swig target, assuming %s swig target' \
|
|
% (source, typ))
|
|
elif typ!=typ2:
|
|
log.warn('expected %r but source %r defines %r swig target' \
|
|
% (typ, source, typ2))
|
|
if typ2=='c++':
|
|
log.warn('resetting swig target to c++ (some targets may have .c extension)')
|
|
is_cpp = True
|
|
else:
|
|
log.warn('assuming that %r has c++ swig target' % (source))
|
|
if is_cpp:
|
|
target_ext = '.cpp'
|
|
target_file = os.path.join(target_dir, '%s_wrap%s' \
|
|
% (name, target_ext))
|
|
else:
|
|
log.warn(' source %s does not exist: skipping swig\'ing.' \
|
|
% (source))
|
|
name = ext_name[1:]
|
|
skip_swig = 1
|
|
target_file = _find_swig_target(target_dir, name)
|
|
if not os.path.isfile(target_file):
|
|
log.warn(' target %s does not exist:\n '\
|
|
'Assuming %s_wrap.{c,cpp} was generated with '\
|
|
'"build_src --inplace" command.' \
|
|
% (target_file, name))
|
|
target_dir = os.path.dirname(base)
|
|
target_file = _find_swig_target(target_dir, name)
|
|
if not os.path.isfile(target_file):
|
|
raise DistutilsSetupError("%r missing" % (target_file,))
|
|
log.warn(' Yes! Using %r as up-to-date target.' \
|
|
% (target_file))
|
|
target_dirs.append(target_dir)
|
|
new_sources.append(target_file)
|
|
py_files.append(os.path.join(py_target_dir, name+'.py'))
|
|
swig_sources.append(source)
|
|
swig_targets[source] = new_sources[-1]
|
|
else:
|
|
new_sources.append(source)
|
|
|
|
if not swig_sources:
|
|
return new_sources
|
|
|
|
if skip_swig:
|
|
return new_sources + py_files
|
|
|
|
for d in target_dirs:
|
|
self.mkpath(d)
|
|
|
|
swig = self.swig or self.find_swig()
|
|
swig_cmd = [swig, "-python"] + extension.swig_opts
|
|
if is_cpp:
|
|
swig_cmd.append('-c++')
|
|
for d in extension.include_dirs:
|
|
swig_cmd.append('-I'+d)
|
|
for source in swig_sources:
|
|
target = swig_targets[source]
|
|
depends = [source] + extension.depends
|
|
if self.force or newer_group(depends, target, 'newer'):
|
|
log.info("%s: %s" % (os.path.basename(swig) \
|
|
+ (is_cpp and '++' or ''), source))
|
|
self.spawn(swig_cmd + self.swig_opts \
|
|
+ ["-o", target, '-outdir', py_target_dir, source])
|
|
else:
|
|
log.debug(" skipping '%s' swig interface (up-to-date)" \
|
|
% (source))
|
|
|
|
return new_sources + py_files
|
|
|
|
_f_pyf_ext_match = re.compile(r'.*[.](f90|f95|f77|for|ftn|f|pyf)\Z', re.I).match
|
|
_header_ext_match = re.compile(r'.*[.](inc|h|hpp)\Z', re.I).match
|
|
|
|
#### SWIG related auxiliary functions ####
|
|
_swig_module_name_match = re.compile(r'\s*%module\s*(.*\(\s*package\s*=\s*"(?P<package>[\w_]+)".*\)|)\s*(?P<name>[\w_]+)',
|
|
re.I).match
|
|
_has_c_header = re.compile(r'-[*]-\s*c\s*-[*]-', re.I).search
|
|
_has_cpp_header = re.compile(r'-[*]-\s*c[+][+]\s*-[*]-', re.I).search
|
|
|
|
def get_swig_target(source):
|
|
with open(source, 'r') as f:
|
|
result = None
|
|
line = f.readline()
|
|
if _has_cpp_header(line):
|
|
result = 'c++'
|
|
if _has_c_header(line):
|
|
result = 'c'
|
|
return result
|
|
|
|
def get_swig_modulename(source):
|
|
with open(source, 'r') as f:
|
|
name = None
|
|
for line in f:
|
|
m = _swig_module_name_match(line)
|
|
if m:
|
|
name = m.group('name')
|
|
break
|
|
return name
|
|
|
|
def _find_swig_target(target_dir, name):
|
|
for ext in ['.cpp', '.c']:
|
|
target = os.path.join(target_dir, '%s_wrap%s' % (name, ext))
|
|
if os.path.isfile(target):
|
|
break
|
|
return target
|
|
|
|
#### F2PY related auxiliary functions ####
|
|
|
|
_f2py_module_name_match = re.compile(r'\s*python\s*module\s*(?P<name>[\w_]+)',
|
|
re.I).match
|
|
_f2py_user_module_name_match = re.compile(r'\s*python\s*module\s*(?P<name>[\w_]*?'
|
|
r'__user__[\w_]*)', re.I).match
|
|
|
|
def get_f2py_modulename(source):
|
|
name = None
|
|
with open(source) as f:
|
|
for line in f:
|
|
m = _f2py_module_name_match(line)
|
|
if m:
|
|
if _f2py_user_module_name_match(line): # skip *__user__* names
|
|
continue
|
|
name = m.group('name')
|
|
break
|
|
return name
|
|
|
|
##########################################
|