CustomPostParser.visit_EnumeratorList()   A
last analyzed

Complexity

Conditions 5

Size

Total Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
dl 0
loc 9
rs 9.3333
c 0
b 0
f 0
1
#!/usr/bin/env python3
2
3
import os
4
import sys
5
6
import glob
7
8
from cffi import FFI
9
from pycparser import c_parser, c_ast, parse_file, c_generator
10
11
import shutil
12
import subprocess
13
import platform
14
try:
15
    from urllib import urlretrieve
16
except ImportError:
17
    from urllib.request import urlretrieve
18
import zipfile
19
20
SDL2_VERSION = os.environ.get('SDL_VERSION', '2.0.8')
21
TDL_NO_SDL2_EXPORTS = os.environ.get('TDL_NO_SDL2_EXPORTS', '0') == '1'
22
23
CFFI_HEADER = 'tcod/cffi.h'
24
CFFI_EXTRA_CDEFS = 'tcod/cdef.h'
25
26
BITSIZE, LINKAGE = platform.architecture()
27
28
def walk_sources(directory):
29
    for path, dirs, files in os.walk(directory):
30
        for source in files:
31
            if not source.endswith('.c'):
32
                continue
33
            yield os.path.join(path, source)
34
35
def find_sources(directory):
36
    return [os.path.join(directory, source)
37
            for source in os.listdir(directory)
38
            if source.endswith('.c')]
39
40
def get_sdl2_file(version):
41
    if sys.platform == 'win32':
42
        sdl2_file = 'SDL2-devel-%s-VC.zip' % (version,)
43
    else:
44
        assert sys.platform == 'darwin'
45
        sdl2_file = 'SDL2-%s.dmg' % (version,)
46
    sdl2_local_file = os.path.join('dependencies', sdl2_file)
47
    sdl2_remote_file = 'https://www.libsdl.org/release/%s' % sdl2_file
48
    if not os.path.exists(sdl2_local_file):
49
        print('Downloading %s' % sdl2_remote_file)
50
        urlretrieve(sdl2_remote_file, sdl2_local_file)
51
    return sdl2_local_file
52
53
def unpack_sdl2(version):
54
    sdl2_path = 'dependencies/SDL2-%s' % (version,)
55
    if sys.platform == 'darwin':
56
        sdl2_dir = sdl2_path
57
        sdl2_path += '/SDL2.framework'
58
    if os.path.exists(sdl2_path):
59
        return sdl2_path
60
    sdl2_arc = get_sdl2_file(version)
61
    print('Extracting %s' % sdl2_arc)
62
    if sdl2_arc.endswith('.zip'):
63
        with zipfile.ZipFile(sdl2_arc) as zf:
64
            zf.extractall('dependencies/')
65
    else:
66
        assert sdl2_arc.endswith('.dmg')
67
        subprocess.check_call(['hdiutil', 'mount', sdl2_arc])
68
        subprocess.check_call(['mkdir', '-p', sdl2_dir])
69
        subprocess.check_call(['cp', '-r', '/Volumes/SDL2/SDL2.framework',
70
                                           sdl2_dir])
71
        subprocess.check_call(['hdiutil', 'unmount', '/Volumes/SDL2'])
72
    return sdl2_path
73
74
module_name = 'tcod._libtcod'
75
include_dirs = [
76
    '.',
77
    'libtcod/include/',
78
    'libtcod/src/png/',
79
    'libtcod/src/zlib/',
80
]
81
82
extra_parse_args = []
83
extra_compile_args = []
84
extra_link_args = []
85
sources = []
86
87
libraries = []
88
library_dirs = []
89
define_macros = []
90
91
sources += walk_sources('tcod/')
92
sources += walk_sources('tdl/')
93
sources += ['libtcod/src/libtcod_c.c']
94
sources += ['libtcod/src/png/lodepng.c']
95
sources += glob.glob('libtcod/src/zlib/*.c')
96
97
if TDL_NO_SDL2_EXPORTS:
98
    extra_parse_args.append('-DTDL_NO_SDL2_EXPORTS')
99
100
if sys.platform == 'win32':
101
    libraries += ['User32', 'OpenGL32']
102
    define_macros.append(('TCODLIB_API', ''))
103
    define_macros.append(('_CRT_SECURE_NO_WARNINGS', None))
104
105
if 'linux' in sys.platform:
106
    libraries += ['GL']
107
108
if sys.platform == 'darwin':
109
    extra_link_args += ['-framework', 'OpenGL']
110
    extra_link_args += ['-framework', 'SDL2']
111
else:
112
    libraries += ['SDL2']
113
114
# included SDL headers are for whatever OS's don't easily come with them
115
116
if sys.platform in ['win32', 'darwin']:
117
    SDL2_PATH = unpack_sdl2(SDL2_VERSION)
118
    include_dirs.append('libtcod/src/zlib/')
119
120
if sys.platform == 'win32':
121
    include_dirs.append(os.path.join(SDL2_PATH, 'include'))
122
    ARCH_MAPPING = {'32bit': 'x86', '64bit': 'x64'}
123
    SDL2_LIB_DIR = os.path.join(SDL2_PATH, 'lib/', ARCH_MAPPING[BITSIZE])
124
    library_dirs.append(SDL2_LIB_DIR)
125
    SDL2_LIB_DEST = os.path.join('tcod', ARCH_MAPPING[BITSIZE])
126
    if not os.path.exists(SDL2_LIB_DEST):
127
        os.mkdir(SDL2_LIB_DEST)
128
    shutil.copy(os.path.join(SDL2_LIB_DIR, 'SDL2.dll'), SDL2_LIB_DEST)
129
130
def fix_header(filepath):
131
    """Removes leading whitespace from a MacOS header file.
132
133
    This whitespace is causing issues with directives on some platforms.
134
    """
135
    with open(filepath, 'r+') as f:
136
        current = f.read()
137
        fixed = '\n'.join(line.strip() for line in current.split('\n'))
138
        if current == fixed:
139
            return
140
        f.seek(0)
141
        f.truncate()
142
        f.write(fixed)
143
144
if sys.platform == 'darwin':
145
    HEADER_DIR = os.path.join(SDL2_PATH, 'Headers')
146
    fix_header(os.path.join(HEADER_DIR, 'SDL_assert.h'))
147
    fix_header(os.path.join(HEADER_DIR, 'SDL_config_macosx.h'))
148
    include_dirs.append(HEADER_DIR)
149
    extra_link_args += ['-F%s/..' % SDL2_PATH]
150
    extra_link_args += ['-rpath', '%s/..' % SDL2_PATH]
151
    extra_link_args += ['-rpath', '/usr/local/opt/llvm/lib/']
152
153
if sys.platform not in ['win32', 'darwin']:
154
    extra_parse_args += subprocess.check_output(['sdl2-config', '--cflags'],
155
                                              universal_newlines=True
156
                                              ).strip().split()
157
    extra_compile_args += extra_parse_args
158
    extra_link_args += subprocess.check_output(['sdl2-config', '--libs'],
159
                                               universal_newlines=True
160
                                               ).strip().split()
161
162
class CustomPostParser(c_ast.NodeVisitor):
163
164
    def __init__(self):
165
        self.ast = None
166
        self.typedefs = None
167
168
    def parse(self, ast):
169
        self.ast = ast
170
        self.typedefs = []
171
        self.visit(ast)
172
        return ast
173
174
    def visit_Typedef(self, node):
175
        if node.name in ['wchar_t', 'size_t']:
176
            # remove fake typedef placeholders
177
            self.ast.ext.remove(node)
178
        else:
179
            self.generic_visit(node)
180
            if node.name in self.typedefs:
181
                print('warning: %s redefined' % node.name)
182
                self.ast.ext.remove(node)
183
            self.typedefs.append(node.name)
184
185
    def visit_EnumeratorList(self, node):
186
        """Replace enumerator expressions with '...' stubs."""
187
        for type, enum in node.children():
188
            if enum.value is None:
189
                pass
190
            elif isinstance(enum.value, (c_ast.BinaryOp, c_ast.UnaryOp)):
191
                enum.value = c_ast.Constant('int', '...')
192
            elif hasattr(enum.value, 'type'):
193
                enum.value = c_ast.Constant(enum.value.type, '...')
194
195
    def visit_ArrayDecl(self, node):
196
        if not node.dim:
197
            return
198
        if isinstance(node.dim, (c_ast.BinaryOp, c_ast.UnaryOp)):
199
            node.dim = c_ast.Constant('int', '...')
200
201
    def visit_Decl(self, node):
202
        if node.name is None:
203
            self.generic_visit(node)
204
        elif (node.name and 'vsprint' in node.name or
205
              node.name in ['SDL_vsscanf',
206
                            'SDL_vsnprintf',
207
                            'SDL_LogMessageV']):
208
            # exclude va_list related functions
209
            self.ast.ext.remove(node)
210
        elif node.name in ['screen']:
211
            # exclude outdated 'extern SDL_Surface* screen;' line
212
            self.ast.ext.remove(node)
213
        else:
214
            self.generic_visit(node)
215
216
    def visit_FuncDef(self, node):
217
        """Exclude function definitions.  Should be declarations only."""
218
        self.ast.ext.remove(node)
219
220
def get_cdef():
221
    generator = c_generator.CGenerator()
222
    return generator.visit(get_ast())
223
224
def get_ast():
225
    global extra_parse_args
226
    if 'win32' in sys.platform:
227
        extra_parse_args += [r'-I%s/include' % SDL2_PATH]
228
    if 'darwin' in sys.platform:
229
        extra_parse_args += [r'-I%s/Headers' % SDL2_PATH]
230
231
    ast = parse_file(filename=CFFI_HEADER, use_cpp=True,
232
                     cpp_args=[r'-Idependencies/fake_libc_include',
233
                               r'-Ilibtcod/include',
234
                               r'-DDECLSPEC=',
235
                               r'-DSDLCALL=',
236
                               r'-DTCODLIB_API=',
237
                               r'-DSDL_FORCE_INLINE=',
238
                               r'-U__GNUC__',
239
                               r'-D_SDL_thread_h',
240
                               r'-DDOXYGEN_SHOULD_IGNORE_THIS',
241
                               r'-DMAC_OS_X_VERSION_MIN_REQUIRED=1060',
242
                               r'-D__attribute__(x)=',
243
                               r'-D_PSTDINT_H_INCLUDED',
244
                               ] + extra_parse_args)
245
    ast = CustomPostParser().parse(ast)
246
    return ast
247
248
# Can force the use of OpenMP with this variable.
249
try:
250
    USE_OPENMP = eval(os.environ.get('USE_OPENMP', 'None').title())
251
except Exception:
252
    USE_OPENMP = None
253
254
tdl_build = os.environ.get('TDL_BUILD', 'RELEASE').upper()
255
256
MSVC_CFLAGS = {
257
    'DEBUG': ['/Od'],
258
    'RELEASE': ['/GL', '/O2', '/GS-'],
259
}
260
MSVC_LDFLAGS = {
261
    'DEBUG': [],
262
    'RELEASE': ['/LTCG'],
263
}
264
GCC_CFLAGS = {
265
    'DEBUG': ['-O0'],
266
    'RELEASE': ['-flto', '-O3', '-fPIC'],
267
}
268
269
if sys.platform == 'win32' and '--compiler=mingw32' not in sys.argv:
270
    extra_compile_args.extend(MSVC_CFLAGS[tdl_build])
271
    extra_link_args.extend(MSVC_LDFLAGS[tdl_build])
272
273
    if USE_OPENMP is None:
274
        USE_OPENMP = sys.version_info[:2] >= (3, 5)
275
276
    if USE_OPENMP:
277
        extra_compile_args.append('/openmp')
278
else:
279
    extra_compile_args.extend(GCC_CFLAGS[tdl_build])
280
    extra_link_args.extend(GCC_CFLAGS[tdl_build])
281
    if USE_OPENMP is None:
282
        USE_OPENMP = sys.platform != 'darwin'
283
284
    if USE_OPENMP:
285
        extra_compile_args.append('-fopenmp')
286
        extra_link_args.append('-fopenmp')
287
288
ffi = FFI()
289
ffi.cdef(get_cdef())
290
ffi.cdef(open(CFFI_EXTRA_CDEFS, 'r').read())
291
ffi.set_source(
292
    module_name, '#include <tcod/cffi.h>',
293
    include_dirs=include_dirs,
294
    library_dirs=library_dirs,
295
    sources=sources,
296
    libraries=libraries,
297
    extra_compile_args=extra_compile_args,
298
    extra_link_args=extra_link_args,
299
    define_macros=define_macros,
300
)
301
302
if __name__ == "__main__":
303
    ffi.compile()
304