1use {
2 crate::*,
3 lazy_regex::regex_replace_all,
4 rustc_hash::FxHashSet,
5 std::{
6 collections::HashMap,
7 path::PathBuf,
8 },
9};
10
11#[derive(Debug)]
14pub struct Mission<'s> {
15 pub location_name: String,
16 pub concrete_job_ref: ConcreteJobRef,
17 pub execution_directory: PathBuf,
18 pub package_directory: PathBuf,
19 pub workspace_directory: Option<PathBuf>,
20 pub job: Job,
21 pub paths_to_watch: Vec<PathBuf>,
22 pub settings: &'s Settings,
23}
24
25impl Mission<'_> {
26 pub fn ignorer(&self) -> IgnorerSet {
28 let mut set = IgnorerSet::default();
29 if self.job.apply_gitignore != Some(false) {
30 match GitIgnorer::new(&self.package_directory) {
31 Ok(git_ignorer) => {
32 set.add(Box::new(git_ignorer));
33 }
34 Err(e) => {
35 debug!("Failed to initialise git ignorer: {e}");
37 }
38 }
39 }
40 if !self.job.ignore.is_empty() {
41 let mut glob_ignorer = GlobIgnorer::default();
42 for pattern in &self.job.ignore {
43 if let Some(override_pattern) = pattern.strip_prefix('!') {
44 if let Err(e) = set.add_override(override_pattern, &self.package_directory) {
47 warn!("Failed to add override pattern {pattern}: {e}");
48 }
49 } else if let Err(e) = glob_ignorer.add(pattern, &self.package_directory) {
50 warn!("Failed to add ignore pattern {pattern}: {e}");
51 }
52 }
53 set.add(Box::new(glob_ignorer));
54 }
55 set
56 }
57
58 pub fn is_success(
59 &self,
60 report: &Report,
61 ) -> bool {
62 report.is_success(self.job.allow_warnings(), self.job.allow_failures())
63 }
64
65 pub fn make_absolute(
66 &self,
67 path: PathBuf,
68 ) -> PathBuf {
69 if path.is_absolute() {
70 return path;
71 }
72 if let Some(workspace) = &self.workspace_directory {
76 let workspace_joined = workspace.join(&path);
77 if workspace_joined.exists() {
78 return workspace_joined;
79 }
80 }
81 self.package_directory.join(&path)
82 }
83
84 pub fn get_command(&self) -> anyhow::Result<CommandBuilder> {
86 let mut command = if self.job.expand_env_vars() {
87 self.job
88 .command
89 .iter()
90 .map(|token| {
91 regex_replace_all!(r"\$([A-Z0-9a-z_]+)", token, |whole: &str, name| {
92 match std::env::var(name) {
93 Ok(value) => value,
94 Err(_) => {
95 warn!("variable {whole} not found in env");
96 whole.to_string()
97 }
98 }
99 })
100 .to_string()
101 })
102 .collect()
103 } else {
104 self.job.command.clone()
105 };
106
107 if command.is_empty() {
108 anyhow::bail!(
109 "Empty command in job {}",
110 self.concrete_job_ref.badge_label()
111 );
112 }
113
114 let scope = &self.concrete_job_ref.scope;
115 if scope.has_tests() && command.len() > 2 {
116 let tests = if command[0] == "cargo" && command[1] == "test" {
117 &scope.tests[..1]
120 } else {
121 &scope.tests
122 };
123 for test in tests {
124 command.push(test.clone());
125 }
126 }
127
128 let mut tokens = command.iter();
129 let mut command = CommandBuilder::new(
130 tokens.next().unwrap(), );
132 command.with_stdout(self.job.need_stdout());
133 let envs: HashMap<&String, &String> = self
134 .settings
135 .all_jobs
136 .env
137 .iter()
138 .chain(self.job.env.iter())
139 .collect();
140 if !self.job.extraneous_args() {
141 command.args(tokens);
142 command.current_dir(&self.execution_directory);
143 command.envs(envs);
144 debug!("command: {:#?}", &command);
145 return Ok(command);
146 }
147
148 let mut no_default_features_done = false;
149 let mut features_done = false;
150 let mut last_is_features = false;
151 let mut tokens = tokens.chain(&self.settings.additional_job_args);
152 let mut has_double_dash = false;
153 for arg in tokens.by_ref() {
154 if arg == "--" {
155 has_double_dash = true;
159 break;
160 }
161 if last_is_features {
162 if self.settings.all_features {
163 debug!("ignoring features given along --all-features");
164 } else {
165 features_done = true;
166 match (&self.settings.features, self.settings.no_default_features) {
168 (Some(features), false) => {
169 command.arg("--features");
171 command.arg(merge_features(arg, features));
172 }
173 (Some(features), true) => {
174 command.arg("--features");
176 command.arg(features);
177 }
178 (None, true) => {
179 }
181 (None, false) => {
182 command.arg("--features");
184 command.arg(arg);
185 }
186 }
187 }
188 last_is_features = false;
189 } else if arg == "--no-default-features" {
190 no_default_features_done = true;
191 last_is_features = false;
192 command.arg(arg);
193 } else if arg == "--features" {
194 last_is_features = true;
195 } else {
196 command.arg(arg);
197 }
198 }
199 if self.settings.no_default_features && !no_default_features_done {
200 command.arg("--no-default-features");
201 }
202 if self.settings.all_features {
203 command.arg("--all-features");
204 }
205 if !features_done {
206 if let Some(features) = &self.settings.features {
207 if self.settings.all_features {
208 debug!("not using features because of --all-features");
209 } else {
210 command.arg("--features");
211 command.arg(features);
212 }
213 }
214 }
215 if has_double_dash {
216 command.arg("--");
217 for arg in tokens {
218 command.arg(arg);
219 }
220 }
221 command.current_dir(&self.execution_directory);
222 command.envs(envs);
223 debug!("command builder: {:#?}", &command);
224 Ok(command)
225 }
226
227 pub fn kill_command(&self) -> Option<Vec<String>> {
228 self.job.kill.clone()
229 }
230
231 pub fn need_stdout(&self) -> bool {
233 self.job
234 .need_stdout
235 .or(self.settings.all_jobs.need_stdout)
236 .unwrap_or(false)
237 }
238
239 pub fn analyzer(&self) -> AnalyzerRef {
240 self.job.analyzer.unwrap_or_default()
241 }
242
243 pub fn ignored_lines_patterns(&self) -> Option<&Vec<LinePattern>> {
244 self.job
245 .ignored_lines
246 .as_ref()
247 .or(self.settings.all_jobs.ignored_lines.as_ref())
248 .filter(|p| !p.is_empty())
249 }
250
251 pub fn sound_player_if_needed(&self) -> Option<SoundPlayer> {
252 if self.job.sound.is_enabled() {
253 match SoundPlayer::new(self.job.sound.get_base_volume()) {
254 Ok(sound_player) => Some(sound_player),
255 Err(e) => {
256 warn!("Failed to initialise sound player: {e}");
257 None
258 }
259 }
260 } else {
261 None
262 }
263 }
264}
265
266fn merge_features(
267 a: &str,
268 b: &str,
269) -> String {
270 let mut features = FxHashSet::default();
271 for feature in a.split(',') {
272 features.insert(feature);
273 }
274 for feature in b.split(',') {
275 features.insert(feature);
276 }
277 features.iter().copied().collect::<Vec<&str>>().join(",")
278}