1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
//! Takes a wheel and installs it, either in a venv or for monotrail

use std::fs::File;
// The pub ones are reused by monotrail
pub use install_location::{normalize_name, InstallLocation, LockedDir};
use platform_info::PlatformInfoError;
use std::io;
use std::path::Path;
use std::str::FromStr;
use thiserror::Error;
pub use wheel::{
    get_script_launcher, install_wheel, parse_key_value_file, read_record_file, relative_to,
    Script, MONOTRAIL_SCRIPT_SHEBANG,
};
pub use wheel_tags::{Arch, CompatibleTags, Os, WheelFilename};
use zip::result::ZipError;

mod install_location;
#[cfg(feature = "python_bindings")]
mod python_bindings;
mod wheel;
mod wheel_tags;

#[derive(Error, Debug)]
pub enum Error {
    #[error(transparent)]
    IO(#[from] io::Error),
    /// This shouldn't actually be possible to occur
    #[error("Failed to serialize direct_url.json ಠ_ಠ")]
    DirectUrlSerdeJson(#[source] serde_json::Error),
    /// Tags/metadata didn't match platform
    #[error("The wheel is incompatible with the current platform {os} {arch}")]
    IncompatibleWheel { os: Os, arch: Arch },
    /// The wheel is broken
    #[error("The wheel is invalid: {0}")]
    InvalidWheel(String),
    /// pyproject.toml or poetry.lock are broken
    #[error("The poetry dependency specification (pyproject.toml or poetry.lock) is broken (try `poetry update`?): {0}")]
    InvalidPoetry(String),
    /// Doesn't follow file name schema
    #[error("The wheel filename \"{0}\" is invalid: {1}")]
    InvalidWheelFileName(String, String),
    #[error("Failed to read the wheel file {0}")]
    Zip(String, #[source] ZipError),
    #[error("Failed to run python subcommand")]
    PythonSubcommand(#[source] io::Error),
    #[error("Failed to move data files")]
    WalkDir(#[from] walkdir::Error),
    #[error("RECORD file doesn't match wheel contents: {0}")]
    RecordFile(String),
    #[error("RECORD file is invalid")]
    RecordCsv(#[from] csv::Error),
    #[error("Broken virtualenv: {0}")]
    BrokenVenv(String),
    #[error("Failed to detect the operating system version: {0}")]
    OsVersionDetection(String),
    #[error("Failed to detect the current platform")]
    PlatformInfo(#[source] PlatformInfoError),
    #[error("Invalid version specification, only none or == is supported")]
    Pep440,
}

/// High level API: Install a wheel in a virtualenv
///
/// Returns the tag of the wheel
pub fn install_wheel_in_venv(
    wheel: &Path,
    venv: &Path,
    interpreter: &Path,
    major: u8,
    minor: u8,
) -> Result<String, Error> {
    let venv_base = venv.canonicalize()?;
    let location = InstallLocation::Venv {
        venv_base,
        python_version: (major, minor),
    };
    let locked_dir = location.acquire_lock()?;

    let filename = wheel
        .file_name()
        .ok_or_else(|| Error::InvalidWheel("Expected a file".to_string()))?
        .to_string_lossy();
    let filename = WheelFilename::from_str(&filename)?;
    let compatible_tags = CompatibleTags::current(location.get_python_version())?;
    filename.compatibility(&compatible_tags)?;

    install_wheel(
        &locked_dir,
        File::open(wheel)?,
        filename,
        false,
        &[],
        // Only relevant for monotrail style installation
        "",
        interpreter,
    )
}