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/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): 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" "$@"