Make cache non-optional in most crates (#293)

This PR makes the cache non-optional in most of Puffin, which simplifies
the code, allows us to reuse the cache within a single command (even
with `--no-cache`), and also allows us to use the cache for disk storage
across an invocation.

I left the cache as optional for the `Virtualenv` and `InterpreterInfo`
abstractions, since those are generic enough that it seems nice to have
a non-cached version, but it's kind of arbitrary.
This commit is contained in:
Charlie Marsh 2023-11-02 10:40:20 -07:00 committed by GitHub
parent a02bf2e415
commit a4002fe132
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 140 additions and 160 deletions

View file

@ -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(())

View file

@ -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<ExitStatus> {
let Some(cache) = cache else {
return Err(anyhow::anyhow!("No cache found"));
};
pub(crate) fn clean(cache: &Path, mut printer: Printer) -> Result<ExitStatus> {
if !cache.exists() {
writeln!(printer, "No cache found at: {}", cache.display())?;
return Ok(ExitStatus::Success);

View file

@ -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<ExitStatus> {
pub(crate) fn freeze(cache: &Path, _printer: Printer) -> Result<ExitStatus> {
// 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()

View file

@ -37,7 +37,7 @@ pub(crate) async fn pip_compile(
prerelease_mode: PreReleaseMode,
upgrade_mode: UpgradeMode,
index_urls: Option<IndexUrls>,
cache: Option<&Path>,
cache: &Path,
mut printer: Printer,
) -> Result<ExitStatus> {
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())?,
);

View file

@ -28,7 +28,7 @@ pub(crate) async fn pip_sync(
sources: &[RequirementsSource],
link_mode: LinkMode,
index_urls: Option<IndexUrls>,
cache: Option<&Path>,
cache: &Path,
mut printer: Printer,
) -> Result<ExitStatus> {
// 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<IndexUrls>,
cache: Option<&Path>,
cache: &Path,
mut printer: Printer,
) -> Result<ExitStatus> {
// 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")?;

View file

@ -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<ExitStatus> {
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()

View file

@ -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!(

View file

@ -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<ExitStatus> {
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::<Vec<_>>();
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)]

View file

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

View file

@ -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())?,
);

View file

@ -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<Requirement> = if let Some(limit) = args.limit {
@ -38,17 +45,11 @@ pub(crate) async fn resolve_many(args: ResolveManyArgs) -> anyhow::Result<()> {
lines.collect::<anyhow::Result<_, _>>()?
};
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())?,
);

View file

@ -24,7 +24,7 @@ use puffin_traits::BuildContext;
/// documentation.
pub struct BuildDispatch {
client: RegistryClient,
cache: Option<PathBuf>,
cache: PathBuf,
interpreter_info: InterpreterInfo,
base_python: PathBuf,
}
@ -32,7 +32,7 @@ pub struct BuildDispatch {
impl BuildDispatch {
pub fn new(
client: RegistryClient,
cache: Option<PathBuf>,
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")?
};

View file

@ -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<Box<dyn Reporter>>,
}
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<impl AsRef<Path>>,
cache: PathBuf,
) -> Result<InMemoryDistribution> {
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?;

View file

@ -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<Self> {
// 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![];

View file

@ -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<Self> {
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<Self> {
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::<Self>(&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<Self> {
pub(crate) fn query_cached(executable: &Path, cache: &Path) -> Result<Self> {
// 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::<Self>(&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::<Self>(&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<String> {
fn cache_key(executable: &Path) -> Result<String> {
let modified = executable
.metadata()?
.modified()?

View file

@ -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<Self> {
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(),

View file

@ -25,9 +25,9 @@ impl CachedWheel {
pub(super) fn find_in_cache(
distribution: &RemoteDistributionRef<'_>,
tags: &Tags,
cache: &Path,
cache: impl AsRef<Path>,
) -> Option<Self> {
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;
};

View file

@ -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<Option<Metadata21>> {
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<Metadata21> {
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.

View file

@ -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<Option<Metadata21>> {
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.

View file

@ -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")
}

View file

@ -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).