From 8d5b82f1571f495ec723d915e8dc628e6e13c0bc Mon Sep 17 00:00:00 2001 From: Aleksa Sarai Date: Fri, 28 Aug 2026 16:37:14 +1000 Subject: [PATCH 1/2] python: fix ruff 0.16 errors Signed-off-by: Aleksa Sarai --- contrib/bindings/python/README.md | 6 +- contrib/bindings/python/pathrs/__init__.py | 3 +- contrib/bindings/python/pathrs/_internal.py | 55 +++++++++--------- .../python/pathrs/_libpathrs_cffi/lib.pyi | 56 ++++++++----------- contrib/bindings/python/pathrs/_pathrs.py | 30 +++++----- .../bindings/python/pathrs/pathrs_build.py | 12 ++-- contrib/bindings/python/pathrs/procfs.py | 25 +++++---- contrib/bindings/python/setup.py | 6 +- e2e-tests/cmd/python/pathrs-cmd.py | 23 ++++---- examples/python/cat.py | 9 ++- examples/python/static_web.py | 15 +++-- 11 files changed, 119 insertions(+), 121 deletions(-) diff --git a/contrib/bindings/python/README.md b/contrib/bindings/python/README.md index 354b0104..c4e424df 100644 --- a/contrib/bindings/python/README.md +++ b/contrib/bindings/python/README.md @@ -38,11 +38,11 @@ RENAME_EXCHANGE = 0x2 with pathrs.Root("/path/to/rootfs") as root: # symlink - root.symlink("foo", "bar") # foo -> bar + root.symlink("foo", "bar") # foo -> bar # link - root.hardlink("a", "b") # a -> b + root.hardlink("a", "b") # a -> b # rename(at2) - root.rename("foo", "b", flags=RENAME_EXCHANGE) # foo <-> b + root.rename("foo", "b", flags=RENAME_EXCHANGE) # foo <-> b # open(O_CREAT) with root.creat("newfile", "w+") as f: f.write("Some contents.") diff --git a/contrib/bindings/python/pathrs/__init__.py b/contrib/bindings/python/pathrs/__init__.py index d9b3438a..6454c7ec 100644 --- a/contrib/bindings/python/pathrs/__init__.py +++ b/contrib/bindings/python/pathrs/__init__.py @@ -1,4 +1,3 @@ -#!/usr/bin/python3 # SPDX-License-Identifier: MPL-2.0 # # libpathrs: safe path resolution on Linux @@ -13,7 +12,7 @@ import importlib.metadata from . import _pathrs -from ._pathrs import * # noqa: F403 # We just re-export everything. +from ._pathrs import * # We just re-export everything. # In order get pydoc to include the documentation for the re-exported code from # _pathrs, we need to include all of the members in __all__. Rather than diff --git a/contrib/bindings/python/pathrs/_internal.py b/contrib/bindings/python/pathrs/_internal.py index cd7d3eef..c604101f 100644 --- a/contrib/bindings/python/pathrs/_internal.py +++ b/contrib/bindings/python/pathrs/_internal.py @@ -1,4 +1,3 @@ -#!/usr/bin/python3 # SPDX-License-Identifier: MPL-2.0 # # libpathrs: safe path resolution on Linux @@ -9,19 +8,21 @@ # 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/. -import io -import os -import sys +# TODO: Remove this once we only support Python >= 3.10. +from __future__ import annotations # PEP 604 + import copy import errno import fcntl - +import io +import os +import sys import typing from types import TracebackType -from typing import Any, Dict, IO, Optional, TextIO, Type, TypeVar, Union +from typing import IO, Any, ClassVar, TextIO, TypeAlias, TypeVar # TODO: Remove this once we only support Python >= 3.11. -from typing_extensions import Self, TypeAlias +from typing_extensions import Self from ._libpathrs_cffi import lib as libpathrs_so @@ -52,7 +53,7 @@ def _pystr(cstr: CString) -> str: def _cbuffer(size: int) -> CBuffer: - return ffi.new("char[%d]" % (size,)) + return ffi.new(f"char[{size}]") def _is_pathrs_err(ret: int) -> bool: @@ -68,10 +69,10 @@ class PathrsError(Exception): """ message: str - errno: Optional[int] - strerror: Optional[str] + errno: int | None + strerror: str | None - def __init__(self, message: str, /, *, errno: Optional[int] = None): + def __init__(self, message: str, /, *, errno: int | None = None): # Construct Exception. super().__init__(message) @@ -88,7 +89,7 @@ def __init__(self, message: str, /, *, errno: Optional[int] = None): self.strerror = str(errno) @classmethod - def _fetch(cls, err_id: int, /) -> Optional[Self]: + def _fetch(cls, err_id: int, /) -> Self | None: if err_id >= 0: return None @@ -109,10 +110,10 @@ def __str__(self) -> str: if self.errno is None: return self.message else: - return "%s (%s)" % (self.message, self.strerror) + return f"{self.message} ({self.strerror})" def __repr__(self) -> str: - return "Error(%r, errno=%r)" % (self.message, self.errno) + return f"Error({self.message!r}, errno={self.errno!r})" def pprint(self, out: TextIO = sys.stdout) -> None: "Pretty-print the error to the given @out file." @@ -120,8 +121,8 @@ def pprint(self, out: TextIO = sys.stdout) -> None: if self.errno is None: print("pathrs error:", file=out) else: - print("pathrs error [%s]:" % (self.strerror,), file=out) - print(" %s" % (self.message,), file=out) + print(f"pathrs error [{self.strerror}]:", file=out) + print(f" {self.message}", file=out) INTERNAL_ERROR = PathrsError("tried to fetch libpathrs error but no error found") @@ -131,7 +132,7 @@ class FilenoFile(typing.Protocol): def fileno(self) -> int: ... -FileLike = Union[FilenoFile, int] +FileLike = FilenoFile | int def _fileno(file: FileLike) -> int: @@ -151,7 +152,7 @@ def _clonefile(file: FileLike) -> int: Fd = TypeVar("Fd", bound="WrappedFd") -class WrappedFd(object): +class WrappedFd: """ Represents a file descriptor that allows for manual lifetime management, unlike os.fdopen() which are tracked by the GC with no way of "leaking" the @@ -160,7 +161,7 @@ class WrappedFd(object): pathrs will return WrappedFds for most operations that return an fd. """ - _fd: Optional[int] + _fd: int | None def __init__(self, file: FileLike, /): """ @@ -233,12 +234,12 @@ def fdopen(self, mode: str = "r") -> IO[Any]: raise @classmethod - def from_raw_fd(cls: Type[Fd], fd: int, /) -> Fd: + def from_raw_fd(cls, fd: int, /) -> Self: "Shorthand for WrappedFd(fd)." return cls(fd) @classmethod - def from_file(cls: Type[Fd], file: FileLike, /) -> Fd: + def from_file(cls, file: FileLike, /) -> Self: "Shorthand for WrappedFd(file)." return cls(file) @@ -288,7 +289,7 @@ def __copy__(self) -> Self: # A "shallow copy" of a file is the same as a deep copy. return copy.deepcopy(self) - def __deepcopy__(self, memo: Dict[int, Any]) -> Self: + def __deepcopy__(self, memo: dict[int, Any]) -> Self: "Identical to WrappedFd.clone()" return self.clone() @@ -301,9 +302,9 @@ def __enter__(self) -> Self: def __exit__( self, - exc_type: Optional[Type[BaseException]], - exc_value: Optional[BaseException], - exc_traceback: Optional[TracebackType], + exc_type: type[BaseException] | None, + exc_value: BaseException | None, + exc_traceback: TracebackType | None, ) -> None: self.close() @@ -349,9 +350,9 @@ def _convert_mode(mode: str) -> int: class SingletonClass(type): """Metaclass used to create singleton classes.""" - _instances: dict[type, Type[Any]] = {} + _instances: ClassVar[dict[type, type[Any]]] = {} def __call__(cls, *args, **kwargs): # type: ignore[no-untyped-def] # TODO: Not clear what annotations to use, and mypy appears to be confused by metaclasses. if cls not in cls._instances: - cls._instances[cls] = super(SingletonClass, cls).__call__(*args, **kwargs) + cls._instances[cls] = super().__call__(*args, **kwargs) return cls._instances[cls] diff --git a/contrib/bindings/python/pathrs/_libpathrs_cffi/lib.pyi b/contrib/bindings/python/pathrs/_libpathrs_cffi/lib.pyi index 92b3c3da..1d36f3cf 100644 --- a/contrib/bindings/python/pathrs/_libpathrs_cffi/lib.pyi +++ b/contrib/bindings/python/pathrs/_libpathrs_cffi/lib.pyi @@ -8,10 +8,8 @@ # 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/. -from typing import type_check_only, Union - # TODO: Remove this once we only support Python >= 3.10. -from typing_extensions import TypeAlias, Literal +from typing import Literal, TypeAlias, type_check_only from .._pathrs import CBuffer, CString from ..procfs import ProcfsBase @@ -29,7 +27,7 @@ __PATHRS_MAX_ERR_VALUE: ErrorId # TODO: We actually return Union[CError, cffi.FFI.NULL] but we can't express # this using the typing stubs for CFFI... -def pathrs_errorinfo(err_id: Union[ErrorId, int]) -> CError: ... +def pathrs_errorinfo(err_id: ErrorId | int) -> CError: ... def pathrs_errorinfo_free(err: CError) -> None: ... # pathrs_version_info_t * @@ -37,7 +35,7 @@ def pathrs_errorinfo_free(err: CError) -> None: ... class VersionInfo: version_string: CString -def pathrs_version(info: VersionInfo, size: int) -> Union[ErrorId, int]: ... +def pathrs_version(info: VersionInfo, size: int) -> ErrorId | int: ... # uint64_t ProcfsOpenFlags: TypeAlias = int @@ -58,70 +56,64 @@ __PATHRS_PROC_TYPE_PID: ProcfsBase PATHRS_PROC_DEFAULT_ROOTFD: RawFd # procfs API -def pathrs_procfs_open(how: ProcfsOpenHow, size: int) -> Union[RawFd, ErrorId]: ... +def pathrs_procfs_open(how: ProcfsOpenHow, size: int) -> RawFd | ErrorId: ... def pathrs_proc_open( base: ProcfsBase, path: CString, flags: int -) -> Union[RawFd, ErrorId]: ... +) -> RawFd | ErrorId: ... def pathrs_proc_openat( proc_root_fd: RawFd, base: ProcfsBase, path: CString, flags: int -) -> Union[RawFd, ErrorId]: ... +) -> RawFd | ErrorId: ... def pathrs_proc_readlink( base: ProcfsBase, path: CString, linkbuf: CBuffer, linkbuf_size: int -) -> Union[int, ErrorId]: ... +) -> int | ErrorId: ... def pathrs_proc_readlinkat( proc_root_fd: RawFd, base: ProcfsBase, path: CString, linkbuf: CBuffer, linkbuf_size: int, -) -> Union[int, ErrorId]: ... +) -> int | ErrorId: ... # core API -def pathrs_open_root(path: CString) -> Union[RawFd, ErrorId]: ... -def pathrs_reopen(fd: RawFd, flags: int) -> Union[RawFd, ErrorId]: ... -def pathrs_inroot_resolve(rootfd: RawFd, path: CString) -> Union[RawFd, ErrorId]: ... -def pathrs_inroot_resolve_nofollow( - rootfd: RawFd, path: CString -) -> Union[RawFd, ErrorId]: ... -def pathrs_inroot_open( - rootfd: RawFd, path: CString, flags: int -) -> Union[RawFd, ErrorId]: ... +def pathrs_open_root(path: CString) -> RawFd | ErrorId: ... +def pathrs_reopen(fd: RawFd, flags: int) -> RawFd | ErrorId: ... +def pathrs_inroot_resolve(rootfd: RawFd, path: CString) -> RawFd | ErrorId: ... +def pathrs_inroot_resolve_nofollow(rootfd: RawFd, path: CString) -> RawFd | ErrorId: ... +def pathrs_inroot_open(rootfd: RawFd, path: CString, flags: int) -> RawFd | ErrorId: ... def pathrs_inroot_creat( rootfd: RawFd, path: CString, flags: int, filemode: int -) -> Union[RawFd, ErrorId]: ... +) -> RawFd | ErrorId: ... def pathrs_inroot_rename( old_rootfd: RawFd, old_path: CString, new_rootfd: RawFd, new_path: CString, flags: int, -) -> Union[Literal[0], ErrorId]: ... -def pathrs_inroot_rmdir(rootfd: RawFd, path: CString) -> Union[Literal[0], ErrorId]: ... -def pathrs_inroot_unlink( - rootfd: RawFd, path: CString -) -> Union[Literal[0], ErrorId]: ... -def pathrs_inroot_remove_all(rootfd: RawFd, path: CString) -> Union[RawFd, ErrorId]: ... +) -> Literal[0] | ErrorId: ... +def pathrs_inroot_rmdir(rootfd: RawFd, path: CString) -> Literal[0] | ErrorId: ... +def pathrs_inroot_unlink(rootfd: RawFd, path: CString) -> Literal[0] | ErrorId: ... +def pathrs_inroot_remove_all(rootfd: RawFd, path: CString) -> RawFd | ErrorId: ... def pathrs_inroot_mkdir( rootfd: RawFd, path: CString, mode: int -) -> Union[Literal[0], ErrorId]: ... +) -> Literal[0] | ErrorId: ... def pathrs_inroot_mkdir_all( rootfd: RawFd, path: CString, mode: int -) -> Union[Literal[0], ErrorId]: ... +) -> Literal[0] | ErrorId: ... def pathrs_inroot_mknod( rootfd: RawFd, path: CString, mode: int, dev: int -) -> Union[Literal[0], ErrorId]: ... +) -> Literal[0] | ErrorId: ... def pathrs_inroot_hardlink( old_rootfd: RawFd, old_path: CString, new_rootfd: RawFd, new_path: CString, flags: int, -) -> Union[Literal[0], ErrorId]: ... +) -> Literal[0] | ErrorId: ... def pathrs_inroot_symlink( target: CString, rootfd: RawFd, linkpath: CString, -) -> Union[Literal[0], ErrorId]: ... +) -> Literal[0] | ErrorId: ... def pathrs_inroot_readlink( rootfd: RawFd, path: CString, linkbuf: CBuffer, linkbuf_size: int -) -> Union[int, ErrorId]: ... +) -> int | ErrorId: ... diff --git a/contrib/bindings/python/pathrs/_pathrs.py b/contrib/bindings/python/pathrs/_pathrs.py index 9d691f4c..6ec6c5a2 100644 --- a/contrib/bindings/python/pathrs/_pathrs.py +++ b/contrib/bindings/python/pathrs/_pathrs.py @@ -1,4 +1,3 @@ -#!/usr/bin/python3 # SPDX-License-Identifier: MPL-2.0 # # libpathrs: safe path resolution on Linux @@ -9,30 +8,31 @@ # 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/. -import os +# TODO: Remove this once we only support Python >= 3.10. +from __future__ import annotations # PEP 604 +import os import typing -from typing import Any, IO, Union, cast import warnings # TODO: Remove this once we only support Python >= 3.11. -from typing_extensions import TypeAlias +from typing import IO, Any, TypeAlias, cast from ._internal import ( - # Generic helpers. - SingletonClass, + INTERNAL_ERROR, # File type helpers. FileLike, - WrappedFd, - _convert_mode, # Error API. PathrsError, - _is_pathrs_err, - INTERNAL_ERROR, + # Generic helpers. + SingletonClass, + WrappedFd, + _cbuffer, + _convert_mode, # CFFI helpers. _cstr, + _is_pathrs_err, _pystr, - _cbuffer, ) from ._libpathrs_cffi import lib as libpathrs_so @@ -52,12 +52,12 @@ CBuffer: TypeAlias = ffi.CData __all__ = [ - # Core api. - "Root", "Handle", - "library_version", # Error api (re-export). "PathrsError", + # Core api. + "Root", + "library_version", ] @@ -133,7 +133,7 @@ class Root(WrappedFd): relative to. """ - def __init__(self, file_or_path: Union[FileLike, str], /): + def __init__(self, file_or_path: FileLike | str, /): """ Create a handle from a file-like object or a path to a directory. diff --git a/contrib/bindings/python/pathrs/pathrs_build.py b/contrib/bindings/python/pathrs/pathrs_build.py index e378ed40..0e461b1c 100755 --- a/contrib/bindings/python/pathrs/pathrs_build.py +++ b/contrib/bindings/python/pathrs/pathrs_build.py @@ -13,12 +13,14 @@ # 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 +# TODO: Remove this once we only support Python >= 3.10. +from __future__ import annotations # PEP 604 + import os +import re import sys - -from typing import Any, Optional from collections.abc import Iterable +from typing import Any import cffi @@ -97,7 +99,7 @@ def find_rootdir() -> str: return root_dir -def srcdir_ffibuilder(root_dir: Optional[str] = None) -> cffi.FFI: +def srcdir_ffibuilder(root_dir: str | None = 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/*. @@ -108,7 +110,7 @@ def srcdir_ffibuilder(root_dir: Optional[str] = None) -> cffi.FFI: # Figure out which libs are usable. library_dirs: Iterable[str] = ( - os.path.join(root_dir, "target/%s/libpathrs.so" % (mode,)) + os.path.join(root_dir, f"target/{mode}/libpathrs.so") for mode in ("debug", "release") ) library_dirs = (so_path for so_path in library_dirs if os.path.exists(so_path)) diff --git a/contrib/bindings/python/pathrs/procfs.py b/contrib/bindings/python/pathrs/procfs.py index 088dd4a8..b7b6f01f 100644 --- a/contrib/bindings/python/pathrs/procfs.py +++ b/contrib/bindings/python/pathrs/procfs.py @@ -1,4 +1,3 @@ -#!/usr/bin/python3 # SPDX-License-Identifier: MPL-2.0 # # libpathrs: safe path resolution on Linux @@ -10,22 +9,22 @@ # file, You can obtain one at https://mozilla.org/MPL/2.0/. import typing -from typing import Any, IO, cast +from typing import IO, Any, TypeAlias, cast # TODO: Remove this once we only support Python >= 3.11. -from typing_extensions import Self, TypeAlias +from typing_extensions import Self from ._internal import ( + INTERNAL_ERROR, + # Error API. + PathrsError, # File type helpers. WrappedFd, + _cbuffer, _convert_mode, - # Error API. - PathrsError, - _is_pathrs_err, - INTERNAL_ERROR, # CFFI helpers. _cstr, - _cbuffer, + _is_pathrs_err, ) from ._libpathrs_cffi import lib as libpathrs_so @@ -45,10 +44,10 @@ CBuffer: TypeAlias = ffi.CData __all__ = [ + "PROC_PID", "PROC_ROOT", "PROC_SELF", "PROC_THREAD_SELF", - "PROC_PID", "ProcfsHandle", # Shorthand for ProcfsHandle.cached().. "open", @@ -94,7 +93,13 @@ def PROC_PID(pid: int) -> ProcfsBase: class ProcfsHandle(WrappedFd): - """ """ + """ + A handle to a procfs root that can be operated on safely. + + While you can create your own custom handles with ProcfsHandle.new(), most + users should use the module-level procfs.* helper functions, which are all + shorthand for ProcfsHandle.cached().*. + """ _PROCFS_OPEN_HOW_TYPE = "pathrs_procfs_open_how *" diff --git a/contrib/bindings/python/setup.py b/contrib/bindings/python/setup.py index 684799b9..ff65df41 100755 --- a/contrib/bindings/python/setup.py +++ b/contrib/bindings/python/setup.py @@ -9,13 +9,13 @@ # 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/. -import setuptools +from typing import Any -from typing import Any, Dict +import setuptools # This is only needed for backwards compatibility with older versions. -def parse_pyproject() -> Dict[str, Any]: +def parse_pyproject() -> dict[str, Any]: try: import tomllib diff --git a/e2e-tests/cmd/python/pathrs-cmd.py b/e2e-tests/cmd/python/pathrs-cmd.py index 4139aa0d..ec001b2b 100755 --- a/e2e-tests/cmd/python/pathrs-cmd.py +++ b/e2e-tests/cmd/python/pathrs-cmd.py @@ -9,16 +9,17 @@ # 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/. +import argparse import os -import sys import stat -import argparse -from typing import Optional, Self, Sequence, Protocol, Tuple +import sys +from collections.abc import Sequence +from typing import Protocol, Self sys.path.append(os.path.dirname(__file__) + "/../../../contrib/bindings/python") import pathrs from pathrs import procfs -from pathrs.procfs import ProcfsHandle, ProcfsBase +from pathrs.procfs import ProcfsBase, ProcfsHandle def version(args: argparse.Namespace): @@ -38,7 +39,7 @@ def root_resolve(args: argparse.Namespace): root: pathrs.Root = args.root subpath: str = args.subpath follow: bool = args.follow - reopen: Optional[int] = args.reopen + reopen: int | None = args.reopen with root.resolve(subpath, follow_trailing=follow) as handle: print("HANDLE-PATH", fdpath(handle)) @@ -197,7 +198,7 @@ def procfs_readlink(args: argparse.Namespace): def parse_args( args: tuple[str, ...], -) -> Tuple[argparse.ArgumentParser, argparse.Namespace]: +) -> tuple[argparse.ArgumentParser, argparse.Namespace]: parser = argparse.ArgumentParser(prog="pathrs-cmd") parser.set_defaults(func=None) top_subparser = parser.add_subparsers() @@ -207,7 +208,7 @@ def add_mode_flag( name: str, default: int = 0o644, required: bool = False, - help: Optional[str] = None, + help: str | None = None, ) -> None: parser.add_argument( f"--{name}", @@ -264,9 +265,9 @@ def parse_oflags(flags: str) -> int: def add_o_flag( parser: argparse.ArgumentParser, name: str, - default: Optional[int] = os.O_RDONLY, + default: int | None = os.O_RDONLY, required: bool = False, - help: Optional[str] = None, + help: str | None = None, ) -> None: parser.add_argument( f"--{name}", @@ -378,8 +379,8 @@ def __call__( self: Self, parser: argparse.ArgumentParser, namespace: argparse.Namespace, - values: Optional[str | Sequence[str]], - option_string: Optional[str] = None, + values: str | Sequence[str] | None, + option_string: str | None = None, ): inode_type: str dev: int diff --git a/examples/python/cat.py b/examples/python/cat.py index 609fdd05..c842e516 100755 --- a/examples/python/cat.py +++ b/examples/python/cat.py @@ -30,11 +30,10 @@ def chomp(s): def main(root_path, unsafe_path): # Test that context managers work properly with WrappedFd: - with pathrs.Root(root_path) as root: - with root.open(unsafe_path, "r") as f: - for line in f: - line = chomp(line) - print(line) + with pathrs.Root(root_path) as root, root.open(unsafe_path, "r") as f: + for line in f: + line = chomp(line) + print(line) if __name__ == "__main__": diff --git a/examples/python/static_web.py b/examples/python/static_web.py index 726f14c6..9263276a 100755 --- a/examples/python/static_web.py +++ b/examples/python/static_web.py @@ -14,10 +14,10 @@ # An example program which provides a static webserver which will serve files # from a directory, safely resolving paths with libpathrs. +import errno import os -import sys import stat -import errno +import sys import flask import flask.json @@ -60,7 +60,7 @@ def get(path): # Permission denied => 403 Forbidden. errno.EACCES: 403, }.get(e.errno, 500) - flask.abort(status_code, "Could not resolve path: %s." % (e,)) + flask.abort(status_code, f"Could not resolve path: {e}.") with handle: try: @@ -69,11 +69,10 @@ def get(path): f, mimetype="application/octet-stream", direct_passthrough=True ) except IsADirectoryError: - with handle.reopen_raw(os.O_RDONLY) as dirf: - with os.scandir(dirf.fileno()) as s: - return flask.json.jsonify( - {dentry.name: json_dentry(dentry) for dentry in s} - ) + with handle.reopen_raw(os.O_RDONLY) as dirf, os.scandir(dirf.fileno()) as s: + return flask.json.jsonify( + {dentry.name: json_dentry(dentry) for dentry in s} + ) def main(root_path=None): From 2dcd3f6409d3abddf721ee5a0e7e0ad8b0a5c3fc Mon Sep 17 00:00:00 2001 From: Aleksa Sarai Date: Fri, 28 Aug 2026 19:32:53 +1000 Subject: [PATCH 2/2] make: improve builds on old distros with pre-edition2024 Rust libpathrs itself is buildable with pre-1.85 Rust but the existence of test-only crates in our workspace that have transitive post-edition2024 dependencies causes builds of libpathrs to fail with older cargo versions: error: failed to get `anyhow` as a dependency of package `fake-enosys v0.0.0 (/home/abuild/rpmbuild/BUILD/libpathrs-0.2.5/contrib/fake-enosys)` ... Caused by: failed to parse the `edition` key Caused by: this version of Cargo is older than the `2024` edition, and only supports `2015`, `2018`, and `2021` editions. The hacky solution here is to extend hack/with-crate-type.sh to also remove the workspace.members key from Cargo.toml when doing builds via make. This works fine except that we now also need to make backups of Cargo.lock because dropping workspace members causes cargo to GC the dependencies locked in Cargo.lock -- which we don't want. Unfortunately this means that with-crate-type.sh will probably need to remain for longer than I hoped (originally it was meant to be dropped with an MSRV of 1.64). Signed-off-by: Aleksa Sarai --- Cargo.toml | 6 ++++++ hack/with-crate-type.sh | 29 +++++++++++++++++++++++++---- 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2955cf0c..2368fcf4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -105,6 +105,12 @@ unexpected_cfgs = { level = "warn", check-cfg = [ 'cfg(nextest)', ] } +# NOTE: This section will cause parsing issues on pre-edition2024 versions of +# Rust, but they are not needed for compilation of the main libpathrs binary. +# Our Makefile (via hack/with-crate-type.sh) will automatically remove this +# during builds, but if you are running cargo commands yourself you might need +# to work around this as well. +# MSRV(1.85): Remove the above note. [workspace] resolver = "2" members = [ diff --git a/hack/with-crate-type.sh b/hack/with-crate-type.sh index 6a768e27..4b699b53 100755 --- a/hack/with-crate-type.sh +++ b/hack/with-crate-type.sh @@ -57,11 +57,32 @@ esac set -x -backup="$(mktemp "$SRC_ROOT/Cargo.toml.XXXXXX")" -sed -i".${backup##*.}" \ +# Make a backup of Cargo.toml and Cargo.lock. The lockfile backup is needed +# because dropping members from [workspace] causes cargo to prune their +# dependencies from the lockfile, which is not what we want. +backup_dir="$(mktemp -d "$SRC_ROOT/.cargo-backup.XXXXXX")" +cp "$SRC_ROOT"/Cargo.{toml,lock} "$backup_dir" +# shellcheck disable=SC2064 # We want to expand the variables immediately. +trap "mv -t '$SRC_ROOT/' -- '$backup_dir'/Cargo.*" EXIT + +# Replace the crate-type. +sed -i \ "/^crate-type/ s/=.*/= [$(printf '"%s",' "${crate_types[@]}")]/" \ "$SRC_ROOT/Cargo.toml" -# shellcheck disable=SC2064 # We want to expand the variables immediately. -trap "mv '$backup' '$SRC_ROOT/Cargo.toml'" EXIT + +# Drop the workspace = [...] set. Some of our workspace crates have +# dependencies that use edition2024 which triggers a parsing error even if you +# do not actually build them, which causes problems for older distros. +# MSRV(1.85): Drop this once we require edition2024. +# +# TODO: If we ever split out libpathrs into subcrates we will need to cleverer. +# +# The sed-foo below collects everything from "members = [" until the next "]" +# into the pattern space and drops them in one go, to handle multi-line arrays +# more robustly. +sed -i ' + /^\[workspace\]/,/^\[/ { + /^\s*members\s*=\s*\[/ { :x; /\]/!{ N; bx }; d } + }' "$SRC_ROOT/Cargo.toml" "$@"