File: pathrs_build.py

package info (click to toggle)
rust-pathrs 0.2.1-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 1,912 kB
  • sloc: python: 1,138; sh: 371; ansic: 259; makefile: 151
file content (156 lines) | stat: -rwxr-xr-x 5,541 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
#!/usr/bin/python3
# SPDX-License-Identifier: MPL-2.0
#
# libpathrs: safe path resolution on Linux
# Copyright (C) 2019-2025 Aleksa Sarai <cyphar@cyphar.com>
# Copyright (C) 2019-2025 SUSE LLC
#
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at https://mozilla.org/MPL/2.0/.

# This builds the _pathrs module (only needs to be done during the initial
# build of libpathrs, and can be redistributed alongside the pathrs.py wrapping
# library). It's much better than the ABI-mode of CFFI.

import re
import os
import sys

from typing import Any, Optional
from collections.abc import Iterable

import cffi


def load_hdr(ffi: cffi.FFI, hdr_path: str) -> None:
    with open(hdr_path) as f:
        hdr = f.read()

    # We need to first filter out all the bits of <pathrs.h> that are not
    # supported by cffi. Ideally this wouldn't be necessary, but the macro
    # support in cffi is very limited, and we make use of basic features that
    # are unsupported by cffi.

    # Drop all non-#define lines (directives are not supported).
    hdr = re.sub(r"^#\s*(?!define\b).*$", "", hdr, flags=re.MULTILINE)
    # Drop all:
    #  * "#define FOO(n) ..." lines (function-style macros are not supported).
    #  * Empty-value "#define FOO" lines (empty macros are not supported)
    # TODO: We probably should support multi-line macros.
    hdr = re.sub(r"^#\s*define\b\s*\w*(\(.*|)$", "", hdr, flags=re.MULTILINE)

    # Replace each struct-like body that has __CBINDGEN_ALIGNED before it,
    # remove the __CBINDGEN_ALIGNED and add "...;" as the last field in the
    # struct. This is how you tell cffi to get the proper alignment from the
    # compiler (__attribute__((aligned(n))) is not supported by cdef).
    hdr = re.sub(
        r"__CBINDGEN_ALIGNED\(\d+\)([^{;]*){([^}]+)}",
        r"\1 {\2 ...;}",
        hdr,
        flags=re.MULTILINE,
    )

    # Load the header.
    ffi.cdef(hdr)


def create_ffibuilder(**kwargs: Any) -> cffi.FFI:
    ffibuilder = cffi.FFI()
    ffibuilder.cdef("typedef uint32_t dev_t;")

    # We need to use cdef to tell cffi what functions we need to FFI to. But we
    # don't need the structs (I hope).
    for include_dir in kwargs.get("include_dirs", []):
        pathrs_hdr = os.path.join(include_dir, "pathrs.h")
        if os.path.exists(pathrs_hdr):
            load_hdr(ffibuilder, pathrs_hdr)

    # Add a source and link to libpathrs.
    ffibuilder.set_source(
        "_libpathrs_cffi", "#include <pathrs.h>", libraries=["pathrs"], **kwargs
    )

    return ffibuilder


def find_rootdir() -> str:
    # Figure out where the libpathrs source dir is.
    root_dir = None
    candidate = os.path.dirname(sys.path[0] or os.getcwd())
    while candidate != "/":
        try:
            # Look for a Cargo.toml which says it's pathrs.
            candidate_toml = os.path.join(candidate, "Cargo.toml")
            with open(candidate_toml, "r") as f:
                content = f.read()
            if re.findall(r'^name = "pathrs"$', content, re.MULTILINE):
                root_dir = candidate
                break
        except FileNotFoundError:
            pass
        candidate = os.path.dirname(candidate)

    if not root_dir:
        raise FileNotFoundError("Could not find pathrs source-dir root.")

    return root_dir


def srcdir_ffibuilder(root_dir: Optional[str] = None) -> cffi.FFI:
    """
    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.
    library_dirs: Iterable[str] = (
        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=library_dirs,
    )


def system_ffibuilder() -> cffi.FFI:
    """
    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 = srcdir_ffibuilder(root_dir=find_rootdir())
    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()