Skip to content

Commit

Permalink
feature: Use pre-built wasm-bindgen CLIs from Github releases
Browse files Browse the repository at this point in the history
Implements downloading tarballs from `wasm-bindgen`'s GitHub releases page and
using them if the current target has pre-built binaries.
  • Loading branch information
fitzgen committed Aug 29, 2018
1 parent 16acfd3 commit a8e953e
Show file tree
Hide file tree
Showing 9 changed files with 395 additions and 8 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ matrix:
- env: JOB=dist-linux TARGET=x86_64-unknown-linux-musl DEPLOY=1
rust: nightly
before_script: rustup target add $TARGET
script: cargo build --release --target $TARGET --locked
script: cargo build --release --target $TARGET --locked --features vendored-openssl
addons:
apt:
packages:
Expand Down
150 changes: 150 additions & 0 deletions Cargo.lock

Large diffs are not rendered by default.

7 changes: 7 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,13 @@ categories = ["wasm"]

[dependencies]
console = "0.6.1"
curl = "0.4.13"
failure = "0.1.2"
flate2 = "1.0.2"
human-panic = "1.0.1"
indicatif = "0.9.0"
lazy_static = "1.1.0"
openssl = { version = '0.10.11', optional = true }
parking_lot = "0.6"
serde = "1.0.74"
serde_derive = "1.0.74"
Expand All @@ -22,9 +25,13 @@ slog = "2.3"
slog-term = "2.4"
slog-async = "2.3"
structopt = "0.2"
tar = "0.4.16"
toml = "0.4"
which = "2.0.0"

[dev-dependencies]
copy_dir = "0.1.2"
tempfile = "3"

[features]
vendored-openssl = ['openssl/vendored']
144 changes: 138 additions & 6 deletions src/bindgen.rs
Original file line number Diff line number Diff line change
@@ -1,22 +1,35 @@
//! Functionality related to installing and running `wasm-bindgen`.

use curl;
use emoji;
use error::Error;
use failure;
use flate2;
use progressbar::Step;
use slog::Logger;
use std::ffi;
use std::fs;
use std::path::{Path, PathBuf};
use std::process::Command;
use tar;
use which::which;
use PBAR;

/// Install the `wasm-bindgen` CLI with `cargo install`.
pub fn cargo_install_wasm_bindgen(
path: &Path,
/// Install the `wasm-bindgen` CLI.
///
/// Prefers an existing local install, if any exists. Then checks if there is a
/// global install on `$PATH` that fits the bill. Then attempts to download a
/// tarball from the GitHub releases page, if this target has prebuilt
/// binaries. Finally, falls back to `cargo install`.
pub fn install_wasm_bindgen(
root_path: &Path,
version: &str,
install_permitted: bool,
step: &Step,
log: &Logger,
) -> Result<(), Error> {
// If the `wasm-bindgen` dependency is already met, print a message and return.
if wasm_bindgen_path(path)
if wasm_bindgen_path(root_path)
.map(|bindgen_path| wasm_bindgen_version_check(&bindgen_path, version))
.unwrap_or(false)
{
Expand All @@ -34,14 +47,123 @@ pub fn cargo_install_wasm_bindgen(

let msg = format!("{}Installing wasm-bindgen...", emoji::DOWN_ARROW);
PBAR.step(step, &msg);

download_prebuilt_wasm_bindgen(root_path, version).or_else(|e| {
warn!(
log,
"could not download pre-built `wasm-bindgen`: {}. Falling back to `cargo install`.", e
);
cargo_install_wasm_bindgen(root_path, version)
})
}

fn curl(url: &str) -> Result<Vec<u8>, failure::Error> {
let mut data = Vec::new();

fn with_url_context<T, E>(url: &str, r: Result<T, E>) -> Result<T, impl failure::Fail>
where
Result<T, E>: failure::ResultExt<T, E>,
{
use failure::ResultExt;
r.with_context(|_| format!("when requesting {}", url))
}

let mut easy = curl::easy::Easy::new();
with_url_context(url, easy.follow_location(true))?;
with_url_context(url, easy.url(url))?;

{
let mut transfer = easy.transfer();
with_url_context(
url,
transfer.write_function(|part| {
data.extend_from_slice(part);
Ok(part.len())
}),
)?;
with_url_context(url, transfer.perform())?;
}

let code = with_url_context(url, easy.response_code())?;
if 200 <= code && code < 300 {
Ok(data)
} else {
Err(Error::http(&format!(
"received a bad HTTP status code ({}) when requesting {}",
code, url
)).into())
}
}

/// Download a tarball containing a pre-built `wasm-bindgen` binary.
pub fn download_prebuilt_wasm_bindgen(root_path: &Path, version: &str) -> Result<(), Error> {
let linux = cfg!(target_os = "linux");
let macos = cfg!(target_os = "macos");
let x86_64 = cfg!(target_arch = "x86_64");

let target = if linux && x86_64 {
"x86_64-unknown-linux-musl"
} else if macos && x86_64 {
"x86_64-apple-darwin"
} else {
return Err(Error::unsupported(
"there are no pre-built `wasm-bindgen` binaries for this target",
));
};

let url = format!(
"https://github.com/rustwasm/wasm-bindgen/releases/download/{0}/wasm-bindgen-{0}-{1}.tar.gz",
version,
target
);

let tarball = curl(&url).map_err(|e| Error::http(&e.to_string()))?;
let mut archive = tar::Archive::new(flate2::read::GzDecoder::new(&tarball[..]));

let bin = root_path.join("bin");
fs::create_dir_all(&bin)?;

let mut found_wasm_bindgen = false;
let mut found_test_runner = false;

for entry in archive.entries()? {
let mut entry = entry?;

let dest = match entry.path()?.file_stem() {
Some(f) if f == ffi::OsStr::new("wasm-bindgen") => {
found_wasm_bindgen = true;
bin.join(entry.path()?.file_name().unwrap())
}
Some(f) if f == ffi::OsStr::new("wasm-bindgen-test-runner") => {
found_test_runner = true;
bin.join(entry.path()?.file_name().unwrap())
}
_ => continue,
};

entry.unpack(dest)?;
}

if found_wasm_bindgen && found_test_runner {
Ok(())
} else {
Err(Error::archive(
"the wasm-bindgen tarball was missing expected executables",
))
}
}

/// Use `cargo install` to install the `wasm-bindgen` CLI to the given root
/// path.
pub fn cargo_install_wasm_bindgen(root_path: &Path, version: &str) -> Result<(), Error> {
let output = Command::new("cargo")
.arg("install")
.arg("--force")
.arg("wasm-bindgen-cli")
.arg("--version")
.arg(version)
.arg("--root")
.arg(path)
.arg(root_path)
.output()?;
if !output.status.success() {
let message = "Installing wasm-bindgen failed".to_string();
Expand All @@ -51,6 +173,11 @@ pub fn cargo_install_wasm_bindgen(
stderr: s.to_string(),
})
} else {
if cfg!(target_os = "windows") {
assert!(root_path.join("bin").join("wasm-bindgen.exe").is_file());
} else {
assert!(root_path.join("bin").join("wasm-bindgen").is_file());
}
Ok(())
}
}
Expand All @@ -67,6 +194,7 @@ pub fn wasm_bindgen_build(
) -> Result<(), Error> {
let msg = format!("{}Running WASM-bindgen...", emoji::RUNNER);
PBAR.step(step, &msg);

let binary_name = name.replace("-", "_");
let release_or_debug = if debug { "debug" } else { "release" };

Expand Down Expand Up @@ -128,7 +256,11 @@ fn wasm_bindgen_path(crate_path: &Path) -> Option<PathBuf> {
let local_bindgen_path = |crate_path: &Path| -> Option<PathBuf> {
let mut p = crate_path.to_path_buf();
p.push("bin");
p.push("wasm-bindgen");
if cfg!(target_os = "windows") {
p.push("wasm-bindgen.exe");
} else {
p.push("wasm-bindgen");
}
if p.is_file() {
Some(p)
} else {
Expand Down
3 changes: 2 additions & 1 deletion src/command/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -226,11 +226,12 @@ impl Build {
BuildMode::Normal => true,
BuildMode::Noinstall => false,
};
bindgen::cargo_install_wasm_bindgen(
bindgen::install_wasm_bindgen(
&self.crate_path,
&bindgen_version,
install_permitted,
step,
log,
)?;
info!(&log, "Installing wasm-bindgen-cli was successful.");

Expand Down
59 changes: 59 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
//! Code related to error handling for wasm-pack
use curl;
use serde_json;
use std::borrow::Cow;
use std::io;
Expand All @@ -19,6 +20,10 @@ pub enum Error {
#[fail(display = "{}", _0)]
SerdeToml(#[cause] toml::de::Error),

#[fail(display = "{}", _0)]
/// A curl error.
Curl(#[cause] curl::Error),

/// An error invoking another CLI tool.
#[fail(display = "{}. stderr:\n\n{}", message, stderr)]
Cli {
Expand All @@ -34,12 +39,35 @@ pub enum Error {
/// A message describing the configuration error.
message: String,
},

#[fail(display = "{}", message)]
/// Error when the 'pkg' directory is not found.
PkgNotFound {
/// Message describing the error.
message: String,
},

#[fail(display = "{}", message)]
/// An error related to an archive that we downloaded.
Archive {
/// Error message.
message: String,
},

#[fail(display = "{}", message)]
/// Error when some operation or feature is unsupported for the current
/// target or environment.
Unsupported {
/// Error message.
message: String,
},

#[fail(display = "{}", message)]
/// Error related to some HTTP request.
Http {
/// Error message.
message: String,
},
}

impl Error {
Expand All @@ -58,6 +86,27 @@ impl Error {
})
}

/// Construct an archive error.
pub fn archive(message: &str) -> Self {
Error::Archive {
message: message.to_string(),
}
}

/// Construct an unsupported error.
pub fn unsupported(message: &str) -> Self {
Error::Unsupported {
message: message.to_string(),
}
}

/// Construct an http error.
pub fn http(message: &str) -> Self {
Error::Http {
message: message.to_string(),
}
}

/// Get a string description of this error's type.
pub fn error_type(&self) -> String {
match self {
Expand All @@ -74,6 +123,10 @@ impl Error {
Error::PkgNotFound {
message: _,
} => "Unable to find the 'pkg' directory at the path, set the path as the parent of the 'pkg' directory \n\n",
Error::Curl(_) => "There was an error making an HTTP request with curl. Details:\n\n",
Error::Archive {..} => "There was an error related to an archive file. Details:\n\n",
Error::Unsupported {..} => "There was an unsupported operation attempted. Details:\n\n",
Error::Http {..} => "There wasn an HTTP error. Details:\n\n",
}.to_string()
}
}
Expand All @@ -84,6 +137,12 @@ impl From<io::Error> for Error {
}
}

impl From<curl::Error> for Error {
fn from(e: curl::Error) -> Self {
Error::Curl(e)
}
}

impl From<serde_json::Error> for Error {
fn from(e: serde_json::Error) -> Self {
Error::SerdeJson(e)
Expand Down
3 changes: 3 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
#![deny(missing_docs)]

extern crate console;
extern crate curl;
#[macro_use]
extern crate failure;
extern crate flate2;
extern crate indicatif;
#[macro_use]
extern crate lazy_static;
Expand All @@ -18,6 +20,7 @@ extern crate structopt;
extern crate slog;
extern crate slog_async;
extern crate slog_term;
extern crate tar;
extern crate toml;
extern crate which;

Expand Down
Loading

0 comments on commit a8e953e

Please sign in to comment.