Separate cache construction from initialization (#3607)

## Summary

Ensures that we only initialize the cache for commands that require it.

Closes https://github.com/astral-sh/uv/issues/3539.
This commit is contained in:
Charlie Marsh 2024-05-15 12:29:39 -04:00 committed by GitHub
parent 647f38be31
commit 55aedda379
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 78 additions and 49 deletions

View file

@ -13,7 +13,7 @@ fn resolve_warm_jupyter(c: &mut Criterion<WallTime>) {
.build() .build()
.unwrap(); .unwrap();
let cache = &Cache::from_path(".cache").unwrap(); let cache = &Cache::from_path(".cache").unwrap().init().unwrap();
let manifest = &Manifest::simple(vec![Requirement::from_pep508( let manifest = &Manifest::simple(vec![Requirement::from_pep508(
pep508_rs::Requirement::from_str("jupyter").unwrap(), pep508_rs::Requirement::from_str("jupyter").unwrap(),
) )

View file

@ -51,13 +51,6 @@ impl Cache {
impl TryFrom<CacheArgs> for Cache { impl TryFrom<CacheArgs> for Cache {
type Error = io::Error; type Error = io::Error;
/// Prefer, in order:
/// 1. A temporary cache directory, if the user requested `--no-cache`.
/// 2. The specific cache directory specified by the user via `--cache-dir` or `UV_CACHE_DIR`.
/// 3. The system-appropriate cache directory.
/// 4. A `.uv_cache` directory in the current working directory.
///
/// Returns an absolute cache dir.
fn try_from(value: CacheArgs) -> Result<Self, Self::Error> { fn try_from(value: CacheArgs) -> Result<Self, Self::Error> {
Cache::from_settings(value.no_cache, value.cache_dir) Cache::from_settings(value.no_cache, value.cache_dir)
} }

View file

@ -128,7 +128,7 @@ impl Cache {
/// A persistent cache directory at `root`. /// A persistent cache directory at `root`.
pub fn from_path(root: impl Into<PathBuf>) -> Result<Self, io::Error> { pub fn from_path(root: impl Into<PathBuf>) -> Result<Self, io::Error> {
Ok(Self { Ok(Self {
root: Self::init(root)?, root: root.into(),
refresh: Refresh::None, refresh: Refresh::None,
_temp_dir_drop: None, _temp_dir_drop: None,
}) })
@ -138,7 +138,7 @@ impl Cache {
pub fn temp() -> Result<Self, io::Error> { pub fn temp() -> Result<Self, io::Error> {
let temp_dir = tempdir()?; let temp_dir = tempdir()?;
Ok(Self { Ok(Self {
root: Self::init(temp_dir.path())?, root: temp_dir.path().to_path_buf(),
refresh: Refresh::None, refresh: Refresh::None,
_temp_dir_drop: Some(Arc::new(temp_dir)), _temp_dir_drop: Some(Arc::new(temp_dir)),
}) })
@ -243,15 +243,15 @@ impl Cache {
Ok(id) Ok(id)
} }
/// Initialize a directory for use as a cache. /// Initialize the cache.
fn init(root: impl Into<PathBuf>) -> Result<PathBuf, io::Error> { pub fn init(self) -> Result<Self, io::Error> {
let root = root.into(); let root = &self.root;
// Create the cache directory, if it doesn't exist. // Create the cache directory, if it doesn't exist.
fs::create_dir_all(&root)?; fs::create_dir_all(root)?;
// Add the CACHEDIR.TAG. // Add the CACHEDIR.TAG.
cachedir::ensure_tag(&root)?; cachedir::ensure_tag(root)?;
// Add the .gitignore. // Add the .gitignore.
match fs::OpenOptions::new() match fs::OpenOptions::new()
@ -289,7 +289,10 @@ impl Cache {
.write(true) .write(true)
.open(root.join(CacheBucket::BuiltWheels.to_str()).join(".git"))?; .open(root.join(CacheBucket::BuiltWheels.to_str()).join(".git"))?;
fs::canonicalize(root) Ok(Self {
root: fs::canonicalize(root)?,
..self
})
} }
/// Clear the cache, removing all entries. /// Clear the cache, removing all entries.

View file

@ -11,7 +11,7 @@ use uv_client::RegistryClientBuilder;
#[tokio::test] #[tokio::test]
async fn remote_metadata_with_and_without_cache() -> Result<()> { async fn remote_metadata_with_and_without_cache() -> Result<()> {
let cache = Cache::temp()?; let cache = Cache::temp()?.init()?;
let client = RegistryClientBuilder::new(cache).build(); let client = RegistryClientBuilder::new(cache).build();
// The first run is without cache (the tempdir is empty), the second has the cache from the // The first run is without cache (the tempdir is empty), the second has the cache from the

View file

@ -48,7 +48,7 @@ async fn test_user_agent_has_version() -> Result<()> {
}); });
// Initialize uv-client // Initialize uv-client
let cache = Cache::temp()?; let cache = Cache::temp()?.init()?;
let client = RegistryClientBuilder::new(cache).build(); let client = RegistryClientBuilder::new(cache).build();
// Send request to our dummy server // Send request to our dummy server
@ -122,7 +122,7 @@ async fn test_user_agent_has_linehaul() -> Result<()> {
.unwrap(); .unwrap();
// Initialize uv-client // Initialize uv-client
let cache = Cache::temp()?; let cache = Cache::temp()?.init()?;
let mut builder = RegistryClientBuilder::new(cache).markers(&markers); let mut builder = RegistryClientBuilder::new(cache).markers(&markers);
let linux = Platform::new( let linux = Platform::new(

View file

@ -53,7 +53,7 @@ pub(crate) async fn build(args: BuildArgs) -> Result<PathBuf> {
BuildKind::Wheel BuildKind::Wheel
}; };
let cache = Cache::try_from(args.cache_args)?; let cache = Cache::try_from(args.cache_args)?.init()?;
let venv = PythonEnvironment::from_virtualenv(&cache)?; let venv = PythonEnvironment::from_virtualenv(&cache)?;
let client = RegistryClientBuilder::new(cache.clone()).build(); let client = RegistryClientBuilder::new(cache.clone()).build();

View file

@ -15,7 +15,7 @@ pub(crate) struct CompileArgs {
} }
pub(crate) async fn compile(args: CompileArgs) -> anyhow::Result<()> { pub(crate) async fn compile(args: CompileArgs) -> anyhow::Result<()> {
let cache = Cache::try_from(args.cache_args)?; let cache = Cache::try_from(args.cache_args)?.init()?;
let interpreter = if let Some(python) = args.python { let interpreter = if let Some(python) = args.python {
python python

View file

@ -18,9 +18,8 @@ pub(crate) struct WheelMetadataArgs {
} }
pub(crate) async fn wheel_metadata(args: WheelMetadataArgs) -> Result<()> { pub(crate) async fn wheel_metadata(args: WheelMetadataArgs) -> Result<()> {
let cache_dir = Cache::try_from(args.cache_args)?; let cache = Cache::try_from(args.cache_args)?.init()?;
let client = RegistryClientBuilder::new(cache).build();
let client = RegistryClientBuilder::new(cache_dir.clone()).build();
let filename = WheelFilename::from_str( let filename = WheelFilename::from_str(
args.url args.url

View file

@ -727,12 +727,12 @@ mod windows {
#[test] #[test]
#[cfg_attr(not(windows), ignore)] #[cfg_attr(not(windows), ignore)]
fn no_such_python_path() { fn no_such_python_path() {
let result = let cache = Cache::temp().unwrap().init().unwrap();
find_requested_python(r"C:\does\not\exists\python3.12", &Cache::temp().unwrap()) let result = find_requested_python(r"C:\does\not\exists\python3.12", &cache)
.unwrap() .unwrap()
.ok_or(Error::RequestedPythonNotFound( .ok_or(Error::RequestedPythonNotFound(
r"C:\does\not\exists\python3.12".to_string(), r"C:\does\not\exists\python3.12".to_string(),
)); ));
assert_snapshot!( assert_snapshot!(
format_err(result), format_err(result),
@"Failed to locate Python interpreter at: `C:\\does\\not\\exists\\python3.12`" @"Failed to locate Python interpreter at: `C:\\does\\not\\exists\\python3.12`"
@ -760,8 +760,9 @@ mod tests {
#[test] #[test]
#[cfg_attr(not(unix), ignore)] #[cfg_attr(not(unix), ignore)]
fn no_such_python_version() { fn no_such_python_version() {
let cache = Cache::temp().unwrap().init().unwrap();
let request = "3.1000"; let request = "3.1000";
let result = find_requested_python(request, &Cache::temp().unwrap()) let result = find_requested_python(request, &cache)
.unwrap() .unwrap()
.ok_or(Error::NoSuchPython(request.to_string())); .ok_or(Error::NoSuchPython(request.to_string()));
assert_snapshot!( assert_snapshot!(
@ -773,8 +774,9 @@ mod tests {
#[test] #[test]
#[cfg_attr(not(unix), ignore)] #[cfg_attr(not(unix), ignore)]
fn no_such_python_binary() { fn no_such_python_binary() {
let cache = Cache::temp().unwrap().init().unwrap();
let request = "python3.1000"; let request = "python3.1000";
let result = find_requested_python(request, &Cache::temp().unwrap()) let result = find_requested_python(request, &cache)
.unwrap() .unwrap()
.ok_or(Error::NoSuchPython(request.to_string())); .ok_or(Error::NoSuchPython(request.to_string()));
assert_snapshot!( assert_snapshot!(
@ -786,7 +788,8 @@ mod tests {
#[test] #[test]
#[cfg_attr(not(unix), ignore)] #[cfg_attr(not(unix), ignore)]
fn no_such_python_path() { fn no_such_python_path() {
let result = find_requested_python("/does/not/exists/python3.12", &Cache::temp().unwrap()) let cache = Cache::temp().unwrap().init().unwrap();
let result = find_requested_python("/does/not/exists/python3.12", &cache)
.unwrap() .unwrap()
.ok_or(Error::RequestedPythonNotFound( .ok_or(Error::RequestedPythonNotFound(
"/does/not/exists/python3.12".to_string(), "/does/not/exists/python3.12".to_string(),

View file

@ -677,7 +677,7 @@ mod tests {
} }
"##}; "##};
let cache = Cache::temp().unwrap(); let cache = Cache::temp().unwrap().init().unwrap();
fs::write( fs::write(
&mocked_interpreter, &mocked_interpreter,

View file

@ -112,12 +112,12 @@ mod tests {
#[test] #[test]
#[cfg_attr(not(windows), ignore)] #[cfg_attr(not(windows), ignore)]
fn no_such_python_path() { fn no_such_python_path() {
let result = let cache = Cache::temp().unwrap().init().unwrap();
find_requested_python(r"C:\does\not\exists\python3.12", &Cache::temp().unwrap()) let result = find_requested_python(r"C:\does\not\exists\python3.12", &cache)
.unwrap() .unwrap()
.ok_or(Error::RequestedPythonNotFound( .ok_or(Error::RequestedPythonNotFound(
r"C:\does\not\exists\python3.12".to_string(), r"C:\does\not\exists\python3.12".to_string(),
)); ));
assert_snapshot!( assert_snapshot!(
format_err(result), format_err(result),
@"Failed to locate Python interpreter at: `C:\\does\\not\\exists\\python3.12`" @"Failed to locate Python interpreter at: `C:\\does\\not\\exists\\python3.12`"

View file

@ -123,15 +123,15 @@ async fn resolve(
markers: &'static MarkerEnvironment, markers: &'static MarkerEnvironment,
tags: &Tags, tags: &Tags,
) -> Result<ResolutionGraph> { ) -> Result<ResolutionGraph> {
let client = RegistryClientBuilder::new(Cache::temp()?).build(); let cache = Cache::temp().unwrap().init().unwrap();
let real_interpreter = find_default_python(&cache).expect("Expected a python to be installed");
let client = RegistryClientBuilder::new(cache).build();
let flat_index = FlatIndex::default(); let flat_index = FlatIndex::default();
let index = InMemoryIndex::default(); let index = InMemoryIndex::default();
// TODO(konstin): Should we also use the bootstrapped pythons here?
let real_interpreter =
find_default_python(&Cache::temp().unwrap()).expect("Expected a python to be installed");
let interpreter = Interpreter::artificial(real_interpreter.platform().clone(), markers.clone()); let interpreter = Interpreter::artificial(real_interpreter.platform().clone(), markers.clone());
let python_requirement = PythonRequirement::from_marker_environment(&interpreter, markers); let python_requirement = PythonRequirement::from_marker_environment(&interpreter, markers);
let build_context = DummyContext::new(Cache::temp()?, interpreter.clone()); let cache = Cache::temp().unwrap().init().unwrap();
let build_context = DummyContext::new(cache, interpreter.clone());
let hashes = HashStrategy::None; let hashes = HashStrategy::None;
let installed_packages = EmptyInstalledPackages; let installed_packages = EmptyInstalledPackages;
let concurrency = Concurrency::default(); let concurrency = Concurrency::default();

View file

@ -177,7 +177,8 @@ async fn run() -> Result<ExitStatus> {
// Resolve the settings from the command-line arguments and workspace configuration. // Resolve the settings from the command-line arguments and workspace configuration.
let args = PipCompileSettings::resolve(args, workspace); let args = PipCompileSettings::resolve(args, workspace);
let cache = cache.with_refresh(args.refresh); // Initialize the cache.
let cache = cache.init()?.with_refresh(args.refresh);
let requirements = args let requirements = args
.src_file .src_file
.into_iter() .into_iter()
@ -247,7 +248,8 @@ async fn run() -> Result<ExitStatus> {
// Resolve the settings from the command-line arguments and workspace configuration. // Resolve the settings from the command-line arguments and workspace configuration.
let args = PipSyncSettings::resolve(args, workspace); let args = PipSyncSettings::resolve(args, workspace);
let cache = cache.with_refresh(args.refresh); // Initialize the cache.
let cache = cache.init()?.with_refresh(args.refresh);
let sources = args let sources = args
.src_file .src_file
.into_iter() .into_iter()
@ -288,10 +290,12 @@ async fn run() -> Result<ExitStatus> {
command: PipCommand::Install(args), command: PipCommand::Install(args),
}) => { }) => {
args.compat_args.validate()?; args.compat_args.validate()?;
// Resolve the settings from the command-line arguments and workspace configuration. // Resolve the settings from the command-line arguments and workspace configuration.
let args = PipInstallSettings::resolve(args, workspace); let args = PipInstallSettings::resolve(args, workspace);
let cache = cache.with_refresh(args.refresh); // Initialize the cache.
let cache = cache.init()?.with_refresh(args.refresh);
let requirements = args let requirements = args
.package .package
.into_iter() .into_iter()
@ -360,6 +364,9 @@ async fn run() -> Result<ExitStatus> {
// Resolve the settings from the command-line arguments and workspace configuration. // Resolve the settings from the command-line arguments and workspace configuration.
let args = PipUninstallSettings::resolve(args, workspace); let args = PipUninstallSettings::resolve(args, workspace);
// Initialize the cache.
let cache = cache.init()?;
let sources = args let sources = args
.package .package
.into_iter() .into_iter()
@ -391,6 +398,9 @@ async fn run() -> Result<ExitStatus> {
// Resolve the settings from the command-line arguments and workspace configuration. // Resolve the settings from the command-line arguments and workspace configuration.
let args = PipFreezeSettings::resolve(args, workspace); let args = PipFreezeSettings::resolve(args, workspace);
// Initialize the cache.
let cache = cache.init()?;
commands::pip_freeze( commands::pip_freeze(
args.exclude_editable, args.exclude_editable,
args.shared.strict, args.shared.strict,
@ -408,6 +418,9 @@ async fn run() -> Result<ExitStatus> {
// Resolve the settings from the command-line arguments and workspace configuration. // Resolve the settings from the command-line arguments and workspace configuration.
let args = PipListSettings::resolve(args, workspace); let args = PipListSettings::resolve(args, workspace);
// Initialize the cache.
let cache = cache.init()?;
commands::pip_list( commands::pip_list(
args.editable, args.editable,
args.exclude_editable, args.exclude_editable,
@ -426,6 +439,9 @@ async fn run() -> Result<ExitStatus> {
// Resolve the settings from the command-line arguments and workspace configuration. // Resolve the settings from the command-line arguments and workspace configuration.
let args = PipShowSettings::resolve(args, workspace); let args = PipShowSettings::resolve(args, workspace);
// Initialize the cache.
let cache = cache.init()?;
commands::pip_show( commands::pip_show(
args.package, args.package,
args.shared.strict, args.shared.strict,
@ -441,6 +457,9 @@ async fn run() -> Result<ExitStatus> {
// Resolve the settings from the command-line arguments and workspace configuration. // Resolve the settings from the command-line arguments and workspace configuration.
let args = PipCheckSettings::resolve(args, workspace); let args = PipCheckSettings::resolve(args, workspace);
// Initialize the cache.
let cache = cache.init()?;
commands::pip_check( commands::pip_check(
args.shared.python.as_deref(), args.shared.python.as_deref(),
args.shared.system, args.shared.system,
@ -467,6 +486,9 @@ async fn run() -> Result<ExitStatus> {
// Resolve the settings from the command-line arguments and workspace configuration. // Resolve the settings from the command-line arguments and workspace configuration.
let args = settings::VenvSettings::resolve(args, workspace); let args = settings::VenvSettings::resolve(args, workspace);
// Initialize the cache.
let cache = cache.init()?;
// Since we use ".venv" as the default name, we use "." as the default prompt. // Since we use ".venv" as the default name, we use "." as the default prompt.
let prompt = args.prompt.or_else(|| { let prompt = args.prompt.or_else(|| {
if args.name == PathBuf::from(".venv") { if args.name == PathBuf::from(".venv") {
@ -499,6 +521,9 @@ async fn run() -> Result<ExitStatus> {
// Resolve the settings from the command-line arguments and workspace configuration. // Resolve the settings from the command-line arguments and workspace configuration.
let args = settings::RunSettings::resolve(args, workspace); let args = settings::RunSettings::resolve(args, workspace);
// Initialize the cache.
let cache = cache.init()?;
let requirements = args let requirements = args
.with .with
.into_iter() .into_iter()
@ -535,12 +560,18 @@ async fn run() -> Result<ExitStatus> {
// Resolve the settings from the command-line arguments and workspace configuration. // Resolve the settings from the command-line arguments and workspace configuration.
let _args = settings::SyncSettings::resolve(args, workspace); let _args = settings::SyncSettings::resolve(args, workspace);
// Initialize the cache.
let cache = cache.init()?;
commands::sync(globals.preview, &cache, printer).await commands::sync(globals.preview, &cache, printer).await
} }
Commands::Lock(args) => { Commands::Lock(args) => {
// Resolve the settings from the command-line arguments and workspace configuration. // Resolve the settings from the command-line arguments and workspace configuration.
let _args = settings::LockSettings::resolve(args, workspace); let _args = settings::LockSettings::resolve(args, workspace);
// Initialize the cache.
let cache = cache.init()?;
commands::lock(globals.preview, &cache, printer).await commands::lock(globals.preview, &cache, printer).await
} }
#[cfg(feature = "self-update")] #[cfg(feature = "self-update")]

View file

@ -374,7 +374,7 @@ pub fn python_path_with_versions(
temp_dir: &assert_fs::TempDir, temp_dir: &assert_fs::TempDir,
python_versions: &[&str], python_versions: &[&str],
) -> anyhow::Result<OsString> { ) -> anyhow::Result<OsString> {
let cache = Cache::from_path(temp_dir.child("cache").to_path_buf())?; let cache = Cache::from_path(temp_dir.child("cache").to_path_buf())?.init()?;
let selected_pythons = python_versions let selected_pythons = python_versions
.iter() .iter()
.flat_map(|python_version| { .flat_map(|python_version| {