Completed
Push — master ( baee0a...fc506e )
by Kyle
01:09
created

unpack_sdl2()   B

Complexity

Conditions 6

Size

Total Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

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