Author: Armin Rigo <[email protected]>
Branch: static-callback-embedding
Changeset: r2573:e982f7a7b2f4
Date: 2016-01-13 11:44 +0100
http://bitbucket.org/cffi/cffi/changeset/e982f7a7b2f4/
Log: refactor details, start writing docs
diff --git a/cffi/api.py b/cffi/api.py
--- a/cffi/api.py
+++ b/cffi/api.py
@@ -620,26 +620,23 @@
recompile(self, module_name, source,
c_file=filename, call_c_compiler=False, **kwds)
- def compile(self, tmpdir='.', verbose=0, ext=None):
- """Values recognized for the ext parameter:
+ def compile(self, tmpdir='.', verbose=0, target=None):
+ """The 'target' argument gives the final file name of the
+ compiled DLL. Use '*' to force distutils' choice, suitable for
+ regular CPython C API modules. Use a file name ending in '.*'
+ to ask for the system's default extension for dynamic libraries
+ (.so/.dll).
- - 'capi': use distutils' default to build CPython C API extensions
- - 'system': use the system's default for dynamic libraries
(.so/.dll)
- - '.FOO': exactly .FOO
-
- The default is 'capi' when building a non-embedded C API extension,
- and 'system' when building an embedded library.
+ The default is '*' when building a non-embedded C API extension,
+ and (module_name + '.*') when building an embedded library.
"""
from .recompiler import recompile
#
if not hasattr(self, '_assigned_source'):
raise ValueError("set_source() must be called before compile()")
- if ext not in (None, 'capi', 'system') and '.' not in ext:
- raise ValueError("bad value for 'ext' argument: %r" % (ext,))
module_name, source, source_extension, kwds = self._assigned_source
return recompile(self, module_name, source, tmpdir=tmpdir,
- target_extention=ext,
- source_extension=source_extension,
+ target=target, source_extension=source_extension,
compiler_verbose=verbose, **kwds)
def init_once(self, func, tag):
diff --git a/cffi/ffiplatform.py b/cffi/ffiplatform.py
--- a/cffi/ffiplatform.py
+++ b/cffi/ffiplatform.py
@@ -21,14 +21,14 @@
allsources.append(os.path.normpath(src))
return Extension(name=modname, sources=allsources, **kwds)
-def compile(tmpdir, ext, compiler_verbose=0, target_extention=None,
+def compile(tmpdir, ext, compiler_verbose=0, target_extension=None,
embedding=False):
"""Compile a C extension module using distutils."""
saved_environ = os.environ.copy()
try:
outputfilename = _build(tmpdir, ext, compiler_verbose,
- target_extention, embedding)
+ target_extension, embedding)
outputfilename = os.path.abspath(outputfilename)
finally:
# workaround for a distutils bugs where some env vars can
@@ -62,7 +62,7 @@
MSVCCompiler._remove_visual_c_ref = \
MSVCCompiler._remove_visual_c_ref_CFFI_BAK
-def _build(tmpdir, ext, compiler_verbose=0, target_extention=None,
+def _build(tmpdir, ext, compiler_verbose=0, target_extension=None,
embedding=False):
# XXX compact but horrible :-(
from distutils.core import Distribution
@@ -82,21 +82,9 @@
old_SO = _save_val('SO')
old_EXT_SUFFIX = _save_val('EXT_SUFFIX')
try:
- if target_extention is None:
- if embedding:
- target_extention = 'system'
- else:
- target_extention = 'capi'
- if target_extention == 'capi':
- pass # keep the values already in 'SO' and 'EXT_SUFFIX'
- else:
- if target_extention == 'system':
- if sys.platform == 'win32':
- target_extention = '.dll'
- else:
- target_extention = '.so'
- _restore_val('SO', target_extention)
- _restore_val('EXT_SUFFIX', target_extention)
+ if target_extension is not None:
+ _restore_val('SO', target_extension)
+ _restore_val('EXT_SUFFIX', target_extension)
distutils.log.set_verbosity(compiler_verbose)
dist.run_command('build_ext')
cmd_obj = dist.get_command_obj('build_ext')
diff --git a/cffi/recompiler.py b/cffi/recompiler.py
--- a/cffi/recompiler.py
+++ b/cffi/recompiler.py
@@ -1359,7 +1359,7 @@
def recompile(ffi, module_name, preamble, tmpdir='.', call_c_compiler=True,
c_file=None, source_extension='.c', extradir=None,
- compiler_verbose=1, target_extention=None, **kwds):
+ compiler_verbose=1, target=None, **kwds):
if not isinstance(module_name, str):
module_name = module_name.encode('ascii')
if ffi._windows_unicode:
@@ -1376,14 +1376,39 @@
ext_c_file = os.path.join(*parts)
else:
ext_c_file = c_file
- ext = ffiplatform.get_extension(ext_c_file, module_name, **kwds)
+ #
+ if target is None:
+ if embedding:
+ target = '%s.*' % module_name
+ else:
+ target = '*'
+ if target == '*':
+ target_module_name = module_name
+ target_extension = None # use default
+ else:
+ if target.endswith('.*'):
+ target = target[:-2]
+ if sys.platform == 'win32':
+ target += '.dll'
+ else:
+ target += '.so'
+ # split along the first '.' (not the last one, otherwise the
+ # preceeding dots are interpreted as splitting package names)
+ index = target.find('.')
+ if index < 0:
+ raise ValueError("target argument %r should be a file name "
+ "containing a '.'" % (target,))
+ target_module_name = target[:index]
+ target_extension = target[index:]
+ #
+ ext = ffiplatform.get_extension(ext_c_file, target_module_name, **kwds)
updated = make_c_source(ffi, module_name, preamble, c_file)
if call_c_compiler:
cwd = os.getcwd()
try:
os.chdir(tmpdir)
outputfilename = ffiplatform.compile('.', ext,
compiler_verbose,
- target_extention,
+ target_extension,
embedding=embedding)
finally:
os.chdir(cwd)
diff --git a/doc/source/embedding.rst b/doc/source/embedding.rst
new file mode 100644
--- /dev/null
+++ b/doc/source/embedding.rst
@@ -0,0 +1,52 @@
+================================
+Using CFFI for embedding
+================================
+
+.. contents::
+
+From *version 1.5,* you can use CFFI to generate a ``.so/.dll`` which
+is no longer usable only from Python, but which exports the API of
+your choice to any C application that wants to link with this
+``.so/.dll``.
+
+
+Usage
+-----
+
+See the `paragraph in the overview page`__ for a quick introduction.
+We decompose and explain every step below. We will call *DLL* the
+dynamically-loaded library that we are producing; it is a file with
+the (default) extension ``.dll`` on Windows or ``.so`` on other
+platforms. As usual, it is produced by generating some intermediate
+``.c`` code and then calling the regular platform-specific C compiler.
+
+.. __: overview.html#embedding
+
+* **ffi.embedding_api(source):** parses the given C source, which
+ declares functions that you want to be exported by the DLL. It can
+ also declare types, constants and global variables that are part of
+ the C-level API of your DLL.
+
+ The functions are automatically produced in the ``.c`` file: they
+ contain code that initializes the Python interpreter the first time
+ any of them is called, followed by code to call the associated
+ Python function (see next point).
+
+ The global variables, on the other hand, are not automatically
+ produced; you have to write their definition explicitly in
+ ``ffi.set_source()``, as regular C code. (The C code, as usual, can
+ include an initializer, or define the missing length for ``int
+ glob[];``, for example).
+
+* **ffi.embedding_init_code(python_code):** this stores the given
+ Python source code inside the DLL. This code will be executed at
+ runtime when the DLL is first initialized, just after Python itself
+ is initialized. This Python interpreter runs with the DLL ready
+ to be imported as a xxxxxxxxxxxxxx
+
+
+ It should typically attach a Python function to each
+ of the C functions declared in ``embedding_api()``. It does this
+ by importing the ``ffi`` object from the
+
+ with ``@ffi.def_extern()``.
diff --git a/doc/source/index.rst b/doc/source/index.rst
--- a/doc/source/index.rst
+++ b/doc/source/index.rst
@@ -18,6 +18,7 @@
overview
using
cdef
+ embedding
Goals
diff --git a/doc/source/overview.rst b/doc/source/overview.rst
--- a/doc/source/overview.rst
+++ b/doc/source/overview.rst
@@ -287,6 +287,54 @@
distributed in precompiled form like any other extension module.*
+.. _embedding:
+
+Embedding
+---------
+
+*New in version 1.5.*
+
+CFFI can also be used for embedding__, by creating a standard
+dynamically-linked library (``.dll`` under Windows, ``.so``
+elsewhere). This DLL can then be used from any C application.
+
+.. code-block:: python
+
+ import cffi
+ ffi = cffi.FFI()
+
+ ffi.embedding_api("""
+ int do_stuff(int, int);
+ """)
+
+ ffi.set_source("mystuff", "")
+
+ ffi.embedding_init_code("""
+ from mystuff import ffi
+
+ @ffi.def_extern()
+ def do_stuff(x, y):
+ print("adding %d and %d" % (x, y))
+ return x + y
+ """)
+
+ ffi.compile(verbose=True)
+
+This simple example creates ``mystuff.dll`` or ``mystuff.so`` as a DLL
+with a single exported function, ``do_stuff()``. You execute the
+script above once, with the interpreter you want to have internally
+used; it can be CPython 2.x or 3.x or PyPy. This DLL can then be used
+"as usual" from an application; the application doesn't need to know
+that it is talking with a library made with Python and CFFI. At
+runtime, when the application calls ``int do_stuff(int, int)``, the
+Python interpreter is automatically initialized and ``def do_stuff(x,
+y):`` gets called. `See the details in the documentation about
+embedding.`__
+
+.. __: embedding.html
+.. __: embedding.html
+
+
What actually happened?
-----------------------
diff --git a/testing/cffi1/test_zdist.py b/testing/cffi1/test_zdist.py
--- a/testing/cffi1/test_zdist.py
+++ b/testing/cffi1/test_zdist.py
@@ -59,11 +59,16 @@
if (name.endswith('.so') or name.endswith('.pyd') or
name.endswith('.dylib')):
found_so = os.path.join(curdir, name)
- # foo.cpython-34m.so => foo
- name = name.split('.')[0]
- # foo_d.so => foo (Python 2 debug builds)
+ # foo.so => foo
+ parts = name.split('.')
+ del parts[-1]
+ if len(parts) > 1 and parts[-1] != 'bar':
+ # foo.cpython-34m.so => foo, but foo.bar.so => foo.bar
+ del parts[-1]
+ name = '.'.join(parts)
+ # foo_d => foo (Python 2 debug builds)
if name.endswith('_d') and hasattr(sys, 'gettotalrefcount'):
- name = name.rsplit('_', 1)[0]
+ name = name[:-2]
name += '.SO'
if name.startswith('pycparser') and name.endswith('.egg'):
continue # no clue why this shows up sometimes and not
others
@@ -208,6 +213,58 @@
'Release': '?'}})
@chdir_to_tmp
+ def test_api_compile_explicit_target_1(self):
+ ffi = cffi.FFI()
+ ffi.set_source("mod_name_in_package.mymod", "/*code would be here*/")
+ x = ffi.compile(target="foo.bar.*")
+ if sys.platform != 'win32':
+ sofile = self.check_produced_files({
+ 'foo.bar.SO': None,
+ 'mod_name_in_package': {'mymod.c': None,
+ 'mymod.o': None}})
+ assert os.path.isabs(x) and os.path.samefile(x, sofile)
+ else:
+ self.check_produced_files({
+ 'foo.bar.SO': None,
+ 'mod_name_in_package': {'mymod.c': None},
+ 'Release': '?'})
+
+ @chdir_to_tmp
+ def test_api_compile_explicit_target_2(self):
+ ffi = cffi.FFI()
+ ffi.set_source("mod_name_in_package.mymod", "/*code would be here*/")
+ x = ffi.compile(target=os.path.join("mod_name_in_package",
"foo.bar.*"))
+ if sys.platform != 'win32':
+ sofile = self.check_produced_files({
+ 'mod_name_in_package': {'foo.bar.SO': None,
+ 'mymod.c': None,
+ 'mymod.o': None}})
+ assert os.path.isabs(x) and os.path.samefile(x, sofile)
+ else:
+ self.check_produced_files({
+ 'mod_name_in_package': {'foo.bar.SO': None,
+ 'mymod.c': None},
+ 'Release': '?'})
+
+ @chdir_to_tmp
+ def test_api_compile_explicit_target_3(self):
+ ffi = cffi.FFI()
+ ffi.set_source("mod_name_in_package.mymod", "/*code would be here*/")
+ x = ffi.compile(target="foo.bar.baz")
+ if sys.platform != 'win32':
+ self.check_produced_files({
+ 'foo.bar.baz': None,
+ 'mod_name_in_package': {'mymod.c': None,
+ 'mymod.o': None}})
+ sofile = os.path.join(str(self.udir), 'foo.bar.baz')
+ assert os.path.isabs(x) and os.path.samefile(x, sofile)
+ else:
+ self.check_produced_files({
+ 'foo.bar.baz': None,
+ 'mod_name_in_package': {'mymod.c': None},
+ 'Release': '?'})
+
+ @chdir_to_tmp
def test_api_distutils_extension_1(self):
ffi = cffi.FFI()
ffi.set_source("mod_name_in_package.mymod", "/*code would be here*/")
_______________________________________________
pypy-commit mailing list
[email protected]
https://mail.python.org/mailman/listinfo/pypy-commit