This is an automated email from the ASF dual-hosted git repository.

astitcher pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/qpid-proton.git


The following commit(s) were added to refs/heads/main by this push:
     new a2e9eb634 PROTON-2816/PROTON-2817: Support building python extension 
with unbundled proton
a2e9eb634 is described below

commit a2e9eb63444bbda70f8870e28d5ef93b68c0bbe5
Author: Andrew Stitcher <[email protected]>
AuthorDate: Fri Jan 12 16:03:38 2024 -0500

    PROTON-2816/PROTON-2817: Support building python extension with unbundled 
proton
    
    There are 3 cases:
    * Bundled proton library - this should be the default and is what you
      should use with pip etc.
    * System proton library - this is what you should use in a distro
      package.
    * Use the in tree library built earlier in the build process - this is
      what you usually want for developer debugging, and should be the
      version actually tested by the in tree tests.
    
    This change also add control of the ability to package and test in
    isolated virtual python environments. Using these is the default, but
    under some circumstances you do not want the build process to construct
    a virtual environment by pulling packages from the internet. So we now
    have an option to turn this behaviour off and rely on the hosts python
    environment.
---
 python/CMakeLists.txt                       | 87 +++++++++++++++++++++--------
 python/MANIFEST.in                          |  1 +
 python/{setup.py => ext_build_devtree.py}   | 24 +++++++-
 python/{setup.py => ext_build_unbundled.py} | 25 ++++++++-
 python/setup.py                             |  6 +-
 5 files changed, 113 insertions(+), 30 deletions(-)

diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt
index d67a96948..4f3ab4fa3 100644
--- a/python/CMakeLists.txt
+++ b/python/CMakeLists.txt
@@ -53,6 +53,8 @@ set(py_dist_files
     README.rst
     MANIFEST.in
     ext_build.py
+    ext_build_devtree.py
+    ext_build_unbundled.py
     cproton.h
     cproton_ext.c
     cproton.py
@@ -128,7 +130,6 @@ add_custom_command(OUTPUT .timestamp.copied_pysrc
                    COMMAND ${CMAKE_COMMAND} -E copy 
${PN_C_SOURCE_DIR}/core/frame_generators.h src/core
                    COMMAND ${CMAKE_COMMAND} -E copy 
${PN_C_SOURCE_DIR}/core/frame_consumers.c  src/core
                    COMMAND ${CMAKE_COMMAND} -E copy 
${PN_C_SOURCE_DIR}/core/frame_consumers.h  src/core
-                   COMMAND ${CMAKE_COMMAND} -E copy 
${PROJECT_SOURCE_DIR}/VERSION.txt            .
                    COMMAND ${CMAKE_COMMAND} -E touch .timestamp.copied_pysrc
                    DEPENDS generated_c_files ${py_cgen} ${py_csrc} ${py_cinc} 
${PROJECT_SOURCE_DIR}/VERSION.txt)
 
@@ -139,28 +140,55 @@ foreach(file IN LISTS py_dist_files pysrc)
   list(APPEND pysrc_files "${CMAKE_CURRENT_BINARY_DIR}/${file}")
 endforeach()
 
-add_custom_target(pysrc_copied ALL DEPENDS .timestamp.copied_pysrc 
${pysrc_files})
+add_custom_command(OUTPUT VERSION.txt
+                  COMMAND ${CMAKE_COMMAND} -E copy 
${PROJECT_SOURCE_DIR}/VERSION.txt .
+                  DEPENDS ${PROJECT_SOURCE_DIR}/VERSION.txt)
+
+add_custom_target(pysrc_copied DEPENDS ${pysrc_files} VERSION.txt)
+add_custom_target(pypkg_src_copied ALL DEPENDS pysrc_copied 
.timestamp.copied_pysrc)
 
 add_custom_command(OUTPUT ./tox.ini
                    COMMAND ${CMAKE_COMMAND} -E copy 
"${CMAKE_CURRENT_SOURCE_DIR}/tox.ini" tox.ini
                    DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/tox.ini")
 
+option(ENABLE_PYTHON_ISOLATED "Enable python building/testing with isolated 
environments." ON)
+option(BUILD_PYTHON_UNBUNDLED_PKG "Build Python package without bundling 
qpid-proton-core library" Off)
+
 # Make python source and binary packages if we have prerequisites
+check_python_module("build" BUILD_MODULE_FOUND)
+
+if (ENABLE_PYTHON_ISOLATED)
+  set (pypkgbuildoption "")
+else ()
+  set (pypkgbuildoption "-n")
+endif ()
+
 check_python_module("setuptools" SETUPTOOLS_MODULE_FOUND)
 check_python_module("wheel" WHEEL_MODULE_FOUND)
-check_python_module("build" BUILD_MODULE_FOUND)
 check_python_module("cffi" CFFI_MODULE_FOUND)
-if (BUILD_MODULE_FOUND AND SETUPTOOLS_MODULE_FOUND AND WHEEL_MODULE_FOUND AND 
CFFI_MODULE_FOUND)
-  add_custom_command(OUTPUT .timestamp.dist
-    DEPENDS pysrc_copied
-    COMMAND ${CMAKE_COMMAND} -E remove -f .timestamp.dist
-    COMMAND ${Python_EXECUTABLE} -m build -n
-    COMMAND ${CMAKE_COMMAND} -E touch .timestamp.dist)
+
+if (BUILD_MODULE_FOUND AND
+    (ENABLE_PYTHON_ISOLATED OR (SETUPTOOLS_MODULE_FOUND AND WHEEL_MODULE_FOUND 
AND CFFI_MODULE_FOUND)))
+  if (BUILD_PYTHON_UNBUNDLED_PKG)
+    add_custom_command(OUTPUT .timestamp.dist
+      DEPENDS pypkg_src_copied
+      COMMAND ${CMAKE_COMMAND} -E remove -f .timestamp.dist
+      COMMAND ${CMAKE_COMMAND} -E env
+        QPID_PYTHON_UNBUNDLING=unbundled
+        ${Python_EXECUTABLE} -m build ${pypkgbuildoption}
+      COMMAND ${CMAKE_COMMAND} -E touch .timestamp.dist)
+  else ()
+    add_custom_command(OUTPUT .timestamp.dist
+      DEPENDS pypkg_src_copied
+      COMMAND ${CMAKE_COMMAND} -E remove -f .timestamp.dist
+      COMMAND ${Python_EXECUTABLE} -m build ${pypkgbuildoption}
+      COMMAND ${CMAKE_COMMAND} -E touch .timestamp.dist)
+  endif ()
   add_custom_target(pydist ALL DEPENDS .timestamp.dist)
 endif ()
 
 if (BUILD_TESTING)
-  add_custom_target(pytest_cffi ALL DEPENDS .timestamp.cproton_ffi)
+  add_custom_target(pytest_cffi ALL DEPENDS .timestamp.test_env)
 
   # python test: python/tests/proton-test
   set (py_src "${CMAKE_CURRENT_SOURCE_DIR}")
@@ -179,8 +207,7 @@ if (BUILD_TESTING)
     set (python_coverage_options -m coverage run --parallel-mode)
   endif(CMAKE_BUILD_TYPE MATCHES "Coverage")
 
-  option(ENABLE_PYTHON_TEST_VENV "Enable python testing within a separate 
virtual environment." ON)
-  if (ENABLE_PYTHON_TEST_VENV)
+  if (ENABLE_PYTHON_ISOLATED)
     # Create Python virtual environment to run tests
     set(pytest_venv "${py_bin}/pytest_env")
     # Have to use a conditional here as you can't use generator expressions in 
OUTPUT or BYPRODUCTS
@@ -193,27 +220,41 @@ if (BUILD_TESTING)
     set(pytest_executable "${pytest_bin}/python${CMAKE_EXECUTABLE_SUFFIX}")
 
     add_custom_command(
-      OUTPUT ${pytest_venv}/env.txt
+      OUTPUT .timestamp.test_env
       COMMAND ${Python_EXECUTABLE} -m venv ${pytest_venv}
       COMMAND ${pytest_executable} -m pip install --disable-pip-version-check 
cffi setuptools
+      COMMAND ${CMAKE_COMMAND} -E env
+        "CMAKE_BINARY_DIR=${CMAKE_BINARY_DIR}"
+        "QPID_PROTON_CORE_TARGET_DIR=$<TARGET_FILE_DIR:qpid-proton-core>"
+        "QPID_PYTHON_UNBUNDLING=devtree"
+        ${pytest_executable} -m pip install -e .
       COMMAND ${pytest_executable} -m pip freeze > ${pytest_venv}/env.txt
+      COMMAND ${CMAKE_COMMAND} -E touch .timestamp.test_env
       BYPRODUCTS ${pytest_executable}
     )
-
   else()
     set(pytest_executable "${Python_EXECUTABLE}")
     set(pytest_venv "${py_bin}")
-    FILE(TOUCH "${pytest_venv}/env.txt")
+    add_custom_command(
+      OUTPUT .timestamp.test_env
+      COMMAND ${CMAKE_COMMAND} -E remove -f ${pytest_venv}/env.txt
+      COMMAND ${CMAKE_COMMAND} -E env
+        "CMAKE_BINARY_DIR=${CMAKE_BINARY_DIR}"
+        "QPID_PROTON_CORE_TARGET_DIR=$<TARGET_FILE_DIR:qpid-proton-core>"
+        ${pytest_executable} ext_build_devtree.py
+      COMMAND ${CMAKE_COMMAND} -E touch .timestamp.test_env
+      DEPENDS pysrc_copied qpid-proton-core
+    )
   endif()
 
-  # Create c code for cffi extension
-  add_custom_command(
-    OUTPUT .timestamp.cproton_ffi
-    COMMAND ${CMAKE_COMMAND} -E remove -f .timestamp.cproton_ffi
-    COMMAND ${pytest_executable} ext_build.py
-    COMMAND ${CMAKE_COMMAND} -E touch .timestamp.cproton_ffi
-    DEPENDS ${pytest_venv}/env.txt pysrc_copied
-  )
+  # If we are on windows copy the qpid-proton-core dll to the test directory 
so we can find it easily
+  if (WIN32)
+    add_custom_command(
+      OUTPUT .timestamp.test_env
+      APPEND
+      COMMAND ${CMAKE_COMMAND} -E copy "$<TARGET_FILE:qpid-proton-core>" .
+    )
+  endif()
 
   pn_add_test(
     INTERPRETED
diff --git a/python/MANIFEST.in b/python/MANIFEST.in
index 605fc85c5..2ca528cca 100644
--- a/python/MANIFEST.in
+++ b/python/MANIFEST.in
@@ -18,6 +18,7 @@
 
 include VERSION.txt
 include ext_build.py
+include ext_build_unbundled.py
 include cproton.h
 include cproton_ext.c
 include cproton.py
diff --git a/python/setup.py b/python/ext_build_devtree.py
similarity index 56%
copy from python/setup.py
copy to python/ext_build_devtree.py
index 51976ace9..8a0a66be2 100644
--- a/python/setup.py
+++ b/python/ext_build_devtree.py
@@ -15,9 +15,27 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
+#
+
+import os
 
-from setuptools import setup
+from cffi import FFI
 
-setup(
-    cffi_modules='ext_build.py:ffibuilder'
+bld_tree_top = os.environ.get("CMAKE_BINARY_DIR")
+bld_clibdir = os.environ.get("QPID_PROTON_CORE_TARGET_DIR")
+cdefs = open('cproton.h').read()
+c_code = open('cproton_ext.c').read()
+extra_link_args = [f"-Wl,-rpath,{bld_clibdir}"] if os.name == 'posix' else None
+ffibuilder = FFI()
+ffibuilder.cdef(cdefs)
+ffibuilder.set_source(
+    "cproton_ffi",
+    c_code,
+    include_dirs=[f"{bld_tree_top}/c/include"],
+    library_dirs=[f"{bld_clibdir}"],
+    libraries=["qpid-proton-core"],
+    extra_link_args=extra_link_args,
 )
+
+if __name__ == "__main__":
+    ffibuilder.compile(verbose=True)
diff --git a/python/setup.py b/python/ext_build_unbundled.py
similarity index 59%
copy from python/setup.py
copy to python/ext_build_unbundled.py
index 51976ace9..d1ea27d57 100644
--- a/python/setup.py
+++ b/python/ext_build_unbundled.py
@@ -15,9 +15,28 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
+#
+
+import cffi.pkgconfig
+
+from cffi import FFI
 
-from setuptools import setup
+ffibuilder = FFI()
 
-setup(
-    cffi_modules='ext_build.py:ffibuilder'
+# cdef() expects a single string declaring the C types, functions and
+# globals needed to use the shared object. It must be in valid C syntax
+# with cffi extensions
+cdefs = open('cproton.h').read()
+ffibuilder.cdef(cdefs)
+
+cffi.pkgconfig.flags_from_pkgconfig(['libqpid-proton-core'])
+
+c_code = open('cproton_ext.c').read()
+ffibuilder.set_source_pkgconfig(
+    "cproton_ffi",
+    ['libqpid-proton-core'],
+    c_code,
 )
+
+if __name__ == "__main__":
+    ffibuilder.compile(verbose=True)
diff --git a/python/setup.py b/python/setup.py
index 51976ace9..76d4db5df 100644
--- a/python/setup.py
+++ b/python/setup.py
@@ -16,8 +16,12 @@
 # specific language governing permissions and limitations
 # under the License.
 
+import os
 from setuptools import setup
 
+unbundling = os.environ.get("QPID_PYTHON_UNBUNDLING")
+unbundling = f'_{unbundling}' if unbundling else ''
+
 setup(
-    cffi_modules='ext_build.py:ffibuilder'
+    cffi_modules=f'ext_build{unbundling}.py:ffibuilder'
 )


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to