use std::collections::BTreeSet; use cargo_metadata::{DependencyKind, Metadata, Package}; use crate::{ git, utils::{is_pre_release_version, is_workspace_member, package_by_name}, }; pub fn dependencies(ctx: &crate::Context, verbose: bool, add_production_crates: bool) -> anyhow::Result> { let mut seen = BTreeSet::new(); let mut changed_crate_names_to_publish = Vec::new(); for crate_name in &ctx.crate_names { if seen.contains(crate_name) { continue; } if dependency_tree_has_link_to_existing_crate_names(&ctx.meta, crate_name, &changed_crate_names_to_publish)? { // redo all work which includes the previous tree. Could be more efficient but that would be more complicated. seen.clear(); changed_crate_names_to_publish.clear(); } let num_crates_for_publishing_without_dependencies = changed_crate_names_to_publish.len(); let package = package_by_name(&ctx.meta, crate_name)?; let skipped = depth_first_traversal( ctx, add_production_crates, &mut seen, &mut changed_crate_names_to_publish, package, verbose, )?; if !verbose && skipped > 0 { log::info!( "Skipped {} dependent crates as they didn't change since their last release. Use --verbose/-v to see much more.", skipped ); } if num_crates_for_publishing_without_dependencies == changed_crate_names_to_publish.len() { let crate_package = package_by_name(&ctx.meta, crate_name)?; if !git::has_changed_since_last_release(crate_package, ctx, verbose)? { log::info!( "Skipping provided {} v{} hasn't changed since last released", crate_package.name, crate_package.version ); continue; } } changed_crate_names_to_publish.push(crate_name.to_owned()); seen.insert(crate_name.to_owned()); } Ok(changed_crate_names_to_publish) } fn depth_first_traversal( ctx: &crate::Context, add_production_crates: bool, seen: &mut BTreeSet, changed_crate_names_to_publish: &mut Vec, package: &Package, verbose: bool, ) -> anyhow::Result { let mut skipped = 0; for dependency in package.dependencies.iter().filter(|d| d.kind == DependencyKind::Normal) { if seen.contains(&dependency.name) || !is_workspace_member(&ctx.meta, &dependency.name) { continue; } seen.insert(dependency.name.clone()); let dep_package = package_by_name(&ctx.meta, &dependency.name)?; skipped += depth_first_traversal( ctx, add_production_crates, seen, changed_crate_names_to_publish, dep_package, verbose, )?; if git::has_changed_since_last_release(dep_package, ctx, verbose)? { if is_pre_release_version(&dep_package.version) || add_production_crates { if verbose { log::info!( "Adding '{}' v{} to set of published crates as it changed since last release", dep_package.name, dep_package.version ); } changed_crate_names_to_publish.push(dependency.name.clone()); } else { log::warn!( "'{}' v{} changed since last release - consider releasing it beforehand.", dep_package.name, dep_package.version ); } } else { if verbose { log::info!( "'{}' v{} - skipped release as it didn't change", dep_package.name, dep_package.version ); } skipped += 1; } } Ok(skipped) } fn dependency_tree_has_link_to_existing_crate_names( meta: &Metadata, root_name: &str, existing_names: &[String], ) -> anyhow::Result { let mut dependency_names = vec![root_name]; let mut seen = BTreeSet::new(); while let Some(crate_name) = dependency_names.pop() { if !seen.insert(crate_name) { continue; } if existing_names.iter().any(|n| n == crate_name) { return Ok(true); } dependency_names.extend( package_by_name(meta, crate_name)? .dependencies .iter() .filter(|dep| is_workspace_member(meta, &dep.name)) .map(|dep| dep.name.as_str()), ) } Ok(false) }