Skip to content

Commit 2d7324b

Browse files
committed
python bindings: make cffi builds more friendly for distros
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]>
1 parent 051b917 commit 2d7324b

File tree

3 files changed

+65
-22
lines changed

3 files changed

+65
-22
lines changed

CHANGELOG.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,19 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
66

77
## [Unreleased] ##
88

9+
### Added ###
10+
- python bindings: The `cffi` build script is now a little easier to use for
11+
distributions that want to build the python bindings at the same time as the
12+
main library. After compiling the library, set the `PATHRS_SRC_ROOT`
13+
environment variable to the root of the `libpathrs` source directory. This
14+
will instruct the `cffi` build script (when called from `setup.py` or
15+
`python3 -m build`) to link against the library built in the source directory
16+
rather than using system libraries. As long as you install the same library
17+
later, this should cause no issues.
18+
19+
Standard wheel builds still work the same way, so users that want to link
20+
against the system libraries don't need to make any changes.
21+
922
## [0.1.0] - 2024-09-14 ##
1023

1124
> 負けたくないことに理由って要る?

contrib/bindings/python/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
1+
/build/
12
/dist/
23
/*.egg-info/

contrib/bindings/python/pathrs/pathrs_build.py

Lines changed: 51 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,9 @@ def create_ffibuilder(**kwargs):
5858

5959
return ffibuilder
6060

61-
def find_ffibuilder():
61+
def find_rootdir():
6262
# Figure out where the libpathrs source dir is.
63-
ROOT_DIR = None
63+
root_dir = None
6464
candidate = os.path.dirname(sys.path[0] or os.getcwd())
6565
while candidate != "/":
6666
try:
@@ -69,36 +69,65 @@ def find_ffibuilder():
6969
with open(candidate_toml, "r") as f:
7070
content = f.read()
7171
if re.findall(r'^name = "pathrs"$', content, re.MULTILINE):
72-
ROOT_DIR = candidate
72+
root_dir = candidate
7373
break
7474
except:
7575
pass
7676
candidate = os.path.dirname(candidate)
7777

78-
# TODO: Support using the system paths.
79-
if not ROOT_DIR:
80-
raise RuntimeError("Could not find pathrs source-dir root.")
78+
if not root_dir:
79+
raise FileNotFoundError("Could not find pathrs source-dir root.")
80+
81+
return root_dir
82+
83+
def srcdir_ffibuilder(root_dir=None):
84+
"""
85+
Build the CFFI bindings using the provided root_dir as the root of a
86+
pathrs source tree which has compiled cdylibs ready in target/*.
87+
"""
88+
89+
if root_dir is None:
90+
root_dir = find_rootdir()
8191

8292
# Figure out which libs are usable.
83-
lib_paths = []
84-
for mode in ["debug", "release"]:
85-
so_path = os.path.join(ROOT_DIR, "target/%s/libpathrs.so" % (mode,))
86-
if os.path.exists(so_path):
87-
lib_paths.append(so_path)
88-
lib_paths = sorted(lib_paths, key=lambda path: -os.path.getmtime(path))
89-
lib_paths = [os.path.dirname(path) for path in lib_paths]
93+
library_dirs = (
94+
os.path.join(root_dir, "target/%s/libpathrs.so" % (mode,))
95+
for mode in ("debug", "release")
96+
)
97+
library_dirs = (so_path for so_path in library_dirs if os.path.exists(so_path))
98+
library_dirs = sorted(library_dirs, key=lambda path: -os.path.getmtime(path))
99+
library_dirs = [os.path.dirname(path) for path in library_dirs]
90100

91101
# Compile the libpathrs module.
92-
return create_ffibuilder(include_dirs=[os.path.join(ROOT_DIR, "include")],
93-
library_dirs=lib_paths)
102+
return create_ffibuilder(include_dirs=[os.path.join(root_dir, "include")],
103+
library_dirs=library_dirs)
94104

95-
if __name__ == "__main__":
96-
# Compile the cffi module if running outside of setuptools.
97-
ffibuilder = find_ffibuilder()
98-
ffibuilder.compile(verbose=True)
99-
else:
100-
# Use the system libraries if running inside setuptools.
101-
ffibuilder = create_ffibuilder(include_dirs=[
105+
def system_ffibuilder():
106+
"""
107+
Build the CFFI bindings using the installed libpathrs system libraries.
108+
"""
109+
110+
return create_ffibuilder(include_dirs=[
102111
"/usr/include",
103112
"/usr/local/include"
104113
])
114+
115+
if __name__ == "__main__":
116+
try:
117+
# Search for the compiled libraries to link to from our libpathrs
118+
# source if running outside of setuptools as a regular program.
119+
ffibuilder = find_ffibuilder()
120+
except FileNotFoundError:
121+
# If we couldn't find a valid library in the source dir, just fallback
122+
# to using the system libraries.
123+
ffibuilder = system_ffibuilder()
124+
ffibuilder.compile(verbose=True)
125+
elif os.environ.get("PATHRS_SRC_ROOT", "") != "":
126+
# If we're running in setup tools, we can't easily find the source dir.
127+
# However, distributions can set PATHRS_SRC_ROOT to the path of the
128+
# libpathrs source directory to make it easier to build the python modules
129+
# in the same %build script as the main library.
130+
ffibuilder = srcdir_ffibuilder(root_dir=os.environ.get("PATHRS_SRC_ROOT"))
131+
else:
132+
# Use the system libraries if running inside standard setuptools.
133+
ffibuilder = system_ffibuilder()

0 commit comments

Comments
 (0)