#![allow(unknown_lints)]
#![allow(dead_code)]
#![allow(unused_must_use)]
#![allow(clippy::unnecessary_map_or)]
#![allow(clippy::single_match)]
#![allow(clippy::needless_borrows_for_generic_args)]
#![allow(clippy::useless_format)]
#![allow(clippy::derivable_impls)]
#![allow(clippy::needless_return)]
use anyhow::{Context, Result};
use clap::{Parser, Subcommand};
use env_logger::Env;
use log::{info, warn};
use std::path::{Path, PathBuf};
use py2pyd::{build_tools, compiler, python_env, uv_compiler};
#[derive(Parser)]
#[command(author, version, about, long_about = None)]
struct Cli {
#[arg(short, long, action = clap::ArgAction::Count)]
verbose: u8,
#[arg(long)]
python_path: Option<String>,
#[arg(long)]
python_version: Option<String>,
#[arg(long)]
keep_temp: bool,
#[arg(long, default_value = "true")]
use_uv: bool,
#[arg(long)]
packages: Option<String>,
#[command(subcommand)]
command: Commands,
}
#[derive(Subcommand)]
enum Commands {
Compile {
#[arg(short, long)]
input: PathBuf,
#[arg(short, long)]
output: Option<PathBuf>,
#[arg(short = 'O', long, default_value = "2")]
optimize: u8,
},
Batch {
#[arg(short, long)]
input: String,
#[arg(short, long)]
output: PathBuf,
#[arg(short = 'O', long, default_value = "2")]
optimize: u8,
#[arg(short, long)]
recursive: bool,
},
}
fn main() -> Result<()> {
let cli = Cli::parse();
let env = match cli.verbose {
0 => Env::default().default_filter_or("warn"),
1 => Env::default().default_filter_or("info"),
2 => Env::default().default_filter_or("debug"),
_ => Env::default().default_filter_or("trace"),
};
env_logger::init_from_env(env);
info!("Checking for required build tools...");
let build_tools =
build_tools::check_build_tools().with_context(|| "Failed to check build tools")?;
info!("Build tools found:\n{}", build_tools.get_tools_info());
match &cli.command {
Commands::Compile {
input,
output,
optimize,
} => {
let output = output.clone().unwrap_or_else(|| {
let file_name = input.file_name().unwrap_or_default();
let mut output_path = PathBuf::from(file_name);
if cfg!(windows) {
output_path.set_extension("pyd");
} else {
output_path.set_extension("so");
}
output_path
});
info!("Compiling {} to {}", input.display(), output.display());
info!("Optimization level: {optimize}");
let packages = cli
.packages
.as_ref()
.map(|p| {
p.split(',')
.map(|s| s.trim().to_string())
.collect::<Vec<_>>()
})
.unwrap_or_default();
if cli.use_uv {
let config = uv_compiler::CompileConfig {
python_path: cli.python_path.as_deref().map(PathBuf::from),
python_version: cli.python_version.clone(),
optimize_level: *optimize,
keep_temp_files: cli.keep_temp,
target_dcc: None,
packages,
};
uv_compiler::compile_file(input, &output, &config)
.with_context(|| format!("Failed to compile {}", input.display()))?;
} else {
info!("Initializing Python environment...");
python_env::initialize_python_env(
cli.python_path.as_deref(),
cli.python_version.as_deref(),
)
.with_context(|| "Failed to initialize Python environment")?;
python_env::set_python_env_vars()
.with_context(|| "Failed to set Python environment variables")?;
let python_path =
python_env::get_python_path().with_context(|| "Failed to get Python path")?;
info!("Using Python interpreter: {}", python_path.display());
compile_file(input, &output, *optimize)
.with_context(|| format!("Failed to compile {}", input.display()))?;
if cli.keep_temp {
let venv_path = python_env::get_venv_path()?;
info!("Keeping virtual environment at: {}", venv_path.display());
info!(
"You can activate it with: {}\\Scripts\\activate",
venv_path.display()
);
} else {
info!("Cleaning up temporary virtual environment...");
if let Err(e) = python_env::cleanup_venv() {
warn!("Failed to clean up virtual environment: {e}");
} else {
info!("Virtual environment cleaned up successfully");
}
}
}
info!("Successfully compiled to {}", output.display());
}
Commands::Batch {
input,
output,
optimize,
recursive,
} => {
info!("Batch compiling from {} to {}", input, output.display());
info!("Optimization level: {optimize}");
let packages = cli
.packages
.as_ref()
.map(|p| {
p.split(',')
.map(|s| s.trim().to_string())
.collect::<Vec<_>>()
})
.unwrap_or_default();
if cli.use_uv {
let config = uv_compiler::CompileConfig {
python_path: cli.python_path.as_deref().map(PathBuf::from),
python_version: cli.python_version.clone(),
optimize_level: *optimize,
keep_temp_files: cli.keep_temp,
target_dcc: None,
packages,
};
uv_compiler::batch_compile(input, output, &config, *recursive)
.with_context(|| "Failed to batch compile")?;
} else {
info!("Initializing Python environment...");
python_env::initialize_python_env(
cli.python_path.as_deref(),
cli.python_version.as_deref(),
)
.with_context(|| "Failed to initialize Python environment")?;
python_env::set_python_env_vars()
.with_context(|| "Failed to set Python environment variables")?;
let python_path =
python_env::get_python_path().with_context(|| "Failed to get Python path")?;
info!("Using Python interpreter: {}", python_path.display());
batch_compile(input, output, *optimize, *recursive)
.with_context(|| "Failed to batch compile")?;
if cli.keep_temp {
let venv_path = python_env::get_venv_path()?;
info!("Keeping virtual environment at: {}", venv_path.display());
info!(
"You can activate it with: {}\\Scripts\\activate",
venv_path.display()
);
} else {
info!("Cleaning up temporary virtual environment...");
if let Err(e) = python_env::cleanup_venv() {
warn!("Failed to clean up virtual environment: {e}");
} else {
info!("Virtual environment cleaned up successfully");
}
}
}
info!("Successfully batch compiled");
}
}
Ok(())
}
fn compile_file(input: &Path, output: &Path, optimize: u8) -> Result<()> {
compiler::compile_file(input, output, "generic", optimize)
}
fn batch_compile(
input_pattern: &str,
output_dir: &Path,
optimize: u8,
recursive: bool,
) -> Result<()> {
compiler::batch_compile(input_pattern, output_dir, "generic", optimize, recursive)
}