diff --git a/.appveyor.yml b/.appveyor.yml index f50ea0e6..b14e7e60 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -19,23 +19,25 @@ test_script: before_deploy: - ps: | - $NAME = "wasm-pack-${env:APPVEYOR_REPO_TAG_NAME}-${env:TARGET}" + $NAME = "wasm-pack-${env:APPVEYOR_REPO_TAG_NAME}-x86_64-pc-windows-msvc" New-Item -Path $NAME -ItemType directory Copy-Item target/release/wasm-pack.exe "${NAME}/" + Copy-Item target/release/wasm-pack.exe wasm-pack-init.exe Copy-Item LICENSE-MIT "${NAME}/" Copy-Item LICENSE-APACHE "${NAME}/" Copy-Item README.md "${NAME}/" 7z a -ttar "${NAME}.tar" "${NAME}" 7z a "${NAME}.tar.gz" "${NAME}.tar" Push-AppveyorArtifact "${NAME}.tar.gz" + Push-AppveyorArtifact wasm-pack-init.exe deploy: - artifact: /.*\.tar.gz/ + artifact: /.*\.tar.gz/, /.*\.exe/ description: 'Appveyor Automated Release' provider: GitHub draft: false prerelease: false - autho_token: + auth_token: secure: iHsRUqwGf/Zh7OuYpHOWQL8buaOL+c8/6kXLRly8V2j0LCUo7CcDs0NxQ0vl2bhZ on: appveyor_repo_tag: true diff --git a/.gitignore b/.gitignore index 4b2ee66a..ea6c5a6e 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ target/ tests/.crates.toml tests/bin wasm-pack.log +/build-installer diff --git a/.travis.yml b/.travis.yml index 5b143b2f..977b9d5f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,6 +14,9 @@ cache: directories: - /home/travis/.cargo +GH_TOKEN: &GH_TOKEN + secure: fx0rR5Ii1KcsydexE6QpkDbqItNdj3Lt6L5yFZaKKB/ejw9M555NkXA+0GZqV0sLZ54qfR8zTaXAf6eBFKgcG9etaCl7vTXqsvDrlssth82oki1zufP39uuoOy4WgFq8OfACOtUq7opDAgYmpaGzlFiny+c5j7asGwDtAU1Fc3JeJsvAnxHKg9+0spXFD6kBQd5CWpqDXv2rLFK0b8IM2fHAzd0PiJZQWqz//2Cj/r9rTiewtIzqigctAfOgFwYoQvfdM+0mKb4pefG+zXEGfxxQr4r5hqZ6UMO7hto3Jnm9LRjNR8dNaDQCqQ0bkdLTAMTC3nV/gZPM679yQU3KHueVjg9pleNzuKnuBgYmH9+BrlG1dW68kqA+6Xh+wIJYrLuagWhJDlCtiU6PM5QAbFg3mabPIBG3M2IHTrOVATme+iW5vpROARhgjbQEF235DyvZaT+Tml3+PY+PfcRax2DVUhvGQViv4tzppbT0PjjBlEbGct49cFLGdqZIJBiVrYW24I2QkENTnUgZsFIBuJlVCBHZwZlLo9ldVvu4XTMKw65z42zoTzobjtbC1QPEZPiaJXSxC7W569fqL/ORXwGToFk6rQjXwEqDP2okGiusR75LXrZD6qFibNpqeypRFtqOzntsOfTUGrlaN1yTt/6dz0V0j9uI7a9/CHVcblI= + DEPLOY_TO_GITHUB: &DEPLOY_TO_GITHUB before_deploy: - git config --local user.name "Ashley Williams" @@ -25,14 +28,11 @@ DEPLOY_TO_GITHUB: &DEPLOY_TO_GITHUB - tar czvf $name.tar.gz $name deploy: provider: releases - api_key: - secure: fx0rR5Ii1KcsydexE6QpkDbqItNdj3Lt6L5yFZaKKB/ejw9M555NkXA+0GZqV0sLZ54qfR8zTaXAf6eBFKgcG9etaCl7vTXqsvDrlssth82oki1zufP39uuoOy4WgFq8OfACOtUq7opDAgYmpaGzlFiny+c5j7asGwDtAU1Fc3JeJsvAnxHKg9+0spXFD6kBQd5CWpqDXv2rLFK0b8IM2fHAzd0PiJZQWqz//2Cj/r9rTiewtIzqigctAfOgFwYoQvfdM+0mKb4pefG+zXEGfxxQr4r5hqZ6UMO7hto3Jnm9LRjNR8dNaDQCqQ0bkdLTAMTC3nV/gZPM679yQU3KHueVjg9pleNzuKnuBgYmH9+BrlG1dW68kqA+6Xh+wIJYrLuagWhJDlCtiU6PM5QAbFg3mabPIBG3M2IHTrOVATme+iW5vpROARhgjbQEF235DyvZaT+Tml3+PY+PfcRax2DVUhvGQViv4tzppbT0PjjBlEbGct49cFLGdqZIJBiVrYW24I2QkENTnUgZsFIBuJlVCBHZwZlLo9ldVvu4XTMKw65z42zoTzobjtbC1QPEZPiaJXSxC7W569fqL/ORXwGToFk6rQjXwEqDP2okGiusR75LXrZD6qFibNpqeypRFtqOzntsOfTUGrlaN1yTt/6dz0V0j9uI7a9/CHVcblI= - file_glob: true + api_key: *GH_TOKEN file: wasm-pack-$TRAVIS_TAG-$TARGET.tar.gz skip_cleanup: true on: branch: master - condition: $DEPLOY = 1 tags: true matrix: @@ -59,18 +59,20 @@ matrix: - (test -x $HOME/.cargo/bin/mdbook || cargo install --vers "^0.1" mdbook) - cargo install-update -a script: - - cd docs && mdbook build + - (cd docs && mdbook build) + - rustc ./docs/installer/build-installer.rs + - ./build-installer deploy: provider: pages skip-cleanup: true - github-token: $GITHUB_TOKEN + github-token: *GH_TOKEN local-dir: docs/book keep-history: false on: branch: master # dist linux binary - - env: JOB=dist-linux TARGET=x86_64-unknown-linux-musl DEPLOY=1 + - env: JOB=dist-linux TARGET=x86_64-unknown-linux-musl rust: nightly before_script: rustup target add $TARGET script: cargo build --release --target $TARGET --locked --features vendored-openssl @@ -81,7 +83,7 @@ matrix: <<: *DEPLOY_TO_GITHUB # dist OSX binary - - env: JOB=dist-osx MACOSX_DEPLOYMENT_TARGET=10.7 DEPLOY=1 TARGET=x86_64-apple-darwin + - env: JOB=dist-osx MACOSX_DEPLOYMENT_TARGET=10.7 TARGET=x86_64-apple-darwin os: osx rust: nightly script: cargo build --release --target $TARGET --locked diff --git a/Cargo.lock b/Cargo.lock index 8aa26c7d..2b06be94 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -900,6 +900,7 @@ dependencies = [ name = "wasm-pack" version = "0.4.2" dependencies = [ + "atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", "console 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", "copy_dir 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "curl 0.4.14 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/Cargo.toml b/Cargo.toml index ac1d24fe..1113f156 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,7 @@ readme = "README.md" categories = ["wasm"] [dependencies] +atty = "0.2.11" console = "0.6.1" curl = "0.4.13" failure = "0.1.2" diff --git a/docs/installer/build-installer.rs b/docs/installer/build-installer.rs new file mode 100644 index 00000000..a5aa5995 --- /dev/null +++ b/docs/installer/build-installer.rs @@ -0,0 +1,30 @@ +use std::fs; + +fn main() { + fs::create_dir_all("docs/book/installer").unwrap(); + fs::copy( + "docs/installer/wasm-pack.js", + "docs/book/installer/wasm-pack.js", + ).unwrap(); + let index = fs::read_to_string("docs/installer/index.html").unwrap(); + fs::write( + "docs/book/installer/index.html", + fixup(&index), + ).unwrap(); + + let init = fs::read_to_string("docs/installer/init.sh").unwrap(); + fs::write( + "docs/book/installer/init.sh", + fixup(&init), + ).unwrap(); +} + +fn fixup(input: &str) -> String { + let manifest = fs::read_to_string("Cargo.toml").unwrap(); + let version = manifest.lines() + .find(|line| line.starts_with("version =")) + .unwrap(); + let version = &version[version.find('"').unwrap() + 1..version.rfind('"').unwrap()]; + + input.replace("$VERSION", &format!("v{}", version)) +} diff --git a/docs/installer/index.html b/docs/installer/index.html new file mode 100644 index 00000000..f4463b28 --- /dev/null +++ b/docs/installer/index.html @@ -0,0 +1,106 @@ + + + + + wasm-pack + + + + +
+ Install wasm-pack! A tool with a blurb here. +
+ + + + + + + +
+
+

+ To install wasm-pack, if you are running Unix,
+ run the following in your terminal, then follow the onscreen + instructions. +

+
curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
+
+ +
+ +
+

+ If you are running Windows 64-bit,
download and run + wasm-pack-init.exe + then follow the onscreen instructions. +

+
+ +
+ +
+

+ For all other platforms, run the following in your terminal: +

+
cargo install wasm-pack
+
+
+ + diff --git a/docs/installer/init.sh b/docs/installer/init.sh new file mode 100644 index 00000000..247a1ba6 --- /dev/null +++ b/docs/installer/init.sh @@ -0,0 +1,180 @@ +#!/bin/bash +# Copyright 2016 The Rust Project Developers. See the COPYRIGHT +# file at the top-level directory of this distribution and at +# http://rust-lang.org/COPYRIGHT. +# +# Licensed under the Apache License, Version 2.0 or the MIT license +# , at your +# option. This file may not be copied, modified, or distributed +# except according to those terms. + +# This is just a little script that can be downloaded from the internet to +# install wasm-pack. It just does platform detection, downloads the installer +# and runs it. + +set -u + +UPDATE_ROOT="https://github.com/rustwasm/wasm-pack/releases/download/$VERSION" + +main() { + downloader --check + need_cmd uname + need_cmd mktemp + need_cmd chmod + need_cmd mkdir + need_cmd rm + need_cmd rmdir + need_cmd tar + need_cmd which + need_cmd dirname + + get_architecture || return 1 + local _arch="$RETVAL" + assert_nz "$_arch" "arch" + + local _ext="" + case "$_arch" in + *windows*) + _ext=".exe" + ;; + esac + + which rustup > /dev/null 2>&1 + need_ok "failed to find Rust installation, is rustup installed?" + local _rustup=`which rustup` + local _tardir="wasm-pack-$VERSION-${_arch}" + local _url="$UPDATE_ROOT/${_tardir}.tar.gz" + local _dir="$(mktemp -d 2>/dev/null || ensure mktemp -d -t wasm-pack)" + local _file="$_dir/input.tar.gz" + local _wasmpack="$_dir/wasm-pack$_ext" + local _wasmpackinit="$_dir/wasm-pack-init$_ext" + + printf '%s\n' 'info: downloading wasm-pack' 1>&2 + + ensure mkdir -p "$_dir" + downloader "$_url" "$_file" + if [ $? != 0 ]; then + say "failed to download $_url" + say "this may be a standard network error, but it may also indicate" + say "that wasm-pack's release process is not working. When in doubt" + say "please feel free to open an issue!" + exit 1 + fi + ensure tar xf "$_file" --strip-components 1 -C "$_dir" + mv "$_wasmpack" "$_wasmpackinit" + "$_wasmpackinit" "$@" + local _retval=$? + + ignore rm -rf "$_dir" + + return "$_retval" +} + +get_architecture() { + local _ostype="$(uname -s)" + local _cputype="$(uname -m)" + + if [ "$_ostype" = Darwin -a "$_cputype" = i386 ]; then + # Darwin `uname -s` lies + if sysctl hw.optional.x86_64 | grep -q ': 1'; then + local _cputype=x86_64 + fi + fi + + case "$_ostype" in + Linux) + local _ostype=unknown-linux-musl + ;; + + Darwin) + local _ostype=apple-darwin + ;; + + MINGW* | MSYS* | CYGWIN*) + local _ostype=pc-windows-msvc + ;; + + *) + err "no precompiled binaries available for OS: $_ostype" + ;; + esac + + case "$_cputype" in + x86_64 | x86-64 | x64 | amd64) + local _cputype=x86_64 + ;; + *) + err "no precompiled binaries available for CPU architecture: $_cputype" + + esac + + local _arch="$_cputype-$_ostype" + + RETVAL="$_arch" +} + +say() { + echo "wasm-pack-init: $1" +} + +err() { + say "$1" >&2 + exit 1 +} + +need_cmd() { + if ! check_cmd "$1" + then err "need '$1' (command not found)" + fi +} + +check_cmd() { + command -v "$1" > /dev/null 2>&1 + return $? +} + +need_ok() { + if [ $? != 0 ]; then err "$1"; fi +} + +assert_nz() { + if [ -z "$1" ]; then err "assert_nz $2"; fi +} + +# Run a command that should never fail. If the command fails execution +# will immediately terminate with an error showing the failing +# command. +ensure() { + "$@" + need_ok "command failed: $*" +} + +# This is just for indicating that commands' results are being +# intentionally ignored. Usually, because it's being executed +# as part of error handling. +ignore() { + "$@" +} + +# This wraps curl or wget. Try curl first, if not installed, +# use wget instead. +downloader() { + if check_cmd curl + then _dld=curl + elif check_cmd wget + then _dld=wget + else _dld='curl or wget' # to be used in error message of need_cmd + fi + + if [ "$1" = --check ] + then need_cmd "$_dld" + elif [ "$_dld" = curl ] + then curl -sSfL "$1" -o "$2" + elif [ "$_dld" = wget ] + then wget "$1" -O "$2" + else err "Unknown downloader" # should not reach here + fi +} + +main "$@" || exit 1 diff --git a/docs/installer/wasm-pack.js b/docs/installer/wasm-pack.js new file mode 100644 index 00000000..5d87b703 --- /dev/null +++ b/docs/installer/wasm-pack.js @@ -0,0 +1,92 @@ +var platforms = ["default", "unknown", "win64", "unix"]; +var platform_override = null; + +function detect_platform() { + "use strict"; + + if (platform_override !== null) { + return platforms[platform_override]; + } + + var os = "unknown"; + + if (navigator.platform == "Linux x86_64") {os = "unix";} + if (navigator.platform == "Linux i686") {os = "unix";} + if (navigator.platform == "Linux i686 on x86_64") {os = "unix";} + if (navigator.platform == "Linux aarch64") {os = "unix";} + if (navigator.platform == "Linux armv6l") {os = "unix";} + if (navigator.platform == "Linux armv7l") {os = "unix";} + if (navigator.platform == "Linux armv8l") {os = "unix";} + if (navigator.platform == "Linux ppc64") {os = "unix";} + if (navigator.platform == "Linux mips") {os = "unix";} + if (navigator.platform == "Linux mips64") {os = "unix";} + if (navigator.platform == "Mac") {os = "unix";} + // if (navigator.platform == "Win32") {os = "win32";} + if (navigator.platform == "Win64" || + navigator.userAgent.indexOf("WOW64") != -1 || + navigator.userAgent.indexOf("Win64") != -1) { os = "win64"; } + if (navigator.platform == "FreeBSD x86_64") {os = "unix";} + if (navigator.platform == "FreeBSD amd64") {os = "unix";} + if (navigator.platform == "NetBSD x86_64") {os = "unix";} + if (navigator.platform == "NetBSD amd64") {os = "unix";} + + // I wish I knew by now, but I don't. Try harder. + if (os == "unknown") { + // if (navigator.appVersion.indexOf("Win")!=-1) {os = "win32";} + if (navigator.appVersion.indexOf("Mac")!=-1) {os = "unix";} + // rust-www/#692 - FreeBSD epiphany! + if (navigator.appVersion.indexOf("FreeBSD")!=-1) {os = "unix";} + } + + // Firefox Quantum likes to hide platform and appVersion but oscpu works + if (navigator.oscpu) { + // if (navigator.oscpu.indexOf("Win32")!=-1) {os = "win32";} + if (navigator.oscpu.indexOf("Win64")!=-1) {os = "win64";} + if (navigator.oscpu.indexOf("Mac")!=-1) {os = "unix";} + if (navigator.oscpu.indexOf("Linux")!=-1) {os = "unix";} + if (navigator.oscpu.indexOf("FreeBSD")!=-1) {os = "unix";} + if (navigator.oscpu.indexOf("NetBSD")!=-1) {os = "unix";} + } + + return os; +} + +function adjust_for_platform() { + "use strict"; + + var platform = detect_platform(); + + platforms.forEach(function (platform_elem) { + var platform_div = document.getElementById("platform-instructions-" + platform_elem); + platform_div.style.display = "none"; + if (platform == platform_elem || + (platform == 'unknown' && platform_elem == 'default')) { + platform_div.style.display = "block"; + } + }); +} + +function go_to_default_platform() { + platform_override = 0; + adjust_for_platform(); +} + +function set_up_default_platform_buttons() { + var defaults_buttons = document.getElementsByClassName('default-platform-button'); + for (var i = 0; i < defaults_buttons.length; i++) { + defaults_buttons[i].onclick = go_to_default_platform; + } +} + +function fill_in_bug_report_values() { + var nav_plat = document.getElementById("nav-plat"); + var nav_app = document.getElementById("nav-app"); + nav_plat.textContent = navigator.platform; + nav_app.textContent = navigator.appVersion; +} + +(function () { + adjust_for_platform(); + set_up_default_platform_buttons(); + fill_in_bug_report_values(); +}()); diff --git a/src/installer.rs b/src/installer.rs new file mode 100644 index 00000000..d393e036 --- /dev/null +++ b/src/installer.rs @@ -0,0 +1,125 @@ +//! Self-installation of `wasm-pack` +//! +//! This module contains one public function which will self-install the +//! currently running executable as `wasm-pack`. Our goal is to install this in +//! a place that's already in `PATH`, ideally in an idiomatic location. To that +//! end we place `wasm-pack` next to the `rustup` executable in `PATH`. +//! +//! This installer is run directly (probably by clicking on it) on Windows, +//! meaning it will pop up a console (as we're a console app). Output goes to +//! the console and users interact with it through the console. On Unix this is +//! intended to be run from a shell script (docs/installer/init.sh) which is +//! downloaded via curl/sh, and then the shell script downloads this executable +//! and runs it. +//! +//! This may get more complicated over time (self upates anyone?) but for now +//! it's pretty simple! We're largely just moving over our currently running +//! executable to a different path. + +use std::env; +use std::fs; +use std::io; +use std::path::Path; +use std::process; + +use atty; +use failure::{Error, ResultExt}; + +pub fn install() -> ! { + if let Err(e) = do_install() { + eprintln!("{}", e); + for cause in e.iter_causes() { + eprintln!("Caused by: {}", cause); + } + } + + // On Windows we likely popped up a console for the installation. If we were + // to exit here immediately then the user wouldn't see any error that + // happened above or any successful message. Let's wait for them to say + // they've read everything and then continue. + if cfg!(windows) { + println!("Press enter to close this window..."); + let mut line = String::new(); + drop(io::stdin().read_line(&mut line)); + } + + process::exit(0); +} + +fn do_install() -> Result<(), Error> { + // Find `rustup.exe` in PATH, we'll be using its installation directory as + // our installation directory. + let path = env::var_os("PATH").unwrap_or_default(); + let rustup = env::split_paths(&path) + .map(|p| p.join("rustup").with_extension(env::consts::EXE_EXTENSION)) + .find(|p| p.exists()); + let rustup = match rustup { + Some(path) => path, + None => { + bail!( + "failed to find an installation of `rustup` in `PATH`, \ + is rustup already installed?" + ); + } + }; + let installation_dir = match rustup.parent() { + Some(parent) => parent, + None => bail!("can't install when `rustup` is at the root of the filesystem"), + }; + let destination = installation_dir + .join("wasm-pack") + .with_extension(env::consts::EXE_EXTENSION); + + if destination.exists() { + confirm_can_overwrite(&destination)?; + } + + // Our relatively simple install step! + let me = env::current_exe()?; + fs::copy(&me, &destination) + .with_context(|_| format!("failed to copy executable to `{}`", destination.display()))?; + println!( + "info: successfully installed wasm-pack to `{}`", + destination.display() + ); + + // ... and that's it! + + Ok(()) +} + +fn confirm_can_overwrite(dst: &Path) -> Result<(), Error> { + // If the `-f` argument was passed, we can always overwrite everything. + if env::args().any(|arg| arg == "-f") { + return Ok(()); + } + + // If we're not attached to a TTY then we can't get user input, so there's + // nothing to do except inform the user about the `-f` flag. + if !atty::is(atty::Stream::Stdin) { + bail!( + "existing wasm-pack installation found at `{}`, pass `-f` to \ + force installation over this file, otherwise aborting \ + installation now", + dst.display() + ); + } + + // It looks like we're at an interactive prompt, so ask the user if they'd + // like to overwrite the previous installation. + eprintln!( + "info: existing wasm-pack installation found at `{}`", + dst.display() + ); + eprint!("info: would you like to overwrite this file? [y/N]: "); + let mut line = String::new(); + io::stdin() + .read_line(&mut line) + .with_context(|_| "failed to read stdin")?; + + if line.starts_with("y") || line.starts_with("Y") { + return Ok(()); + } + + bail!("aborting installation"); +} diff --git a/src/main.rs b/src/main.rs index 39b56a35..9c83123b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,31 +1,42 @@ +extern crate atty; +#[macro_use] extern crate failure; #[macro_use] extern crate human_panic; extern crate structopt; extern crate wasm_pack; -use failure::Fail; use std::env; use structopt::StructOpt; -use wasm_pack::{command::run_wasm_pack, error::Error, logger, Cli}; +use wasm_pack::{command::run_wasm_pack, logger, Cli}; + +mod installer; fn main() { setup_panic!(); if let Err(e) = run() { eprintln!("{}", e); - for cause in Fail::iter_causes(&e) { + for cause in e.iter_causes() { eprintln!("Caused by: {}", cause); } ::std::process::exit(1); } } -fn run() -> Result<(), Error> { +fn run() -> Result<(), failure::Error> { // Deprecate `init` if let Some("init") = env::args().nth(1).as_ref().map(|arg| arg.as_str()) { println!("wasm-pack init is deprecated, consider using wasm-pack build"); } + if let Ok(me) = env::current_exe() { + // If we're actually running as the installer then execute our + // self-installation, otherwise just continue as usual. + if me.file_stem().and_then(|s| s.to_str()) == Some("wasm-pack-init") { + installer::install(); + } + } + let args = Cli::from_args(); let log = logger::new(&args.cmd, args.verbosity)?; run_wasm_pack(args.cmd, &log)?;