1use crate::*;
2use std::{
3 collections::HashSet,
4 path::{Path, PathBuf},
5};
6
7pub fn exec_cargo_task() {
9 task::check_pre_env_task();
11
12 if env_loader::load().is_err() {
14 ct_fatal!(
15 r"ERROR: Could not find '{}' directory.
16Have you run 'cargo task ct-init'?",
17 CARGO_TASK_DIR,
18 );
19 }
20
21 let mut env = _cargo_task_util::ct_env();
23
24 ct_info!("cargo-task running...");
25
26 clean_build_workspace(&env);
27 let mut did_build_workspace = false;
28
29 let mut task_list = Vec::new();
31 for (task, task_meta) in env.tasks.iter() {
32 if task_meta.bootstrap {
33 fill_task_deps(
34 &env,
35 &mut task_list,
36 task.to_string(),
37 HashSet::new(),
38 );
39 }
40 }
41
42 if !task_list.is_empty() {
44 ct_info!("executing bootstrap list: {:?}", task_list);
45 for task in task_list {
46 if !task::check_system_task(task.as_str(), &env) {
47 task::ct_init();
49
50 run_task(&env, &task, &mut did_build_workspace);
51 }
52 }
53 ct_info!("reloading env post-bootstrap");
54 if env_loader::load().is_err() {
55 ct_fatal!(
56 r"ERROR: Could not find '{}' directory.
57 Have you run 'cargo task ct-init'?",
58 CARGO_TASK_DIR,
59 );
60 }
61 clean_build_workspace(&env);
62 did_build_workspace = false;
63
64 env = _cargo_task_util::ct_force_new_env();
65 }
66
67 let mut task_list = Vec::new();
69 for task in env.task_list.iter() {
70 fill_task_deps(&env, &mut task_list, task.to_string(), HashSet::new());
71 }
72
73 if task_list.is_empty() {
75 for (task, task_meta) in env.tasks.iter() {
76 if task_meta.default {
77 fill_task_deps(
78 &env,
79 &mut task_list,
80 task.to_string(),
81 HashSet::new(),
82 );
83 }
84 }
85 }
86
87 ct_info!("task order: {:?}", task_list);
88
89 for task in task_list {
90 if !task::check_system_task(task.as_str(), &env) {
91 task::ct_init();
93
94 run_task(&env, &task, &mut did_build_workspace);
95 }
96 }
97
98 clean_build_workspace(&env);
99
100 ct_info!("cargo-task complete : )");
101}
102
103fn fill_task_deps(
105 env: &_cargo_task_util::CTEnv,
106 task_list: &mut Vec<String>,
107 task: String,
108 mut visited: HashSet<String>,
109) {
110 visited.insert(task.clone());
111 if !env.tasks.contains_key(&task) {
112 if !task_list.contains(&task) {
114 task_list.push(task);
115 }
116 return;
117 }
118 for dep in env.tasks.get(&task).unwrap().task_deps.iter() {
119 if visited.contains(dep) {
120 ct_fatal!("circular task dependency within {:?}", visited);
121 }
122 fill_task_deps(env, task_list, dep.to_string(), visited.clone());
123 }
124 if !task_list.contains(&task) {
125 task_list.push(task);
126 }
127}
128
129fn clean_build_workspace(env: &_cargo_task_util::CTEnv) {
131 let mut ws = env.cargo_task_target.clone();
132 ws.push("ct-workspace");
133 let _ = std::fs::remove_dir_all(&ws);
134}
135
136fn generate_build_workspace(env: &_cargo_task_util::CTEnv) {
138 let mut all_tasks = Vec::new();
139 let mut ws = env.cargo_task_target.clone();
140 ws.push("ct-workspace");
141 ct_check_fatal!(std::fs::create_dir_all(&ws));
142
143 let mut ctu_src = env.cargo_task_path.clone();
145 ctu_src.push("cargo_task_util");
146 let mut ctu_dest = ws.clone();
147 ctu_dest.push("cargo_task_util");
148 if let Ok(meta) = std::fs::metadata(&ctu_src) {
149 if meta.is_dir() {
150 copy_dir(&ctu_src, &ctu_dest);
151 }
152 }
153
154 for (task, task_meta) in env.tasks.iter() {
155 all_tasks.push(task);
156
157 let mut task_dir = ws.clone();
158 task_dir.push(task);
159
160 if task_meta.is_script {
161 ct_check_fatal!(std::fs::create_dir_all(&task_dir));
162 let mut cargo_toml = task_dir.clone();
163 cargo_toml.push("Cargo.toml");
164 let deps = if let Some(deps) = &task_meta.cargo_deps {
165 deps
166 } else {
167 ""
168 };
169 ct_check_fatal!(std::fs::write(
170 &cargo_toml,
171 format!(
172 r#"[package]
173name = "{}"
174version = "0.0.1"
175edition = "2018"
176
177[dependencies]
178cargo_task_util = "*"
179{}
180"#,
181 task, deps,
182 )
183 ));
184 let mut src_dir = task_dir.clone();
185 src_dir.push("src");
186 ct_check_fatal!(std::fs::create_dir_all(&src_dir));
187 let mut main_file = src_dir;
188 main_file.push("main.rs");
189 ct_check_fatal!(std::fs::copy(&task_meta.path, &main_file));
190 } else {
191 copy_dir(&task_meta.path, &task_dir);
192 }
193 }
194
195 let ctu = "cargo_task_util".to_string();
197 all_tasks.push(&ctu);
198
199 ws.push("Cargo.toml");
200 ct_check_fatal!(std::fs::write(
201 &ws,
202 format!(
203 r#"[workspace]
204members = {:?}
205
206[patch.crates-io]
207cargo_task_util = {{ path = "cargo_task_util" }}
208"#,
209 all_tasks
210 ),
211 ));
212}
213
214fn copy_dir<S: AsRef<Path>, D: AsRef<Path>>(src: S, dest: D) {
216 ct_check_fatal!(std::fs::create_dir_all(&dest));
217 for item in ct_check_fatal!(std::fs::read_dir(src)).flatten() {
218 let meta = ct_check_fatal!(item.metadata());
219 let mut dest = dest.as_ref().to_owned();
220 dest.push(item.file_name());
221 if meta.is_dir() {
222 copy_dir(item.path(), &dest);
223 } else if meta.is_file() {
224 ct_check_fatal!(std::fs::copy(item.path(), &dest));
225 }
226 }
227}
228
229fn run_task(
231 env: &_cargo_task_util::CTEnv,
232 task_name: &str,
233 did_build_workspace: &mut bool,
234) {
235 if !env.tasks.contains_key(task_name) {
236 ct_fatal!("invalid task name '{}'", task_name);
237 }
238
239 let task_meta = env.tasks.get(task_name).unwrap();
240 if let Some(min_version) = &task_meta.min_version {
241 if parse_semver(crate::CARGO_TASK_VER) < parse_semver(min_version) {
242 ct_fatal!(
243 "cargo-task {} < required min version {}",
244 crate::CARGO_TASK_VER,
245 min_version,
246 );
247 }
248 }
249
250 let task = task_build(env, task_name, did_build_workspace);
251
252 ct_info!("run task: '{}'", task_name);
253 std::env::set_var("CT_CUR_TASK", task_name);
254
255 let mut cmd = std::process::Command::new(task);
256 cmd.current_dir(&env.work_dir);
257 for arg in env.arg_list.iter() {
258 cmd.arg(arg);
259 }
260 cmd.stdin(std::process::Stdio::piped());
261 let res: Result<(), String> = (move || {
262 let mut child = cmd.spawn().map_err(|e| format!("{:?}", e))?;
263
264 drop(child.stdin.take().unwrap());
266
267 let res: Result<(), String> = (|| {
268 let status = child.wait().map_err(|e| format!("{:?}", e))?;
269
270 if !status.success() {
271 return Err(format!("{} exited non-zero", task_name));
272 }
273
274 Ok(())
275 })();
276
277 let mut p = env.cargo_task_target.clone();
278 let directive_file_name = format!("task-directive-{}.atat", child.id());
279 p.push(directive_file_name);
280
281 if let Ok(file) = std::fs::File::open(&p) {
282 let mut parser = at_at::AtAtParser::new(file);
283 while let Some(res) = parser.parse() {
284 for item in res {
285 if let at_at::AtAtParseItem::KeyValue(k, v) = item {
286 match k.as_str() {
287 "ct-set-env" => {
288 let idx = match v.find('=') {
289 Some(idx) => idx,
290 None => ct_fatal!(
291 "no '=' found in ct-set-env directive"
292 ),
293 };
294 let n = &v[..idx];
295 let v = &v[idx + 1..];
296 std::env::set_var(n, v);
297 ct_info!("CT-SET-ENV: {}={}", n, v);
298 }
299 _ => ct_fatal!("unrecognized AtAt command '{}'", k),
300 }
301 }
302 }
303 }
304 }
305
306 let _ = std::fs::remove_file(&p);
307
308 res
309 })();
310 std::env::remove_var("CT_CUR_TASK");
311
312 if let Err(e) = res {
313 ct_fatal!("{}", e);
314 }
315}
316
317fn task_build(
319 env: &_cargo_task_util::CTEnv,
320 task_name: &str,
321 did_build_workspace: &mut bool,
322) -> PathBuf {
323 let task_meta = env.tasks.get(task_name).unwrap();
324
325 let target_dir = env.cargo_task_target.clone();
326
327 let mut artifact_path = target_dir.clone();
328 artifact_path.push("release");
329 artifact_path.push(task_name);
330
331 if let Ok(meta) = std::fs::metadata(&artifact_path) {
332 let artifact_time = meta
333 .modified()
334 .expect("failed to get artifact modified time");
335 let dir_time = get_newest_time(&task_meta.path);
336
337 if artifact_time >= dir_time {
338 return artifact_path;
339 }
340 }
341
342 ct_info!("build task '{}'", task_name);
343
344 if !*did_build_workspace {
345 *did_build_workspace = true;
346 generate_build_workspace(env);
347 }
348
349 let mut crate_path = env.cargo_task_target.clone();
350 crate_path.push("ct-workspace");
351 crate_path.push(task_name);
352
353 let mut cmd = env.cargo();
354 cmd.arg("build");
355 cmd.arg("--release");
356
357 let mut manifest_path = crate_path;
358 manifest_path.push("Cargo.toml");
359
360 cmd.arg("--manifest-path");
361 cmd.arg(manifest_path);
362
363 cmd.arg("--target-dir");
364 cmd.arg(&target_dir);
365
366 ct_check_fatal!(env.exec(cmd));
367
368 artifact_path
369}
370
371fn get_newest_time<P: AsRef<Path>>(path: P) -> std::time::SystemTime {
373 let mut newest_time = std::time::SystemTime::UNIX_EPOCH;
374
375 if let Ok(metadata) = std::fs::metadata(&path) {
376 if metadata.is_file() {
377 return metadata.modified().expect("failed to get modified time");
378 }
379 }
380
381 for item in std::fs::read_dir(&path)
382 .expect("failed to read directory")
383 .flatten()
384 {
385 let t = item.file_type().expect("failed to get file type");
386
387 if t.is_dir() {
388 let updated = get_newest_time(item.path());
389 if updated > newest_time {
390 newest_time = updated;
391 }
392 } else if t.is_file() {
393 let updated = item
394 .metadata()
395 .expect("failed to get metadata")
396 .modified()
397 .expect("failed to get modified time");
398 if updated > newest_time {
399 newest_time = updated;
400 }
401 }
402 }
403
404 newest_time
405}
406
407fn parse_semver(s: &str) -> (usize, usize, usize) {
409 let r = s.split('.').collect::<Vec<_>>();
410 if r.len() != 3 {
411 ct_fatal!("invalid semver: {}", s);
412 }
413 (
414 r[0].parse().unwrap(),
415 r[1].parse().unwrap(),
416 r[2].parse().unwrap(),
417 )
418}