canic_testkit/artifacts/
dfx.rs1use super::wasm::WasmBuildProfile;
2use std::{
3 fs, io,
4 path::Path,
5 process::{Command, Output},
6 time::SystemTime,
7};
8
9pub fn artifact_is_fresh_against_inputs(
11 workspace_root: &Path,
12 artifact_path: &Path,
13 watched_relative_paths: &[&str],
14) -> io::Result<bool> {
15 let artifact_mtime = fs::metadata(artifact_path)?.modified()?;
16 let newest_input = newest_watched_input_mtime(workspace_root, watched_relative_paths)?;
17 Ok(newest_input <= artifact_mtime)
18}
19
20#[must_use]
22pub fn dfx_artifact_ready(
23 workspace_root: &Path,
24 artifact_relative_path: &str,
25 watched_relative_paths: &[&str],
26) -> bool {
27 let artifact_path = workspace_root.join(artifact_relative_path);
28
29 match fs::metadata(&artifact_path) {
30 Ok(meta) if meta.is_file() && meta.len() > 0 => {
31 artifact_is_fresh_against_inputs(workspace_root, &artifact_path, watched_relative_paths)
32 .unwrap_or(false)
33 }
34 _ => false,
35 }
36}
37
38pub fn build_dfx_all(
40 workspace_root: &Path,
41 lock_relative_path: &str,
42 network: &str,
43 profile: WasmBuildProfile,
44) {
45 let output = run_dfx_build_with_lock(workspace_root, lock_relative_path, network, profile);
46 assert!(
47 output.status.success(),
48 "dfx build --all failed: {}",
49 String::from_utf8_lossy(&output.stderr)
50 );
51}
52
53fn newest_watched_input_mtime(
55 workspace_root: &Path,
56 watched_relative_paths: &[&str],
57) -> io::Result<SystemTime> {
58 let mut newest = SystemTime::UNIX_EPOCH;
59
60 for relative in watched_relative_paths {
61 let path = workspace_root.join(relative);
62 newest = newest.max(newest_path_mtime(&path)?);
63 }
64
65 Ok(newest)
66}
67
68fn newest_path_mtime(path: &Path) -> io::Result<SystemTime> {
70 let metadata = fs::metadata(path)?;
71 let mut newest = metadata.modified()?;
72
73 if metadata.is_dir() {
74 for entry in fs::read_dir(path)? {
75 let entry = entry?;
76 newest = newest.max(newest_path_mtime(&entry.path())?);
77 }
78 }
79
80 Ok(newest)
81}
82
83fn run_dfx_build_with_lock(
86 workspace_root: &Path,
87 lock_relative_path: &str,
88 network: &str,
89 profile: WasmBuildProfile,
90) -> Output {
91 let lock_file = workspace_root.join(lock_relative_path);
92 let target_dir = workspace_root.join("target/dfx-build");
93 if let Some(parent) = lock_file.parent() {
94 let _ = fs::create_dir_all(parent);
95 }
96 let _ = fs::create_dir_all(&target_dir);
97
98 match Command::new("flock")
99 .current_dir(workspace_root)
100 .arg(lock_file.as_os_str())
101 .arg("bash")
102 .env("DFX_NETWORK", network)
103 .env("RELEASE", profile.dfx_release_value())
104 .env("CARGO_TARGET_DIR", &target_dir)
105 .args([
106 "-lc",
107 "dfx canister create --all -qq >/dev/null 2>&1 || true\n\
108 dfx build --all",
109 ])
110 .output()
111 {
112 Ok(output) => output,
113 Err(err) if err.kind() == io::ErrorKind::NotFound => {
114 run_dfx_build(workspace_root, network, profile)
115 }
116 Err(err) => panic!("failed to run `flock` for `dfx build --all`: {err}"),
117 }
118}
119
120fn run_dfx_build(workspace_root: &Path, network: &str, profile: WasmBuildProfile) -> Output {
123 let target_dir = workspace_root.join("target/dfx-build");
124 let _ = fs::create_dir_all(&target_dir);
125
126 let _ = Command::new("dfx")
127 .current_dir(workspace_root)
128 .env("DFX_NETWORK", network)
129 .env("RELEASE", profile.dfx_release_value())
130 .env("CARGO_TARGET_DIR", &target_dir)
131 .args(["canister", "create", "--all", "-qq"])
132 .output();
133
134 Command::new("dfx")
135 .current_dir(workspace_root)
136 .env("DFX_NETWORK", network)
137 .env("RELEASE", profile.dfx_release_value())
138 .env("CARGO_TARGET_DIR", &target_dir)
139 .args(["build", "--all"])
140 .output()
141 .expect("failed to run `dfx build --all`")
142}