#!/bin/bash # This program contains parts of narwhal's "sea" program, # as well as bits borrowed from Tim Caswell's "nvm" # nave install # Fetch the version of node and install it in nave's folder. # nave use # Install the if it isn't already, and then start # a subshell with that version's folder at the start of the # $PATH # nave use program.js # Like "nave use", but have the subshell start the program.js # immediately. # When told to use a version: # Ensure that the version exists, install it, and # then add its prefix to the PATH, and start a subshell. if [ "$NAVE_DEBUG" != "" ]; then set -x fi if [ -z "$BASH" ]; then cat >&2 <&2 exit 1 ;; esac # Use fancy pants globs shopt -s extglob # Try to figure out the os and arch for binary fetching uname="$(uname -a)" os= arch= case "$uname" in Linux\ *) os=linux ;; Darwin\ *) os=darwin ;; SunOS\ *) os=sunos ;; esac case "$uname" in *i386*) arch=x86 ;; *x86_64*) arch=x64 ;; *raspberrypi*) arch=arm-pi ;; esac tar=${TAR-tar} main () { local SELF_PATH DIR SYM # get the absolute path of the executable SELF_PATH="$0" if [ "${SELF_PATH:0:1}" != "." ] && [ "${SELF_PATH:0:1}" != "/" ]; then SELF_PATH=./"$SELF_PATH" fi SELF_PATH=$( cd -P -- "$(dirname -- "$SELF_PATH")" \ && pwd -P \ ) && SELF_PATH=$SELF_PATH/$(basename -- "$0") # resolve symlinks while [ -h "$SELF_PATH" ]; do DIR=$(dirname -- "$SELF_PATH") SYM=$(readlink -- "$SELF_PATH") SELF_PATH=$( cd -- "$DIR" \ && cd -- $(dirname -- "$SYM") \ && pwd \ )/$(basename -- "$SYM") done if ! [ -d "$NAVE_DIR" ]; then if [ -d "$HOME" ]; then NAVE_DIR="$HOME"/.nave else NAVE_DIR=/usr/local/lib/nave fi fi if ! [ -d "$NAVE_DIR" ] && ! mkdir -p -- "$NAVE_DIR"; then NAVE_DIR="$(dirname -- "$SELF_PATH")" fi # set up the naverc init file. # For zsh compatibility, we name this file ".zshenv" instead of # the more reasonable "naverc" name. # Important! Update this number any time the init content is changed. local rcversion="#3" local rcfile="$NAVE_DIR/.zshenv" if ! [ -f "$rcfile" ] \ || [ "$(head -n1 "$rcfile")" != "$rcversion" ]; then cat > "$rcfile" < "$NAVE_DIR/.zlogout" <&2 exit $ret fi } function enquote_all () { local ARG ARGS ARGS="" for ARG in "$@"; do [ -n "$ARGS" ] && ARGS="$ARGS " ARGS="$ARGS'""$( echo " $ARG" \ | cut -c 2- \ | sed 's/'"'"'/'"'"'"'"'"'"'"'"'/g' \ )""'" done echo "$ARGS" } ensure_dir () { if ! [ -d "$1" ]; then mkdir -p -- "$1" || fail "couldn't create $1" fi } remove_dir () { if [ -d "$1" ]; then rm -rf -- "$1" || fail "Could not remove $1" fi } fail () { echo "$@" >&2 exit 1 } nave_fetch () { local version=$(ver "$1") if nave_has "$version"; then echo "already fetched $version" >&2 return 0 fi local src="$NAVE_SRC/$version" remove_dir "$src" ensure_dir "$src" local url local urls=( "https://iojs.org/dist/v$version/iojs-v$version.tar.gz" "http://nodejs.org/dist/v$version/node-v$version.tar.gz" "http://nodejs.org/dist/node-v$version.tar.gz" "http://nodejs.org/dist/node-$version.tar.gz" ) for url in "${urls[@]}"; do get -#Lf "$url" > "$src".tgz if [ $? -eq 0 ]; then $tar xzf "$src".tgz -C "$src" --strip-components=1 if [ $? -eq 0 ]; then echo "fetched from $url" >&2 return 0 fi fi done rm "$src".tgz remove_dir "$src" echo "Couldn't fetch $version" >&2 return 1 } get () { curl -H "user-agent:nave/$(curl --version | head -n1)" "$@" return $? } build () { local version="$1" # shortcut - try the binary if possible. if [ -n "$os" ]; then local binavail # binaries started with node 0.8.6 case "$version" in 0.8.[012345]) binavail=0 ;; 0.[1234567]) binavail=0 ;; *) binavail=1 ;; esac if [ $binavail -eq 1 ]; then local t="$version-$os-$arch" local tgz="$NAVE_SRC/$t.tgz" for url in "https://iojs.org/dist/v$version/iojs-v${t}.tar.gz" \ "http://nodejs.org/dist/v$version/node-v${t}.tar.gz"; do get -#Lf "$url" > "$tgz" if [ $? -ne 0 ]; then # binary download failed. oh well. cleanup, and proceed. rm "$tgz" else # unpack straight into the build target. $tar xzf "$tgz" -C "$2" --strip-components 1 if [ $? -ne 0 ]; then rm "$tgz" nave_uninstall "$version" echo "Binary unpack failed, trying source." >&2 fi # it worked! echo "installed from binary" >&2 return 0 fi done echo "Binary download failed, trying source." >&2 fi fi nave_fetch "$version" if [ $? != 0 ]; then # fetch failed, don't continue and try to build it. return 1 fi local src="$NAVE_SRC/$version" local jobs=$NAVE_JOBS jobs=${jobs:-$JOBS} jobs=${jobs:-$(sysctl -n hw.ncpu)} jobs=${jobs:-2} ( cd -- "$src" [ -f ~/.naverc ] && . ~/.naverc || true if [ "$NAVE_CONFIG" == "" ]; then NAVE_CONFIG=() fi JOBS=$jobs ./configure "${NAVE_CONFIG[@]}" --prefix="$2" \ || fail "Failed to configure $version" JOBS=$jobs make -j$jobs \ || fail "Failed to make $version" make install || fail "Failed to install $version" ) || fail "fail" return $? } nave_usemain () { if [ ${NAVELVL-0} -gt 0 ]; then fail "Can't usemain inside a nave subshell. Exit to main shell." fi local version=$(ver "$1") local current=$(node -v || true) local wn=$(which node || true) local prefix="/usr/local" if [ "x$wn" != "x" ]; then prefix="${wn/\/bin\/node/}" if [ "x$prefix" == "x" ]; then prefix="/usr/local" fi fi current="${current/v/}" if [ "$current" == "$version" ]; then echo "$version already installed" >&2 return 0 fi build "$version" "$prefix" } nave_install () { local version=$(ver "$1") if [ -z "$version" ]; then fail "Must supply a version ('stable', 'latest' or numeric)" fi if nave_installed "$version"; then echo "Already installed: $version" >&2 return 0 fi local install="$NAVE_ROOT/$version" ensure_dir "$install" build "$version" "$install" local ret=$? if [ $ret -ne 0 ]; then remove_dir "$install" return $ret fi } nave_test () { local version=$(ver "$1") nave_fetch "$version" local src="$NAVE_SRC/$version" ( cd -- "$src" [ -f ~/.naverc ] && . ~/.naverc || true if [ "$NAVE_CONFIG" == "" ]; then NAVE_CONFIG=() fi ./configure "${NAVE_CONFIG[@]}" || fail "failed to ./configure" make test-all || fail "Failed tests" ) || fail "failed" } nave_ls () { ls -- $NAVE_SRC | version_list "src" \ && ls -- $NAVE_ROOT | version_list "installed" \ && nave_ls_named \ || return 1 } nave_ls_remote () { get -s http://nodejs.org/dist/ \ | version_list "remote" \ || return 1 } nave_ls_named () { echo "named:" ls -- "$NAVE_ROOT" \ | egrep -v '[0-9]+\.[0-9]+\.[0-9]+' \ | sort \ | while read name; do echo "$name: $(ver $($NAVE_ROOT/$name/bin/node -v 2>/dev/null))" done } nave_ls_all () { nave_ls \ && (echo ""; nave_ls_remote) \ || return 1 } ver () { local version="$1" local nonames="$2" version="${version/v/}" case $version in latest | stable) nave_$version ;; +([0-9])\.+([0-9])) nave_version_family "$version" ;; +([0-9])\.+([0-9])\.+([0-9])) echo $version ;; *) [ "$nonames" = "" ] && echo $version ;; esac } nave_version_family () { local family="$1" family="${family/v/}" { get -s http://nodejs.org/dist/; get -s https://iojs.org/dist/; } \ | egrep -o $family'\.[0-9]+' \ | sort -u -k 1,1n -k 2,2n -k 3,3n -t . \ | tail -n1 } nave_latest () { get -s https://iojs.org/dist/ \ | egrep -o '[0-9]+\.[0-9]+\.[0-9]+' \ | sort -u -k 1,1n -k 2,2n -k 3,3n -t . \ | tail -n1 } nave_stable () { get -s http://nodejs.org/dist/ \ | egrep -o '[0-9]+\.[0-9]*[02468]\.[0-9]+' \ | sort -u -k 1,1n -k 2,2n -k 3,3n -t . \ | tail -n1 } version_list_named () { egrep -v '[0-9]+\.[0-9]+\.[0-9]+' \ | sort -u -k 1,1n -k 2,2n -k 3,3n -t . \ | organize_version_list \ || return 1 } version_list () { echo "$1:" egrep -o '[0-9]+\.[0-9]+\.[0-9]+' \ | sort -u -k 1,1n -k 2,2n -k 3,3n -t . \ | organize_version_list \ || return 1 } organize_version_list () { local i=0 local v while read v; do if [ $i -eq 8 ]; then i=0 echo "$v" else let 'i = i + 1' echo -ne "$v\t" fi done echo "" [ $i -ne 0 ] && echo "" return 0 } nave_has () { local version=$(ver "$1") [ -x "$NAVE_SRC/$version/configure" ] || return 1 } nave_installed () { local version=$(ver "$1") [ -x "$NAVE_ROOT/$version/bin/node" ] || return 1 } nave_use () { local version=$(ver "$1") # if it's not a version number, then treat as a name. case "$version" in +([0-9])\.+([0-9])\.+([0-9])) ;; *) nave_named "$@" return $? ;; esac if [ -z "$version" ]; then fail "Must supply a version" fi if [ "$version" == "$NAVENAME" ]; then echo "already using $version" >&2 if [ $# -gt 1 ]; then shift "$@" fi return $? fi nave_install "$version" || fail "failed to install $version" local prefix="$NAVE_ROOT/$version" local lvl=$[ ${NAVELVL-0} + 1 ] echo "using $version" >&2 if [ $# -gt 1 ]; then shift nave_exec "$lvl" "$version" "$version" "$prefix" "$@" return $? else nave_login "$lvl" "$version" "$version" "$prefix" return $? fi } # internal nave_exec () { nave_run "exec" "$@" return $? } nave_login () { nave_run "login" "$@" return $? } nave_run () { local exec="$1" shift local lvl="$1" shift local name="$1" shift local version="$1" shift local prefix="$1" shift local bin="$prefix/bin" local lib="$prefix/lib/node" local man="$prefix/share/man" ensure_dir "$bin" ensure_dir "$lib" ensure_dir "$man" # now $@ is the command to run, or empty if it's not an exec. local exit_code local args=() local isLogin if [ "$exec" == "exec" ]; then isLogin="" # source the nave env file, then run the command. args=("-c" ". $(enquote_all $NAVE_DIR/.zshenv); $(enquote_all "$@")") elif [ "$shell" == "zsh" ]; then isLogin="1" # no need to set rcfile, since ZDOTDIR is set. args=() else isLogin="1" # bash, use --rcfile argument args=("--rcfile" "$NAVE_DIR/.zshenv") fi local nave="$version" if [ "$version" != "$name" ]; then nave="$name"-"$version" fi NAVELVL=$lvl \ NAVEPATH="$bin" \ NAVEVERSION="$version" \ NAVENAME="$name" \ NAVE="$nave" \ npm_config_binroot="$bin"\ npm_config_root="$lib" \ npm_config_manroot="$man" \ npm_config_prefix="$prefix" \ NODE_PATH="$lib" \ NAVE_LOGIN="$isLogin" \ NAVE_DIR="$NAVE_DIR" \ ZDOTDIR="$NAVE_DIR" \ "$SHELL" "${args[@]}" exit_code=$? hash -r return $exit_code } nave_named () { local name="$1" shift local version=$(ver "$1" NONAMES) if [ "$version" != "" ]; then shift fi add_named_env "$name" "$version" || fail "failed to create $name env" if [ "$name" == "$NAVENAME" ] && [ "$version" == "$NAVEVERSION" ]; then echo "already using $name" >&2 if [ $# -gt 0 ]; then "$@" fi return $? fi if [ "$version" = "" ]; then version="$(ver "$("$NAVE_ROOT/$name/bin/node" -v 2>/dev/null)")" fi local prefix="$NAVE_ROOT/$name" local lvl=$[ ${NAVELVL-0} + 1 ] # get the version if [ $# -gt 0 ]; then nave_exec "$lvl" "$name" "$version" "$prefix" "$@" return $? else nave_login "$lvl" "$name" "$version" "$prefix" return $? fi } add_named_env () { local name="$1" local version="$2" local cur="$(ver "$($NAVE_ROOT/$name/bin/node -v 2>/dev/null)" "NONAMES")" if [ "$version" != "" ]; then version="$(ver "$version" "NONAMES")" else version="$cur" fi if [ "$version" = "" ]; then echo "What version of node?" read -p "stable, latest, x.y, or x.y.z > " version version=$(ver "$version") fi # if that version is already there, then nothing to do. if [ "$cur" = "$version" ]; then return 0 fi echo "Creating new env named '$name' using node $version" >&2 nave_install "$version" || fail "failed to install $version" ensure_dir "$NAVE_ROOT/$name/bin" ensure_dir "$NAVE_ROOT/$name/lib/node" ensure_dir "$NAVE_ROOT/$name/lib/node_modules" ensure_dir "$NAVE_ROOT/$name/share/man" ln -sf -- "$NAVE_ROOT/$version/bin/node" "$NAVE_ROOT/$name/bin/node" ln -sf -- "$NAVE_ROOT/$version/bin/npm" "$NAVE_ROOT/$name/bin/npm" ln -sf -- "$NAVE_ROOT/$version/bin/node-waf" "$NAVE_ROOT/$name/bin/node-waf" } nave_clean () { rm -rf "$NAVE_SRC/$(ver "$1")" "$NAVE_SRC/$(ver "$1")".tgz "$NAVE_SRC/$(ver "$1")"-*.tgz } nave_uninstall () { remove_dir "$NAVE_ROOT/$(ver "$1")" } nave_help () { cat < Commands: install Install the version passed (ex: 0.1.103) use Enter a subshell where is being used use Enter a subshell, and run "", then exit use Create a named env, using the specified version. If the name already exists, but the version differs, then it will update the link. usemain Install in /usr/local/bin (ie, use as your main nodejs) clean Delete the source code for uninstall Delete the install for ls List versions currently installed ls-remote List remote node versions ls-all List remote and local node versions latest Show the most recent dist version help Output help information can be the string "latest" to get the latest distribution. can be the string "stable" to get the latest stable version. EOF } main "$@"