1use std::{thread, time};
2
3use console::style;
4use semver::Version;
5use vfs::VfsPath;
6
7use crate::CrateConfig;
8use crate::Increment;
9use crate::Publisher;
10use crate::Vcs;
11use crate::version_iter::VersionIter;
12use crate::{PublishOptions, new_cargo_config_path};
13use color_eyre::eyre::Result;
14
15pub struct VPath<'a> {
19 real_path: &'a str,
20 virtual_path: VfsPath,
21}
22
23impl<'a> VPath<'a> {
24 #[must_use]
25 pub fn new(real_path: &'a str, virtual_path: VfsPath) -> Self {
26 Self {
27 real_path,
28 virtual_path,
29 }
30 }
31}
32
33pub trait Release<'a> {
34 fn release(
40 &self,
41 root: VPath<'a>,
42 incr: Increment,
43 all_features: bool,
44 no_verify: bool,
45 ) -> Result<()>;
46}
47
48pub struct Workspace<P: Publisher, V: Vcs> {
49 delay_seconds: u64,
50 publisher: P,
51 vcs: V,
52}
53
54impl<P: Publisher, V: Vcs> Workspace<P, V> {
55 pub fn new(delay_seconds: u64, publisher: P, vcs: V) -> Self {
56 Self {
57 delay_seconds,
58 publisher,
59 vcs,
60 }
61 }
62}
63
64impl<'a, P: Publisher, V: Vcs> Release<'a> for Workspace<P, V> {
65 fn release(
66 &self,
67 root: VPath<'a>,
68 incr: Increment,
69 all_features: bool,
70 no_verify: bool,
71 ) -> Result<()> {
72 let crate_conf = new_cargo_config_path(&root.virtual_path)?;
73
74 let mut it = VersionIter::open(&crate_conf)?;
75 let version = crate::update_configs(&crate_conf, &mut it, incr)?;
76
77 let ver = commit_version(&self.vcs, root.real_path, &version)?;
78
79 let delay_str = format!("{}", self.delay_seconds);
80 let delay = time::Duration::from_secs(self.delay_seconds);
81 let crates_to_publish = it.topo_sort();
82 for (i, publish) in crates_to_publish.iter().enumerate() {
83 let options = PublishOptions {
84 crate_to_publish: Some(publish),
85 all_features,
86 no_verify,
87 };
88 self.publisher.publish(root.real_path, options)?;
89 if i < crates_to_publish.len() - 1 {
92 println!(
93 " Waiting {} seconds after publish {} ...",
94 style(&delay_str).green().bold(),
95 style(publish).green().bold()
96 );
97 thread::sleep(delay);
98 }
99 }
100
101 self.vcs.create_tag(root.real_path, &ver)?;
102 self.vcs.push_tag(root.real_path, &ver)?;
103
104 Ok(())
105 }
106}
107
108#[derive(Default)]
109pub struct Crate<P: Publisher, V: Vcs> {
110 publisher: P,
111 vcs: V,
112}
113
114impl<P: Publisher, V: Vcs> Crate<P, V> {
115 pub fn new(publisher: P, vcs: V) -> Self {
116 Self { publisher, vcs }
117 }
118}
119
120impl<'a, P: Publisher, V: Vcs> Release<'a> for Crate<P, V> {
121 fn release(
122 &self,
123 root: VPath<'a>,
124 incr: Increment,
125 all_features: bool,
126 no_verify: bool,
127 ) -> Result<()> {
128 let crate_conf = new_cargo_config_path(&root.virtual_path)?;
129
130 let conf = CrateConfig::open(&crate_conf)?;
131 let ver = conf.new_version(String::new());
132 let version = crate::update_config(&crate_conf, &ver, incr)?;
133
134 let ver = commit_version(&self.vcs, root.real_path, &version)?;
135
136 let options = PublishOptions {
137 crate_to_publish: None,
138 all_features,
139 no_verify,
140 };
141 self.publisher.publish(root.real_path, options)?;
142
143 self.vcs.create_tag(root.real_path, &ver)?;
144 self.vcs.push_tag(root.real_path, &ver)?;
145
146 Ok(())
147 }
148}
149
150fn commit_version(vcs: &impl Vcs, path: &str, version: &Version) -> Result<String> {
151 let ver = format!("v{version}");
152 let commit_msg = format!("changelog: {ver}");
153 vcs.commit(path, &commit_msg)?;
154 Ok(ver)
155}
156
157#[cfg(test)]
158mod tests {
159 #![allow(clippy::unwrap_in_result)]
160 #![allow(clippy::unwrap_used)]
161 use super::*;
162 use crate::MockVcs;
163 use crate::{CARGO_CONFIG, MockPublisher};
164 use mockall::predicate::{eq, str};
165 use rstest::{fixture, rstest};
166 use vfs::MemoryFS;
167
168 #[rstest]
169 #[case::all_features(true)]
170 #[case::default_features(false)]
171 #[trace]
172 fn release_workspace(root: VfsPath, #[case] all_features: bool) {
173 let mut mock_pub = MockPublisher::new();
175 let mut mock_vcs = MockVcs::new();
176
177 mock_vcs
178 .expect_commit()
179 .with(eq("/x"), eq("changelog: v0.2.0"))
180 .times(1)
181 .returning(|_, _| Ok(()));
182
183 let solp_options: PublishOptions = PublishOptions {
184 crate_to_publish: Some("solp"),
185 all_features,
186 no_verify: false,
187 };
188 mock_pub
189 .expect_publish()
190 .withf(move |p, o| p == "/x" && *o == solp_options)
191 .times(1)
192 .returning(|_, _| Ok(()));
193
194 let solv_options: PublishOptions = PublishOptions {
195 crate_to_publish: Some("solv"),
196 all_features,
197 no_verify: false,
198 };
199 mock_pub
200 .expect_publish()
201 .withf(move |p, o| p == "/x" && *o == solv_options)
202 .times(1)
203 .returning(|_, _| Ok(()));
204
205 mock_vcs
206 .expect_create_tag()
207 .with(eq("/x"), eq("v0.2.0"))
208 .times(1)
209 .returning(|_, _| Ok(()));
210
211 mock_vcs
212 .expect_push_tag()
213 .with(eq("/x"), eq("v0.2.0"))
214 .times(1)
215 .returning(|_, _| Ok(()));
216
217 let w = Workspace::new(0, mock_pub, mock_vcs);
218 let path = VPath::new("/x", root);
219
220 let r = w.release(path, Increment::Minor, all_features, false);
222
223 assert!(r.is_ok());
225 }
226
227 #[rstest]
228 #[case::all_features(true)]
229 #[case::default_features(false)]
230 #[trace]
231 fn release_crate(root: VfsPath, #[case] all_features: bool) {
232 let mut mock_pub = MockPublisher::new();
234 let mut mock_vcs = MockVcs::new();
235
236 mock_vcs
237 .expect_commit()
238 .with(eq("/x"), eq("changelog: v0.2.0"))
239 .times(1)
240 .returning(|_, _| Ok(()));
241
242 let options: PublishOptions = PublishOptions {
243 crate_to_publish: None,
244 all_features,
245 no_verify: false,
246 };
247 mock_pub
248 .expect_publish()
249 .withf(move |p, o| p == "/x" && *o == options)
250 .times(1)
251 .returning(|_, _| Ok(()));
252
253 mock_vcs
254 .expect_create_tag()
255 .with(eq("/x"), eq("v0.2.0"))
256 .times(1)
257 .returning(|_, _| Ok(()));
258
259 mock_vcs
260 .expect_push_tag()
261 .with(eq("/x"), eq("v0.2.0"))
262 .times(1)
263 .returning(|_, _| Ok(()));
264
265 let c = Crate::new(mock_pub, mock_vcs);
266
267 let path = VPath::new("/x", root.join("solp").unwrap());
268
269 let r = c.release(path, Increment::Minor, all_features, false);
271
272 assert!(r.is_ok());
274 }
275
276 #[fixture]
277 fn root() -> VfsPath {
278 let root = VfsPath::new(MemoryFS::new());
279
280 root.join("solv").unwrap().create_dir().unwrap();
281 root.join("solp").unwrap().create_dir().unwrap();
282 root.join(CARGO_CONFIG)
283 .unwrap()
284 .create_file()
285 .unwrap()
286 .write_all(WKS.as_bytes())
287 .unwrap();
288
289 let ch_fn = |c: &str, d: &str| {
290 let ch_conf = root.join(c).unwrap().join(CARGO_CONFIG).unwrap();
291 ch_conf
292 .create_file()
293 .unwrap()
294 .write_all(d.as_bytes())
295 .unwrap();
296 };
297
298 ch_fn("solv", SOLV);
299 ch_fn("solp", SOLP);
300
301 root
302 }
303
304 const WKS: &str = r#"
305[workspace]
306
307members = [
308 "solv",
309 "solp",
310]
311 "#;
312
313 const SOLP: &str = r#"
314[package]
315name = "solp"
316description = "Microsoft Visual Studio solution parsing library"
317repository = "https://github.com/aegoroff/solv"
318version = "0.1.13"
319authors = ["egoroff <egoroff@gmail.com>"]
320edition = "2018"
321license = "MIT"
322workspace = ".."
323
324# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
325
326[build-dependencies] # <-- We added this and everything after!
327lalrpop = "0.19"
328
329[dependencies]
330lalrpop-util = "0.19"
331regex = "1"
332jwalk = "0.6"
333phf = { version = "0.8", features = ["macros"] }
334itertools = "0.10"
335
336 "#;
337
338 const SOLV: &str = r#"
339[package]
340name = "solv"
341description = "Microsoft Visual Studio solution validator"
342repository = "https://github.com/aegoroff/solv"
343version = "0.1.13"
344authors = ["egoroff <egoroff@gmail.com>"]
345edition = "2018"
346license = "MIT"
347workspace = ".."
348
349# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
350
351[dependencies]
352prettytable-rs = "^0.8"
353ansi_term = "0.12"
354humantime = "2.1"
355clap = "2"
356fnv = "1"
357solp = { path = "../solp/", version = "0.1.13" }
358
359 "#;
360}