#!/usr/bin/env bash # DeepDive Installer set -euo pipefail : ${RELEASE:=v0.8-STABLE} # the DeepDive release version to install : ${BRANCH:=v0.8.x} # the DeepDive branch to clone, download, and build : ${PREFIX:=~/local} # the path to install deepdive : ${GITCLONE:=deepdive} # the path to clone deepdive's repo : ${GITHUB_BASEURL:=https://github.com/HazyResearch/deepdive} : ${INSTALLER_BRANCH:=master} # the branch from which the installer scripts should be downloaded INSTALLER_HOME_URL="$GITHUB_BASEURL"/raw/"$INSTALLER_BRANCH"/util/install INSTALLER_HOME_DIR=$(dirname "$0")/install # see if running from the git repo : ${INSTALLER_LOCAL_FIRST:=false} ! [[ -e "$INSTALLER_HOME_DIR"/../../.git ]] || INSTALLER_LOCAL_FIRST=true ! $INSTALLER_LOCAL_FIRST || # set GITCLONE to the containing git working copy when running from it case $(declare -p GITCLONE) in "declare --"*) false ;; *) true ;; esac || GITCLONE="$INSTALLER_HOME_DIR"/../.. INSTALLER_BRANCH=HEAD BRANCH=HEAD # use a fixed locale to ensure consistent behavior export LC_ALL=en_US.UTF-8 has() { type "$@"; } &>/dev/null error() { if [ -t 1 ]; then # on tty output, color errors in red echo -ne '\033[31m' echo "$@" echo -ne '\033[0m' else echo "$@" fi false } >&2 # find a way to get files over HTTP by URL if has curl; then get-url() { curl -fsSL "$@"; } elif has wget; then get-url() { wget -qO- "$@"; } else error "Neither curl nor wget is available" fi $INSTALLER_LOCAL_FIRST || # unless this is running directly from a git repo # run the correct installer directly from GitHub unless already doing so ${INSTALLER_REMOTE_EXEC:-false} || INSTALLER_REMOTE_EXEC=true \ exec bash <(set -x; get-url "${INSTALLER_HOME_URL}.sh") "$@" download() { local url=$1 local file=${2:-$(basename "$1")} file=$(python -c 'import urllib,sys;print urllib.unquote(sys.argv[1])' "$file") if [[ -e "$file" ]]; then echo "# File exists, skipping download from $url" else echo "# Downloading ${2:+$file from }$url" if type curl &>/dev/null; then curl -fLRo "$file" "$url" elif type wget &>/dev/null; then wget -N -O "$file" "$url" else error "No known method to download (curl or wget)" fi fi } timeout_or_do() { local timeout=${1:?Missing timeout in seconds}; shift local msg=${1:?Missing prompt message}; shift if [[ -t 0 ]]; then local key= if read -p "$msg" -t "$timeout" -n 1 -s key; echo; [[ -n "$key" ]]; then "$@" fi fi } INSTALLER_TEMP_DIR= init_INSTALLER_TEMP_DIR() { # makes sure INSTALLER_TEMP_DIR points to a temporary directory if ! [[ -d "$INSTALLER_TEMP_DIR" ]]; then INSTALLER_TEMP_DIR=$(mktemp -d "${TMPDIR:-/tmp}"/deepdive-installer.XXXXXXX) trap "rm -rf $INSTALLER_TEMP_DIR" EXIT fi } # common installers ########################################################### # installs DeepDive's build dependencies install__deepdive_build_deps() { local deps= deps=( git make bzip2 unzip jdk ) error "Please make sure the following packages are available on your system:$( printf '\n %s' "${deps[@]}")" || [[ ! -t 1 ]] || read -p "# Continue? " || true } # installs DeepDive's runtime dependencies install__deepdive_runtime_deps() { local deps= deps=( bash coreutils xargs bc gnuplot jre python ) error "Please make sure the following packages are available on your system:$( printf '\n %s' "${deps[@]}")" || [[ ! -t 1 ]] || read -p "# Continue? " || true } # fetches DeepDive source tree install__deepdive_git_repo() { # $GITCLONE already points to a git clone and the branch can be checked out { [[ -e "$GITCLONE"/.git ]] && (cd "$GITCLONE" && git checkout "$BRANCH"); } || # or grab a clone with git git clone --recursive --branch "$BRANCH" "$GITHUB_BASEURL".git "$GITCLONE" } # installs DeepDive from source by going through the full build install__deepdive_from_source_no_dependencies() { # clone git repo if necessary run_installer_for _deepdive_git_repo cd "$GITCLONE" echo "# Installing from DeepDive source at $PWD" # install DeepDive from source make install PREFIX="$PREFIX" } install__deepdive_from_source() { # prepare fetching and building source run_installer_for _deepdive_build_deps run_installer_for _deepdive_from_source_no_dependencies # install runtime dependencies run_installer_for _deepdive_runtime_deps } # utilities to allow selecting a different RELEASE interactively timeout_or_select_release() { local forWhat=${1:?Missing description of release}; shift "$@" # show what will happen with the current release timeout_or_do 2 "# Press any key to select a different release or Enter to proceed..." \ select_release "$forWhat" "$@" } select_release() { local forWhat=${1:?Missing description of release}; shift PS3="# Select a release${forWhat:+ $forWhat}: " select release in $(list_deepdive_releases); do case $release in "") break ;; *) RELEASE=$release "$@" # show what will happen with the selected release break esac done } list_deepdive_releases() { # find all links to a GitHub tree from the release page get-url "$GITHUB_BASEURL"/releases/ | sed ' \:/.*/tree/:!d s/.*href="//; s/".*$// s:.*/tree/:: s:/.*$:: ' } # installs DeepDive with a release binary install_deepdive_from_release() { timeout_or_select_release "to install" \ eval 'echo "# Installing DeepDive release $RELEASE..."' local os=$(uname) local arch=$(uname -m) case $os-$arch in Darwin-x86_64|\ Linux-x86_64) ;; # we publish binaries for x86 (64bit) only *) error "$os-$arch: No binary release available for your OS" "DeepDive must be installed from source" esac local tarball="deepdive-${RELEASE}-${os}.tar.gz" local url="$GITHUB_BASEURL/releases/download/${RELEASE}/$tarball" ( # showing what is going on set -x rm -f "$tarball" # download tarball download "$url" # unpack tarball mkdir -p "$PREFIX" tar xzvf "$tarball" -C "$PREFIX" ) || return $? echo echo "# DeepDive release $RELEASE has been installed at $PREFIX" echo "# Please add the following line to your ~/.bash_profile:" echo "export PATH=\"$PREFIX/bin:\$PATH\"" } # installs DeepDive with a release binary and runtime dependencies install_deepdive() { run_installer_for deepdive_from_release run_installer_for _deepdive_runtime_deps } # installs DeepDive examples and tests install__deepdive_examples_tests() { timeout_or_select_release "to download examples and tests from" \ eval 'echo "# Downloading examples and tests (from $RELEASE)..."' download_deepdive_github_tree "DeepDive examples and tests" \ deepdive-${RELEASE#v} \ examples test {compiler,runner,inference,shell,util{,/build}}/test } install_spouse_example() { timeout_or_select_release "to download spouse example from" \ eval 'echo "# Downloading spouse example (from $RELEASE)..."' download_deepdive_github_tree "Spouse example DeepDive app" \ spouse_example-${RELEASE#v} \ examples/spouse } # launches tutorial notebook install_deepdive_example_notebook() { : ${NOTEBOOK_BRANCH:=tutorial-in-a-notebook} : ${NOTEBOOK_PATH:=examples/spouse/DeepDive%20Tutorial%20-%20Extracting%20mentions%20of%20spouses%20from%20the%20news.ipynb} : ${NOTEBOOK_URL:=$GITHUB_BASEURL/raw/$NOTEBOOK_BRANCH/$NOTEBOOK_PATH} mkdir -p deepdive_notebooks cd deepdive_notebooks download "$NOTEBOOK_URL" PATH="$PREFIX/jupyter/bin:$PATH" type jupyter || run_installer_for _jupyter jupyter notebook *.ipynb } # launches a Jupyter notebook after installing it locally install_jupyter_notebook() { PATH="$PREFIX/jupyter/bin:$PATH" type jupyter || run_installer_for _jupyter jupyter notebook } # installs a local Jupyter within a virtualenv with easy_install or pip from scratch install__jupyter() { PATH="$PREFIX/jupyter/bin:$PATH" if ! [[ -x "$PREFIX"/jupyter/bin/jupyter ]]; then if ! [[ -x "$PREFIX"/jupyter/bin/pip ]]; then : ${PYTHONVERSION:=2.7} # easy_install should be available on Mac by default and many other Python installations if type easy_install-$PYTHONVERSION; then # use easy_install to bootstrap a virutalenv that has pip export PYTHONPATH="$PREFIX"/jupyter/lib/python$PYTHONVERSION/site-packages mkdir -p "$PYTHONPATH" easy_install-$PYTHONVERSION --prefix "$PREFIX"/jupyter virtualenv else # download get-pip.py to bootstrap a virtualenv mkdir -p "$PREFIX"/jupyter/bootstrap ( cd "$PREFIX"/jupyter/bootstrap download https://bootstrap.pypa.io/get-pip.py [[ -e virtualenv.py ]] || "$(type -p python)" get-pip.py virtualenv --ignore-installed --target . ) virtualenv() { PYTHONPATH="$PREFIX"/jupyter/bootstrap \ "$(type -p python)" -c 'import virtualenv; virtualenv.main()' "$@"; } fi virtualenv "$PREFIX"/jupyter fi # use pip to install Jupyter pip install jupyter fi type jupyter } # how to download a subtree of DeepDive's GitHub repo download_deepdive_github_tree() { local what=$1; shift local dest=$1; shift # the rest of the arguments are relative paths to download if [[ -s "$dest"/.downloaded ]]; then echo "# $what already downloaded at $(cd "$dest" && pwd)" else local tarballPrefix=deepdive-"${RELEASE#v}" local tmpdir="$dest".download mkdir -p "$tmpdir" set -x get-url "$GITHUB_BASEURL"/archive/"$RELEASE".tar.gz | tar xvzf - -C "$tmpdir" "${@/#/$tarballPrefix/}" ! [[ -e "$dest" ]] || mv -f "$dest" "$dest"~ if [[ $# -eq 1 && -d "$tmpdir/$tarballPrefix/$1" ]]; then # don't nest things deep when downloading only one path mv -f "$tmpdir/$tarballPrefix/$1" "$dest" else mv -f "$tmpdir/$tarballPrefix" "$dest" fi date >"$dest"/.downloaded rm -rf "$tmpdir" fi } # runs tests against installed DeepDive install_run_deepdive_tests() { run_installer_for _deepdive_examples_tests set -x PATH="$PREFIX/bin:$PATH" \ deepdive env deepdive-${RELEASE#v}/test/test-installed.sh } # sandbox with Docker Compose install_deepdive_docker_sandbox() { if has docker-compose; then RELEASE=master # always use the sandbox on master branch download_deepdive_github_tree "DeepDive Sandbox" \ deepdive-${RELEASE#v} \ sandbox test/postgresql/Dockerfile.postgres cd deepdive-${RELEASE#v}/sandbox docker-compose pull docker-compose up --no-build deepdive-notebooks else error "docker-compose not available" \ "Please install it first from: https://docs.docker.com/compose/install/" fi } ################################################################################ # detect operating system case $(uname) in Linux) if has apt-get && has debconf; then # Ubuntu/Debian os=Ubuntu # TODO support other Linux distros elif [[ -e /etc/redhat-release ]]; then # CentOS/RedHat os=RedHat else # unrecognized Linux distro os= # try to grab a Linux distro identifier { set -o pipefail hint=$(lsb_release -i | cut -f2) || hint=$(. /etc/lsb-release && echo $DISTRIB_ID) || hint=$(head -1 /etc/redhat-release) || hint= # give up finding hint } 2>/dev/null error "WARNING: Unsupported GNU/Linux distribution${hint:+: $hint}" fi ;; Darwin) # Mac OS X os=Mac ;; *) # unsupported OS os= error "WARNING: Unsupported Operating System: $(uname)" esac || error "To build and install DeepDive correctly on your system, see:" || error " http://deepdive.stanford.edu/doc/advanced/developer.html#build-test" || error "Beware that the following installer options may not work." || true echo "### DeepDive installer${os:+ for $os}" # load OS-specific install scripts located at install/install.*.sh # each script defines bash functions whose names start with `install_`. source_script() { local script=$1 if $INSTALLER_LOCAL_FIRST && [[ -e "$INSTALLER_HOME_DIR/$script" ]]; then source "$INSTALLER_HOME_DIR/$script" else # may be this script is run as a one-liner, get script from GitHub #source <(get-url "$INSTALLER_HOME_URL/$script") "$@" # XXX using a workaround since source with process substitution has problem in bash 3 (OS X default) # See: https://bugzilla.altlinux.org/show_bug.cgi?id=7475 init_INSTALLER_TEMP_DIR local script_path="$INSTALLER_TEMP_DIR/$script" mkdir -p "$(dirname "$script_path")" get-url "$INSTALLER_HOME_URL/$script" >"$script_path" source "$script_path" "$@" fi } source_os_script() { source_script install."$os"."$1".sh; } [[ -z "$os" ]] || source_script install."$os".sh # run selected installers, either interactively or via command-line arguments list_installer_names() { local show_only=${1:-visible} # find installer names from all defined install_* functions _list_installer_names() { declare -F | sed 's/^declare -f //; /^install_/!d; s/^install_//' } # unless the first argument is 'all', hide installers whose name begins with underscore case $show_only in all) list_installer_names visible list_installer_names hidden ;; hidden) _list_installer_names | sed '/^_/!d' ;; visible) _list_installer_names | sed '/^_/d' ;; esac } run_installer_for() { ( # XXX running this in a context where set -e (errexit) is ignored, e.g., in # if, while, ||, or && command lists (except the last one) is problematic # since the install_* functions will continue running even upon errors. set +e; (set -e; false; true); local errexit_effective=$?; set -e if [[ $errexit_effective -eq 0 ]]; then read line file <<<$(caller) error "INSTALLER INTERNAL ERROR: $file:$line: "'run_installer_for cannot be run in a context where `set -e` is ignored.' return 1 fi # run installer by name local name=$1 local install_func="install_$name" if type "$install_func" &>/dev/null; then echo "## Starting installation for $name" # isolate install function in a subshell set +e; (set -e +u; "$install_func"); local c=$?; set -e if [[ $c -eq 0 ]]; then echo "## Finished installation for $name" else error "## Failed installation for $name" || true return $c fi else error "No such installer: $name" fi ) } if [[ $# -eq 0 ]]; then if [[ -t 0 ]]; then # ask user what to install if input is a tty set +e # dont abort the select loop on installer error interactive_installer() { local show_only=${1:-visible} case $show_only in all) PS3="# Install what (enter to repeat options, q to quit, or a number)? " ;; *) PS3="# Install what (enter to repeat options, a to see all, q to quit, or a number)? " ;; esac select option in $(list_installer_names $show_only); do if [[ -n "$option" ]]; then run_installer_for "$option" else case $REPLY in ""|q) # quit break ;; a) # show all options (including hidden ones) case $show_only in all) invalid_choice ;; *) interactive_installer all; break ;; esac ;; *) invalid_choice esac continue fi done } invalid_choice() { error "$REPLY: Invalid option"; } interactive_installer else # otherwise, show options echo "# Specify what to install as command-line arguments:" list_installer_names all # TODO show what each installer does false fi else # what to install specified via command line arguments for name; do run_installer_for "$name" done fi