1use crate::{cargo_cli::CargoCli, helpers::regenerate_lockfile, output::OutputContext};
5use color_eyre::{eyre::WrapErr, Result};
6use guppy::graph::PackageMetadata;
7use hakari::HakariBuilder;
8use log::{error, info};
9use owo_colors::OwoColorize;
10
11pub(crate) fn publish_hakari(
12 package_name: &str,
13 builder: HakariBuilder<'_>,
14 pass_through: &[String],
15 output: OutputContext,
16) -> Result<()> {
17 let hakari_package = builder
18 .hakari_package()
19 .expect("hakari-package must be specified in hakari.toml");
20 let workspace = builder.graph().workspace();
21 let package = workspace.member_by_name(package_name)?;
22
23 let mut remove_dep = if hakari_package.publish().is_never() {
25 TempRemoveDep::new(builder, package, output.clone())?
26 } else {
27 info!(
28 "not removing dependency to {} because it is marked as published (publish != false)",
29 hakari_package.name().style(output.styles.package_name)
30 );
31 TempRemoveDep::none()
32 };
33
34 let mut cargo_cli = CargoCli::new("publish", output.clone());
35 cargo_cli.add_args(pass_through.iter().map(|arg| arg.as_str()));
36 if !remove_dep.is_none() {
39 cargo_cli.add_arg("--allow-dirty");
40 }
41
42 let workspace_dir = package
43 .source()
44 .workspace_path()
45 .expect("package is in workspace");
46 let abs_path = workspace.root().join(workspace_dir);
47
48 let all_args = cargo_cli.all_args().join(" ");
49
50 info!(
51 "{} {}\n---",
52 "executing".style(output.styles.command),
53 all_args
54 );
55 let expression = cargo_cli.to_expression().dir(abs_path);
56
57 match expression.run() {
58 Ok(_) => remove_dep.finish(true),
59 Err(err) => {
60 remove_dep.finish(false)?;
61 Err(err).wrap_err_with(|| format!("`{}` failed", all_args))
62 }
63 }
64}
65
66#[derive(Debug)]
68struct TempRemoveDep<'g> {
69 inner: Option<TempRemoveDepInner<'g>>,
70}
71
72impl<'g> TempRemoveDep<'g> {
73 fn new(
74 builder: HakariBuilder<'g>,
75 package: PackageMetadata<'g>,
76 output: OutputContext,
77 ) -> Result<Self> {
78 let hakari_package = builder
79 .hakari_package()
80 .expect("hakari-package must be specified in hakari.toml");
81 let package_set = package.to_package_set();
82 let remove_ops = builder
83 .remove_dep_ops(&package_set, false)
84 .expect("hakari-package must be specified in hakari.toml");
85 let inner = if remove_ops.is_empty() {
86 info!(
87 "dependency from {} to {} not present",
88 package.name().style(output.styles.package_name),
89 hakari_package.name().style(output.styles.package_name),
90 );
91 None
92 } else {
93 info!(
94 "removing dependency from {} to {}",
95 package.name().style(output.styles.package_name),
96 hakari_package.name().style(output.styles.package_name),
97 );
98 remove_ops
99 .apply()
100 .wrap_err_with(|| format!("error removing dependency from {}", package.name()))?;
101 Some(TempRemoveDepInner {
102 builder,
103 package,
104 output,
105 })
106 };
107
108 Ok(Self { inner })
109 }
110
111 fn none() -> Self {
112 Self { inner: None }
113 }
114
115 fn is_none(&self) -> bool {
116 self.inner.is_none()
117 }
118
119 fn finish(&mut self, success: bool) -> Result<()> {
120 match self.inner.take() {
121 Some(inner) => inner.finish(success),
122 None => {
123 Ok(())
125 }
126 }
127 }
128}
129
130impl Drop for TempRemoveDep<'_> {
131 fn drop(&mut self) {
132 let _ = self.finish(false);
134 }
135}
136
137#[derive(Debug)]
138struct TempRemoveDepInner<'g> {
139 builder: HakariBuilder<'g>,
140 package: PackageMetadata<'g>,
141 output: OutputContext,
142}
143
144impl TempRemoveDepInner<'_> {
145 fn finish(self, success: bool) -> Result<()> {
146 let package_set = self.package.to_package_set();
147 let add_ops = self
148 .builder
149 .add_dep_ops(&package_set, true)
150 .expect("hakari-package must be specified in hakari.toml");
151
152 if success {
153 info!(
154 "re-adding dependency from {} to {}",
155 self.package.name().style(self.output.styles.package_name),
156 self.builder
157 .hakari_package()
158 .unwrap()
159 .name()
160 .style(self.output.styles.package_name),
161 );
162 } else {
163 eprintln!("---");
164 error!("execution failed, rolling back changes");
165 }
166
167 add_ops.apply()?;
168 regenerate_lockfile(self.output)?;
169 Ok(())
170 }
171}