cargo_artifact_dependency/
lib.rs1mod error;
42mod install_root;
43mod utils;
44
45#[cfg(test)]
46mod network_tests;
47#[cfg(test)]
48mod tests;
49
50use std::{
51 io,
52 path::{Path, PathBuf},
53};
54
55use apply_if::ApplyIf;
56use cargo_install::CargoInstallBuilder;
57use derive_builder::Builder;
58
59pub use crate::error::{Error, Result};
60use crate::utils::{cargo_install_version_req, executable_name, files_in_dir};
61
62#[derive(Clone, Debug, PartialEq, Default, Eq)]
63pub enum BuildProfile {
64 Debug,
65 #[default]
66 Release,
67 Custom(String),
68}
69
70#[derive(Builder, Clone, Debug, PartialEq, Eq)]
75#[builder(
76 pattern = "owned",
77 setter(into, strip_option),
78 build_fn(validate = "Self::validate")
79)]
80pub struct ArtifactDependency {
81 pub crate_name: String,
83 #[builder(default)]
84 pub version: Option<String>,
86 #[builder(default)]
87 pub path: Option<PathBuf>,
89 #[builder(default)]
90 pub bin_name: Option<String>,
92 #[builder(default)]
93 pub profile: BuildProfile,
95 #[builder(default)]
96 pub target: Option<String>,
98 #[builder(default = "true")]
99 pub locked: bool,
101}
102
103impl Default for ArtifactDependency {
104 fn default() -> Self {
105 Self {
106 crate_name: String::new(),
107 version: None,
108 path: None,
109 bin_name: None,
110 profile: BuildProfile::Release,
111 target: None,
112 locked: true,
113 }
114 }
115}
116
117impl ArtifactDependencyBuilder {
118 fn validate(&self) -> std::result::Result<(), String> {
119 if let Some(Some(path)) = &self.path
120 && !path.is_absolute()
121 {
122 return Err(format!("path must be absolute: `{}`", path.display()));
123 }
124
125 Ok(())
126 }
127}
128
129impl ArtifactDependency {
130 pub fn resolve(&self) -> Result<PathBuf> {
132 let install_root = self.install_root();
133
134 if let Some(artifact_path) = cached_artifact(&install_root, self.bin_name.as_deref())? {
135 return Ok(artifact_path);
136 }
137
138 CargoInstallBuilder::default()
139 .crate_name(&self.crate_name)
140 .root(&install_root)
141 .locked(self.locked)
142 .apply_if_some(self.version.as_deref(), |builder, version_req| {
143 builder.version(cargo_install_version_req(version_req).into_owned())
144 })
145 .apply_if_some(self.path(), |builder, path| builder.path(path))
146 .apply_if_some(self.bin_name.as_deref(), |builder, bin_name| {
147 builder.bin(bin_name)
148 })
149 .apply_if_some(self.target.as_deref(), |builder, target| {
150 builder.target(target)
151 })
152 .apply_if(matches!(self.profile, BuildProfile::Debug), |builder| {
153 builder.debug(true)
154 })
155 .apply_if_some(
156 match &self.profile {
157 BuildProfile::Custom(profile) => Some(profile.as_str()),
158 _ => None,
159 },
160 |builder, profile| builder.profile(profile),
161 )
162 .build()
163 .expect("CargoInstallBuilder should not fail with optional-only fields")
164 .run()?;
165
166 find_artifact(&install_root, self.bin_name.as_deref())
167 }
168}
169
170fn cached_artifact(install_root: &Path, bin_name: Option<&str>) -> Result<Option<PathBuf>> {
172 match find_artifact(install_root, bin_name) {
173 Ok(artifact_path) => Ok(Some(artifact_path)),
174 Err(Error::Io(err)) if err.kind() == io::ErrorKind::NotFound => Ok(None),
175 Err(Error::NoInstalledBinaries { .. } | Error::InvalidArtifactPath { .. }) => Ok(None),
176 Err(err) => Err(err),
177 }
178}
179
180fn find_artifact(install_root: &Path, bin_name: Option<&str>) -> Result<PathBuf> {
182 let bin_dir = install_root.join("bin");
183
184 match bin_name {
185 Some(bin_name) => find_binary_with_name(bin_dir, bin_name),
186 None => find_single_binary(bin_dir),
187 }
188}
189
190fn find_binary_with_name(dir: PathBuf, name: &str) -> Result<PathBuf> {
191 let artifact_path = dir.join(executable_name(name));
192 if artifact_path.is_file() {
193 Ok(artifact_path)
194 } else {
195 Err(Error::InvalidArtifactPath {
196 path: artifact_path,
197 })
198 }
199}
200
201fn find_single_binary(dir: PathBuf) -> Result<PathBuf> {
203 let mut binaries = files_in_dir(&dir)?;
204
205 match binaries.len() {
206 0 => Err(Error::NoInstalledBinaries { dir }),
207 1 => Ok(binaries.remove(0)),
208 _ => Err(Error::AmbiguousInstalledBinaries),
209 }
210}