1use crate::cmd::{
10 format_cmd, get_cmd_stdout_utf8, wait_for_child, RunCommandError,
11};
12use std::env;
13use std::fmt::{self, Display, Formatter};
14use std::path::{Path, PathBuf};
15use std::process::{Command, Stdio};
16
17#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
19pub struct Package {
20 workspace: PathBuf,
23
24 name: String,
26}
27
28impl Package {
29 pub fn new<S>(name: S) -> Self
33 where
34 S: Into<String>,
35 {
36 let workspace = env::current_dir().unwrap();
37 Self::with_workspace(name, workspace)
38 }
39
40 pub fn with_workspace<S, P>(name: S, workspace: P) -> Self
45 where
46 S: Into<String>,
47 P: Into<PathBuf>,
48 {
49 Self {
50 workspace: workspace.into(),
51 name: name.into(),
52 }
53 }
54
55 pub fn name(&self) -> &str {
57 &self.name
58 }
59
60 pub fn workspace(&self) -> &Path {
62 &self.workspace
63 }
64
65 pub fn get_git_tag_name(&self, local_version: &str) -> String {
67 format!("{}-v{}", self.name, local_version)
68 }
69
70 pub fn get_local_version(&self) -> Result<String, GetLocalVersionError> {
73 let mut metadata_cmd = self.get_cargo_metadata_cmd();
76 let metadata_cmd_str = format_cmd(&metadata_cmd);
77 println!("Running: {}", metadata_cmd_str);
78 let mut metadata_proc =
79 metadata_cmd.stdout(Stdio::piped()).spawn().map_err(|err| {
80 GetLocalVersionError::Process(RunCommandError::Launch {
81 cmd: metadata_cmd_str.clone(),
82 err,
83 })
84 })?;
85
86 let pipe = metadata_proc.stdout.take().unwrap();
88
89 let mut jq_cmd = Command::new("jq");
90 jq_cmd.arg("--raw-output");
91 jq_cmd.arg(format!(
92 ".packages[] | select(.name == \"{}\") | .version",
93 self.name
94 ));
95 jq_cmd.stdin(pipe);
96
97 let mut output = get_cmd_stdout_utf8(jq_cmd)
98 .map_err(GetLocalVersionError::Process)?;
99
100 wait_for_child(metadata_proc, metadata_cmd_str)
101 .map_err(GetLocalVersionError::Process)?;
102
103 if output.is_empty() {
104 Err(GetLocalVersionError::PackageNotFound(
105 self.name().to_string(),
106 ))
107 } else {
108 output.pop();
110 Ok(output)
111 }
112 }
113
114 fn get_cargo_metadata_cmd(&self) -> Command {
115 let mut cmd = Command::new("cargo");
116 cmd.arg("metadata");
117 cmd.args(["--format-version", "1"]);
118 cmd.arg("--manifest-path");
119 cmd.arg(self.workspace.join("Cargo.toml"));
120 cmd.arg("--no-deps");
122 cmd
123 }
124}
125
126#[derive(Debug)]
128pub enum GetLocalVersionError {
129 Process(RunCommandError),
131
132 PackageNotFound(String),
134}
135
136impl Display for GetLocalVersionError {
137 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
138 match self {
139 Self::Process(err) => {
140 write!(f, "failed to get cargo metadata: {err}")
141 }
142 Self::PackageNotFound(pkg) => {
143 write!(f, "package {pkg} not found in cargo metadata")
144 }
145 }
146 }
147}
148
149impl std::error::Error for GetLocalVersionError {}