use std::path::Path;
use anyhow::{Context, bail};
use toml::Value;
use crate::model::{InputKind, RustMir2Error, TargetProject};
fn read_manifest(manifest_path: &Path) -> anyhow::Result<Value> {
let content = std::fs::read_to_string(manifest_path)
.with_context(|| format!("failed to read manifest `{}`", manifest_path.display()))?;
toml::from_str(&content)
.with_context(|| format!("failed to parse manifest `{}`", manifest_path.display()))
}
fn package_name_from_manifest(manifest: &Value) -> Option<String> {
manifest
.get("package")
.and_then(Value::as_table)
.and_then(|package| package.get("name"))
.and_then(Value::as_str)
.map(ToOwned::to_owned)
}
fn workspace_members_from_manifest(manifest: &Value) -> Vec<String> {
manifest
.get("workspace")
.and_then(Value::as_table)
.and_then(|workspace| workspace.get("members"))
.and_then(Value::as_array)
.into_iter()
.flatten()
.filter_map(Value::as_str)
.map(ToOwned::to_owned)
.collect()
}
pub fn resolve_target_project(input_path: &Path) -> Result<TargetProject, RustMir2Error> {
try_resolve_target_project(input_path).map_err(Into::into)
}
pub(crate) fn try_resolve_target_project(input_path: &Path) -> anyhow::Result<TargetProject> {
let canonical = input_path.canonicalize().with_context(|| {
format!(
"failed to canonicalize input path `{}`",
input_path.display()
)
})?;
let manifest_path = if canonical.is_file() {
if canonical
.file_name()
.is_some_and(|name| name == "Cargo.toml")
{
canonical.clone()
} else {
bail!("input file `{}` is not a Cargo.toml", canonical.display());
}
} else {
canonical.join("Cargo.toml")
};
if !manifest_path.is_file() {
bail!("no Cargo.toml found at `{}`", manifest_path.display());
}
let manifest = read_manifest(&manifest_path)?;
let manifest_dir = manifest_path
.parent()
.context("manifest path had no parent directory")?;
let package_name = package_name_from_manifest(&manifest);
let workspace_members = workspace_members_from_manifest(&manifest);
if package_name.is_none() && !workspace_members.is_empty() {
let mut expected_package_names = workspace_members
.into_iter()
.map(|member| {
let member_manifest = manifest_dir.join(member).join("Cargo.toml");
let member_value = read_manifest(&member_manifest)?;
package_name_from_manifest(&member_value).with_context(|| {
format!(
"workspace member manifest `{}` has no package.name",
member_manifest.display()
)
})
})
.collect::<anyhow::Result<Vec<_>>>()?;
expected_package_names.sort();
return Ok(TargetProject {
input_kind: InputKind::Workspace,
project_root: manifest_dir.to_path_buf(),
manifest_path,
expected_package_names,
});
}
let package_name = package_name.context("failed to resolve target package name")?;
Ok(TargetProject {
input_kind: InputKind::SingleCrate,
project_root: manifest_dir.to_path_buf(),
manifest_path,
expected_package_names: vec![package_name],
})
}