mirror of https://github.com/astral-sh/uv
93 lines
3.0 KiB
Rust
93 lines
3.0 KiB
Rust
use std::path::Path;
|
|
use std::str::FromStr;
|
|
|
|
use anyhow::Result;
|
|
use async_std::fs::File;
|
|
use install_wheel_rs::{install_wheel, InstallLocation};
|
|
use tracing::debug;
|
|
use url::Url;
|
|
|
|
use puffin_client::PypiClientBuilder;
|
|
use puffin_interpreter::PythonExecutable;
|
|
use puffin_platform::tags::Tags;
|
|
use puffin_platform::Platform;
|
|
use puffin_resolve::resolve;
|
|
|
|
use crate::commands::ExitStatus;
|
|
|
|
/// Install a set of requirements into the current Python environment.
|
|
pub(crate) async fn install(src: &Path, cache: Option<&Path>) -> Result<ExitStatus> {
|
|
// Read the `requirements.txt` from disk.
|
|
let requirements_txt = std::fs::read_to_string(src)?;
|
|
|
|
// Parse the `requirements.txt` into a list of requirements.
|
|
let requirements = puffin_package::requirements::Requirements::from_str(&requirements_txt)?;
|
|
|
|
// Detect the current Python interpreter.
|
|
let platform = Platform::current()?;
|
|
let python = PythonExecutable::from_env(&platform)?;
|
|
debug!(
|
|
"Using Python interpreter: {}",
|
|
python.executable().display()
|
|
);
|
|
|
|
// Determine the current environment markers.
|
|
let markers = python.markers();
|
|
|
|
// Determine the compatible platform tags.
|
|
let tags = Tags::from_env(&platform, python.version())?;
|
|
|
|
// Instantiate a client.
|
|
let client = {
|
|
let mut pypi_client = PypiClientBuilder::default();
|
|
if let Some(cache) = cache {
|
|
pypi_client = pypi_client.cache(cache);
|
|
}
|
|
pypi_client.build()
|
|
};
|
|
|
|
// Resolve the dependencies.
|
|
// TODO(charlie): When installing, assume `--no-deps`.
|
|
let resolution = resolve(&requirements, markers, &tags, &client).await?;
|
|
|
|
// Create a temporary directory, in which we'll store the wheels.
|
|
let tmp_dir = tempfile::tempdir()?;
|
|
|
|
// Download each wheel.
|
|
// TODO(charlie): Store these in a content-addressed cache.
|
|
// TODO(charlie): Use channels to efficiently stream-and-install.
|
|
for (name, package) in resolution.iter() {
|
|
let url = Url::parse(package.url())?;
|
|
let reader = client.stream_external(&url).await?;
|
|
|
|
// TODO(charlie): Stream the unzip.
|
|
let mut writer = File::create(tmp_dir.path().join(format!("{name}.whl"))).await?;
|
|
async_std::io::copy(reader, &mut writer).await?;
|
|
}
|
|
|
|
// Install each wheel.
|
|
// TODO(charlie): Use channels to efficiently stream-and-install.
|
|
let location = InstallLocation::Venv {
|
|
venv_base: python.venv().to_path_buf(),
|
|
python_version: python.simple_version(),
|
|
};
|
|
let locked_dir = location.acquire_lock()?;
|
|
for (name, package) in resolution.iter() {
|
|
let path = tmp_dir.path().join(format!("{name}.whl"));
|
|
let filename = install_wheel_rs::WheelFilename::from_str(package.filename())?;
|
|
|
|
// TODO(charlie): Should this be async?
|
|
install_wheel(
|
|
&locked_dir,
|
|
std::fs::File::open(path)?,
|
|
filename,
|
|
false,
|
|
&[],
|
|
"",
|
|
python.executable(),
|
|
)?;
|
|
}
|
|
|
|
Ok(ExitStatus::Success)
|
|
}
|