use std::collections::BTreeSet; use cargo_metadata::{DependencyKind, Package, PackageId}; use crate::{ git, traverse::dependency::{ManifestAdjustment, VersionAdjustment}, utils::{ is_pre_release_version, package_by_id, package_by_name, package_eq_dependency_ignore_dev_without_version, workspace_package_by_dependency, }, version, version::{Bump, BumpSpec}, Context,}; pub mod dependency { use crate::{git, version}; /// Skipped crates are always dependent ones #[derive(Copy, Clone, Debug, PartialOrd, Ord, Eq, PartialEq)] pub enum NoPublishReason { Unchanged, DeniedAutopublishOfProductionCrate, PublishDisabledInManifest, BreakingChangeCausesManifestUpdate, } impl std::fmt::Display for NoPublishReason { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_str(match self { NoPublishReason::PublishDisabledInManifest => "disabled", NoPublishReason::DeniedAutopublishOfProductionCrate => "denied", NoPublishReason::Unchanged => "unchanged", NoPublishReason::BreakingChangeCausesManifestUpdate => "dep-breaking", }) } } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Kind { /// Initially selected by user UserSelection, /// A changed dependency of the user selected crate that thus needs publishing DependencyOrDependentOfUserSelection, } #[derive(Clone, Debug)] pub enum VersionAdjustment { /// The crate changed and should see a version change Changed { change: git::PackageChangeKind, bump: version::Bump, }, /// One of the crates dependencies signalled breaking changes, and is published because of that. Breakage { bump: version::Bump, /// Set if there is a change at all, which might not be the case for previously skipped crates. change: Option<git::PackageChangeKind>, /// The direct dependency causing the breakage because it's breaking itself causing_dependency_names: Vec<String>, }, } impl VersionAdjustment { pub fn bump(&self) -> &version::Bump { match self { VersionAdjustment::Breakage { bump, .. } | VersionAdjustment::Changed { bump, .. } => bump, } } } #[allow(clippy::large_enum_variant)] #[derive(Clone, Debug)] pub enum ManifestAdjustment { DueToDependencyChange, Version(VersionAdjustment), } #[derive(Clone, Debug)] pub enum Mode { ToBePublished { adjustment: VersionAdjustment, }, /// Won't be published but manifest might have to be fixed if a version bump is present. NotForPublishing { reason: NoPublishReason, adjustment: Option<ManifestAdjustment>, }, } impl Mode { pub fn manifest_will_change(&self) -> bool { matches!( self, Mode::ToBePublished { .. } | Mode::NotForPublishing { adjustment: Some(_), .. } ) } pub fn safety_bump(&self) -> Option<&version::Bump> { match self { Mode::ToBePublished { adjustment } | Mode::NotForPublishing { adjustment: Some(ManifestAdjustment::Version(adjustment)), .. } => match adjustment { VersionAdjustment::Breakage { bump, .. } => Some(bump), VersionAdjustment::Changed { .. } => None, }, _ => None, } } pub fn version_adjustment_bump(&self) -> Option<&version::Bump> { match self { Mode::ToBePublished { adjustment } | Mode::NotForPublishing { adjustment: Some(ManifestAdjustment::Version(adjustment)), .. } => Some(adjustment.bump()), _ => None, } } }} #[derive(Clone)]pub struct Dependency<'meta> { pub package: &'meta Package, pub kind: dependency::Kind, pub mode: dependency::Mode,} impl<'a> std::fmt::Debug for Dependency<'a> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let mut dbg = f.debug_struct("Dependency"); dbg.field("package", &self.package.id.repr); dbg.field("kind", &self.kind); dbg.field("mode", &self.mode); dbg.finish() }} pub struct Options { pub allow_auto_publish_of_stable_crates: bool, pub bump_when_needed: bool, pub isolate_dependencies_from_breaking_changes: bool, pub traverse_graph: bool,} pub fn dependencies( ctx: &crate::Context, Options { allow_auto_publish_of_stable_crates, bump_when_needed, isolate_dependencies_from_breaking_changes, traverse_graph, }: Options,) -> anyhow::Result<Vec<Dependency<'_>>> { let mut seen = BTreeSet::new(); let mut crates = Vec::new(); for crate_name in &ctx.crate_names { let mut crates_this_round = Vec::new(); let package = package_by_name(&ctx.meta, crate_name)?; if seen.contains(&&package.id) { continue; } if traverse_graph { depth_first_traversal( ctx, &mut seen, &mut crates_this_round, package, allow_auto_publish_of_stable_crates, bump_when_needed, )?; } match git::change_since_last_release(package, ctx)? { Some(user_package_change) => { crates_this_round.push(Dependency { package, kind: dependency::Kind::UserSelection, mode: if package_may_be_published(package) { dependency::Mode::ToBePublished { adjustment: VersionAdjustment::Changed { change: user_package_change, bump: version::bump_package(package, ctx, bump_when_needed)?, }, } } else { dependency::Mode::NotForPublishing { reason: dependency::NoPublishReason::PublishDisabledInManifest, adjustment: None, } }, }); seen.insert(&package.id); } None => { crates_this_round.push(Dependency { package, kind: dependency::Kind::UserSelection, mode: dependency::Mode::NotForPublishing { reason: dependency::NoPublishReason::Unchanged, adjustment: None, }, }); } } merge_crates(&mut crates, crates_this_round); } if isolate_dependencies_from_breaking_changes { forward_propagate_breaking_changes_for_publishing( ctx, &mut crates, bump_when_needed, allow_auto_publish_of_stable_crates, )?; forward_propagate_breaking_changes_for_manifest_updates( ctx, &mut crates, bump_when_needed, allow_auto_publish_of_stable_crates, )?; } crates.extend(find_workspace_crates_depending_on_adjusted_crates(ctx, &crates)); Ok(crates)} fn merge_crates<'meta>(dest: &mut Vec<Dependency<'meta>>, src: Vec<Dependency<'meta>>) { if dest.is_empty() { *dest = src; } else { for dep in src { if !dest.iter().any(|dest| dest.package.id == dep.package.id) { dest.push(dep); } } }} fn forward_propagate_breaking_changes_for_manifest_updates<'meta>( ctx: &'meta Context, crates: &mut Vec<Dependency<'meta>>, bump_when_needed: bool, allow_auto_publish_of_stable_crates: bool,) -> anyhow::Result<()> { let mut non_publishing_crates_with_safety_bumps = Vec::new(); let mut backing = crates .iter() .filter( |c| matches!(&c.mode, dependency::Mode::ToBePublished { adjustment } if adjustment.bump().is_breaking()), ) .map(ToOwned::to_owned) .collect::<Vec<_>>(); let workspace_packages: Vec<_> = ctx .meta .workspace_members .iter() .map(|wmid| package_by_id(&ctx.meta, wmid)) .filter(|p| package_may_be_published(p)) // will publish, non-publishing ones need no safety bumps .collect(); let mut set_to_expand_from = &backing; let mut seen = BTreeSet::default(); loop { let mut new_crates_this_round = Vec::<Dependency<'_>>::new(); for dependee in set_to_expand_from { for dependant in workspace_packages.iter().filter(|p| { p.dependencies .iter() .any(|dep| package_eq_dependency_ignore_dev_without_version(dependee.package, dep)) }) { if seen.contains(&&dependant.id) { continue; } seen.insert(&dependant.id); let crate_is_known_already = crates .iter() .find_map(|c| { (c.package.id == dependant.id) .then(|| c.mode.version_adjustment_bump().map(|b| b.is_breaking())) }) .flatten(); let bump = breaking_version_bump(ctx, dependant, bump_when_needed)?; if bump.next_release_changes_manifest() { if crate_is_known_already.is_some() { let is_breaking = crate_is_known_already.unwrap_or(false); if !is_breaking { log::debug!( "Wanted to mark '{}' for breaking manifest change, but its already known without breaking change.", dependant.name ); } } else if is_pre_release_version(&dependant.version) || allow_auto_publish_of_stable_crates { let kind = ctx .crate_names .contains(&dependant.name) .then(|| dependency::Kind::UserSelection) .unwrap_or(dependency::Kind::DependencyOrDependentOfUserSelection); let adjustment = dependency::VersionAdjustment::Breakage { bump, change: None, causing_dependency_names: vec![dependee.package.name.to_owned()], }; new_crates_this_round.push(Dependency { package: dependant, kind, mode: match kind { dependency::Kind::UserSelection => dependency::Mode::ToBePublished { adjustment }, dependency::Kind::DependencyOrDependentOfUserSelection => { dependency::Mode::NotForPublishing { reason: dependency::NoPublishReason::BreakingChangeCausesManifestUpdate, adjustment: Some(ManifestAdjustment::Version(adjustment)), } } }, }); } else { log::trace!( "Ignored stable crate '{}' despite being eligible for safety bump and manifest change.", dependant.name ); } } } } if new_crates_this_round.is_empty() { break; } non_publishing_crates_with_safety_bumps.extend(new_crates_this_round.iter().cloned()); backing = new_crates_this_round; set_to_expand_from = &backing; } crates.extend(non_publishing_crates_with_safety_bumps); Ok(())} fn package_may_be_published(p: &Package) -> bool { p.publish.is_none()} fn forward_propagate_breaking_changes_for_publishing( ctx: &Context, crates: &mut [Dependency<'_>], bump_when_needed: bool, allow_auto_publish_of_stable_crates: bool,) -> anyhow::Result<()> { let mut previous_edits = Vec::new(); loop { let mut seen_this_round = BTreeSet::default(); let mut edits = Vec::new(); // skipped don't have version bumps, we don't have manifest updates yet for (idx, starting_crate_for_backward_search) in crates .iter() .enumerate() .rev() .filter(|(_, c)| matches!(c.mode, dependency::Mode::ToBePublished { .. })) { find_safety_bump_edits_backwards_from_crates_for_publish( crates, (idx, starting_crate_for_backward_search), &mut seen_this_round, &mut edits, ); seen_this_round.insert(idx); } if edits == previous_edits { break; } previous_edits = edits.clone(); for edit_for_publish in edits { edit_for_publish.apply(crates, ctx, bump_when_needed, allow_auto_publish_of_stable_crates)?; } } Ok(())} #[derive(PartialEq, Eq, Clone)]struct EditForPublish { crates_idx: usize, causing_dependency_indices: Vec<usize>,} impl EditForPublish { fn from(idx: usize, causing_dependency_indices: Vec<usize>) -> Self { EditForPublish { crates_idx: idx, causing_dependency_indices, } } fn apply( self, crates: &mut [Dependency<'_>], ctx: &Context, bump_when_needed: bool, allow_auto_publish_of_stable_crates: bool, ) -> anyhow::Result<()> { let causing_dependency_names = self .causing_dependency_indices .into_iter() .map(|idx| crates[idx].package.name.clone()) .collect(); let dep_mut = &mut crates[self.crates_idx]; if is_pre_release_version(&dep_mut.package.version) || allow_auto_publish_of_stable_crates { let breaking_bump = breaking_version_bump(ctx, dep_mut.package, bump_when_needed)?; match &mut dep_mut.mode { dependency::Mode::NotForPublishing { adjustment: maybe_adjustment, .. } => { let adjustment = match maybe_adjustment.take() { Some(ManifestAdjustment::DueToDependencyChange) => { unreachable!("BUG: code generating these runs later") } Some(ManifestAdjustment::Version(mut adjustment)) => { make_breaking(&mut adjustment, breaking_bump, causing_dependency_names); adjustment } None => dependency::VersionAdjustment::Breakage { bump: breaking_bump, causing_dependency_names, change: None, }, }; dep_mut.mode = dependency::Mode::ToBePublished { adjustment }; } dependency::Mode::ToBePublished { adjustment, .. } => { make_breaking(adjustment, breaking_bump, causing_dependency_names); } } } else { log::trace!( "Ignored stable crate '{}' despite being eligible for safety bump and publishing.", dep_mut.package.name ); } Ok(()) }} fn breaking_version_bump(ctx: &Context, package: &Package, bump_when_needed: bool) -> anyhow::Result<Bump> { let breaking_spec = is_pre_release_version(&package.version) .then(|| BumpSpec::Minor) .unwrap_or(BumpSpec::Major); version::bump_package_with_spec(package, breaking_spec, ctx, bump_when_needed)} fn make_breaking(adjustment: &mut VersionAdjustment, breaking_bump: version::Bump, breaking_crate_names: Vec<String>) { match adjustment { VersionAdjustment::Breakage { .. } => {} VersionAdjustment::Changed { change, bump } => { bump.next_release = breaking_bump.next_release; *adjustment = VersionAdjustment::Breakage { bump: bump.clone(), change: Some(change.clone()), causing_dependency_names: breaking_crate_names, }; } }} fn find_safety_bump_edits_backwards_from_crates_for_publish( crates: &[Dependency<'_>], start: (usize, &Dependency<'_>), seen: &mut BTreeSet<usize>, edits: &mut Vec<EditForPublish>,) -> Vec<usize> { let (current_idx, current) = start; let mut breaking_indices = Vec::new(); for (dep_idx, dep) in current.package.dependencies.iter().filter_map(|dep| { crates .iter() .enumerate() .find(|(_, c)| package_eq_dependency_ignore_dev_without_version(c.package, dep)) }) { if seen.contains(&dep_idx) { continue; } match dep.mode.version_adjustment_bump() { Some(dep_bump) if dep_bump.is_breaking() => { if !edits.iter().any(|e| e.crates_idx == current_idx) { edits.push(EditForPublish::from(current_idx, vec![dep_idx])); } if !breaking_indices.contains(&dep_idx) { breaking_indices.push(dep_idx); } } _ => { seen.insert(dep_idx); let breaking_package_indices = find_safety_bump_edits_backwards_from_crates_for_publish(crates, (dep_idx, dep), seen, edits);Lines 1–500 of 607. Load more
use std::collections::BTreeSet; use cargo_metadata::{DependencyKind, Package, PackageId}; use crate::{ git, traverse::dependency::{ManifestAdjustment, VersionAdjustment}, utils::{ is_pre_release_version, package_by_id, package_by_name, package_eq_dependency_ignore_dev_without_version, workspace_package_by_dependency, }, version, version::{Bump, BumpSpec}, Context, }; pub mod dependency { use crate::{git, version}; /// Skipped crates are always dependent ones #[derive(Copy, Clone, Debug, PartialOrd, Ord, Eq, PartialEq)] pub enum NoPublishReason { Unchanged, DeniedAutopublishOfProductionCrate, PublishDisabledInManifest, BreakingChangeCausesManifestUpdate, } impl std::fmt::Display for NoPublishReason { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_str(match self { NoPublishReason::PublishDisabledInManifest => "disabled", NoPublishReason::DeniedAutopublishOfProductionCrate => "denied", NoPublishReason::Unchanged => "unchanged", NoPublishReason::BreakingChangeCausesManifestUpdate => "dep-breaking", }) } } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Kind { /// Initially selected by user UserSelection, /// A changed dependency of the user selected crate that thus needs publishing DependencyOrDependentOfUserSelection, } #[derive(Clone, Debug)] pub enum VersionAdjustment { /// The crate changed and should see a version change Changed { change: git::PackageChangeKind, bump: version::Bump, }, /// One of the crates dependencies signalled breaking changes, and is published because of that. Breakage { bump: version::Bump, /// Set if there is a change at all, which might not be the case for previously skipped crates. change: Option<git::PackageChangeKind>, /// The direct dependency causing the breakage because it's breaking itself causing_dependency_names: Vec<String>, }, } impl VersionAdjustment { pub fn bump(&self) -> &version::Bump { match self { VersionAdjustment::Breakage { bump, .. } | VersionAdjustment::Changed { bump, .. } => bump, } } } #[allow(clippy::large_enum_variant)] #[derive(Clone, Debug)] pub enum ManifestAdjustment { DueToDependencyChange, Version(VersionAdjustment), } #[derive(Clone, Debug)] pub enum Mode { ToBePublished { adjustment: VersionAdjustment, }, /// Won't be published but manifest might have to be fixed if a version bump is present. NotForPublishing { reason: NoPublishReason, adjustment: Option<ManifestAdjustment>, }, } impl Mode { pub fn manifest_will_change(&self) -> bool { matches!( self, Mode::ToBePublished { .. } | Mode::NotForPublishing { adjustment: Some(_), .. } ) } pub fn safety_bump(&self) -> Option<&version::Bump> { match self { Mode::ToBePublished { adjustment } | Mode::NotForPublishing { adjustment: Some(ManifestAdjustment::Version(adjustment)), .. } => match adjustment { VersionAdjustment::Breakage { bump, .. } => Some(bump), VersionAdjustment::Changed { .. } => None, }, _ => None, } } pub fn version_adjustment_bump(&self) -> Option<&version::Bump> { match self { Mode::ToBePublished { adjustment } | Mode::NotForPublishing { adjustment: Some(ManifestAdjustment::Version(adjustment)), .. } => Some(adjustment.bump()), _ => None, } } } } #[derive(Clone)] pub struct Dependency<'meta> { pub package: &'meta Package, pub kind: dependency::Kind, pub mode: dependency::Mode, } impl<'a> std::fmt::Debug for Dependency<'a> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let mut dbg = f.debug_struct("Dependency"); dbg.field("package", &self.package.id.repr); dbg.field("kind", &self.kind); dbg.field("mode", &self.mode); dbg.finish() } } pub struct Options { pub allow_auto_publish_of_stable_crates: bool, pub bump_when_needed: bool, pub isolate_dependencies_from_breaking_changes: bool, pub traverse_graph: bool, } pub fn dependencies( ctx: &crate::Context, Options { allow_auto_publish_of_stable_crates, bump_when_needed, isolate_dependencies_from_breaking_changes, traverse_graph, }: Options, ) -> anyhow::Result<Vec<Dependency<'_>>> { let mut seen = BTreeSet::new(); let mut crates = Vec::new(); for crate_name in &ctx.crate_names { let mut crates_this_round = Vec::new(); let package = package_by_name(&ctx.meta, crate_name)?; if seen.contains(&&package.id) { continue; } if traverse_graph { depth_first_traversal( ctx, &mut seen, &mut crates_this_round, package, allow_auto_publish_of_stable_crates, bump_when_needed, )?; } match git::change_since_last_release(package, ctx)? { Some(user_package_change) => { crates_this_round.push(Dependency { package, kind: dependency::Kind::UserSelection, mode: if package_may_be_published(package) { dependency::Mode::ToBePublished { adjustment: VersionAdjustment::Changed { change: user_package_change, bump: version::bump_package(package, ctx, bump_when_needed)?, }, } } else { dependency::Mode::NotForPublishing { reason: dependency::NoPublishReason::PublishDisabledInManifest, adjustment: None, } }, }); seen.insert(&package.id); } None => { crates_this_round.push(Dependency { package, kind: dependency::Kind::UserSelection, mode: dependency::Mode::NotForPublishing { reason: dependency::NoPublishReason::Unchanged, adjustment: None, }, }); } } merge_crates(&mut crates, crates_this_round); } if isolate_dependencies_from_breaking_changes { forward_propagate_breaking_changes_for_publishing( ctx, &mut crates, bump_when_needed, allow_auto_publish_of_stable_crates, )?; forward_propagate_breaking_changes_for_manifest_updates( ctx, &mut crates, bump_when_needed, allow_auto_publish_of_stable_crates, )?; } crates.extend(find_workspace_crates_depending_on_adjusted_crates(ctx, &crates)); Ok(crates) } fn merge_crates<'meta>(dest: &mut Vec<Dependency<'meta>>, src: Vec<Dependency<'meta>>) { if dest.is_empty() { *dest = src; } else { for dep in src { if !dest.iter().any(|dest| dest.package.id == dep.package.id) { dest.push(dep); } } } } fn forward_propagate_breaking_changes_for_manifest_updates<'meta>( ctx: &'meta Context, crates: &mut Vec<Dependency<'meta>>, bump_when_needed: bool, allow_auto_publish_of_stable_crates: bool, ) -> anyhow::Result<()> { let mut non_publishing_crates_with_safety_bumps = Vec::new(); let mut backing = crates .iter() .filter( |c| matches!(&c.mode, dependency::Mode::ToBePublished { adjustment } if adjustment.bump().is_breaking()), ) .map(ToOwned::to_owned) .collect::<Vec<_>>(); let workspace_packages: Vec<_> = ctx .meta .workspace_members .iter() .map(|wmid| package_by_id(&ctx.meta, wmid)) .filter(|p| package_may_be_published(p)) // will publish, non-publishing ones need no safety bumps .collect(); let mut set_to_expand_from = &backing; let mut seen = BTreeSet::default(); loop { let mut new_crates_this_round = Vec::<Dependency<'_>>::new(); for dependee in set_to_expand_from { for dependant in workspace_packages.iter().filter(|p| { p.dependencies .iter() .any(|dep| package_eq_dependency_ignore_dev_without_version(dependee.package, dep)) }) { if seen.contains(&&dependant.id) { continue; } seen.insert(&dependant.id); let crate_is_known_already = crates .iter() .find_map(|c| { (c.package.id == dependant.id) .then(|| c.mode.version_adjustment_bump().map(|b| b.is_breaking())) }) .flatten(); let bump = breaking_version_bump(ctx, dependant, bump_when_needed)?; if bump.next_release_changes_manifest() { if crate_is_known_already.is_some() { let is_breaking = crate_is_known_already.unwrap_or(false); if !is_breaking { log::debug!( "Wanted to mark '{}' for breaking manifest change, but its already known without breaking change.", dependant.name ); } } else if is_pre_release_version(&dependant.version) || allow_auto_publish_of_stable_crates { let kind = ctx .crate_names .contains(&dependant.name) .then(|| dependency::Kind::UserSelection) .unwrap_or(dependency::Kind::DependencyOrDependentOfUserSelection); let adjustment = dependency::VersionAdjustment::Breakage { bump, change: None, causing_dependency_names: vec![dependee.package.name.to_owned()], }; new_crates_this_round.push(Dependency { package: dependant, kind, mode: match kind { dependency::Kind::UserSelection => dependency::Mode::ToBePublished { adjustment }, dependency::Kind::DependencyOrDependentOfUserSelection => { dependency::Mode::NotForPublishing { reason: dependency::NoPublishReason::BreakingChangeCausesManifestUpdate, adjustment: Some(ManifestAdjustment::Version(adjustment)), } } }, }); } else { log::trace!( "Ignored stable crate '{}' despite being eligible for safety bump and manifest change.", dependant.name ); } } } } if new_crates_this_round.is_empty() { break; } non_publishing_crates_with_safety_bumps.extend(new_crates_this_round.iter().cloned()); backing = new_crates_this_round; set_to_expand_from = &backing; } crates.extend(non_publishing_crates_with_safety_bumps); Ok(()) } fn package_may_be_published(p: &Package) -> bool { p.publish.is_none() } fn forward_propagate_breaking_changes_for_publishing( ctx: &Context, crates: &mut [Dependency<'_>], bump_when_needed: bool, allow_auto_publish_of_stable_crates: bool, ) -> anyhow::Result<()> { let mut previous_edits = Vec::new(); loop { let mut seen_this_round = BTreeSet::default(); let mut edits = Vec::new(); // skipped don't have version bumps, we don't have manifest updates yet for (idx, starting_crate_for_backward_search) in crates .iter() .enumerate() .rev() .filter(|(_, c)| matches!(c.mode, dependency::Mode::ToBePublished { .. })) { find_safety_bump_edits_backwards_from_crates_for_publish( crates, (idx, starting_crate_for_backward_search), &mut seen_this_round, &mut edits, ); seen_this_round.insert(idx); } if edits == previous_edits { break; } previous_edits = edits.clone(); for edit_for_publish in edits { edit_for_publish.apply(crates, ctx, bump_when_needed, allow_auto_publish_of_stable_crates)?; } } Ok(()) } #[derive(PartialEq, Eq, Clone)] struct EditForPublish { crates_idx: usize, causing_dependency_indices: Vec<usize>, } impl EditForPublish { fn from(idx: usize, causing_dependency_indices: Vec<usize>) -> Self { EditForPublish { crates_idx: idx, causing_dependency_indices, } } fn apply( self, crates: &mut [Dependency<'_>], ctx: &Context, bump_when_needed: bool, allow_auto_publish_of_stable_crates: bool, ) -> anyhow::Result<()> { let causing_dependency_names = self .causing_dependency_indices .into_iter() .map(|idx| crates[idx].package.name.clone()) .collect(); let dep_mut = &mut crates[self.crates_idx]; if is_pre_release_version(&dep_mut.package.version) || allow_auto_publish_of_stable_crates { let breaking_bump = breaking_version_bump(ctx, dep_mut.package, bump_when_needed)?; match &mut dep_mut.mode { dependency::Mode::NotForPublishing { adjustment: maybe_adjustment, .. } => { let adjustment = match maybe_adjustment.take() { Some(ManifestAdjustment::DueToDependencyChange) => { unreachable!("BUG: code generating these runs later") } Some(ManifestAdjustment::Version(mut adjustment)) => { make_breaking(&mut adjustment, breaking_bump, causing_dependency_names); adjustment } None => dependency::VersionAdjustment::Breakage { bump: breaking_bump, causing_dependency_names, change: None, }, }; dep_mut.mode = dependency::Mode::ToBePublished { adjustment }; } dependency::Mode::ToBePublished { adjustment, .. } => { make_breaking(adjustment, breaking_bump, causing_dependency_names); } } } else { log::trace!( "Ignored stable crate '{}' despite being eligible for safety bump and publishing.", dep_mut.package.name ); } Ok(()) } } fn breaking_version_bump(ctx: &Context, package: &Package, bump_when_needed: bool) -> anyhow::Result<Bump> { let breaking_spec = is_pre_release_version(&package.version) .then(|| BumpSpec::Minor) .unwrap_or(BumpSpec::Major); version::bump_package_with_spec(package, breaking_spec, ctx, bump_when_needed) } fn make_breaking(adjustment: &mut VersionAdjustment, breaking_bump: version::Bump, breaking_crate_names: Vec<String>) { match adjustment { VersionAdjustment::Breakage { .. } => {} VersionAdjustment::Changed { change, bump } => { bump.next_release = breaking_bump.next_release; *adjustment = VersionAdjustment::Breakage { bump: bump.clone(), change: Some(change.clone()), causing_dependency_names: breaking_crate_names, }; } } } fn find_safety_bump_edits_backwards_from_crates_for_publish( crates: &[Dependency<'_>], start: (usize, &Dependency<'_>), seen: &mut BTreeSet<usize>, edits: &mut Vec<EditForPublish>, ) -> Vec<usize> { let (current_idx, current) = start; let mut breaking_indices = Vec::new(); for (dep_idx, dep) in current.package.dependencies.iter().filter_map(|dep| { crates .iter() .enumerate() .find(|(_, c)| package_eq_dependency_ignore_dev_without_version(c.package, dep)) }) { if seen.contains(&dep_idx) { continue; } match dep.mode.version_adjustment_bump() { Some(dep_bump) if dep_bump.is_breaking() => { if !edits.iter().any(|e| e.crates_idx == current_idx) { edits.push(EditForPublish::from(current_idx, vec![dep_idx])); } if !breaking_indices.contains(&dep_idx) { breaking_indices.push(dep_idx); } } _ => { seen.insert(dep_idx); let breaking_package_indices = find_safety_bump_edits_backwards_from_crates_for_publish(crates, (dep_idx, dep), seen, edits);