1import sys, os
2import subprocess
3import errno
4
5# on Windows we give up and always import setuptools early to fix things for us
6if sys.platform == "win32":
7    import setuptools
8
9
10sources = ['c/_cffi_backend.c']
11libraries = ['ffi']
12include_dirs = ['/usr/include/ffi',
13                '/usr/include/libffi']    # may be changed by pkg-config
14define_macros = []
15library_dirs = []
16extra_compile_args = []
17extra_link_args = []
18
19
20def _ask_pkg_config(resultlist, option, result_prefix='', sysroot=False):
21    pkg_config = os.environ.get('PKG_CONFIG','pkg-config')
22    try:
23        p = subprocess.Popen([pkg_config, option, 'libffi'],
24                             stdout=subprocess.PIPE)
25    except OSError as e:
26        if e.errno not in [errno.ENOENT, errno.EACCES]:
27            raise
28    else:
29        t = p.stdout.read().decode().strip()
30        p.stdout.close()
31        if p.wait() == 0:
32            res = t.split()
33            # '-I/usr/...' -> '/usr/...'
34            for x in res:
35                assert x.startswith(result_prefix)
36            res = [x[len(result_prefix):] for x in res]
37            #print 'PKG_CONFIG:', option, res
38            #
39            sysroot = sysroot and os.environ.get('PKG_CONFIG_SYSROOT_DIR', '')
40            if sysroot:
41                # old versions of pkg-config don't support this env var,
42                # so here we emulate its effect if needed
43                res = [path if path.startswith(sysroot)
44                            else sysroot + path
45                         for path in res]
46            #
47            resultlist[:] = res
48
49no_compiler_found = False
50def no_working_compiler_found():
51    sys.stderr.write("""
52    No working compiler found, or bogus compiler options passed to
53    the compiler from Python's standard "distutils" module.  See
54    the error messages above.  Likely, the problem is not related
55    to CFFI but generic to the setup.py of any Python package that
56    tries to compile C code.  (Hints: on OS/X 10.8, for errors about
57    -mno-fused-madd see http://stackoverflow.com/questions/22313407/
58    Otherwise, see https://wiki.python.org/moin/CompLangPython or
59    the IRC channel #python on irc.freenode.net.)
60
61    Trying to continue anyway.  If you are trying to install CFFI from
62    a build done in a different context, you can ignore this warning.
63    \n""")
64    global no_compiler_found
65    no_compiler_found = True
66
67def get_config():
68    from distutils.core import Distribution
69    from distutils.sysconfig import get_config_vars
70    get_config_vars()      # workaround for a bug of distutils, e.g. on OS/X
71    config = Distribution().get_command_obj('config')
72    return config
73
74def ask_supports_thread():
75    config = get_config()
76    ok = (sys.platform != 'win32' and
77          config.try_compile('__thread int some_threadlocal_variable_42;'))
78    if ok:
79        define_macros.append(('USE__THREAD', None))
80    else:
81        ok1 = config.try_compile('int some_regular_variable_42;')
82        if not ok1:
83            no_working_compiler_found()
84        else:
85            sys.stderr.write("Note: will not use '__thread' in the C code\n")
86            _safe_to_ignore()
87
88def ask_supports_sync_synchronize():
89    if sys.platform == 'win32' or no_compiler_found:
90        return
91    config = get_config()
92    ok = config.try_link('int main(void) { __sync_synchronize(); return 0; }')
93    if ok:
94        define_macros.append(('HAVE_SYNC_SYNCHRONIZE', None))
95    else:
96        sys.stderr.write("Note: will not use '__sync_synchronize()'"
97                         " in the C code\n")
98        _safe_to_ignore()
99
100def _safe_to_ignore():
101    sys.stderr.write("***** The above error message can be safely ignored.\n\n")
102
103def uses_msvc():
104    config = get_config()
105    return config.try_compile('#ifndef _MSC_VER\n#error "not MSVC"\n#endif')
106
107def use_pkg_config():
108    if sys.platform == 'darwin' and os.path.exists('/usr/local/bin/brew'):
109        use_homebrew_for_libffi()
110
111    _ask_pkg_config(include_dirs,       '--cflags-only-I', '-I', sysroot=True)
112    _ask_pkg_config(extra_compile_args, '--cflags-only-other')
113    _ask_pkg_config(library_dirs,       '--libs-only-L', '-L', sysroot=True)
114    _ask_pkg_config(extra_link_args,    '--libs-only-other')
115    _ask_pkg_config(libraries,          '--libs-only-l', '-l')
116
117def use_homebrew_for_libffi():
118    # We can build by setting:
119    # PKG_CONFIG_PATH = $(brew --prefix libffi)/lib/pkgconfig
120    with os.popen('brew --prefix libffi') as brew_prefix_cmd:
121        prefix = brew_prefix_cmd.read().strip()
122    pkgconfig = os.path.join(prefix, 'lib', 'pkgconfig')
123    os.environ['PKG_CONFIG_PATH'] = (
124        os.environ.get('PKG_CONFIG_PATH', '') + ':' + pkgconfig)
125
126
127if sys.platform == 'win32' and uses_msvc():
128    COMPILE_LIBFFI = 'c/libffi_msvc'    # from the CPython distribution
129else:
130    COMPILE_LIBFFI = None
131
132if COMPILE_LIBFFI:
133    assert os.path.isdir(COMPILE_LIBFFI), "directory not found!"
134    include_dirs[:] = [COMPILE_LIBFFI]
135    libraries[:] = []
136    _filenames = [filename.lower() for filename in os.listdir(COMPILE_LIBFFI)]
137    _filenames = [filename for filename in _filenames
138                           if filename.endswith('.c')]
139    if sys.maxsize > 2**32:
140        # 64-bit: unlist win32.c, and add instead win64.obj.  If the obj
141        # happens to get outdated at some point in the future, you need to
142        # rebuild it manually from win64.asm.
143        _filenames.remove('win32.c')
144        extra_link_args.append(os.path.join(COMPILE_LIBFFI, 'win64.obj'))
145    sources.extend(os.path.join(COMPILE_LIBFFI, filename)
146                   for filename in _filenames)
147else:
148    use_pkg_config()
149    ask_supports_thread()
150    ask_supports_sync_synchronize()
151
152if 'freebsd' in sys.platform:
153    include_dirs.append('/usr/local/include')
154    library_dirs.append('/usr/local/lib')
155
156if 'darwin' in sys.platform:
157    try:
158        p = subprocess.Popen(['xcrun', '--show-sdk-path'],
159                             stdout=subprocess.PIPE)
160    except OSError as e:
161        if e.errno not in [errno.ENOENT, errno.EACCES]:
162            raise
163    else:
164        t = p.stdout.read().decode().strip()
165        p.stdout.close()
166        if p.wait() == 0:
167            include_dirs.append(t + '/usr/include/ffi')
168
169
170
171if __name__ == '__main__':
172    from setuptools import setup, Distribution, Extension
173
174    class CFFIDistribution(Distribution):
175        def has_ext_modules(self):
176            # Event if we don't have extension modules (e.g. on PyPy) we want to
177            # claim that we do so that wheels get properly tagged as Python
178            # specific.  (thanks dstufft!)
179            return True
180
181    # On PyPy, cffi is preinstalled and it is not possible, at least for now,
182    # to install a different version.  We work around it by making the setup()
183    # arguments mostly empty in this case.
184    cpython = ('_cffi_backend' not in sys.builtin_module_names)
185
186    setup(
187        name='cffi',
188        description='Foreign Function Interface for Python calling C code.',
189        long_description="""
190CFFI
191====
192
193Foreign Function Interface for Python calling C code.
194Please see the `Documentation <http://cffi.readthedocs.org/>`_.
195
196Contact
197-------
198
199`Mailing list <https://groups.google.com/forum/#!forum/python-cffi>`_
200""",
201        version='1.12.2',
202        packages=['cffi'] if cpython else [],
203        package_data={'cffi': ['_cffi_include.h', 'parse_c_type.h',
204                               '_embedding.h', '_cffi_errors.h']}
205                     if cpython else {},
206        zip_safe=False,
207
208        url='http://cffi.readthedocs.org',
209        author='Armin Rigo, Maciej Fijalkowski',
210        author_email='python-cffi@googlegroups.com',
211
212        license='MIT',
213
214        distclass=CFFIDistribution,
215        ext_modules=[Extension(
216            name='_cffi_backend',
217            include_dirs=include_dirs,
218            sources=sources,
219            libraries=libraries,
220            define_macros=define_macros,
221            library_dirs=library_dirs,
222            extra_compile_args=extra_compile_args,
223            extra_link_args=extra_link_args,
224        )] if cpython else [],
225
226        install_requires=[
227            'pycparser' if sys.version_info >= (2, 7) else 'pycparser<2.19',
228        ] if cpython else [],
229
230        entry_points = {
231            "distutils.setup_keywords": [
232                "cffi_modules = cffi.setuptools_ext:cffi_modules",
233            ],
234        },
235
236        classifiers=[
237            'Programming Language :: Python',
238            'Programming Language :: Python :: 2',
239            'Programming Language :: Python :: 2.6',
240            'Programming Language :: Python :: 2.7',
241            'Programming Language :: Python :: 3',
242            'Programming Language :: Python :: 3.2',
243            'Programming Language :: Python :: 3.3',
244            'Programming Language :: Python :: 3.4',
245            'Programming Language :: Python :: 3.5',
246            'Programming Language :: Python :: 3.6',
247            'Programming Language :: Python :: Implementation :: CPython',
248            'Programming Language :: Python :: Implementation :: PyPy',
249        ],
250    )
251