mirror of
https://github.com/astral-sh/uv.git
synced 2025-07-07 21:35:00 +00:00
Add persistent storage of installed toolchains (#3797)
Extends #3726 Moves toolchain storage out of `UV_BOOTSTRAP_DIR` (`./bin`) into the proper user data directory as defined by #3726. Replaces `UV_BOOTSTRAP_DIR` with `UV_TOOLCHAIN_DIR` for customization. Installed toolchains will be discovered without opt-in, but the idea is still that these are not yet user-facing.
This commit is contained in:
parent
4191c331a7
commit
30e780e1dd
15 changed files with 448 additions and 210 deletions
2
.env
2
.env
|
@ -1,2 +0,0 @@
|
||||||
PATH=$PWD/bin:$PATH
|
|
||||||
UV_TEST_PYTHON_PATH=$PWD/bin
|
|
1
.github/workflows/ci.yml
vendored
1
.github/workflows/ci.yml
vendored
|
@ -144,7 +144,6 @@ jobs:
|
||||||
|
|
||||||
- name: "Cargo test"
|
- name: "Cargo test"
|
||||||
run: |
|
run: |
|
||||||
export UV_BOOTSTRAP_DIR="$(pwd)/bin"
|
|
||||||
cargo nextest run \
|
cargo nextest run \
|
||||||
--features python-patch \
|
--features python-patch \
|
||||||
--workspace \
|
--workspace \
|
||||||
|
|
|
@ -36,20 +36,13 @@ If tests fail due to a mismatch in the JSON Schema, run: `cargo dev generate-jso
|
||||||
|
|
||||||
### Python
|
### Python
|
||||||
|
|
||||||
Testing uv requires multiple specific Python versions. You can install them into
|
Testing uv requires multiple specific Python versions; they can be installed with:
|
||||||
`<project root>/bin` via our bootstrapping script:
|
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
cargo run -p uv-dev -- fetch-python
|
cargo dev fetch-python
|
||||||
```
|
```
|
||||||
|
|
||||||
You may need to add the versions to your `PATH`:
|
The storage directory can be configured with `UV_TOOLCHAIN_DIR`.
|
||||||
|
|
||||||
```shell
|
|
||||||
source .env
|
|
||||||
```
|
|
||||||
|
|
||||||
You can configure the bootstrapping directory with `UV_BOOTSTRAP_DIR`.
|
|
||||||
|
|
||||||
### Local testing
|
### Local testing
|
||||||
|
|
||||||
|
|
10
Cargo.lock
generated
10
Cargo.lock
generated
|
@ -4927,6 +4927,7 @@ dependencies = [
|
||||||
"uv-client",
|
"uv-client",
|
||||||
"uv-extract",
|
"uv-extract",
|
||||||
"uv-fs",
|
"uv-fs",
|
||||||
|
"uv-state",
|
||||||
"uv-warnings",
|
"uv-warnings",
|
||||||
"which",
|
"which",
|
||||||
"winapi",
|
"winapi",
|
||||||
|
@ -5034,6 +5035,15 @@ dependencies = [
|
||||||
"uv-warnings",
|
"uv-warnings",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "uv-state"
|
||||||
|
version = "0.0.1"
|
||||||
|
dependencies = [
|
||||||
|
"directories",
|
||||||
|
"fs-err",
|
||||||
|
"tempfile",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "uv-types"
|
name = "uv-types"
|
||||||
version = "0.0.1"
|
version = "0.0.1"
|
||||||
|
|
|
@ -42,6 +42,7 @@ uv-interpreter = { path = "crates/uv-interpreter" }
|
||||||
uv-normalize = { path = "crates/uv-normalize" }
|
uv-normalize = { path = "crates/uv-normalize" }
|
||||||
uv-requirements = { path = "crates/uv-requirements" }
|
uv-requirements = { path = "crates/uv-requirements" }
|
||||||
uv-resolver = { path = "crates/uv-resolver" }
|
uv-resolver = { path = "crates/uv-resolver" }
|
||||||
|
uv-state = { path = "crates/uv-state" }
|
||||||
uv-types = { path = "crates/uv-types" }
|
uv-types = { path = "crates/uv-types" }
|
||||||
uv-version = { path = "crates/uv-version" }
|
uv-version = { path = "crates/uv-version" }
|
||||||
uv-virtualenv = { path = "crates/uv-virtualenv" }
|
uv-virtualenv = { path = "crates/uv-virtualenv" }
|
||||||
|
|
|
@ -14,7 +14,7 @@ use tracing::{info, info_span, Instrument};
|
||||||
|
|
||||||
use uv_fs::Simplified;
|
use uv_fs::Simplified;
|
||||||
use uv_interpreter::managed::{
|
use uv_interpreter::managed::{
|
||||||
DownloadResult, Error, PythonDownload, PythonDownloadRequest, TOOLCHAIN_DIRECTORY,
|
DownloadResult, Error, InstalledToolchains, PythonDownload, PythonDownloadRequest,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Parser, Debug)]
|
#[derive(Parser, Debug)]
|
||||||
|
@ -25,13 +25,8 @@ pub(crate) struct FetchPythonArgs {
|
||||||
pub(crate) async fn fetch_python(args: FetchPythonArgs) -> Result<()> {
|
pub(crate) async fn fetch_python(args: FetchPythonArgs) -> Result<()> {
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
|
|
||||||
let bootstrap_dir = TOOLCHAIN_DIRECTORY.clone().unwrap_or_else(|| {
|
let toolchains = InstalledToolchains::from_settings()?.init()?;
|
||||||
std::env::current_dir()
|
let toolchain_dir = toolchains.root();
|
||||||
.expect("Use `UV_BOOTSTRAP_DIR` if the current directory is not usable.")
|
|
||||||
.join("bin")
|
|
||||||
});
|
|
||||||
|
|
||||||
fs_err::create_dir_all(&bootstrap_dir)?;
|
|
||||||
|
|
||||||
let versions = if args.versions.is_empty() {
|
let versions = if args.versions.is_empty() {
|
||||||
info!("Reading versions from file...");
|
info!("Reading versions from file...");
|
||||||
|
@ -61,7 +56,7 @@ pub(crate) async fn fetch_python(args: FetchPythonArgs) -> Result<()> {
|
||||||
let mut tasks = futures::stream::iter(downloads.iter())
|
let mut tasks = futures::stream::iter(downloads.iter())
|
||||||
.map(|download| {
|
.map(|download| {
|
||||||
async {
|
async {
|
||||||
let result = download.fetch(&client, &bootstrap_dir).await;
|
let result = download.fetch(&client, toolchain_dir).await;
|
||||||
(download.python_version(), result)
|
(download.python_version(), result)
|
||||||
}
|
}
|
||||||
.instrument(info_span!("download", key = %download))
|
.instrument(info_span!("download", key = %download))
|
||||||
|
@ -98,7 +93,7 @@ pub(crate) async fn fetch_python(args: FetchPythonArgs) -> Result<()> {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Order matters here, as we overwrite previous links
|
// Order matters here, as we overwrite previous links
|
||||||
info!("Installing to `{}`...", bootstrap_dir.user_display());
|
info!("Installing to `{}`...", toolchain_dir.user_display());
|
||||||
|
|
||||||
// On Windows, linking the executable generally results in broken installations
|
// On Windows, linking the executable generally results in broken installations
|
||||||
// and each toolchain path will need to be added to the PATH separately in the
|
// and each toolchain path will need to be added to the PATH separately in the
|
||||||
|
@ -110,10 +105,10 @@ pub(crate) async fn fetch_python(args: FetchPythonArgs) -> Result<()> {
|
||||||
// TODO(zanieb): This path should be a part of the download metadata
|
// TODO(zanieb): This path should be a part of the download metadata
|
||||||
let executable = path.join("install").join("bin").join("python3");
|
let executable = path.join("install").join("bin").join("python3");
|
||||||
for target in [
|
for target in [
|
||||||
bootstrap_dir.join(format!("python{}", version.python_full_version())),
|
toolchain_dir.join(format!("python{}", version.python_full_version())),
|
||||||
bootstrap_dir.join(format!("python{}.{}", version.major(), version.minor())),
|
toolchain_dir.join(format!("python{}.{}", version.major(), version.minor())),
|
||||||
bootstrap_dir.join(format!("python{}", version.major())),
|
toolchain_dir.join(format!("python{}", version.major())),
|
||||||
bootstrap_dir.join("python"),
|
toolchain_dir.join("python"),
|
||||||
] {
|
] {
|
||||||
// Attempt to remove it, we'll fail on link if we couldn't remove it for some reason
|
// Attempt to remove it, we'll fail on link if we couldn't remove it for some reason
|
||||||
// but if it's missing we don't want to error
|
// but if it's missing we don't want to error
|
||||||
|
@ -132,10 +127,6 @@ pub(crate) async fn fetch_python(args: FetchPythonArgs) -> Result<()> {
|
||||||
};
|
};
|
||||||
|
|
||||||
info!("Installed {} versions", requests.len());
|
info!("Installed {} versions", requests.len());
|
||||||
info!(
|
|
||||||
r#"To enable discovery: export UV_BOOTSTRAP_DIR="{}""#,
|
|
||||||
bootstrap_dir.display()
|
|
||||||
);
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,7 @@ uv-cache = { workspace = true }
|
||||||
uv-client = { workspace = true }
|
uv-client = { workspace = true }
|
||||||
uv-extract = { workspace = true }
|
uv-extract = { workspace = true }
|
||||||
uv-fs = { workspace = true }
|
uv-fs = { workspace = true }
|
||||||
|
uv-state = { workspace = true }
|
||||||
uv-warnings = { workspace = true }
|
uv-warnings = { workspace = true }
|
||||||
|
|
||||||
anyhow = { workspace = true }
|
anyhow = { workspace = true }
|
||||||
|
|
|
@ -8,7 +8,7 @@ use which::which;
|
||||||
|
|
||||||
use crate::implementation::{ImplementationName, LenientImplementationName};
|
use crate::implementation::{ImplementationName, LenientImplementationName};
|
||||||
use crate::interpreter::Error as InterpreterError;
|
use crate::interpreter::Error as InterpreterError;
|
||||||
use crate::managed::toolchains_for_current_platform;
|
use crate::managed::InstalledToolchains;
|
||||||
use crate::py_launcher::py_list_paths;
|
use crate::py_launcher::py_list_paths;
|
||||||
use crate::virtualenv::{
|
use crate::virtualenv::{
|
||||||
conda_prefix_from_env, virtualenv_from_env, virtualenv_from_working_dir,
|
conda_prefix_from_env, virtualenv_from_env, virtualenv_from_working_dir,
|
||||||
|
@ -238,17 +238,20 @@ fn python_executables<'a>(
|
||||||
.chain(
|
.chain(
|
||||||
sources.contains(InterpreterSource::ManagedToolchain).then(move ||
|
sources.contains(InterpreterSource::ManagedToolchain).then(move ||
|
||||||
std::iter::once(
|
std::iter::once(
|
||||||
toolchains_for_current_platform()
|
InstalledToolchains::from_settings().map_err(Error::from).and_then(|installed_toolchains| {
|
||||||
.map(|toolchains|
|
debug!("Searching for managed toolchains at `{}`", installed_toolchains.root().user_display());
|
||||||
|
let toolchains = installed_toolchains.find_matching_current_platform()?;
|
||||||
// Check that the toolchain version satisfies the request to avoid unnecessary interpreter queries later
|
// Check that the toolchain version satisfies the request to avoid unnecessary interpreter queries later
|
||||||
toolchains.filter(move |toolchain|
|
Ok(
|
||||||
version.is_none() || version.is_some_and(|version|
|
toolchains.into_iter().filter(move |toolchain|
|
||||||
version.matches_version(toolchain.python_version())
|
version.is_none() || version.is_some_and(|version|
|
||||||
|
version.matches_version(toolchain.python_version())
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
.inspect(|toolchain| debug!("Found managed toolchain `{toolchain}`"))
|
||||||
|
.map(|toolchain| (InterpreterSource::ManagedToolchain, toolchain.executable()))
|
||||||
)
|
)
|
||||||
.map(|toolchain| (InterpreterSource::ManagedToolchain, toolchain.executable()))
|
})
|
||||||
)
|
|
||||||
.map_err(Error::from)
|
|
||||||
).flatten_ok()
|
).flatten_ok()
|
||||||
).into_iter().flatten()
|
).into_iter().flatten()
|
||||||
)
|
)
|
||||||
|
|
|
@ -78,6 +78,7 @@ mod tests {
|
||||||
discovery::{self, DiscoveredInterpreter, InterpreterRequest, VersionRequest},
|
discovery::{self, DiscoveredInterpreter, InterpreterRequest, VersionRequest},
|
||||||
find_best_interpreter, find_default_interpreter, find_interpreter,
|
find_best_interpreter, find_default_interpreter, find_interpreter,
|
||||||
implementation::ImplementationName,
|
implementation::ImplementationName,
|
||||||
|
managed::InstalledToolchains,
|
||||||
virtualenv::virtualenv_python_executable,
|
virtualenv::virtualenv_python_executable,
|
||||||
Error, InterpreterNotFound, InterpreterSource, PythonEnvironment, PythonVersion,
|
Error, InterpreterNotFound, InterpreterSource, PythonEnvironment, PythonVersion,
|
||||||
SourceSelector, SystemPython,
|
SourceSelector, SystemPython,
|
||||||
|
@ -287,12 +288,16 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn find_default_interpreter_empty_path() -> Result<()> {
|
fn find_default_interpreter_empty_path() -> Result<()> {
|
||||||
|
let toolchains = InstalledToolchains::temp()?;
|
||||||
let cache = Cache::temp()?;
|
let cache = Cache::temp()?;
|
||||||
|
|
||||||
with_vars(
|
with_vars(
|
||||||
[
|
[
|
||||||
("UV_TEST_PYTHON_PATH", None),
|
("UV_TEST_PYTHON_PATH", None),
|
||||||
("UV_BOOTSTRAP_DIR", None),
|
(
|
||||||
|
"UV_TOOLCHAIN_DIR",
|
||||||
|
Some(toolchains.root().to_str().unwrap()),
|
||||||
|
),
|
||||||
("PATH", Some("")),
|
("PATH", Some("")),
|
||||||
],
|
],
|
||||||
|| {
|
|| {
|
||||||
|
@ -310,7 +315,7 @@ mod tests {
|
||||||
with_vars(
|
with_vars(
|
||||||
[
|
[
|
||||||
("UV_TEST_PYTHON_PATH", None),
|
("UV_TEST_PYTHON_PATH", None),
|
||||||
("UV_BOOTSTRAP_DIR", None),
|
("UV_TOOLCHAIN_DIR", Some(toolchains.root().into())),
|
||||||
("PATH", None::<OsString>),
|
("PATH", None::<OsString>),
|
||||||
],
|
],
|
||||||
|| {
|
|| {
|
||||||
|
@ -332,13 +337,14 @@ mod tests {
|
||||||
fn find_default_interpreter_invalid_executable() -> Result<()> {
|
fn find_default_interpreter_invalid_executable() -> Result<()> {
|
||||||
let cache = Cache::temp()?;
|
let cache = Cache::temp()?;
|
||||||
let tempdir = TempDir::new()?;
|
let tempdir = TempDir::new()?;
|
||||||
|
let toolchains = InstalledToolchains::temp()?;
|
||||||
let python = tempdir.child(format!("python{}", std::env::consts::EXE_SUFFIX));
|
let python = tempdir.child(format!("python{}", std::env::consts::EXE_SUFFIX));
|
||||||
python.touch()?;
|
python.touch()?;
|
||||||
|
|
||||||
with_vars(
|
with_vars(
|
||||||
[
|
[
|
||||||
("UV_TEST_PYTHON_PATH", None),
|
("UV_TEST_PYTHON_PATH", None),
|
||||||
("UV_BOOTSTRAP_DIR", None),
|
("UV_TOOLCHAIN_DIR", Some(toolchains.root().as_os_str())),
|
||||||
("PATH", Some(tempdir.path().as_os_str())),
|
("PATH", Some(tempdir.path().as_os_str())),
|
||||||
],
|
],
|
||||||
|| {
|
|| {
|
||||||
|
@ -359,6 +365,7 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn find_default_interpreter_valid_executable() -> Result<()> {
|
fn find_default_interpreter_valid_executable() -> Result<()> {
|
||||||
let tempdir = TempDir::new()?;
|
let tempdir = TempDir::new()?;
|
||||||
|
let toolchains = InstalledToolchains::temp()?;
|
||||||
let cache = Cache::temp()?;
|
let cache = Cache::temp()?;
|
||||||
let python = tempdir.child(format!("python{}", std::env::consts::EXE_SUFFIX));
|
let python = tempdir.child(format!("python{}", std::env::consts::EXE_SUFFIX));
|
||||||
create_mock_interpreter(
|
create_mock_interpreter(
|
||||||
|
@ -371,7 +378,7 @@ mod tests {
|
||||||
with_vars(
|
with_vars(
|
||||||
[
|
[
|
||||||
("UV_TEST_PYTHON_PATH", None),
|
("UV_TEST_PYTHON_PATH", None),
|
||||||
("UV_BOOTSTRAP_DIR", None),
|
("UV_TOOLCHAIN_DIR", Some(toolchains.root().as_os_str())),
|
||||||
("PATH", Some(tempdir.path().as_os_str())),
|
("PATH", Some(tempdir.path().as_os_str())),
|
||||||
],
|
],
|
||||||
|| {
|
|| {
|
||||||
|
@ -395,6 +402,7 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn find_default_interpreter_valid_executable_after_invalid() -> Result<()> {
|
fn find_default_interpreter_valid_executable_after_invalid() -> Result<()> {
|
||||||
let tempdir = TempDir::new()?;
|
let tempdir = TempDir::new()?;
|
||||||
|
let toolchains = InstalledToolchains::temp()?;
|
||||||
let cache = Cache::temp()?;
|
let cache = Cache::temp()?;
|
||||||
let children = create_children(
|
let children = create_children(
|
||||||
&tempdir,
|
&tempdir,
|
||||||
|
@ -440,7 +448,7 @@ mod tests {
|
||||||
with_vars(
|
with_vars(
|
||||||
[
|
[
|
||||||
("UV_TEST_PYTHON_PATH", None),
|
("UV_TEST_PYTHON_PATH", None),
|
||||||
("UV_BOOTSTRAP_DIR", None),
|
("UV_TOOLCHAIN_DIR", Some(toolchains.root().into())),
|
||||||
(
|
(
|
||||||
"PATH",
|
"PATH",
|
||||||
Some(env::join_paths(
|
Some(env::join_paths(
|
||||||
|
@ -476,6 +484,7 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn find_default_interpreter_only_python2_executable() -> Result<()> {
|
fn find_default_interpreter_only_python2_executable() -> Result<()> {
|
||||||
let tempdir = TempDir::new()?;
|
let tempdir = TempDir::new()?;
|
||||||
|
let toolchains = InstalledToolchains::temp()?;
|
||||||
let pwd = tempdir.child("pwd");
|
let pwd = tempdir.child("pwd");
|
||||||
pwd.create_dir_all()?;
|
pwd.create_dir_all()?;
|
||||||
let cache = Cache::temp()?;
|
let cache = Cache::temp()?;
|
||||||
|
@ -485,7 +494,7 @@ mod tests {
|
||||||
with_vars(
|
with_vars(
|
||||||
[
|
[
|
||||||
("UV_TEST_PYTHON_PATH", None),
|
("UV_TEST_PYTHON_PATH", None),
|
||||||
("UV_BOOTSTRAP_DIR", None),
|
("UV_TOOLCHAIN_DIR", Some(toolchains.root().as_os_str())),
|
||||||
("PATH", Some(tempdir.path().as_os_str())),
|
("PATH", Some(tempdir.path().as_os_str())),
|
||||||
("PWD", Some(pwd.path().as_os_str())),
|
("PWD", Some(pwd.path().as_os_str())),
|
||||||
],
|
],
|
||||||
|
@ -508,6 +517,7 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn find_default_interpreter_skip_python2_executable() -> Result<()> {
|
fn find_default_interpreter_skip_python2_executable() -> Result<()> {
|
||||||
let tempdir = TempDir::new()?;
|
let tempdir = TempDir::new()?;
|
||||||
|
let toolchains = InstalledToolchains::temp()?;
|
||||||
let cache = Cache::temp()?;
|
let cache = Cache::temp()?;
|
||||||
tempdir.child("bad").create_dir_all()?;
|
tempdir.child("bad").create_dir_all()?;
|
||||||
tempdir.child("good").create_dir_all()?;
|
tempdir.child("good").create_dir_all()?;
|
||||||
|
@ -531,7 +541,7 @@ mod tests {
|
||||||
with_vars(
|
with_vars(
|
||||||
[
|
[
|
||||||
("UV_TEST_PYTHON_PATH", None),
|
("UV_TEST_PYTHON_PATH", None),
|
||||||
("UV_BOOTSTRAP_DIR", None),
|
("UV_TOOLCHAIN_DIR", Some(toolchains.root().into())),
|
||||||
(
|
(
|
||||||
"PATH",
|
"PATH",
|
||||||
Some(env::join_paths([
|
Some(env::join_paths([
|
||||||
|
@ -566,12 +576,13 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn find_interpreter_system_python_allowed() -> Result<()> {
|
fn find_interpreter_system_python_allowed() -> Result<()> {
|
||||||
let tempdir = TempDir::new()?;
|
let tempdir = TempDir::new()?;
|
||||||
|
let toolchains = InstalledToolchains::temp()?;
|
||||||
let cache = Cache::temp()?;
|
let cache = Cache::temp()?;
|
||||||
|
|
||||||
with_vars(
|
with_vars(
|
||||||
[
|
[
|
||||||
("UV_TEST_PYTHON_PATH", None::<OsString>),
|
("UV_TEST_PYTHON_PATH", None::<OsString>),
|
||||||
("UV_BOOTSTRAP_DIR", None),
|
("UV_TOOLCHAIN_DIR", Some(toolchains.root().into())),
|
||||||
(
|
(
|
||||||
"PATH",
|
"PATH",
|
||||||
Some(mock_interpreters(
|
Some(mock_interpreters(
|
||||||
|
@ -605,7 +616,7 @@ mod tests {
|
||||||
with_vars(
|
with_vars(
|
||||||
[
|
[
|
||||||
("UV_TEST_PYTHON_PATH", None::<OsString>),
|
("UV_TEST_PYTHON_PATH", None::<OsString>),
|
||||||
("UV_BOOTSTRAP_DIR", None),
|
("UV_TOOLCHAIN_DIR", Some(toolchains.root().into())),
|
||||||
(
|
(
|
||||||
"PATH",
|
"PATH",
|
||||||
Some(mock_interpreters(
|
Some(mock_interpreters(
|
||||||
|
@ -641,12 +652,13 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn find_interpreter_system_python_required() -> Result<()> {
|
fn find_interpreter_system_python_required() -> Result<()> {
|
||||||
let tempdir = TempDir::new()?;
|
let tempdir = TempDir::new()?;
|
||||||
|
let toolchains = InstalledToolchains::temp()?;
|
||||||
let cache = Cache::temp()?;
|
let cache = Cache::temp()?;
|
||||||
|
|
||||||
with_vars(
|
with_vars(
|
||||||
[
|
[
|
||||||
("UV_TEST_PYTHON_PATH", None::<OsString>),
|
("UV_TEST_PYTHON_PATH", None::<OsString>),
|
||||||
("UV_BOOTSTRAP_DIR", None),
|
("UV_TOOLCHAIN_DIR", Some(toolchains.root().into())),
|
||||||
(
|
(
|
||||||
"PATH",
|
"PATH",
|
||||||
Some(mock_interpreters(
|
Some(mock_interpreters(
|
||||||
|
@ -682,11 +694,13 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn find_interpreter_system_python_disallowed() -> Result<()> {
|
fn find_interpreter_system_python_disallowed() -> Result<()> {
|
||||||
let tempdir = TempDir::new()?;
|
let tempdir = TempDir::new()?;
|
||||||
|
let toolchains = InstalledToolchains::temp()?;
|
||||||
let cache = Cache::temp()?;
|
let cache = Cache::temp()?;
|
||||||
|
|
||||||
with_vars(
|
with_vars(
|
||||||
[
|
[
|
||||||
("UV_TEST_PYTHON_PATH", None::<OsString>),
|
("UV_TEST_PYTHON_PATH", None::<OsString>),
|
||||||
|
("UV_TOOLCHAIN_DIR", Some(toolchains.root().into())),
|
||||||
(
|
(
|
||||||
"PATH",
|
"PATH",
|
||||||
Some(mock_interpreters(
|
Some(mock_interpreters(
|
||||||
|
@ -722,13 +736,14 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn find_interpreter_version_minor() -> Result<()> {
|
fn find_interpreter_version_minor() -> Result<()> {
|
||||||
let tempdir = TempDir::new()?;
|
let tempdir = TempDir::new()?;
|
||||||
|
let toolchains = InstalledToolchains::temp()?;
|
||||||
let cache = Cache::temp()?;
|
let cache = Cache::temp()?;
|
||||||
let sources = SourceSelector::All;
|
let sources = SourceSelector::All;
|
||||||
|
|
||||||
with_vars(
|
with_vars(
|
||||||
[
|
[
|
||||||
("UV_TEST_PYTHON_PATH", None),
|
("UV_TEST_PYTHON_PATH", None),
|
||||||
("UV_BOOTSTRAP_DIR", None),
|
("UV_TOOLCHAIN_DIR", Some(toolchains.root().into())),
|
||||||
(
|
(
|
||||||
"PATH",
|
"PATH",
|
||||||
Some(simple_mock_interpreters(
|
Some(simple_mock_interpreters(
|
||||||
|
@ -774,13 +789,14 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn find_interpreter_version_patch() -> Result<()> {
|
fn find_interpreter_version_patch() -> Result<()> {
|
||||||
let tempdir = TempDir::new()?;
|
let tempdir = TempDir::new()?;
|
||||||
|
let toolchains = InstalledToolchains::temp()?;
|
||||||
let cache = Cache::temp()?;
|
let cache = Cache::temp()?;
|
||||||
let sources = SourceSelector::All;
|
let sources = SourceSelector::All;
|
||||||
|
|
||||||
with_vars(
|
with_vars(
|
||||||
[
|
[
|
||||||
("UV_TEST_PYTHON_PATH", None),
|
("UV_TEST_PYTHON_PATH", None),
|
||||||
("UV_BOOTSTRAP_DIR", None),
|
("UV_TOOLCHAIN_DIR", Some(toolchains.root().into())),
|
||||||
(
|
(
|
||||||
"PATH",
|
"PATH",
|
||||||
Some(simple_mock_interpreters(
|
Some(simple_mock_interpreters(
|
||||||
|
@ -826,13 +842,14 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn find_interpreter_version_minor_no_match() -> Result<()> {
|
fn find_interpreter_version_minor_no_match() -> Result<()> {
|
||||||
let tempdir = TempDir::new()?;
|
let tempdir = TempDir::new()?;
|
||||||
|
let toolchains = InstalledToolchains::temp()?;
|
||||||
let cache = Cache::temp()?;
|
let cache = Cache::temp()?;
|
||||||
let sources = SourceSelector::All;
|
let sources = SourceSelector::All;
|
||||||
|
|
||||||
with_vars(
|
with_vars(
|
||||||
[
|
[
|
||||||
("UV_TEST_PYTHON_PATH", None),
|
("UV_TEST_PYTHON_PATH", None),
|
||||||
("UV_BOOTSTRAP_DIR", None),
|
("UV_TOOLCHAIN_DIR", Some(toolchains.root().into())),
|
||||||
(
|
(
|
||||||
"PATH",
|
"PATH",
|
||||||
Some(simple_mock_interpreters(
|
Some(simple_mock_interpreters(
|
||||||
|
@ -868,13 +885,14 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn find_interpreter_version_patch_no_match() -> Result<()> {
|
fn find_interpreter_version_patch_no_match() -> Result<()> {
|
||||||
let tempdir = TempDir::new()?;
|
let tempdir = TempDir::new()?;
|
||||||
|
let toolchains = InstalledToolchains::temp()?;
|
||||||
let cache = Cache::temp()?;
|
let cache = Cache::temp()?;
|
||||||
let sources = SourceSelector::All;
|
let sources = SourceSelector::All;
|
||||||
|
|
||||||
with_vars(
|
with_vars(
|
||||||
[
|
[
|
||||||
("UV_TEST_PYTHON_PATH", None),
|
("UV_TEST_PYTHON_PATH", None),
|
||||||
("UV_BOOTSTRAP_DIR", None),
|
("UV_TOOLCHAIN_DIR", Some(toolchains.root().into())),
|
||||||
(
|
(
|
||||||
"PATH",
|
"PATH",
|
||||||
Some(simple_mock_interpreters(
|
Some(simple_mock_interpreters(
|
||||||
|
@ -910,12 +928,13 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn find_best_interpreter_version_patch_exact() -> Result<()> {
|
fn find_best_interpreter_version_patch_exact() -> Result<()> {
|
||||||
let tempdir = TempDir::new()?;
|
let tempdir = TempDir::new()?;
|
||||||
|
let toolchains = InstalledToolchains::temp()?;
|
||||||
let cache = Cache::temp()?;
|
let cache = Cache::temp()?;
|
||||||
|
|
||||||
with_vars(
|
with_vars(
|
||||||
[
|
[
|
||||||
("UV_TEST_PYTHON_PATH", None),
|
("UV_TEST_PYTHON_PATH", None),
|
||||||
("UV_BOOTSTRAP_DIR", None),
|
("UV_TOOLCHAIN_DIR", Some(toolchains.root().into())),
|
||||||
(
|
(
|
||||||
"PATH",
|
"PATH",
|
||||||
Some(simple_mock_interpreters(
|
Some(simple_mock_interpreters(
|
||||||
|
@ -960,12 +979,13 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn find_best_interpreter_version_patch_fallback() -> Result<()> {
|
fn find_best_interpreter_version_patch_fallback() -> Result<()> {
|
||||||
let tempdir = TempDir::new()?;
|
let tempdir = TempDir::new()?;
|
||||||
|
let toolchains = InstalledToolchains::temp()?;
|
||||||
let cache = Cache::temp()?;
|
let cache = Cache::temp()?;
|
||||||
|
|
||||||
with_vars(
|
with_vars(
|
||||||
[
|
[
|
||||||
("UV_TEST_PYTHON_PATH", None),
|
("UV_TEST_PYTHON_PATH", None),
|
||||||
("UV_BOOTSTRAP_DIR", None),
|
("UV_TOOLCHAIN_DIR", Some(toolchains.root().into())),
|
||||||
(
|
(
|
||||||
"PATH",
|
"PATH",
|
||||||
Some(simple_mock_interpreters(
|
Some(simple_mock_interpreters(
|
||||||
|
@ -1007,7 +1027,7 @@ mod tests {
|
||||||
with_vars(
|
with_vars(
|
||||||
[
|
[
|
||||||
("UV_TEST_PYTHON_PATH", None),
|
("UV_TEST_PYTHON_PATH", None),
|
||||||
("UV_BOOTSTRAP_DIR", None),
|
("UV_TOOLCHAIN_DIR", Some(toolchains.root().into())),
|
||||||
(
|
(
|
||||||
"PATH",
|
"PATH",
|
||||||
Some(simple_mock_interpreters(
|
Some(simple_mock_interpreters(
|
||||||
|
@ -1052,6 +1072,7 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn find_best_interpreter_skips_broken_active_environment() -> Result<()> {
|
fn find_best_interpreter_skips_broken_active_environment() -> Result<()> {
|
||||||
let tempdir = TempDir::new()?;
|
let tempdir = TempDir::new()?;
|
||||||
|
let toolchains = InstalledToolchains::temp()?;
|
||||||
let cache = Cache::temp()?;
|
let cache = Cache::temp()?;
|
||||||
|
|
||||||
let venv = mock_venv(&tempdir, "3.12.0")?;
|
let venv = mock_venv(&tempdir, "3.12.0")?;
|
||||||
|
@ -1061,7 +1082,7 @@ mod tests {
|
||||||
with_vars(
|
with_vars(
|
||||||
[
|
[
|
||||||
("UV_TEST_PYTHON_PATH", None),
|
("UV_TEST_PYTHON_PATH", None),
|
||||||
("UV_BOOTSTRAP_DIR", None),
|
("UV_TOOLCHAIN_DIR", Some(toolchains.root().into())),
|
||||||
(
|
(
|
||||||
"PATH",
|
"PATH",
|
||||||
Some(simple_mock_interpreters(&tempdir, &["3.11.1", "3.12.3"])?),
|
Some(simple_mock_interpreters(&tempdir, &["3.11.1", "3.12.3"])?),
|
||||||
|
@ -1105,6 +1126,7 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn find_best_interpreter_skips_source_without_match() -> Result<()> {
|
fn find_best_interpreter_skips_source_without_match() -> Result<()> {
|
||||||
let tempdir = TempDir::new()?;
|
let tempdir = TempDir::new()?;
|
||||||
|
let toolchains = InstalledToolchains::temp()?;
|
||||||
let cache = Cache::temp()?;
|
let cache = Cache::temp()?;
|
||||||
|
|
||||||
let venv = mock_venv(&tempdir, "3.12.0")?;
|
let venv = mock_venv(&tempdir, "3.12.0")?;
|
||||||
|
@ -1112,7 +1134,7 @@ mod tests {
|
||||||
with_vars(
|
with_vars(
|
||||||
[
|
[
|
||||||
("UV_TEST_PYTHON_PATH", None),
|
("UV_TEST_PYTHON_PATH", None),
|
||||||
("UV_BOOTSTRAP_DIR", None),
|
("UV_TOOLCHAIN_DIR", Some(toolchains.root().into())),
|
||||||
(
|
(
|
||||||
"PATH",
|
"PATH",
|
||||||
Some(simple_mock_interpreters(&tempdir, &["3.10.1"])?),
|
Some(simple_mock_interpreters(&tempdir, &["3.10.1"])?),
|
||||||
|
@ -1156,6 +1178,7 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn find_best_interpreter_returns_to_earlier_source_on_fallback() -> Result<()> {
|
fn find_best_interpreter_returns_to_earlier_source_on_fallback() -> Result<()> {
|
||||||
let tempdir = TempDir::new()?;
|
let tempdir = TempDir::new()?;
|
||||||
|
let toolchains = InstalledToolchains::temp()?;
|
||||||
let cache = Cache::temp()?;
|
let cache = Cache::temp()?;
|
||||||
|
|
||||||
let venv = mock_venv(&tempdir, "3.10.0")?;
|
let venv = mock_venv(&tempdir, "3.10.0")?;
|
||||||
|
@ -1163,7 +1186,7 @@ mod tests {
|
||||||
with_vars(
|
with_vars(
|
||||||
[
|
[
|
||||||
("UV_TEST_PYTHON_PATH", None),
|
("UV_TEST_PYTHON_PATH", None),
|
||||||
("UV_BOOTSTRAP_DIR", None),
|
("UV_TOOLCHAIN_DIR", Some(toolchains.root().into())),
|
||||||
(
|
(
|
||||||
"PATH",
|
"PATH",
|
||||||
Some(simple_mock_interpreters(&tempdir, &["3.10.3"])?),
|
Some(simple_mock_interpreters(&tempdir, &["3.10.3"])?),
|
||||||
|
@ -1207,6 +1230,7 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn find_best_interpreter_virtualenv_used_if_system_not_allowed() -> Result<()> {
|
fn find_best_interpreter_virtualenv_used_if_system_not_allowed() -> Result<()> {
|
||||||
let tempdir = TempDir::new()?;
|
let tempdir = TempDir::new()?;
|
||||||
|
let toolchains = InstalledToolchains::temp()?;
|
||||||
let cache = Cache::temp()?;
|
let cache = Cache::temp()?;
|
||||||
|
|
||||||
let venv = mock_venv(&tempdir, "3.11.1")?;
|
let venv = mock_venv(&tempdir, "3.11.1")?;
|
||||||
|
@ -1215,7 +1239,7 @@ mod tests {
|
||||||
with_vars(
|
with_vars(
|
||||||
[
|
[
|
||||||
("UV_TEST_PYTHON_PATH", None),
|
("UV_TEST_PYTHON_PATH", None),
|
||||||
("UV_BOOTSTRAP_DIR", None),
|
("UV_TOOLCHAIN_DIR", Some(toolchains.root().into())),
|
||||||
(
|
(
|
||||||
"PATH",
|
"PATH",
|
||||||
Some(simple_mock_interpreters(&tempdir, &["3.11.2"])?),
|
Some(simple_mock_interpreters(&tempdir, &["3.11.2"])?),
|
||||||
|
@ -1257,7 +1281,7 @@ mod tests {
|
||||||
with_vars(
|
with_vars(
|
||||||
[
|
[
|
||||||
("UV_TEST_PYTHON_PATH", None),
|
("UV_TEST_PYTHON_PATH", None),
|
||||||
("UV_BOOTSTRAP_DIR", None),
|
("UV_TOOLCHAIN_DIR", Some(toolchains.root().into())),
|
||||||
(
|
(
|
||||||
"PATH",
|
"PATH",
|
||||||
Some(simple_mock_interpreters(&tempdir, &["3.11.2", "3.10.0"])?),
|
Some(simple_mock_interpreters(&tempdir, &["3.11.2", "3.10.0"])?),
|
||||||
|
@ -1301,6 +1325,7 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn find_environment_from_active_environment() -> Result<()> {
|
fn find_environment_from_active_environment() -> Result<()> {
|
||||||
let tempdir = TempDir::new()?;
|
let tempdir = TempDir::new()?;
|
||||||
|
let toolchains = InstalledToolchains::temp()?;
|
||||||
let cache = Cache::temp()?;
|
let cache = Cache::temp()?;
|
||||||
|
|
||||||
let venv = mock_venv(&tempdir, "3.12.0")?;
|
let venv = mock_venv(&tempdir, "3.12.0")?;
|
||||||
|
@ -1308,7 +1333,7 @@ mod tests {
|
||||||
with_vars(
|
with_vars(
|
||||||
[
|
[
|
||||||
("UV_TEST_PYTHON_PATH", None),
|
("UV_TEST_PYTHON_PATH", None),
|
||||||
("UV_BOOTSTRAP_DIR", None),
|
("UV_TOOLCHAIN_DIR", Some(toolchains.root().into())),
|
||||||
(
|
(
|
||||||
"PATH",
|
"PATH",
|
||||||
Some(simple_mock_interpreters(&tempdir, &["3.10.1", "3.11.2"])?),
|
Some(simple_mock_interpreters(&tempdir, &["3.10.1", "3.11.2"])?),
|
||||||
|
@ -1334,13 +1359,14 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn find_environment_from_conda_prefix() -> Result<()> {
|
fn find_environment_from_conda_prefix() -> Result<()> {
|
||||||
let tempdir = TempDir::new()?;
|
let tempdir = TempDir::new()?;
|
||||||
|
let toolchains = InstalledToolchains::temp()?;
|
||||||
let cache = Cache::temp()?;
|
let cache = Cache::temp()?;
|
||||||
let conda_prefix = mock_conda_prefix(&tempdir, "3.12.0")?;
|
let conda_prefix = mock_conda_prefix(&tempdir, "3.12.0")?;
|
||||||
|
|
||||||
with_vars(
|
with_vars(
|
||||||
[
|
[
|
||||||
("UV_TEST_PYTHON_PATH", None),
|
("UV_TEST_PYTHON_PATH", None),
|
||||||
("UV_BOOTSTRAP_DIR", None),
|
("UV_TOOLCHAIN_DIR", Some(toolchains.root().into())),
|
||||||
(
|
(
|
||||||
"PATH",
|
"PATH",
|
||||||
Some(simple_mock_interpreters(&tempdir, &["3.10.1", "3.11.2"])?),
|
Some(simple_mock_interpreters(&tempdir, &["3.10.1", "3.11.2"])?),
|
||||||
|
@ -1367,6 +1393,7 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn find_environment_from_conda_prefix_and_virtualenv() -> Result<()> {
|
fn find_environment_from_conda_prefix_and_virtualenv() -> Result<()> {
|
||||||
let tempdir = TempDir::new()?;
|
let tempdir = TempDir::new()?;
|
||||||
|
let toolchains = InstalledToolchains::temp()?;
|
||||||
let cache = Cache::temp()?;
|
let cache = Cache::temp()?;
|
||||||
let generic = mock_venv(&tempdir, "3.12.0")?;
|
let generic = mock_venv(&tempdir, "3.12.0")?;
|
||||||
let conda = mock_conda_prefix(&tempdir, "3.12.1")?;
|
let conda = mock_conda_prefix(&tempdir, "3.12.1")?;
|
||||||
|
@ -1374,7 +1401,7 @@ mod tests {
|
||||||
with_vars(
|
with_vars(
|
||||||
[
|
[
|
||||||
("UV_TEST_PYTHON_PATH", None),
|
("UV_TEST_PYTHON_PATH", None),
|
||||||
("UV_BOOTSTRAP_DIR", None),
|
("UV_TOOLCHAIN_DIR", Some(toolchains.root().into())),
|
||||||
(
|
(
|
||||||
"PATH",
|
"PATH",
|
||||||
Some(simple_mock_interpreters(&tempdir, &["3.10.2", "3.11.3"])?),
|
Some(simple_mock_interpreters(&tempdir, &["3.10.2", "3.11.3"])?),
|
||||||
|
@ -1402,6 +1429,7 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn find_environment_from_discovered_environment() -> Result<()> {
|
fn find_environment_from_discovered_environment() -> Result<()> {
|
||||||
let tempdir = TempDir::new()?;
|
let tempdir = TempDir::new()?;
|
||||||
|
let toolchains = InstalledToolchains::temp()?;
|
||||||
let cache = Cache::temp()?;
|
let cache = Cache::temp()?;
|
||||||
|
|
||||||
let _venv = mock_venv(&tempdir, "3.12.0")?;
|
let _venv = mock_venv(&tempdir, "3.12.0")?;
|
||||||
|
@ -1409,7 +1437,7 @@ mod tests {
|
||||||
with_vars(
|
with_vars(
|
||||||
[
|
[
|
||||||
("UV_TEST_PYTHON_PATH", None),
|
("UV_TEST_PYTHON_PATH", None),
|
||||||
("UV_BOOTSTRAP_DIR", None),
|
("UV_TOOLCHAIN_DIR", Some(toolchains.root().into())),
|
||||||
(
|
(
|
||||||
"PATH",
|
"PATH",
|
||||||
Some(simple_mock_interpreters(&tempdir, &["3.10.1", "3.11.2"])?),
|
Some(simple_mock_interpreters(&tempdir, &["3.10.1", "3.11.2"])?),
|
||||||
|
@ -1434,6 +1462,7 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn find_environment_from_parent_interpreter() -> Result<()> {
|
fn find_environment_from_parent_interpreter() -> Result<()> {
|
||||||
let tempdir = TempDir::new()?;
|
let tempdir = TempDir::new()?;
|
||||||
|
let toolchains = InstalledToolchains::temp()?;
|
||||||
let cache = Cache::temp()?;
|
let cache = Cache::temp()?;
|
||||||
let pwd = tempdir.child("pwd");
|
let pwd = tempdir.child("pwd");
|
||||||
pwd.create_dir_all()?;
|
pwd.create_dir_all()?;
|
||||||
|
@ -1449,7 +1478,7 @@ mod tests {
|
||||||
with_vars(
|
with_vars(
|
||||||
[
|
[
|
||||||
("UV_TEST_PYTHON_PATH", None),
|
("UV_TEST_PYTHON_PATH", None),
|
||||||
("UV_BOOTSTRAP_DIR", None),
|
("UV_TOOLCHAIN_DIR", Some(toolchains.root().into())),
|
||||||
(
|
(
|
||||||
"PATH",
|
"PATH",
|
||||||
Some(simple_mock_interpreters(&tempdir, &["3.12.2", "3.12.3"])?),
|
Some(simple_mock_interpreters(&tempdir, &["3.12.2", "3.12.3"])?),
|
||||||
|
@ -1476,6 +1505,7 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn find_environment_from_parent_interpreter_system_explicit() -> Result<()> {
|
fn find_environment_from_parent_interpreter_system_explicit() -> Result<()> {
|
||||||
let tempdir = TempDir::new()?;
|
let tempdir = TempDir::new()?;
|
||||||
|
let toolchains = InstalledToolchains::temp()?;
|
||||||
let cache = Cache::temp()?;
|
let cache = Cache::temp()?;
|
||||||
let pwd = tempdir.child("pwd");
|
let pwd = tempdir.child("pwd");
|
||||||
pwd.create_dir_all()?;
|
pwd.create_dir_all()?;
|
||||||
|
@ -1491,7 +1521,7 @@ mod tests {
|
||||||
with_vars(
|
with_vars(
|
||||||
[
|
[
|
||||||
("UV_TEST_PYTHON_PATH", None),
|
("UV_TEST_PYTHON_PATH", None),
|
||||||
("UV_BOOTSTRAP_DIR", None),
|
("UV_TOOLCHAIN_DIR", Some(toolchains.root().into())),
|
||||||
(
|
(
|
||||||
"PATH",
|
"PATH",
|
||||||
Some(simple_mock_interpreters(&tempdir, &["3.12.2", "3.12.3"])?),
|
Some(simple_mock_interpreters(&tempdir, &["3.12.2", "3.12.3"])?),
|
||||||
|
@ -1518,6 +1548,7 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn find_environment_from_parent_interpreter_system_disallowed() -> Result<()> {
|
fn find_environment_from_parent_interpreter_system_disallowed() -> Result<()> {
|
||||||
let tempdir = TempDir::new()?;
|
let tempdir = TempDir::new()?;
|
||||||
|
let toolchains = InstalledToolchains::temp()?;
|
||||||
let cache = Cache::temp()?;
|
let cache = Cache::temp()?;
|
||||||
let pwd = tempdir.child("pwd");
|
let pwd = tempdir.child("pwd");
|
||||||
pwd.create_dir_all()?;
|
pwd.create_dir_all()?;
|
||||||
|
@ -1533,7 +1564,7 @@ mod tests {
|
||||||
with_vars(
|
with_vars(
|
||||||
[
|
[
|
||||||
("UV_TEST_PYTHON_PATH", None),
|
("UV_TEST_PYTHON_PATH", None),
|
||||||
("UV_BOOTSTRAP_DIR", None),
|
("UV_TOOLCHAIN_DIR", Some(toolchains.root().into())),
|
||||||
(
|
(
|
||||||
"PATH",
|
"PATH",
|
||||||
Some(simple_mock_interpreters(&tempdir, &["3.12.2", "3.12.3"])?),
|
Some(simple_mock_interpreters(&tempdir, &["3.12.2", "3.12.3"])?),
|
||||||
|
@ -1560,6 +1591,7 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn find_environment_from_parent_interpreter_system_required() -> Result<()> {
|
fn find_environment_from_parent_interpreter_system_required() -> Result<()> {
|
||||||
let tempdir = TempDir::new()?;
|
let tempdir = TempDir::new()?;
|
||||||
|
let toolchains = InstalledToolchains::temp()?;
|
||||||
let cache = Cache::temp()?;
|
let cache = Cache::temp()?;
|
||||||
let pwd = tempdir.child("pwd");
|
let pwd = tempdir.child("pwd");
|
||||||
pwd.create_dir_all()?;
|
pwd.create_dir_all()?;
|
||||||
|
@ -1575,7 +1607,7 @@ mod tests {
|
||||||
with_vars(
|
with_vars(
|
||||||
[
|
[
|
||||||
("UV_TEST_PYTHON_PATH", None),
|
("UV_TEST_PYTHON_PATH", None),
|
||||||
("UV_BOOTSTRAP_DIR", None),
|
("UV_TOOLCHAIN_DIR", Some(toolchains.root().into())),
|
||||||
(
|
(
|
||||||
"PATH",
|
"PATH",
|
||||||
Some(simple_mock_interpreters(&tempdir, &["3.12.2", "3.12.3"])?),
|
Some(simple_mock_interpreters(&tempdir, &["3.12.2", "3.12.3"])?),
|
||||||
|
@ -1602,6 +1634,7 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn find_environment_active_environment_skipped_if_system_required() -> Result<()> {
|
fn find_environment_active_environment_skipped_if_system_required() -> Result<()> {
|
||||||
let tempdir = TempDir::new()?;
|
let tempdir = TempDir::new()?;
|
||||||
|
let toolchains = InstalledToolchains::temp()?;
|
||||||
let cache = Cache::temp()?;
|
let cache = Cache::temp()?;
|
||||||
|
|
||||||
let venv = mock_venv(&tempdir, "3.12.0")?;
|
let venv = mock_venv(&tempdir, "3.12.0")?;
|
||||||
|
@ -1610,7 +1643,7 @@ mod tests {
|
||||||
with_vars(
|
with_vars(
|
||||||
[
|
[
|
||||||
("UV_TEST_PYTHON_PATH", None),
|
("UV_TEST_PYTHON_PATH", None),
|
||||||
("UV_BOOTSTRAP_DIR", None),
|
("UV_TOOLCHAIN_DIR", Some(toolchains.root().into())),
|
||||||
(
|
(
|
||||||
"PATH",
|
"PATH",
|
||||||
Some(simple_mock_interpreters(&tempdir, &["3.10.1", "3.11.2"])?),
|
Some(simple_mock_interpreters(&tempdir, &["3.10.1", "3.11.2"])?),
|
||||||
|
@ -1633,7 +1666,7 @@ mod tests {
|
||||||
with_vars(
|
with_vars(
|
||||||
[
|
[
|
||||||
("UV_TEST_PYTHON_PATH", None),
|
("UV_TEST_PYTHON_PATH", None),
|
||||||
("UV_BOOTSTRAP_DIR", None),
|
("UV_TOOLCHAIN_DIR", Some(toolchains.root().into())),
|
||||||
(
|
(
|
||||||
"PATH",
|
"PATH",
|
||||||
Some(simple_mock_interpreters(&tempdir, &["3.10.1", "3.12.2"])?),
|
Some(simple_mock_interpreters(&tempdir, &["3.10.1", "3.12.2"])?),
|
||||||
|
@ -1656,7 +1689,7 @@ mod tests {
|
||||||
with_vars(
|
with_vars(
|
||||||
[
|
[
|
||||||
("UV_TEST_PYTHON_PATH", None),
|
("UV_TEST_PYTHON_PATH", None),
|
||||||
("UV_BOOTSTRAP_DIR", None),
|
("UV_TOOLCHAIN_DIR", Some(toolchains.root().into())),
|
||||||
(
|
(
|
||||||
"PATH",
|
"PATH",
|
||||||
Some(simple_mock_interpreters(&tempdir, &["3.10.1", "3.12.2"])?),
|
Some(simple_mock_interpreters(&tempdir, &["3.10.1", "3.12.2"])?),
|
||||||
|
@ -1680,12 +1713,13 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn find_environment_fails_if_no_virtualenv_and_system_not_allowed() -> Result<()> {
|
fn find_environment_fails_if_no_virtualenv_and_system_not_allowed() -> Result<()> {
|
||||||
let tempdir = TempDir::new()?;
|
let tempdir = TempDir::new()?;
|
||||||
|
let toolchains = InstalledToolchains::temp()?;
|
||||||
let cache = Cache::temp()?;
|
let cache = Cache::temp()?;
|
||||||
|
|
||||||
with_vars(
|
with_vars(
|
||||||
[
|
[
|
||||||
("UV_TEST_PYTHON_PATH", None),
|
("UV_TEST_PYTHON_PATH", None),
|
||||||
("UV_BOOTSTRAP_DIR", None),
|
("UV_TOOLCHAIN_DIR", Some(toolchains.root().into())),
|
||||||
(
|
(
|
||||||
"PATH",
|
"PATH",
|
||||||
Some(simple_mock_interpreters(&tempdir, &["3.10.1", "3.11.2"])?),
|
Some(simple_mock_interpreters(&tempdir, &["3.10.1", "3.11.2"])?),
|
||||||
|
@ -1707,6 +1741,7 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn find_environment_allows_name_in_working_directory() -> Result<()> {
|
fn find_environment_allows_name_in_working_directory() -> Result<()> {
|
||||||
let tempdir = TempDir::new()?;
|
let tempdir = TempDir::new()?;
|
||||||
|
let toolchains = InstalledToolchains::temp()?;
|
||||||
let cache = Cache::temp()?;
|
let cache = Cache::temp()?;
|
||||||
let python = tempdir.join("foobar");
|
let python = tempdir.join("foobar");
|
||||||
create_mock_interpreter(
|
create_mock_interpreter(
|
||||||
|
@ -1719,6 +1754,7 @@ mod tests {
|
||||||
with_vars(
|
with_vars(
|
||||||
[
|
[
|
||||||
("UV_TEST_PYTHON_PATH", None::<OsString>),
|
("UV_TEST_PYTHON_PATH", None::<OsString>),
|
||||||
|
("UV_TOOLCHAIN_DIR", Some(toolchains.root().into())),
|
||||||
("PATH", None),
|
("PATH", None),
|
||||||
("PWD", Some(tempdir.path().into())),
|
("PWD", Some(tempdir.path().into())),
|
||||||
],
|
],
|
||||||
|
@ -1740,6 +1776,7 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn find_environment_allows_relative_file_path() -> Result<()> {
|
fn find_environment_allows_relative_file_path() -> Result<()> {
|
||||||
let tempdir = TempDir::new()?;
|
let tempdir = TempDir::new()?;
|
||||||
|
let toolchains = InstalledToolchains::temp()?;
|
||||||
let cache = Cache::temp()?;
|
let cache = Cache::temp()?;
|
||||||
tempdir.child("foo").create_dir_all()?;
|
tempdir.child("foo").create_dir_all()?;
|
||||||
let python = tempdir.child("foo").join("bar");
|
let python = tempdir.child("foo").join("bar");
|
||||||
|
@ -1753,6 +1790,7 @@ mod tests {
|
||||||
with_vars(
|
with_vars(
|
||||||
[
|
[
|
||||||
("UV_TEST_PYTHON_PATH", None::<OsString>),
|
("UV_TEST_PYTHON_PATH", None::<OsString>),
|
||||||
|
("UV_TOOLCHAIN_DIR", Some(toolchains.root().into())),
|
||||||
("PATH", None),
|
("PATH", None),
|
||||||
("PWD", Some(tempdir.path().into())),
|
("PWD", Some(tempdir.path().into())),
|
||||||
],
|
],
|
||||||
|
@ -1777,6 +1815,7 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn find_environment_allows_absolute_file_path() -> Result<()> {
|
fn find_environment_allows_absolute_file_path() -> Result<()> {
|
||||||
let tempdir = TempDir::new()?;
|
let tempdir = TempDir::new()?;
|
||||||
|
let toolchains = InstalledToolchains::temp()?;
|
||||||
let cache = Cache::temp()?;
|
let cache = Cache::temp()?;
|
||||||
tempdir.child("foo").create_dir_all()?;
|
tempdir.child("foo").create_dir_all()?;
|
||||||
let python = tempdir.child("foo").join("bar");
|
let python = tempdir.child("foo").join("bar");
|
||||||
|
@ -1790,6 +1829,7 @@ mod tests {
|
||||||
with_vars(
|
with_vars(
|
||||||
[
|
[
|
||||||
("UV_TEST_PYTHON_PATH", None::<OsString>),
|
("UV_TEST_PYTHON_PATH", None::<OsString>),
|
||||||
|
("UV_TOOLCHAIN_DIR", Some(toolchains.root().into())),
|
||||||
("PATH", None),
|
("PATH", None),
|
||||||
("PWD", Some(tempdir.path().into())),
|
("PWD", Some(tempdir.path().into())),
|
||||||
],
|
],
|
||||||
|
@ -1814,6 +1854,7 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn find_environment_allows_venv_directory_path() -> Result<()> {
|
fn find_environment_allows_venv_directory_path() -> Result<()> {
|
||||||
let tempdir = TempDir::new()?;
|
let tempdir = TempDir::new()?;
|
||||||
|
let toolchains = InstalledToolchains::temp()?;
|
||||||
// Create a separate pwd to avoid ancestor discovery of the venv
|
// Create a separate pwd to avoid ancestor discovery of the venv
|
||||||
let pwd = TempDir::new()?;
|
let pwd = TempDir::new()?;
|
||||||
let cache = Cache::temp()?;
|
let cache = Cache::temp()?;
|
||||||
|
@ -1822,6 +1863,7 @@ mod tests {
|
||||||
with_vars(
|
with_vars(
|
||||||
[
|
[
|
||||||
("UV_TEST_PYTHON_PATH", None::<OsString>),
|
("UV_TEST_PYTHON_PATH", None::<OsString>),
|
||||||
|
("UV_TOOLCHAIN_DIR", Some(toolchains.root().into())),
|
||||||
("PATH", None),
|
("PATH", None),
|
||||||
("PWD", Some(pwd.path().into())),
|
("PWD", Some(pwd.path().into())),
|
||||||
],
|
],
|
||||||
|
@ -1846,6 +1888,7 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn find_environment_allows_file_path_with_system_explicit() -> Result<()> {
|
fn find_environment_allows_file_path_with_system_explicit() -> Result<()> {
|
||||||
let tempdir = TempDir::new()?;
|
let tempdir = TempDir::new()?;
|
||||||
|
let toolchains = InstalledToolchains::temp()?;
|
||||||
let cache = Cache::temp()?;
|
let cache = Cache::temp()?;
|
||||||
tempdir.child("foo").create_dir_all()?;
|
tempdir.child("foo").create_dir_all()?;
|
||||||
let python = tempdir.child("foo").join("bar");
|
let python = tempdir.child("foo").join("bar");
|
||||||
|
@ -1859,6 +1902,7 @@ mod tests {
|
||||||
with_vars(
|
with_vars(
|
||||||
[
|
[
|
||||||
("UV_TEST_PYTHON_PATH", None::<OsString>),
|
("UV_TEST_PYTHON_PATH", None::<OsString>),
|
||||||
|
("UV_TOOLCHAIN_DIR", Some(toolchains.root().into())),
|
||||||
("PATH", None),
|
("PATH", None),
|
||||||
("PWD", Some(tempdir.path().into())),
|
("PWD", Some(tempdir.path().into())),
|
||||||
],
|
],
|
||||||
|
@ -1883,6 +1927,7 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn find_environment_does_not_allow_file_path_with_system_disallowed() -> Result<()> {
|
fn find_environment_does_not_allow_file_path_with_system_disallowed() -> Result<()> {
|
||||||
let tempdir = TempDir::new()?;
|
let tempdir = TempDir::new()?;
|
||||||
|
let toolchains = InstalledToolchains::temp()?;
|
||||||
let cache = Cache::temp()?;
|
let cache = Cache::temp()?;
|
||||||
tempdir.child("foo").create_dir_all()?;
|
tempdir.child("foo").create_dir_all()?;
|
||||||
let python = tempdir.child("foo").join("bar");
|
let python = tempdir.child("foo").join("bar");
|
||||||
|
@ -1896,6 +1941,7 @@ mod tests {
|
||||||
with_vars(
|
with_vars(
|
||||||
[
|
[
|
||||||
("UV_TEST_PYTHON_PATH", None::<OsString>),
|
("UV_TEST_PYTHON_PATH", None::<OsString>),
|
||||||
|
("UV_TOOLCHAIN_DIR", Some(toolchains.root().into())),
|
||||||
("PATH", None),
|
("PATH", None),
|
||||||
("PWD", Some(tempdir.path().into())),
|
("PWD", Some(tempdir.path().into())),
|
||||||
],
|
],
|
||||||
|
@ -1924,12 +1970,14 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn find_environment_treats_missing_file_path_as_file() -> Result<()> {
|
fn find_environment_treats_missing_file_path_as_file() -> Result<()> {
|
||||||
let tempdir = TempDir::new()?;
|
let tempdir = TempDir::new()?;
|
||||||
|
let toolchains = InstalledToolchains::temp()?;
|
||||||
let cache = Cache::temp()?;
|
let cache = Cache::temp()?;
|
||||||
tempdir.child("foo").create_dir_all()?;
|
tempdir.child("foo").create_dir_all()?;
|
||||||
|
|
||||||
with_vars(
|
with_vars(
|
||||||
[
|
[
|
||||||
("UV_TEST_PYTHON_PATH", None::<OsString>),
|
("UV_TEST_PYTHON_PATH", None::<OsString>),
|
||||||
|
("UV_TOOLCHAIN_DIR", Some(toolchains.root().into())),
|
||||||
("PATH", None),
|
("PATH", None),
|
||||||
("PWD", Some(tempdir.path().into())),
|
("PWD", Some(tempdir.path().into())),
|
||||||
],
|
],
|
||||||
|
@ -1956,6 +2004,7 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn find_environment_executable_name_in_search_path() -> Result<()> {
|
fn find_environment_executable_name_in_search_path() -> Result<()> {
|
||||||
let tempdir = TempDir::new()?;
|
let tempdir = TempDir::new()?;
|
||||||
|
let toolchains = InstalledToolchains::temp()?;
|
||||||
let pwd = tempdir.child("pwd");
|
let pwd = tempdir.child("pwd");
|
||||||
pwd.create_dir_all()?;
|
pwd.create_dir_all()?;
|
||||||
let cache = Cache::temp()?;
|
let cache = Cache::temp()?;
|
||||||
|
@ -1970,6 +2019,7 @@ mod tests {
|
||||||
with_vars(
|
with_vars(
|
||||||
[
|
[
|
||||||
("UV_TEST_PYTHON_PATH", None::<OsString>),
|
("UV_TEST_PYTHON_PATH", None::<OsString>),
|
||||||
|
("UV_TOOLCHAIN_DIR", Some(toolchains.root().into())),
|
||||||
("PATH", Some(tempdir.path().into())),
|
("PATH", Some(tempdir.path().into())),
|
||||||
("PWD", Some(pwd.path().into())),
|
("PWD", Some(pwd.path().into())),
|
||||||
],
|
],
|
||||||
|
@ -1991,11 +2041,13 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn find_environment_pypy() -> Result<()> {
|
fn find_environment_pypy() -> Result<()> {
|
||||||
let tempdir = TempDir::new()?;
|
let tempdir = TempDir::new()?;
|
||||||
|
let toolchains = InstalledToolchains::temp()?;
|
||||||
let cache = Cache::temp()?;
|
let cache = Cache::temp()?;
|
||||||
|
|
||||||
with_vars(
|
with_vars(
|
||||||
[
|
[
|
||||||
("UV_TEST_PYTHON_PATH", None::<OsString>),
|
("UV_TEST_PYTHON_PATH", None::<OsString>),
|
||||||
|
("UV_TOOLCHAIN_DIR", Some(toolchains.root().into())),
|
||||||
(
|
(
|
||||||
"PATH",
|
"PATH",
|
||||||
Some(mock_interpreters(
|
Some(mock_interpreters(
|
||||||
|
@ -2023,11 +2075,13 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn find_environment_pypy_request_ignores_cpython() -> Result<()> {
|
fn find_environment_pypy_request_ignores_cpython() -> Result<()> {
|
||||||
let tempdir = TempDir::new()?;
|
let tempdir = TempDir::new()?;
|
||||||
|
let toolchains = InstalledToolchains::temp()?;
|
||||||
let cache = Cache::temp()?;
|
let cache = Cache::temp()?;
|
||||||
|
|
||||||
with_vars(
|
with_vars(
|
||||||
[
|
[
|
||||||
("UV_TEST_PYTHON_PATH", None::<OsString>),
|
("UV_TEST_PYTHON_PATH", None::<OsString>),
|
||||||
|
("UV_TOOLCHAIN_DIR", Some(toolchains.root().into())),
|
||||||
(
|
(
|
||||||
"PATH",
|
"PATH",
|
||||||
Some(mock_interpreters(
|
Some(mock_interpreters(
|
||||||
|
@ -2058,12 +2112,14 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn find_environment_pypy_request_skips_wrong_versions() -> Result<()> {
|
fn find_environment_pypy_request_skips_wrong_versions() -> Result<()> {
|
||||||
let tempdir = TempDir::new()?;
|
let tempdir = TempDir::new()?;
|
||||||
|
let toolchains = InstalledToolchains::temp()?;
|
||||||
let cache = Cache::temp()?;
|
let cache = Cache::temp()?;
|
||||||
|
|
||||||
// We should prefer the `pypy` executable with the requested version
|
// We should prefer the `pypy` executable with the requested version
|
||||||
with_vars(
|
with_vars(
|
||||||
[
|
[
|
||||||
("UV_TEST_PYTHON_PATH", None::<OsString>),
|
("UV_TEST_PYTHON_PATH", None::<OsString>),
|
||||||
|
("UV_TOOLCHAIN_DIR", Some(toolchains.root().into())),
|
||||||
(
|
(
|
||||||
"PATH",
|
"PATH",
|
||||||
Some(mock_interpreters(
|
Some(mock_interpreters(
|
||||||
|
@ -2094,12 +2150,14 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn find_environment_pypy_finds_executable_with_version_name() -> Result<()> {
|
fn find_environment_pypy_finds_executable_with_version_name() -> Result<()> {
|
||||||
let tempdir = TempDir::new()?;
|
let tempdir = TempDir::new()?;
|
||||||
|
let toolchains = InstalledToolchains::temp()?;
|
||||||
let cache = Cache::temp()?;
|
let cache = Cache::temp()?;
|
||||||
|
|
||||||
// We should find executables that include the version number
|
// We should find executables that include the version number
|
||||||
with_vars(
|
with_vars(
|
||||||
[
|
[
|
||||||
("UV_TEST_PYTHON_PATH", None::<OsString>),
|
("UV_TEST_PYTHON_PATH", None::<OsString>),
|
||||||
|
("UV_TOOLCHAIN_DIR", Some(toolchains.root().into())),
|
||||||
(
|
(
|
||||||
"PATH",
|
"PATH",
|
||||||
Some(mock_interpreters(
|
Some(mock_interpreters(
|
||||||
|
@ -2134,6 +2192,7 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn find_environment_pypy_prefers_executable_with_implementation_name() -> Result<()> {
|
fn find_environment_pypy_prefers_executable_with_implementation_name() -> Result<()> {
|
||||||
let tempdir = TempDir::new()?;
|
let tempdir = TempDir::new()?;
|
||||||
|
let toolchains = InstalledToolchains::temp()?;
|
||||||
let cache = Cache::temp()?;
|
let cache = Cache::temp()?;
|
||||||
|
|
||||||
// We should prefer `pypy` executables over `python` executables even if they are both pypy
|
// We should prefer `pypy` executables over `python` executables even if they are both pypy
|
||||||
|
@ -2152,6 +2211,7 @@ mod tests {
|
||||||
with_vars(
|
with_vars(
|
||||||
[
|
[
|
||||||
("UV_TEST_PYTHON_PATH", None::<OsString>),
|
("UV_TEST_PYTHON_PATH", None::<OsString>),
|
||||||
|
("UV_TOOLCHAIN_DIR", Some(toolchains.root().into())),
|
||||||
("PATH", Some(tempdir.path().into())),
|
("PATH", Some(tempdir.path().into())),
|
||||||
("PWD", Some(tempdir.path().into())),
|
("PWD", Some(tempdir.path().into())),
|
||||||
],
|
],
|
||||||
|
@ -2209,6 +2269,7 @@ mod tests {
|
||||||
|
|
||||||
// We should prefer executables with the version number over those with implementation names
|
// We should prefer executables with the version number over those with implementation names
|
||||||
let tempdir = TempDir::new()?;
|
let tempdir = TempDir::new()?;
|
||||||
|
let toolchains = InstalledToolchains::temp()?;
|
||||||
create_mock_interpreter(
|
create_mock_interpreter(
|
||||||
&tempdir.path().join("pypy3.10"),
|
&tempdir.path().join("pypy3.10"),
|
||||||
&PythonVersion::from_str("3.10.0").unwrap(),
|
&PythonVersion::from_str("3.10.0").unwrap(),
|
||||||
|
@ -2224,6 +2285,7 @@ mod tests {
|
||||||
with_vars(
|
with_vars(
|
||||||
[
|
[
|
||||||
("UV_TEST_PYTHON_PATH", None::<OsString>),
|
("UV_TEST_PYTHON_PATH", None::<OsString>),
|
||||||
|
("UV_TOOLCHAIN_DIR", Some(toolchains.root().into())),
|
||||||
("PATH", Some(tempdir.path().into())),
|
("PATH", Some(tempdir.path().into())),
|
||||||
("PWD", Some(tempdir.path().into())),
|
("PWD", Some(tempdir.path().into())),
|
||||||
],
|
],
|
||||||
|
@ -2258,6 +2320,7 @@ mod tests {
|
||||||
with_vars(
|
with_vars(
|
||||||
[
|
[
|
||||||
("UV_TEST_PYTHON_PATH", None::<OsString>),
|
("UV_TEST_PYTHON_PATH", None::<OsString>),
|
||||||
|
("UV_TOOLCHAIN_DIR", Some(toolchains.root().into())),
|
||||||
("PATH", Some(tempdir.path().into())),
|
("PATH", Some(tempdir.path().into())),
|
||||||
("PWD", Some(tempdir.path().into())),
|
("PWD", Some(tempdir.path().into())),
|
||||||
],
|
],
|
||||||
|
|
|
@ -18,6 +18,8 @@ use uv_fs::Simplified;
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
|
#[error(transparent)]
|
||||||
|
IO(#[from] io::Error),
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
PlatformError(#[from] PlatformError),
|
PlatformError(#[from] PlatformError),
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
|
|
|
@ -1,47 +1,152 @@
|
||||||
|
use core::fmt;
|
||||||
|
use fs_err as fs;
|
||||||
use std::collections::BTreeSet;
|
use std::collections::BTreeSet;
|
||||||
use std::ffi::OsStr;
|
use std::ffi::OsStr;
|
||||||
use std::path::PathBuf;
|
use std::io::{self, Write};
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use once_cell::sync::Lazy;
|
use uv_state::{StateBucket, StateStore};
|
||||||
use tracing::debug;
|
|
||||||
|
|
||||||
use uv_fs::Simplified;
|
|
||||||
|
|
||||||
use crate::managed::downloads::Error;
|
use crate::managed::downloads::Error;
|
||||||
use crate::platform::{Arch, Libc, Os};
|
use crate::platform::{Arch, Libc, Os};
|
||||||
use crate::python_version::PythonVersion;
|
use crate::python_version::PythonVersion;
|
||||||
|
|
||||||
/// The directory where Python toolchains we install are stored.
|
/// A collection of installed Python toolchains.
|
||||||
pub static TOOLCHAIN_DIRECTORY: Lazy<Option<PathBuf>> =
|
#[derive(Debug, Clone)]
|
||||||
Lazy::new(|| std::env::var_os("UV_BOOTSTRAP_DIR").map(PathBuf::from));
|
pub struct InstalledToolchains {
|
||||||
|
/// The path to the top-level directory of the installed toolchains.
|
||||||
|
root: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
pub fn toolchains_for_current_platform() -> Result<impl Iterator<Item = Toolchain>, Error> {
|
impl InstalledToolchains {
|
||||||
let platform_key = platform_key_from_env()?;
|
/// A directory for installed toolchains at `root`.
|
||||||
let iter = toolchain_directories()?
|
pub fn from_path(root: impl Into<PathBuf>) -> Result<Self, io::Error> {
|
||||||
.into_iter()
|
Ok(Self { root: root.into() })
|
||||||
// Sort "newer" versions of Python first
|
}
|
||||||
.rev()
|
|
||||||
.filter_map(move |path| {
|
/// Prefer, in order:
|
||||||
if path
|
/// 1. The specific toolchain directory specified by the user, i.e., `UV_TOOLCHAIN_DIR`
|
||||||
.file_name()
|
/// 2. A bucket in the system-appropriate user-level data directory, e.g., `~/.local/uv/toolchains`
|
||||||
.map(OsStr::to_string_lossy)
|
/// 3. A bucket in the local data directory, e.g., `./.uv/toolchains`
|
||||||
.is_some_and(|filename| filename.ends_with(&platform_key))
|
pub fn from_settings() -> Result<Self, io::Error> {
|
||||||
{
|
if let Some(toolchain_dir) = std::env::var_os("UV_TOOLCHAIN_DIR") {
|
||||||
Toolchain::new(path.clone())
|
Self::from_path(toolchain_dir)
|
||||||
.inspect_err(|err| {
|
} else {
|
||||||
debug!(
|
Self::from_path(StateStore::from_settings(None)?.bucket(StateBucket::Toolchains))
|
||||||
"Ignoring invalid toolchain directory {}: {err}",
|
}
|
||||||
path.user_display()
|
}
|
||||||
);
|
|
||||||
|
/// Create a temporary installed toolchain directory.
|
||||||
|
pub fn temp() -> Result<Self, io::Error> {
|
||||||
|
Self::from_path(StateStore::temp()?.bucket(StateBucket::Toolchains))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Initialize the installed toolchain directory.
|
||||||
|
///
|
||||||
|
/// Ensures the directory is created.
|
||||||
|
pub fn init(self) -> Result<Self, io::Error> {
|
||||||
|
let root = &self.root;
|
||||||
|
|
||||||
|
// Create the cache directory, if it doesn't exist.
|
||||||
|
fs::create_dir_all(root)?;
|
||||||
|
|
||||||
|
// Add a .gitignore.
|
||||||
|
match fs::OpenOptions::new()
|
||||||
|
.write(true)
|
||||||
|
.create_new(true)
|
||||||
|
.open(root.join(".gitignore"))
|
||||||
|
{
|
||||||
|
Ok(mut file) => file.write_all(b"*")?,
|
||||||
|
Err(err) if err.kind() == io::ErrorKind::AlreadyExists => (),
|
||||||
|
Err(err) => return Err(err),
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Iterate over each installed toolchain in this directory.
|
||||||
|
///
|
||||||
|
/// Toolchains are sorted descending by name, such that we get deterministic
|
||||||
|
/// ordering across platforms. This also results in newer Python versions coming first,
|
||||||
|
/// but should not be relied on — instead the toolchains should be sorted later by
|
||||||
|
/// the parsed Python version.
|
||||||
|
fn find_all(&self) -> Result<impl DoubleEndedIterator<Item = Toolchain>, Error> {
|
||||||
|
let dirs = match fs_err::read_dir(&self.root) {
|
||||||
|
Ok(toolchain_dirs) => {
|
||||||
|
// Collect sorted directory paths; `read_dir` is not stable across platforms
|
||||||
|
let directories: BTreeSet<_> = toolchain_dirs
|
||||||
|
.filter_map(|read_dir| match read_dir {
|
||||||
|
Ok(entry) => match entry.file_type() {
|
||||||
|
Ok(file_type) => file_type.is_dir().then_some(Ok(entry.path())),
|
||||||
|
Err(err) => Some(Err(err)),
|
||||||
|
},
|
||||||
|
Err(err) => Some(Err(err)),
|
||||||
})
|
})
|
||||||
.ok()
|
.collect::<Result<_, std::io::Error>>()
|
||||||
} else {
|
.map_err(|err| Error::ReadError {
|
||||||
None
|
dir: self.root.clone(),
|
||||||
|
err,
|
||||||
|
})?;
|
||||||
|
directories
|
||||||
}
|
}
|
||||||
});
|
Err(err) if err.kind() == std::io::ErrorKind::NotFound => BTreeSet::default(),
|
||||||
|
Err(err) => {
|
||||||
|
return Err(Error::ReadError {
|
||||||
|
dir: self.root.clone(),
|
||||||
|
err,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(dirs
|
||||||
|
.into_iter()
|
||||||
|
.map(|path| Toolchain::new(path).unwrap())
|
||||||
|
.rev())
|
||||||
|
}
|
||||||
|
|
||||||
Ok(iter)
|
/// Iterate over toolchains that support the current platform.
|
||||||
|
pub fn find_matching_current_platform(
|
||||||
|
&self,
|
||||||
|
) -> Result<impl DoubleEndedIterator<Item = Toolchain>, Error> {
|
||||||
|
let platform_key = platform_key_from_env()?;
|
||||||
|
|
||||||
|
let iter = InstalledToolchains::from_settings()?
|
||||||
|
.find_all()?
|
||||||
|
.filter(move |toolchain| {
|
||||||
|
toolchain
|
||||||
|
.path
|
||||||
|
.file_name()
|
||||||
|
.map(OsStr::to_string_lossy)
|
||||||
|
.is_some_and(|filename| filename.ends_with(&platform_key))
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(iter)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Iterate over toolchains that satisfy the given Python version on this platform.
|
||||||
|
///
|
||||||
|
/// ## Errors
|
||||||
|
///
|
||||||
|
/// - The platform metadata cannot be read
|
||||||
|
/// - A directory in the toolchain directory cannot be read
|
||||||
|
pub fn find_version<'a>(
|
||||||
|
&self,
|
||||||
|
version: &'a PythonVersion,
|
||||||
|
) -> Result<impl DoubleEndedIterator<Item = Toolchain> + 'a, Error> {
|
||||||
|
Ok(self
|
||||||
|
.find_matching_current_platform()?
|
||||||
|
.filter(move |toolchain| {
|
||||||
|
toolchain
|
||||||
|
.path
|
||||||
|
.file_name()
|
||||||
|
.map(OsStr::to_string_lossy)
|
||||||
|
.is_some_and(|filename| filename.starts_with(&format!("cpython-{version}")))
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn root(&self) -> &Path {
|
||||||
|
&self.root
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An installed Python toolchain.
|
/// An installed Python toolchain.
|
||||||
|
@ -72,6 +177,7 @@ impl Toolchain {
|
||||||
python_version,
|
python_version,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn executable(&self) -> PathBuf {
|
pub fn executable(&self) -> PathBuf {
|
||||||
if cfg!(windows) {
|
if cfg!(windows) {
|
||||||
self.path.join("install").join("python.exe")
|
self.path.join("install").join("python.exe")
|
||||||
|
@ -87,84 +193,6 @@ impl Toolchain {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the directories in the toolchain directory.
|
|
||||||
///
|
|
||||||
/// Toolchain directories are sorted descending by name, such that we get deterministic
|
|
||||||
/// ordering across platforms. This also results in newer Python versions coming first,
|
|
||||||
/// but should not be relied on — instead the toolchains should be sorted later by
|
|
||||||
/// the parsed Python version.
|
|
||||||
fn toolchain_directories() -> Result<BTreeSet<PathBuf>, Error> {
|
|
||||||
let Some(toolchain_dir) = TOOLCHAIN_DIRECTORY.as_ref() else {
|
|
||||||
return Ok(BTreeSet::default());
|
|
||||||
};
|
|
||||||
match fs_err::read_dir(toolchain_dir.clone()) {
|
|
||||||
Ok(toolchain_dirs) => {
|
|
||||||
// Collect sorted directory paths; `read_dir` is not stable across platforms
|
|
||||||
let directories: BTreeSet<_> = toolchain_dirs
|
|
||||||
.filter_map(|read_dir| match read_dir {
|
|
||||||
Ok(entry) => match entry.file_type() {
|
|
||||||
Ok(file_type) => file_type.is_dir().then_some(Ok(entry.path())),
|
|
||||||
Err(err) => Some(Err(err)),
|
|
||||||
},
|
|
||||||
Err(err) => Some(Err(err)),
|
|
||||||
})
|
|
||||||
.collect::<Result<_, std::io::Error>>()
|
|
||||||
.map_err(|err| Error::ReadError {
|
|
||||||
dir: toolchain_dir.clone(),
|
|
||||||
err,
|
|
||||||
})?;
|
|
||||||
Ok(directories)
|
|
||||||
}
|
|
||||||
Err(err) if err.kind() == std::io::ErrorKind::NotFound => Ok(BTreeSet::default()),
|
|
||||||
Err(err) => Err(Error::ReadError {
|
|
||||||
dir: toolchain_dir.clone(),
|
|
||||||
err,
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return the toolchains that satisfy the given Python version on this platform.
|
|
||||||
///
|
|
||||||
/// ## Errors
|
|
||||||
///
|
|
||||||
/// - The platform metadata cannot be read
|
|
||||||
/// - A directory in the toolchain directory cannot be read
|
|
||||||
pub fn toolchains_for_version(version: &PythonVersion) -> Result<Vec<Toolchain>, Error> {
|
|
||||||
let platform_key = platform_key_from_env()?;
|
|
||||||
|
|
||||||
// TODO(zanieb): Consider returning an iterator instead of a `Vec`
|
|
||||||
// Note we need to collect paths regardless for sorting by version.
|
|
||||||
|
|
||||||
let toolchain_dirs = toolchain_directories()?;
|
|
||||||
|
|
||||||
Ok(toolchain_dirs
|
|
||||||
.into_iter()
|
|
||||||
// Sort "newer" versions of Python first
|
|
||||||
.rev()
|
|
||||||
.filter_map(|path| {
|
|
||||||
if path
|
|
||||||
.file_name()
|
|
||||||
.map(OsStr::to_string_lossy)
|
|
||||||
.is_some_and(|filename| {
|
|
||||||
filename.starts_with(&format!("cpython-{version}"))
|
|
||||||
&& filename.ends_with(&platform_key)
|
|
||||||
})
|
|
||||||
{
|
|
||||||
Toolchain::new(path.clone())
|
|
||||||
.inspect_err(|err| {
|
|
||||||
debug!(
|
|
||||||
"Ignoring invalid toolchain directory {}: {err}",
|
|
||||||
path.user_display()
|
|
||||||
);
|
|
||||||
})
|
|
||||||
.ok()
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Generate a platform portion of a key from the environment.
|
/// Generate a platform portion of a key from the environment.
|
||||||
fn platform_key_from_env() -> Result<String, Error> {
|
fn platform_key_from_env() -> Result<String, Error> {
|
||||||
let os = Os::from_env()?;
|
let os = Os::from_env()?;
|
||||||
|
@ -172,3 +200,16 @@ fn platform_key_from_env() -> Result<String, Error> {
|
||||||
let libc = Libc::from_env();
|
let libc = Libc::from_env();
|
||||||
Ok(format!("{os}-{arch}-{libc}").to_lowercase())
|
Ok(format!("{os}-{arch}-{libc}").to_lowercase())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Toolchain {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"{}",
|
||||||
|
self.path
|
||||||
|
.file_name()
|
||||||
|
.unwrap_or(self.path.as_os_str())
|
||||||
|
.to_string_lossy()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
pub use crate::managed::downloads::{DownloadResult, Error, PythonDownload, PythonDownloadRequest};
|
pub use crate::managed::downloads::{DownloadResult, Error, PythonDownload, PythonDownloadRequest};
|
||||||
pub use crate::managed::find::{
|
pub use crate::managed::find::{InstalledToolchains, Toolchain};
|
||||||
toolchains_for_current_platform, toolchains_for_version, Toolchain, TOOLCHAIN_DIRECTORY,
|
|
||||||
};
|
|
||||||
|
|
||||||
mod downloads;
|
mod downloads;
|
||||||
mod find;
|
mod find;
|
||||||
|
|
18
crates/uv-state/Cargo.toml
Normal file
18
crates/uv-state/Cargo.toml
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
[package]
|
||||||
|
name = "uv-state"
|
||||||
|
version = "0.0.1"
|
||||||
|
edition = { workspace = true }
|
||||||
|
rust-version = { workspace = true }
|
||||||
|
homepage = { workspace = true }
|
||||||
|
documentation = { workspace = true }
|
||||||
|
repository = { workspace = true }
|
||||||
|
authors = { workspace = true }
|
||||||
|
license = { workspace = true }
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
directories = { workspace = true }
|
||||||
|
tempfile = { workspace = true }
|
||||||
|
fs-err = { workspace = true }
|
108
crates/uv-state/src/lib.rs
Normal file
108
crates/uv-state/src/lib.rs
Normal file
|
@ -0,0 +1,108 @@
|
||||||
|
use std::{
|
||||||
|
io::{self, Write},
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
sync::Arc,
|
||||||
|
};
|
||||||
|
|
||||||
|
use directories::ProjectDirs;
|
||||||
|
use fs_err as fs;
|
||||||
|
use tempfile::{tempdir, TempDir};
|
||||||
|
|
||||||
|
/// The main state storage abstraction.
|
||||||
|
///
|
||||||
|
/// This is appropriate
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct StateStore {
|
||||||
|
/// The state storage.
|
||||||
|
root: PathBuf,
|
||||||
|
/// A temporary state storage.
|
||||||
|
///
|
||||||
|
/// Included to ensure that the temporary store exists for the length of the operation, but
|
||||||
|
/// is dropped at the end as appropriate.
|
||||||
|
_temp_dir_drop: Option<Arc<TempDir>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StateStore {
|
||||||
|
/// A persistent state store at `root`.
|
||||||
|
pub fn from_path(root: impl Into<PathBuf>) -> Result<Self, io::Error> {
|
||||||
|
Ok(Self {
|
||||||
|
root: root.into(),
|
||||||
|
_temp_dir_drop: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a temporary state store.
|
||||||
|
pub fn temp() -> Result<Self, io::Error> {
|
||||||
|
let temp_dir = tempdir()?;
|
||||||
|
Ok(Self {
|
||||||
|
root: temp_dir.path().to_path_buf(),
|
||||||
|
_temp_dir_drop: Some(Arc::new(temp_dir)),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the root of the state store.
|
||||||
|
pub fn root(&self) -> &Path {
|
||||||
|
&self.root
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Initialize the state store.
|
||||||
|
pub fn init(self) -> Result<Self, io::Error> {
|
||||||
|
let root = &self.root;
|
||||||
|
|
||||||
|
// Create the state store directory, if it doesn't exist.
|
||||||
|
fs::create_dir_all(root)?;
|
||||||
|
|
||||||
|
// Add a .gitignore.
|
||||||
|
match fs::OpenOptions::new()
|
||||||
|
.write(true)
|
||||||
|
.create_new(true)
|
||||||
|
.open(root.join(".gitignore"))
|
||||||
|
{
|
||||||
|
Ok(mut file) => file.write_all(b"*")?,
|
||||||
|
Err(err) if err.kind() == io::ErrorKind::AlreadyExists => (),
|
||||||
|
Err(err) => return Err(err),
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
root: fs::canonicalize(root)?,
|
||||||
|
..self
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The folder for a specific cache bucket
|
||||||
|
pub fn bucket(&self, state_bucket: StateBucket) -> PathBuf {
|
||||||
|
self.root.join(state_bucket.to_str())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Prefer, in order:
|
||||||
|
/// 1. The specific state directory specified by the user.
|
||||||
|
/// 2. The system-appropriate user-level data directory.
|
||||||
|
/// 3. A `.uv` directory in the current working directory.
|
||||||
|
///
|
||||||
|
/// Returns an absolute cache dir.
|
||||||
|
pub fn from_settings(state_dir: Option<PathBuf>) -> Result<Self, io::Error> {
|
||||||
|
if let Some(state_dir) = state_dir {
|
||||||
|
StateStore::from_path(state_dir)
|
||||||
|
} else if let Some(project_dirs) = ProjectDirs::from("", "", "uv") {
|
||||||
|
StateStore::from_path(project_dirs.data_dir())
|
||||||
|
} else {
|
||||||
|
StateStore::from_path(".uv")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The different kinds of data in the state store are stored in different bucket, which in our case
|
||||||
|
/// are subdirectories of the state store root.
|
||||||
|
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
|
||||||
|
pub enum StateBucket {
|
||||||
|
// Managed toolchain
|
||||||
|
Toolchains,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StateBucket {
|
||||||
|
fn to_str(self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Self::Toolchains => "toolchains",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,13 +9,14 @@ use regex::Regex;
|
||||||
use std::borrow::BorrowMut;
|
use std::borrow::BorrowMut;
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::ffi::OsString;
|
use std::ffi::OsString;
|
||||||
|
use std::iter::Iterator;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::process::Output;
|
use std::process::Output;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use uv_cache::Cache;
|
use uv_cache::Cache;
|
||||||
use uv_fs::Simplified;
|
use uv_fs::Simplified;
|
||||||
use uv_interpreter::managed::toolchains_for_version;
|
use uv_interpreter::managed::InstalledToolchains;
|
||||||
use uv_interpreter::{
|
use uv_interpreter::{
|
||||||
find_interpreter, InterpreterRequest, PythonVersion, SourceSelector, VersionRequest,
|
find_interpreter, InterpreterRequest, PythonVersion, SourceSelector, VersionRequest,
|
||||||
};
|
};
|
||||||
|
@ -337,15 +338,22 @@ pub fn create_venv<Parent: assert_fs::prelude::PathChild + AsRef<std::path::Path
|
||||||
cache_dir: &assert_fs::TempDir,
|
cache_dir: &assert_fs::TempDir,
|
||||||
python: &str,
|
python: &str,
|
||||||
) -> PathBuf {
|
) -> PathBuf {
|
||||||
let python = toolchains_for_version(
|
let python = InstalledToolchains::from_settings()
|
||||||
&PythonVersion::from_str(python).expect("Tests should use a valid Python version"),
|
.map(|installed_toolchains| {
|
||||||
)
|
installed_toolchains
|
||||||
.expect("Tests are run on a supported platform")
|
.find_version(
|
||||||
.first()
|
&PythonVersion::from_str(python)
|
||||||
.map(uv_interpreter::managed::Toolchain::executable)
|
.expect("Tests should use a valid Python version"),
|
||||||
// We'll search for the request Python on the PATH if not found in the toolchain versions
|
)
|
||||||
// We hack this into a `PathBuf` to satisfy the compiler but it's just a string
|
.expect("Tests are run on a supported platform")
|
||||||
.unwrap_or(PathBuf::from(python));
|
.next()
|
||||||
|
.as_ref()
|
||||||
|
.map(uv_interpreter::managed::Toolchain::executable)
|
||||||
|
})
|
||||||
|
// We'll search for the request Python on the PATH if not found in the toolchain versions
|
||||||
|
// We hack this into a `PathBuf` to satisfy the compiler but it's just a string
|
||||||
|
.unwrap_or_default()
|
||||||
|
.unwrap_or(PathBuf::from(python));
|
||||||
|
|
||||||
let venv = temp_dir.child(".venv");
|
let venv = temp_dir.child(".venv");
|
||||||
Command::new(get_bin())
|
Command::new(get_bin())
|
||||||
|
@ -380,20 +388,24 @@ pub fn python_path_with_versions(
|
||||||
let selected_pythons = python_versions
|
let selected_pythons = python_versions
|
||||||
.iter()
|
.iter()
|
||||||
.flat_map(|python_version| {
|
.flat_map(|python_version| {
|
||||||
let inner = toolchains_for_version(
|
let inner = InstalledToolchains::from_settings()
|
||||||
&PythonVersion::from_str(python_version)
|
.map(|toolchains| {
|
||||||
.expect("Tests should use a valid Python version"),
|
toolchains
|
||||||
)
|
.find_version(
|
||||||
.expect("Tests are run on a supported platform")
|
&PythonVersion::from_str(python_version)
|
||||||
.iter()
|
.expect("Tests should use a valid Python version"),
|
||||||
.map(|toolchain| {
|
)
|
||||||
toolchain
|
.expect("Tests are run on a supported platform")
|
||||||
.executable()
|
.map(|toolchain| {
|
||||||
.parent()
|
toolchain
|
||||||
.expect("Executables must exist in a directory")
|
.executable()
|
||||||
.to_path_buf()
|
.parent()
|
||||||
})
|
.expect("Executables must exist in a directory")
|
||||||
.collect::<Vec<_>>();
|
.to_path_buf()
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
})
|
||||||
|
.unwrap_or_default();
|
||||||
if inner.is_empty() {
|
if inner.is_empty() {
|
||||||
// Fallback to a system lookup if we failed to find one in the toolchain directory
|
// Fallback to a system lookup if we failed to find one in the toolchain directory
|
||||||
let request = InterpreterRequest::Version(
|
let request = InterpreterRequest::Version(
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue