Esempio n. 1
0
    def _compile(
            self, name, tpl, tpl_vars, libs, libd=None, incd=None, flags=None):
        """
        Compiles a source template into a module and returns it.

        The module's name is specified by ``name``.

        The template to compile is given by ``tpl``, while any variables
        required to process the template should be given as the dict
        ``tpl_vars``.

        Any C libraries needed for compilation should be given in the sequence
        type ``libs``. Library dirs and include dirs can be passed in using
        ``libd`` and ``incd``. Extra compiler arguments can be given in the
        list ``flags``.
        """
        src_file = self._source_file()
        d_cache = tempfile.mkdtemp('myokit')
        try:
            # Create output directories
            d_build = os.path.join(d_cache, 'build')
            d_modul = os.path.join(d_cache, 'module')
            os.makedirs(d_build)
            os.makedirs(d_modul)

            # Export c file
            src_file = os.path.join(d_cache, src_file)
            self._export(tpl, tpl_vars, src_file)

            # Add runtime_library_dirs (to prevent LD_LIBRARY_PATH) errors on
            # unconventional linux sundials installations, but not on windows
            # as this can lead to a weird error in setuptools
            runtime = None if platform.system() == 'Windows' else libd

            # Create extension
            ext = Extension(
                name,
                sources=[src_file],
                libraries=libs,
                library_dirs=libd,
                runtime_library_dirs=runtime,
                include_dirs=incd,
                extra_compile_args=flags,
            )

            # Compile, catch output
            with myokit.SubCapture() as s:
                try:
                    setup(
                        name=name,
                        description='Temporary module',
                        ext_modules=[ext],
                        script_args=[
                            'build', '--build-base=' + d_build,
                            'install', '--install-lib=' + d_modul,
                        ])
                except (Exception, SystemExit) as e:
                    s.disable()
                    t = [
                        'Unable to compile.',
                        'Error message:',
                        '    ' + e.message,
                        'Error traceback',
                        traceback.format_exc(),
                        'Compiler output:',
                    ]
                    captured = s.text().strip()
                    t.extend(['    ' + x for x in captured.splitlines()])
                    raise myokit.CompilationError('\n'.join(t))

            # Include module (and refresh in case 2nd model is loaded)
            (f, pathname, description) = imp.find_module(name, [d_modul])
            return imp.load_dynamic(name, pathname)

        finally:
            try:
                shutil.rmtree(d_cache)
            except Exception:
                pass
Esempio n. 2
0
    def _compile(self,
                 name,
                 tpl,
                 tpl_vars,
                 libs,
                 libd=None,
                 incd=None,
                 carg=None,
                 larg=None):
        """
        Compiles a source template into a module and returns it.

        The module's name is specified by ``name``.

        The template to compile is given by ``tpl``, while any variables
        required to process the template should be given as the dict
        ``tpl_vars``.

        Any C libraries needed for compilation should be given in the sequence
        type ``libs``. Library dirs and include dirs can be passed in using
        ``libd`` and ``incd``. Extra compiler arguments can be given in the
        list ``carg``, and linker args in ``larg``.
        """
        src_file = self._source_file()
        working_dir = os.getcwd()
        d_cache = tempfile.mkdtemp('myokit')
        try:
            # Create output directories
            d_build = os.path.join(d_cache, 'build')
            os.makedirs(d_build)

            # Export c file
            src_file = os.path.join(d_cache, src_file)
            self._export(tpl, tpl_vars, src_file)

            # Ensure headers can be read from myokit/_sim
            if incd is None:
                incd = []
            incd.append(myokit.DIR_CFUNC)

            # Inputs must all be strings
            name = str(name)
            src_file = str(src_file)
            incd = [str(x) for x in incd]
            libd = None if libd is None else [str(x) for x in libd]
            libs = None if libs is None else [str(x) for x in libs]
            carg = None if carg is None else [str(x) for x in carg]
            larg = None if larg is None else [str(x) for x in larg]

            # Uncomment to debug C89 issues
            '''
            if carg is None:
                carg = []
            carg.extend([
                #'-Wall',
                #'-Wextra',
                #'-Werror=strict-prototypes',
                #'-Werror=old-style-definition',
                #'-Werror=missing-prototypes',
                #'-Werror=missing-declarations',
                '-Werror=declaration-after-statement',
            ])
            #'''

            # Add runtime_library_dirs (to prevent LD_LIBRARY_PATH) errors on
            # unconventional linux sundials installations, but not on windows
            # as this can lead to a weird error in setuptools
            runtime = libd
            if platform.system() == 'Windows':  # pragma: no linux cover
                if libd is not None:
                    runtime = None

                    # Determine strategy
                    try:
                        os.add_dll_directory
                        use_add_dll_directory = True
                    except AttributeError:
                        use_add_dll_directory = False

                    # Make windows search the libd directories
                    if use_add_dll_directory:
                        # Python 3.8 and up
                        for path in libd:
                            if os.path.isdir(path):
                                os.add_dll_directory(path)

                    else:
                        # Older versions: add libd to path
                        path = os.environ.get('path', '')
                        if path is None:
                            path = ''
                        to_add = [x for x in libd if x not in path]
                        os.environ['path'] = os.pathsep.join([path] + to_add)

            # Create extension
            ext = Extension(
                name,
                sources=[src_file],
                libraries=libs,
                library_dirs=libd,
                runtime_library_dirs=runtime,
                include_dirs=incd,
                extra_compile_args=carg,
                extra_link_args=larg,
            )

            # Compile in build directory, catch output
            with myokit.SubCapture() as s:
                try:
                    os.chdir(d_build)
                    setup(name=name,
                          description='Temporary module',
                          ext_modules=[ext],
                          script_args=[
                              str('build_ext'),
                              str('--inplace'),
                          ])
                except (Exception, SystemExit) as e:  # pragma: no cover
                    s.disable()
                    t = ['Unable to compile.', 'Error message:']
                    t.append(str(e))
                    t.append('Error traceback')
                    t.append(traceback.format_exc())
                    t.append('Compiler output:')
                    captured = s.text().strip()
                    t.extend(['    ' + x for x in captured.splitlines()])
                    raise myokit.CompilationError('\n'.join(t))

            # Include module (and refresh in case 2nd model is loaded)
            return load_module(name, d_build)

        finally:
            # Revert changes to working directory
            os.chdir(working_dir)

            # Delete cached module
            try:
                myokit._rmtree(d_cache)
            except Exception:  # pragma: no cover
                pass
    def _compile(self,
                 name,
                 tpl,
                 tpl_vars,
                 libs,
                 libd=None,
                 incd=None,
                 flags=None):
        """
        Compiles a source template into a module and returns it.

        The module's name is specified by ``name``.

        The template to compile is given by ``tpl``, while any variables
        required to process the template should be given as the dict
        ``tpl_vars``.

        Any C libraries needed for compilation should be given in the sequence
        type ``libs``. Library dirs and include dirs can be passed in using
        ``libd`` and ``incd``. Extra compiler arguments can be given in the
        list ``flags``.
        """
        src_file = self._source_file()
        d_cache = tempfile.mkdtemp('myokit')
        try:
            # Create output directories
            d_build = os.path.join(d_cache, 'build')
            d_modul = os.path.join(d_cache, 'module')
            os.makedirs(d_build)
            os.makedirs(d_modul)

            # Export c file
            src_file = os.path.join(d_cache, src_file)
            self._export(tpl, tpl_vars, src_file)

            # Inputs must all be strings
            name = str(name)
            src_file = str(src_file)
            flags = None if flags is None else [str(x) for x in flags]
            libd = None if libd is None else [str(x) for x in libd]
            incd = None if incd is None else [str(x) for x in incd]
            libs = None if libs is None else [str(x) for x in libs]

            # Add runtime_library_dirs (to prevent LD_LIBRARY_PATH) errors on
            # unconventional linux sundials installations, but not on windows
            # as this can lead to a weird error in setuptools
            runtime = libd
            if (platform.system() == 'Windows'
                    and libd is not None):  # pragma: no linux cover
                runtime = None

                # Instead, add libd to path on windows
                try:
                    path = os.environ['path']
                except KeyError:
                    path = ''
                to_add = [x for x in libd if x not in path]
                if to_add:
                    os.environ['path'] = os.pathsep.join([path] + to_add)

            # Create extension
            ext = Extension(
                name,
                sources=[src_file],
                libraries=libs,
                library_dirs=libd,
                runtime_library_dirs=runtime,
                include_dirs=incd,
                extra_compile_args=flags,
            )

            # Compile, catch output
            with myokit.SubCapture() as s:
                try:
                    setup(name=name,
                          description='Temporary module',
                          ext_modules=[ext],
                          script_args=[
                              str('build'),
                              str('--build-base=' + d_build),
                              str('install'),
                              str('--install-lib=' + d_modul),
                              str('--old-and-unmanageable'),
                          ])
                except (Exception, SystemExit) as e:
                    s.disable()
                    t = ['Unable to compile.', 'Error message:']
                    t.append(str(e))
                    t.append('Error traceback')
                    t.append(traceback.format_exc())
                    t.append('Compiler output:')
                    captured = s.text().strip()
                    t.extend(['    ' + x for x in captured.splitlines()])
                    raise myokit.CompilationError('\n'.join(t))
                finally:
                    egg = name + '.egg-info'
                    if os.path.exists(egg):
                        shutil.rmtree(egg)

            # Include module (and refresh in case 2nd model is loaded)
            return load_module(name, d_modul)

        finally:
            try:
                shutil.rmtree(d_cache)
            except Exception:  # pragma: no cover
                pass
Esempio n. 4
0
    def _compile(self, name, template, variables, libs, libd=None, incd=None,
                 carg=None, larg=None, continue_in_debug_mode=False):
        """
        Compiles a source ``template`` with the given ``variables`` into a
        module called ``name``, then imports it and returns a reference to the
        imported module.

        Any C libraries needed for compilation should be given in the sequence
        type ``libs``. Library dirs and include dirs can be passed in using
        ``libd`` and ``incd``. Extra compiler arguments can be given in the
        list ``carg``, and linker args in ``larg``.

        If ``myokit.DEBUG_SG`` or ``myokit.DEBUG_WG`` are set, the method will
        print the generated code to screen and/or write it to disk. Following
        this, it will terminate with exit code 1 unless
        ``continue_in_debug_mode`` is changed to ``True``.
        """
        # Show and/or write code in debug mode
        if myokit.DEBUG_SG or myokit.DEBUG_WG:  # pragma: no cover
            if myokit.DEBUG_SG:
                self._debug_show(template, variables)
            else:
                self._debug_write(template, variables)
            if not continue_in_debug_mode:
                sys.exit(1)

        # Write to temp dir and compile
        src_file = self._source_file()
        working_dir = os.getcwd()
        d_cache = tempfile.mkdtemp('myokit')
        try:
            # Create output directories
            d_build = os.path.join(d_cache, 'build')
            os.makedirs(d_build)

            # Export c file
            src_file = os.path.join(d_cache, src_file)
            self._export_inner(template, variables, src_file)

            # Ensure headers can be read from myokit/_sim
            if incd is None:
                incd = []
            incd.append(myokit.DIR_CFUNC)

            # Inputs must all be strings
            name = str(name)
            src_file = str(src_file)
            incd = [str(x) for x in incd]
            libd = None if libd is None else [str(x) for x in libd]
            libs = None if libs is None else [str(x) for x in libs]
            carg = None if carg is None else [str(x) for x in carg]
            larg = None if larg is None else [str(x) for x in larg]

            # Show warnings
            if myokit.DEBUG_SC:
                if carg is None:
                    carg = []
                carg.append('-Wall')
                if platform.system() == 'Linux':
                    carg.extend([
                        '-Wextra',
                        '-Wstrict-prototypes',
                        '-Wold-style-definition',
                        '-Wmissing-prototypes',
                        '-Wmissing-declarations',
                        '-Wdeclaration-after-statement',
                    ])

            # Add runtime_library_dirs to prevent LD_LIBRARY_PATH errors on
            # unconventional linux sundials installations, but not on windows
            # as this can lead to a weird error in setuptools
            runtime = libd
            if platform.system() == 'Windows':  # pragma: no linux cover
                if libd is not None:
                    runtime = None

                    # Make windows search the libd directories
                    path = os.environ.get('path', '')
                    if path is None:
                        path = ''
                    to_add = [x for x in libd if x not in path]
                    os.environ['path'] = os.pathsep.join([path] + to_add)

                    # In Python 3.8+, they need to be registered with
                    # add_dll_directory too. This does not seem to be 100%
                    # consistent. AppVeyor tests pass when using
                    # add_dll_directory *without* adding the directories to the
                    # path, while installations via miniconda seem to need the
                    # path method too.
                    try:
                        # Fail if add_dll_directory not present
                        os.add_dll_directory

                        # Add DLL paths
                        for path in libd:
                            if os.path.isdir(path):
                                os.add_dll_directory(path)
                    except AttributeError:
                        pass

            # Create extension
            ext = Extension(
                name,
                sources=[src_file],
                libraries=libs,
                library_dirs=libd,
                runtime_library_dirs=runtime,
                include_dirs=incd,
                extra_compile_args=carg,
                extra_link_args=larg,
            )

            # Compile in build directory, catch output
            capture = not myokit.DEBUG_SC
            error, trace = None, None
            with myokit.tools.capture(fd=True, enabled=capture) as s:
                try:
                    os.chdir(d_build)
                    setup(
                        name=name,
                        description='Temporary module',
                        ext_modules=[ext],
                        script_args=[
                            str('build_ext'),
                            str('--inplace'),
                        ])
                except (Exception, SystemExit) as e:  # pragma: no cover
                    error = e
                    trace = traceback.format_exc()
            if error is not None:  # pragma: no cover
                t = ['Unable to compile.', 'Error message:']
                t.append(str(error))
                t.append(trace)
                t.append('Compiler output:')
                captured = s.text().strip()
                t.extend(['    ' + x for x in captured.splitlines()])
                raise myokit.CompilationError('\n'.join(t))

            # Include module (and refresh in case 2nd model is loaded)
            return load_module(name, d_build)

        finally:
            # Revert changes to working directory
            os.chdir(working_dir)

            # Delete cached module
            try:
                myokit.tools.rmtree(d_cache)
            except Exception:   # pragma: no cover
                pass