1#!/usr/bin/env python
2"""
3Build script for the shared library providing the C ABI bridge to LLVM.
4"""
5
6from __future__ import print_function
7
8from ctypes.util import find_library
9import re
10import os
11import subprocess
12import shutil
13import sys
14import tempfile
15
16
17here_dir = os.path.abspath(os.path.dirname(__file__))
18build_dir = os.path.join(here_dir, 'build')
19target_dir = os.path.join(os.path.dirname(here_dir), 'llvmlite', 'binding')
20
21is_64bit = sys.maxsize >= 2**32
22
23
24def try_cmake(cmake_dir, build_dir, generator):
25    old_dir = os.getcwd()
26    try:
27        os.chdir(build_dir)
28        subprocess.check_call(['cmake', '-G', generator, cmake_dir])
29    finally:
30        os.chdir(old_dir)
31
32
33def run_llvm_config(llvm_config, args):
34    cmd = [llvm_config] + args
35    p = subprocess.Popen(cmd,
36                         stdout=subprocess.PIPE,
37                         stderr=subprocess.PIPE)
38    out, err = p.communicate()
39    out = out.decode()
40    err = err.decode()
41    rc = p.wait()
42    if rc != 0:
43        raise RuntimeError("Command %s returned with code %d; stderr follows:\n%s\n"
44                           % (cmd, rc, err))
45    return out
46
47
48def find_win32_generator():
49    """
50    Find a suitable cmake "generator" under Windows.
51    """
52    # XXX this assumes we will find a generator that's the same, or
53    # compatible with, the one which was used to compile LLVM... cmake
54    # seems a bit lacking here.
55    cmake_dir = os.path.join(here_dir, 'dummy')
56    # LLVM 4.0+ needs VS 2015 minimum.
57    generators = []
58    if os.environ.get("CMAKE_GENERATOR"):
59        generators.append(os.environ.get("CMAKE_GENERATOR"))
60
61    # Drop generators that are too old
62    vspat = re.compile(r'Visual Studio (\d+)')
63    def drop_old_vs(g):
64        m = vspat.match(g)
65        if m is None:
66            return True  # keep those we don't recognize
67        ver = int(m.group(1))
68        return ver >= 14
69    generators = list(filter(drop_old_vs, generators))
70
71    generators.append('Visual Studio 15 2017' + (' Win64' if is_64bit else ''))
72    for generator in generators:
73        build_dir = tempfile.mkdtemp()
74        print("Trying generator %r" % (generator,))
75        try:
76            try_cmake(cmake_dir, build_dir, generator)
77        except subprocess.CalledProcessError:
78            continue
79        else:
80            # Success
81            return generator
82        finally:
83            shutil.rmtree(build_dir)
84    raise RuntimeError("No compatible cmake generator installed on this machine")
85
86
87def main_win32():
88    generator = find_win32_generator()
89    config = 'Release'
90    if not os.path.exists(build_dir):
91        os.mkdir(build_dir)
92    # Run configuration step
93    try_cmake(here_dir, build_dir, generator)
94    subprocess.check_call(['cmake', '--build', build_dir, '--config', config])
95    shutil.copy(os.path.join(build_dir, config, 'llvmlite.dll'), target_dir)
96
97
98def main_posix(kind, library_ext):
99    os.chdir(here_dir)
100    # Check availability of llvm-config
101    llvm_config = os.environ.get('LLVM_CONFIG', 'llvm-config')
102    print("LLVM version... ", end='')
103    sys.stdout.flush()
104    try:
105        out = subprocess.check_output([llvm_config, '--version'])
106    except (OSError, subprocess.CalledProcessError):
107        raise RuntimeError("%s failed executing, please point LLVM_CONFIG "
108                           "to the path for llvm-config" % (llvm_config,))
109
110    out = out.decode('latin1')
111    print(out)
112
113    # See if the user is overriding the version check, this is unsupported
114    try:
115        _ver_check_skip = os.environ.get("LLVMLITE_SKIP_LLVM_VERSION_CHECK", 0)
116        skipcheck = int(_ver_check_skip)
117    except ValueError as e:
118        msg = ('If set, the environment variable '
119               'LLVMLITE_SKIP_LLVM_VERSION_CHECK should be an integer, got '
120               '"{}".')
121        raise ValueError(msg.format(_ver_check_skip)) from e
122
123    if skipcheck:
124        # user wants to use an unsupported version, warn about doing this...
125        msg = ("The LLVM version check for supported versions has been "
126               "overridden.\nThis is unsupported behaviour, llvmlite may not "
127               "work as intended.\nRequested LLVM version: {}".format(
128                   out.strip()))
129        warn = ' * '.join(("WARNING",) * 8)
130        blk = '=' * 80
131        warning = '{}\n{}\n{}'.format(blk, warn, blk)
132        print(warning)
133        print(msg)
134        print(warning + '\n')
135    else:
136
137        if not (out.startswith('10.0.') or out.startswith('9.0')):
138            msg = ("Building llvmlite requires LLVM 10.0.x or 9.0.x, got "
139                   "{!r}. Be sure to set LLVM_CONFIG to the right executable "
140                   "path.\nRead the documentation at "
141                   "http://llvmlite.pydata.org/ for more information about "
142                   "building llvmlite.\n".format(out.strip()))
143            raise RuntimeError(msg)
144
145    # Get LLVM information for building
146    libs = run_llvm_config(llvm_config, "--system-libs --libs all".split())
147    # Normalize whitespace (trim newlines)
148    os.environ['LLVM_LIBS'] = ' '.join(libs.split())
149
150    cxxflags = run_llvm_config(llvm_config, ["--cxxflags"])
151    # on OSX cxxflags has null bytes at the end of the string, remove them
152    cxxflags = cxxflags.replace('\0', '')
153    cxxflags = cxxflags.split() + ['-fno-rtti', '-g']
154
155    # look for SVML
156    include_dir = run_llvm_config(llvm_config, ['--includedir']).strip()
157    svml_indicator = os.path.join(include_dir, 'llvm', 'IR', 'SVML.inc')
158    if os.path.isfile(svml_indicator):
159        cxxflags = cxxflags + ['-DHAVE_SVML']
160        print('SVML detected')
161    else:
162        print('SVML not detected')
163
164    os.environ['LLVM_CXXFLAGS'] = ' '.join(cxxflags)
165
166    ldflags = run_llvm_config(llvm_config, ["--ldflags"])
167    os.environ['LLVM_LDFLAGS'] = ldflags.strip()
168    # static link libstdc++ for portability
169    if int(os.environ.get('LLVMLITE_CXX_STATIC_LINK', 0)):
170        os.environ['CXX_STATIC_LINK'] = "-static-libstdc++"
171
172    makefile = "Makefile.%s" % (kind,)
173    subprocess.check_call(['make', '-f', makefile])
174    shutil.copy('libllvmlite' + library_ext, target_dir)
175
176
177def main():
178    if sys.platform == 'win32':
179        main_win32()
180    elif sys.platform.startswith('linux'):
181        main_posix('linux', '.so')
182    elif sys.platform.startswith(('freebsd','openbsd', 'dragonfly')):
183        main_posix('freebsd', '.so')
184    elif sys.platform == 'darwin':
185        main_posix('osx', '.dylib')
186    else:
187        raise RuntimeError("unsupported platform: %r" % (sys.platform,))
188
189
190if __name__ == "__main__":
191    main()
192