From 182ffc444e21b797ef8c2175a041dbc8ae92eb57 Mon Sep 17 00:00:00 2001 From: Rose Davidson Date: Mon, 2 Sep 2024 16:21:32 -0400 Subject: [PATCH] Allow writing generated code to a file-like object. (#115) This allows a caller to better control when and where the generated code is written, for issue #47. (cherry picked from commit 9e9dffbc3a7fb0016eea381a150d44f00c7c7a3a) --- doc/source/cdef.rst | 12 ++++++++++++ src/cffi/recompiler.py | 10 ++++++++++ testing/cffi1/test_new_ffi_1.py | 9 ++++++++- 3 files changed, 30 insertions(+), 1 deletion(-) diff --git a/doc/source/cdef.rst b/doc/source/cdef.rst index d5d5b34d..ca0d241a 100644 --- a/doc/source/cdef.rst +++ b/doc/source/cdef.rst @@ -667,6 +667,12 @@ write). If you choose, you can include this .py file pre-packaged in your own distributions: it is identical for any Python version (2 or 3). +*New in version 1.17.1:* ``filename`` can instead be a file-like object +(such as a StringIO instance). The generated code will be written to this +file-like object. However, if an error arises during generation, partial +code may be written; it is the caller's responsibility to clean up +if this occurs. + **ffibuilder.emit_c_code(filename):** generate the given .c file (for API mode) without compiling it. Can be used if you have some other method to compile it, e.g. if you want to integrate with some larger build @@ -676,6 +682,12 @@ platform, the .c file itself is generic (it would be exactly the same if produced on a different OS, with a different version of CPython, or with PyPy; it is done with generating the appropriate ``#ifdef``). +*New in version 1.17.1:* ``filename`` can instead be a file-like object +(such as a StringIO instance). The generated code will be written to this +file-like object. However, if an error arises during generation, partial +code may be written; it is the caller's responsibility to clean up +if this occurs. + **ffibuilder.distutils_extension(tmpdir='build', verbose=True):** for distutils-based ``setup.py`` files. Calling this creates the .c file if needed in the given ``tmpdir``, and returns a diff --git a/src/cffi/recompiler.py b/src/cffi/recompiler.py index ac6c163e..0af0ef36 100644 --- a/src/cffi/recompiler.py +++ b/src/cffi/recompiler.py @@ -1419,6 +1419,10 @@ def write(self, s): s = s.encode('ascii') super(NativeIO, self).write(s) +def _is_file_like(maybefile): + # compare to xml.etree.ElementTree._get_writer + return hasattr(maybefile, 'write') + def _make_c_or_py_source(ffi, module_name, preamble, target_file, verbose): if verbose: print("generating %s" % (target_file,)) @@ -1426,6 +1430,9 @@ def _make_c_or_py_source(ffi, module_name, preamble, target_file, verbose): target_is_python=(preamble is None)) recompiler.collect_type_table() recompiler.collect_step_tables() + if _is_file_like(target_file): + recompiler.write_source_to_f(target_file, preamble) + return True f = NativeIO() recompiler.write_source_to_f(f, preamble) output = f.getvalue() @@ -1525,6 +1532,9 @@ def recompile(ffi, module_name, preamble, tmpdir='.', call_c_compiler=True, if ffi._windows_unicode: ffi._apply_windows_unicode(kwds) if preamble is not None: + if call_c_compiler and _is_file_like(c_file): + raise TypeError("Writing to file-like objects is not supported " + "with call_c_compiler=True") embedding = (ffi._embedding is not None) if embedding: ffi._apply_embedding_fix(kwds) diff --git a/testing/cffi1/test_new_ffi_1.py b/testing/cffi1/test_new_ffi_1.py index 49cf0c2e..bc65c523 100644 --- a/testing/cffi1/test_new_ffi_1.py +++ b/testing/cffi1/test_new_ffi_1.py @@ -4,7 +4,7 @@ import cffi from testing.udir import udir from testing.support import * -from cffi.recompiler import recompile +from cffi.recompiler import recompile, NativeIO from cffi.cffi_opcode import PRIMITIVE_TO_INDEX SIZE_OF_INT = ctypes.sizeof(ctypes.c_int) @@ -1776,6 +1776,13 @@ def test_emit_c_code(self): ffi.emit_c_code(c_file) assert os.path.isfile(c_file) + def test_emit_c_code_to_file_obj(self): + ffi = cffi.FFI() + ffi.set_source("foobar", "??") + fileobj = NativeIO() + ffi.emit_c_code(fileobj) + assert 'foobar' in fileobj.getvalue() + def test_import_from_lib(self): ffi2 = cffi.FFI() ffi2.cdef("int myfunc(int); extern int myvar;\n#define MYFOO ...\n")