canic_host/build_toolchain/
mod.rs1#[cfg(test)]
8mod tests;
9
10use crate::{
11 binaryen::{
12 BinaryenExecutable, BinaryenToolError, current_binaryen_authority,
13 install_required_binaryen, resolve_required_binaryen,
14 },
15 build_profile::CanisterBuildProfile,
16 ic_wasm::{
17 IcWasmExecutable, IcWasmToolError, current_ic_wasm_authority, install_required_ic_wasm,
18 resolve_required_ic_wasm,
19 },
20};
21use std::{
22 env,
23 ffi::OsStr,
24 path::{Path, PathBuf},
25};
26use thiserror::Error as ThisError;
27
28#[derive(Clone, Debug, Eq, PartialEq)]
35pub struct BuildToolchain {
36 profile: CanisterBuildProfile,
37 ic_wasm: IcWasmExecutable,
38 binaryen: Option<BinaryenExecutable>,
39}
40
41impl BuildToolchain {
42 #[cfg(test)]
44 pub(crate) fn resolve_prepared(
45 profile: CanisterBuildProfile,
46 ) -> Result<Self, BuildToolchainError> {
47 let mut tools = Self::resolve(CanisterBuildProfile::Fast)?;
48 tools.profile = profile;
49 if profile == CanisterBuildProfile::Release {
50 let directories = env::var_os("PATH")
51 .map(|paths| env::split_paths(&paths).collect::<Vec<_>>())
52 .unwrap_or_default();
53 let path = crate::tool_resolution::resolve(Path::new("wasm-opt"), &directories)
54 .map_err(|source| BinaryenToolError::Io {
55 operation: "resolve prepared fixture optimizer",
56 path: PathBuf::from("wasm-opt"),
57 source,
58 })?
59 .ok_or(BinaryenToolError::MissingOptimizerWithoutHome)?;
60 tools.binaryen = Some(crate::binaryen::admit_required_binaryen(&path)?);
61 }
62 Ok(tools)
63 }
64
65 pub fn resolve(profile: CanisterBuildProfile) -> Result<Self, BuildToolchainError> {
67 let ic_wasm = resolve_required_ic_wasm()?;
68 let binaryen = match profile {
69 CanisterBuildProfile::Release => Some(resolve_required_binaryen()?),
70 CanisterBuildProfile::Debug | CanisterBuildProfile::Fast => None,
71 };
72 Ok(Self {
73 profile,
74 ic_wasm,
75 binaryen,
76 })
77 }
78
79 #[must_use]
81 pub fn diagnostic_lines(&self) -> Vec<String> {
82 let mut lines = vec![format!(
83 "ic-wasm: {} ({})",
84 self.ic_wasm.path().display(),
85 self.ic_wasm.version_identity()
86 )];
87 if let Some(binaryen) = &self.binaryen {
88 lines.push(format!(
89 "wasm-opt: {} ({})",
90 binaryen.path().display(),
91 binaryen.version_identity()
92 ));
93 }
94 lines
95 }
96
97 pub(crate) fn require_profile(
98 &self,
99 profile: CanisterBuildProfile,
100 ) -> Result<(), BuildToolchainError> {
101 if self.profile != profile {
102 return Err(BuildToolchainError::ProfileMismatch {
103 resolved: self.profile,
104 requested: profile,
105 });
106 }
107 Ok(())
108 }
109
110 pub(crate) const fn ic_wasm(&self) -> &IcWasmExecutable {
111 &self.ic_wasm
112 }
113
114 pub(crate) const fn binaryen(&self) -> Option<&BinaryenExecutable> {
115 self.binaryen.as_ref()
116 }
117}
118
119#[derive(Clone, Debug, Eq, PartialEq)]
126pub struct InstalledBuildToolchain {
127 ic_wasm: IcWasmExecutable,
128 binaryen: BinaryenExecutable,
129 install_directory: PathBuf,
130}
131
132impl InstalledBuildToolchain {
133 #[must_use]
134 pub const fn ic_wasm(&self) -> &IcWasmExecutable {
135 &self.ic_wasm
136 }
137
138 #[must_use]
139 pub const fn binaryen(&self) -> &BinaryenExecutable {
140 &self.binaryen
141 }
142
143 #[must_use]
144 pub fn install_directory(&self) -> &Path {
145 self.install_directory.as_path()
146 }
147
148 #[must_use]
149 pub fn root_home_warning(&self) -> Option<String> {
150 let home = env::var_os("HOME");
151 root_home_warning_for(home.as_deref(), self.install_directory())
152 }
153}
154
155fn root_home_warning_for(home: Option<&OsStr>, install_directory: &Path) -> Option<String> {
156 home.filter(|home| Path::new(home) == Path::new("/"))
157 .map(|_| {
158 format!(
159 "warning: HOME resolves to `/`; tools were installed under {}. Set HOME to the intended account home and reinstall if this root-level location is accidental.",
160 install_directory.display()
161 )
162 })
163}
164
165#[derive(Debug, ThisError)]
172pub enum BuildToolchainError {
173 #[error(transparent)]
174 Binaryen(#[from] BinaryenToolError),
175
176 #[error(transparent)]
177 IcWasm(#[from] IcWasmToolError),
178
179 #[error(
180 "build toolchain was resolved for {resolved:?}, but artifact finalization requested {requested:?}"
181 )]
182 ProfileMismatch {
183 resolved: CanisterBuildProfile,
184 requested: CanisterBuildProfile,
185 },
186
187 #[error(
188 "installed Wasm tools resolved different directories: ic-wasm at {ic_wasm}, wasm-opt at {binaryen}"
189 )]
190 SplitInstallDirectories { ic_wasm: PathBuf, binaryen: PathBuf },
191}
192
193pub fn install_required_build_toolchain() -> Result<InstalledBuildToolchain, BuildToolchainError> {
195 current_binaryen_authority()?;
197 current_ic_wasm_authority()?;
198
199 let ic_wasm = install_required_ic_wasm()?;
200 let binaryen = install_required_binaryen()?;
201 let ic_wasm_directory = ic_wasm
202 .path()
203 .parent()
204 .map(Path::to_path_buf)
205 .ok_or_else(|| BuildToolchainError::SplitInstallDirectories {
206 ic_wasm: ic_wasm.path().to_path_buf(),
207 binaryen: binaryen.path().to_path_buf(),
208 })?;
209 if binaryen.path().parent() != Some(ic_wasm_directory.as_path()) {
210 return Err(BuildToolchainError::SplitInstallDirectories {
211 ic_wasm: ic_wasm.path().to_path_buf(),
212 binaryen: binaryen.path().to_path_buf(),
213 });
214 }
215 Ok(InstalledBuildToolchain {
216 ic_wasm,
217 binaryen,
218 install_directory: ic_wasm_directory,
219 })
220}