diff --git a/crates/gourgeist/src/main.rs b/crates/gourgeist/src/main.rs index 5c32e8a17..72618d08e 100644 --- a/crates/gourgeist/src/main.rs +++ b/crates/gourgeist/src/main.rs @@ -26,7 +26,7 @@ fn run() -> Result<(), gourgeist::Error> { let location = cli.path.unwrap_or(Utf8PathBuf::from(".venv")); let python = parse_python_cli(cli.python)?; let platform = Platform::current()?; - let info = InterpreterInfo::query_cached(python.as_std_path(), platform, None).unwrap(); + let info = InterpreterInfo::query(python.as_std_path(), platform, None).unwrap(); create_venv(location, &python, &info, cli.bare)?; Ok(()) diff --git a/crates/puffin-cli/src/commands/clean.rs b/crates/puffin-cli/src/commands/clean.rs index d608424aa..b6d15ad38 100644 --- a/crates/puffin-cli/src/commands/clean.rs +++ b/crates/puffin-cli/src/commands/clean.rs @@ -9,11 +9,7 @@ use crate::commands::ExitStatus; use crate::printer::Printer; /// Clear the cache. -pub(crate) fn clean(cache: Option<&Path>, mut printer: Printer) -> Result { - let Some(cache) = cache else { - return Err(anyhow::anyhow!("No cache found")); - }; - +pub(crate) fn clean(cache: &Path, mut printer: Printer) -> Result { if !cache.exists() { writeln!(printer, "No cache found at: {}", cache.display())?; return Ok(ExitStatus::Success); diff --git a/crates/puffin-cli/src/commands/freeze.rs b/crates/puffin-cli/src/commands/freeze.rs index 76d7fe634..4139ed21e 100644 --- a/crates/puffin-cli/src/commands/freeze.rs +++ b/crates/puffin-cli/src/commands/freeze.rs @@ -11,10 +11,10 @@ use crate::commands::ExitStatus; use crate::printer::Printer; /// Enumerate the installed packages in the current environment. -pub(crate) fn freeze(cache: Option<&Path>, _printer: Printer) -> Result { +pub(crate) fn freeze(cache: &Path, _printer: Printer) -> Result { // Detect the current Python interpreter. let platform = Platform::current()?; - let python = Virtualenv::from_env(platform, cache)?; + let python = Virtualenv::from_env(platform, Some(cache))?; debug!( "Using Python interpreter: {}", python.python_executable().display() diff --git a/crates/puffin-cli/src/commands/pip_compile.rs b/crates/puffin-cli/src/commands/pip_compile.rs index 1f497d717..0108ba628 100644 --- a/crates/puffin-cli/src/commands/pip_compile.rs +++ b/crates/puffin-cli/src/commands/pip_compile.rs @@ -37,7 +37,7 @@ pub(crate) async fn pip_compile( prerelease_mode: PreReleaseMode, upgrade_mode: UpgradeMode, index_urls: Option, - cache: Option<&Path>, + cache: &Path, mut printer: Printer, ) -> Result { let start = std::time::Instant::now(); @@ -88,7 +88,7 @@ pub(crate) async fn pip_compile( // Detect the current Python interpreter. let platform = Platform::current()?; - let venv = Virtualenv::from_env(platform, cache)?; + let venv = Virtualenv::from_env(platform, Some(cache))?; debug!( "Using Python {} at {}", @@ -105,7 +105,7 @@ pub(crate) async fn pip_compile( // Instantiate a client. let client = { let mut builder = RegistryClientBuilder::default(); - builder = builder.cache(cache); + builder = builder.cache(Some(cache)); if let Some(IndexUrls { index, extra_index }) = index_urls { if let Some(index) = index { builder = builder.index(index); @@ -119,7 +119,7 @@ pub(crate) async fn pip_compile( let build_dispatch = BuildDispatch::new( RegistryClientBuilder::default().build(), - cache.map(Path::to_path_buf), + cache.to_path_buf(), venv.interpreter_info().clone(), fs::canonicalize(venv.python_executable())?, ); diff --git a/crates/puffin-cli/src/commands/pip_sync.rs b/crates/puffin-cli/src/commands/pip_sync.rs index b11f395c6..79bfaef6f 100644 --- a/crates/puffin-cli/src/commands/pip_sync.rs +++ b/crates/puffin-cli/src/commands/pip_sync.rs @@ -28,7 +28,7 @@ pub(crate) async fn pip_sync( sources: &[RequirementsSource], link_mode: LinkMode, index_urls: Option, - cache: Option<&Path>, + cache: &Path, mut printer: Printer, ) -> Result { // Read all requirements from the provided sources. @@ -51,7 +51,7 @@ pub(crate) async fn sync_requirements( requirements: &[Requirement], link_mode: LinkMode, index_urls: Option, - cache: Option<&Path>, + cache: &Path, mut printer: Printer, ) -> Result { // Audit the requirements. @@ -59,7 +59,7 @@ pub(crate) async fn sync_requirements( // Detect the current Python interpreter. let platform = Platform::current()?; - let venv = Virtualenv::from_env(platform, cache)?; + let venv = Virtualenv::from_env(platform, Some(cache))?; debug!( "Using Python interpreter: {}", venv.python_executable().display() @@ -99,7 +99,7 @@ pub(crate) async fn sync_requirements( // Instantiate a client. let client = { let mut builder = RegistryClientBuilder::default(); - builder = builder.cache(cache); + builder = builder.cache(Some(cache)); if let Some(IndexUrls { index, extra_index }) = index_urls { if let Some(index) = index { builder = builder.index(index); @@ -163,7 +163,6 @@ pub(crate) async fn sync_requirements( }; // Unzip any downloaded distributions. - let staging = tempfile::tempdir()?; let unzips = if downloads.is_empty() { vec![] } else { @@ -173,7 +172,7 @@ pub(crate) async fn sync_requirements( .with_reporter(UnzipReporter::from(printer).with_length(downloads.len() as u64)); let unzips = unzipper - .unzip(downloads, cache.unwrap_or(staging.path())) + .unzip(downloads, cache) .await .context("Failed to download and unpack wheels")?; diff --git a/crates/puffin-cli/src/commands/pip_uninstall.rs b/crates/puffin-cli/src/commands/pip_uninstall.rs index 79d587676..35b992d82 100644 --- a/crates/puffin-cli/src/commands/pip_uninstall.rs +++ b/crates/puffin-cli/src/commands/pip_uninstall.rs @@ -15,7 +15,7 @@ use crate::requirements::{ExtrasSpecification, RequirementsSource, RequirementsS /// Uninstall packages from the current environment. pub(crate) async fn pip_uninstall( sources: &[RequirementsSource], - cache: Option<&Path>, + cache: &Path, mut printer: Printer, ) -> Result { let start = std::time::Instant::now(); @@ -29,7 +29,7 @@ pub(crate) async fn pip_uninstall( // Detect the current Python interpreter. let platform = Platform::current()?; - let venv = Virtualenv::from_env(platform, cache)?; + let venv = Virtualenv::from_env(platform, Some(cache))?; debug!( "Using Python interpreter: {}", venv.python_executable().display() diff --git a/crates/puffin-cli/src/commands/venv.rs b/crates/puffin-cli/src/commands/venv.rs index eb85b5f6c..1bfe7a2c2 100644 --- a/crates/puffin-cli/src/commands/venv.rs +++ b/crates/puffin-cli/src/commands/venv.rs @@ -65,7 +65,7 @@ fn venv_impl( }; let platform = Platform::current().into_diagnostic()?; - let interpreter_info = InterpreterInfo::query_cached(&base_python, platform, None) + let interpreter_info = InterpreterInfo::query(&base_python, platform, None) .map_err(VenvError::InterpreterError)?; writeln!( diff --git a/crates/puffin-cli/src/main.rs b/crates/puffin-cli/src/main.rs index 9cbebae64..64465bfae 100644 --- a/crates/puffin-cli/src/main.rs +++ b/crates/puffin-cli/src/main.rs @@ -1,9 +1,12 @@ -use std::path::PathBuf; +use std::borrow::Cow; +use std::path::{Path, PathBuf}; use std::process::ExitCode; +use anyhow::Result; use clap::{Args, Parser, Subcommand}; use colored::Colorize; use directories::ProjectDirs; +use tempfile::tempdir; use url::Url; use puffin_normalize::ExtraName; @@ -169,8 +172,7 @@ struct RemoveArgs { name: String, } -#[tokio::main] -async fn main() -> ExitCode { +async fn inner() -> Result { let cli = Cli::parse(); logging::setup_logging(if cli.verbose { @@ -187,16 +189,23 @@ async fn main() -> ExitCode { printer::Printer::Default }; + // 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 `PUFFIN_CACHE_DIR`. + // 3. The system-appropriate cache directory. + // 4. A `.puffin_cache` directory in the current working directory. let project_dirs = ProjectDirs::from("", "", "puffin"); - let cache_dir = (!cli.no_cache) - .then(|| { - cli.cache_dir - .as_deref() - .or_else(|| project_dirs.as_ref().map(ProjectDirs::cache_dir)) - }) - .flatten(); + let cache_dir = if cli.no_cache { + Cow::Owned(tempdir()?.into_path()) + } else if let Some(cache_dir) = cli.cache_dir { + Cow::Owned(cache_dir) + } else if let Some(project_dirs) = project_dirs.as_ref() { + Cow::Borrowed(project_dirs.cache_dir()) + } else { + Cow::Borrowed(Path::new(".puffin_cache")) + }; - let result = match cli.command { + match cli.command { Commands::PipCompile(args) => { let requirements = args .src_file @@ -228,7 +237,7 @@ async fn main() -> ExitCode { args.prerelease.unwrap_or_default(), args.upgrade.into(), index_urls, - cache_dir, + &cache_dir, printer, ) .await @@ -245,7 +254,7 @@ async fn main() -> ExitCode { &sources, args.link_mode.unwrap_or_default(), index_urls, - cache_dir, + &cache_dir, printer, ) .await @@ -257,16 +266,19 @@ async fn main() -> ExitCode { .map(RequirementsSource::from) .chain(args.requirement.into_iter().map(RequirementsSource::from)) .collect::>(); - commands::pip_uninstall(&sources, cache_dir, printer).await + commands::pip_uninstall(&sources, &cache_dir, printer).await } - Commands::Clean => commands::clean(cache_dir, printer), - Commands::Freeze => commands::freeze(cache_dir, printer), + Commands::Clean => commands::clean(&cache_dir, printer), + Commands::Freeze => commands::freeze(&cache_dir, printer), Commands::Venv(args) => commands::venv(&args.name, args.python.as_deref(), printer), Commands::Add(args) => commands::add(&args.name, printer), Commands::Remove(args) => commands::remove(&args.name, printer), - }; + } +} - match result { +#[tokio::main] +async fn main() -> ExitCode { + match inner().await { Ok(code) => code.into(), Err(err) => { #[allow(clippy::print_stderr)] diff --git a/crates/puffin-dev/src/build.rs b/crates/puffin-dev/src/build.rs index d86635f57..7bc0aa0b0 100644 --- a/crates/puffin-dev/src/build.rs +++ b/crates/puffin-dev/src/build.rs @@ -1,7 +1,7 @@ use std::env; use std::path::PathBuf; -use anyhow::Context; +use anyhow::{Context, Result}; use clap::Parser; use directories::ProjectDirs; use fs_err as fs; @@ -25,7 +25,7 @@ pub(crate) struct BuildArgs { } /// Build a source distribution to a wheel -pub(crate) async fn build(args: BuildArgs) -> anyhow::Result { +pub(crate) async fn build(args: BuildArgs) -> Result { let wheel_dir = if let Some(wheel_dir) = args.wheels { fs::create_dir_all(&wheel_dir).context("Invalid wheel directory")?; wheel_dir @@ -33,13 +33,15 @@ pub(crate) async fn build(args: BuildArgs) -> anyhow::Result { env::current_dir()? }; - let dirs = ProjectDirs::from("", "", "puffin"); - let cache = dirs + let project_dirs = ProjectDirs::from("", "", "puffin"); + let cache = project_dirs .as_ref() - .map(|dir| ProjectDirs::cache_dir(dir).to_path_buf()); + .map(|project_dirs| project_dirs.cache_dir().to_path_buf()) + .or_else(|| Some(tempfile::tempdir().ok()?.into_path())) + .unwrap_or_else(|| PathBuf::from(".puffin_cache")); let platform = Platform::current()?; - let venv = Virtualenv::from_env(platform, cache.as_deref())?; + let venv = Virtualenv::from_env(platform, Some(&cache))?; let build_dispatch = BuildDispatch::new( RegistryClientBuilder::default().build(), diff --git a/crates/puffin-dev/src/resolve_cli.rs b/crates/puffin-dev/src/resolve_cli.rs index 29de6b803..ebabbb574 100644 --- a/crates/puffin-dev/src/resolve_cli.rs +++ b/crates/puffin-dev/src/resolve_cli.rs @@ -1,5 +1,5 @@ use std::fs; -use std::path::{Path, PathBuf}; +use std::path::PathBuf; use clap::Parser; use directories::ProjectDirs; @@ -24,13 +24,17 @@ pub(crate) struct ResolveCliArgs { pub(crate) async fn resolve_cli(args: ResolveCliArgs) -> anyhow::Result<()> { let project_dirs = ProjectDirs::from("", "", "puffin"); - let cache = project_dirs.as_ref().map(ProjectDirs::cache_dir); + let cache = project_dirs + .as_ref() + .map(|project_dirs| project_dirs.cache_dir().to_path_buf()) + .or_else(|| Some(tempfile::tempdir().ok()?.into_path())) + .unwrap_or_else(|| PathBuf::from(".puffin_cache")); let platform = Platform::current()?; - let venv = Virtualenv::from_env(platform, cache)?; + let venv = Virtualenv::from_env(platform, Some(&cache))?; let build_dispatch = BuildDispatch::new( - RegistryClientBuilder::default().cache(cache).build(), - cache.map(Path::to_path_buf), + RegistryClientBuilder::default().cache(Some(&cache)).build(), + cache.clone(), venv.interpreter_info().clone(), fs::canonicalize(venv.python_executable())?, ); diff --git a/crates/puffin-dev/src/resolve_many.rs b/crates/puffin-dev/src/resolve_many.rs index f4e12961f..a550276e4 100644 --- a/crates/puffin-dev/src/resolve_many.rs +++ b/crates/puffin-dev/src/resolve_many.rs @@ -1,4 +1,4 @@ -use std::path::{Path, PathBuf}; +use std::path::PathBuf; use std::str::FromStr; use std::sync::Arc; @@ -30,6 +30,13 @@ pub(crate) struct ResolveManyArgs { } pub(crate) async fn resolve_many(args: ResolveManyArgs) -> anyhow::Result<()> { + let project_dirs = ProjectDirs::from("", "", "puffin"); + let cache = project_dirs + .as_ref() + .map(|project_dirs| project_dirs.cache_dir().to_path_buf()) + .or_else(|| Some(tempfile::tempdir().ok()?.into_path())) + .unwrap_or_else(|| PathBuf::from(".puffin_cache")); + let data = fs::read_to_string(&args.list)?; let lines = data.lines().map(Requirement::from_str); let requirements: Vec = if let Some(limit) = args.limit { @@ -38,17 +45,11 @@ pub(crate) async fn resolve_many(args: ResolveManyArgs) -> anyhow::Result<()> { lines.collect::>()? }; - let project_dirs = ProjectDirs::from("", "", "puffin"); - let cache = args - .cache_dir - .as_deref() - .or_else(|| project_dirs.as_ref().map(ProjectDirs::cache_dir)); - let platform = Platform::current()?; - let venv = Virtualenv::from_env(platform, cache)?; + let venv = Virtualenv::from_env(platform, Some(&cache))?; let build_dispatch = BuildDispatch::new( - RegistryClientBuilder::default().cache(cache).build(), - cache.map(Path::to_path_buf), + RegistryClientBuilder::default().cache(Some(&cache)).build(), + cache.clone(), venv.interpreter_info().clone(), fs::canonicalize(venv.python_executable())?, ); diff --git a/crates/puffin-dispatch/src/lib.rs b/crates/puffin-dispatch/src/lib.rs index e8776439a..2a2fc97f7 100644 --- a/crates/puffin-dispatch/src/lib.rs +++ b/crates/puffin-dispatch/src/lib.rs @@ -24,7 +24,7 @@ use puffin_traits::BuildContext; /// documentation. pub struct BuildDispatch { client: RegistryClient, - cache: Option, + cache: PathBuf, interpreter_info: InterpreterInfo, base_python: PathBuf, } @@ -32,7 +32,7 @@ pub struct BuildDispatch { impl BuildDispatch { pub fn new( client: RegistryClient, - cache: Option, + cache: PathBuf, interpreter_info: InterpreterInfo, base_python: PathBuf, ) -> Self { @@ -46,8 +46,8 @@ impl BuildDispatch { } impl BuildContext for BuildDispatch { - fn cache(&self) -> Option<&Path> { - self.cache.as_deref() + fn cache(&self) -> &Path { + self.cache.as_path() } fn interpreter_info(&self) -> &InterpreterInfo { @@ -105,11 +105,7 @@ impl BuildContext for BuildDispatch { local, remote, extraneous, - } = PartitionedRequirements::try_from_requirements( - requirements, - self.cache.as_deref(), - venv, - )?; + } = PartitionedRequirements::try_from_requirements(requirements, &self.cache, venv)?; let tags = Tags::from_env( self.interpreter_info.platform(), @@ -141,14 +137,13 @@ impl BuildContext for BuildDispatch { if remote.len() == 1 { "" } else { "s" }, remote.iter().map(ToString::to_string).join(", ") ); - Downloader::new(&self.client, self.cache.as_deref()) + Downloader::new(&self.client, &self.cache) .download(remote) .await .context("Failed to download build dependencies")? }; // Unzip any downloaded distributions. - let staging = tempfile::tempdir()?; let unzips = if downloads.is_empty() { vec![] } else { @@ -158,7 +153,7 @@ impl BuildContext for BuildDispatch { downloads.iter().map(ToString::to_string).join(", ") ); Unzipper::default() - .unzip(downloads, self.cache.as_deref().unwrap_or(staging.path())) + .unzip(downloads, &self.cache) .await .context("Failed to unpack build dependencies")? }; diff --git a/crates/puffin-installer/src/downloader.rs b/crates/puffin-installer/src/downloader.rs index 26e492f19..6dbf3696a 100644 --- a/crates/puffin-installer/src/downloader.rs +++ b/crates/puffin-installer/src/downloader.rs @@ -1,5 +1,5 @@ use std::cmp::Reverse; -use std::path::Path; +use std::path::{Path, PathBuf}; use anyhow::Result; use cacache::{Algorithm, Integrity}; @@ -13,13 +13,13 @@ use puffin_distribution::RemoteDistribution; pub struct Downloader<'a> { client: &'a RegistryClient, - cache: Option<&'a Path>, + cache: &'a Path, reporter: Option>, } impl<'a> Downloader<'a> { /// Initialize a new downloader. - pub fn new(client: &'a RegistryClient, cache: Option<&'a Path>) -> Self { + pub fn new(client: &'a RegistryClient, cache: &'a Path) -> Self { Self { client, cache, @@ -57,7 +57,7 @@ impl<'a> Downloader<'a> { fetches.spawn(fetch_wheel( remote.clone(), self.client.clone(), - self.cache.map(Path::to_path_buf), + self.cache.to_path_buf(), )); } @@ -97,19 +97,17 @@ impl std::fmt::Display for InMemoryDistribution { async fn fetch_wheel( remote: RemoteDistribution, client: RegistryClient, - cache: Option>, + cache: PathBuf, ) -> Result { match &remote { - RemoteDistribution::Registry(_package, _version, file) => { + RemoteDistribution::Registry(.., file) => { // Parse the wheel's SRI. let sri = Integrity::from_hex(&file.hashes.sha256, Algorithm::Sha256)?; // Read from the cache, if possible. - if let Some(cache) = cache.as_ref() { - if let Ok(buffer) = cacache::read_hash(&cache, &sri).await { - debug!("Extracted wheel from cache: {remote}"); - return Ok(InMemoryDistribution { remote, buffer }); - } + if let Ok(buffer) = cacache::read_hash(&cache, &sri).await { + debug!("Extracted wheel from cache: {remote}"); + return Ok(InMemoryDistribution { remote, buffer }); } // Fetch the wheel. @@ -122,13 +120,11 @@ async fn fetch_wheel( tokio::io::copy(&mut reader, &mut buffer).await?; // Write the buffer to the cache, if possible. - if let Some(cache) = cache.as_ref() { - cacache::write_hash(&cache, &buffer).await?; - } + cacache::write_hash(&cache, &buffer).await?; Ok(InMemoryDistribution { remote, buffer }) } - RemoteDistribution::Url(_package, url) => { + RemoteDistribution::Url(.., url) => { // Fetch the wheel. let reader = client.stream_external(url).await?; diff --git a/crates/puffin-installer/src/plan.rs b/crates/puffin-installer/src/plan.rs index 2266994a1..680e038fd 100644 --- a/crates/puffin-installer/src/plan.rs +++ b/crates/puffin-installer/src/plan.rs @@ -30,24 +30,15 @@ impl PartitionedRequirements { /// need to be downloaded, and those that should be removed. pub fn try_from_requirements( requirements: &[Requirement], - cache: Option<&Path>, + cache: &Path, venv: &Virtualenv, ) -> Result { // Index all the already-installed packages in site-packages. let mut site_packages = SitePackages::try_from_executable(venv)?; // Index all the already-downloaded wheels in the cache. - let registry_index = if let Some(cache) = cache { - RegistryIndex::try_from_directory(cache)? - } else { - RegistryIndex::default() - }; - - let url_index = if let Some(cache) = cache { - UrlIndex::try_from_directory(cache)? - } else { - UrlIndex::default() - }; + let registry_index = RegistryIndex::try_from_directory(cache)?; + let url_index = UrlIndex::try_from_directory(cache)?; let mut local = vec![]; let mut remote = vec![]; diff --git a/crates/puffin-interpreter/src/interpreter_info.rs b/crates/puffin-interpreter/src/interpreter_info.rs index 44a1264fd..7f06a1ba6 100644 --- a/crates/puffin-interpreter/src/interpreter_info.rs +++ b/crates/puffin-interpreter/src/interpreter_info.rs @@ -2,15 +2,17 @@ use std::io; use std::path::{Path, PathBuf}; use std::process::Command; -use pep440_rs::Version; +use anyhow::Result; use serde::{Deserialize, Serialize}; use thiserror::Error; use tracing::debug; -use crate::python_platform::PythonPlatform; +use pep440_rs::Version; use pep508_rs::MarkerEnvironment; use platform_host::Platform; +use crate::python_platform::PythonPlatform; + /// A Python executable and its associated platform markers. #[derive(Debug, Clone)] pub struct InterpreterInfo { @@ -21,12 +23,13 @@ pub struct InterpreterInfo { } impl InterpreterInfo { - pub fn query_cached( - executable: &Path, - platform: Platform, - cache: Option<&Path>, - ) -> anyhow::Result { - let info = InterpreterQueryResult::query_cached(executable, cache)?; + /// Detect the interpreter info for the given Python executable. + pub fn query(executable: &Path, platform: Platform, cache: Option<&Path>) -> Result { + let info = if let Some(cache) = cache { + InterpreterQueryResult::query_cached(executable, cache)? + } else { + InterpreterQueryResult::query(executable)? + }; debug_assert!( info.base_prefix == info.base_exec_prefix, "Not a venv python: {}, prefix: {}", @@ -119,7 +122,7 @@ impl InterpreterQueryResult { String::from_utf8_lossy(&output.stdout).trim(), String::from_utf8_lossy(&output.stderr).trim() ), - ) + ), }); } let data = serde_json::from_slice::(&output.stdout).map_err(|err| @@ -133,8 +136,8 @@ impl InterpreterQueryResult { err, String::from_utf8_lossy(&output.stdout).trim(), String::from_utf8_lossy(&output.stderr).trim() - ) - ) + ), + ), } )?; @@ -146,20 +149,16 @@ impl InterpreterQueryResult { /// Running a Python script is (relatively) expensive, and the markers won't change /// unless the Python executable changes, so we use the executable's last modified /// time as a cache key. - pub(crate) fn query_cached(executable: &Path, cache: Option<&Path>) -> anyhow::Result { + pub(crate) fn query_cached(executable: &Path, cache: &Path) -> Result { // Read from the cache. - let key = if let Some(cache) = cache { - if let Ok(key) = cache_key(executable) { - if let Ok(data) = cacache::read_sync(cache, &key) { - if let Ok(info) = serde_json::from_slice::(&data) { - debug!("Using cached markers for {}", executable.display()); - return Ok(info); - } + let key = if let Ok(key) = cache_key(executable) { + if let Ok(data) = cacache::read_sync(cache, &key) { + if let Ok(info) = serde_json::from_slice::(&data) { + debug!("Using cached markers for {}", executable.display()); + return Ok(info); } - Some(key) - } else { - None } + Some(key) } else { None }; @@ -169,10 +168,8 @@ impl InterpreterQueryResult { let info = Self::query(executable)?; // Write to the cache. - if let Some(cache) = cache { - if let Some(key) = key { - cacache::write_sync(cache, key, serde_json::to_vec(&info)?)?; - } + if let Some(key) = key { + cacache::write_sync(cache, key, serde_json::to_vec(&info)?)?; } Ok(info) @@ -181,7 +178,7 @@ impl InterpreterQueryResult { /// Create a cache key for the Python executable, consisting of the executable's /// last modified time and the executable's path. -fn cache_key(executable: &Path) -> anyhow::Result { +fn cache_key(executable: &Path) -> Result { let modified = executable .metadata()? .modified()? diff --git a/crates/puffin-interpreter/src/virtual_env.rs b/crates/puffin-interpreter/src/virtual_env.rs index 6352ec3e3..01fbee4bf 100644 --- a/crates/puffin-interpreter/src/virtual_env.rs +++ b/crates/puffin-interpreter/src/virtual_env.rs @@ -21,7 +21,7 @@ impl Virtualenv { let platform = PythonPlatform::from(platform); let venv = detect_virtual_env(&platform)?; let executable = platform.venv_python(&venv); - let interpreter_info = InterpreterInfo::query_cached(&executable, platform.0, cache)?; + let interpreter_info = InterpreterInfo::query(&executable, platform.0, cache)?; Ok(Self { root: venv, @@ -32,7 +32,7 @@ impl Virtualenv { pub fn from_virtualenv(platform: Platform, root: &Path, cache: Option<&Path>) -> Result { let platform = PythonPlatform::from(platform); let executable = platform.venv_python(root); - let interpreter_info = InterpreterInfo::query_cached(&executable, platform.0, cache)?; + let interpreter_info = InterpreterInfo::query(&executable, platform.0, cache)?; Ok(Self { root: root.to_path_buf(), diff --git a/crates/puffin-resolver/src/distribution/cached_wheel.rs b/crates/puffin-resolver/src/distribution/cached_wheel.rs index 496e39007..7036c3dce 100644 --- a/crates/puffin-resolver/src/distribution/cached_wheel.rs +++ b/crates/puffin-resolver/src/distribution/cached_wheel.rs @@ -25,9 +25,9 @@ impl CachedWheel { pub(super) fn find_in_cache( distribution: &RemoteDistributionRef<'_>, tags: &Tags, - cache: &Path, + cache: impl AsRef, ) -> Option { - let wheel_dir = cache.join(distribution.id()); + let wheel_dir = cache.as_ref().join(distribution.id()); let Ok(read_dir) = fs_err::read_dir(wheel_dir) else { return None; }; diff --git a/crates/puffin-resolver/src/distribution/source_distribution.rs b/crates/puffin-resolver/src/distribution/source_distribution.rs index f8fcf5bfd..c9d445da4 100644 --- a/crates/puffin-resolver/src/distribution/source_distribution.rs +++ b/crates/puffin-resolver/src/distribution/source_distribution.rs @@ -2,7 +2,7 @@ use std::str::FromStr; use anyhow::Result; use fs_err::tokio as fs; -use tempfile::tempdir; +use tempfile::tempdir_in; use tokio_util::compat::FuturesAsyncReadCompatExt; use tracing::debug; @@ -36,10 +36,7 @@ impl<'a, T: BuildContext> SourceDistributionFetcher<'a, T> { distribution: &RemoteDistributionRef<'_>, tags: &Tags, ) -> Result> { - let Some(cache) = self.0.cache() else { - return Ok(None); - }; - CachedWheel::find_in_cache(distribution, tags, &cache.join(BUILT_WHEELS_CACHE)) + CachedWheel::find_in_cache(distribution, tags, self.0.cache().join(BUILT_WHEELS_CACHE)) .as_ref() .map(CachedWheel::read_dist_info) .transpose() @@ -53,8 +50,6 @@ impl<'a, T: BuildContext> SourceDistributionFetcher<'a, T> { ) -> Result { debug!("Building: {distribution}"); - let temp_dir = tempdir()?; - let source = Source::try_from(distribution)?; let sdist_file = match source { Source::Url(url) => { @@ -64,8 +59,9 @@ impl<'a, T: BuildContext> SourceDistributionFetcher<'a, T> { let mut reader = tokio::io::BufReader::new(reader.compat()); // Download the source distribution. + let temp_dir = tempdir_in(self.0.cache())?.into_path(); let sdist_filename = distribution.filename()?; - let sdist_file = temp_dir.path().join(sdist_filename.as_ref()); + let sdist_file = temp_dir.join(sdist_filename.as_ref()); let mut writer = tokio::fs::File::create(&sdist_file).await?; tokio::io::copy(&mut reader, &mut writer).await?; @@ -74,20 +70,18 @@ impl<'a, T: BuildContext> SourceDistributionFetcher<'a, T> { Source::Git(git) => { debug!("Fetching source distribution from: {git}"); - let git_dir = self.0.cache().map_or_else( - || temp_dir.path().join(GIT_CACHE), - |cache| cache.join(GIT_CACHE), - ); + let git_dir = self.0.cache().join(GIT_CACHE); let source = GitSource::new(git, git_dir); tokio::task::spawn_blocking(move || source.fetch()).await?? } }; // Create a directory for the wheel. - let wheel_dir = self.0.cache().map_or_else( - || temp_dir.path().join(BUILT_WHEELS_CACHE), - |cache| cache.join(BUILT_WHEELS_CACHE).join(distribution.id()), - ); + let wheel_dir = self + .0 + .cache() + .join(BUILT_WHEELS_CACHE) + .join(distribution.id()); fs::create_dir_all(&wheel_dir).await?; // Build the wheel. diff --git a/crates/puffin-resolver/src/distribution/wheel.rs b/crates/puffin-resolver/src/distribution/wheel.rs index 337c19cb7..1766ef297 100644 --- a/crates/puffin-resolver/src/distribution/wheel.rs +++ b/crates/puffin-resolver/src/distribution/wheel.rs @@ -3,7 +3,7 @@ use std::str::FromStr; use anyhow::Result; use fs_err::tokio as fs; -use tempfile::tempdir; + use tokio_util::compat::FuturesAsyncReadCompatExt; use tracing::debug; @@ -18,11 +18,11 @@ use crate::distribution::cached_wheel::CachedWheel; const REMOTE_WHEELS_CACHE: &str = "remote-wheels-v0"; /// Fetch a built distribution from a remote source, or from a local cache. -pub(crate) struct WheelFetcher<'a>(Option<&'a Path>); +pub(crate) struct WheelFetcher<'a>(&'a Path); impl<'a> WheelFetcher<'a> { /// Initialize a [`WheelFetcher`] from a [`BuildContext`]. - pub(crate) fn new(cache: Option<&'a Path>) -> Self { + pub(crate) fn new(cache: &'a Path) -> Self { Self(cache) } @@ -32,10 +32,7 @@ impl<'a> WheelFetcher<'a> { distribution: &RemoteDistributionRef<'_>, tags: &Tags, ) -> Result> { - let Some(cache) = self.0 else { - return Ok(None); - }; - CachedWheel::find_in_cache(distribution, tags, &cache.join(REMOTE_WHEELS_CACHE)) + CachedWheel::find_in_cache(distribution, tags, self.0.join(REMOTE_WHEELS_CACHE)) .as_ref() .map(CachedWheel::read_dist_info) .transpose() @@ -51,13 +48,9 @@ impl<'a> WheelFetcher<'a> { let url = distribution.url()?; let reader = client.stream_external(&url).await?; let mut reader = tokio::io::BufReader::new(reader.compat()); - let temp_dir = tempdir()?; // Create a directory for the wheel. - let wheel_dir = self.0.map_or_else( - || temp_dir.path().join(REMOTE_WHEELS_CACHE), - |cache| cache.join(REMOTE_WHEELS_CACHE).join(distribution.id()), - ); + let wheel_dir = self.0.join(REMOTE_WHEELS_CACHE).join(distribution.id()); fs::create_dir_all(&wheel_dir).await?; // Download the wheel. diff --git a/crates/puffin-resolver/tests/resolver.rs b/crates/puffin-resolver/tests/resolver.rs index 3c82b4af0..962c33722 100644 --- a/crates/puffin-resolver/tests/resolver.rs +++ b/crates/puffin-resolver/tests/resolver.rs @@ -22,7 +22,7 @@ use puffin_traits::BuildContext; struct DummyContext; impl BuildContext for DummyContext { - fn cache(&self) -> Option<&Path> { + fn cache(&self) -> &Path { panic!("The test should not need to build source distributions") } diff --git a/crates/puffin-traits/src/lib.rs b/crates/puffin-traits/src/lib.rs index 05401d8c8..a53e7a3fb 100644 --- a/crates/puffin-traits/src/lib.rs +++ b/crates/puffin-traits/src/lib.rs @@ -48,7 +48,7 @@ use puffin_interpreter::{InterpreterInfo, Virtualenv}; // TODO(konstin): Proper error types pub trait BuildContext { // TODO(konstin): Add a cache abstraction - fn cache(&self) -> Option<&Path>; + fn cache(&self) -> &Path; /// All (potentially nested) source distribution builds use the same base python and can reuse /// it's metadata (e.g. wheel compatibility tags).