Optional managed Python archive download cache (#12175)

Part of #11834

Currently, all Python installation are a streaming download-and-extract.
With this PR, we add the `UV_PYTHON_CACHE_DIR` variable. When set, the
installation is split into downloading the interpreter into
`UV_PYTHON_CACHE_DIR` and extracting it there from a second step. If the
archive is already present in `UV_PYTHON_CACHE_DIR`, we skip the
download.

The feature can be used to speed up tests and CI. Locally for me, `cargo
test -p uv -- python_install` goes from 43s to 7s (1,7s in release mode)
when setting `UV_PYTHON_CACHE_DIR`. It can also be used for offline
installation of Python interpreter, by copying the archives to a
directory in the offline machine, while the path rewriting is still
performed on the target machine on installation.
This commit is contained in:
konsti
2025-04-28 12:09:09 +02:00
committed by GitHub
parent cfe82dc22a
commit b33a19689c
7 changed files with 371 additions and 96 deletions
+97 -1
View File
@@ -1,4 +1,4 @@
use std::{path::Path, process::Command};
use std::{env, path::Path, process::Command};
use crate::common::{uv_snapshot, TestContext};
use assert_fs::{
@@ -6,6 +6,7 @@ use assert_fs::{
prelude::{FileTouch, PathChild, PathCreateDir},
};
use predicates::prelude::predicate;
use tracing::debug;
use uv_fs::Simplified;
use uv_static::EnvVars;
@@ -1274,3 +1275,98 @@ fn python_install_314() {
----- stderr -----
");
}
/// Test caching Python archives with `UV_PYTHON_CACHE_DIR`.
#[test]
fn python_install_cached() {
// It does not make sense to run this test when the developer selected faster test runs
// by setting the env var.
if env::var_os("UV_PYTHON_CACHE_DIR").is_some() {
debug!("Skipping test because UV_PYTHON_CACHE_DIR is set");
return;
}
let context: TestContext = TestContext::new_with_versions(&[])
.with_filtered_python_keys()
.with_filtered_exe_suffix()
.with_managed_python_dirs();
let python_cache = context.temp_dir.child("python-cache");
// Install the latest version
uv_snapshot!(context.filters(), context
.python_install()
.env(EnvVars::UV_PYTHON_CACHE_DIR, python_cache.as_ref()), @r"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Installed Python 3.13.3 in [TIME]
+ cpython-3.13.3-[PLATFORM]
");
let bin_python = context
.bin_dir
.child(format!("python3.13{}", std::env::consts::EXE_SUFFIX));
// The executable should not be installed in the bin directory (requires preview)
bin_python.assert(predicate::path::missing());
// Should be a no-op when already installed
uv_snapshot!(context.filters(), context
.python_install()
.env(EnvVars::UV_PYTHON_CACHE_DIR, python_cache.as_ref()), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Python is already installed. Use `uv python install <request>` to install another version.
"###);
uv_snapshot!(context.filters(), context.python_uninstall().arg("3.13"), @r"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Searching for Python versions matching: Python 3.13
Uninstalled Python 3.13.3 in [TIME]
- cpython-3.13.3-[PLATFORM]
");
// The cached archive can be installed offline
uv_snapshot!(context.filters(), context
.python_install()
.arg("--offline")
.env(EnvVars::UV_PYTHON_CACHE_DIR, python_cache.as_ref()), @r"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Installed Python 3.13.3 in [TIME]
+ cpython-3.13.3-[PLATFORM]
");
// 3.12 isn't cached, so it can't be installed
let mut filters = context.filters();
filters.push((
"cpython-3.12.10.*.tar.gz",
"cpython-3.12.10[DATE]-[PLATFORM].tar.gz",
));
uv_snapshot!(filters, context
.python_install()
.arg("3.12")
.arg("--offline")
.env(EnvVars::UV_PYTHON_CACHE_DIR, python_cache.as_ref()), @r"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
error: Failed to install cpython-3.12.10-[PLATFORM]
Caused by: An offline Python installation was requested, but cpython-3.12.10[DATE]-[PLATFORM].tar.gz) is missing in python-cache
");
}