Completed
Push — master ( dc3d89...4876a7 )
by Kyle
47s
created

get_ast()   A

Complexity

Conditions 3

Size

Total Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

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