virtualenv_rs/
packages.rs

1use crate::bare::VenvPaths;
2use crate::interpreter::InterpreterInfo;
3use crate::{crate_cache_dir, Error};
4use camino::{FromPathBufError, Utf8Path, Utf8PathBuf};
5use fs_err as fs;
6use fs_err::File;
7use install_wheel_rs::{install_wheel, InstallLocation, WheelFilename};
8#[cfg(feature = "parallel")]
9use rayon::iter::{IntoParallelIterator, ParallelIterator};
10use std::io;
11use std::io::BufWriter;
12use std::str::FromStr;
13use tempfile::NamedTempFile;
14use tracing::info;
15
16pub fn download_wheel_cached(filename: &str, url: &str) -> Result<Utf8PathBuf, Error> {
17    let wheels_cache = crate_cache_dir()?.join("wheels");
18    let cached_wheel = wheels_cache.join(filename);
19    if cached_wheel.is_file() {
20        info!("Using cached wheel at {cached_wheel}");
21        return Ok(cached_wheel);
22    }
23
24    info!("Downloading wheel from {url} to {cached_wheel}");
25    fs::create_dir_all(&wheels_cache)?;
26    let mut tempfile = NamedTempFile::new_in(wheels_cache)?;
27    let tempfile_path: Utf8PathBuf = tempfile
28        .path()
29        .to_path_buf()
30        .try_into()
31        .map_err(|err: FromPathBufError| err.into_io_error())?;
32    let mut response = minreq::get(url).send_lazy()?;
33    io::copy(&mut response, &mut BufWriter::new(&mut tempfile)).map_err(|err| {
34        Error::WheelDownload {
35            url: url.to_string(),
36            path: tempfile_path.to_path_buf(),
37            err,
38        }
39    })?;
40    tempfile.persist(&cached_wheel)?;
41    Ok(cached_wheel)
42}
43
44/// Install pip, setuptools and wheel from cache pypi with atm fixed wheels
45pub fn install_base_packages(
46    location: &Utf8Path,
47    info: &InterpreterInfo,
48    paths: &VenvPaths,
49) -> Result<(), Error> {
50    let install_location = InstallLocation::Venv {
51        venv_base: location.canonicalize()?,
52        python_version: (info.major, info.minor),
53    };
54    let install_location = install_location.acquire_lock()?;
55
56    // TODO: Use the json api instead
57    // TODO: Only check the json API so often (monthly? daily?)
58    let packages = [
59        ("pip-23.2.1-py3-none-any.whl", "https://files.pythonhosted.org/packages/50/c2/e06851e8cc28dcad7c155f4753da8833ac06a5c704c109313b8d5a62968a/pip-23.2.1-py3-none-any.whl"),
60        ("setuptools-68.2.2-py3-none-any.whl", "https://files.pythonhosted.org/packages/bb/26/7945080113158354380a12ce26873dd6c1ebd88d47f5bc24e2c5bb38c16a/setuptools-68.2.2-py3-none-any.whl"),
61        ("wheel-0.41.2-py3-none-any.whl", "https://files.pythonhosted.org/packages/b8/8b/31273bf66016be6ad22bb7345c37ff350276cfd46e389a0c2ac5da9d9073/wheel-0.41.2-py3-none-any.whl"),
62    ];
63    #[cfg(feature = "rayon")]
64    let iterator = packages.into_par_iter();
65    #[cfg(not(feature = "rayon"))]
66    let iterator = packages.into_iter();
67    iterator
68        .map(|(filename, url)| {
69            let wheel_file = download_wheel_cached(filename, url)?;
70            let parsed_filename = WheelFilename::from_str(filename).unwrap();
71            install_wheel(
72                &install_location,
73                File::open(wheel_file)?,
74                parsed_filename,
75                false,
76                &[],
77                // Only relevant for monotrail style installation
78                "",
79                paths.interpreter.as_std_path(),
80            )
81            .map_err(|err| Error::InstallWheel {
82                package: filename.to_string(),
83                err,
84            })?;
85            Ok(())
86        })
87        .collect::<Result<Vec<()>, Error>>()?;
88    Ok(())
89}