bomper/replacers/
cargo.rs1use anyhow::anyhow;
2use cargo_metadata::camino::Utf8Path;
3use std::path::Path;
4use std::{io::prelude::*, path::PathBuf, str::FromStr};
5
6use super::file;
7use super::VersionReplacement;
8use crate::config::CargoReplaceMode;
9use crate::error::{Error, Result};
10use crate::replacers::ReplacementBuilder;
11
12pub struct Replacer {
16 lock_path: PathBuf,
17 versions: VersionReplacement,
18 replace_mode: CargoReplaceMode,
19}
20
21impl Replacer {
22 #[must_use]
23 pub fn new(versions: VersionReplacement, replace_mode: CargoReplaceMode) -> Self {
24 Self {
25 lock_path: PathBuf::from("Cargo.lock"),
27 versions,
28 replace_mode,
29 }
30 }
31}
32
33impl ReplacementBuilder for Replacer {
34 fn determine_replacements(self) -> Result<Option<Vec<file::Replacer>>> {
35 let mut replacers = Vec::new();
36
37 let metadata = get_workspace_metadata()?;
38 let workspace_root = &metadata.workspace_root;
39
40 let mut lockfile = cargo_lock::Lockfile::load(&self.lock_path)?;
42
43 let packages = match &self.replace_mode {
44 CargoReplaceMode::Autodetect => metadata.packages,
45 CargoReplaceMode::Packages(packages) => list_packages(&metadata, packages),
46 };
47
48 let new_version = cargo_lock::Version::from_str(&self.versions.new_version)?;
49 let old_version = cargo_lock::Version::from_str(&self.versions.old_version)?;
50
51 let package_names = packages
52 .iter()
53 .map(|package| package.name.as_str().to_string())
54 .collect::<Vec<String>>();
55
56 lockfile.packages.iter_mut().for_each(|package| {
58 let package_name = package.name.as_str().to_string();
59
60 if package_names.contains(&package_name) && package.version == old_version {
61 package.version = new_version.clone();
62 }
63 });
64
65 let new_data = lockfile.to_string().into_bytes();
66
67 let temp_file = tempfile::NamedTempFile::new_in(
68 (self.lock_path)
69 .parent()
70 .ok_or_else(|| Error::InvalidPath(self.lock_path.clone()))?,
71 )?;
72 let mut file = temp_file.as_file();
73 file.write_all(&new_data)?;
74
75 replacers.push(file::Replacer {
76 path: self.lock_path.clone(),
77 temp_file,
78 });
79
80 for package in packages {
82 let replacer =
83 update_package(&package, workspace_root, &self.lock_path, &self.versions)?;
84 if let Some(replacer) = replacer {
85 replacers.push(replacer);
86 };
87 }
88
89 let root_toml_path = workspace_root.join("Cargo.toml");
97 let found_workspace_root = replacers
98 .iter()
99 .find(|replacer| replacer.path == root_toml_path);
100
101 if found_workspace_root.is_none() {
102 let replacer = update_workspace_root(workspace_root, &self.versions)?;
104 if let Some(replacer) = replacer {
105 replacers.push(replacer);
106 };
107 }
108
109 Ok(Some(replacers))
110 }
111}
112
113fn list_packages(
115 metadata: &cargo_metadata::Metadata,
116 names: &[String],
117) -> Vec<cargo_metadata::Package> {
118 metadata
119 .clone()
120 .packages
121 .into_iter()
122 .filter(|package| names.contains(&package.name))
123 .collect()
124}
125
126fn get_workspace_metadata() -> Result<cargo_metadata::Metadata> {
128 let mut metadata_cmd = cargo_metadata::MetadataCommand::new();
129 metadata_cmd.features(cargo_metadata::CargoOpt::AllFeatures);
130 metadata_cmd.no_deps();
131
132 let metadata = metadata_cmd.exec()?;
133
134 Ok(metadata)
135}
136
137fn update_workspace_root(
139 workspace_root: &Utf8Path,
140 versions: &VersionReplacement,
141) -> Result<Option<file::Replacer>> {
142 let cargo_toml_path = workspace_root.join("Cargo.toml");
143 let cargo_toml_path = cargo_toml_path.strip_prefix(workspace_root)?;
144 let cargo_toml_content = std::fs::read(cargo_toml_path)?;
145
146 let mut cargo_toml = cargo_toml::Manifest::from_slice(&cargo_toml_content)?;
147
148 let Some(ref mut workspace) = cargo_toml.workspace else {
149 return Ok(None);
150 };
151 let Some(ref mut workspace_package) = workspace.package else {
152 return Ok(None);
153 };
154
155 if workspace_package.version != Some(versions.old_version.clone()) {
156 return Ok(None);
157 }
158 workspace_package.version = Some(versions.new_version.clone());
159
160 let temp_file = tempfile::NamedTempFile::new_in(
161 (workspace_root)
162 .parent()
163 .ok_or_else(|| Error::Other(anyhow!("Invalid path: {:?}", workspace_root)))?,
164 )?;
165 let mut file = temp_file.as_file();
166
167 let data = toml::to_string(&cargo_toml)?;
168 file.write_all(data.as_bytes())?;
169
170 Ok(Some(file::Replacer {
171 path: cargo_toml_path.into(),
172 temp_file,
173 }))
174}
175
176fn update_package(
178 package: &cargo_metadata::Package,
179 workspace_root: &Utf8Path,
180 lock_path: &Path,
181 versions: &VersionReplacement,
182) -> Result<Option<file::Replacer>> {
183 let cargo_toml_path = package.manifest_path.clone();
184 let cargo_toml_path = cargo_toml_path.strip_prefix(workspace_root)?;
185 let cargo_toml_content = std::fs::read(cargo_toml_path)?;
186
187 let mut cargo_toml = cargo_toml::Manifest::from_slice(&cargo_toml_content)?;
188 {
191 let Some(ref mut toml_package) = cargo_toml.package else {
192 return Err(Error::InvalidCargoToml(cargo_toml_path.into()));
193 };
194
195 let file_version = match &mut toml_package.version {
196 cargo_toml::Inheritable::Inherited { .. } => return Ok(None),
198 cargo_toml::Inheritable::Set(value) => value,
199 };
200
201 if file_version != &versions.old_version {
202 return Ok(None);
203 }
204
205 file_version.clone_from(&versions.new_version);
206 }
207
208 let workspace_root_toml_path = workspace_root.join("Cargo.toml");
211 if cargo_toml_path == workspace_root_toml_path {
212 modify_workspace_root(&mut cargo_toml, versions);
213 }
214
215 let temp_file = tempfile::NamedTempFile::new_in(
216 (lock_path)
217 .parent()
218 .ok_or_else(|| Error::InvalidPath((lock_path).to_path_buf()))?,
219 )?;
220 let mut file = temp_file.as_file();
221
222 let data = toml::to_string(&cargo_toml)?;
223 file.write_all(data.as_bytes())?;
224
225 Ok(Some(file::Replacer {
226 path: cargo_toml_path.into(),
227 temp_file,
228 }))
229}
230
231fn modify_workspace_root(cargo_toml: &mut cargo_toml::Manifest, versions: &VersionReplacement) {
232 let Some(ref mut workspace) = cargo_toml.workspace else {
233 return;
234 };
235 let Some(ref mut workspace_package) = workspace.package else {
236 return;
237 };
238
239 if workspace_package.version != Some(versions.old_version.clone()) {
240 return;
241 }
242 workspace_package.version = Some(versions.new_version.clone());
243}