From f2f5efe1f7584ad6e31a804c52de51e0a622f62d Mon Sep 17 00:00:00 2001 From: Carl Csaposs Date: Fri, 6 Feb 2026 15:13:31 +0100 Subject: [PATCH 1/2] feat(base): cache uv directory Based on https://github.com/canonical/craft-providers/pull/394 For https://github.com/canonical/charmcraft/issues/2461 --- craft_providers/base.py | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/craft_providers/base.py b/craft_providers/base.py index e2ad77aaf..a1309e525 100644 --- a/craft_providers/base.py +++ b/craft_providers/base.py @@ -835,22 +835,24 @@ def _mount_shared_cache_dirs(self, executor: Executor) -> None: ) guest_base_cache_path = pathlib.Path(guest_cache_proc.stdout) - # PIP cache - host_pip_cache_path = host_base_cache_path / "pip" - host_pip_cache_path.mkdir(parents=True, exist_ok=True) + for tool in ("pip", "uv"): + host_tool_cache_path = host_base_cache_path / tool + host_tool_cache_path.mkdir(parents=True, exist_ok=True) - guest_pip_cache_path = guest_base_cache_path / "pip" - executor.execute_run( - ["mkdir", "-p", guest_pip_cache_path.as_posix()], - ) - - try: - executor.mount(host_source=host_pip_cache_path, target=guest_pip_cache_path) - except ProviderError as exc: - logger.warning( - "Failed to mount cache in instance. Proceeding without cache." + guest_tool_cache_path = guest_base_cache_path / tool + executor.execute_run( + ["mkdir", "-p", guest_tool_cache_path.as_posix()], ) - logger.debug(exc) + + try: + executor.mount( + host_source=host_tool_cache_path, target=guest_tool_cache_path + ) + except ProviderError as exc: + logger.warning( + "Failed to mount cache in instance. Proceeding without cache." + ) + logger.debug(exc) def _pre_setup_packages(self, executor: Executor) -> None: """Do anything before setting up the packages. From 3d523f7f2c43e7ca082a20abcc5db4a208e224b1 Mon Sep 17 00:00:00 2001 From: Carl Csaposs Date: Fri, 6 Feb 2026 15:26:18 +0100 Subject: [PATCH 2/2] Fix unit tests --- tests/unit/bases/test_ubuntu_buildd.py | 10 +++++++--- tests/unit/test_base.py | 25 ++++++++++++++++++++----- 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/tests/unit/bases/test_ubuntu_buildd.py b/tests/unit/bases/test_ubuntu_buildd.py index 565a498b4..06cecec7f 100644 --- a/tests/unit/bases/test_ubuntu_buildd.py +++ b/tests/unit/bases/test_ubuntu_buildd.py @@ -706,14 +706,18 @@ def test_mount_cache_dirs(fake_process, fake_executor, cache_path: pathlib.Path, fake_process.register( [*DEFAULT_FAKE_CMD, "mkdir", "-p", "/root/.cache/pip"], ) + fake_process.register( + [*DEFAULT_FAKE_CMD, "mkdir", "-p", "/root/.cache/uv"], + ) base._mount_shared_cache_dirs(fake_executor) expected_mounts = [ { - "host_source": host_cache_dir / "pip", - "target": user_cache_dir / "pip", - }, + "host_source": host_cache_dir / tool, + "target": user_cache_dir / tool, + } + for tool in ("pip", "uv") ] assert fake_executor.records_of_mount == expected_mounts diff --git a/tests/unit/test_base.py b/tests/unit/test_base.py index a62bf33ef..327f7eaa1 100644 --- a/tests/unit/test_base.py +++ b/tests/unit/test_base.py @@ -155,14 +155,23 @@ def test_mount_shared_cache_dirs(fake_process, fake_base, fake_executor, cache_d fake_process.register( [*DEFAULT_FAKE_CMD, "mkdir", "-p", "/root/.cache/pip"], ) + fake_process.register( + [*DEFAULT_FAKE_CMD, "mkdir", "-p", "/root/.cache/uv"], + ) fake_base._mount_shared_cache_dirs(fake_executor) - expected = { - "host_source": cache_dir.resolve() / "base-v7" / "FakeBaseAlias.TREBLE" / "pip", - "target": user_cache_dir / "pip", - } - assert fake_executor.records_of_mount == [expected] + expected = [ + { + "host_source": cache_dir.resolve() + / "base-v7" + / "FakeBaseAlias.TREBLE" + / tool, + "target": user_cache_dir / tool, + } + for tool in ("pip", "uv") + ] + assert fake_executor.records_of_mount == expected @pytest.mark.parametrize("cache_dir", [pathlib.Path("/tmp/fake-cache-dir")]) @@ -181,6 +190,9 @@ def test_mount_shared_cache_dirs_mkdir_failed( fake_process.register( [*DEFAULT_FAKE_CMD, "mkdir", "-p", "/root/.cache/pip"], ) + fake_process.register( + [*DEFAULT_FAKE_CMD, "mkdir", "-p", "/root/.cache/uv"], + ) mocker.patch("pathlib.Path.mkdir", side_effect=OSError) @@ -205,6 +217,9 @@ def test_mount_shared_cache_dirs_mount_failed( fake_process.register( [*DEFAULT_FAKE_CMD, "mkdir", "-p", "/root/.cache/pip"], ) + fake_process.register( + [*DEFAULT_FAKE_CMD, "mkdir", "-p", "/root/.cache/uv"], + ) mocker.patch("pathlib.Path.mkdir") # don't try to create the directory mocker.patch.object(fake_executor, "mount", side_effect=ProviderError(error_msg))