File: wrap

package info (click to toggle)
btllib 1.7.5%2Bdfsg-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 4,336 kB
  • sloc: cpp: 79,742; python: 941; sh: 302; makefile: 5
file content (130 lines) | stat: -rwxr-xr-x 4,367 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
#!/usr/bin/env python3

import os
import re
import functools
import subprocess


class PythonWrapper:

    INTERFACE_TEMPLATE = """%module btllib

%{{
#define SWIG_FILE_WITH_INIT

{cpp_includes}
%}}

%include <stdint.i>
%include <typemaps.i>
%include <pyprimtypes.swg>
%include <pyopers.swg>
%include <std_common.i>
%include <cstring.i>
%include <std_string.i>
%include <exception.i>
%include <std_iostream.i>
%include <carrays.i>
%include <std_vector.i>
%include <stl.i>

%include "../extra_common.i"
%include "extra.i"

{swig_includes}

%include "../extra_templates.i"
"""

    def __init__(self, out_path: str, include_path: str):
        self._out_path = out_path
        self._include_path = include_path

    def _remove_old_files(self):
        if os.path.exists(os.path.join(self._out_path, 'btllib.py')):
            os.remove(os.path.join(self._out_path, 'btllib.py'))

    def _load_include_file_names(self):
        include_files = os.listdir(os.path.join(self._include_path, 'btllib'))
        code_filter = functools.partial(re.match, r'.*\.(h|hpp|cpp|cxx)$')
        code_files = filter(code_filter, include_files)
        path_fixer = functools.partial(os.path.join, 'btllib')
        include_files = map(path_fixer, code_files)
        return list(include_files)

    def _update_interface_file(self):
        with open(os.path.join(self._out_path, 'btllib.i')) as f:
            current_lines = list(map(str.strip, f.readlines()))
        # Add include statements only for newly added files.
        # This will prevent changing the ordering of the includes.
        include_dir_files = self._load_include_file_names()
        include_files = []
        for line in current_lines:
            match = re.match(r'#include "(.*)"', line)
            if match and match.group(1) in include_dir_files:
                include_files.append(match.group(1))
        for file in include_dir_files:
            if file not in include_files:
                include_files.append(file)
        cpp = os.linesep.join(f'#include "{f}"' for f in include_files)
        swig = os.linesep.join(f'%include "{f}"' for f in include_files)
        interface = PythonWrapper.INTERFACE_TEMPLATE.format(cpp_includes=cpp,
                                                            swig_includes=swig)
        with open(os.path.join(self._out_path, 'btllib.i'), 'w') as f:
            f.write(interface)

    def _call_swig(self):
        swig_cmd = [
            'swig', '-python', '-fastproxy', '-fastdispatch', '-builtin',
            '-c++', f'-I{self._include_path}', 'btllib.i'
        ]
        return subprocess.run(swig_cmd,
                              capture_output=True,
                              text=True,
                              cwd=self._out_path)

    def _fix_unsigned_long_long(self):
        # The following is necessary because SWIG produces inconsistent code that cannot be compiled on all platforms.
        # On some platforms, uint64_t is unsigned long int and unsigned long long int on others.
        cxx_path = os.path.join(self._out_path, 'btllib_wrap.cxx')
        with open(cxx_path) as f:
            cxx_contents = f.read()
        cxx_contents = cxx_contents.replace('unsigned long long', 'uint64_t')
        with open(cxx_path, 'w') as f:
            f.write(cxx_contents)

    def generate(self):
        self._remove_old_files()
        self._update_interface_file()
        swig_result = self._call_swig()
        self._fix_unsigned_long_long()
        return swig_result


def check_meson_env():
    if 'MESON_SOURCE_ROOT' not in os.environ:
        print("[ERROR] This script can only be ran with meson!")
        exit(1)


def check_swig_result(swig_result, wrapper_language: str):
    lang = wrapper_language.capitalize()
    if swig_result.returncode == 0:
        print(f"{lang} wrappers generated successfully")
    elif swig_result.returncode == 1:
        print(f"Error when calling SWIG for {lang}, stderr:")
        print(swig_result.stderr)


def main():
    check_meson_env()
    project_root = os.environ['MESON_SOURCE_ROOT']
    include_path = os.path.join(project_root, 'include')
    python_dir = os.path.join(project_root, 'wrappers', 'python')
    swig_result_py = PythonWrapper(python_dir, include_path).generate()
    check_swig_result(swig_result_py, 'python')


if __name__ == '__main__':
    main()