@@ 1,36 1,25 @@
import sys
+import pathlib
from setuptools import setup, Extension
-import sysconfig
+from setuptools.command.build_ext import build_ext
from Cython.Build import cythonize
-#### System configuration information
-# A value like '.cpython-39-x86_64-linux-gnu.so'
-# or '.cpython-39-darwin.so' or '.so'
-# Used to determine the shared library filename.
-EXT_SUFFIX = sysconfig.get_config_var("EXT_SUFFIX")
-EXT_INFIX = EXT_SUFFIX.rpartition(".")[0]
-
-# A value like 'linux-x86_64', 'macosx-10.14-x86_64', or
-# 'freebsd-12.2-RELEASE-p9-amd64'
-platform = sysconfig.get_platform()
-
-# The default location of the build directory.
-# (This assumes the directory was not specified on the command-line.)
-plat_specifier = "%s-%d.%d" % (platform, *sys.version_info[:2])
-
-# Figure out the platform-specific linker arguments so the new
-# extensions can access the C functions in the core library.
-if sys.platform == "darwin":
- libcore_link_args = []
-else:
- # The compiler needs the -L to find libcore in the build directory.
- # The run-time loader needs an '$ORIGIN' rpath to find libcore in the install directory.
- libcore_link_args = [f"-Lbuild/lib.{plat_specifier}/abcd", f"-labcd_core{EXT_INFIX}", "-Wl,-rpath,$ORIGIN"]
+# Version 1.1 of this package tried to figure out the extra_link_args
+# statically, before calling setup(), based on the platform name and
+# version. This ended up being non-portable. A conda-based Python used
+# the subdirectory "lib.linux-x86_64-cpython-39" where I expected
+# "lib.linux-x86_64-3.9".
+#
+# Version 1.2 switched to a "build_ext" extension which uses
+# get_ext_fullpath() to get the output library file and from
+# that determine the library path and name.
#### Extension configuration information
-
-# This starts with 'lib' so I can use the '-l' flag.
+
+# This module starts with 'lib' so I can use the '-l' flag in the
+# extra_link_args of the other modules.
+
module1 = Extension(
"abcd.libabcd_core",
sources = ["abcd.c"],
@@ 42,7 31,6 @@ module2 = Extension(
"abcd.name",
sources = ["abcd_name.c"],
depends = ["abcd.h"],
- extra_link_args = libcore_link_args,
)
# A Cython extension
@@ 50,15 38,53 @@ module3 = Extension(
"abcd.add",
sources = ["abcd_add.pyx"],
depends = ["abcd.h"],
- extra_link_args = libcore_link_args,
)
+# Configure the internal dependencies for each package.
+# Specify that module X depends on modules [A, B ...].
+# In this case, module2 and module3 depend on module1.
+internal_libraries = {
+ "abcd.name": ["abcd.libabcd_core"],
+ "abcd.add": ["abcd.libabcd_core"],
+}
+
+#### extend the "build" command so it adds the appropriate dependencies
+
+# Given the output path for a given module, figure out the
+# "-L" and "-l" arguments for extra_link_args.
+
+# This are needed for Linux-based OSes, but not Darwin.
+
+def get_library_link_args(path):
+ libdir = path.parent # Used for "-L".
+ stem = path.stem # Used for "-l".
+
+ # remove the leading "lib" prefix, so we can use "-l"
+ assert stem[:3] == "lib", stem # double-checking
+ libname = stem[3:]
+
+ # The run-time loader needs an '$ORIGIN' rpath to find the library in the
+ # install directory.
+ return [f"-L{libdir}", f"-l{libname}", "-Wl,-rpath,$ORIGIN"]
+
+
+class build_ext_with_libraries(build_ext):
+ def build_extensions(self):
+ if sys.platform != "darwin":
+ # Update the extra_link_args
+ for e in self.extensions:
+ internal_libs = internal_libraries.get(e.name, [])
+ for internal_lib in internal_libs:
+ path = pathlib.Path(self.get_ext_fullpath("abcd.libabcd_core"))
+ e.extra_link_args.extend(get_library_link_args(path))
+ super().build_extensions()
+
#### setup()
setup(
name = "abcd",
- version = "1.1",
- description = "example of a shared library used by C/Python and Cython extensions",
+ version = "1.2",
+ description = "example of having C/Python and Cython extensions depend on an included shared library",
author = "Andrew Dalke",
packages = ["abcd"],
ext_modules = [
@@ 67,4 93,5 @@ setup(
] + cythonize([
module3,
]),
+ cmdclass = {"build_ext": build_ext_with_libraries},
)