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