1use self::krate::Krate;
4use crate::child;
5use crate::install;
6use crate::PBAR;
7use anyhow::{anyhow, bail, Context, Result};
8use binary_install::{Cache, Download};
9use log::debug;
10use log::{info, warn};
11use std::env;
12use std::fs;
13use std::path::Path;
14use std::process::Command;
15use which::which;
16
17mod arch;
18mod krate;
19mod mode;
20mod os;
21mod tool;
22pub use self::arch::Arch;
23pub use self::mode::InstallMode;
24pub use self::os::Os;
25pub use self::tool::Tool;
26
27pub enum Status {
29 CannotInstall,
31 PlatformNotSupported,
33 Found(Download),
35}
36
37pub fn get_tool_path(status: &Status, tool: Tool) -> Result<&Download> {
39 match status {
40 Status::Found(download) => Ok(download),
41 Status::CannotInstall => bail!("Not able to find or install a local {}.", tool),
42 install::Status::PlatformNotSupported => {
43 bail!("{} does not currently support your platform.", tool)
44 }
45 }
46}
47
48pub fn download_prebuilt_or_cargo_install(
55 tool: Tool,
56 cache: &Cache,
57 version: &str,
58 install_permitted: bool,
59) -> Result<Status> {
60 if let Ok(path) = which(tool.to_string()) {
66 debug!("found global {} binary at: {}", tool, path.display());
67 if check_version(&tool, &path, version)? {
68 let download = Download::at(path.parent().unwrap());
69 return Ok(Status::Found(download));
70 }
71 }
72
73 let msg = format!("Installing {}...", tool);
74 PBAR.info(&msg);
75
76 let dl = download_prebuilt(&tool, cache, version, install_permitted);
77 match dl {
78 Ok(dl) => return Ok(dl),
79 Err(e) => {
80 warn!(
81 "could not download pre-built `{}`: {}. Falling back to `cargo install`.",
82 tool, e
83 );
84 }
85 }
86
87 cargo_install(tool, cache, version, install_permitted)
88}
89
90pub fn check_version(tool: &Tool, path: &Path, expected_version: &str) -> Result<bool> {
92 let expected_version = if expected_version == "latest" {
93 let krate = Krate::new(tool)?;
94 krate.max_version
95 } else {
96 expected_version.to_string()
97 };
98
99 let v = get_cli_version(tool, path)?;
100 info!(
101 "Checking installed `{}` version == expected version: {} == {}",
102 tool, v, &expected_version
103 );
104 Ok(v == expected_version)
105}
106
107pub fn get_cli_version(tool: &Tool, path: &Path) -> Result<String> {
109 let mut cmd = Command::new(path);
110 cmd.arg("--version");
111 let stdout = child::run_capture_stdout(cmd, tool)?;
112 let version = stdout.trim().split_whitespace().nth(1);
113 match version {
114 Some(v) => Ok(v.to_string()),
115 None => bail!("Something went wrong! We couldn't determine your version of the wasm-bindgen CLI. We were supposed to set that up for you, so it's likely not your fault! You should file an issue: https://github.com/drager/wasm-pack/issues/new?template=bug_report.md.")
116 }
117}
118
119pub fn download_prebuilt(
121 tool: &Tool,
122 cache: &Cache,
123 version: &str,
124 install_permitted: bool,
125) -> Result<Status> {
126 let url = match prebuilt_url(tool, version) {
127 Ok(url) => url,
128 Err(e) => bail!(
129 "no prebuilt {} binaries are available for this platform: {}",
130 tool,
131 e,
132 ),
133 };
134 match tool {
135 Tool::WasmBindgen => {
136 let binaries = &["wasm-bindgen", "wasm-bindgen-test-runner"];
137 match cache.download(install_permitted, "wasm-bindgen", binaries, &url)? {
138 Some(download) => Ok(Status::Found(download)),
139 None => bail!("wasm-bindgen v{} is not installed!", version),
140 }
141 }
142 Tool::CargoGenerate => {
143 let binaries = &["cargo-generate"];
144 match cache.download(install_permitted, "cargo-generate", binaries, &url)? {
145 Some(download) => Ok(Status::Found(download)),
146 None => bail!("cargo-generate v{} is not installed!", version),
147 }
148 }
149 Tool::WasmOpt => {
150 let binaries: &[&str] = match Os::get()? {
151 Os::MacOS => &["bin/wasm-opt", "lib/libbinaryen.dylib"],
152 Os::Linux => &["bin/wasm-opt"],
153 Os::Windows => &["bin/wasm-opt.exe"],
154 };
155 match cache.download(install_permitted, "wasm-opt", binaries, &url)? {
156 Some(download) => Ok(Status::Found(download)),
157 None => Ok(Status::CannotInstall),
159 }
160 }
161 }
162}
163
164fn prebuilt_url(tool: &Tool, version: &str) -> Result<String> {
167 let os = Os::get()?;
168 let arch = Arch::get()?;
169 prebuilt_url_for(tool, version, &arch, &os)
170}
171
172pub fn prebuilt_url_for(tool: &Tool, version: &str, arch: &Arch, os: &Os) -> Result<String> {
174 let target = match (os, arch, tool) {
175 (Os::Linux, Arch::AArch64, Tool::WasmOpt) => "aarch64-linux",
176 (Os::Linux, Arch::AArch64, _) => "aarch64-unknown-linux-gnu",
177 (Os::Linux, Arch::X86_64, Tool::WasmOpt) => "x86_64-linux",
178 (Os::Linux, Arch::X86_64, _) => "x86_64-unknown-linux-musl",
179 (Os::MacOS, Arch::X86_64, Tool::WasmOpt) => "x86_64-macos",
180 (Os::MacOS, Arch::X86_64, _) => "x86_64-apple-darwin",
181 (Os::MacOS, Arch::AArch64, Tool::CargoGenerate) => "aarch64-apple-darwin",
182 (Os::MacOS, Arch::AArch64, Tool::WasmOpt) => "arm64-macos",
183 (Os::Windows, Arch::X86_64, Tool::WasmOpt) => "x86_64-windows",
184 (Os::Windows, Arch::X86_64, _) => "x86_64-pc-windows-msvc",
185 _ => bail!("Unrecognized target!"),
186 };
187 match tool {
188 Tool::WasmBindgen => {
189 Ok(format!(
190 "https://github.com/rustwasm/wasm-bindgen/releases/download/{0}/wasm-bindgen-{0}-{1}.tar.gz",
191 version,
192 target
193 ))
194 },
195 Tool::CargoGenerate => {
196 Ok(format!(
197 "https://github.com/cargo-generate/cargo-generate/releases/download/v{0}/cargo-generate-v{0}-{1}.tar.gz",
198 "0.18.2",
199 target
200 ))
201 },
202 Tool::WasmOpt => {
203 Ok(format!(
204 "https://github.com/WebAssembly/binaryen/releases/download/{vers}/binaryen-{vers}-{target}.tar.gz",
205 vers = "version_117", target = target,
207 ))
208 }
209 }
210}
211
212pub fn cargo_install(
215 tool: Tool,
216 cache: &Cache,
217 version: &str,
218 install_permitted: bool,
219) -> Result<Status> {
220 debug!(
221 "Attempting to use a `cargo install`ed version of `{}={}`",
222 tool, version,
223 );
224
225 let dirname = format!("{}-cargo-install-{}", tool, version);
226 let destination = cache.join(dirname.as_ref());
227 if destination.exists() {
228 debug!(
229 "`cargo install`ed `{}={}` already exists at {}",
230 tool,
231 version,
232 destination.display()
233 );
234 let download = Download::at(&destination);
235 return Ok(Status::Found(download));
236 }
237
238 if !install_permitted {
239 return Ok(Status::CannotInstall);
240 }
241
242 let tmp = cache.join(format!(".{}", dirname).as_ref());
245 drop(fs::remove_dir_all(&tmp));
246 debug!("cargo installing {} to tempdir: {}", tool, tmp.display(),);
247
248 let context = format!("failed to create temp dir for `cargo install {}`", tool);
249 fs::create_dir_all(&tmp).context(context)?;
250
251 let crate_name = match tool {
252 Tool::WasmBindgen => "wasm-bindgen-cli".to_string(),
253 _ => tool.to_string(),
254 };
255 let mut cmd = Command::new("cargo");
256
257 cmd.arg("install")
258 .arg("--force")
259 .arg(crate_name)
260 .arg("--root")
261 .arg(&tmp);
262
263 if version != "latest" {
264 cmd.arg("--version").arg(version);
265 }
266
267 let context = format!("Installing {} with cargo", tool);
268 child::run(cmd, "cargo install").context(context)?;
269
270 let binaries: Result<Vec<&str>> = match tool {
275 Tool::WasmBindgen => Ok(vec!["wasm-bindgen", "wasm-bindgen-test-runner"]),
276 Tool::CargoGenerate => Ok(vec!["cargo-generate"]),
277 Tool::WasmOpt => bail!("Cannot install wasm-opt with cargo."),
278 };
279
280 for b in binaries?.iter().cloned() {
281 let from = tmp
282 .join("bin")
283 .join(b)
284 .with_extension(env::consts::EXE_EXTENSION);
285 let to = tmp.join(from.file_name().unwrap());
286 fs::rename(&from, &to).with_context(|| {
287 anyhow!(
288 "failed to move {} to {} for `cargo install`ed `{}`",
289 from.display(),
290 to.display(),
291 b
292 )
293 })?;
294 }
295
296 fs::rename(&tmp, &destination)?;
298
299 let download = Download::at(&destination);
300 Ok(Status::Found(download))
301}