use std::path::PathBuf;
use anyhow::Result;
use tracing::{info, warn};
use crate::cli::Target;
use crate::report::{FileReport, MigrationReport, ReportSeverity};
use crate::targets;
use crate::walker;
#[derive(Debug, Clone)]
pub struct MigrateOptions {
pub input: PathBuf,
pub output: PathBuf,
pub target: Target,
pub dry_run: bool,
pub verbose: bool,
pub filter: Option<String>,
}
pub fn run(opts: MigrateOptions) -> Result<MigrationReport> {
let files = walker::collect_cuda_files(&opts.input, opts.filter.as_deref())?;
if files.is_empty() {
anyhow::bail!(
"no .cu/.cuh files found at {}",
opts.input.display()
);
}
info!("found {} CUDA file(s)", files.len());
let targets_to_run: Vec<Target> = match opts.target {
Target::All => Target::iter_real().to_vec(),
t => vec![t],
};
let target_dirs: Vec<PathBuf> = targets_to_run
.iter()
.map(|t| opts.output.join(t.as_str()))
.collect();
if !opts.dry_run {
for d in &target_dirs {
std::fs::create_dir_all(d)?;
}
}
let mut report = MigrationReport::new(opts.input.clone(), opts.output.clone());
for file in &files {
if opts.verbose {
info!("parsing {}", file.display());
}
let unit = match crate::parser::translate_path(file) {
Ok(u) => u,
Err(e) => {
warn!("parse error in {}: {e}", file.display());
report.push(FileReport {
source: file.clone(),
outputs: vec![],
warnings: vec![(ReportSeverity::Error, format!("parse error: {e}"))],
});
continue;
}
};
let mut outputs = Vec::new();
for t in &targets_to_run {
let backend = targets::for_target(*t);
let text = backend.emit(&unit);
let out = walker::map_output_path(
&root_dir(&opts.input),
&opts.output,
t.as_str(),
file,
);
let warnings = collect_warnings(&unit, *t);
if !opts.dry_run {
walker::ensure_parent_dir(&out)?;
std::fs::write(&out, text)?;
if opts.verbose {
info!("wrote {}", out.display());
}
}
outputs.push((t.as_str().to_string(), out));
for w in warnings {
report.record_warning(&unit.path, t.as_str(), w);
}
}
for n in &unit.nodes {
if let crate::ir::IrNode::RuntimeCall { name, mappings, .. } = n {
for t in &targets_to_run {
if mappings[crate::ir::slot_index(*t)].is_none() {
report.record_warning(
&unit.path,
t.as_str(),
format!(
"no automatic mapping for CUDA API `{name}`; left as TODO"
),
);
}
}
}
}
report.push(FileReport {
source: file.clone(),
outputs,
warnings: Vec::new(),
});
}
report.report_path = opts
.output
.join(format!("migration-report.json.{}", report.started_at));
if !opts.dry_run {
crate::report::write_json_report(&report, &report.report_path)?;
}
Ok(report)
}
fn root_dir(input: &std::path::Path) -> std::path::PathBuf {
if input.is_dir() {
input.to_path_buf()
} else {
input
.parent()
.map(std::path::Path::to_path_buf)
.unwrap_or_else(|| std::path::PathBuf::from("."))
}
}
fn collect_warnings(unit: &crate::ir::TranslationUnit, _target: Target) -> Vec<String> {
let mut out = Vec::new();
for n in &unit.nodes {
if let crate::ir::IrNode::Warning { message, .. } = n {
out.push(message.clone());
}
if let crate::ir::IrNode::QualifierDecl { qualifier, .. } = n {
use crate::ir::CudaQualifier::*;
match qualifier {
LaunchBounds | ClusterDim | GridDim => out.push(format!(
"qualifier {qualifier:?} is not auto-translated; manual review needed"
)),
_ => {}
}
}
}
out
}