Skip to content

Commit

Permalink
python bindings: make cffi builds more friendly for distros
Browse files Browse the repository at this point in the history
Some distributions (such as openSUS) would like to make the python
bindings a subpackage of the main libpathrs package, which means they
need to build the bindings and the library at the same time.

This is possible with setuptools, but being able to create wheels would
be ideal. The trick is to make it so that if you pass the magic
environment variable PATHRS_SRC_ROOT, we can use that as the source dir
for compiling against even when inside a venv for "python -m build".

This has no impact on the wheels we will make for PyPI.

Signed-off-by: Aleksa Sarai <[email protected]>
  • Loading branch information
cyphar committed Sep 17, 2024
1 parent 051b917 commit 2d7324b
Show file tree
Hide file tree
Showing 3 changed files with 65 additions and 22 deletions.
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,19 @@ and this project adheres to [Semantic Versioning](http://semver.org/).

## [Unreleased] ##

### Added ###
- python bindings: The `cffi` build script is now a little easier to use for
distributions that want to build the python bindings at the same time as the
main library. After compiling the library, set the `PATHRS_SRC_ROOT`
environment variable to the root of the `libpathrs` source directory. This
will instruct the `cffi` build script (when called from `setup.py` or
`python3 -m build`) to link against the library built in the source directory
rather than using system libraries. As long as you install the same library
later, this should cause no issues.

Standard wheel builds still work the same way, so users that want to link
against the system libraries don't need to make any changes.

## [0.1.0] - 2024-09-14 ##

> 負けたくないことに理由って要る?
Expand Down
1 change: 1 addition & 0 deletions contrib/bindings/python/.gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
/build/
/dist/
/*.egg-info/
73 changes: 51 additions & 22 deletions contrib/bindings/python/pathrs/pathrs_build.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,9 @@ def create_ffibuilder(**kwargs):

return ffibuilder

def find_ffibuilder():
def find_rootdir():
# Figure out where the libpathrs source dir is.
ROOT_DIR = None
root_dir = None
candidate = os.path.dirname(sys.path[0] or os.getcwd())
while candidate != "/":
try:
Expand All @@ -69,36 +69,65 @@ def find_ffibuilder():
with open(candidate_toml, "r") as f:
content = f.read()
if re.findall(r'^name = "pathrs"$', content, re.MULTILINE):
ROOT_DIR = candidate
root_dir = candidate
break
except:
pass
candidate = os.path.dirname(candidate)

# TODO: Support using the system paths.
if not ROOT_DIR:
raise RuntimeError("Could not find pathrs source-dir root.")
if not root_dir:
raise FileNotFoundError("Could not find pathrs source-dir root.")

return root_dir

def srcdir_ffibuilder(root_dir=None):
"""
Build the CFFI bindings using the provided root_dir as the root of a
pathrs source tree which has compiled cdylibs ready in target/*.
"""

if root_dir is None:
root_dir = find_rootdir()

# Figure out which libs are usable.
lib_paths = []
for mode in ["debug", "release"]:
so_path = os.path.join(ROOT_DIR, "target/%s/libpathrs.so" % (mode,))
if os.path.exists(so_path):
lib_paths.append(so_path)
lib_paths = sorted(lib_paths, key=lambda path: -os.path.getmtime(path))
lib_paths = [os.path.dirname(path) for path in lib_paths]
library_dirs = (
os.path.join(root_dir, "target/%s/libpathrs.so" % (mode,))
for mode in ("debug", "release")
)
library_dirs = (so_path for so_path in library_dirs if os.path.exists(so_path))
library_dirs = sorted(library_dirs, key=lambda path: -os.path.getmtime(path))
library_dirs = [os.path.dirname(path) for path in library_dirs]

# Compile the libpathrs module.
return create_ffibuilder(include_dirs=[os.path.join(ROOT_DIR, "include")],
library_dirs=lib_paths)
return create_ffibuilder(include_dirs=[os.path.join(root_dir, "include")],
library_dirs=library_dirs)

if __name__ == "__main__":
# Compile the cffi module if running outside of setuptools.
ffibuilder = find_ffibuilder()
ffibuilder.compile(verbose=True)
else:
# Use the system libraries if running inside setuptools.
ffibuilder = create_ffibuilder(include_dirs=[
def system_ffibuilder():
"""
Build the CFFI bindings using the installed libpathrs system libraries.
"""

return create_ffibuilder(include_dirs=[
"/usr/include",
"/usr/local/include"
])

if __name__ == "__main__":
try:
# Search for the compiled libraries to link to from our libpathrs
# source if running outside of setuptools as a regular program.
ffibuilder = find_ffibuilder()
except FileNotFoundError:
# If we couldn't find a valid library in the source dir, just fallback
# to using the system libraries.
ffibuilder = system_ffibuilder()
ffibuilder.compile(verbose=True)
elif os.environ.get("PATHRS_SRC_ROOT", "") != "":
# If we're running in setup tools, we can't easily find the source dir.
# However, distributions can set PATHRS_SRC_ROOT to the path of the
# libpathrs source directory to make it easier to build the python modules
# in the same %build script as the main library.
ffibuilder = srcdir_ffibuilder(root_dir=os.environ.get("PATHRS_SRC_ROOT"))
else:
# Use the system libraries if running inside standard setuptools.
ffibuilder = system_ffibuilder()

0 comments on commit 2d7324b

Please sign in to comment.