mirror of
https://github.com/astral-sh/uv.git
synced 2025-07-07 13:25:00 +00:00
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:
parent
a02bf2e415
commit
a4002fe132
21 changed files with 140 additions and 160 deletions
|
@ -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(())
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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())?,
|
||||
);
|
||||
|
|
|
@ -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")?;
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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!(
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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())?,
|
||||
);
|
||||
|
|
|
@ -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())?,
|
||||
);
|
||||
|
|
|
@ -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")?
|
||||
};
|
||||
|
|
|
@ -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?;
|
||||
|
||||
|
|
|
@ -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![];
|
||||
|
|
|
@ -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()?
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
||||
|
|
|
@ -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).
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue