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 build_dfx_all_with_env(workspace_root, lock_relative_path, network, profile, &[]);
46}
47
48pub fn build_dfx_all_with_env(
51 workspace_root: &Path,
52 lock_relative_path: &str,
53 network: &str,
54 profile: WasmBuildProfile,
55 extra_env: &[(&str, &str)],
56) {
57 let output = run_dfx_build_with_lock(
58 workspace_root,
59 lock_relative_path,
60 network,
61 profile,
62 extra_env,
63 );
64 assert!(
65 output.status.success(),
66 "dfx build --all failed: {}",
67 String::from_utf8_lossy(&output.stderr)
68 );
69}
70
71fn newest_watched_input_mtime(
73 workspace_root: &Path,
74 watched_relative_paths: &[&str],
75) -> io::Result<SystemTime> {
76 let mut newest = SystemTime::UNIX_EPOCH;
77
78 for relative in watched_relative_paths {
79 let path = workspace_root.join(relative);
80 newest = newest.max(newest_path_mtime(&path)?);
81 }
82
83 Ok(newest)
84}
85
86fn newest_path_mtime(path: &Path) -> io::Result<SystemTime> {
88 let metadata = fs::metadata(path)?;
89 let mut newest = metadata.modified()?;
90
91 if metadata.is_dir() {
92 for entry in fs::read_dir(path)? {
93 let entry = entry?;
94 newest = newest.max(newest_path_mtime(&entry.path())?);
95 }
96 }
97
98 Ok(newest)
99}
100
101fn run_dfx_build_with_lock(
104 workspace_root: &Path,
105 lock_relative_path: &str,
106 network: &str,
107 profile: WasmBuildProfile,
108 extra_env: &[(&str, &str)],
109) -> Output {
110 let lock_file = workspace_root.join(lock_relative_path);
111 let target_dir = workspace_root.join("target/dfx-build");
112 if let Some(parent) = lock_file.parent() {
113 let _ = fs::create_dir_all(parent);
114 }
115 let _ = fs::create_dir_all(&target_dir);
116
117 let mut flock = Command::new("flock");
118 flock
119 .current_dir(workspace_root)
120 .arg(lock_file.as_os_str())
121 .arg("bash")
122 .env("DFX_NETWORK", network)
123 .env("RELEASE", profile.dfx_release_value())
124 .env("CARGO_TARGET_DIR", &target_dir)
125 .args([
126 "-lc",
127 "dfx canister create --all -qq >/dev/null 2>&1 || true\n\
128 dfx build --all",
129 ]);
130 for (key, value) in extra_env {
131 flock.env(key, value);
132 }
133
134 match flock.output() {
135 Ok(output) => output,
136 Err(err) if err.kind() == io::ErrorKind::NotFound => {
137 run_dfx_build(workspace_root, network, profile, extra_env)
138 }
139 Err(err) => panic!("failed to run `flock` for `dfx build --all`: {err}"),
140 }
141}
142
143fn run_dfx_build(
146 workspace_root: &Path,
147 network: &str,
148 profile: WasmBuildProfile,
149 extra_env: &[(&str, &str)],
150) -> Output {
151 let target_dir = workspace_root.join("target/dfx-build");
152 let _ = fs::create_dir_all(&target_dir);
153
154 let mut create = Command::new("dfx");
155 create
156 .current_dir(workspace_root)
157 .env("DFX_NETWORK", network)
158 .env("RELEASE", profile.dfx_release_value())
159 .env("CARGO_TARGET_DIR", &target_dir)
160 .args(["canister", "create", "--all", "-qq"]);
161 for (key, value) in extra_env {
162 create.env(key, value);
163 }
164 let _ = create.output();
165
166 let mut build = Command::new("dfx");
167 build
168 .current_dir(workspace_root)
169 .env("DFX_NETWORK", network)
170 .env("RELEASE", profile.dfx_release_value())
171 .env("CARGO_TARGET_DIR", &target_dir)
172 .args(["build", "--all"]);
173 for (key, value) in extra_env {
174 build.env(key, value);
175 }
176
177 build.output().expect("failed to run `dfx build --all`")
178}