mirror of
https://github.com/astral-sh/uv.git
synced 2025-11-25 13:36:41 +00:00
Preparing for #235, some refactoring to `puffin_interpreter`. * Added a dedicated error type instead of anyhow * `InterpreterInfo` -> `Interpreter` * `detect_virtual_env` now returns an option so it can be chained for #235
This commit is contained in:
parent
9d35128840
commit
1c0e03f807
19 changed files with 201 additions and 163 deletions
|
|
@ -10,7 +10,7 @@ use fs_err::os::unix::fs::symlink;
|
|||
use fs_err::File;
|
||||
use tracing::info;
|
||||
|
||||
use puffin_interpreter::InterpreterInfo;
|
||||
use puffin_interpreter::Interpreter;
|
||||
|
||||
/// The bash activate scripts with the venv dependent paths patches out
|
||||
const ACTIVATE_TEMPLATES: &[(&str, &str)] = &[
|
||||
|
|
@ -56,8 +56,8 @@ pub struct VenvPaths {
|
|||
}
|
||||
|
||||
/// Write all the files that belong to a venv without any packages installed.
|
||||
pub fn create_bare_venv(location: &Utf8Path, info: &InterpreterInfo) -> io::Result<VenvPaths> {
|
||||
let base_python: &Utf8Path = info
|
||||
pub fn create_bare_venv(location: &Utf8Path, interpreter: &Interpreter) -> io::Result<VenvPaths> {
|
||||
let base_python: &Utf8Path = interpreter
|
||||
.sys_executable()
|
||||
.try_into()
|
||||
.map_err(|err: FromPathError| err.into_io_error())?;
|
||||
|
|
@ -113,14 +113,14 @@ pub fn create_bare_venv(location: &Utf8Path, info: &InterpreterInfo) -> io::Resu
|
|||
symlink(base_python, &venv_python)?;
|
||||
symlink(
|
||||
"python",
|
||||
bin_dir.join(format!("python{}", info.simple_version().0)),
|
||||
bin_dir.join(format!("python{}", interpreter.simple_version().0)),
|
||||
)?;
|
||||
symlink(
|
||||
"python",
|
||||
bin_dir.join(format!(
|
||||
"python{}.{}",
|
||||
info.simple_version().0,
|
||||
info.simple_version().1
|
||||
interpreter.simple_version().0,
|
||||
interpreter.simple_version().1
|
||||
)),
|
||||
)?;
|
||||
}
|
||||
|
|
@ -133,8 +133,8 @@ pub fn create_bare_venv(location: &Utf8Path, info: &InterpreterInfo) -> io::Resu
|
|||
"{{ RELATIVE_SITE_PACKAGES }}",
|
||||
&format!(
|
||||
"../lib/python{}.{}/site-packages",
|
||||
info.simple_version().0,
|
||||
info.simple_version().1
|
||||
interpreter.simple_version().0,
|
||||
interpreter.simple_version().1
|
||||
),
|
||||
);
|
||||
fs::write(bin_dir.join(name), activator)?;
|
||||
|
|
@ -153,17 +153,20 @@ pub fn create_bare_venv(location: &Utf8Path, info: &InterpreterInfo) -> io::Resu
|
|||
let pyvenv_cfg_data = &[
|
||||
("home", python_home),
|
||||
("implementation", "CPython".to_string()),
|
||||
("version_info", info.markers().python_version.string.clone()),
|
||||
(
|
||||
"version_info",
|
||||
interpreter.markers().python_version.string.clone(),
|
||||
),
|
||||
("gourgeist", env!("CARGO_PKG_VERSION").to_string()),
|
||||
// I wouldn't allow this option anyway
|
||||
("include-system-site-packages", "false".to_string()),
|
||||
(
|
||||
"base-prefix",
|
||||
info.base_prefix().to_string_lossy().to_string(),
|
||||
interpreter.base_prefix().to_string_lossy().to_string(),
|
||||
),
|
||||
(
|
||||
"base-exec-prefix",
|
||||
info.base_exec_prefix().to_string_lossy().to_string(),
|
||||
interpreter.base_exec_prefix().to_string_lossy().to_string(),
|
||||
),
|
||||
("base-executable", base_python.to_string()),
|
||||
];
|
||||
|
|
@ -176,8 +179,8 @@ pub fn create_bare_venv(location: &Utf8Path, info: &InterpreterInfo) -> io::Resu
|
|||
.join("lib")
|
||||
.join(format!(
|
||||
"python{}.{}",
|
||||
info.simple_version().0,
|
||||
info.simple_version().1
|
||||
interpreter.simple_version().0,
|
||||
interpreter.simple_version().1
|
||||
))
|
||||
.join("site-packages");
|
||||
fs::create_dir_all(&site_packages)?;
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ use thiserror::Error;
|
|||
|
||||
pub use interpreter::parse_python_cli;
|
||||
use platform_host::PlatformError;
|
||||
use puffin_interpreter::{InterpreterInfo, Virtualenv};
|
||||
use puffin_interpreter::{Interpreter, Virtualenv};
|
||||
|
||||
pub use crate::bare::create_bare_venv;
|
||||
|
||||
|
|
@ -24,10 +24,13 @@ pub enum Error {
|
|||
}
|
||||
|
||||
/// Create a virtualenv.
|
||||
pub fn create_venv(location: &Path, info: &InterpreterInfo) -> Result<Virtualenv, Error> {
|
||||
pub fn create_venv(location: &Path, interpreter: &Interpreter) -> Result<Virtualenv, Error> {
|
||||
let location: &Utf8Path = location
|
||||
.try_into()
|
||||
.map_err(|err: FromPathError| err.into_io_error())?;
|
||||
let paths = create_bare_venv(location, info)?;
|
||||
Ok(Virtualenv::new_prefix(paths.root.as_std_path(), info))
|
||||
let paths = create_bare_venv(location, interpreter)?;
|
||||
Ok(Virtualenv::new_prefix(
|
||||
paths.root.as_std_path(),
|
||||
interpreter,
|
||||
))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ use tracing_subscriber::{fmt, EnvFilter};
|
|||
|
||||
use gourgeist::{create_bare_venv, parse_python_cli};
|
||||
use platform_host::Platform;
|
||||
use puffin_interpreter::InterpreterInfo;
|
||||
use puffin_interpreter::Interpreter;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
struct Cli {
|
||||
|
|
@ -25,7 +25,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(python.as_std_path(), platform, None).unwrap();
|
||||
let info = Interpreter::query(python.as_std_path(), platform, None).unwrap();
|
||||
create_bare_venv(&location, &info)?;
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ use tracing::{debug, instrument};
|
|||
use zip::ZipArchive;
|
||||
|
||||
use pep508_rs::Requirement;
|
||||
use puffin_interpreter::{InterpreterInfo, Virtualenv};
|
||||
use puffin_interpreter::{Interpreter, Virtualenv};
|
||||
use puffin_traits::BuildContext;
|
||||
|
||||
/// e.g. `pygraphviz/graphviz_wrap.c:3020:10: fatal error: graphviz/cgraph.h: No such file or directory`
|
||||
|
|
@ -220,7 +220,7 @@ impl SourceBuild {
|
|||
pub async fn setup(
|
||||
source: &Path,
|
||||
subdirectory: Option<&Path>,
|
||||
interpreter_info: &InterpreterInfo,
|
||||
interpreter: &Interpreter,
|
||||
build_context: &impl BuildContext,
|
||||
source_build_context: SourceBuildContext,
|
||||
package_id: &str,
|
||||
|
|
@ -250,7 +250,7 @@ impl SourceBuild {
|
|||
None
|
||||
};
|
||||
|
||||
let venv = gourgeist::create_venv(&temp_dir.path().join(".venv"), interpreter_info)?;
|
||||
let venv = gourgeist::create_venv(&temp_dir.path().join(".venv"), interpreter)?;
|
||||
|
||||
// There are packages such as DTLSSocket 0.1.16 that say
|
||||
// ```toml
|
||||
|
|
|
|||
|
|
@ -116,24 +116,24 @@ pub(crate) async fn pip_compile(
|
|||
|
||||
debug!(
|
||||
"Using Python {} at {}",
|
||||
venv.interpreter_info().markers().python_version,
|
||||
venv.interpreter().markers().python_version,
|
||||
venv.python_executable().display()
|
||||
);
|
||||
|
||||
// Determine the compatible platform tags.
|
||||
let tags = Tags::from_env(
|
||||
venv.interpreter_info().platform(),
|
||||
venv.interpreter_info().simple_version(),
|
||||
venv.interpreter().platform(),
|
||||
venv.interpreter().simple_version(),
|
||||
)?;
|
||||
|
||||
// Determine the markers to use for resolution.
|
||||
let markers = python_version.map_or_else(
|
||||
|| Cow::Borrowed(venv.interpreter_info().markers()),
|
||||
|python_version| Cow::Owned(python_version.markers(venv.interpreter_info().markers())),
|
||||
|| Cow::Borrowed(venv.interpreter().markers()),
|
||||
|python_version| Cow::Owned(python_version.markers(venv.interpreter().markers())),
|
||||
);
|
||||
// Inject the fake python version if necessary
|
||||
let interpreter_info = venv
|
||||
.interpreter_info()
|
||||
.interpreter()
|
||||
.clone()
|
||||
.patch_markers(markers.clone().into_owned());
|
||||
|
||||
|
|
|
|||
|
|
@ -105,8 +105,8 @@ pub(crate) async fn sync_requirements(
|
|||
|
||||
// Determine the current environment markers.
|
||||
let tags = Tags::from_env(
|
||||
venv.interpreter_info().platform(),
|
||||
venv.interpreter_info().simple_version(),
|
||||
venv.interpreter().platform(),
|
||||
venv.interpreter().simple_version(),
|
||||
)?;
|
||||
|
||||
// Instantiate a client.
|
||||
|
|
@ -129,8 +129,7 @@ pub(crate) async fn sync_requirements(
|
|||
} else {
|
||||
let start = std::time::Instant::now();
|
||||
|
||||
let wheel_finder =
|
||||
puffin_resolver::DistFinder::new(&tags, &client, venv.interpreter_info())
|
||||
let wheel_finder = puffin_resolver::DistFinder::new(&tags, &client, venv.interpreter())
|
||||
.with_reporter(FinderReporter::from(printer).with_length(remote.len() as u64));
|
||||
let resolution = wheel_finder.resolve(&remote).await?;
|
||||
|
||||
|
|
@ -223,7 +222,7 @@ pub(crate) async fn sync_requirements(
|
|||
let build_dispatch = BuildDispatch::new(
|
||||
client,
|
||||
cache.to_path_buf(),
|
||||
venv.interpreter_info().clone(),
|
||||
venv.interpreter().clone(),
|
||||
fs::canonicalize(venv.python_executable())?,
|
||||
no_build,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -5,10 +5,11 @@ use anyhow::Result;
|
|||
use colored::Colorize;
|
||||
use fs_err as fs;
|
||||
use miette::{Diagnostic, IntoDiagnostic};
|
||||
use platform_host::Platform;
|
||||
use puffin_interpreter::InterpreterInfo;
|
||||
use thiserror::Error;
|
||||
|
||||
use platform_host::Platform;
|
||||
use puffin_interpreter::Interpreter;
|
||||
|
||||
use crate::commands::ExitStatus;
|
||||
use crate::printer::Printer;
|
||||
|
||||
|
|
@ -43,7 +44,7 @@ enum VenvError {
|
|||
|
||||
#[error("Failed to extract Python interpreter info")]
|
||||
#[diagnostic(code(puffin::venv::interpreter))]
|
||||
InterpreterError(#[source] anyhow::Error),
|
||||
InterpreterError(#[source] puffin_interpreter::Error),
|
||||
|
||||
#[error("Failed to create virtual environment")]
|
||||
#[diagnostic(code(puffin::venv::creation))]
|
||||
|
|
@ -73,8 +74,8 @@ fn venv_impl(
|
|||
};
|
||||
|
||||
let platform = Platform::current().into_diagnostic()?;
|
||||
let interpreter_info = InterpreterInfo::query(&base_python, platform, None)
|
||||
.map_err(VenvError::InterpreterError)?;
|
||||
let interpreter_info =
|
||||
Interpreter::query(&base_python, platform, None).map_err(VenvError::InterpreterError)?;
|
||||
|
||||
writeln!(
|
||||
printer,
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ pub(crate) async fn build(args: BuildArgs) -> Result<PathBuf> {
|
|||
let build_dispatch = BuildDispatch::new(
|
||||
RegistryClientBuilder::new(cache_dir.path().clone()).build(),
|
||||
cache_dir.path().clone(),
|
||||
venv.interpreter_info().clone(),
|
||||
venv.interpreter().clone(),
|
||||
fs::canonicalize(venv.python_executable())?,
|
||||
false,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -50,15 +50,15 @@ pub(crate) async fn resolve_cli(args: ResolveCliArgs) -> Result<()> {
|
|||
let build_dispatch = BuildDispatch::new(
|
||||
client.clone(),
|
||||
cache_dir.path().clone(),
|
||||
venv.interpreter_info().clone(),
|
||||
venv.interpreter().clone(),
|
||||
fs::canonicalize(venv.python_executable())?,
|
||||
args.no_build,
|
||||
);
|
||||
|
||||
// Copied from `BuildDispatch`
|
||||
let tags = Tags::from_env(
|
||||
venv.interpreter_info().platform(),
|
||||
venv.interpreter_info().simple_version(),
|
||||
venv.interpreter().platform(),
|
||||
venv.interpreter().simple_version(),
|
||||
)?;
|
||||
let resolver = Resolver::new(
|
||||
Manifest::new(
|
||||
|
|
@ -68,7 +68,7 @@ pub(crate) async fn resolve_cli(args: ResolveCliArgs) -> Result<()> {
|
|||
None,
|
||||
),
|
||||
ResolutionOptions::default(),
|
||||
venv.interpreter_info().markers(),
|
||||
venv.interpreter().markers(),
|
||||
&tags,
|
||||
&client,
|
||||
&build_dispatch,
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ pub(crate) async fn resolve_many(args: ResolveManyArgs) -> Result<()> {
|
|||
let build_dispatch = BuildDispatch::new(
|
||||
RegistryClientBuilder::new(cache_dir.path().clone()).build(),
|
||||
cache_dir.path().clone(),
|
||||
venv.interpreter_info().clone(),
|
||||
venv.interpreter().clone(),
|
||||
fs::canonicalize(venv.python_executable())?,
|
||||
args.no_build,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ use platform_tags::Tags;
|
|||
use puffin_build::{SourceBuild, SourceBuildContext};
|
||||
use puffin_client::RegistryClient;
|
||||
use puffin_installer::{Builder, Downloader, InstallPlan, Installer, Unzipper};
|
||||
use puffin_interpreter::{InterpreterInfo, Virtualenv};
|
||||
use puffin_interpreter::{Interpreter, Virtualenv};
|
||||
use puffin_resolver::{DistFinder, Manifest, ResolutionOptions, Resolver};
|
||||
use puffin_traits::BuildContext;
|
||||
|
||||
|
|
@ -26,7 +26,7 @@ use puffin_traits::BuildContext;
|
|||
pub struct BuildDispatch {
|
||||
client: RegistryClient,
|
||||
cache: PathBuf,
|
||||
interpreter_info: InterpreterInfo,
|
||||
interpreter: Interpreter,
|
||||
base_python: PathBuf,
|
||||
no_build: bool,
|
||||
source_build_context: SourceBuildContext,
|
||||
|
|
@ -37,14 +37,14 @@ impl BuildDispatch {
|
|||
pub fn new(
|
||||
client: RegistryClient,
|
||||
cache: PathBuf,
|
||||
interpreter_info: InterpreterInfo,
|
||||
interpreter: Interpreter,
|
||||
base_python: PathBuf,
|
||||
no_build: bool,
|
||||
) -> Self {
|
||||
Self {
|
||||
client,
|
||||
cache,
|
||||
interpreter_info,
|
||||
interpreter,
|
||||
base_python,
|
||||
no_build,
|
||||
source_build_context: SourceBuildContext::default(),
|
||||
|
|
@ -64,8 +64,8 @@ impl BuildContext for BuildDispatch {
|
|||
self.cache.as_path()
|
||||
}
|
||||
|
||||
fn interpreter_info(&self) -> &InterpreterInfo {
|
||||
&self.interpreter_info
|
||||
fn interpreter(&self) -> &Interpreter {
|
||||
&self.interpreter
|
||||
}
|
||||
|
||||
fn base_python(&self) -> &Path {
|
||||
|
|
@ -83,13 +83,13 @@ impl BuildContext for BuildDispatch {
|
|||
) -> Pin<Box<dyn Future<Output = Result<Vec<Requirement>>> + Send + 'a>> {
|
||||
Box::pin(async {
|
||||
let tags = Tags::from_env(
|
||||
self.interpreter_info.platform(),
|
||||
self.interpreter_info.simple_version(),
|
||||
self.interpreter.platform(),
|
||||
self.interpreter.simple_version(),
|
||||
)?;
|
||||
let resolver = Resolver::new(
|
||||
Manifest::new(requirements.to_vec(), Vec::default(), Vec::default(), None),
|
||||
self.options,
|
||||
self.interpreter_info.markers(),
|
||||
self.interpreter.markers(),
|
||||
&tags,
|
||||
&self.client,
|
||||
self,
|
||||
|
|
@ -124,8 +124,8 @@ impl BuildContext for BuildDispatch {
|
|||
} = InstallPlan::try_from_requirements(requirements, &self.cache, venv)?;
|
||||
|
||||
let tags = Tags::from_env(
|
||||
self.interpreter_info.platform(),
|
||||
self.interpreter_info.simple_version(),
|
||||
self.interpreter.platform(),
|
||||
self.interpreter.simple_version(),
|
||||
)?;
|
||||
|
||||
// Resolve the dependencies.
|
||||
|
|
@ -137,7 +137,7 @@ impl BuildContext for BuildDispatch {
|
|||
if remote.len() == 1 { "" } else { "s" },
|
||||
remote.iter().map(ToString::to_string).join(", ")
|
||||
);
|
||||
let resolution = DistFinder::new(&tags, &self.client, self.interpreter_info())
|
||||
let resolution = DistFinder::new(&tags, &self.client, self.interpreter())
|
||||
.resolve(&remote)
|
||||
.await
|
||||
.context("Failed to resolve build dependencies")?;
|
||||
|
|
@ -249,7 +249,7 @@ impl BuildContext for BuildDispatch {
|
|||
let builder = SourceBuild::setup(
|
||||
source,
|
||||
subdirectory,
|
||||
&self.interpreter_info,
|
||||
&self.interpreter,
|
||||
self,
|
||||
self.source_build_context.clone(),
|
||||
package_id,
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ impl<'a> Installer<'a> {
|
|||
wheels.par_iter().try_for_each(|wheel| {
|
||||
let location = install_wheel_rs::InstallLocation::new(
|
||||
self.venv.root(),
|
||||
self.venv.interpreter_info().simple_version(),
|
||||
self.venv.interpreter().simple_version(),
|
||||
);
|
||||
|
||||
install_wheel_rs::linker::install_wheel(
|
||||
|
|
|
|||
|
|
@ -1,10 +1,8 @@
|
|||
use std::io;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::Command;
|
||||
use std::time::UNIX_EPOCH;
|
||||
|
||||
use anyhow::Result;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use thiserror::Error;
|
||||
use tracing::debug;
|
||||
|
||||
use pep440_rs::Version;
|
||||
|
|
@ -12,10 +10,11 @@ use pep508_rs::MarkerEnvironment;
|
|||
use platform_host::Platform;
|
||||
|
||||
use crate::python_platform::PythonPlatform;
|
||||
use crate::Error;
|
||||
|
||||
/// A Python executable and its associated platform markers.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct InterpreterInfo {
|
||||
pub struct Interpreter {
|
||||
pub(crate) platform: PythonPlatform,
|
||||
pub(crate) markers: MarkerEnvironment,
|
||||
pub(crate) base_exec_prefix: PathBuf,
|
||||
|
|
@ -23,9 +22,13 @@ pub struct InterpreterInfo {
|
|||
pub(crate) sys_executable: PathBuf,
|
||||
}
|
||||
|
||||
impl InterpreterInfo {
|
||||
impl Interpreter {
|
||||
/// Detect the interpreter info for the given Python executable.
|
||||
pub fn query(executable: &Path, platform: Platform, cache: Option<&Path>) -> Result<Self> {
|
||||
pub fn query(
|
||||
executable: &Path,
|
||||
platform: Platform,
|
||||
cache: Option<&Path>,
|
||||
) -> Result<Self, Error> {
|
||||
let info = if let Some(cache) = cache {
|
||||
InterpreterQueryResult::query_cached(executable, cache)?
|
||||
} else {
|
||||
|
|
@ -65,7 +68,7 @@ impl InterpreterInfo {
|
|||
}
|
||||
}
|
||||
|
||||
impl InterpreterInfo {
|
||||
impl Interpreter {
|
||||
/// Returns the path to the Python virtual environment.
|
||||
pub fn platform(&self) -> &Platform {
|
||||
&self.platform
|
||||
|
|
@ -106,18 +109,6 @@ impl InterpreterInfo {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub(crate) enum InterpreterQueryError {
|
||||
#[error(transparent)]
|
||||
IO(#[from] io::Error),
|
||||
#[error("Failed to query python interpreter at {interpreter}")]
|
||||
PythonSubcommand {
|
||||
interpreter: PathBuf,
|
||||
#[source]
|
||||
err: io::Error,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub(crate) struct InterpreterQueryResult {
|
||||
pub(crate) markers: MarkerEnvironment,
|
||||
|
|
@ -128,11 +119,11 @@ pub(crate) struct InterpreterQueryResult {
|
|||
|
||||
impl InterpreterQueryResult {
|
||||
/// Return the resolved [`InterpreterQueryResult`] for the given Python executable.
|
||||
pub(crate) fn query(interpreter: &Path) -> Result<Self, InterpreterQueryError> {
|
||||
pub(crate) fn query(interpreter: &Path) -> Result<Self, Error> {
|
||||
let output = Command::new(interpreter)
|
||||
.args(["-c", include_str!("get_interpreter_info.py")])
|
||||
.output()
|
||||
.map_err(|err| InterpreterQueryError::PythonSubcommand {
|
||||
.map_err(|err| Error::PythonSubcommandLaunch {
|
||||
interpreter: interpreter.to_path_buf(),
|
||||
err,
|
||||
})?;
|
||||
|
|
@ -140,35 +131,27 @@ impl InterpreterQueryResult {
|
|||
// stderr isn't technically a criterion for success, but i don't know of any cases where there
|
||||
// should be stderr output and if there is, we want to know
|
||||
if !output.status.success() || !output.stderr.is_empty() {
|
||||
return Err(InterpreterQueryError::PythonSubcommand {
|
||||
interpreter: interpreter.to_path_buf(),
|
||||
err: io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
format!(
|
||||
"Querying python at {} failed with status {}:\n--- stdout:\n{}\n--- stderr:\n{}",
|
||||
return Err(Error::PythonSubcommandOutput {
|
||||
message: format!(
|
||||
"Querying python at {} failed with status {}",
|
||||
interpreter.display(),
|
||||
output.status,
|
||||
String::from_utf8_lossy(&output.stdout).trim(),
|
||||
String::from_utf8_lossy(&output.stderr).trim()
|
||||
),
|
||||
),
|
||||
stdout: String::from_utf8_lossy(&output.stdout).trim().to_string(),
|
||||
stderr: String::from_utf8_lossy(&output.stderr).trim().to_string(),
|
||||
});
|
||||
}
|
||||
let data = serde_json::from_slice::<Self>(&output.stdout).map_err(|err|
|
||||
InterpreterQueryError::PythonSubcommand {
|
||||
interpreter: interpreter.to_path_buf(),
|
||||
err: io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
format!(
|
||||
"Querying python at {} did not return the expected data ({}):\n--- stdout:\n{}\n--- stderr:\n{}",
|
||||
let data = serde_json::from_slice::<Self>(&output.stdout).map_err(|err| {
|
||||
Error::PythonSubcommandOutput {
|
||||
message: format!(
|
||||
"Querying python at {} did not return the expected data: {}",
|
||||
interpreter.display(),
|
||||
err,
|
||||
String::from_utf8_lossy(&output.stdout).trim(),
|
||||
String::from_utf8_lossy(&output.stderr).trim()
|
||||
),
|
||||
),
|
||||
stdout: String::from_utf8_lossy(&output.stdout).trim().to_string(),
|
||||
stderr: String::from_utf8_lossy(&output.stderr).trim().to_string(),
|
||||
}
|
||||
)?;
|
||||
})?;
|
||||
|
||||
Ok(data)
|
||||
}
|
||||
|
|
@ -178,7 +161,7 @@ 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: &Path) -> Result<Self> {
|
||||
pub(crate) fn query_cached(executable: &Path, cache: &Path) -> Result<Self, Error> {
|
||||
// Read from the cache.
|
||||
let key = if let Ok(key) = cache_key(executable) {
|
||||
if let Ok(data) = cacache::read_sync(cache, &key) {
|
||||
|
|
@ -211,11 +194,15 @@ 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) -> Result<String> {
|
||||
let modified = executable
|
||||
.metadata()?
|
||||
fn cache_key(executable: &Path) -> Result<String, Error> {
|
||||
let modified = fs_err::metadata(executable)?
|
||||
// Note: This is infallible on windows and unix (i.e. all platforms we support)
|
||||
.modified()?
|
||||
.duration_since(std::time::UNIX_EPOCH)?
|
||||
.as_millis();
|
||||
Ok(format!("puffin:v0:{}:{}", executable.display(), modified))
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.map_err(|err| Error::SystemTime(executable.to_path_buf(), err))?;
|
||||
Ok(format!(
|
||||
"puffin:v0:{}:{}",
|
||||
executable.display(),
|
||||
modified.as_millis()
|
||||
))
|
||||
}
|
||||
|
|
@ -1,6 +1,44 @@
|
|||
pub use crate::interpreter_info::InterpreterInfo;
|
||||
use std::io;
|
||||
use std::path::PathBuf;
|
||||
use std::time::SystemTimeError;
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
pub use crate::interpreter::Interpreter;
|
||||
pub use crate::virtual_env::Virtualenv;
|
||||
|
||||
mod interpreter_info;
|
||||
mod interpreter;
|
||||
mod python_platform;
|
||||
mod virtual_env;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum Error {
|
||||
#[error("Expected {0} to be a virtual environment, but pyvenv.cfg is missing")]
|
||||
MissingPyVenvCfg(PathBuf),
|
||||
#[error("Your virtualenv at {0} is broken. It contains a pyvenv.cfg but no python at {1}")]
|
||||
BrokenVenv(PathBuf, PathBuf),
|
||||
#[error("Both VIRTUAL_ENV and CONDA_PREFIX are set. Please unset one of them.")]
|
||||
Conflict,
|
||||
#[error("Couldn't find a virtualenv or conda environment (Looked for VIRTUAL_ENV, CONDA_PREFIX and .venv)")]
|
||||
NotFound,
|
||||
#[error(transparent)]
|
||||
Io(#[from] io::Error),
|
||||
#[error("Invalid modified date on {0}")]
|
||||
SystemTime(PathBuf, #[source] SystemTimeError),
|
||||
#[error("Failed to query python interpreter at {interpreter}")]
|
||||
PythonSubcommandLaunch {
|
||||
interpreter: PathBuf,
|
||||
#[source]
|
||||
err: io::Error,
|
||||
},
|
||||
#[error("{message}:\n--- stdout:\n{stdout}\n--- stderr:\n{stderr}\n---")]
|
||||
PythonSubcommandOutput {
|
||||
message: String,
|
||||
stdout: String,
|
||||
stderr: String,
|
||||
},
|
||||
#[error("Failed to write to cache")]
|
||||
Cacache(#[from] cacache::Error),
|
||||
#[error("Failed to write to cache")]
|
||||
Serde(#[from] serde_json::Error),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,52 +1,58 @@
|
|||
use std::env;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use crate::InterpreterInfo;
|
||||
use anyhow::{bail, Result};
|
||||
use platform_host::Platform;
|
||||
use tracing::debug;
|
||||
|
||||
use platform_host::Platform;
|
||||
|
||||
use crate::python_platform::PythonPlatform;
|
||||
use crate::{Error, Interpreter};
|
||||
|
||||
/// A Python executable and its associated platform markers.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Virtualenv {
|
||||
root: PathBuf,
|
||||
interpreter_info: InterpreterInfo,
|
||||
interpreter: Interpreter,
|
||||
}
|
||||
|
||||
impl Virtualenv {
|
||||
/// Venv the current Python executable from the host environment.
|
||||
pub fn from_env(platform: Platform, cache: Option<&Path>) -> Result<Self> {
|
||||
pub fn from_env(platform: Platform, cache: Option<&Path>) -> Result<Self, Error> {
|
||||
let platform = PythonPlatform::from(platform);
|
||||
let venv = detect_virtual_env(&platform)?;
|
||||
let Some(venv) = detect_virtual_env(&platform)? else {
|
||||
return Err(Error::NotFound);
|
||||
};
|
||||
let executable = platform.venv_python(&venv);
|
||||
let interpreter_info = InterpreterInfo::query(&executable, platform.0, cache)?;
|
||||
let interpreter = Interpreter::query(&executable, platform.0, cache)?;
|
||||
|
||||
Ok(Self {
|
||||
root: venv,
|
||||
interpreter_info,
|
||||
interpreter,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn from_virtualenv(platform: Platform, root: &Path, cache: Option<&Path>) -> Result<Self> {
|
||||
pub fn from_virtualenv(
|
||||
platform: Platform,
|
||||
root: &Path,
|
||||
cache: Option<&Path>,
|
||||
) -> Result<Self, Error> {
|
||||
let platform = PythonPlatform::from(platform);
|
||||
let executable = platform.venv_python(root);
|
||||
let interpreter_info = InterpreterInfo::query(&executable, platform.0, cache)?;
|
||||
let interpreter = Interpreter::query(&executable, platform.0, cache)?;
|
||||
|
||||
Ok(Self {
|
||||
root: root.to_path_buf(),
|
||||
interpreter_info,
|
||||
interpreter,
|
||||
})
|
||||
}
|
||||
|
||||
/// Creating a new venv from a python interpreter changes this
|
||||
pub fn new_prefix(venv: &Path, interpreter_info: &InterpreterInfo) -> Self {
|
||||
pub fn new_prefix(venv: &Path, interpreter: &Interpreter) -> Self {
|
||||
Self {
|
||||
root: venv.to_path_buf(),
|
||||
interpreter_info: InterpreterInfo {
|
||||
interpreter: Interpreter {
|
||||
base_prefix: venv.to_path_buf(),
|
||||
..interpreter_info.clone()
|
||||
..interpreter.clone()
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
@ -74,25 +80,37 @@ impl Virtualenv {
|
|||
&self.root
|
||||
}
|
||||
|
||||
pub fn interpreter_info(&self) -> &InterpreterInfo {
|
||||
&self.interpreter_info
|
||||
pub fn interpreter(&self) -> &Interpreter {
|
||||
&self.interpreter
|
||||
}
|
||||
|
||||
/// Returns the path to the `site-packages` directory inside a virtual environment.
|
||||
pub fn site_packages(&self) -> PathBuf {
|
||||
self.interpreter_info
|
||||
self.interpreter
|
||||
.platform
|
||||
.venv_site_packages(&self.root, self.interpreter_info().simple_version())
|
||||
.venv_site_packages(&self.root, self.interpreter().simple_version())
|
||||
}
|
||||
}
|
||||
|
||||
/// Locate the current virtual environment.
|
||||
pub(crate) fn detect_virtual_env(target: &PythonPlatform) -> Result<PathBuf> {
|
||||
pub(crate) fn detect_virtual_env(target: &PythonPlatform) -> Result<Option<PathBuf>, Error> {
|
||||
match (env::var_os("VIRTUAL_ENV"), env::var_os("CONDA_PREFIX")) {
|
||||
(Some(dir), None) => return Ok(PathBuf::from(dir)),
|
||||
(None, Some(dir)) => return Ok(PathBuf::from(dir)),
|
||||
(Some(dir), None) => {
|
||||
debug!(
|
||||
"Found a virtualenv through VIRTUAL_ENV at {}",
|
||||
Path::new(&dir).display()
|
||||
);
|
||||
return Ok(Some(PathBuf::from(dir)));
|
||||
}
|
||||
(None, Some(dir)) => {
|
||||
debug!(
|
||||
"Found a virtualenv through CONDA_PREFIX at {}",
|
||||
Path::new(&dir).display()
|
||||
);
|
||||
return Ok(Some(PathBuf::from(dir)));
|
||||
}
|
||||
(Some(_), Some(_)) => {
|
||||
bail!("Both VIRTUAL_ENV and CONDA_PREFIX are set. Please unset one of them.")
|
||||
return Err(Error::Conflict);
|
||||
}
|
||||
(None, None) => {
|
||||
// No environment variables set. Try to find a virtualenv in the current directory.
|
||||
|
|
@ -105,23 +123,16 @@ pub(crate) fn detect_virtual_env(target: &PythonPlatform) -> Result<PathBuf> {
|
|||
let dot_venv = dir.join(".venv");
|
||||
if dot_venv.is_dir() {
|
||||
if !dot_venv.join("pyvenv.cfg").is_file() {
|
||||
bail!(
|
||||
"Expected {} to be a virtual environment, but pyvenv.cfg is missing",
|
||||
dot_venv.display()
|
||||
);
|
||||
return Err(Error::MissingPyVenvCfg(dot_venv));
|
||||
}
|
||||
let python = target.venv_python(&dot_venv);
|
||||
if !python.is_file() {
|
||||
bail!(
|
||||
"Your virtualenv at {} is broken. It contains a pyvenv.cfg but no python at {}",
|
||||
dot_venv.display(),
|
||||
python.display()
|
||||
);
|
||||
return Err(Error::BrokenVenv(dot_venv, python));
|
||||
}
|
||||
debug!("Found a virtualenv named .venv at {}", dot_venv.display());
|
||||
return Ok(dot_venv);
|
||||
return Ok(Some(dot_venv));
|
||||
}
|
||||
}
|
||||
|
||||
bail!("Couldn't find a virtualenv or conda environment.")
|
||||
Ok(None)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ use pep440_rs::Version;
|
|||
use pep508_rs::{Requirement, VersionOrUrl};
|
||||
use platform_tags::{TagPriority, Tags};
|
||||
use puffin_client::RegistryClient;
|
||||
use puffin_interpreter::InterpreterInfo;
|
||||
use puffin_interpreter::Interpreter;
|
||||
use puffin_normalize::PackageName;
|
||||
use pypi_types::{File, IndexUrl, SimpleJson};
|
||||
|
||||
|
|
@ -26,21 +26,17 @@ pub struct DistFinder<'a> {
|
|||
tags: &'a Tags,
|
||||
client: &'a RegistryClient,
|
||||
reporter: Option<Box<dyn Reporter>>,
|
||||
interpreter_info: &'a InterpreterInfo,
|
||||
interpreter: &'a Interpreter,
|
||||
}
|
||||
|
||||
impl<'a> DistFinder<'a> {
|
||||
/// Initialize a new distribution finder.
|
||||
pub fn new(
|
||||
tags: &'a Tags,
|
||||
client: &'a RegistryClient,
|
||||
interpreter_info: &'a InterpreterInfo,
|
||||
) -> Self {
|
||||
pub fn new(tags: &'a Tags, client: &'a RegistryClient, interpreter: &'a Interpreter) -> Self {
|
||||
Self {
|
||||
tags,
|
||||
client,
|
||||
reporter: None,
|
||||
interpreter_info,
|
||||
interpreter,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -157,7 +153,7 @@ impl<'a> DistFinder<'a> {
|
|||
.requires_python
|
||||
.as_ref()
|
||||
.map_or(true, |requires_python| {
|
||||
requires_python.contains(self.interpreter_info.version())
|
||||
requires_python.contains(self.interpreter.version())
|
||||
})
|
||||
{
|
||||
continue;
|
||||
|
|
|
|||
|
|
@ -552,7 +552,7 @@ impl<'a, Context: BuildContext + Send + Sync> Resolver<'a, Context> {
|
|||
metadata,
|
||||
&package_name,
|
||||
self.tags,
|
||||
self.build_context.interpreter_info().version(),
|
||||
self.build_context.interpreter().version(),
|
||||
self.exclude_newer.as_ref(),
|
||||
);
|
||||
self.index
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ use pep508_rs::{MarkerEnvironment, Requirement, StringVersion};
|
|||
use platform_host::{Arch, Os, Platform};
|
||||
use platform_tags::Tags;
|
||||
use puffin_client::RegistryClientBuilder;
|
||||
use puffin_interpreter::{InterpreterInfo, Virtualenv};
|
||||
use puffin_interpreter::{Interpreter, Virtualenv};
|
||||
use puffin_resolver::{
|
||||
Graph, Manifest, PreReleaseMode, ResolutionMode, ResolutionOptions, Resolver,
|
||||
};
|
||||
|
|
@ -31,7 +31,7 @@ static EXCLUDE_NEWER: Lazy<DateTime<Utc>> = Lazy::new(|| {
|
|||
});
|
||||
|
||||
struct DummyContext {
|
||||
interpreter_info: InterpreterInfo,
|
||||
interpreter: Interpreter,
|
||||
}
|
||||
|
||||
impl BuildContext for DummyContext {
|
||||
|
|
@ -39,8 +39,8 @@ impl BuildContext for DummyContext {
|
|||
panic!("The test should not need to build source distributions")
|
||||
}
|
||||
|
||||
fn interpreter_info(&self) -> &InterpreterInfo {
|
||||
&self.interpreter_info
|
||||
fn interpreter(&self) -> &Interpreter {
|
||||
&self.interpreter
|
||||
}
|
||||
|
||||
fn base_python(&self) -> &Path {
|
||||
|
|
@ -82,7 +82,7 @@ async fn resolve(
|
|||
let temp_dir = tempdir()?;
|
||||
let client = RegistryClientBuilder::new(temp_dir.path()).build();
|
||||
let build_context = DummyContext {
|
||||
interpreter_info: InterpreterInfo::artificial(
|
||||
interpreter: Interpreter::artificial(
|
||||
Platform::current()?,
|
||||
markers.clone(),
|
||||
PathBuf::from("/dev/null"),
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ use std::pin::Pin;
|
|||
use anyhow::Result;
|
||||
|
||||
use pep508_rs::Requirement;
|
||||
use puffin_interpreter::{InterpreterInfo, Virtualenv};
|
||||
use puffin_interpreter::{Interpreter, Virtualenv};
|
||||
|
||||
/// Avoid cyclic crate dependencies between resolver, installer and builder.
|
||||
///
|
||||
|
|
@ -54,7 +54,7 @@ pub trait BuildContext {
|
|||
|
||||
/// All (potentially nested) source distribution builds use the same base python and can reuse
|
||||
/// it's metadata (e.g. wheel compatibility tags).
|
||||
fn interpreter_info(&self) -> &InterpreterInfo;
|
||||
fn interpreter(&self) -> &Interpreter;
|
||||
|
||||
/// The system (or conda) python interpreter to create venvs.
|
||||
fn base_python(&self) -> &Path;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue