← Back to issue list

Craft Parts does not support Python 3.14

View original Github issue

Metadata

Project
craft-parts
Number
#1642
Type
issue
State
closed
Author
lengau
Labels
Status: Triaged Type: Bug
Created
Updated
Closed

Current evaluation

Python 3.14 chroot failures were fixed by explicitly using the multiprocessing fork context in #1672 (commit 12da4851).

Resolution: closed resolved

Reason: The resolving change is commit 12da4851, merged via PR #1672, titled “fix: use fork context for chroot in python 3.14.” It adds multiprocessing.get_context("fork") for chroot execution, matching the diagnosis that Python 3.14 changed the default to forkserver.

Evaluated by: mr-cal-bot

No scores available.

Issue body

### Bug Description Craft Parts errors out on Python 3.14 due to the change in the way it handles threads. It is unclear whether this is just to do with testing or if it's actually a failure. ### To Reproduce Run the chroot unit tests (tests/unit/overlays/test_chroot.py) without root in Python 3.12 (succeeds) and Python 3.14 (fails) ### part yaml ```shell ``` ### Relevant log output ```shell $ .venv/bin/python -m pytest tests/unit/overlays/test_chroot.py ================================================================================================================== test session starts ================================================================================================================== platform linux -- Python 3.14.6, pytest-9.0.3, pluggy-1.6.0 rootdir: /home/lengau/Work/Code/craft-parts configfile: pyproject.toml plugins: mock-3.15.1, check-2.7.6, cov-7.0.0, rerunfailures-16.1, subprocess-1.5.3, requests-mock-1.12.1, hypothesis-6.151.9, pyfakefs-6.1.6, anyio-4.12.1 collected 7 items tests/unit/overlays/test_chroot.py FFFF..F [100%] ======================================================================================================================= FAILURES ======================================================================================================================== ________________________________________________________________________________________________________________ TestChroot.test_chroot _________________________________________________________________________________________________________________ self = <tests.unit.overlays.test_chroot.TestChroot object at 0x7b4c49967390>, mocker = <pytest_mock.plugin.MockerFixture object at 0x7b4c498856a0>, new_dir = local('/tmp/pytest-of-lengau/pytest-60/test_chroot0') mock_chroot = <Mock spec='builtin_function_or_method' id='135567581408832'> def test_chroot(self, mocker, new_dir, mock_chroot): mock_mount = mocker.patch("craft_parts.utils.os_utils.mount") mock_umount = mocker.patch("craft_parts.utils.os_utils.umount") spy_process = mocker.spy(multiprocessing, "Process") new_root = Path(new_dir, "dir1") # this runs in the child process Path("dir1").mkdir() for subdir in ["etc", "proc", "sys", "dev", "dev/shm"]: Path(new_root, subdir).mkdir() > chroot.chroot(new_root, target_func, "content") /home/lengau/Work/Code/craft-parts/tests/unit/overlays/test_chroot.py:65: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ path = PosixPath('/tmp/pytest-of-lengau/pytest-60/test_chroot0/dir1'), target = <function target_func at 0x7b4c498a3740>, use_host_sources = False, args = ('content',), kwargs = {} parent_conn = <multiprocessing.connection.Connection object at 0x7b4c49718440>, child_conn = <multiprocessing.connection.Connection object at 0x7b4c497687d0>, child = <Process name='Process-1' pid=3401899 parent=3401891 stopped exitcode=0> res = None, err = "[Errno 1] Operation not permitted: '/tmp/pytest-of-lengau/pytest-60/test_chroot0/dir1'" def chroot( path: Path, target: Callable[..., _T], *args: Any, use_host_sources: bool = False, **kwargs: Any, ) -> _T: """Execute a callable in a chroot environment. :param path: The new filesystem root. :param target: The callable to run in the chroot environment. :param args: Arguments for target. :param kwargs: Keyword arguments for target. :param use_host_sources: Whether overlay steps should also include the repository sources defined on the host. :returns: The target function return value. """ logger.debug("[pid=%d] parent process", os.getpid()) # This typehint technically should be "Connection[Any, tuple[_T, None] | tuple[None, str]]" # However, types surrounding multiprocessing are finnicky at best and the way we handle the # result here makes the typehint effectively true, since we don't attempt to access the first # field of the tuple unless the second field is None. parent_conn: Connection[Any, tuple[_T, str | None]] parent_conn, child_conn = multiprocessing.Pipe() child = multiprocessing.Process( target=_runner, args=(Path(path), child_conn, target, args, kwargs) ) logger.debug("[pid=%d] set up chroot", os.getpid()) _setup_chroot(path, use_host_sources=use_host_sources) try: child.start() res, err = parent_conn.recv() child.join() finally: logger.debug("[pid=%d] clean up chroot", os.getpid()) _cleanup_chroot(path, use_host_sources=use_host_sources) if isinstance(err, str): > raise errors.OverlayChrootExecutionError(err) E craft_parts.overlays.errors.OverlayChrootExecutionError: Overlay environment execution error: [Errno 1] Operation not permitted: '/tmp/pytest-of-lengau/pytest-60/test_chroot0/dir1' /home/lengau/Work/Code/craft-parts/craft_parts/overlays/chroot.py:84: OverlayChrootExecutionError ------------------------------------------------------------------------------------------------------------------ Captured log setup ------------------------------------------------------------------------------------------------------------------- WARNING root:features.py:42 deleting current features configuration _________________________________________________________________________________________________________ TestChroot.test_chroot_no_mountpoints _________________________________________________________________________________________________________ self = <tests.unit.overlays.test_chroot.TestChroot object at 0x7b4c49967d90>, mocker = <pytest_mock.plugin.MockerFixture object at 0x7b4c497682d0>, new_dir = local('/tmp/pytest-of-lengau/pytest-60/test_chroot_no_mountpoints0') def test_chroot_no_mountpoints(self, mocker, new_dir): mock_mount = mocker.patch("craft_parts.utils.os_utils.mount") mock_umount = mocker.patch("craft_parts.utils.os_utils.umount") spy_process = mocker.spy(multiprocessing, "Process") new_root = Path(new_dir, "dir1") # this runs in the child process mocker.patch("os.chroot") Path("dir1").mkdir() > chroot.chroot(new_root, target_func, "content") /home/lengau/Work/Code/craft-parts/tests/unit/overlays/test_chroot.py:102: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ path = PosixPath('/tmp/pytest-of-lengau/pytest-60/test_chroot_no_mountpoints0/dir1'), target = <function target_func at 0x7b4c498a3740>, use_host_sources = False, args = ('content',), kwargs = {} parent_conn = <multiprocessing.connection.Connection object at 0x7b4c498afce0>, child_conn = <multiprocessing.connection.Connection object at 0x7b4c497a4640>, child = <Process name='Process-2' pid=3401900 parent=3401891 stopped exitcode=0> res = None, err = "[Errno 1] Operation not permitted: '/tmp/pytest-of-lengau/pytest-60/test_chroot_no_mountpoints0/dir1'" def chroot( path: Path, target: Callable[..., _T], *args: Any, use_host_sources: bool = False, **kwargs: Any, ) -> _T: """Execute a callable in a chroot environment. :param path: The new filesystem root. :param target: The callable to run in the chroot environment. :param args: Arguments for target. :param kwargs: Keyword arguments for target. :param use_host_sources: Whether overlay steps should also include the repository sources defined on the host. :returns: The target function return value. """ logger.debug("[pid=%d] parent process", os.getpid()) # This typehint technically should be "Connection[Any, tuple[_T, None] | tuple[None, str]]" # However, types surrounding multiprocessing are finnicky at best and the way we handle the # result here makes the typehint effectively true, since we don't attempt to access the first # field of the tuple unless the second field is None. parent_conn: Connection[Any, tuple[_T, str | None]] parent_conn, child_conn = multiprocessing.Pipe() child = multiprocessing.Process( target=_runner, args=(Path(path), child_conn, target, args, kwargs) ) logger.debug("[pid=%d] set up chroot", os.getpid()) _setup_chroot(path, use_host_sources=use_host_sources) try: child.start() res, err = parent_conn.recv() child.join() finally: logger.debug("[pid=%d] clean up chroot", os.getpid()) _cleanup_chroot(path, use_host_sources=use_host_sources) if isinstance(err, str): > raise errors.OverlayChrootExecutionError(err) E craft_parts.overlays.errors.OverlayChrootExecutionError: Overlay environment execution error: [Errno 1] Operation not permitted: '/tmp/pytest-of-lengau/pytest-60/test_chroot_no_mountpoints0/dir1' /home/lengau/Work/Code/craft-parts/craft_parts/overlays/chroot.py:84: OverlayChrootExecutionError ------------------------------------------------------------------------------------------------------------------- Captured log call ------------------------------------------------------------------------------------------------------------------- WARNING craft_parts.overlays.chroot:chroot.py:215 [pid=3401891] mount: PosixPath('/tmp/pytest-of-lengau/pytest-60/test_chroot_no_mountpoints0/dir1/etc/resolv.conf') not found. Skipping WARNING craft_parts.overlays.chroot:chroot.py:215 [pid=3401891] mount: PosixPath('/tmp/pytest-of-lengau/pytest-60/test_chroot_no_mountpoints0/dir1/proc') not found. Skipping WARNING craft_parts.overlays.chroot:chroot.py:215 [pid=3401891] mount: PosixPath('/tmp/pytest-of-lengau/pytest-60/test_chroot_no_mountpoints0/dir1/sys') not found. Skipping WARNING craft_parts.overlays.chroot:chroot.py:215 [pid=3401891] mount: PosixPath('/tmp/pytest-of-lengau/pytest-60/test_chroot_no_mountpoints0/dir1/dev') not found. Skipping WARNING craft_parts.overlays.chroot:chroot.py:227 [pid=3401891] umount: PosixPath('/tmp/pytest-of-lengau/pytest-60/test_chroot_no_mountpoints0/dir1/dev') not found! WARNING craft_parts.overlays.chroot:chroot.py:227 [pid=3401891] umount: PosixPath('/tmp/pytest-of-lengau/pytest-60/test_chroot_no_mountpoints0/dir1/sys') not found! WARNING craft_parts.overlays.chroot:chroot.py:227 [pid=3401891] umount: PosixPath('/tmp/pytest-of-lengau/pytest-60/test_chroot_no_mountpoints0/dir1/proc') not found! WARNING craft_parts.overlays.chroot:chroot.py:227 [pid=3401891] umount: PosixPath('/tmp/pytest-of-lengau/pytest-60/test_chroot_no_mountpoints0/dir1/etc/resolv.conf') not found! _____________________________________________________________________________________________________ TestChroot.test_chroot_symlinked_resolv_conf ______________________________________________________________________________________________________ self = <tests.unit.overlays.test_chroot.TestChroot object at 0x7b4c498ac8a0>, mocker = <pytest_mock.plugin.MockerFixture object at 0x7b4c4976b110>, new_dir = local('/tmp/pytest-of-lengau/pytest-60/test_chroot_symlinked_resolv_c0') def test_chroot_symlinked_resolv_conf(self, mocker, new_dir): mock_mount = mocker.patch("craft_parts.utils.os_utils.mount") mock_umount = mocker.patch("craft_parts.utils.os_utils.umount") spy_process = mocker.spy(multiprocessing, "Process") new_root = Path(new_dir, "dir1") # this runs in the child process mocker.patch("os.chroot") Path("dir1").mkdir() Path("dir1/etc").mkdir() Path("dir1/etc/resolv.conf").symlink_to("whatever") > chroot.chroot(new_root, target_func, "content") /home/lengau/Work/Code/craft-parts/tests/unit/overlays/test_chroot.py:127: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ path = PosixPath('/tmp/pytest-of-lengau/pytest-60/test_chroot_symlinked_resolv_c0/dir1'), target = <function target_func at 0x7b4c498a3740>, use_host_sources = False, args = ('content',), kwargs = {} parent_conn = <multiprocessing.connection.Connection object at 0x7b4c4974e7a0>, child_conn = <multiprocessing.connection.Connection object at 0x7b4c4974e140>, child = <Process name='Process-3' pid=3401901 parent=3401891 stopped exitcode=0> res = None, err = "[Errno 1] Operation not permitted: '/tmp/pytest-of-lengau/pytest-60/test_chroot_symlinked_resolv_c0/dir1'" def chroot( path: Path, target: Callable[..., _T], *args: Any, use_host_sources: bool = False, **kwargs: Any, ) -> _T: """Execute a callable in a chroot environment. :param path: The new filesystem root. :param target: The callable to run in the chroot environment. :param args: Arguments for target. :param kwargs: Keyword arguments for target. :param use_host_sources: Whether overlay steps should also include the repository sources defined on the host. :returns: The target function return value. """ logger.debug("[pid=%d] parent process", os.getpid()) # This typehint technically should be "Connection[Any, tuple[_T, None] | tuple[None, str]]" # However, types surrounding multiprocessing are finnicky at best and the way we handle the # result here makes the typehint effectively true, since we don't attempt to access the first # field of the tuple unless the second field is None. parent_conn: Connection[Any, tuple[_T, str | None]] parent_conn, child_conn = multiprocessing.Pipe() child = multiprocessing.Process( target=_runner, args=(Path(path), child_conn, target, args, kwargs) ) logger.debug("[pid=%d] set up chroot", os.getpid()) _setup_chroot(path, use_host_sources=use_host_sources) try: child.start() res, err = parent_conn.recv() child.join() finally: logger.debug("[pid=%d] clean up chroot", os.getpid()) _cleanup_chroot(path, use_host_sources=use_host_sources) if isinstance(err, str): > raise errors.OverlayChrootExecutionError(err) E craft_parts.overlays.errors.OverlayChrootExecutionError: Overlay environment execution error: [Errno 1] Operation not permitted: '/tmp/pytest-of-lengau/pytest-60/test_chroot_symlinked_resolv_c0/dir1' /home/lengau/Work/Code/craft-parts/craft_parts/overlays/chroot.py:84: OverlayChrootExecutionError ------------------------------------------------------------------------------------------------------------------- Captured log call ------------------------------------------------------------------------------------------------------------------- WARNING craft_parts.overlays.chroot:chroot.py:215 [pid=3401891] mount: PosixPath('/tmp/pytest-of-lengau/pytest-60/test_chroot_symlinked_resolv_c0/dir1/proc') not found. Skipping WARNING craft_parts.overlays.chroot:chroot.py:215 [pid=3401891] mount: PosixPath('/tmp/pytest-of-lengau/pytest-60/test_chroot_symlinked_resolv_c0/dir1/sys') not found. Skipping WARNING craft_parts.overlays.chroot:chroot.py:215 [pid=3401891] mount: PosixPath('/tmp/pytest-of-lengau/pytest-60/test_chroot_symlinked_resolv_c0/dir1/dev') not found. Skipping WARNING craft_parts.overlays.chroot:chroot.py:227 [pid=3401891] umount: PosixPath('/tmp/pytest-of-lengau/pytest-60/test_chroot_symlinked_resolv_c0/dir1/dev') not found! WARNING craft_parts.overlays.chroot:chroot.py:227 [pid=3401891] umount: PosixPath('/tmp/pytest-of-lengau/pytest-60/test_chroot_symlinked_resolv_c0/dir1/sys') not found! WARNING craft_parts.overlays.chroot:chroot.py:227 [pid=3401891] umount: PosixPath('/tmp/pytest-of-lengau/pytest-60/test_chroot_symlinked_resolv_c0/dir1/proc') not found! _________________________________________________________________________________________________________ TestChroot.test_chroot_no_resolv_conf _________________________________________________________________________________________________________ self = <tests.unit.overlays.test_chroot.TestChroot object at 0x7b4c498acc30>, mocker = <pytest_mock.plugin.MockerFixture object at 0x7b4c497a4770>, new_dir = local('/tmp/pytest-of-lengau/pytest-60/test_chroot_no_resolv_conf0') def test_chroot_no_resolv_conf(self, mocker, new_dir): mock_mount = mocker.patch("craft_parts.utils.os_utils.mount") mock_umount = mocker.patch("craft_parts.utils.os_utils.umount") spy_process = mocker.spy(multiprocessing, "Process") new_root = Path(new_dir, "dir1") # this runs in the child process mocker.patch("os.chroot") Path("dir1").mkdir() Path("dir1/etc").mkdir() > chroot.chroot(new_root, target_func, "content") /home/lengau/Work/Code/craft-parts/tests/unit/overlays/test_chroot.py:156: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ path = PosixPath('/tmp/pytest-of-lengau/pytest-60/test_chroot_no_resolv_conf0/dir1'), target = <function target_func at 0x7b4c498a3740>, use_host_sources = False, args = ('content',), kwargs = {} parent_conn = <multiprocessing.connection.Connection object at 0x7b4c497d4450>, child_conn = <multiprocessing.connection.Connection object at 0x7b4c497cb5c0>, child = <Process name='Process-4' pid=3401902 parent=3401891 stopped exitcode=0> res = None, err = "[Errno 1] Operation not permitted: '/tmp/pytest-of-lengau/pytest-60/test_chroot_no_resolv_conf0/dir1'" def chroot( path: Path, target: Callable[..., _T], *args: Any, use_host_sources: bool = False, **kwargs: Any, ) -> _T: """Execute a callable in a chroot environment. :param path: The new filesystem root. :param target: The callable to run in the chroot environment. :param args: Arguments for target. :param kwargs: Keyword arguments for target. :param use_host_sources: Whether overlay steps should also include the repository sources defined on the host. :returns: The target function return value. """ logger.debug("[pid=%d] parent process", os.getpid()) # This typehint technically should be "Connection[Any, tuple[_T, None] | tuple[None, str]]" # However, types surrounding multiprocessing are finnicky at best and the way we handle the # result here makes the typehint effectively true, since we don't attempt to access the first # field of the tuple unless the second field is None. parent_conn: Connection[Any, tuple[_T, str | None]] parent_conn, child_conn = multiprocessing.Pipe() child = multiprocessing.Process( target=_runner, args=(Path(path), child_conn, target, args, kwargs) ) logger.debug("[pid=%d] set up chroot", os.getpid()) _setup_chroot(path, use_host_sources=use_host_sources) try: child.start() res, err = parent_conn.recv() child.join() finally: logger.debug("[pid=%d] clean up chroot", os.getpid()) _cleanup_chroot(path, use_host_sources=use_host_sources) if isinstance(err, str): > raise errors.OverlayChrootExecutionError(err) E craft_parts.overlays.errors.OverlayChrootExecutionError: Overlay environment execution error: [Errno 1] Operation not permitted: '/tmp/pytest-of-lengau/pytest-60/test_chroot_no_resolv_conf0/dir1' /home/lengau/Work/Code/craft-parts/craft_parts/overlays/chroot.py:84: OverlayChrootExecutionError ------------------------------------------------------------------------------------------------------------------- Captured log call ------------------------------------------------------------------------------------------------------------------- WARNING craft_parts.overlays.chroot:chroot.py:215 [pid=3401891] mount: PosixPath('/tmp/pytest-of-lengau/pytest-60/test_chroot_no_resolv_conf0/dir1/proc') not found. Skipping WARNING craft_parts.overlays.chroot:chroot.py:215 [pid=3401891] mount: PosixPath('/tmp/pytest-of-lengau/pytest-60/test_chroot_no_resolv_conf0/dir1/sys') not found. Skipping WARNING craft_parts.overlays.chroot:chroot.py:215 [pid=3401891] mount: PosixPath('/tmp/pytest-of-lengau/pytest-60/test_chroot_no_resolv_conf0/dir1/dev') not found. Skipping WARNING craft_parts.overlays.chroot:chroot.py:227 [pid=3401891] umount: PosixPath('/tmp/pytest-of-lengau/pytest-60/test_chroot_no_resolv_conf0/dir1/dev') not found! WARNING craft_parts.overlays.chroot:chroot.py:227 [pid=3401891] umount: PosixPath('/tmp/pytest-of-lengau/pytest-60/test_chroot_no_resolv_conf0/dir1/sys') not found! WARNING craft_parts.overlays.chroot:chroot.py:227 [pid=3401891] umount: PosixPath('/tmp/pytest-of-lengau/pytest-60/test_chroot_no_resolv_conf0/dir1/proc') not found! ________________________________________________________________________________________________________ TestChroot.test_chroot_use_host_sources ________________________________________________________________________________________________________ self = <tests.unit.overlays.test_chroot.TestChroot object at 0x7b4c498717b0>, mocker = <pytest_mock.plugin.MockerFixture object at 0x7b4c4974e030>, new_dir = local('/tmp/pytest-of-lengau/pytest-60/test_chroot_use_host_sources0') mock_chroot = <Mock spec='builtin_function_or_method' id='135567579906800'> def test_chroot_use_host_sources(self, mocker, new_dir, mock_chroot): mock_mount = mocker.patch("craft_parts.utils.os_utils.mount") mock_umount = mocker.patch("craft_parts.utils.os_utils.umount") mock_copytree = mocker.patch("shutil.copytree") spy_process = mocker.spy(multiprocessing, "Process") new_root = Path(new_dir, "dir1") # this runs in the child process Path("dir1").mkdir() for subdir in ["etc", "proc", "sys", "dev", "dev/shm"]: Path(new_root, subdir).mkdir() > chroot.chroot(new_root, target_func, "content", use_host_sources=True) /home/lengau/Work/Code/craft-parts/tests/unit/overlays/test_chroot.py:205: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ path = PosixPath('/tmp/pytest-of-lengau/pytest-60/test_chroot_use_host_sources0/dir1'), target = <function target_func at 0x7b4c498a3740>, use_host_sources = True, args = ('content',), kwargs = {} parent_conn = <multiprocessing.connection.Connection object at 0x7b4c498d98d0>, child_conn = <multiprocessing.connection.Connection object at 0x7b4c498d99b0>, child = <Process name='Process-5' pid=3401903 parent=3401891 stopped exitcode=0> res = None, err = "[Errno 1] Operation not permitted: '/tmp/pytest-of-lengau/pytest-60/test_chroot_use_host_sources0/dir1'" def chroot( path: Path, target: Callable[..., _T], *args: Any, use_host_sources: bool = False, **kwargs: Any, ) -> _T: """Execute a callable in a chroot environment. :param path: The new filesystem root. :param target: The callable to run in the chroot environment. :param args: Arguments for target. :param kwargs: Keyword arguments for target. :param use_host_sources: Whether overlay steps should also include the repository sources defined on the host. :returns: The target function return value. """ logger.debug("[pid=%d] parent process", os.getpid()) # This typehint technically should be "Connection[Any, tuple[_T, None] | tuple[None, str]]" # However, types surrounding multiprocessing are finnicky at best and the way we handle the # result here makes the typehint effectively true, since we don't attempt to access the first # field of the tuple unless the second field is None. parent_conn: Connection[Any, tuple[_T, str | None]] parent_conn, child_conn = multiprocessing.Pipe() child = multiprocessing.Process( target=_runner, args=(Path(path), child_conn, target, args, kwargs) ) logger.debug("[pid=%d] set up chroot", os.getpid()) _setup_chroot(path, use_host_sources=use_host_sources) try: child.start() res, err = parent_conn.recv() child.join() finally: logger.debug("[pid=%d] clean up chroot", os.getpid()) _cleanup_chroot(path, use_host_sources=use_host_sources) if isinstance(err, str): > raise errors.OverlayChrootExecutionError(err) E craft_parts.overlays.errors.OverlayChrootExecutionError: Overlay environment execution error: [Errno 1] Operation not permitted: '/tmp/pytest-of-lengau/pytest-60/test_chroot_use_host_sources0/dir1' /home/lengau/Work/Code/craft-parts/craft_parts/overlays/chroot.py:84: OverlayChrootExecutionError ------------------------------------------------------------------------------------------------------------------- Captured log call ------------------------------------------------------------------------------------------------------------------- WARNING craft_parts.overlays.chroot:chroot.py:215 [pid=3401891] mount: PosixPath('/tmp/pytest-of-lengau/pytest-60/test_chroot_use_host_sources0/dir1/usr/share/keyrings') not found. Skipping WARNING craft_parts.overlays.chroot:chroot.py:227 [pid=3401891] umount: PosixPath('/tmp/pytest-of-lengau/pytest-60/test_chroot_use_host_sources0/dir1/usr/share/keyrings') not found! ----------------------------------------------------------------------------------------------------------------- Captured log teardown ----------------------------------------------------------------------------------------------------------------- WARNING root:features.py:42 deleting current features configuration ================================================================================================================ short test summary info ================================================================================================================ FAILED tests/unit/overlays/test_chroot.py::TestChroot::test_chroot - craft_parts.overlays.errors.OverlayChrootExecutionError: Overlay environment execution error: [Errno 1] Operation not permitted: '/tmp/pytest-of-lengau/pytest-60/test_chroot0/dir1' FAILED tests/unit/overlays/test_chroot.py::TestChroot::test_chroot_no_mountpoints - craft_parts.overlays.errors.OverlayChrootExecutionError: Overlay environment execution error: [Errno 1] Operation not permitted: '/tmp/pytest-of-lengau/pytest-60/test_chroot_no_mountpoints0/dir1' FAILED tests/unit/overlays/test_chroot.py::TestChroot::test_chroot_symlinked_resolv_conf - craft_parts.overlays.errors.OverlayChrootExecutionError: Overlay environment execution error: [Errno 1] Operation not permitted: '/tmp/pytest-of-lengau/pytest-60/test_chroot_symlinked_resolv_c0/dir1' FAILED tests/unit/overlays/test_chroot.py::TestChroot::test_chroot_no_resolv_conf - craft_parts.overlays.errors.OverlayChrootExecutionError: Overlay environment execution error: [Errno 1] Operation not permitted: '/tmp/pytest-of-lengau/pytest-60/test_chroot_no_resolv_conf0/dir1' FAILED tests/unit/overlays/test_chroot.py::TestChroot::test_chroot_use_host_sources - craft_parts.overlays.errors.OverlayChrootExecutionError: Overlay environment execution error: [Errno 1] Operation not permitted: '/tmp/pytest-of-lengau/pytest-60/test_chroot_use_host_sources0/dir1' ============================================================================================================== 5 failed, 2 passed in 2.64s ============================================================================================================== ```

Evaluation history

Date Model Scores Action Summary
stealth/space-bunny-alpha — closed resolved Python 3.14 chroot failures were fixed by explicitly using the multiprocessing fork context in #1672 (commit 12da4851).
stealth/space-bunny-alpha — closed not a bug Closed as triaged after Python 3.14's default change from fork to forkserver was identified as the likely cause; explicitly using fork in chroot() was proposed.
qwen/qwen3.6-35b-a3b — — Closed after a contributor identified Python 3.14's default multiprocessing start method change to forkserver as the cause of chroot failures. Explicitly using the fork start method was proposed as the resolution.
qwen/qwen3.6-35b-a3b
Staleness: 15
Complexity: 25
Confidence: 95
Support Request: 0
keep open Craft Parts chroot tests fail on Python 3.14 due to multiprocessing start method changes. Issue is triaged and a contributor has suggested switching to the fork start method.
qwen/qwen3.6-35b-a3b
Staleness: 20
Complexity: 45
Confidence: 85
Support Request: 10
keep open Fails chroot tests on Python 3.14 due to multiprocessing start method changes. Triaged as a bug; a contributor suggested explicitly using the fork start method to resolve the Operation not permitted error.
qwen/qwen3.6-35b-a3b
Staleness: 10
Complexity: 45
Confidence: 90
Support Request: 5
keep open Unit tests for chroot functionality fail on Python 3.14 due to multiprocessing changes, raising permission errors. The issue is triaged and awaiting investigation.
qwen3.6-35b-a3b-mtp-q6
Staleness: 5
Complexity: 45
Confidence: 90
Support Request: 10
keep open Reports test failures in Craft Parts when running on Python 3.14 due to multiprocessing changes. The issue is triaged and labeled as a bug, awaiting investigation and a fix.

Update history

Date Change
closed
updated

Related work

  • Likely Fixed By: canonical/craft-parts#1672 (confidence 100%)

    Commit 12da4851 explicitly selects the fork multiprocessing context for chroot execution.

Related issues

Issue Project State Summary Similarity
#1672 fix: use fork context for chroot in python 3.14 craft-parts merged Merged PR canonical/craft-parts#1672 (commit 12da4851): chroot() now explicitly uses the multiprocessing 'fork' start context, restoring parent chroot state on Python 3.14. Closes #1642.
74%