use self::krate::Krate;
use crate::child;
use crate::emoji;
use crate::install;
use crate::PBAR;
use anyhow::{anyhow, bail, Context, Result};
use binary_install::{Cache, Download};
use log::debug;
use log::{info, warn};
use std::env;
use std::fs;
use std::path::Path;
use std::process::Command;
use which::which;
mod arch;
mod krate;
mod mode;
mod os;
mod tool;
pub use self::arch::Arch;
pub use self::mode::InstallMode;
pub use self::os::Os;
pub use self::tool::Tool;
pub enum Status {
CannotInstall,
PlatformNotSupported,
Found(Download),
}
pub fn get_tool_path(status: &Status, tool: Tool) -> Result<&Download> {
match status {
Status::Found(download) => Ok(download),
Status::CannotInstall => bail!("Not able to find or install a local {}.", tool),
install::Status::PlatformNotSupported => {
bail!("{} does not currently support your platform.", tool)
}
}
}
pub fn download_prebuilt_or_cargo_install(
tool: Tool,
cache: &Cache,
version: &str,
install_permitted: bool,
) -> Result<Status> {
if let Ok(path) = which(tool.to_string()) {
debug!("found global {} binary at: {}", tool, path.display());
if check_version(&tool, &path, version)? {
let download = Download::at(path.parent().unwrap());
return Ok(Status::Found(download));
}
}
let msg = format!("{}Installing {}...", emoji::DOWN_ARROW, tool);
PBAR.info(&msg);
let dl = download_prebuilt(&tool, cache, version, install_permitted);
match dl {
Ok(dl) => return Ok(dl),
Err(e) => {
warn!(
"could not download pre-built `{}`: {}. Falling back to `cargo install`.",
tool, e
);
}
}
cargo_install(tool, cache, version, install_permitted)
}
pub fn check_version(tool: &Tool, path: &Path, expected_version: &str) -> Result<bool> {
let expected_version = if expected_version == "latest" {
let krate = Krate::new(tool)?;
krate.max_version
} else {
expected_version.to_string()
};
let v = get_cli_version(tool, path)?;
info!(
"Checking installed `{}` version == expected version: {} == {}",
tool, v, &expected_version
);
Ok(v == expected_version)
}
pub fn get_cli_version(tool: &Tool, path: &Path) -> Result<String> {
let mut cmd = Command::new(path);
cmd.arg("--version");
let stdout = child::run_capture_stdout(cmd, tool)?;
let version = stdout.trim().split_whitespace().nth(1);
match version {
Some(v) => Ok(v.to_string()),
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.")
}
}
pub fn download_prebuilt(
tool: &Tool,
cache: &Cache,
version: &str,
install_permitted: bool,
) -> Result<Status> {
let url = match prebuilt_url(tool, version) {
Ok(url) => url,
Err(e) => bail!(
"no prebuilt {} binaries are available for this platform: {}",
tool,
e,
),
};
match tool {
Tool::WasmBindgen => {
let binaries = &["wasm-bindgen", "wasm-bindgen-test-runner"];
match cache.download(install_permitted, "wasm-bindgen", binaries, &url)? {
Some(download) => Ok(Status::Found(download)),
None => bail!("wasm-bindgen v{} is not installed!", version),
}
}
Tool::CargoGenerate => {
let binaries = &["cargo-generate"];
match cache.download(install_permitted, "cargo-generate", binaries, &url)? {
Some(download) => Ok(Status::Found(download)),
None => bail!("cargo-generate v{} is not installed!", version),
}
}
Tool::WasmOpt => {
let binaries: &[&str] = match Os::get()? {
Os::MacOS => &["bin/wasm-opt", "lib/libbinaryen.dylib"],
Os::Linux => &["bin/wasm-opt"],
Os::Windows => &["bin/wasm-opt.exe"],
};
match cache.download(install_permitted, "wasm-opt", binaries, &url)? {
Some(download) => Ok(Status::Found(download)),
None => Ok(Status::CannotInstall),
}
}
}
}
fn prebuilt_url(tool: &Tool, version: &str) -> Result<String> {
let os = Os::get()?;
let arch = Arch::get()?;
prebuilt_url_for(tool, version, &arch, &os)
}
pub fn prebuilt_url_for(tool: &Tool, version: &str, arch: &Arch, os: &Os) -> Result<String> {
let target = match (os, arch, tool) {
(Os::Linux, Arch::AArch64, Tool::WasmOpt) => "aarch64-linux",
(Os::Linux, Arch::AArch64, _) => "aarch64-unknown-linux-gnu",
(Os::Linux, Arch::X86_64, Tool::WasmOpt) => "x86_64-linux",
(Os::Linux, Arch::X86_64, Tool::CargoGenerate) => "x86_64-unknown-linux-gnu",
(Os::Linux, Arch::X86_64, _) => "x86_64-unknown-linux-musl",
(Os::MacOS, Arch::X86_64, Tool::WasmOpt) => "x86_64-macos",
(Os::MacOS, Arch::X86_64, _) => "x86_64-apple-darwin",
(Os::MacOS, Arch::AArch64, Tool::CargoGenerate) => "aarch64-apple-darwin",
(Os::MacOS, Arch::AArch64, Tool::WasmOpt) => "arm64-macos",
(Os::Windows, Arch::X86_64, Tool::WasmOpt) => "x86_64-windows",
(Os::Windows, Arch::X86_64, _) => "x86_64-pc-windows-msvc",
_ => bail!("Unrecognized target!"),
};
match tool {
Tool::WasmBindgen => {
Ok(format!(
"https://github.com/wasm-bindgen/wasm-bindgen/releases/download/{0}/wasm-bindgen-{0}-{1}.tar.gz",
version,
target
))
},
Tool::CargoGenerate => {
Ok(format!(
"https://github.com/cargo-generate/cargo-generate/releases/download/v{0}/cargo-generate-v{0}-{1}.tar.gz",
"0.23.7",
target
))
},
Tool::WasmOpt => {
Ok(format!(
"https://github.com/WebAssembly/binaryen/releases/download/{vers}/binaryen-{vers}-{target}.tar.gz",
vers = "version_117", target = target,
))
}
}
}
pub fn cargo_install(
tool: Tool,
cache: &Cache,
version: &str,
install_permitted: bool,
) -> Result<Status> {
debug!(
"Attempting to use a `cargo install`ed version of `{}={}`",
tool, version,
);
let dirname = format!("{}-cargo-install-{}", tool, version);
let destination = cache.join(dirname.as_ref());
if destination.exists() {
debug!(
"`cargo install`ed `{}={}` already exists at {}",
tool,
version,
destination.display()
);
let download = Download::at(&destination);
return Ok(Status::Found(download));
}
if !install_permitted {
return Ok(Status::CannotInstall);
}
let tmp = cache.join(format!(".{}", dirname).as_ref());
drop(fs::remove_dir_all(&tmp));
debug!("cargo installing {} to tempdir: {}", tool, tmp.display(),);
let context = format!("failed to create temp dir for `cargo install {}`", tool);
fs::create_dir_all(&tmp).context(context)?;
let crate_name = match tool {
Tool::WasmBindgen => "wasm-bindgen-cli".to_string(),
_ => tool.to_string(),
};
let mut cmd = Command::new("cargo");
cmd.arg("install")
.arg("--force")
.arg(crate_name)
.arg("--root")
.arg(&tmp);
if version != "latest" {
cmd.arg("--version").arg(version);
}
let context = format!("Installing {} with cargo", tool);
child::run(cmd, "cargo install").context(context)?;
let binaries: Result<Vec<&str>> = match tool {
Tool::WasmBindgen => Ok(vec!["wasm-bindgen", "wasm-bindgen-test-runner"]),
Tool::CargoGenerate => Ok(vec!["cargo-generate"]),
Tool::WasmOpt => bail!("Cannot install wasm-opt with cargo."),
};
for b in binaries?.iter().cloned() {
let from = tmp
.join("bin")
.join(b)
.with_extension(env::consts::EXE_EXTENSION);
let to = tmp.join(from.file_name().unwrap());
fs::rename(&from, &to).with_context(|| {
anyhow!(
"failed to move {} to {} for `cargo install`ed `{}`",
from.display(),
to.display(),
b
)
})?;
}
fs::rename(&tmp, &destination)?;
let download = Download::at(&destination);
Ok(Status::Found(download))
}