use crate::commands::bump::cargo_edit::{MemberInfo, WorkspaceInfo, load_workspace};
use crate::commands::bump::plan::resolve_member_version;
use crate::commands::version_files_resolve::{EnrolledUnit, enrolled_units};
use crate::pipeline;
use anodizer_core::config::Config;
use anodizer_core::log::{StageLogger, Verbosity};
use anodizer_core::version_files::check_version_present;
use anodizer_stage_build::version_sync::read_cargo_version;
use anyhow::{Context, Result, bail};
use std::collections::BTreeSet;
use std::path::Path;
pub(crate) fn run(
config_override: Option<&Path>,
verbose: bool,
debug: bool,
quiet: bool,
) -> Result<()> {
let log = StageLogger::new("check", Verbosity::from_flags(quiet, verbose, debug));
let repo_root = match crate::commands::helpers::discover_workspace_root(config_override) {
Ok(root) => root,
Err(_) => std::env::current_dir().context("resolving repo root")?,
};
let config = match config_override {
Some(p) => {
log.verbose(&format!("loading config from {}", p.display()));
pipeline::load_config(p)?
}
None => {
log.verbose(&format!("loading config from {}", repo_root.display()));
pipeline::load_repo_config(&repo_root)?
}
};
run_guard(&config, &repo_root, &log)
}
pub(crate) fn run_guard(config: &Config, repo_root: &Path, log: &StageLogger) -> Result<()> {
let mut findings: Vec<String> = vec![];
let mut checked = 0usize;
let units = enrolled_units(config);
let ws = load_workspace(repo_root)?;
let mut seen: BTreeSet<(String, Option<String>, String)> = BTreeSet::new();
for unit in units {
let version = match unit_reference_version(repo_root, &unit, ws.as_ref()) {
Ok(v) => v,
Err(e) => {
findings.push(format!(
"{}: cannot read current version: {e:#}",
unit.label()
));
continue;
}
};
let file_list = unit.files;
for entry in &file_list {
let file = entry.path();
let anchor = entry.anchor().map(str::to_string);
if !seen.insert((file.to_string(), anchor.clone(), version.clone())) {
continue;
}
match check_version_present(repo_root, &[(file.to_string(), anchor.clone())], &version)
{
Ok(results) => {
checked += 1;
let present = results.first().map(|(_, p)| *p).unwrap_or(false);
match (present, &anchor) {
(true, Some(anchor)) => {
log.verbose(&format!("{file} contains {version} inside {anchor}"));
}
(true, None) => log.verbose(&format!("{file} contains {version}")),
(false, Some(anchor)) => findings.push(format!(
"STALE: {file} (match {anchor}: expected {version}, not found)"
)),
(false, None) => {
findings.push(format!("STALE: {file} (expected {version}, not found)"))
}
}
}
Err(e) => {
findings.push(format!("STALE: {file} ({e:#})"));
}
}
}
}
if checked == 0 && findings.is_empty() {
log.status("no version_files configured");
return Ok(());
}
if findings.is_empty() {
log.status(&format!("all {checked} version_files are in sync"));
Ok(())
} else {
for f in &findings {
log.error(f);
}
bail!(
"version_files check failed with {} finding(s)",
findings.len()
);
}
}
fn unit_reference_version(
repo_root: &Path,
unit: &EnrolledUnit,
ws: Option<&WorkspaceInfo>,
) -> Result<String> {
let unit_dir = repo_root.join(&unit.path);
if let Some(ws) = ws
&& let Some(member) = ws.members.iter().find(|m| member_matches(m, &unit_dir))
{
return resolve_member_version(member, ws);
}
if unit.is_lockstep_root {
return ws
.and_then(|ws| ws.workspace_package_version.clone())
.with_context(|| {
format!(
"lockstep workspace at {} has no [workspace.package].version",
anodizer_core::path_util::display_under_root(repo_root, &unit_dir)
)
});
}
let own = read_cargo_version(repo_root, &unit.path)?;
if own == "0.0.0"
&& let Some(ws_version) = ws.and_then(|w| w.workspace_package_version.clone())
{
return Ok(ws_version);
}
Ok(own)
}
fn member_matches(member: &MemberInfo, unit_dir: &Path) -> bool {
match (member.crate_dir.canonicalize(), unit_dir.canonicalize()) {
(Ok(a), Ok(b)) => a == b,
_ => member.crate_dir == unit_dir,
}
}