1import sys, os, platform
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.libera.chat.)
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
126if sys.platform == "win32" and uses_msvc():
127    if platform.machine() == "ARM64":
128        include_dirs.append(os.path.join("c/libffi_arm64/include"))
129        library_dirs.append(os.path.join("c/libffi_arm64"))
130    else:
131        COMPILE_LIBFFI = 'c/libffi_x86_x64'    # from the CPython distribution
132        assert os.path.isdir(COMPILE_LIBFFI), "directory not found!"
133        include_dirs[:] = [COMPILE_LIBFFI]
134        libraries[:] = []
135        _filenames = [filename.lower() for filename in os.listdir(COMPILE_LIBFFI)]
136        _filenames = [filename for filename in _filenames
137                            if filename.endswith('.c')]
138        if sys.maxsize > 2**32:
139            # 64-bit: unlist win32.c, and add instead win64.obj.  If the obj
140            # happens to get outdated at some point in the future, you need to
141            # rebuild it manually from win64.asm.
142            _filenames.remove('win32.c')
143            extra_link_args.append(os.path.join(COMPILE_LIBFFI, 'win64.obj'))
144        sources.extend(os.path.join(COMPILE_LIBFFI, filename)
145                    for filename in _filenames)
146else:
147    use_pkg_config()
148    ask_supports_thread()
149    ask_supports_sync_synchronize()
150
151if 'darwin' in sys.platform:
152    # priority is given to `pkg_config`, but always fall back on SDK's libffi.
153    extra_compile_args += ['-iwithsysroot/usr/include/ffi']
154
155if 'freebsd' in sys.platform:
156    include_dirs.append('/usr/local/include')
157    library_dirs.append('/usr/local/lib')
158
159if __name__ == '__main__':
160    from setuptools import setup, Distribution, Extension
161
162    class CFFIDistribution(Distribution):
163        def has_ext_modules(self):
164            # Event if we don't have extension modules (e.g. on PyPy) we want to
165            # claim that we do so that wheels get properly tagged as Python
166            # specific.  (thanks dstufft!)
167            return True
168
169    # On PyPy, cffi is preinstalled and it is not possible, at least for now,
170    # to install a different version.  We work around it by making the setup()
171    # arguments mostly empty in this case.
172    cpython = ('_cffi_backend' not in sys.builtin_module_names)
173
174    setup(
175        name='cffi',
176        description='Foreign Function Interface for Python calling C code.',
177        long_description="""
178CFFI
179====
180
181Foreign Function Interface for Python calling C code.
182Please see the `Documentation <http://cffi.readthedocs.org/>`_.
183
184Contact
185-------
186
187`Mailing list <https://groups.google.com/forum/#!forum/python-cffi>`_
188""",
189        version='1.15.0',
190        packages=['cffi'] if cpython else [],
191        package_data={'cffi': ['_cffi_include.h', 'parse_c_type.h',
192                               '_embedding.h', '_cffi_errors.h']}
193                     if cpython else {},
194        zip_safe=False,
195
196        url='http://cffi.readthedocs.org',
197        author='Armin Rigo, Maciej Fijalkowski',
198        author_email='python-cffi@googlegroups.com',
199
200        license='MIT',
201
202        distclass=CFFIDistribution,
203        ext_modules=[Extension(
204            name='_cffi_backend',
205            include_dirs=include_dirs,
206            sources=sources,
207            libraries=libraries,
208            define_macros=define_macros,
209            library_dirs=library_dirs,
210            extra_compile_args=extra_compile_args,
211            extra_link_args=extra_link_args,
212        )] if cpython else [],
213
214        install_requires=[
215            'pycparser' if sys.version_info >= (2, 7) else 'pycparser<2.19',
216        ] if cpython else [],
217
218        entry_points = {
219            "distutils.setup_keywords": [
220                "cffi_modules = cffi.setuptools_ext:cffi_modules",
221            ],
222        },
223
224        classifiers=[
225            'Programming Language :: Python',
226            'Programming Language :: Python :: 2',
227            'Programming Language :: Python :: 2.7',
228            'Programming Language :: Python :: 3',
229            'Programming Language :: Python :: 3.6',
230            'Programming Language :: Python :: 3.7',
231            'Programming Language :: Python :: 3.8',
232            'Programming Language :: Python :: 3.9',
233            'Programming Language :: Python :: 3.10',
234            'Programming Language :: Python :: Implementation :: CPython',
235            'Programming Language :: Python :: Implementation :: PyPy',
236            'License :: OSI Approved :: MIT License',
237        ],
238    )
239