commit
a267075be8
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,50 @@ |
||||
# wasm-pack new |
||||
|
||||
The `wasm-pack new` command creates a new RustWasm project for you, |
||||
using [`cargo-generate`] under the hood. |
||||
|
||||
It takes 3 parameters, name, template, and mode: |
||||
|
||||
``` |
||||
wasm-pack new <name> --template <template> --mode <normal|noinstall|force> |
||||
``` |
||||
|
||||
The template will default to the `rustwasm/wasm-pack-template`. |
||||
|
||||
## Name |
||||
|
||||
The `wasm-pack new` command must be given a name argument, e.g.: |
||||
|
||||
``` |
||||
wasm-pack new myproject |
||||
``` |
||||
|
||||
## Template |
||||
|
||||
The `wasm-pack new` command can be given an optional template argument, e.g.: |
||||
|
||||
``` |
||||
wasm-pack new myproject --template https://github.com/rustwasm/wasm-pack-template |
||||
``` |
||||
|
||||
The template can be an address to a git repo that contains a [`cargo-generate`] |
||||
template. |
||||
|
||||
[`cargo-generate`]: https://github.com/ashleygwilliams/cargo-generate |
||||
|
||||
## Mode |
||||
|
||||
The `wasm-pack new` command can be given an optional mode argument, e.g.: |
||||
|
||||
``` |
||||
wasm-pack new myproject --mode noinstall |
||||
``` |
||||
|
||||
The mode pass can be either "normal", "noinstall", or "force". "normal is passed by |
||||
degault. |
||||
|
||||
`noinstall` means that wasm-pack should not attempt to install any underlying tools. |
||||
If a necessary tool cannot be found, the command will error. |
||||
|
||||
`force` means that wasm-pack should not check the local Rust version. If a local Rust |
||||
is an unacceptable Rust version, the command will error. |
@ -0,0 +1,13 @@ |
||||
# Quickstart |
||||
|
||||
1. Install `rust` using [`rustup`]. |
||||
1. [Install this tool.] |
||||
1. Run `wasm-pack new hello-wasm`. |
||||
1. `cd hello-wasm` |
||||
1. Run `wasm-pack build`. |
||||
1. This tool generates files in a `pkg` dir |
||||
1. To publish to npm, run `wasm-pack publish`. You may need to login to the |
||||
registry you want to publish to. You can login using `wasm-pack login`. |
||||
|
||||
[`rustup`]: https://rustup.rs/ |
||||
[Install this tool.]: https://rustwasm.github.io/wasm-pack/installer/ |
@ -0,0 +1,28 @@ |
||||
use cache; |
||||
use failure::Error; |
||||
use generate; |
||||
use install::{self, Tool}; |
||||
use log::info; |
||||
use std::result; |
||||
use PBAR; |
||||
|
||||
/// Executes the 'cargo-generate' command in the current directory
|
||||
/// which generates a new rustwasm project from a template.
|
||||
pub fn generate( |
||||
template: String, |
||||
name: String, |
||||
install_permitted: bool, |
||||
) -> result::Result<(), Error> { |
||||
info!("Generating a new rustwasm project..."); |
||||
let download = install::download_prebuilt_or_cargo_install( |
||||
Tool::CargoGenerate, |
||||
&cache::get_wasm_pack_cache()?, |
||||
"latest", |
||||
install_permitted, |
||||
)?; |
||||
generate::generate(&template, &name, &download)?; |
||||
|
||||
let msg = format!("🐑 Generated new project at /{}", name); |
||||
PBAR.info(&msg); |
||||
Ok(()) |
||||
} |
@ -0,0 +1,25 @@ |
||||
//! Functionality related to running `cargo-generate`.
|
||||
|
||||
use binary_install::Download; |
||||
use child; |
||||
use emoji; |
||||
use failure::{self, ResultExt}; |
||||
use std::process::Command; |
||||
|
||||
/// Run `cargo generate` in the current directory to create a new
|
||||
/// project from a template
|
||||
pub fn generate(template: &str, name: &str, download: &Download) -> Result<(), failure::Error> { |
||||
let bin_path = download.binary("cargo-generate")?; |
||||
let mut cmd = Command::new(&bin_path); |
||||
cmd.arg("generate"); |
||||
cmd.arg("--git").arg(&template); |
||||
cmd.arg("--name").arg(&name); |
||||
|
||||
println!( |
||||
"{} Generating a new rustwasm project with name '{}'...", |
||||
emoji::SHEEP, |
||||
name |
||||
); |
||||
child::run(cmd, "cargo-generate").context("Running cargo-generate")?; |
||||
Ok(()) |
||||
} |
@ -0,0 +1,24 @@ |
||||
use install::Tool; |
||||
use serde::Deserialize; |
||||
|
||||
#[derive(Debug, Deserialize)] |
||||
pub struct Krate { |
||||
pub max_version: String, |
||||
} |
||||
|
||||
#[derive(Debug, Deserialize)] |
||||
pub struct KrateResponse { |
||||
#[serde(rename = "crate")] |
||||
pub krate: Krate, |
||||
} |
||||
|
||||
impl Krate { |
||||
pub fn new(name: &Tool) -> Result<Krate, failure::Error> { |
||||
let krate_address = format!("https://crates.io/api/v1/crates/{}", name); |
||||
let client = reqwest::Client::new(); |
||||
let mut res = client.get(&krate_address).send()?; |
||||
|
||||
let kr: KrateResponse = serde_json::from_str(&res.text()?)?; |
||||
Ok(kr.krate) |
||||
} |
||||
} |
@ -0,0 +1,245 @@ |
||||
//! Functionality related to installing prebuilt binaries and/or running cargo install.
|
||||
|
||||
use self::krate::Krate; |
||||
use binary_install::{Cache, Download}; |
||||
use child; |
||||
use emoji; |
||||
use failure::{self, ResultExt}; |
||||
use log::debug; |
||||
use log::{info, warn}; |
||||
use std::env; |
||||
use std::fs; |
||||
use std::path::PathBuf; |
||||
use std::process::Command; |
||||
use target; |
||||
use which::which; |
||||
use PBAR; |
||||
|
||||
mod krate; |
||||
mod mode; |
||||
mod tool; |
||||
pub use self::mode::InstallMode; |
||||
pub use self::tool::Tool; |
||||
|
||||
/// Install a cargo CLI tool
|
||||
///
|
||||
/// 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 download_prebuilt_or_cargo_install( |
||||
tool: Tool, |
||||
cache: &Cache, |
||||
version: &str, |
||||
install_permitted: bool, |
||||
) -> Result<Download, failure::Error> { |
||||
// If the tool is installed globally and it has the right version, use
|
||||
// that. Assume that other tools are installed next to it.
|
||||
//
|
||||
// This situation can arise if the tool is already installed via
|
||||
// `cargo install`, for example.
|
||||
if let Ok(path) = which(tool.to_string()) { |
||||
debug!("found global {} binary at: {}", tool, path.display()); |
||||
if check_version(&tool, &path, version)? { |
||||
return Ok(Download::at(path.parent().unwrap())); |
||||
} |
||||
} |
||||
|
||||
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) |
||||
} |
||||
|
||||
/// Check if the tool dependency is locally satisfied.
|
||||
fn check_version( |
||||
tool: &Tool, |
||||
path: &PathBuf, |
||||
expected_version: &str, |
||||
) -> Result<bool, failure::Error> { |
||||
let expected_version = if expected_version == "latest" { |
||||
let krate = Krate::new(tool)?; |
||||
krate.max_version |
||||
} else { |
||||
expected_version.to_string() |
||||
}; |
||||
|
||||
let mut cmd = Command::new(path); |
||||
cmd.arg("--version"); |
||||
child::run_capture_stdout(cmd, tool) |
||||
.map(|stdout| { |
||||
stdout |
||||
.trim() |
||||
.split_whitespace() |
||||
.nth(1) |
||||
.map(|v| { |
||||
info!( |
||||
"Checking installed `{}` version == expected version: {} == {}", |
||||
tool, v, &expected_version |
||||
); |
||||
Ok(v == expected_version) |
||||
}) |
||||
.unwrap_or(Ok(false)) |
||||
}) |
||||
.unwrap_or(Ok(false)) |
||||
} |
||||
|
||||
/// Downloads a precompiled copy of the tool, if available.
|
||||
pub fn download_prebuilt( |
||||
tool: &Tool, |
||||
cache: &Cache, |
||||
version: &str, |
||||
install_permitted: bool, |
||||
) -> Result<Download, failure::Error> { |
||||
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(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(download), |
||||
None => bail!("cargo-generate v{} is not installed!", version), |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
/// Returns the URL of a precompiled version of wasm-bindgen, if we have one
|
||||
/// available for our host platform.
|
||||
fn prebuilt_url(tool: &Tool, version: &str) -> Result<String, failure::Error> { |
||||
let target = if target::LINUX && target::x86_64 { |
||||
"x86_64-unknown-linux-musl" |
||||
} else if target::MACOS && target::x86_64 { |
||||
"x86_64-apple-darwin" |
||||
} else if target::WINDOWS && target::x86_64 { |
||||
"x86_64-pc-windows-msvc" |
||||
} else { |
||||
bail!("Unrecognized target!") |
||||
}; |
||||
|
||||
match tool { |
||||
Tool::WasmBindgen => { |
||||
Ok(format!( |
||||
"https://github.com/rustwasm/wasm-bindgen/releases/download/{0}/wasm-bindgen-{0}-{1}.tar.gz", |
||||
version, |
||||
target |
||||
)) |
||||
}, |
||||
Tool::CargoGenerate => { |
||||
Ok(format!( |
||||
"https://github.com/ashleygwilliams/cargo-generate/releases/download/v{0}/cargo-generate-v{0}-{1}.tar.gz", |
||||
Krate::new(&Tool::CargoGenerate)?.max_version, |
||||
target |
||||
)) |
||||
} |
||||
} |
||||
} |
||||
|
||||
/// Use `cargo install` to install the tool locally into the given
|
||||
/// crate.
|
||||
pub fn cargo_install( |
||||
tool: Tool, |
||||
cache: &Cache, |
||||
version: &str, |
||||
install_permitted: bool, |
||||
) -> Result<Download, failure::Error> { |
||||
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() |
||||
); |
||||
return Ok(Download::at(&destination)); |
||||
} |
||||
|
||||
if !install_permitted { |
||||
bail!("{} v{} is not installed!", tool, version) |
||||
} |
||||
|
||||
// Run `cargo install` to a temporary location to handle ctrl-c gracefully
|
||||
// and ensure we don't accidentally use stale files in the future
|
||||
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("--version") |
||||
.arg(version) |
||||
.arg("--root") |
||||
.arg(&tmp); |
||||
|
||||
let context = format!("Installing {} with cargo", tool); |
||||
child::run(cmd, "cargo install").context(context)?; |
||||
|
||||
// `cargo install` will put the installed binaries in `$root/bin/*`, but we
|
||||
// just want them in `$root/*` directly (which matches how the tarballs are
|
||||
// laid out, and where the rest of our code expects them to be). So we do a
|
||||
// little renaming here.
|
||||
let binaries = match tool { |
||||
Tool::WasmBindgen => vec!["wasm-bindgen", "wasm-bindgen-test-runner"], |
||||
Tool::CargoGenerate => vec!["cargo-genrate"], |
||||
}; |
||||
|
||||
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(|_| { |
||||
format!( |
||||
"failed to move {} to {} for `cargo install`ed `{}`", |
||||
from.display(), |
||||
to.display(), |
||||
b |
||||
) |
||||
})?; |
||||
} |
||||
|
||||
// Finally, move the `tmp` directory into our binary cache.
|
||||
fs::rename(&tmp, &destination)?; |
||||
|
||||
Ok(Download::at(&destination)) |
||||
} |
@ -0,0 +1,43 @@ |
||||
use std::str::FromStr; |
||||
|
||||
/// The `InstallMode` determines which mode of initialization we are running, and
|
||||
/// what install steps we perform.
|
||||
#[derive(Clone, Copy, Debug)] |
||||
pub enum InstallMode { |
||||
/// Perform all the install steps.
|
||||
Normal, |
||||
/// Don't install tools like `wasm-bindgen`, just use the global
|
||||
/// environment's existing versions to do builds.
|
||||
Noinstall, |
||||
/// Skip the rustc version check
|
||||
Force, |
||||
} |
||||
|
||||
impl Default for InstallMode { |
||||
fn default() -> InstallMode { |
||||
InstallMode::Normal |
||||
} |
||||
} |
||||
|
||||
impl FromStr for InstallMode { |
||||
type Err = failure::Error; |
||||
fn from_str(s: &str) -> Result<Self, failure::Error> { |
||||
match s { |
||||
"no-install" => Ok(InstallMode::Noinstall), |
||||
"normal" => Ok(InstallMode::Normal), |
||||
"force" => Ok(InstallMode::Force), |
||||
_ => bail!("Unknown build mode: {}", s), |
||||
} |
||||
} |
||||
} |
||||
|
||||
impl InstallMode { |
||||
/// Determines if installation is permitted during a function call based on --mode flag
|
||||
pub fn install_permitted(self) -> bool { |
||||
match self { |
||||
InstallMode::Normal => true, |
||||
InstallMode::Force => true, |
||||
InstallMode::Noinstall => false, |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,18 @@ |
||||
use std::fmt; |
||||
|
||||
/// Represents the set of CLI tools wasm-pack uses
|
||||
pub enum Tool { |
||||
/// cargo-generate CLI tool
|
||||
CargoGenerate, |
||||
/// wasm-bindgen CLI tools
|
||||
WasmBindgen, |
||||
} |
||||
|
||||
impl fmt::Display for Tool { |
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
||||
match self { |
||||
Tool::CargoGenerate => write!(f, "cargo-generate"), |
||||
Tool::WasmBindgen => write!(f, "wasm-bindgen"), |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,21 @@ |
||||
use assert_cmd::prelude::*; |
||||
use utils; |
||||
|
||||
#[test] |
||||
fn new_with_no_name_errors() { |
||||
let fixture = utils::fixture::not_a_crate(); |
||||
fixture.install_local_cargo_generate(); |
||||
fixture.wasm_pack().arg("new").assert().failure(); |
||||
} |
||||
|
||||
#[test] |
||||
fn new_with_name_succeeds() { |
||||
let fixture = utils::fixture::not_a_crate(); |
||||
fixture.install_local_cargo_generate(); |
||||
fixture |
||||
.wasm_pack() |
||||
.arg("new") |
||||
.arg("hello") |
||||
.assert() |
||||
.success(); |
||||
} |
Loading…
Reference in new issue