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 pub fn resolve(profile: CanisterBuildProfile) -> Result<Self, BuildToolchainError> {
44 let ic_wasm = resolve_required_ic_wasm()?;
45 let binaryen = match profile {
46 CanisterBuildProfile::Release => Some(resolve_required_binaryen()?),
47 CanisterBuildProfile::Debug | CanisterBuildProfile::Fast => None,
48 };
49 Ok(Self {
50 profile,
51 ic_wasm,
52 binaryen,
53 })
54 }
55
56 #[must_use]
58 pub fn diagnostic_lines(&self) -> Vec<String> {
59 let mut lines = vec![format!(
60 "ic-wasm: {} ({})",
61 self.ic_wasm.path().display(),
62 self.ic_wasm.version_identity()
63 )];
64 if let Some(binaryen) = &self.binaryen {
65 lines.push(format!(
66 "wasm-opt: {} ({})",
67 binaryen.path().display(),
68 binaryen.version_identity()
69 ));
70 }
71 lines
72 }
73
74 pub(crate) fn require_profile(
75 &self,
76 profile: CanisterBuildProfile,
77 ) -> Result<(), BuildToolchainError> {
78 if self.profile != profile {
79 return Err(BuildToolchainError::ProfileMismatch {
80 resolved: self.profile,
81 requested: profile,
82 });
83 }
84 Ok(())
85 }
86
87 pub(crate) const fn ic_wasm(&self) -> &IcWasmExecutable {
88 &self.ic_wasm
89 }
90
91 pub(crate) const fn binaryen(&self) -> Option<&BinaryenExecutable> {
92 self.binaryen.as_ref()
93 }
94}
95
96#[derive(Clone, Debug, Eq, PartialEq)]
103pub struct InstalledBuildToolchain {
104 ic_wasm: IcWasmExecutable,
105 binaryen: BinaryenExecutable,
106 install_directory: PathBuf,
107}
108
109impl InstalledBuildToolchain {
110 #[must_use]
111 pub const fn ic_wasm(&self) -> &IcWasmExecutable {
112 &self.ic_wasm
113 }
114
115 #[must_use]
116 pub const fn binaryen(&self) -> &BinaryenExecutable {
117 &self.binaryen
118 }
119
120 #[must_use]
121 pub fn install_directory(&self) -> &Path {
122 self.install_directory.as_path()
123 }
124
125 #[must_use]
126 pub fn root_home_warning(&self) -> Option<String> {
127 let home = env::var_os("HOME");
128 root_home_warning_for(home.as_deref(), self.install_directory())
129 }
130}
131
132fn root_home_warning_for(home: Option<&OsStr>, install_directory: &Path) -> Option<String> {
133 home.filter(|home| Path::new(home) == Path::new("/"))
134 .map(|_| {
135 format!(
136 "warning: HOME resolves to `/`; tools were installed under {}. Set HOME to the intended account home and reinstall if this root-level location is accidental.",
137 install_directory.display()
138 )
139 })
140}
141
142#[derive(Debug, ThisError)]
149pub enum BuildToolchainError {
150 #[error(transparent)]
151 Binaryen(#[from] BinaryenToolError),
152
153 #[error(transparent)]
154 IcWasm(#[from] IcWasmToolError),
155
156 #[error(
157 "build toolchain was resolved for {resolved:?}, but artifact finalization requested {requested:?}"
158 )]
159 ProfileMismatch {
160 resolved: CanisterBuildProfile,
161 requested: CanisterBuildProfile,
162 },
163
164 #[error(
165 "installed Wasm tools resolved different directories: ic-wasm at {ic_wasm}, wasm-opt at {binaryen}"
166 )]
167 SplitInstallDirectories { ic_wasm: PathBuf, binaryen: PathBuf },
168}
169
170pub fn install_required_build_toolchain() -> Result<InstalledBuildToolchain, BuildToolchainError> {
172 current_binaryen_authority()?;
174 current_ic_wasm_authority()?;
175
176 let ic_wasm = install_required_ic_wasm()?;
177 let binaryen = install_required_binaryen()?;
178 let ic_wasm_directory = ic_wasm
179 .path()
180 .parent()
181 .map(Path::to_path_buf)
182 .ok_or_else(|| BuildToolchainError::SplitInstallDirectories {
183 ic_wasm: ic_wasm.path().to_path_buf(),
184 binaryen: binaryen.path().to_path_buf(),
185 })?;
186 if binaryen.path().parent() != Some(ic_wasm_directory.as_path()) {
187 return Err(BuildToolchainError::SplitInstallDirectories {
188 ic_wasm: ic_wasm.path().to_path_buf(),
189 binaryen: binaryen.path().to_path_buf(),
190 });
191 }
192 Ok(InstalledBuildToolchain {
193 ic_wasm,
194 binaryen,
195 install_directory: ic_wasm_directory,
196 })
197}