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()
.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(
pep508_rs::Requirement::from_str("jupyter").unwrap(),
)

View file

@ -51,13 +51,6 @@ impl Cache {
impl TryFrom<CacheArgs> for Cache {
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> {
Cache::from_settings(value.no_cache, value.cache_dir)
}

View file

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

View file

@ -11,7 +11,7 @@ use uv_client::RegistryClientBuilder;
#[tokio::test]
async fn remote_metadata_with_and_without_cache() -> Result<()> {
let cache = Cache::temp()?;
let cache = Cache::temp()?.init()?;
let client = RegistryClientBuilder::new(cache).build();
// 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
let cache = Cache::temp()?;
let cache = Cache::temp()?.init()?;
let client = RegistryClientBuilder::new(cache).build();
// Send request to our dummy server
@ -122,7 +122,7 @@ async fn test_user_agent_has_linehaul() -> Result<()> {
.unwrap();
// Initialize uv-client
let cache = Cache::temp()?;
let cache = Cache::temp()?.init()?;
let mut builder = RegistryClientBuilder::new(cache).markers(&markers);
let linux = Platform::new(

View file

@ -53,7 +53,7 @@ pub(crate) async fn build(args: BuildArgs) -> Result<PathBuf> {
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 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<()> {
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 {
python

View file

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

View file

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

View file

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

View file

@ -123,15 +123,15 @@ async fn resolve(
markers: &'static MarkerEnvironment,
tags: &Tags,
) -> 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 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 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 installed_packages = EmptyInstalledPackages;
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.
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
.src_file
.into_iter()
@ -247,7 +248,8 @@ async fn run() -> Result<ExitStatus> {
// Resolve the settings from the command-line arguments and workspace configuration.
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
.src_file
.into_iter()
@ -288,10 +290,12 @@ async fn run() -> Result<ExitStatus> {
command: PipCommand::Install(args),
}) => {
args.compat_args.validate()?;
// Resolve the settings from the command-line arguments and workspace configuration.
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
.package
.into_iter()
@ -360,6 +364,9 @@ async fn run() -> Result<ExitStatus> {
// Resolve the settings from the command-line arguments and workspace configuration.
let args = PipUninstallSettings::resolve(args, workspace);
// Initialize the cache.
let cache = cache.init()?;
let sources = args
.package
.into_iter()
@ -391,6 +398,9 @@ async fn run() -> Result<ExitStatus> {
// Resolve the settings from the command-line arguments and workspace configuration.
let args = PipFreezeSettings::resolve(args, workspace);
// Initialize the cache.
let cache = cache.init()?;
commands::pip_freeze(
args.exclude_editable,
args.shared.strict,
@ -408,6 +418,9 @@ async fn run() -> Result<ExitStatus> {
// Resolve the settings from the command-line arguments and workspace configuration.
let args = PipListSettings::resolve(args, workspace);
// Initialize the cache.
let cache = cache.init()?;
commands::pip_list(
args.editable,
args.exclude_editable,
@ -426,6 +439,9 @@ async fn run() -> Result<ExitStatus> {
// Resolve the settings from the command-line arguments and workspace configuration.
let args = PipShowSettings::resolve(args, workspace);
// Initialize the cache.
let cache = cache.init()?;
commands::pip_show(
args.package,
args.shared.strict,
@ -441,6 +457,9 @@ async fn run() -> Result<ExitStatus> {
// Resolve the settings from the command-line arguments and workspace configuration.
let args = PipCheckSettings::resolve(args, workspace);
// Initialize the cache.
let cache = cache.init()?;
commands::pip_check(
args.shared.python.as_deref(),
args.shared.system,
@ -467,6 +486,9 @@ async fn run() -> Result<ExitStatus> {
// Resolve the settings from the command-line arguments and workspace configuration.
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.
let prompt = args.prompt.or_else(|| {
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.
let args = settings::RunSettings::resolve(args, workspace);
// Initialize the cache.
let cache = cache.init()?;
let requirements = args
.with
.into_iter()
@ -535,12 +560,18 @@ async fn run() -> Result<ExitStatus> {
// Resolve the settings from the command-line arguments and workspace configuration.
let _args = settings::SyncSettings::resolve(args, workspace);
// Initialize the cache.
let cache = cache.init()?;
commands::sync(globals.preview, &cache, printer).await
}
Commands::Lock(args) => {
// Resolve the settings from the command-line arguments and workspace configuration.
let _args = settings::LockSettings::resolve(args, workspace);
// Initialize the cache.
let cache = cache.init()?;
commands::lock(globals.preview, &cache, printer).await
}
#[cfg(feature = "self-update")]

View file

@ -374,7 +374,7 @@ pub fn python_path_with_versions(
temp_dir: &assert_fs::TempDir,
python_versions: &[&str],
) -> 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
.iter()
.flat_map(|python_version| {