File: mod.rs

package info (click to toggle)
python-maturin 1.9.4-3
  • links: PTS, VCS
  • area: main
  • in suites: forky
  • size: 4,540 kB
  • sloc: python: 656; javascript: 93; sh: 55; makefile: 10
file content (224 lines) | stat: -rw-r--r-- 7,010 bytes parent folder | download | duplicates (2)
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
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
use anyhow::{bail, Result};
use fs_err as fs;
use maturin::Target;
use normpath::PathExt as _;
use std::path::Path;
use std::path::PathBuf;
use std::process::{Command, Stdio};
use std::{env, io, str};

pub mod develop;
pub mod errors;
pub mod integration;
pub mod metadata;
pub mod other;

#[derive(Clone, Copy, PartialEq, Eq)]
pub enum TestInstallBackend {
    Pip,
    Uv,
}

/// Check that the package is either not installed or works correctly
pub fn check_installed(package: &Path, python: &Path) -> Result<()> {
    let path = if cfg!(windows) {
        // on Windows, also add Scripts to PATH
        let python_dir = python.parent().unwrap();
        env::join_paths([&python_dir.join("Scripts"), python_dir])?.into()
    } else {
        python.parent().unwrap().to_path_buf()
    };
    let mut check_installed = Path::new(package)
        .join("check_installed")
        .join("check_installed.py");
    if !check_installed.is_file() {
        check_installed = Path::new(package)
            .parent()
            .unwrap()
            .join("check_installed")
            .join("check_installed.py");
    }
    let output = Command::new(python)
        .arg(check_installed)
        .env("PATH", path)
        .output()
        .unwrap();
    if !output.status.success() {
        bail!(
            "Check install fail: {} \n--- Stdout:\n{}\n--- Stderr:\n{}",
            output.status,
            str::from_utf8(&output.stdout)?,
            str::from_utf8(&output.stderr)?
        );
    }

    let message = str::from_utf8(&output.stdout).unwrap().trim();

    if message != "SUCCESS" {
        panic!("Not SUCCESS: {message}");
    }

    Ok(())
}

/// Replaces the real cargo with cargo-mock if the mock crate has been compiled
///
/// If the mock crate hasn't been compile this does nothing
pub fn maybe_mock_cargo() {
    // libtest spawns multiple threads to run the tests in parallel, but all of those threads share
    // the same environment variables, so this uses the also global stdout lock to
    // make this region exclusive
    let stdout = io::stdout();
    let handle = stdout.lock();
    let mock_cargo_path = PathBuf::from("test-crates/cargo-mock/target/release/");
    if mock_cargo_path.join("cargo").is_file() || mock_cargo_path.join("cargo.exe").is_file() {
        let old_path = env::var_os("PATH").expect("PATH must be set");
        let mut path_split: Vec<PathBuf> = env::split_paths(&old_path).collect();
        // Another thread might have already modified the path
        if mock_cargo_path != path_split[0] {
            path_split.insert(0, mock_cargo_path);
            let new_path =
                env::join_paths(path_split).expect("Expected to be able to re-join PATH");
            env::set_var("PATH", new_path);
        }
    }
    drop(handle);
}

/// Better error formatting
#[track_caller]
pub fn handle_result<T>(result: Result<T>) -> T {
    match result {
        Err(e) => {
            for cause in e.chain().rev() {
                eprintln!("Cause: {cause}");
            }
            panic!("{}", e);
        }
        Ok(result) => result,
    }
}

/// Get Python implementation
pub fn get_python_implementation(python_interp: &Path) -> Result<String> {
    let code = "import sys; print(sys.implementation.name, end='')";
    let output = Command::new(python_interp).arg("-c").arg(code).output()?;
    let python_impl = String::from_utf8(output.stdout).unwrap();
    Ok(python_impl)
}

/// Get the current tested Python implementation
pub fn test_python_implementation() -> Result<String> {
    let python = test_python_path().map(PathBuf::from).unwrap_or_else(|| {
        let target = Target::from_target_triple(None).unwrap();
        target.get_python()
    });
    get_python_implementation(&python)
}

/// Create virtualenv
pub fn create_virtualenv(name: &str, python_interp: Option<PathBuf>) -> Result<(PathBuf, PathBuf)> {
    let interp = python_interp.or_else(|| test_python_path().map(PathBuf::from));
    let venv_interp = interp.clone().unwrap_or_else(|| {
        let target = Target::from_target_triple(None).unwrap();
        target.get_python()
    });
    let venv_name = match get_python_implementation(&venv_interp) {
        Ok(python_impl) => format!("{name}-{python_impl}"),
        Err(_) => name.to_string(),
    };

    let venv_dir = create_named_virtualenv(&venv_name, interp)?;

    let target = Target::from_target_triple(None)?;
    let python = target.get_venv_python(&venv_dir);
    Ok((venv_dir, python))
}

pub fn create_named_virtualenv(venv_name: &str, interp: Option<PathBuf>) -> Result<PathBuf> {
    let venv_dir = PathBuf::from("test-crates")
        .normalize()?
        .into_path_buf()
        .join("venvs")
        .join(venv_name);

    if venv_dir.is_dir() {
        fs::remove_dir_all(&venv_dir)?;
    }

    let mut cmd = {
        if let Ok(uv) = which::which("uv") {
            let mut cmd = Command::new(uv);
            cmd.args(["venv", "--seed"]);
            cmd
        } else {
            Command::new("virtualenv")
        }
    };
    if let Some(interp) = interp {
        cmd.arg("-p").arg(interp);
    }
    let output = cmd
        .arg(dunce::simplified(&venv_dir))
        .stderr(Stdio::inherit())
        .output()
        .expect("Failed to create a virtualenv");
    if !output.status.success() {
        panic!(
            "Failed to run virtualenv: {}\n---stdout:\n{}---stderr:\n{}",
            output.status,
            str::from_utf8(&output.stdout)?,
            str::from_utf8(&output.stderr)?
        );
    }
    Ok(venv_dir)
}

/// Creates conda environments
pub fn create_conda_env(name: &str, major: usize, minor: usize) -> Result<(PathBuf, PathBuf)> {
    use serde::Deserialize;

    #[derive(Deserialize)]
    struct CondaCreateResult {
        prefix: PathBuf,
        success: bool,
    }

    let mut cmd = if cfg!(windows) {
        let mut cmd = Command::new("cmd.exe");
        cmd.arg("/c").arg("conda");
        cmd
    } else {
        Command::new("conda")
    };
    let output = cmd
        .arg("create")
        .arg("-n")
        .arg(name)
        .arg(format!("python={major}.{minor}"))
        .arg("-q")
        .arg("-y")
        .arg("--json")
        .output()
        .expect("Conda not available.");
    if !output.status.success() {
        panic!(
            "Failed to create conda environment: {}\n---stdout:\n{}---stderr:\n{}",
            output.status,
            str::from_utf8(&output.stdout)?,
            str::from_utf8(&output.stderr)?
        );
    }
    let result: CondaCreateResult = serde_json::from_slice(&output.stdout)?;
    if !result.success {
        bail!("Failed to create conda environment {}.", name);
    }
    let target = Target::from_target_triple(None)?;
    let python = target.get_venv_python(&result.prefix);
    Ok((result.prefix, python))
}

/// Path to the python interpreter for testing
pub fn test_python_path() -> Option<String> {
    env::var("MATURIN_TEST_PYTHON").ok()
}