hakari/cli_ops/
manage_deps.rs

1// Copyright (c) The cargo-guppy Contributors
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4//! Add and remove dependencies.
5
6use crate::{
7    cli_ops::{WorkspaceOp, WorkspaceOps},
8    hakari::DepFormatVersion,
9    HakariBuilder, WorkspaceHackLineStyle,
10};
11use guppy::{
12    graph::{DependencyDirection, PackageLink, PackageMetadata, PackageSet},
13    VersionReq,
14};
15
16impl<'g> HakariBuilder<'g> {
17    /// Returns the set of operations that need to be performed to add the workspace-hack
18    /// dependency to the given set of workspace crates.
19    ///
20    /// Also includes remove operations for the workspace-hack dependency from excluded crates.
21    ///
22    /// Returns `None` if the hakari package wasn't specified at construction time.
23    ///
24    /// Requires the `cli-support` feature to be enabled.
25    pub fn manage_dep_ops(&self, workspace_set: &PackageSet<'g>) -> Option<WorkspaceOps<'g, '_>> {
26        let graph = self.graph();
27        let hakari_package = self.hakari_package()?;
28
29        let (add_to, remove_from) =
30            workspace_set.filter_partition(DependencyDirection::Reverse, |package| {
31                let link_opt = package
32                    .link_to(hakari_package.id())
33                    .expect("valid package ID");
34                let should_be_included = !self.is_excluded(package.id()).expect("valid package ID");
35                match (link_opt, should_be_included) {
36                    (None, true) => Some(true),
37                    (Some(_), false) => Some(false),
38                    (Some(link), true) => match self.dep_format_version {
39                        DepFormatVersion::V1 => None,
40                        DepFormatVersion::V2 | DepFormatVersion::V3 | DepFormatVersion::V4 => {
41                            needs_update_v2(hakari_package, link, self.workspace_hack_line_style)
42                                .then_some(true)
43                        }
44                    },
45                    (None, false) => None,
46                }
47            });
48
49        let mut ops = Vec::with_capacity(2);
50        if !add_to.is_empty() {
51            ops.push(WorkspaceOp::AddDependency {
52                name: hakari_package.name(),
53                crate_path: hakari_package
54                    .source()
55                    .workspace_path()
56                    .expect("hakari package is in workspace"),
57                version: hakari_package.version(),
58                dep_format: self.dep_format_version,
59                line_style: self.workspace_hack_line_style,
60                add_to,
61            });
62        }
63        if !remove_from.is_empty() {
64            ops.push(WorkspaceOp::RemoveDependency {
65                name: hakari_package.name(),
66                remove_from,
67            });
68        }
69        Some(WorkspaceOps::new(graph, ops))
70    }
71
72    /// Returns the set of operations that need to be performed to add the workspace-hack
73    /// dependency to the given set of workspace crates.
74    ///
75    /// Returns `None` if the hakari package wasn't specified at construction time.
76    ///
77    /// Requires the `cli-support` feature to be enabled.
78    pub fn add_dep_ops(
79        &self,
80        workspace_set: &PackageSet<'g>,
81        force: bool,
82    ) -> Option<WorkspaceOps<'g, '_>> {
83        let graph = self.graph();
84        let hakari_package = self.hakari_package()?;
85
86        let add_to = if force {
87            workspace_set.clone()
88        } else {
89            workspace_set.filter(DependencyDirection::Reverse, |package| {
90                let link_opt = package
91                    .link_to(hakari_package.id())
92                    .expect("valid package ID");
93                match link_opt {
94                    Some(link) => {
95                        needs_update_v2(hakari_package, link, self.workspace_hack_line_style)
96                    }
97                    None => true,
98                }
99            })
100        };
101
102        let op = if !add_to.is_empty() {
103            Some(WorkspaceOp::AddDependency {
104                name: hakari_package.name(),
105                version: hakari_package.version(),
106                crate_path: hakari_package
107                    .source()
108                    .workspace_path()
109                    .expect("hakari package is in workspace"),
110                dep_format: self.dep_format_version,
111                line_style: self.workspace_hack_line_style,
112                add_to,
113            })
114        } else {
115            None
116        };
117        Some(WorkspaceOps::new(graph, op))
118    }
119
120    /// Returns the set of operations that need to be performed to remove the workspace-hack
121    /// dependency from the given set of workspace crates.
122    ///
123    /// Returns `None` if the hakari package wasn't specified at construction time.
124    ///
125    /// Requires the `cli-support` feature to be enabled.
126    pub fn remove_dep_ops(
127        &self,
128        workspace_set: &PackageSet<'g>,
129        force: bool,
130    ) -> Option<WorkspaceOps<'g, '_>> {
131        let graph = self.graph();
132        let hakari_package = self.hakari_package()?;
133
134        let remove_from = if force {
135            workspace_set.clone()
136        } else {
137            workspace_set.filter(DependencyDirection::Reverse, |package| {
138                graph
139                    .directly_depends_on(package.id(), hakari_package.id())
140                    .expect("valid package ID")
141            })
142        };
143
144        let op = if !remove_from.is_empty() {
145            Some(WorkspaceOp::RemoveDependency {
146                name: hakari_package.name(),
147                remove_from,
148            })
149        } else {
150            None
151        };
152        Some(WorkspaceOps::new(graph, op))
153    }
154}
155
156#[allow(clippy::if_same_then_else, clippy::needless_bool)]
157fn needs_update_v2(
158    hakari_package: &PackageMetadata<'_>,
159    link: PackageLink<'_>,
160    line_style: WorkspaceHackLineStyle,
161) -> bool {
162    if !link.version_req().matches(hakari_package.version()) {
163        // The version number doesn't match: it must be updated.
164        true
165    } else if link.version_req() == &VersionReq::STAR {
166        // The version number isn't specified. Require it in case line_style isn't workspace-dotted.
167        match line_style {
168            WorkspaceHackLineStyle::Full | WorkspaceHackLineStyle::VersionOnly => true,
169            WorkspaceHackLineStyle::WorkspaceDotted => false,
170        }
171    } else {
172        false
173    }
174}