miden-debug 0.9.0

An interactive debugger for Miden VM programs
Documentation
use std::{
    env,
    path::{Path, PathBuf},
    process::Command,
};

fn main() {
    println!("cargo:rerun-if-env-changed=PYO3_PYTHON");
    println!("cargo:rerun-if-env-changed=PYO3_CONFIG_FILE");
    println!("cargo:rerun-if-env-changed=PYO3_NO_PYTHON");

    if env::var_os("CARGO_FEATURE_PYTHON").is_none() {
        return;
    }

    pyo3_build_config::use_pyo3_cfgs();
    pyo3_build_config::add_libpython_rpath_link_args();
    pyo3_build_config::add_python_framework_link_args();

    validate_python();
}

fn validate_python() {
    let config = pyo3_build_config::get();

    if config.implementation != pyo3_build_config::PythonImplementation::CPython {
        panic!(
            "miden-debug Python scripting embeds CPython, but PyO3 selected {} {}. Set \
             PYO3_PYTHON to a CPython executable, or disable the `python` feature.",
            config.implementation, config.version
        );
    }

    if !config.shared {
        panic!(
            "miden-debug Python scripting requires a shared libpython, but PyO3 selected a static \
             Python {} configuration. Set PYO3_PYTHON to an interpreter with a shared libpython, \
             or provide PYO3_CONFIG_FILE with shared=true.",
            config.version
        );
    }

    if let Some(executable) = config.executable.as_deref() {
        validate_python_executable(executable, &config.version.to_string());
    } else {
        println!(
            "cargo:warning=Python scripting is using PYO3_CONFIG_FILE/cross-compile metadata; no \
             host Python executable was available for an import smoke test"
        );
    }

    match (config.python_framework_prefix.as_deref(), config.lib_dir.as_deref()) {
        (Some(prefix), _) => validate_framework_prefix(prefix),
        (None, Some(lib_dir)) => validate_lib_dir(lib_dir),
        (None, None) => panic!(
            "miden-debug Python scripting requires a Python library directory or framework \
             prefix, but PyO3 did not report one. Set PYO3_PYTHON or PYO3_CONFIG_FILE to a \
             complete Python development installation."
        ),
    }
}

fn validate_python_executable(executable: &str, expected_version: &str) {
    let output = Command::new(executable)
        .arg("-c")
        .arg(
            "import ctypes, sys; version=f'{sys.version_info[0]}.{sys.version_info[1]}'; assert \
             version == __import__('os').environ['MIDEN_DEBUG_EXPECTED_PYTHON_VERSION']; \
             ctypes.pythonapi.Py_IsInitialized",
        )
        .env("MIDEN_DEBUG_EXPECTED_PYTHON_VERSION", expected_version)
        .output()
        .unwrap_or_else(|err| {
            panic!("failed to execute Python interpreter `{executable}` selected by PyO3: {err}")
        });

    if !output.status.success() {
        panic!(
            "Python interpreter `{}` selected by PyO3 failed the embed smoke \
             test.\nstdout:\n{}\nstderr:\n{}",
            executable,
            String::from_utf8_lossy(&output.stdout),
            String::from_utf8_lossy(&output.stderr),
        );
    }
}

fn validate_framework_prefix(prefix: &str) {
    let prefix = Path::new(prefix);
    if !prefix.exists() {
        panic!(
            "PyO3 selected Python framework prefix `{}`, but it does not exist. Set PYO3_PYTHON \
             to a usable framework Python.",
            prefix.display()
        );
    }

    let framework = prefix.join("Python3.framework");
    if framework.exists() {
        return;
    }

    println!(
        "cargo:warning=PyO3 selected Python framework prefix `{}`, but Python3.framework was not \
         found directly under it; continuing because custom framework layouts are possible",
        prefix.display()
    );
}

fn validate_lib_dir(lib_dir: &str) {
    let lib_dir = PathBuf::from(lib_dir);
    if !lib_dir.exists() {
        panic!(
            "PyO3 selected Python library directory `{}`, but it does not exist. Set PYO3_PYTHON \
             to a Python installation with development libraries.",
            lib_dir.display()
        );
    }
}