1""" Build swig and f2py sources.
2"""
3import os
4import re
5import sys
6import shlex
7import copy
8
9from distutils.command import build_ext
10from distutils.dep_util import newer_group, newer
11from distutils.util import get_platform
12from distutils.errors import DistutilsError, DistutilsSetupError
13
14
15# this import can't be done here, as it uses numpy stuff only available
16# after it's installed
17#import numpy.f2py
18from numpy.distutils import log
19from numpy.distutils.misc_util import (
20    fortran_ext_match, appendpath, is_string, is_sequence, get_cmd
21    )
22from numpy.distutils.from_template import process_file as process_f_file
23from numpy.distutils.conv_template import process_file as process_c_file
24
25def subst_vars(target, source, d):
26    """Substitute any occurrence of @foo@ by d['foo'] from source file into
27    target."""
28    var = re.compile('@([a-zA-Z_]+)@')
29    with open(source, 'r') as fs:
30        with open(target, 'w') as ft:
31            for l in fs:
32                m = var.search(l)
33                if m:
34                    ft.write(l.replace('@%s@' % m.group(1), d[m.group(1)]))
35                else:
36                    ft.write(l)
37
38class build_src(build_ext.build_ext):
39
40    description = "build sources from SWIG, F2PY files or a function"
41
42    user_options = [
43        ('build-src=', 'd', "directory to \"build\" sources to"),
44        ('f2py-opts=', None, "list of f2py command line options"),
45        ('swig=', None, "path to the SWIG executable"),
46        ('swig-opts=', None, "list of SWIG command line options"),
47        ('swig-cpp', None, "make SWIG create C++ files (default is autodetected from sources)"),
48        ('f2pyflags=', None, "additional flags to f2py (use --f2py-opts= instead)"), # obsolete
49        ('swigflags=', None, "additional flags to swig (use --swig-opts= instead)"), # obsolete
50        ('force', 'f', "forcibly build everything (ignore file timestamps)"),
51        ('inplace', 'i',
52         "ignore build-lib and put compiled extensions into the source " +
53         "directory alongside your pure Python modules"),
54        ('verbose-cfg', None,
55         "change logging level from WARN to INFO which will show all " +
56         "compiler output")
57        ]
58
59    boolean_options = ['force', 'inplace', 'verbose-cfg']
60
61    help_options = []
62
63    def initialize_options(self):
64        self.extensions = None
65        self.package = None
66        self.py_modules = None
67        self.py_modules_dict = None
68        self.build_src = None
69        self.build_lib = None
70        self.build_base = None
71        self.force = None
72        self.inplace = None
73        self.package_dir = None
74        self.f2pyflags = None # obsolete
75        self.f2py_opts = None
76        self.swigflags = None # obsolete
77        self.swig_opts = None
78        self.swig_cpp = None
79        self.swig = None
80        self.verbose_cfg = None
81
82    def finalize_options(self):
83        self.set_undefined_options('build',
84                                   ('build_base', 'build_base'),
85                                   ('build_lib', 'build_lib'),
86                                   ('force', 'force'))
87        if self.package is None:
88            self.package = self.distribution.ext_package
89        self.extensions = self.distribution.ext_modules
90        self.libraries = self.distribution.libraries or []
91        self.py_modules = self.distribution.py_modules or []
92        self.data_files = self.distribution.data_files or []
93
94        if self.build_src is None:
95            plat_specifier = ".{}-{}.{}".format(get_platform(), *sys.version_info[:2])
96            self.build_src = os.path.join(self.build_base, 'src'+plat_specifier)
97
98        # py_modules_dict is used in build_py.find_package_modules
99        self.py_modules_dict = {}
100
101        if self.f2pyflags:
102            if self.f2py_opts:
103                log.warn('ignoring --f2pyflags as --f2py-opts already used')
104            else:
105                self.f2py_opts = self.f2pyflags
106            self.f2pyflags = None
107        if self.f2py_opts is None:
108            self.f2py_opts = []
109        else:
110            self.f2py_opts = shlex.split(self.f2py_opts)
111
112        if self.swigflags:
113            if self.swig_opts:
114                log.warn('ignoring --swigflags as --swig-opts already used')
115            else:
116                self.swig_opts = self.swigflags
117            self.swigflags = None
118
119        if self.swig_opts is None:
120            self.swig_opts = []
121        else:
122            self.swig_opts = shlex.split(self.swig_opts)
123
124        # use options from build_ext command
125        build_ext = self.get_finalized_command('build_ext')
126        if self.inplace is None:
127            self.inplace = build_ext.inplace
128        if self.swig_cpp is None:
129            self.swig_cpp = build_ext.swig_cpp
130        for c in ['swig', 'swig_opt']:
131            o = '--'+c.replace('_', '-')
132            v = getattr(build_ext, c, None)
133            if v:
134                if getattr(self, c):
135                    log.warn('both build_src and build_ext define %s option' % (o))
136                else:
137                    log.info('using "%s=%s" option from build_ext command' % (o, v))
138                    setattr(self, c, v)
139
140    def run(self):
141        log.info("build_src")
142        if not (self.extensions or self.libraries):
143            return
144        self.build_sources()
145
146    def build_sources(self):
147
148        if self.inplace:
149            self.get_package_dir = \
150                     self.get_finalized_command('build_py').get_package_dir
151
152        self.build_py_modules_sources()
153
154        for libname_info in self.libraries:
155            self.build_library_sources(*libname_info)
156
157        if self.extensions:
158            self.check_extensions_list(self.extensions)
159
160            for ext in self.extensions:
161                self.build_extension_sources(ext)
162
163        self.build_data_files_sources()
164        self.build_npy_pkg_config()
165
166    def build_data_files_sources(self):
167        if not self.data_files:
168            return
169        log.info('building data_files sources')
170        from numpy.distutils.misc_util import get_data_files
171        new_data_files = []
172        for data in self.data_files:
173            if isinstance(data, str):
174                new_data_files.append(data)
175            elif isinstance(data, tuple):
176                d, files = data
177                if self.inplace:
178                    build_dir = self.get_package_dir('.'.join(d.split(os.sep)))
179                else:
180                    build_dir = os.path.join(self.build_src, d)
181                funcs = [f for f in files if hasattr(f, '__call__')]
182                files = [f for f in files if not hasattr(f, '__call__')]
183                for f in funcs:
184                    if f.__code__.co_argcount==1:
185                        s = f(build_dir)
186                    else:
187                        s = f()
188                    if s is not None:
189                        if isinstance(s, list):
190                            files.extend(s)
191                        elif isinstance(s, str):
192                            files.append(s)
193                        else:
194                            raise TypeError(repr(s))
195                filenames = get_data_files((d, files))
196                new_data_files.append((d, filenames))
197            else:
198                raise TypeError(repr(data))
199        self.data_files[:] = new_data_files
200
201
202    def _build_npy_pkg_config(self, info, gd):
203        template, install_dir, subst_dict = info
204        template_dir = os.path.dirname(template)
205        for k, v in gd.items():
206            subst_dict[k] = v
207
208        if self.inplace == 1:
209            generated_dir = os.path.join(template_dir, install_dir)
210        else:
211            generated_dir = os.path.join(self.build_src, template_dir,
212                    install_dir)
213        generated = os.path.basename(os.path.splitext(template)[0])
214        generated_path = os.path.join(generated_dir, generated)
215        if not os.path.exists(generated_dir):
216            os.makedirs(generated_dir)
217
218        subst_vars(generated_path, template, subst_dict)
219
220        # Where to install relatively to install prefix
221        full_install_dir = os.path.join(template_dir, install_dir)
222        return full_install_dir, generated_path
223
224    def build_npy_pkg_config(self):
225        log.info('build_src: building npy-pkg config files')
226
227        # XXX: another ugly workaround to circumvent distutils brain damage. We
228        # need the install prefix here, but finalizing the options of the
229        # install command when only building sources cause error. Instead, we
230        # copy the install command instance, and finalize the copy so that it
231        # does not disrupt how distutils want to do things when with the
232        # original install command instance.
233        install_cmd = copy.copy(get_cmd('install'))
234        if not install_cmd.finalized == 1:
235            install_cmd.finalize_options()
236        build_npkg = False
237        if self.inplace == 1:
238            top_prefix = '.'
239            build_npkg = True
240        elif hasattr(install_cmd, 'install_libbase'):
241            top_prefix = install_cmd.install_libbase
242            build_npkg = True
243
244        if build_npkg:
245            for pkg, infos in self.distribution.installed_pkg_config.items():
246                pkg_path = self.distribution.package_dir[pkg]
247                prefix = os.path.join(os.path.abspath(top_prefix), pkg_path)
248                d = {'prefix': prefix}
249                for info in infos:
250                    install_dir, generated = self._build_npy_pkg_config(info, d)
251                    self.distribution.data_files.append((install_dir,
252                        [generated]))
253
254    def build_py_modules_sources(self):
255        if not self.py_modules:
256            return
257        log.info('building py_modules sources')
258        new_py_modules = []
259        for source in self.py_modules:
260            if is_sequence(source) and len(source)==3:
261                package, module_base, source = source
262                if self.inplace:
263                    build_dir = self.get_package_dir(package)
264                else:
265                    build_dir = os.path.join(self.build_src,
266                                             os.path.join(*package.split('.')))
267                if hasattr(source, '__call__'):
268                    target = os.path.join(build_dir, module_base + '.py')
269                    source = source(target)
270                if source is None:
271                    continue
272                modules = [(package, module_base, source)]
273                if package not in self.py_modules_dict:
274                    self.py_modules_dict[package] = []
275                self.py_modules_dict[package] += modules
276            else:
277                new_py_modules.append(source)
278        self.py_modules[:] = new_py_modules
279
280    def build_library_sources(self, lib_name, build_info):
281        sources = list(build_info.get('sources', []))
282
283        if not sources:
284            return
285
286        log.info('building library "%s" sources' % (lib_name))
287
288        sources = self.generate_sources(sources, (lib_name, build_info))
289
290        sources = self.template_sources(sources, (lib_name, build_info))
291
292        sources, h_files = self.filter_h_files(sources)
293
294        if h_files:
295            log.info('%s - nothing done with h_files = %s',
296                     self.package, h_files)
297
298        #for f in h_files:
299        #    self.distribution.headers.append((lib_name,f))
300
301        build_info['sources'] = sources
302        return
303
304    def build_extension_sources(self, ext):
305
306        sources = list(ext.sources)
307
308        log.info('building extension "%s" sources' % (ext.name))
309
310        fullname = self.get_ext_fullname(ext.name)
311
312        modpath = fullname.split('.')
313        package = '.'.join(modpath[0:-1])
314
315        if self.inplace:
316            self.ext_target_dir = self.get_package_dir(package)
317
318        sources = self.generate_sources(sources, ext)
319        sources = self.template_sources(sources, ext)
320        sources = self.swig_sources(sources, ext)
321        sources = self.f2py_sources(sources, ext)
322        sources = self.pyrex_sources(sources, ext)
323
324        sources, py_files = self.filter_py_files(sources)
325
326        if package not in self.py_modules_dict:
327            self.py_modules_dict[package] = []
328        modules = []
329        for f in py_files:
330            module = os.path.splitext(os.path.basename(f))[0]
331            modules.append((package, module, f))
332        self.py_modules_dict[package] += modules
333
334        sources, h_files = self.filter_h_files(sources)
335
336        if h_files:
337            log.info('%s - nothing done with h_files = %s',
338                     package, h_files)
339        #for f in h_files:
340        #    self.distribution.headers.append((package,f))
341
342        ext.sources = sources
343
344    def generate_sources(self, sources, extension):
345        new_sources = []
346        func_sources = []
347        for source in sources:
348            if is_string(source):
349                new_sources.append(source)
350            else:
351                func_sources.append(source)
352        if not func_sources:
353            return new_sources
354        if self.inplace and not is_sequence(extension):
355            build_dir = self.ext_target_dir
356        else:
357            if is_sequence(extension):
358                name = extension[0]
359            #    if 'include_dirs' not in extension[1]:
360            #        extension[1]['include_dirs'] = []
361            #    incl_dirs = extension[1]['include_dirs']
362            else:
363                name = extension.name
364            #    incl_dirs = extension.include_dirs
365            #if self.build_src not in incl_dirs:
366            #    incl_dirs.append(self.build_src)
367            build_dir = os.path.join(*([self.build_src]
368                                       +name.split('.')[:-1]))
369        self.mkpath(build_dir)
370
371        if self.verbose_cfg:
372            new_level = log.INFO
373        else:
374            new_level = log.WARN
375        old_level = log.set_threshold(new_level)
376
377        for func in func_sources:
378            source = func(extension, build_dir)
379            if not source:
380                continue
381            if is_sequence(source):
382                [log.info("  adding '%s' to sources." % (s,)) for s in source]
383                new_sources.extend(source)
384            else:
385                log.info("  adding '%s' to sources." % (source,))
386                new_sources.append(source)
387        log.set_threshold(old_level)
388        return new_sources
389
390    def filter_py_files(self, sources):
391        return self.filter_files(sources, ['.py'])
392
393    def filter_h_files(self, sources):
394        return self.filter_files(sources, ['.h', '.hpp', '.inc'])
395
396    def filter_files(self, sources, exts = []):
397        new_sources = []
398        files = []
399        for source in sources:
400            (base, ext) = os.path.splitext(source)
401            if ext in exts:
402                files.append(source)
403            else:
404                new_sources.append(source)
405        return new_sources, files
406
407    def template_sources(self, sources, extension):
408        new_sources = []
409        if is_sequence(extension):
410            depends = extension[1].get('depends')
411            include_dirs = extension[1].get('include_dirs')
412        else:
413            depends = extension.depends
414            include_dirs = extension.include_dirs
415        for source in sources:
416            (base, ext) = os.path.splitext(source)
417            if ext == '.src':  # Template file
418                if self.inplace:
419                    target_dir = os.path.dirname(base)
420                else:
421                    target_dir = appendpath(self.build_src, os.path.dirname(base))
422                self.mkpath(target_dir)
423                target_file = os.path.join(target_dir, os.path.basename(base))
424                if (self.force or newer_group([source] + depends, target_file)):
425                    if _f_pyf_ext_match(base):
426                        log.info("from_template:> %s" % (target_file))
427                        outstr = process_f_file(source)
428                    else:
429                        log.info("conv_template:> %s" % (target_file))
430                        outstr = process_c_file(source)
431                    with open(target_file, 'w') as fid:
432                        fid.write(outstr)
433                if _header_ext_match(target_file):
434                    d = os.path.dirname(target_file)
435                    if d not in include_dirs:
436                        log.info("  adding '%s' to include_dirs." % (d))
437                        include_dirs.append(d)
438                new_sources.append(target_file)
439            else:
440                new_sources.append(source)
441        return new_sources
442
443    def pyrex_sources(self, sources, extension):
444        """Pyrex not supported; this remains for Cython support (see below)"""
445        new_sources = []
446        ext_name = extension.name.split('.')[-1]
447        for source in sources:
448            (base, ext) = os.path.splitext(source)
449            if ext == '.pyx':
450                target_file = self.generate_a_pyrex_source(base, ext_name,
451                                                           source,
452                                                           extension)
453                new_sources.append(target_file)
454            else:
455                new_sources.append(source)
456        return new_sources
457
458    def generate_a_pyrex_source(self, base, ext_name, source, extension):
459        """Pyrex is not supported, but some projects monkeypatch this method.
460
461        That allows compiling Cython code, see gh-6955.
462        This method will remain here for compatibility reasons.
463        """
464        return []
465
466    def f2py_sources(self, sources, extension):
467        new_sources = []
468        f2py_sources = []
469        f_sources = []
470        f2py_targets = {}
471        target_dirs = []
472        ext_name = extension.name.split('.')[-1]
473        skip_f2py = 0
474
475        for source in sources:
476            (base, ext) = os.path.splitext(source)
477            if ext == '.pyf': # F2PY interface file
478                if self.inplace:
479                    target_dir = os.path.dirname(base)
480                else:
481                    target_dir = appendpath(self.build_src, os.path.dirname(base))
482                if os.path.isfile(source):
483                    name = get_f2py_modulename(source)
484                    if name != ext_name:
485                        raise DistutilsSetupError('mismatch of extension names: %s '
486                                                  'provides %r but expected %r' % (
487                            source, name, ext_name))
488                    target_file = os.path.join(target_dir, name+'module.c')
489                else:
490                    log.debug('  source %s does not exist: skipping f2py\'ing.' \
491                              % (source))
492                    name = ext_name
493                    skip_f2py = 1
494                    target_file = os.path.join(target_dir, name+'module.c')
495                    if not os.path.isfile(target_file):
496                        log.warn('  target %s does not exist:\n   '\
497                                 'Assuming %smodule.c was generated with '\
498                                 '"build_src --inplace" command.' \
499                                 % (target_file, name))
500                        target_dir = os.path.dirname(base)
501                        target_file = os.path.join(target_dir, name+'module.c')
502                        if not os.path.isfile(target_file):
503                            raise DistutilsSetupError("%r missing" % (target_file,))
504                        log.info('   Yes! Using %r as up-to-date target.' \
505                                 % (target_file))
506                target_dirs.append(target_dir)
507                f2py_sources.append(source)
508                f2py_targets[source] = target_file
509                new_sources.append(target_file)
510            elif fortran_ext_match(ext):
511                f_sources.append(source)
512            else:
513                new_sources.append(source)
514
515        if not (f2py_sources or f_sources):
516            return new_sources
517
518        for d in target_dirs:
519            self.mkpath(d)
520
521        f2py_options = extension.f2py_options + self.f2py_opts
522
523        if self.distribution.libraries:
524            for name, build_info in self.distribution.libraries:
525                if name in extension.libraries:
526                    f2py_options.extend(build_info.get('f2py_options', []))
527
528        log.info("f2py options: %s" % (f2py_options))
529
530        if f2py_sources:
531            if len(f2py_sources) != 1:
532                raise DistutilsSetupError(
533                    'only one .pyf file is allowed per extension module but got'\
534                    ' more: %r' % (f2py_sources,))
535            source = f2py_sources[0]
536            target_file = f2py_targets[source]
537            target_dir = os.path.dirname(target_file) or '.'
538            depends = [source] + extension.depends
539            if (self.force or newer_group(depends, target_file, 'newer')) \
540                   and not skip_f2py:
541                log.info("f2py: %s" % (source))
542                import numpy.f2py
543                numpy.f2py.run_main(f2py_options
544                                    + ['--build-dir', target_dir, source])
545            else:
546                log.debug("  skipping '%s' f2py interface (up-to-date)" % (source))
547        else:
548            #XXX TODO: --inplace support for sdist command
549            if is_sequence(extension):
550                name = extension[0]
551            else: name = extension.name
552            target_dir = os.path.join(*([self.build_src]
553                                        +name.split('.')[:-1]))
554            target_file = os.path.join(target_dir, ext_name + 'module.c')
555            new_sources.append(target_file)
556            depends = f_sources + extension.depends
557            if (self.force or newer_group(depends, target_file, 'newer')) \
558                   and not skip_f2py:
559                log.info("f2py:> %s" % (target_file))
560                self.mkpath(target_dir)
561                import numpy.f2py
562                numpy.f2py.run_main(f2py_options + ['--lower',
563                                                '--build-dir', target_dir]+\
564                                ['-m', ext_name]+f_sources)
565            else:
566                log.debug("  skipping f2py fortran files for '%s' (up-to-date)"\
567                          % (target_file))
568
569        if not os.path.isfile(target_file):
570            raise DistutilsError("f2py target file %r not generated" % (target_file,))
571
572        build_dir = os.path.join(self.build_src, target_dir)
573        target_c = os.path.join(build_dir, 'fortranobject.c')
574        target_h = os.path.join(build_dir, 'fortranobject.h')
575        log.info("  adding '%s' to sources." % (target_c))
576        new_sources.append(target_c)
577        if build_dir not in extension.include_dirs:
578            log.info("  adding '%s' to include_dirs." % (build_dir))
579            extension.include_dirs.append(build_dir)
580
581        if not skip_f2py:
582            import numpy.f2py
583            d = os.path.dirname(numpy.f2py.__file__)
584            source_c = os.path.join(d, 'src', 'fortranobject.c')
585            source_h = os.path.join(d, 'src', 'fortranobject.h')
586            if newer(source_c, target_c) or newer(source_h, target_h):
587                self.mkpath(os.path.dirname(target_c))
588                self.copy_file(source_c, target_c)
589                self.copy_file(source_h, target_h)
590        else:
591            if not os.path.isfile(target_c):
592                raise DistutilsSetupError("f2py target_c file %r not found" % (target_c,))
593            if not os.path.isfile(target_h):
594                raise DistutilsSetupError("f2py target_h file %r not found" % (target_h,))
595
596        for name_ext in ['-f2pywrappers.f', '-f2pywrappers2.f90']:
597            filename = os.path.join(target_dir, ext_name + name_ext)
598            if os.path.isfile(filename):
599                log.info("  adding '%s' to sources." % (filename))
600                f_sources.append(filename)
601
602        return new_sources + f_sources
603
604    def swig_sources(self, sources, extension):
605        # Assuming SWIG 1.3.14 or later. See compatibility note in
606        #   http://www.swig.org/Doc1.3/Python.html#Python_nn6
607
608        new_sources = []
609        swig_sources = []
610        swig_targets = {}
611        target_dirs = []
612        py_files = []     # swig generated .py files
613        target_ext = '.c'
614        if '-c++' in extension.swig_opts:
615            typ = 'c++'
616            is_cpp = True
617            extension.swig_opts.remove('-c++')
618        elif self.swig_cpp:
619            typ = 'c++'
620            is_cpp = True
621        else:
622            typ = None
623            is_cpp = False
624        skip_swig = 0
625        ext_name = extension.name.split('.')[-1]
626
627        for source in sources:
628            (base, ext) = os.path.splitext(source)
629            if ext == '.i': # SWIG interface file
630                # the code below assumes that the sources list
631                # contains not more than one .i SWIG interface file
632                if self.inplace:
633                    target_dir = os.path.dirname(base)
634                    py_target_dir = self.ext_target_dir
635                else:
636                    target_dir = appendpath(self.build_src, os.path.dirname(base))
637                    py_target_dir = target_dir
638                if os.path.isfile(source):
639                    name = get_swig_modulename(source)
640                    if name != ext_name[1:]:
641                        raise DistutilsSetupError(
642                            'mismatch of extension names: %s provides %r'
643                            ' but expected %r' % (source, name, ext_name[1:]))
644                    if typ is None:
645                        typ = get_swig_target(source)
646                        is_cpp = typ=='c++'
647                    else:
648                        typ2 = get_swig_target(source)
649                        if typ2 is None:
650                            log.warn('source %r does not define swig target, assuming %s swig target' \
651                                     % (source, typ))
652                        elif typ!=typ2:
653                            log.warn('expected %r but source %r defines %r swig target' \
654                                     % (typ, source, typ2))
655                            if typ2=='c++':
656                                log.warn('resetting swig target to c++ (some targets may have .c extension)')
657                                is_cpp = True
658                            else:
659                                log.warn('assuming that %r has c++ swig target' % (source))
660                    if is_cpp:
661                        target_ext = '.cpp'
662                    target_file = os.path.join(target_dir, '%s_wrap%s' \
663                                               % (name, target_ext))
664                else:
665                    log.warn('  source %s does not exist: skipping swig\'ing.' \
666                             % (source))
667                    name = ext_name[1:]
668                    skip_swig = 1
669                    target_file = _find_swig_target(target_dir, name)
670                    if not os.path.isfile(target_file):
671                        log.warn('  target %s does not exist:\n   '\
672                                 'Assuming %s_wrap.{c,cpp} was generated with '\
673                                 '"build_src --inplace" command.' \
674                                 % (target_file, name))
675                        target_dir = os.path.dirname(base)
676                        target_file = _find_swig_target(target_dir, name)
677                        if not os.path.isfile(target_file):
678                            raise DistutilsSetupError("%r missing" % (target_file,))
679                        log.warn('   Yes! Using %r as up-to-date target.' \
680                                 % (target_file))
681                target_dirs.append(target_dir)
682                new_sources.append(target_file)
683                py_files.append(os.path.join(py_target_dir, name+'.py'))
684                swig_sources.append(source)
685                swig_targets[source] = new_sources[-1]
686            else:
687                new_sources.append(source)
688
689        if not swig_sources:
690            return new_sources
691
692        if skip_swig:
693            return new_sources + py_files
694
695        for d in target_dirs:
696            self.mkpath(d)
697
698        swig = self.swig or self.find_swig()
699        swig_cmd = [swig, "-python"] + extension.swig_opts
700        if is_cpp:
701            swig_cmd.append('-c++')
702        for d in extension.include_dirs:
703            swig_cmd.append('-I'+d)
704        for source in swig_sources:
705            target = swig_targets[source]
706            depends = [source] + extension.depends
707            if self.force or newer_group(depends, target, 'newer'):
708                log.info("%s: %s" % (os.path.basename(swig) \
709                                     + (is_cpp and '++' or ''), source))
710                self.spawn(swig_cmd + self.swig_opts \
711                           + ["-o", target, '-outdir', py_target_dir, source])
712            else:
713                log.debug("  skipping '%s' swig interface (up-to-date)" \
714                         % (source))
715
716        return new_sources + py_files
717
718_f_pyf_ext_match = re.compile(r'.*[.](f90|f95|f77|for|ftn|f|pyf)\Z', re.I).match
719_header_ext_match = re.compile(r'.*[.](inc|h|hpp)\Z', re.I).match
720
721#### SWIG related auxiliary functions ####
722_swig_module_name_match = re.compile(r'\s*%module\s*(.*\(\s*package\s*=\s*"(?P<package>[\w_]+)".*\)|)\s*(?P<name>[\w_]+)',
723                                     re.I).match
724_has_c_header = re.compile(r'-[*]-\s*c\s*-[*]-', re.I).search
725_has_cpp_header = re.compile(r'-[*]-\s*c[+][+]\s*-[*]-', re.I).search
726
727def get_swig_target(source):
728    with open(source, 'r') as f:
729        result = None
730        line = f.readline()
731        if _has_cpp_header(line):
732            result = 'c++'
733        if _has_c_header(line):
734            result = 'c'
735    return result
736
737def get_swig_modulename(source):
738    with open(source, 'r') as f:
739        name = None
740        for line in f:
741            m = _swig_module_name_match(line)
742            if m:
743                name = m.group('name')
744                break
745    return name
746
747def _find_swig_target(target_dir, name):
748    for ext in ['.cpp', '.c']:
749        target = os.path.join(target_dir, '%s_wrap%s' % (name, ext))
750        if os.path.isfile(target):
751            break
752    return target
753
754#### F2PY related auxiliary functions ####
755
756_f2py_module_name_match = re.compile(r'\s*python\s*module\s*(?P<name>[\w_]+)',
757                                     re.I).match
758_f2py_user_module_name_match = re.compile(r'\s*python\s*module\s*(?P<name>[\w_]*?'
759                                          r'__user__[\w_]*)', re.I).match
760
761def get_f2py_modulename(source):
762    name = None
763    with open(source) as f:
764        for line in f:
765            m = _f2py_module_name_match(line)
766            if m:
767                if _f2py_user_module_name_match(line): # skip *__user__* names
768                    continue
769                name = m.group('name')
770                break
771    return name
772
773##########################################
774