use std::{
fmt::Debug,
fs::File,
sync::{Arc, Mutex},
};
use crate::{
common::compiler::CompilationResult,
runtimes::native_runtime::{NativeAdditionalData, NativeRuntime},
};
#[cfg(feature = "cython")]
use crate::common::compiler::{check_program_installed, CompilationError};
#[cfg(feature = "cython")]
use super::cpp_compiler::CppCompiler;
use super::{Compiler, IntoArgs};
#[derive(Debug, Clone)]
pub struct PythonCompiler;
pub struct PythonCompilerConfig {
pub python_version: String,
#[cfg(feature = "cython")]
pub use_cython: bool,
#[cfg(feature = "cython")]
pub cpp_config: super::cpp_compiler::CppCompilerConfig,
}
impl Debug for PythonCompilerConfig {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("PythonCompilerConfig")
.field("python_version", &self.python_version)
.finish()
}
}
impl Clone for PythonCompilerConfig {
fn clone(&self) -> Self {
Self {
python_version: self.python_version.clone(),
#[cfg(feature = "cython")]
use_cython: self.use_cython,
#[cfg(feature = "cython")]
cpp_config: self.cpp_config.clone(),
}
}
}
impl Default for PythonCompilerConfig {
fn default() -> Self {
Self {
python_version: "python3".to_string(),
#[cfg(feature = "cython")]
use_cython: false,
#[cfg(feature = "cython")]
cpp_config: super::cpp_compiler::CppCompilerConfig::default(),
}
}
}
impl PythonCompilerConfig {
#[cfg(feature = "cython")]
fn cython_default() -> Self {
Self {
python_version: "python3".to_string(),
use_cython: true,
cpp_config: super::cpp_compiler::CppCompilerConfig::default(),
}
}
}
impl IntoArgs for PythonCompilerConfig {
fn into_args(self) -> Vec<String> {
#[allow(unused_mut)]
let mut args: Vec<String> = Vec::new();
#[cfg(feature = "cython")]
{
if self.use_cython {
args.push("-m".to_string());
args.push("cython".to_string());
}
}
args
}
}
impl Compiler<NativeRuntime> for PythonCompiler {
type Config = PythonCompilerConfig;
#[allow(unused_variables)]
fn compile(
&self,
code: &mut impl std::io::Read,
config: Self::Config,
) -> CompilationResult<super::CompiledCode<NativeRuntime>> {
let temp_dir = tempfile::Builder::new().prefix("exers-").tempdir()?;
let mut code_file = File::create(temp_dir.path().join("code.py"))?;
std::io::copy(code, &mut code_file)?;
#[cfg(feature = "cython")]
{
if config.use_cython {
check_program_installed("cython")?;
let mut command = std::process::Command::new("cython");
command.stderr(std::process::Stdio::piped());
command.stdout(std::process::Stdio::null());
command.stdin(std::process::Stdio::null());
command.current_dir(temp_dir.path());
command.arg("code.py");
command.arg("-3"); command.arg("--cplus"); command.arg("--embed"); command.arg("-o");
command.arg("code.cpp");
let output = command.spawn()?.wait_with_output()?;
if !output.status.success() {
return Err(CompilationError::CompilationFailed(
String::from_utf8_lossy(&output.stderr).to_string(),
));
}
let mut code_stream = File::open(temp_dir.path().join("code.cpp"))?;
let compiled = CppCompiler.compile(&mut code_stream, config.cpp_config)?;
return Ok(compiled);
}
}
Ok(super::CompiledCode {
executable: Some(temp_dir.path().join("code.py")),
temp_dir_handle: Arc::new(Mutex::new(Some(temp_dir))),
additional_data: NativeAdditionalData {
program: Some(config.python_version.clone()),
},
runtime_marker: std::marker::PhantomData,
})
}
}
#[cfg(test)]
mod tests {
use crate::{
compilers::Compiler,
runtimes::{native_runtime::NativeRuntime, CodeRuntime},
};
#[test]
fn test_python_compile_native_python3() {
let code = r#"
print("Hello, world!", end="")
"#;
let compiled = super::PythonCompiler
.compile(&mut code.as_bytes(), Default::default())
.unwrap();
let result = NativeRuntime.run(&compiled, Default::default()).unwrap();
assert_eq!(result.stdout, Some("Hello, world!".to_string()));
}
#[cfg(feature = "cython")]
#[test]
fn test_python_compile_native_cython() {
use crate::compilers::python_compiler::PythonCompilerConfig;
let code = r#"
print("Hello, world!", end="")
"#;
let compiled = super::PythonCompiler
.compile(&mut code.as_bytes(), PythonCompilerConfig::cython_default())
.unwrap();
let result = NativeRuntime.run(&compiled, Default::default()).unwrap();
assert_eq!(result.stdout, Some("Hello, world!".to_string()));
}
}