PSARC/2014/110 CFFI: foreign function interface for Python calling C code
authorDanek Duvall <danek.duvall@oracle.com>
Tue, 22 Apr 2014 11:18:42 -0700
changeset 1846 df40919e04fa
parent 1845 caa6e0a2a2e4
child 1847 b43426a2f6ba
PSARC/2014/110 CFFI: foreign function interface for Python calling C code 18468609 integrate cffi
components/python/cffi/Makefile
components/python/cffi/cffi-PYVER.p5m
components/python/cffi/patches/alloca.patch
components/python/cffi/patches/anonymous.patch
components/python/cffi/patches/test.patch
components/python/cffi/resolve.deps
make-rules/setup.py.mk
tools/python/pkglint/userland.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/components/python/cffi/Makefile	Tue Apr 22 11:18:42 2014 -0700
@@ -0,0 +1,86 @@
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License (the "License").
+# You may not use this file except in compliance with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or http://www.opensolaris.org/os/licensing.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+
+#
+# Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
+#
+
+include ../../../make-rules/shared-macros.mk
+
+COMPONENT_NAME=		cffi
+COMPONENT_VERSION=	0.8.2
+COMPONENT_SRC=		$(COMPONENT_NAME)-$(COMPONENT_VERSION)
+COMPONENT_ARCHIVE=	$(COMPONENT_SRC).tar.gz
+COMPONENT_ARCHIVE_HASH=	\
+    sha256:8192393640f7bc304ce82669b35eb90592566a30abbb4924456f52079afc18e2
+COMPONENT_ARCHIVE_URL=	$(call pypi_url)
+COMPONENT_PROJECT_URL=	http://cffi.readthedocs.org/
+COMPONENT_BUGDB=	python-mod/cffi
+
+include $(WS_TOP)/make-rules/prep.mk
+include $(WS_TOP)/make-rules/setup.py.mk
+include $(WS_TOP)/make-rules/ips.mk
+
+ASLR_MODE = $(ASLR_NOT_APPLICABLE)
+
+# We need to go look at pycparser in the workspace until it's available in the
+# build environment.
+PYCPARSER = $(WS_COMPONENTS)/python/pycparser/build/prototype/$(MACH)/$(PYTHON_LIB)
+
+$(PYCPARSER):
+	(cd ../pycparser; $(MAKE) install)
+
+COMPONENT_TEST_DIR =	$(@D)/tests
+COMPONENT_TEST_ENV =	$(PYTHON_ENV)
+COMPONENT_TEST_ENV +=	PYTHONPATH=$(PROTO_DIR)/$(PYTHON_LIB):$(PYCPARSER)
+COMPONENT_TEST_ENV +=	TESTOWNLIB_CC="$(CC) %s $(CC_BITS) -G -KPIC -o %s"
+COMPONENT_TEST_CMD =	$(PYTHON.$(BITS)) /usr/bin/py.test-$(PYTHON_VERSION)
+COMPONENT_TEST_ARGS =	--resultlog $(@D)/testresults
+COMPONENT_TEST_ARGS +=	-p no:codechecker
+COMPONENT_TEST_ARGS +=	$(TEST_SKIPS)
+COMPONENT_TEST_ARGS +=	c testing
+# The following tests cause core dumps on SPARC (bus error, illegal hardware
+# instruction).  We don't skip test_install_and_reload_module{,_package}, which
+# fail on both 64-bit platforms, since those don't cause the interpreter to
+# crash.
+$(BUILD_DIR)/sparcv7-%/.tested: TEST_SKIPS = -k "not test_callback_return_type and not test_struct_packed and not test_opaque_integer_as_function_result"
+$(BUILD_DIR)/sparcv9-%/.tested: TEST_SKIPS = -k "not test_callback and not test_a_lot_of_callbacks and not test_struct_packed and not test_opaque_integer_as_function_result and not test_wchar"
+
+# Copy the tests and a few of the source files they read into a scratch
+# directory so running the tests don't leave turds in the source directory.
+COMPONENT_PRE_TEST_ACTION = \
+	$(MKDIR) $(@D)/tests/doc/source; \
+	$(CP) -r $(SOURCE_DIR)/c $(SOURCE_DIR)/testing $(@D)/tests; \
+	$(CP) $(SOURCE_DIR)/setup.py $(@D)/tests; \
+	$(CP) $(SOURCE_DIR)/doc/source/conf.py $(SOURCE_DIR)/doc/source/index.rst $(@D)/tests/doc/source
+
+$(BUILD_32_and_64): $(PYCPARSER)
+
+# common targets
+build:		$(BUILD_32_and_64)
+
+install:	$(INSTALL_32_and_64)
+
+test:		$(TEST_32_and_64)
+
+BUILD_PKG_DEPENDENCIES =	$(BUILD_TOOLS)
+
+include $(WS_TOP)/make-rules/depend.mk
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/components/python/cffi/cffi-PYVER.p5m	Tue Apr 22 11:18:42 2014 -0700
@@ -0,0 +1,76 @@
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License (the "License").
+# You may not use this file except in compliance with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or http://www.opensolaris.org/os/licensing.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+
+#
+# Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
+#
+
+set name=pkg.fmri \
+    value=pkg:/library/python/cffi-$(PYV)@$(IPS_COMPONENT_VERSION),$(BUILD_VERSION)
+set name=pkg.summary \
+    value="Foreign function interface for Python calling C code"
+set name=pkg.description \
+    value="CFFI provides a convenient and reliable way of calling C code from Python.  It does not require learning a new language or an extensive API, and tries to minimize the amount of C code you have to write.  It works at both an ABI level, allowing you to reference symbols in libraries as well as at an API level, allowing you to embed C code in your Python program."
+set name=com.oracle.info.description value="the cffi Python module"
+set name=com.oracle.info.tpno value=16913
+set name=info.classification \
+    value=org.opensolaris.category.2008:Development/Python \
+    value=org.opensolaris.category.2008:Development/C
+set name=info.source-url value=$(COMPONENT_ARCHIVE_URL)
+set name=info.upstream [email protected]
+set name=info.upstream-url value=$(COMPONENT_PROJECT_URL)
+set name=org.opensolaris.arc-caseid value=PSARC/2014/110
+set name=org.opensolaris.consolidation value=$(CONSOLIDATION)
+#
+file path=usr/lib/python$(PYVER)/vendor-packages/64/_cffi_backend.so
+file path=usr/lib/python$(PYVER)/vendor-packages/_cffi_backend.so
+file path=usr/lib/python$(PYVER)/vendor-packages/cffi-$(COMPONENT_VERSION)-py$(PYVER).egg-info/PKG-INFO
+file path=usr/lib/python$(PYVER)/vendor-packages/cffi-$(COMPONENT_VERSION)-py$(PYVER).egg-info/SOURCES.txt
+file path=usr/lib/python$(PYVER)/vendor-packages/cffi-$(COMPONENT_VERSION)-py$(PYVER).egg-info/dependency_links.txt
+file path=usr/lib/python$(PYVER)/vendor-packages/cffi-$(COMPONENT_VERSION)-py$(PYVER).egg-info/not-zip-safe
+file path=usr/lib/python$(PYVER)/vendor-packages/cffi-$(COMPONENT_VERSION)-py$(PYVER).egg-info/requires.txt
+file path=usr/lib/python$(PYVER)/vendor-packages/cffi-$(COMPONENT_VERSION)-py$(PYVER).egg-info/top_level.txt
+file path=usr/lib/python$(PYVER)/vendor-packages/cffi/__init__.py
+file path=usr/lib/python$(PYVER)/vendor-packages/cffi/api.py
+file path=usr/lib/python$(PYVER)/vendor-packages/cffi/backend_ctypes.py
+file path=usr/lib/python$(PYVER)/vendor-packages/cffi/commontypes.py
+file path=usr/lib/python$(PYVER)/vendor-packages/cffi/cparser.py
+file path=usr/lib/python$(PYVER)/vendor-packages/cffi/ffiplatform.py
+file path=usr/lib/python$(PYVER)/vendor-packages/cffi/gc_weakref.py
+file path=usr/lib/python$(PYVER)/vendor-packages/cffi/lock.py
+file path=usr/lib/python$(PYVER)/vendor-packages/cffi/model.py
+file path=usr/lib/python$(PYVER)/vendor-packages/cffi/vengine_cpy.py
+file path=usr/lib/python$(PYVER)/vendor-packages/cffi/vengine_gen.py
+file path=usr/lib/python$(PYVER)/vendor-packages/cffi/verifier.py
+#
+license LICENSE license=MIT
+#
+# force a dependency on the Python runtime
+depend type=require fmri=__TBD pkg.debug.depend.file=python$(PYVER) \
+    pkg.debug.depend.path=usr/bin
+
+# force a dependency on the cffi package
+depend type=require \
+    fmri=library/python/[email protected]$(IPS_COMPONENT_VERSION),$(BUILD_VERSION)
+
+# force a dependency on pycparser; pkgdepend work is needed to flush this
+# out.
+depend type=require fmri=library/python/pycparser-$(PYV)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/components/python/cffi/patches/alloca.patch	Tue Apr 22 11:18:42 2014 -0700
@@ -0,0 +1,22 @@
+Need to include alloca.h when using alloca().  This is filed upstream as:
+
+    https://bitbucket.org/cffi/cffi/issue/144/fix-for-issue-128-is-incomplete
+
+and fixed in
+
+    https://bitbucket.org/cffi/cffi/commits/41b3ef920695
+
+diff --git a/cffi/vengine_cpy.py b/cffi/vengine_cpy.py
+--- a/cffi/vengine_cpy.py
++++ b/cffi/vengine_cpy.py
[email protected]@ -771,6 +771,10 @@
+ typedef unsigned __int32 uint32_t;
+ typedef unsigned __int64 uint64_t;
+ typedef unsigned char _Bool;
++#else
++#if (defined (__SVR4) && defined (__sun)) || defined(_AIX)
++#  include <alloca.h>
++#endif
+ #endif
+ 
+ #if PY_MAJOR_VERSION < 3
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/components/python/cffi/patches/anonymous.patch	Tue Apr 22 11:18:42 2014 -0700
@@ -0,0 +1,96 @@
+# HG changeset patch
+# User Armin Rigo <[email protected]>
+# Date 1396624834 -7200
+# Node ID facaf5de29c9e8cdaa148112ece17a4205cf2d84
+# Parent  0457fbb2d45243a93cb8ed242646d7487bd36dc4
+Issue #142: don't generate C files that use '$' in identifiers.
+
+diff --git a/cffi/vengine_cpy.py b/cffi/vengine_cpy.py
+--- a/cffi/vengine_cpy.py
++++ b/cffi/vengine_cpy.py
[email protected]@ -632,13 +632,18 @@
+     # ----------
+     # enums
+ 
++    def _enum_funcname(self, prefix, name):
++        # "$enum_$1" => "___D_enum____D_1"
++        name = name.replace('$', '___D_')
++        return '_cffi_e_%s_%s' % (prefix, name)
++
+     def _generate_cpy_enum_decl(self, tp, name, prefix='enum'):
+         if tp.partial:
+             for enumerator in tp.enumerators:
+                 self._generate_cpy_const(True, enumerator, delayed=False)
+             return
+         #
+-        funcname = '_cffi_e_%s_%s' % (prefix, name)
++        funcname = self._enum_funcname(prefix, name)
+         prnt = self._prnt
+         prnt('static int %s(PyObject *lib)' % funcname)
+         prnt('{')
+diff --git a/cffi/vengine_gen.py b/cffi/vengine_gen.py
+--- a/cffi/vengine_gen.py
++++ b/cffi/vengine_gen.py
[email protected]@ -410,13 +410,18 @@
+     # ----------
+     # enums
+ 
++    def _enum_funcname(self, prefix, name):
++        # "$enum_$1" => "___D_enum____D_1"
++        name = name.replace('$', '___D_')
++        return '_cffi_e_%s_%s' % (prefix, name)
++
+     def _generate_gen_enum_decl(self, tp, name, prefix='enum'):
+         if tp.partial:
+             for enumerator in tp.enumerators:
+                 self._generate_gen_const(True, enumerator)
+             return
+         #
+-        funcname = '_cffi_e_%s_%s' % (prefix, name)
++        funcname = self._enum_funcname(prefix, name)
+         self.export_symbols.append(funcname)
+         prnt = self._prnt
+         prnt('int %s(char *out_error)' % funcname)
[email protected]@ -453,7 +458,7 @@
+         else:
+             BType = self.ffi._typeof_locked("char[]")[0]
+             BFunc = self.ffi._typeof_locked("int(*)(char*)")[0]
+-            funcname = '_cffi_e_%s_%s' % (prefix, name)
++            funcname = self._enum_funcname(prefix, name)
+             function = module.load_function(BFunc, funcname)
+             p = self.ffi.new(BType, 256)
+             if function(p) < 0:
+diff --git a/testing/test_verify.py b/testing/test_verify.py
+--- a/testing/test_verify.py
++++ b/testing/test_verify.py
[email protected]@ -1,4 +1,4 @@
+-import py
++import py, re
+ import sys, os, math, weakref
+ from cffi import FFI, VerificationError, VerificationMissing, model
+ from testing.support import *
[email protected]@ -29,6 +29,24 @@
+ def setup_module():
+     import cffi.verifier
+     cffi.verifier.cleanup_tmpdir()
++    #
++    # check that no $ sign is produced in the C file; it used to be the
++    # case that anonymous enums would produce '$enum_$1', which was
++    # used as part of a function name.  GCC accepts such names, but it's
++    # apparently non-standard.
++    _r_comment = re.compile(r"/\*.*?\*/|//.*?$", re.DOTALL | re.MULTILINE)
++    _r_string = re.compile(r'\".*?\"')
++    def _write_source_and_check(self, file=None):
++        base_write_source(self, file)
++        if file is None:
++            f = open(self.sourcefilename)
++            data = f.read()
++            f.close()
++            data = _r_comment.sub(' ', data)
++            data = _r_string.sub('"skipped"', data)
++            assert '$' not in data
++    base_write_source = cffi.verifier.Verifier._write_source
++    cffi.verifier.Verifier._write_source = _write_source_and_check
+ 
+ 
+ def test_module_type():
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/components/python/cffi/patches/test.patch	Tue Apr 22 11:18:42 2014 -0700
@@ -0,0 +1,157 @@
+The testsuite makes some incorrect assumptions:
+
+  - we need to not assume "a standard gcc", so replace the -Werror flag
+    with -errwarn to turn on the warnings are expected to cause compilation
+    to fail.
+
+  - don't use the -pthread flag for compilation; as Studio doesn't have any
+    clue what to do with it, and we don't need it on Solaris, anyway.
+
+  - don't force the use of gcc when compiling a test shared object; get
+    that passed in from the makefile.
+
+  - don't assume that stdin/stdout/stderr are changeable:
+
+        https://bitbucket.org/cffi/cffi/issue/145/solaris-stdout-and-stderr-not-in-libc-not
+
+    fixed in
+
+        https://bitbucket.org/cffi/cffi/commits/237031079adc
+
+  - don't assume that enums can be unsigned or are the same size as long:
+
+    https://bitbucket.org/cffi/cffi/issue/143/test_enum_size-makes-invalid-assumptions
+
+Also fix a problem with some assignments to void*:
+
+    https://bitbucket.org/cffi/cffi/issue/146/incorrect-type-for-c_callback-variable
+
+fixed in
+
+    https://bitbucket.org/cffi/cffi/commits/51d87933eb4b
+
+--- cffi-0.8.2/testing/test_verify.py	Thu Mar  6 22:51:56 2014
++++ cffi-0.8.2/testing/test_verify.py	Thu Mar 20 18:39:01 2014
[email protected]@ -18,8 +18,10 @@
+         extra_compile_args = [
+             '-Werror', '-Qunused-arguments', '-Wno-error=shorten-64-to-32']
+     else:
+-        # assume a standard gcc
+-        extra_compile_args = ['-Werror']
++        extra_compile_args = [
++          '-errtags=yes',
++          '-errwarn=E_ASSIGNMENT_TYPE_MISMATCH,E_RETURN_VALUE_TYPE_MISMATCH'
++        ]
+ 
+     class FFI(FFI):
+         def verify(self, *args, **kwds):
+--- cffi-0.8.2/testing/callback_in_thread.py	Fri Mar 21 15:58:21 2014
++++ cffi-0.8.2/testing/callback_in_thread.py	Fri Mar 21 15:58:30 2014
[email protected]@ -22,7 +22,7 @@
+             pthread_create(&thread, NULL, my_wait_function, (void*)mycb);
+             return 0;
+         }
+-    """, extra_compile_args=['-pthread'])
++    """)
+     seen = []
+     @ffi.callback('int(*)(int,int)')
+     def mycallback(x, y):
+--- cffi-0.8.2/testing/test_ownlib.py	Tue Oct  9 02:10:04 2012
++++ cffi-0.8.2/testing/test_ownlib.py	Tue Mar 25 15:39:35 2014
[email protected]@ -1,4 +1,4 @@
+-import py, sys
++import os, py, sys
+ import subprocess, weakref
+ from cffi import FFI
+ from cffi.backend_ctypes import CTypesBackend
[email protected]@ -28,7 +28,7 @@
+         from testing.udir import udir
+         udir.join('testownlib.c').write(SOURCE)
+         subprocess.check_call(
+-            'gcc testownlib.c -shared -fPIC -o testownlib.so',
++            os.getenv('TESTOWNLIB_CC') % ('testownlib.c', 'testownlib.so'),
+             cwd=str(udir), shell=True)
+         cls.module = str(udir.join('testownlib.so'))
+ 
+--- cffi-0.8.2/c/test_c.py	Thu Mar  6 22:51:56 2014
++++ cffi-0.8.2/c/test_c.py	Mon Mar 24 14:53:27 2014
[email protected]@ -1102,7 +1102,7 @@
+ def test_read_variable():
+     ## FIXME: this test assumes glibc specific behavior, it's not compliant with C standard
+     ## https://bugs.pypy.org/issue1643
+-    if sys.platform == 'win32' or sys.platform == 'darwin' or sys.platform.startswith('freebsd'):
++    if not sys.platform.startswith("linux"):
+         py.test.skip("untested")
+     BVoidP = new_pointer_type(new_void_type())
+     ll = find_and_load_library('c')
[email protected]@ -1112,7 +1112,7 @@
+ def test_read_variable_as_unknown_length_array():
+     ## FIXME: this test assumes glibc specific behavior, it's not compliant with C standard
+     ## https://bugs.pypy.org/issue1643
+-    if sys.platform == 'win32' or sys.platform == 'darwin' or sys.platform.startswith('freebsd'):
++    if not sys.platform.startswith("linux"):
+         py.test.skip("untested")
+     BCharP = new_pointer_type(new_primitive_type("char"))
+     BArray = new_array_type(BCharP, None)
[email protected]@ -1124,7 +1124,7 @@
+ def test_write_variable():
+     ## FIXME: this test assumes glibc specific behavior, it's not compliant with C standard
+     ## https://bugs.pypy.org/issue1643
+-    if sys.platform == 'win32' or sys.platform == 'darwin' or sys.platform.startswith('freebsd'):
++    if not sys.platform.startswith("linux"):
+         py.test.skip("untested")
+     BVoidP = new_pointer_type(new_void_type())
+     ll = find_and_load_library('c')
+--- cffi-0.8.2/testing/test_verify.py	Thu Mar  6 22:51:56 2014
++++ cffi-0.8.2/testing/test_verify.py	Mon Mar 24 15:03:49 2014
[email protected]@ -1472,8 +1474,8 @@
+     assert func() == 42
+ 
+ def test_FILE_stored_in_stdout():
+-    if sys.platform == 'win32':
+-        py.test.skip("MSVC: cannot assign to stdout")
++    if not sys.platform.startswith("linux"):
++        py.test.skip("likely, we cannot assign to stdout")
+     ffi = FFI()
+     ffi.cdef("int printf(const char *, ...); FILE *setstdout(FILE *);")
+     lib = ffi.verify("""
+--- cffi-0.8.2/testing/test_verify.py	Thu Mar  6 22:51:56 2014
++++ cffi-0.8.2/testing/test_verify.py	Mon Mar 24 15:06:23 2014
[email protected]@ -1598,13 +1598,13 @@
+              ('-123',          4, -1),
+              ('-2147483647-1', 4, -1),
+              ]
+-    if FFI().sizeof("long") == 8:
++    if FFI().sizeof("long") == 8 and sys.platform != 'sunos5':
+         cases += [('4294967296L',        8, 2**64-1),
+                   ('%dUL' % (2**64-1),   8, 2**64-1),
+                   ('-2147483649L',       8, -1),
+                   ('%dL-1L' % (1-2**63), 8, -1)]
+     for hidden_value, expected_size, expected_minus1 in cases:
+-        if sys.platform == 'win32' and 'U' in hidden_value:
++        if sys.platform in ('win32', 'sunos5') and 'U' in hidden_value:
+             continue   # skipped on Windows
+         ffi = FFI()
+         ffi.cdef("enum foo_e { AA, BB, ... };")
[email protected]@ -1629,7 +1627,7 @@
+                         (maxulong, -1, ''),
+                         (-1, 0xffffffff, 'U'),
+                         (-1, maxulong, 'UL')]:
+-        if c2c and sys.platform == 'win32':
++        if c2c and sys.platform in ('win32', 'sunos5'):
+             continue     # enums may always be signed with MSVC
+         ffi = FFI()
+         ffi.cdef("enum foo_e { AA=%s };" % c1)
+--- a/testing/test_verify.py
++++ b/testing/test_verify.py
[email protected]@ -1656,8 +1656,8 @@
+     ffi = FFI()
+     ffi.cdef("""
+         int (*python_callback)(int how_many, int *values);
+-        void *const c_callback;   /* pass this ptr to C routines */
+-        int some_c_function(void *cb);
++        int (*const c_callback)(int,...);   /* pass this ptr to C routines */
++        int some_c_function(int(*cb)(int,...));
+     """)
+     lib = ffi.verify("""
+         #include <stdarg.h>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/components/python/cffi/resolve.deps	Tue Apr 22 11:18:42 2014 -0700
@@ -0,0 +1,4 @@
+library/libffi
+runtime/python-26
+runtime/python-27
+system/library
--- a/make-rules/setup.py.mk	Tue Apr 22 11:33:57 2014 -0700
+++ b/make-rules/setup.py.mk	Tue Apr 22 11:18:42 2014 -0700
@@ -44,6 +44,8 @@
 INSTALL_64 = $(PYTHON_VERSIONS:%=$(BUILD_DIR)/$(MACH64)-%/.installed)
 INSTALL_NO_ARCH = $(PYTHON_VERSIONS:%=$(BUILD_DIR)/$(MACH)-%/.installed)
 
+TEST_32 = $(PYTHON_VERSIONS:%=$(BUILD_DIR)/$(MACH32)-%/.tested)
+TEST_64 = $(PYTHON_VERSIONS:%=$(BUILD_DIR)/$(MACH64)-%/.tested)
 TEST_NO_ARCH = $(PYTHON_VERSIONS:%=$(BUILD_DIR)/$(MACH)-%/.tested)
 
 PYTHON_ENV =	CC="$(CC)"
--- a/tools/python/pkglint/userland.py	Tue Apr 22 11:33:57 2014 -0700
+++ b/tools/python/pkglint/userland.py	Tue Apr 22 11:18:42 2014 -0700
@@ -29,9 +29,12 @@
 import pkg.lint.base as base
 from pkg.lint.engine import lint_fmri_successor
 import pkg.elf as elf
+import pkg.fmri
+import platform
 import re
 import os.path
 import subprocess
+import sys
 
 class UserlandActionChecker(base.ActionChecker):
         """An opensolaris.org-specific class to check actions."""
@@ -46,7 +49,7 @@
 			self.proto_path = path.split()
 		else:
 			self.proto_path = None
-		solaris_ver = os.getenv('SOLARIS_VERSION')
+		solaris_ver = os.getenv('SOLARIS_VERSION', '')
 		#
 		# These lists are used to check if a 32/64-bit binary
 		# is in a proper 32/64-bit directory.
@@ -516,7 +519,7 @@
 			engine.error( _("missing ARC data (org.opensolaris.arc-caseid)"),
 				msgid="%s%s.0" % (self.name, pkglint_id))
 
-	component_check.pkglint_dest = _(
+	component_check.pkglint_desc = _(
 		"license actions and ARC information are required if you deliver files.")
 
         def publisher_in_fmri(self, manifest, engine, pkglint_id="002"):
@@ -529,3 +532,69 @@
                         engine.error(_("package %s has a publisher set!") %
                             manifest.fmri,
                             msgid="%s%s.2" % (self.name, pkglint_id))
+
+        publisher_in_fmri.pkglint_desc = _(
+            "Publishers mentioned in package FMRIs must be in fixed set.")
+
+        # CFFI names the modules it creates with a hash that includes the
+        # version of CFFI (since the schema may change from one version to
+        # another).  This means that if a package depends on CFFI, then it must
+        # also incorporate the version it builds with, which should be the
+        # version in the gate.
+        def uses_cffi(self, manifest, engine, pkglint_id="003"):
+                cffi_match = {"fmri": "*/cffi*"}
+                cffi_require = None
+                cffi_incorp = None
+                for action in manifest.gen_actions_by_type("depend"):
+                        if not any(
+                            f
+                            for f in action.attrlist("fmri")
+                            if "/cffi-" in pkg.fmri.PkgFmri(f).pkg_name):
+                                continue
+                        if action.attrs["type"] in ("require", "require-any"):
+                                cffi_require = action
+                        elif action.attrs["type"] == "incorporate":
+                                cffi_incorp = action
+
+                try:
+                        sys.path[0:0] = [os.path.join(os.getenv("WS_TOP", ""),
+                            "components/python/cffi/build/prototype/"
+                            "%s/usr/lib/python%d.%d/vendor-packages" %
+                            ((platform.processor(),) + sys.version_info[:2]))]
+                        import cffi
+                        cffi_version = cffi.__version__
+                        del sys.path[0]
+                except ImportError:
+                        cffi_version = None
+
+                if not cffi_require:
+                        return
+
+                if not cffi_version:
+                        engine.warning(_("package %s depends on CFFI, but we "
+                            "cannot determine the version of CFFI needed") %
+                            manifest.fmri,
+                            msgid="%s%s.1" % (self.name, pkglint_id))
+
+                if not cffi_incorp:
+                        engine.error(_("package %(pkg)s depends on CFFI, but "
+                            "does not incorporate it (should be at %(should)s)")
+                            % {"pkg": manifest.fmri, "should": cffi_version},
+                            msgid="%s%s.2" % (self.name, pkglint_id))
+
+                # The final check can only be done if neither of the previous
+                # checks have fired.
+                if not cffi_version or not cffi_incorp:
+                    return
+
+                cffi_incorp_ver = str(pkg.fmri.PkgFmri(
+                    cffi_incorp.attrs["fmri"]).version.release)
+                if cffi_incorp_ver != cffi_version:
+                        engine.error(_("package %(pkg)s depends on CFFI, but "
+                            "incorporates it at the wrong version (%(actual)s "
+                            "instead of %(should)s)") % {"pkg": manifest.fmri,
+                            "actual": cffi_incorp_ver, "should": cffi_version},
+                            msgid="%s%s.3" % (self.name, pkglint_id))
+
+        uses_cffi.pkglint_desc = _(
+            "Packages using CFFI incorporate CFFI at the correct version.")