use std::{
env::current_dir,
path::{Path, PathBuf},
};
use anyhow::{Context, anyhow, bail};
use crate::utils::PathResultExt;
#[derive(Clone, Debug)]
pub struct ProjectLayout {
manifest_path: PathBuf,
manifest_dir: PathBuf,
workspace_dir: PathBuf,
}
impl ProjectLayout {
pub fn manifest_path(&self) -> &Path {
&self.manifest_path
}
pub fn manifest_dir(&self) -> &Path {
&self.manifest_dir
}
pub fn workspace_dir(&self) -> &Path {
&self.workspace_dir
}
}
pub fn resolve_project_layout(input: Option<PathBuf>) -> anyhow::Result<ProjectLayout> {
let manifest_path = resolve_manifest_path(input)?;
let manifest_dir = manifest_path
.parent()
.ok_or_else(|| anyhow!("manifest has no parent: {}", manifest_path.display()))?
.to_path_buf();
let metadata = cargo_metadata::MetadataCommand::new()
.manifest_path(&manifest_path)
.no_deps()
.exec()
.with_context(|| {
format!(
"failed to load cargo metadata from {}",
manifest_path.display()
)
})?;
Ok(ProjectLayout {
manifest_path,
manifest_dir,
workspace_dir: PathBuf::from(metadata.workspace_root.as_std_path()),
})
}
fn resolve_manifest_path(input: Option<PathBuf>) -> anyhow::Result<PathBuf> {
let path = match input {
Some(path) => path,
None => current_dir().context("failed to get current working directory")?,
};
let manifest_path = if path.is_dir() {
path.join("Cargo.toml")
} else {
path
};
if manifest_path.file_name().and_then(|name| name.to_str()) != Some("Cargo.toml") {
bail!(
"manifest must be a Cargo.toml file or a directory containing Cargo.toml: {}",
manifest_path.display()
);
}
if !manifest_path.exists() {
bail!("Cargo.toml not found: {}", manifest_path.display());
}
manifest_path
.canonicalize()
.with_path("failed to canonicalize manifest path", &manifest_path)
}