Skip to main content

simics_python_utils/
environment.rs

1// Copyright (C) 2024 Intel Corporation
2// SPDX-License-Identifier: Apache-2.0
3
4use crate::version::PythonVersion;
5use anyhow::{anyhow, Result};
6use std::path::PathBuf;
7
8/// Source of the Python environment
9#[derive(Debug, Clone, PartialEq, Eq)]
10pub enum PackageSource {
11    /// Found in traditional Simics base package (1000)
12    Traditional,
13    /// Found in separate Simics Python package (1033), used in Simics 7.28.0+
14    SeparatePackage,
15}
16
17/// Complete Python environment information for Simics
18#[derive(Debug, Clone)]
19pub struct PythonEnvironment {
20    /// Path to mini-python executable
21    pub mini_python: PathBuf,
22    /// Path to Python include directory (contains python3.X subdirectory)
23    pub include_dir: PathBuf,
24    /// Include flag for C compilation (e.g., "-I/path/to/include/python3.9")
25    pub include_flag: String,
26    /// Directory containing libpython*.so files
27    pub lib_dir: PathBuf,
28    /// Full path to the specific libpython*.so file
29    pub lib_path: PathBuf,
30    /// Parsed Python version information
31    pub version: PythonVersion,
32    /// Source package where Python was found
33    pub package_source: PackageSource,
34}
35
36impl PythonEnvironment {
37    /// Create a new Python environment
38    pub fn new(
39        mini_python: PathBuf,
40        include_dir: PathBuf,
41        lib_dir: PathBuf,
42        lib_path: PathBuf,
43        version: PythonVersion,
44        package_source: PackageSource,
45    ) -> Self {
46        let include_flag = format!("-I{}", include_dir.display());
47
48        Self {
49            mini_python,
50            include_dir,
51            include_flag,
52            lib_dir,
53            lib_path,
54            version,
55            package_source,
56        }
57    }
58
59    /// Set the package source for this environment
60    pub fn with_source(mut self, source: PackageSource) -> Self {
61        self.package_source = source;
62        self
63    }
64
65    /// Validate that all required files and directories exist
66    pub fn validate(&self) -> Result<()> {
67        if !self.mini_python.exists() {
68            return Err(anyhow!(
69                "Mini-python executable not found: {}",
70                self.mini_python.display()
71            ));
72        }
73
74        if !self.mini_python.is_file() {
75            return Err(anyhow!(
76                "Mini-python path is not a file: {}",
77                self.mini_python.display()
78            ));
79        }
80
81        if !self.include_dir.exists() {
82            return Err(anyhow!(
83                "Python include directory not found: {}",
84                self.include_dir.display()
85            ));
86        }
87
88        if !self.include_dir.is_dir() {
89            return Err(anyhow!(
90                "Python include path is not a directory: {}",
91                self.include_dir.display()
92            ));
93        }
94
95        if !self.lib_dir.exists() {
96            return Err(anyhow!(
97                "Python library directory not found: {}",
98                self.lib_dir.display()
99            ));
100        }
101
102        if !self.lib_dir.is_dir() {
103            return Err(anyhow!(
104                "Python library path is not a directory: {}",
105                self.lib_dir.display()
106            ));
107        }
108
109        if !self.lib_path.exists() {
110            return Err(anyhow!(
111                "Python library file not found: {}",
112                self.lib_path.display()
113            ));
114        }
115
116        if !self.lib_path.is_file() {
117            return Err(anyhow!(
118                "Python library path is not a file: {}",
119                self.lib_path.display()
120            ));
121        }
122
123        Ok(())
124    }
125
126    /// Get the Python major version as string
127    pub fn major_version_str(&self) -> String {
128        self.version.major.to_string()
129    }
130
131    /// Get the Python minor version as string
132    pub fn minor_version_str(&self) -> String {
133        self.version.minor.to_string()
134    }
135
136    /// Get the Py_LIMITED_API define for C compilation
137    pub fn py_limited_api_define(&self) -> String {
138        format!("-DPy_LIMITED_API={}", self.version.py_limited_api_hex())
139    }
140
141    /// Get the library file name (without directory)
142    pub fn lib_filename(&self) -> Result<String> {
143        self.lib_path
144            .file_name()
145            .and_then(|name| name.to_str())
146            .map(|s| s.to_string())
147            .ok_or_else(|| {
148                anyhow!(
149                    "Failed to get library filename from {}",
150                    self.lib_path.display()
151                )
152            })
153    }
154}
155
156impl std::fmt::Display for PythonEnvironment {
157    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
158        write!(
159            f,
160            "PythonEnvironment {{ version: {}, source: {:?}, mini_python: {}, include: {}, lib: {} }}",
161            self.version,
162            self.package_source,
163            self.mini_python.display(),
164            self.include_dir.display(),
165            self.lib_path.display()
166        )
167    }
168}