Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions doc/source/whatsnew.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ v2.2.0.dev0
built against a libffi other than the one provided by the macOS SDK — that
is, the iOS/tvOS/watchOS wheels, or a macOS build using a Homebrew libffi.
(`#265`_).
* The C source generated by ``cffi-gen-src`` and ``ffibuilder.emit_c_code()``
no longer depends on the interpreter running the generator.

.. _`#265`: https://github.com/python-cffi/cffi/pull/265

Expand Down
21 changes: 9 additions & 12 deletions src/cffi/_cffi_include.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,24 +13,19 @@
In that case, we guess what pyconfig.h will do to the macros above,
and check our guess after the #include.

Note that on Windows, with CPython 3.x, you need >= 3.5 and virtualenv
version >= 16.0.0. With older versions of either, you don't get a
copy of PYTHON3.DLL in the virtualenv. We can't check the version of
CPython *before* we even include pyconfig.h. ffi.set_source() puts
a ``#define _CFFI_NO_LIMITED_API'' at the start of this file if it is
running on Windows < 3.5, as an attempt at fixing it, but that's
arguably wrong because it may not be the target version of Python.
Still better than nothing I guess. As another workaround, you can
remove the definition of Py_LIMITED_API here.

See also 'py_limited_api' in cffi/setuptools_ext.py.
*/
#if !defined(_CFFI_USE_EMBEDDING) && !defined(Py_LIMITED_API)
# include <patchlevel.h> /* for PY_VERSION_HEX; self-contained */
# ifdef _MSC_VER
# if !defined(_DEBUG) && !defined(Py_DEBUG) && !defined(Py_TRACE_REFS) && !defined(Py_REF_DEBUG) && !defined(_CFFI_NO_LIMITED_API)
# if !defined(Py_GIL_DISABLED)
# define Py_LIMITED_API
# else
# elif PY_VERSION_HEX >= 0x030f0000
/* the free-threaded build supports the limited API (the abi3t
stable ABI) since 3.15. Note that on Windows pyconfig.h does
not define Py_GIL_DISABLED; build backends targeting the
free-threaded build pass it on the compiler command line. */
# define Py_LIMITED_API 0x030f0000
# endif
# endif
Expand All @@ -56,7 +51,9 @@
# if !defined(Py_DEBUG) && !defined(Py_TRACE_REFS) && !defined(Py_REF_DEBUG) && !defined(_CFFI_NO_LIMITED_API)
# if !defined(Py_GIL_DISABLED)
# define Py_LIMITED_API
# else
# elif PY_VERSION_HEX >= 0x030f0000
/* the free-threaded build supports the limited API (the abi3t
stable ABI) since 3.15 */
# define Py_LIMITED_API 0x030f0000
# endif
# endif
Expand Down
9 changes: 1 addition & 8 deletions src/cffi/recompiler.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import io, os, sys, sysconfig
import io, os, sys
from . import ffiplatform, model
from .error import VerificationError
from .cffi_opcode import *
Expand All @@ -7,11 +7,6 @@
VERSION_EMBEDDED = 0x2701
VERSION_CHAR16CHAR32 = 0x2801

FREE_THREADED_BUILD = sysconfig.get_config_var("Py_GIL_DISABLED")
USE_LIMITED_API = ((sys.platform != 'win32' or sys.version_info < (3, 0) or
sys.version_info >= (3, 5)) and
# free-threaded build doesn't support the stable ABI until Python 3.15
(not FREE_THREADED_BUILD or sys.version_info >= (3, 15)))

class GlobalExpr:
def __init__(self, name, address, type_op, size=0, check_value=0):
Expand Down Expand Up @@ -306,8 +301,6 @@ def write_c_source_to_f(self, f, preamble):
prnt = self._prnt
if self.ffi._embedding is not None:
prnt('#define _CFFI_USE_EMBEDDING')
if not USE_LIMITED_API:
prnt('#define _CFFI_NO_LIMITED_API')
#
# first the '#include' (actually done by inlining the file's content)
lines = self._rel_readlines('_cffi_include.h')
Expand Down
9 changes: 2 additions & 7 deletions src/cffi/setuptools_ext.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,17 +82,12 @@ def _set_py_limited_api(Extension, kwds):
it doesn't so far, creating troubles. That's why we check
for "not hasattr(sys, 'gettotalrefcount')" (the 2.7 compatible equivalent
of 'd' not in sys.abiflags). (http://bugs.python.org/issue28401)

On Windows, with CPython <= 3.4, it's better not to use py_limited_api
because virtualenv *still* doesn't copy PYTHON3.DLL on these versions.
Recently (2020) we started shipping only >= 3.5 wheels, though. So
we'll give it another try and set py_limited_api on Windows >= 3.5.
"""
from cffi._shimmed_dist_utils import log
from cffi import recompiler

if ('py_limited_api' not in kwds and not hasattr(sys, 'gettotalrefcount')
and recompiler.USE_LIMITED_API):
and (not sysconfig.get_config_var("Py_GIL_DISABLED")
or sys.version_info >= (3, 15))):
import setuptools
try:
setuptools_major_version = int(setuptools.__version__.partition('.')[0])
Expand Down
13 changes: 13 additions & 0 deletions testing/cffi1/test_cffi_gen_src.py
Original file line number Diff line number Diff line change
Expand Up @@ -211,3 +211,16 @@ def test_import_is_inert():
assert proc.returncode == 0, proc.stderr
assert proc.stdout == ""
assert proc.stderr == ""


def test_output_independent_of_generating_interpreter(tmp_path):
# runs on free-threaded and GIL-enabled CI alike; the output must be
# the same on both, with the limited-API decision left to
# _cffi_include.h at compile time
pyfile = tmp_path / "_squared_build.py"
pyfile.write_text(SIMPLE_SCRIPT)
out = tmp_path / "squared.c"
proc = _run([sys.executable, "-m", "cffi.gen_src"],
"exec-python", str(pyfile), str(out))
assert proc.returncode == 0, proc.stderr
assert "#define _CFFI_NO_LIMITED_API" not in out.read_text().splitlines()
12 changes: 12 additions & 0 deletions testing/cffi1/test_recompiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -2595,3 +2595,15 @@ def test_large_enum():
relements[biglist[i]] = i
assert e.elements == elements
assert e.relements == relements

def test_emit_c_code_is_interpreter_independent():
# the emitted source must be a pure function of the FFI object, with
# no #defines derived from the interpreter running the generator
import io
ffi = FFI()
ffi.cdef("int ff_interp_indep(int);")
ffi.set_source("_CFFI_interp_indep",
"int ff_interp_indep(int x) { return x; }")
f = io.StringIO()
ffi.emit_c_code(f)
assert "#define _CFFI_NO_LIMITED_API" not in f.getvalue().splitlines()