mirror of
https://github.com/astral-sh/uv.git
synced 2025-10-26 18:06:45 +00:00
Allow selection of pyodide interpreters with "pyodide" (#15256)
This commit is contained in:
parent
88a7b2d864
commit
2c54d3929c
8 changed files with 243 additions and 93 deletions
|
|
@ -1190,11 +1190,7 @@ pub fn find_python_installations<'a>(
|
|||
cache,
|
||||
preview,
|
||||
)
|
||||
.filter_ok(|(_source, interpreter)| {
|
||||
interpreter
|
||||
.implementation_name()
|
||||
.eq_ignore_ascii_case(implementation.into())
|
||||
})
|
||||
.filter_ok(|(_source, interpreter)| implementation.matches_interpreter(interpreter))
|
||||
.map_ok(|tuple| Ok(PythonInstallation::from_tuple(tuple)))
|
||||
}),
|
||||
PythonRequest::ImplementationVersion(implementation, version) => {
|
||||
|
|
@ -1212,11 +1208,7 @@ pub fn find_python_installations<'a>(
|
|||
cache,
|
||||
preview,
|
||||
)
|
||||
.filter_ok(|(_source, interpreter)| {
|
||||
interpreter
|
||||
.implementation_name()
|
||||
.eq_ignore_ascii_case(implementation.into())
|
||||
})
|
||||
.filter_ok(|(_source, interpreter)| implementation.matches_interpreter(interpreter))
|
||||
.map_ok(|tuple| Ok(PythonInstallation::from_tuple(tuple)))
|
||||
})
|
||||
}
|
||||
|
|
@ -1260,7 +1252,6 @@ pub(crate) fn find_python_installation(
|
|||
let mut first_prerelease = None;
|
||||
let mut first_managed = None;
|
||||
let mut first_error = None;
|
||||
let mut emscripten_installation = None;
|
||||
for result in installations {
|
||||
// Iterate until the first critical error or happy result
|
||||
if !result.as_ref().err().is_none_or(Error::is_critical) {
|
||||
|
|
@ -1278,15 +1269,6 @@ pub(crate) fn find_python_installation(
|
|||
return result;
|
||||
};
|
||||
|
||||
if installation.os().is_emscripten() {
|
||||
// We want to pick a native Python over an Emscripten Python if we
|
||||
// can find any native Python.
|
||||
if emscripten_installation.is_none() {
|
||||
emscripten_installation = Some(installation.clone());
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if we need to skip the interpreter because it is "not allowed", e.g., if it is a
|
||||
// pre-release version or an alternative implementation, using it requires opt-in.
|
||||
|
||||
|
|
@ -1363,10 +1345,6 @@ pub(crate) fn find_python_installation(
|
|||
return Ok(Ok(installation));
|
||||
}
|
||||
|
||||
if let Some(emscripten_python) = emscripten_installation {
|
||||
return Ok(Ok(emscripten_python));
|
||||
}
|
||||
|
||||
// If we found a Python, but it was unusable for some reason, report that instead of saying we
|
||||
// couldn't find any Python interpreters.
|
||||
if let Some(err) = first_error {
|
||||
|
|
@ -1964,8 +1942,10 @@ impl PythonRequest {
|
|||
Self::Any => true,
|
||||
Self::Version(_) => false,
|
||||
Self::Directory(_) | Self::File(_) | Self::ExecutableName(_) => true,
|
||||
Self::Implementation(_) => true,
|
||||
Self::ImplementationVersion(_, _) => true,
|
||||
Self::Implementation(implementation)
|
||||
| Self::ImplementationVersion(implementation, _) => {
|
||||
!matches!(implementation, ImplementationName::CPython)
|
||||
}
|
||||
Self::Key(request) => request.allows_alternative_implementations(),
|
||||
}
|
||||
}
|
||||
|
|
@ -3680,6 +3660,7 @@ mod tests {
|
|||
"any",
|
||||
&[
|
||||
"python", "python3", "cpython", "cpython3", "pypy", "pypy3", "graalpy", "graalpy3",
|
||||
"pyodide", "pyodide3",
|
||||
],
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -261,7 +261,17 @@ impl PythonDownloadRequest {
|
|||
|
||||
#[must_use]
|
||||
pub fn with_implementation(mut self, implementation: ImplementationName) -> Self {
|
||||
self.implementation = Some(implementation);
|
||||
match implementation {
|
||||
// Pyodide is actually CPython with an Emscripten OS, we paper over that for usability
|
||||
ImplementationName::Pyodide => {
|
||||
self = self.with_os(Os::new(target_lexicon::OperatingSystem::Emscripten));
|
||||
self = self.with_arch(Arch::new(target_lexicon::Architecture::Wasm32, None));
|
||||
self = self.with_libc(Libc::Some(target_lexicon::Environment::Musl));
|
||||
}
|
||||
_ => {
|
||||
self.implementation = Some(implementation);
|
||||
}
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
|
|
@ -438,7 +448,9 @@ impl PythonDownloadRequest {
|
|||
|
||||
/// Whether this download request opts-in to alternative Python implementations.
|
||||
pub fn allows_alternative_implementations(&self) -> bool {
|
||||
self.implementation.is_some()
|
||||
self.implementation
|
||||
.is_some_and(|implementation| !matches!(implementation, ImplementationName::CPython))
|
||||
|| self.os.is_some_and(|os| os.is_emscripten())
|
||||
}
|
||||
|
||||
pub fn satisfied_by_interpreter(&self, interpreter: &Interpreter) -> bool {
|
||||
|
|
@ -461,12 +473,10 @@ impl PythonDownloadRequest {
|
|||
return false;
|
||||
}
|
||||
if let Some(implementation) = self.implementation() {
|
||||
let interpreter_implementation = interpreter.implementation_name();
|
||||
if LenientImplementationName::from(interpreter_implementation)
|
||||
!= LenientImplementationName::from(*implementation)
|
||||
{
|
||||
if !implementation.matches_interpreter(interpreter) {
|
||||
debug!(
|
||||
"Skipping interpreter at `{executable}`: implementation `{interpreter_implementation}` does not match request `{implementation}`"
|
||||
"Skipping interpreter at `{executable}`: implementation `{}` does not match request `{implementation}`",
|
||||
interpreter.implementation_name(),
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
|
@ -1512,13 +1522,16 @@ mod tests {
|
|||
request.version,
|
||||
Some(VersionRequest::from_str("3.12.0").unwrap())
|
||||
);
|
||||
assert_eq!(request.os, Some(Os(target_lexicon::OperatingSystem::Linux)));
|
||||
assert_eq!(
|
||||
request.os,
|
||||
Some(Os::new(target_lexicon::OperatingSystem::Linux))
|
||||
);
|
||||
assert_eq!(
|
||||
request.arch,
|
||||
Some(ArchRequest::Explicit(Arch {
|
||||
family: target_lexicon::Architecture::X86_64,
|
||||
variant: None,
|
||||
}))
|
||||
Some(ArchRequest::Explicit(Arch::new(
|
||||
target_lexicon::Architecture::X86_64,
|
||||
None
|
||||
)))
|
||||
);
|
||||
assert_eq!(
|
||||
request.libc,
|
||||
|
|
@ -1540,10 +1553,10 @@ mod tests {
|
|||
assert_eq!(request.os, None);
|
||||
assert_eq!(
|
||||
request.arch,
|
||||
Some(ArchRequest::Explicit(Arch {
|
||||
family: target_lexicon::Architecture::X86_64,
|
||||
variant: None,
|
||||
}))
|
||||
Some(ArchRequest::Explicit(Arch::new(
|
||||
target_lexicon::Architecture::X86_64,
|
||||
None
|
||||
)))
|
||||
);
|
||||
assert_eq!(request.libc, None);
|
||||
}
|
||||
|
|
@ -1556,7 +1569,10 @@ mod tests {
|
|||
|
||||
assert_eq!(request.implementation, Some(ImplementationName::PyPy));
|
||||
assert_eq!(request.version, None);
|
||||
assert_eq!(request.os, Some(Os(target_lexicon::OperatingSystem::Linux)));
|
||||
assert_eq!(
|
||||
request.os,
|
||||
Some(Os::new(target_lexicon::OperatingSystem::Linux))
|
||||
);
|
||||
assert_eq!(request.arch, None);
|
||||
assert_eq!(request.libc, None);
|
||||
}
|
||||
|
|
@ -1598,14 +1614,14 @@ mod tests {
|
|||
assert_eq!(request.version, None);
|
||||
assert_eq!(
|
||||
request.os,
|
||||
Some(Os(target_lexicon::OperatingSystem::Windows))
|
||||
Some(Os::new(target_lexicon::OperatingSystem::Windows))
|
||||
);
|
||||
assert_eq!(
|
||||
request.arch,
|
||||
Some(ArchRequest::Explicit(Arch {
|
||||
family: target_lexicon::Architecture::X86_64,
|
||||
variant: None,
|
||||
}))
|
||||
Some(ArchRequest::Explicit(Arch::new(
|
||||
target_lexicon::Architecture::X86_64,
|
||||
None
|
||||
)))
|
||||
);
|
||||
assert_eq!(request.libc, None);
|
||||
}
|
||||
|
|
@ -1669,10 +1685,10 @@ mod tests {
|
|||
assert_eq!(request.os, None);
|
||||
assert_eq!(
|
||||
request.arch,
|
||||
Some(ArchRequest::Explicit(Arch {
|
||||
family: target_lexicon::Architecture::X86_64,
|
||||
variant: None,
|
||||
}))
|
||||
Some(ArchRequest::Explicit(Arch::new(
|
||||
target_lexicon::Architecture::X86_64,
|
||||
None
|
||||
)))
|
||||
);
|
||||
assert_eq!(request.libc, None);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ use std::{
|
|||
};
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::Interpreter;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum Error {
|
||||
#[error("Unknown Python implementation `{0}`")]
|
||||
|
|
@ -12,6 +14,7 @@ pub enum Error {
|
|||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Copy, Default, PartialOrd, Ord, Hash)]
|
||||
pub enum ImplementationName {
|
||||
Pyodide,
|
||||
GraalPy,
|
||||
PyPy,
|
||||
#[default]
|
||||
|
|
@ -30,11 +33,11 @@ impl ImplementationName {
|
|||
}
|
||||
|
||||
pub(crate) fn long_names() -> impl Iterator<Item = &'static str> {
|
||||
["cpython", "pypy", "graalpy"].into_iter()
|
||||
["cpython", "pypy", "graalpy", "pyodide"].into_iter()
|
||||
}
|
||||
|
||||
pub(crate) fn iter_all() -> impl Iterator<Item = Self> {
|
||||
[Self::CPython, Self::PyPy, Self::GraalPy].into_iter()
|
||||
[Self::CPython, Self::PyPy, Self::GraalPy, Self::Pyodide].into_iter()
|
||||
}
|
||||
|
||||
pub fn pretty(self) -> &'static str {
|
||||
|
|
@ -42,15 +45,25 @@ impl ImplementationName {
|
|||
Self::CPython => "CPython",
|
||||
Self::PyPy => "PyPy",
|
||||
Self::GraalPy => "GraalPy",
|
||||
Self::Pyodide => "Pyodide",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn executable_name(self) -> &'static str {
|
||||
match self {
|
||||
Self::CPython => "python",
|
||||
Self::CPython | Self::Pyodide => "python",
|
||||
Self::PyPy | Self::GraalPy => self.into(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn matches_interpreter(self, interpreter: &Interpreter) -> bool {
|
||||
match self {
|
||||
Self::Pyodide => interpreter.os().is_emscripten(),
|
||||
_ => interpreter
|
||||
.implementation_name()
|
||||
.eq_ignore_ascii_case(self.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl LenientImplementationName {
|
||||
|
|
@ -75,6 +88,7 @@ impl From<&ImplementationName> for &'static str {
|
|||
ImplementationName::CPython => "cpython",
|
||||
ImplementationName::PyPy => "pypy",
|
||||
ImplementationName::GraalPy => "graalpy",
|
||||
ImplementationName::Pyodide => "pyodide",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -105,6 +119,7 @@ impl FromStr for ImplementationName {
|
|||
"cpython" | "cp" => Ok(Self::CPython),
|
||||
"pypy" | "pp" => Ok(Self::PyPy),
|
||||
"graalpy" | "gp" => Ok(Self::GraalPy),
|
||||
"pyodide" => Ok(Self::Pyodide),
|
||||
_ => Err(Error::UnknownImplementation(s.to_string())),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
use std::borrow::Cow;
|
||||
use std::fmt;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::str::FromStr;
|
||||
|
|
@ -310,7 +311,7 @@ impl PythonInstallation {
|
|||
!matches!(
|
||||
self.implementation(),
|
||||
LenientImplementationName::Known(ImplementationName::CPython)
|
||||
)
|
||||
) || self.os().is_emscripten()
|
||||
}
|
||||
|
||||
/// Return the [`Arch`] of the Python installation as reported by its interpreter.
|
||||
|
|
@ -394,8 +395,12 @@ impl PythonInstallationKey {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn implementation(&self) -> &LenientImplementationName {
|
||||
&self.implementation
|
||||
pub fn implementation(&self) -> Cow<'_, LenientImplementationName> {
|
||||
if self.os().is_emscripten() {
|
||||
Cow::Owned(LenientImplementationName::from(ImplementationName::Pyodide))
|
||||
} else {
|
||||
Cow::Borrowed(&self.implementation)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn version(&self) -> PythonVersion {
|
||||
|
|
@ -484,7 +489,7 @@ impl fmt::Display for PythonInstallationKey {
|
|||
write!(
|
||||
f,
|
||||
"{}-{}.{}.{}{}{}-{}",
|
||||
self.implementation,
|
||||
self.implementation(),
|
||||
self.major,
|
||||
self.minor,
|
||||
self.patch,
|
||||
|
|
|
|||
|
|
@ -457,7 +457,7 @@ mod tests {
|
|||
|
||||
fn add_pyodide_version(&mut self, version: &'static str) -> Result<()> {
|
||||
let path = self.new_search_path_directory(format!("pyodide-{version}"))?;
|
||||
let python = format!("python{}", env::consts::EXE_SUFFIX);
|
||||
let python = format!("pyodide{}", env::consts::EXE_SUFFIX);
|
||||
Self::create_mock_pyodide_interpreter(
|
||||
&path.join(python),
|
||||
&PythonVersion::from_str(version).unwrap(),
|
||||
|
|
@ -2705,7 +2705,9 @@ mod tests {
|
|||
let mut context = TestContext::new()?;
|
||||
|
||||
context.add_pyodide_version("3.13.2")?;
|
||||
let python = context.run(|| {
|
||||
|
||||
// We should not find the Pyodide interpreter by default
|
||||
let result = context.run(|| {
|
||||
find_python_installation(
|
||||
&PythonRequest::Default,
|
||||
EnvironmentPreference::Any,
|
||||
|
|
@ -2713,14 +2715,28 @@ mod tests {
|
|||
&context.cache,
|
||||
Preview::default(),
|
||||
)
|
||||
})?;
|
||||
assert!(
|
||||
result.is_err(),
|
||||
"We should not find an python; got {result:?}"
|
||||
);
|
||||
|
||||
// With `Any`, it should be discoverable
|
||||
let python = context.run(|| {
|
||||
find_python_installation(
|
||||
&PythonRequest::Any,
|
||||
EnvironmentPreference::Any,
|
||||
PythonPreference::OnlySystem,
|
||||
&context.cache,
|
||||
Preview::default(),
|
||||
)
|
||||
})??;
|
||||
// We should find the Pyodide interpreter
|
||||
assert_eq!(
|
||||
python.interpreter().python_full_version().to_string(),
|
||||
"3.13.2"
|
||||
);
|
||||
|
||||
// We should prefer any native Python to the Pyodide Python
|
||||
// We should prefer the native Python to the Pyodide Python
|
||||
context.add_python_versions(&["3.15.7"])?;
|
||||
|
||||
let python = context.run(|| {
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ use uv_configuration::{Preview, PreviewFeatures};
|
|||
use windows_sys::Win32::Storage::FileSystem::FILE_ATTRIBUTE_REPARSE_POINT;
|
||||
|
||||
use uv_fs::{LockedFile, Simplified, replace_symlink, symlink_or_copy_file};
|
||||
use uv_platform::Error as PlatformError;
|
||||
use uv_platform::{Error as PlatformError, Os};
|
||||
use uv_platform::{LibcDetectionError, Platform};
|
||||
use uv_state::{StateBucket, StateStore};
|
||||
use uv_static::EnvVars;
|
||||
|
|
@ -358,8 +358,6 @@ impl ManagedPythonInstallation {
|
|||
/// If windowed is true, `pythonw.exe` is selected over `python.exe` on windows, with no changes
|
||||
/// on non-windows.
|
||||
pub fn executable(&self, windowed: bool) -> PathBuf {
|
||||
let implementation = self.implementation().executable_name();
|
||||
|
||||
let version = match self.implementation() {
|
||||
ImplementationName::CPython => {
|
||||
if cfg!(unix) {
|
||||
|
|
@ -370,12 +368,14 @@ impl ManagedPythonInstallation {
|
|||
}
|
||||
// PyPy uses a full version number, even on Windows.
|
||||
ImplementationName::PyPy => format!("{}.{}", self.key.major, self.key.minor),
|
||||
// Pyodide and GraalPy do not have a version suffix.
|
||||
ImplementationName::Pyodide => String::new(),
|
||||
ImplementationName::GraalPy => String::new(),
|
||||
};
|
||||
|
||||
// On Windows, the executable is just `python.exe` even for alternative variants
|
||||
// GraalPy always uses `graalpy.exe` as the main executable
|
||||
let variant = if *self.implementation() == ImplementationName::GraalPy {
|
||||
let variant = if self.implementation() == ImplementationName::GraalPy {
|
||||
""
|
||||
} else if cfg!(unix) {
|
||||
self.key.variant.suffix()
|
||||
|
|
@ -388,13 +388,15 @@ impl ManagedPythonInstallation {
|
|||
|
||||
let name = format!(
|
||||
"{implementation}{version}{variant}{exe}",
|
||||
implementation = self.implementation().executable_name(),
|
||||
exe = std::env::consts::EXE_SUFFIX
|
||||
);
|
||||
|
||||
let executable = executable_path_from_base(
|
||||
self.python_dir().as_path(),
|
||||
&name,
|
||||
&LenientImplementationName::from(*self.implementation()),
|
||||
&LenientImplementationName::from(self.implementation()),
|
||||
*self.key.os(),
|
||||
);
|
||||
|
||||
// Workaround for python-build-standalone v20241016 which is missing the standard
|
||||
|
|
@ -431,8 +433,8 @@ impl ManagedPythonInstallation {
|
|||
self.key.version()
|
||||
}
|
||||
|
||||
pub fn implementation(&self) -> &ImplementationName {
|
||||
match self.key.implementation() {
|
||||
pub fn implementation(&self) -> ImplementationName {
|
||||
match self.key.implementation().into_owned() {
|
||||
LenientImplementationName::Known(implementation) => implementation,
|
||||
LenientImplementationName::Unknown(_) => {
|
||||
panic!("Managed Python installations should have a known implementation")
|
||||
|
|
@ -466,10 +468,10 @@ impl ManagedPythonInstallation {
|
|||
.file_name()
|
||||
.is_some_and(|filename| filename.to_string_lossy() == *name),
|
||||
PythonRequest::Implementation(implementation) => {
|
||||
implementation == self.implementation()
|
||||
*implementation == self.implementation()
|
||||
}
|
||||
PythonRequest::ImplementationVersion(implementation, version) => {
|
||||
implementation == self.implementation() && version.matches_version(&self.version())
|
||||
*implementation == self.implementation() && version.matches_version(&self.version())
|
||||
}
|
||||
PythonRequest::Version(version) => version.matches_version(&self.version()),
|
||||
PythonRequest::Key(request) => request.satisfied_by_key(self.key()),
|
||||
|
|
@ -579,7 +581,7 @@ impl ManagedPythonInstallation {
|
|||
// sysconfig directly
|
||||
return Ok(());
|
||||
}
|
||||
if *self.implementation() == ImplementationName::CPython {
|
||||
if self.implementation() == ImplementationName::CPython {
|
||||
sysconfig::update_sysconfig(
|
||||
self.path(),
|
||||
self.key.major,
|
||||
|
|
@ -600,7 +602,7 @@ impl ManagedPythonInstallation {
|
|||
pub fn ensure_dylib_patched(&self) -> Result<(), macos_dylib::Error> {
|
||||
if cfg!(target_os = "macos") {
|
||||
if self.key().os().is_like_darwin() {
|
||||
if *self.implementation() == ImplementationName::CPython {
|
||||
if self.implementation() == ImplementationName::CPython {
|
||||
let dylib_path = self.python_dir().join("lib").join(format!(
|
||||
"{}python{}{}{}",
|
||||
std::env::consts::DLL_PREFIX,
|
||||
|
|
@ -716,7 +718,7 @@ impl PythonMinorVersionLink {
|
|||
) -> Option<Self> {
|
||||
let implementation = key.implementation();
|
||||
if !matches!(
|
||||
implementation,
|
||||
implementation.as_ref(),
|
||||
LenientImplementationName::Known(ImplementationName::CPython)
|
||||
) {
|
||||
// We don't currently support transparent upgrades for PyPy or GraalPy.
|
||||
|
|
@ -755,7 +757,8 @@ impl PythonMinorVersionLink {
|
|||
let symlink_executable = executable_path_from_base(
|
||||
symlink_directory.as_path(),
|
||||
&executable_name.to_string_lossy(),
|
||||
implementation,
|
||||
&implementation,
|
||||
*key.os(),
|
||||
);
|
||||
let minor_version_link = Self {
|
||||
symlink_directory,
|
||||
|
|
@ -839,18 +842,28 @@ fn executable_path_from_base(
|
|||
base: &Path,
|
||||
executable_name: &str,
|
||||
implementation: &LenientImplementationName,
|
||||
os: Os,
|
||||
) -> PathBuf {
|
||||
if cfg!(unix)
|
||||
if matches!(
|
||||
implementation,
|
||||
&LenientImplementationName::Known(ImplementationName::GraalPy)
|
||||
) {
|
||||
// GraalPy is always in `bin/` regardless of the os
|
||||
base.join("bin").join(executable_name)
|
||||
} else if os.is_emscripten()
|
||||
|| matches!(
|
||||
implementation,
|
||||
&LenientImplementationName::Known(ImplementationName::GraalPy)
|
||||
&LenientImplementationName::Known(ImplementationName::Pyodide)
|
||||
)
|
||||
{
|
||||
base.join("bin").join(executable_name)
|
||||
} else if cfg!(windows) {
|
||||
// Emscripten's canonical executable is in the base directory
|
||||
base.join(executable_name)
|
||||
} else if os.is_windows() {
|
||||
// On Windows, the executable is in the base directory
|
||||
base.join(executable_name)
|
||||
} else {
|
||||
unimplemented!("Only Windows and Unix systems are supported.")
|
||||
// On Unix, the executable is in `bin/`
|
||||
base.join("bin").join(executable_name)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -96,7 +96,13 @@ impl InstallRequest {
|
|||
|
||||
impl std::fmt::Display for InstallRequest {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.request)
|
||||
let request = self.request.to_canonical_string();
|
||||
let download = self.download_request.to_string();
|
||||
if request != download {
|
||||
write!(f, "{request} ({download})")
|
||||
} else {
|
||||
write!(f, "{request}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -223,6 +229,12 @@ pub(crate) async fn install(
|
|||
.with_preference(VersionFilePreference::Versions),
|
||||
)
|
||||
.await?
|
||||
.inspect(|file| {
|
||||
debug!(
|
||||
"Found Python version file at: {}",
|
||||
file.path().user_display()
|
||||
);
|
||||
})
|
||||
.map(PythonVersionFile::into_versions)
|
||||
.unwrap_or_else(|| {
|
||||
// If no version file is found and no requests were made
|
||||
|
|
@ -237,14 +249,14 @@ pub(crate) async fn install(
|
|||
}]
|
||||
})
|
||||
.into_iter()
|
||||
.map(|a| InstallRequest::new(a, python_downloads_json_url.as_deref()))
|
||||
.map(|request| InstallRequest::new(request, python_downloads_json_url.as_deref()))
|
||||
.collect::<Result<Vec<_>>>()?
|
||||
}
|
||||
} else {
|
||||
targets
|
||||
.iter()
|
||||
.map(|target| PythonRequest::parse(target.as_str()))
|
||||
.map(|a| InstallRequest::new(a, python_downloads_json_url.as_deref()))
|
||||
.map(|request| InstallRequest::new(request, python_downloads_json_url.as_deref()))
|
||||
.collect::<Result<Vec<_>>>()?
|
||||
};
|
||||
|
||||
|
|
@ -565,10 +577,14 @@ pub(crate) async fn install(
|
|||
|
||||
if !changelog.installed.is_empty() {
|
||||
for install_key in &changelog.installed {
|
||||
// Make a note if the selected python is non-native for the architecture,
|
||||
// if none of the matching user requests were explicit
|
||||
// Make a note if the selected python is non-native for the architecture, if none of the
|
||||
// matching user requests were explicit.
|
||||
//
|
||||
// Emscripten is exempted as it is always "emulated".
|
||||
let native_arch = Arch::from_env();
|
||||
if install_key.arch().family() != native_arch.family() {
|
||||
if install_key.arch().family() != native_arch.family()
|
||||
&& !install_key.os().is_emscripten()
|
||||
{
|
||||
let not_explicit =
|
||||
requests_by_new_installation
|
||||
.get(install_key)
|
||||
|
|
|
|||
|
|
@ -2991,8 +2991,9 @@ fn uninstall_last_patch() {
|
|||
#[cfg(unix)] // Pyodide cannot be used on Windows
|
||||
#[test]
|
||||
fn python_install_pyodide() {
|
||||
use assert_cmd::assert::OutputAssertExt;
|
||||
|
||||
let context: TestContext = TestContext::new_with_versions(&[])
|
||||
.with_filtered_python_keys()
|
||||
.with_filtered_exe_suffix()
|
||||
.with_managed_python_dirs()
|
||||
.with_python_download_cache();
|
||||
|
|
@ -3004,7 +3005,7 @@ fn python_install_pyodide() {
|
|||
|
||||
----- stderr -----
|
||||
Installed Python 3.13.2 in [TIME]
|
||||
+ cpython-3.13.2-[PLATFORM] (python3.13)
|
||||
+ pyodide-3.13.2-emscripten-wasm32-musl (python3.13)
|
||||
");
|
||||
|
||||
let bin_python = context
|
||||
|
|
@ -3014,8 +3015,7 @@ fn python_install_pyodide() {
|
|||
// The executable should be installed in the bin directory
|
||||
bin_python.assert(predicate::path::exists());
|
||||
|
||||
// On Unix, it should be a link
|
||||
#[cfg(unix)]
|
||||
// It should be a link
|
||||
bin_python.assert(predicate::path::is_symlink());
|
||||
|
||||
// The link should be a path to the binary
|
||||
|
|
@ -3023,7 +3023,7 @@ fn python_install_pyodide() {
|
|||
filters => context.filters(),
|
||||
}, {
|
||||
insta::assert_snapshot!(
|
||||
read_link(&bin_python), @"[TEMP_DIR]/managed/cpython-3.13.2-[PLATFORM]/bin/python3.13"
|
||||
read_link(&bin_python), @"[TEMP_DIR]/managed/pyodide-3.13.2-emscripten-wasm32-musl/python"
|
||||
);
|
||||
});
|
||||
|
||||
|
|
@ -3043,7 +3043,7 @@ fn python_install_pyodide() {
|
|||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
[TEMP_DIR]/managed/cpython-3.13.2-[PLATFORM]/bin/python3.13
|
||||
[TEMP_DIR]/managed/pyodide-3.13.2-emscripten-wasm32-musl/python
|
||||
|
||||
----- stderr -----
|
||||
");
|
||||
|
|
@ -3069,4 +3069,92 @@ fn python_install_pyodide() {
|
|||
|
||||
----- stderr -----
|
||||
");
|
||||
|
||||
context.python_uninstall().arg("--all").assert().success();
|
||||
fs_err::remove_dir_all(&context.venv).unwrap();
|
||||
|
||||
// Install via `pyodide`
|
||||
uv_snapshot!(context.filters(), context.python_install().arg("pyodide"), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Installed Python 3.13.2 in [TIME]
|
||||
+ pyodide-3.13.2-emscripten-wasm32-musl (python3.13)
|
||||
");
|
||||
|
||||
context.python_uninstall().arg("--all").assert().success();
|
||||
|
||||
// Install via `pyodide@<version>`
|
||||
uv_snapshot!(context.filters(), context.python_install().arg("pyodide@3.13"), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Installed Python 3.13.2 in [TIME]
|
||||
+ pyodide-3.13.2-emscripten-wasm32-musl (python3.13)
|
||||
");
|
||||
|
||||
// Find via `pyodide``
|
||||
uv_snapshot!(context.filters(), context.python_find().arg("pyodide"), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
[TEMP_DIR]/managed/pyodide-3.13.2-emscripten-wasm32-musl/python
|
||||
|
||||
----- stderr -----
|
||||
");
|
||||
|
||||
// Find without a request should fail
|
||||
uv_snapshot!(context.filters(), context.python_find(), @r"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
error: No interpreter found in virtual environments, managed installations, or search path
|
||||
");
|
||||
// Find with "cpython" should also fail
|
||||
uv_snapshot!(context.filters(), context.python_find().arg("cpython"), @r"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
error: No interpreter found for CPython in virtual environments, managed installations, or search path
|
||||
");
|
||||
|
||||
// Install a CPython interpreter
|
||||
let context = context.with_filtered_python_keys();
|
||||
uv_snapshot!(context.filters(), context.python_install().arg("cpython"), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Installed Python 3.13.6 in [TIME]
|
||||
+ cpython-3.13.6-[PLATFORM]
|
||||
");
|
||||
|
||||
// Now, we should prefer that
|
||||
uv_snapshot!(context.filters(), context.python_find().arg("any"), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
[TEMP_DIR]/managed/cpython-3.13.6-[PLATFORM]/bin/python3.13
|
||||
|
||||
----- stderr -----
|
||||
");
|
||||
|
||||
// Unless we request pyodide
|
||||
uv_snapshot!(context.filters(), context.python_find().arg("pyodide"), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
[TEMP_DIR]/managed/pyodide-3.13.2-emscripten-wasm32-musl/python
|
||||
|
||||
----- stderr -----
|
||||
");
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue