Completed
Push — master ( 7a06e5...74d73e )
by Kyle
47s
created

CustomPostParser.visit_ArrayDecl()   A

Complexity

Conditions 3

Size

Total Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

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