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 bundled Simics base package (1000)
12    Bundled,
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    /// Directory containing python3.lib import library (Windows).
31    /// On Unix, this mirrors `lib_dir` and is unused.
32    pub import_lib_dir: PathBuf,
33    /// Parsed Python version information
34    pub version: PythonVersion,
35    /// Source package where Python was found
36    pub package_source: PackageSource,
37}
38
39impl PythonEnvironment {
40    /// Create a new Python environment
41    pub fn new(
42        mini_python: PathBuf,
43        include_dir: PathBuf,
44        lib_dir: PathBuf,
45        lib_path: PathBuf,
46        import_lib_dir: PathBuf,
47        version: PythonVersion,
48        package_source: PackageSource,
49    ) -> Self {
50        let include_flag = format!("-I{}", include_dir.display());
51
52        Self {
53            mini_python,
54            include_dir,
55            include_flag,
56            lib_dir,
57            lib_path,
58            import_lib_dir,
59            version,
60            package_source,
61        }
62    }
63
64    /// Set the package source for this environment
65    pub fn with_source(mut self, source: PackageSource) -> Self {
66        self.package_source = source;
67        self
68    }
69
70    /// Validate that all required files and directories exist
71    pub fn validate(&self) -> Result<()> {
72        if !self.mini_python.exists() {
73            return Err(anyhow!(
74                "Mini-python executable not found: {}",
75                self.mini_python.display()
76            ));
77        }
78
79        if !self.mini_python.is_file() {
80            return Err(anyhow!(
81                "Mini-python path is not a file: {}",
82                self.mini_python.display()
83            ));
84        }
85
86        if !self.include_dir.exists() {
87            return Err(anyhow!(
88                "Python include directory not found: {}",
89                self.include_dir.display()
90            ));
91        }
92
93        if !self.include_dir.is_dir() {
94            return Err(anyhow!(
95                "Python include path is not a directory: {}",
96                self.include_dir.display()
97            ));
98        }
99
100        if !self.lib_dir.exists() {
101            return Err(anyhow!(
102                "Python library directory not found: {}",
103                self.lib_dir.display()
104            ));
105        }
106
107        if !self.lib_dir.is_dir() {
108            return Err(anyhow!(
109                "Python library path is not a directory: {}",
110                self.lib_dir.display()
111            ));
112        }
113
114        if !self.lib_path.exists() {
115            return Err(anyhow!(
116                "Python library file not found: {}",
117                self.lib_path.display()
118            ));
119        }
120
121        if !self.lib_path.is_file() {
122            return Err(anyhow!(
123                "Python library path is not a file: {}",
124                self.lib_path.display()
125            ));
126        }
127
128        #[cfg(windows)]
129        {
130            if !self.import_lib_dir.exists() {
131                return Err(anyhow!(
132                    "Python import library directory not found: {}",
133                    self.import_lib_dir.display()
134                ));
135            }
136
137            if !self.import_lib_dir.is_dir() {
138                return Err(anyhow!(
139                    "Python import library path is not a directory: {}",
140                    self.import_lib_dir.display()
141                ));
142            }
143        }
144
145        Ok(())
146    }
147
148    /// Get the Python major version as string
149    pub fn major_version_str(&self) -> String {
150        self.version.major.to_string()
151    }
152
153    /// Get the Python minor version as string
154    pub fn minor_version_str(&self) -> String {
155        self.version.minor.to_string()
156    }
157
158    /// Get the Py_LIMITED_API define for C compilation
159    pub fn py_limited_api_define(&self) -> String {
160        format!("-DPy_LIMITED_API={}", self.version.py_limited_api_hex())
161    }
162
163    /// Get the path to the python3.lib import library (Windows)
164    pub fn import_lib_path(&self) -> PathBuf {
165        self.import_lib_dir.join("python3.lib")
166    }
167
168    /// Get the library file name (without directory)
169    pub fn lib_filename(&self) -> Result<String> {
170        self.lib_path
171            .file_name()
172            .and_then(|name| name.to_str())
173            .map(|s| s.to_string())
174            .ok_or_else(|| {
175                anyhow!(
176                    "Failed to get library filename from {}",
177                    self.lib_path.display()
178                )
179            })
180    }
181}
182
183impl std::fmt::Display for PythonEnvironment {
184    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
185        write!(
186            f,
187            "PythonEnvironment {{ version: {}, source: {:?}, mini_python: {}, include: {}, lib: {}, import_lib: {} }}",
188            self.version,
189            self.package_source,
190            self.mini_python.display(),
191            self.include_dir.display(),
192            self.lib_path.display(),
193            self.import_lib_dir.display()
194        )
195    }
196}