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

Reply via email to