1use std::path::{Path, PathBuf};
4
5use serde::{Deserialize, Serialize};
6use tempfile::tempdir;
7use walkdir::WalkDir;
8
9use crate::{
10 action::{ActionError, Context},
11 action_impl::{rust_toolchain_versions, spawn, spawn_in, ActionImpl},
12 util::{copy_file, mkdir, UtilError},
13};
14
15#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
17pub struct CargoFetch;
18
19impl ActionImpl for CargoFetch {
20 fn execute(&self, context: &mut Context) -> Result<(), ActionError> {
21 rust_toolchain_versions(context)?;
22 let tmp = tempdir().map_err(CargoError::TempDir)?;
23 let dest = tmp.path();
24 copy_partial_tree(context.source_dir(), dest, |path| {
25 if let Some(name) = path.file_name().map(|s| s.as_encoded_bytes()) {
26 name == b"Cargo.toml"
27 || name == b"Cargo.lock"
28 || (name.ends_with(b".rs") && name != b"build.rs")
29 } else {
30 false
31 }
32 })?;
33
34 let lockfile = dest.join("Cargo.lock");
35 let deny1 = dest.join("deny.toml");
36 let deny2 = dest.join(".cargo/deny.toml");
37 let deny = deny1.exists() || deny2.exists();
38 if lockfile.exists() {
39 spawn_in(context, &["cargo", "fetch", "--locked"], dest.to_path_buf())?;
40 if deny {
41 spawn_in(
42 context,
43 &["cargo", "deny", "--locked", "fetch"],
44 dest.to_path_buf(),
45 )?;
46 }
47 } else {
48 spawn_in(context, &["cargo", "fetch"], dest.to_path_buf())?;
49 if deny {
50 spawn_in(
51 context,
52 &["cargo", "deny", "--locked", "fetch"],
53 dest.to_path_buf(),
54 )?;
55 }
56 }
57
58 Ok(())
59 }
60}
61
62#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
64pub struct CargoFmt;
65
66impl ActionImpl for CargoFmt {
67 fn execute(&self, context: &mut Context) -> Result<(), ActionError> {
68 rust_toolchain_versions(context)?;
69 spawn(context, &["cargo", "fmt", "--check"])?;
70 Ok(())
71 }
72}
73
74#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
78pub struct CargoClippy;
79
80impl ActionImpl for CargoClippy {
81 fn execute(&self, context: &mut Context) -> Result<(), ActionError> {
82 rust_toolchain_versions(context)?;
83 spawn(
84 context,
85 &[
86 "cargo",
87 "clippy",
88 "--offline",
89 "--locked",
90 "--workspace",
91 "--all-targets",
92 "--no-deps",
93 "--",
94 "--deny",
95 "warnings",
96 ],
97 )?;
98 Ok(())
99 }
100}
101
102#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
104pub struct CargoDeny;
105
106impl ActionImpl for CargoDeny {
107 fn execute(&self, context: &mut Context) -> Result<(), ActionError> {
108 rust_toolchain_versions(context)?;
109 spawn(
110 context,
111 &[
112 "cargo",
113 "deny",
114 "--offline",
115 "--locked",
116 "--workspace",
117 "check",
118 ],
119 )?;
120 Ok(())
121 }
122}
123
124#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
126pub struct CargoDoc;
127
128impl ActionImpl for CargoDoc {
129 fn execute(&self, context: &mut Context) -> Result<(), ActionError> {
130 rust_toolchain_versions(context)?;
131 spawn(
132 context,
133 &[
134 "env",
135 "RUSTDOCFLAGS=-D warnings",
136 "cargo",
137 "doc",
138 "--workspace",
139 ],
140 )?;
141 Ok(())
142 }
143}
144
145#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
149pub struct CargoBuild;
150
151impl ActionImpl for CargoBuild {
152 fn execute(&self, context: &mut Context) -> Result<(), ActionError> {
153 rust_toolchain_versions(context)?;
154 spawn(
155 context,
156 &[
157 "cargo",
158 "build",
159 "--offline",
160 "--locked",
161 "--workspace",
162 "--all-targets",
163 ],
164 )?;
165 Ok(())
166 }
167}
168
169#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
171pub struct CargoTest;
172
173impl ActionImpl for CargoTest {
174 fn execute(&self, context: &mut Context) -> Result<(), ActionError> {
175 rust_toolchain_versions(context)?;
176 spawn(
177 context,
178 &["cargo", "test", "--offline", "--locked", "--workspace"],
179 )?;
180 Ok(())
181 }
182}
183
184#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
186pub struct CargoInstall;
187
188impl ActionImpl for CargoInstall {
189 fn execute(&self, context: &mut Context) -> Result<(), ActionError> {
190 rust_toolchain_versions(context)?;
191 let artifacts = context.artifacts_dir().to_string_lossy().to_string();
192 spawn(
193 context,
194 &[
195 "cargo",
196 "install",
197 "--offline",
198 "--locked",
199 "--bins",
200 "--path=.",
201 "--root",
202 &artifacts,
203 ],
204 )?;
205 Ok(())
206 }
207}
208
209fn copy_partial_tree<P, PP, F>(src: P, dest: PP, wanted: F) -> Result<(), CargoError>
210where
211 P: AsRef<Path>,
212 PP: AsRef<Path>,
213 F: Fn(&Path) -> bool,
214{
215 let src = src.as_ref();
216 let dest = dest.as_ref();
217
218 mkdir(dest)?;
219 for e in WalkDir::new(src) {
220 let path = e
221 .map_err(|err| CargoError::CopyTreeWalkDir(src.into(), err))?
222 .path()
223 .to_path_buf();
224 if wanted(&path) {
225 let dest = dest.join(path.strip_prefix(src).unwrap_or(&path));
226 if let Some(parent) = dest.parent() {
227 if !parent.exists() {
228 mkdir(parent)?;
229 }
230 }
231 copy_file(&path, &dest)?;
232 }
233 }
234 Ok(())
235}
236
237#[derive(Debug, thiserror::Error)]
239pub enum CargoError {
240 #[error(transparent)]
242 Util(#[from] UtilError),
243
244 #[error("failed to list contents of upload directory")]
246 WalkDir(#[source] walkdir::Error),
247
248 #[error("no *.changes file built for deb project")]
250 NoChanges,
251
252 #[error("more than one *.changes file built for deb project")]
254 ManyChanges,
255
256 #[error("failed to list files in directory {0} when copying files")]
258 CopyTreeWalkDir(PathBuf, #[source] walkdir::Error),
259
260 #[error("failed to create a temporary directory")]
262 TempDir(#[source] std::io::Error),
263}
264
265impl From<CargoError> for ActionError {
266 fn from(value: CargoError) -> Self {
267 Self::Cargo(value)
268 }
269}