Skip to content
Snippets Groups Projects
install.sh 15.07 KiB
#!/usr/bin/env bash

for flag in "$@"; do
  if [ "$flag" = "-h" -o "$flag" = "--help" ]; then
    echo "Command-line installer for LarvaTagger"
    echo
    echo "Usage:"
    echo "  $0"
    echo "  WITH_BACKEND=1 $0"
    echo "  WITH_BACKEND=1 $0 --legacy"
    echo "  $0 --with-default-backend"
    echo "  $0 --with-backend --legacy"
    echo "  $0 --uninstall"
    echo "  $0 --help"
    echo
    echo "The legacy installation path for the MaggotUBA-based tagger relies"
    echo "on Python3.8 and Torch1."
    echo "The current default relies on Python3.11 and Torch2."
    echo "Other environment variables can be set, similarly to WITH_BACKEND,"
    echo "to control the script's behavior:"
    echo "  variable                    default value"
    echo "  BIN_DIR                     ~/.local/bin"
    echo "  LARVATAGGER_PATH            ~/.local/share/larvatagger"
    echo "  JULIA_CHANNEL               lts"
    echo "  JULIA_VERSION               1.10"
    echo "  PYTHON_VERSION              3.11  (3.8 if --legacy)"
    echo "  MAGGOTUBA_CORE_BRANCH       main"
    echo "  MAGGOTUBA_ADAPTER_BRANCH    torch2  (main if --legacy)"
    echo "  TAGGINGBACKENDS_BRANCH      main"
    echo "  PLANARLARVAE_BRANCH         main"
    echo "  LARVATAGGER_BRANCH          main"
    exit 0
  fi
done

[ -d "`pwd`" ] || cd

if [ -z "$BIN_DIR" ]; then
  BIN_DIR=~/.local/bin
else
  echo "Using environment variable: BIN_DIR= $BIN_DIR"
  echo "Only ~/.local/bin is fully supported at the moment"
fi
if [ -z "$LARVATAGGER_PATH" ]; then
  LARVATAGGER_PATH=~/.local/share/larvatagger
else
  echo "Using environment variable: LARVATAGGER_PATH= $LARVATAGGER_PATH"
fi

if [ "$1" = "--uninstall" ]; then
  for pkg in MaggotUBA TaggingBackends; do
    if [ -d "$LARVATAGGER_PATH/$pkg" ]; then
      env=$(cd "$LARVATAGGER_PATH/$pkg" && poetry env info -p 2>/dev/null)
      [ -d "$env" ] && rm -rf "$env"
    fi
  done
  rm -rf "$BIN_DIR/larvatagger"
  rm -rf "$LARVATAGGER_PATH"

  # testing only; deletes Poetry, PyEnv and JuliaUp, and does not check beforehands
  # whether these dependencies were installed by the present script or not
  if [ "$2" = "--full" ]; then
    PYTHON="python3"
    if [[ "`python3 -V`" =~ "Python 3.6" ]] && command -v python3.8 &>/dev/null; then
      # issue on Maestro
      PYTHON="python3.8"
    fi
    command -v poetry &>/dev/null && curl -sSL https://install.python-poetry.org | $PYTHON - --uninstall
    command -v pyenv &>/dev/null && rm -rf $(pyenv root)
    command -v juliaup &>/dev/null && juliaup self uninstall
    rm -rf ~/.juliaup
    # TODO: clean up .bash_profile for pyenv-related stuff, .bashrc/.zshrc for PATH manipulations, and restart the shell
    rm -rf ~/.julia
  fi
else

# former default:
PYTHON_VERSION=3.8

# the internal_<VAR> variables need to be set non-empty only if
# the corresponding <VAR> variable is non-empty; they are used to
# determine whether or not to report externally sourced variables
internal_WITH_BACKEND=
internal_MAGGOTUBA_ADAPTER_BRANCH=
internal_MAGGOTUBA_ADAPTER_FREE_DEPENDENCIES=
internal_LEGACY=
internal_WITH_DEFAULT_BACKEND=

# primary use cases:
# WITH_BACKEND=1 scripts/install.sh
# WITH_BACKEND=1 scripts/install.sh --legacy
# scripts/install.sh --with-default-backend
# scripts/install.sh --with-backend
# scripts/install.sh --with-backend --experimental
# scripts/install.sh --with-backend --legacy

for arg in "$@"; do
  if [ "$arg" = "--with-default-backend" ]; then
    WITH_BACKEND=1
    internal_WITH_BACKEND=1
    internal_WITH_DEFAULT_BACKEND=1
    if [ "$internal_LEGACY" = "1" ]; then
      echo "Ignoring --legacy; pass --with-backend --legacy instead"
      internal_LEGACY=0
    fi
    break
  elif [ "$arg" = "--with-backend" ]; then
    WITH_BACKEND=1
    internal_WITH_BACKEND=1
  elif [ "$arg" = "--experimental" ]; then
    echo "The --experimental flag is deprecated and is now default"
    internal_LEGACY=0
  elif [ "$arg" = "--legacy" ]; then
    internal_LEGACY=1
    MAGGOTUBA_CORE_BRANCH=
    # former default (empty) falls back to main
    MAGGOTUBA_ADAPTER_BRANCH=
  elif [ "$arg" = "--free-python-dependencies" ]; then
    internal_MAGGOTUBA_ADAPTER_FREE_DEPENDENCIES=1
  elif [ "$arg" = "--lock-python-dependencies" ]; then
    internal_MAGGOTUBA_ADAPTER_FREE_DEPENDENCIES=0
  fi
done

if [ "$WITH_BACKEND" = "1" -a "$internal_LEGACY" = "0" ]; then
  # new defaults (formerly "--experimental")
  MAGGOTUBA_CORE_BRANCH=
  MAGGOTUBA_ADAPTER_BRANCH=torch2
  internal_MAGGOTUBA_ADAPTER_BRANCH=1
  PYTHON_VERSION=3.11
elif [ "$internal_LEGACY" != "1" ]; then
  if [ "$internal_WITH_DEFAULT_BACKEND" = "1" ]; then
    echo "The Python3.11/Torch2 backend is now default"
    echo "Pass flags --with-backend --legacy to select the former Python3.8/Torch1 backend"
  fi
  MAGGOTUBA_CORE_BRANCH=
  MAGGOTUBA_ADAPTER_BRANCH=torch2
  internal_MAGGOTUBA_ADAPTER_BRANCH=1
  PYTHON_VERSION=3.11
fi

PYTHON="python$PYTHON_VERSION"

check_brew() {
  if [ "`uname`" = "Darwin" ]; then
    # macOS
    if ! command -v brew &>/dev/null; then
      if ! [ -f /opt/homebrew/bin/brew ]; then
        echo "Installing Homebrew; admin rights will be requested"
        /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
      fi
      # not sure why zsh does not support ~/ below, while it seems to behave properly elsewhere...
      cat <<"EOF" >>$HOME/.zprofile

eval "$(/opt/homebrew/bin/brew shellenv)"
EOF
      eval "$(/opt/homebrew/bin/brew shellenv)"
    fi
  fi
}

if ! command -v realpath &>/dev/null; then
  # macOS
  check_brew
  if command -v brew &>/dev/null; then
    brew install coreutils
  fi
fi

if ! command -v curl &>/dev/null; then
  if [ "`uname`" = "Darwin" ]; then
    check_brew
    # macOS users are not given the choice as they usually do not care about freedom
    brew install curl
  fi
fi
if ! command -v curl &>/dev/null; then
  echo "Command curl required; aborting"
else

if [ -z "$JULIA_VERSION" ]; then
  JULIA_VERSION=1.10
  JULIA_CHANNEL=1.10
else
  echo "Using environment variable: JULIA_VERSION= $JULIA_VERSION"
  if [ -z "$JULIA_CHANNEL" ]; then
    JULIA_CHANNEL=$JULIA_VERSION
  else
    echo "Using environment variable: JULIA_CHANNEL= $JULIA_CHANNEL"
  fi
fi

JULIA="julia"

install_juliaup() {
  if [ -z "$JULIA_INSTALL_ARGS" ]; then
    JULIA_INSTALL_ARGS=-y
  else
    echo "Using environment variable: JULIA_INSTALL_ARGS= $JULIA_INSTALL_ARGS"
  fi
  curl -fsSL https://install.julialang.org | sh -s -- $JULIA_INSTALL_ARGS --default-channel $JULIA_VERSION
  export PATH=~/.juliaup/bin:$PATH
}
if ! command -v $JULIA &>/dev/null; then
  install_juliaup
elif ! [[ "`$JULIA -v`" =~ "julia version $JULIA_VERSION" ]]; then
  if ! command -v juliaup &>/dev/null; then
    install_juliaup
  fi
fi
if command -v juliaup &>/dev/null; then
  juliaup add $JULIA_CHANNEL
  juliaup default $JULIA_CHANNEL
fi

add_local_bin_to_path() {
  if [ "`realpath $BIN_DIR`" = "`realpath ~/.local/bin`" ]; then
    # ~/.local/bin not in $PATH?
    if [ "$SHELL" = "/bin/zsh" ]; then
      rcfile=~/.zshrc
    elif [ "$SHELL" = "/bin/bash" ]; then
      if [ -f ~/.bashrc ]; then
        rcfile=~/.bashrc
      elif [ -f ~/.bash_profile ]; then
        rcfile=~/.bash_profile
      fi
    fi
    echo "Extending the PATH environment variable in $rcfile"
    cat <<"EOF" >>$rcfile

export PATH=$PATH:~/.local/bin
EOF
  else
    echo "the larvatagger command is available in directory:"
    echo "  $BIN_DIR"
    echo "consider adding the directory to the PATH variable in your rc or profile file"
  fi
  export PATH=$PATH:$BIN_DIR
}

install_pyenv_on_ubuntu2004() {
  echo
  echo "INFO: installing pyenv and its dependencies"
  echo
  if ! [ -d ~/.pyenv ]; then
    sudo apt-get update
    sudo apt-get install --no-install-recommends curl build-essential libssl-dev zlib1g-dev libbz2-dev libreadline-dev libsqlite3-dev llvm libncurses5-dev xz-utils libxml2-dev libxmlsec1-dev libffi-dev liblzma-dev #tk-dev
    curl https://pyenv.run | bash
  fi
  export PATH=~/.pyenv/bin:$PATH
  eval "$(~/.pyenv/bin/pyenv init -)"
  cat <<"EOF" >>~/.bash_profile

command -v pyenv &>/dev/null || export PATH=~/.pyenv/bin:$PATH
eval "$(~/.pyenv/bin/pyenv init -)"
EOF
  pyenv install $PYTHON_VERSION
}

install_pyenv() {
  echo
  echo "INFO: installing pyenv"
  echo
  if ! [ -d ~/.pyenv ]; then
    curl https://pyenv.run | bash
  fi
  if ! command -v pyenv &>/dev/null; then
    export PATH=~/.pyenv/bin:$PATH
    eval "$(~/.pyenv/bin/pyenv init -)"
    cat <<"EOF" >>~/.bash_profile

command -v pyenv &>/dev/null || export PATH=~/.pyenv/bin:$PATH
eval "$(~/.pyenv/bin/pyenv init -)"
EOF
  fi
  pyenv install $PYTHON_VERSION
  pyenv shell $PYTHON_VERSION
}

if [ -n "$WITH_BACKEND" ]; then
  if ! command -v python$PYTHON_VERSION &>/dev/null; then
    if command -v pyenv &>/dev/null; then
      [ `pyenv versions | grep $PYTHON_VERSION` ] || pyenv install $PYTHON_VERSION
    elif [[ "`uname -r`" =~ "-microsoft-standard-WSL2" ]]; then
      install_pyenv_on_ubuntu2004
    elif [[ "`hostname`" =~ ".cluster.embl.de" ]]; then
      install_pyenv
    elif [ "`hostname`" = "maestro-submit" ]; then
      # we could use `module load Python/<version>` but modules are removed without notice
      install_pyenv
    else
      check_brew
      if command -v brew &>/dev/null; then
        brew install python@$PYTHON_VERSION
        if ! command -v python$PYTHON_VERSION &>/dev/null; then
          echo "Try instead $0 --with-backend --experimental"
          echo "Aborting..."
          exit 1
        fi
      else
        echo "WARNING: command $PYTHON not found"
      fi
    fi
  fi
  if ! command -v poetry &>/dev/null; then
    if command -v pipx &>/dev/null; then
      pipx install poetry
    else
      # explicit Python version for Maestro
      curl -fsSL https://install.python-poetry.org | $PYTHON -
      command -v poetry &>/dev/null || add_local_bin_to_path
    fi
  fi
fi

mkdir -p "$LARVATAGGER_PATH" && pushd $_
##

if [ -d PlanarLarvae ]; then
  echo "PlanarLarvae.jl installation found; skipping"
else
  if [ -z "$PLANARLARVAE_BRANCH" ]; then
    PLANARLARVAE_BRANCH=main
  else
    echo "Using environment variable: PLANARLARVAE_BRANCH= $PLANARLARVAE_BRANCH"
    curl -fsSL https://gitlab.pasteur.fr/nyx/planarlarvae.jl/-/archive/${PLANARLARVAE_BRANCH}/planarlarvae.jl-${PLANARLARVAE_BRANCH}.tar.gz | tar zxv
    mv planarlarvae.jl-${PLANARLARVAE_BRANCH} PlanarLarvae
  fi
fi

if [ -d LarvaTagger.jl ]; then
  echo "LarvaTagger.jl installation found; skipping"
else
  if [ -z "$LARVATAGGER_BRANCH" ]; then
    LARVATAGGER_BRANCH=main
  else
    echo "Using environment variable: LARVATAGGER_BRANCH= $LARVATAGGER_BRANCH"
  fi
  curl -fsSL https://gitlab.pasteur.fr/nyx/larvatagger.jl/-/archive/${LARVATAGGER_BRANCH}/larvatagger.jl-${LARVATAGGER_BRANCH}.tar.gz | tar zxv
  mv larvatagger.jl-${LARVATAGGER_BRANCH} LarvaTagger.jl
fi
(cd LarvaTagger.jl && $JULIA --project=. -e 'using Pkg; Pkg.instantiate()')
[ -d PlanarLarvae ] && (cd LarvaTagger.jl && $JULIA --project=. -e 'using Pkg; Pkg.develop(path="../PlanarLarvae")')

if [ -z "$WITH_BACKEND" ]; then

mkdir -p "$BIN_DIR"
cat <<EOF >"$BIN_DIR"/larvatagger
#!/usr/bin/env bash

if command -v juliaup &>/dev/null; then
  PREVIOUS_CHANNEL=\`juliaup status | grep '*' | cut -d'*' -f2 | cut -d\\  -f3\`
  juliaup default $JULIA_VERSION &>/dev/null
fi

OPEN_BROWSER=1 "$LARVATAGGER_PATH/LarvaTagger.jl/scripts/larvatagger" \$@

if command -v juliaup &>/dev/null; then
  juliaup default \$PREVIOUS_CHANNEL &>/dev/null
fi
EOF

else

[ -z "$internal_WITH_BACKEND" ] && echo "Using environment variable: WITH_BACKEND= $WITH_BACKEND"

activate() {
  # pyenv activation is necessary on WSL
  command -v pyenv &>/dev/null && [ -n "`pyenv versions | grep '  $PYTHON_VERSION'`" ] && pyenv local $PYTHON_VERSION
  poetry env use $PYTHON_VERSION 2>/dev/null
}

if [ -d TaggingBackends ]; then
  echo "TaggingBackends installation found; skipping"
else
  if [ -z "$TAGGINGBACKENDS_BRANCH" ]; then
    TAGGINGBACKENDS_BRANCH=main
  else
    echo "Using environment variable: TAGGINGBACKENDS_BRANCH= $TAGGINGBACKENDS_BRANCH"
  fi
  curl -fsSL https://gitlab.pasteur.fr/nyx/TaggingBackends/-/archive/${TAGGINGBACKENDS_BRANCH}/TaggingBackends-${TAGGINGBACKENDS_BRANCH}.tar.gz | tar zxv
  mv TaggingBackends-${TAGGINGBACKENDS_BRANCH} TaggingBackends
fi
(cd TaggingBackends && activate && PYTHON="`poetry env info`/bin/python" $JULIA --project=. -e 'using Pkg; Pkg.instantiate()')
[ -d PlanarLarvae ] && (cd TaggingBackends && $JULIA --project=. -e 'using Pkg; Pkg.develop(path="../PlanarLarvae")')
(cd TaggingBackends && activate && JULIA_PROJECT=$(pwd) poetry install)

if [ -d MaggotUBA-core ]; then
  echo "MaggotUBA-core installation found; skipping"
else
  if [ -z "$MAGGOTUBA_CORE_BRANCH" ]; then
    MAGGOTUBA_CORE_BRANCH=main
  else
    echo "Using environment variable: MAGGOTUBA_CORE_BRANCH= $MAGGOTUBA_CORE_BRANCH"
    curl -fsSL https://gitlab.pasteur.fr/nyx/maggotuba-core/-/archive/${MAGGOTUBA_CORE_BRANCH}/maggotuba-core-${MAGGOTUBA_CORE_BRANCH}.tar.gz | tar zxv
    mv maggotuba-core-${MAGGOTUBA_CORE_BRANCH} MaggotUBA-core
  fi
fi

if [ -d MaggotUBA ]; then
  echo "MaggotUBA installation found; skipping"
else
  if [ -z "$MAGGOTUBA_ADAPTER_BRANCH" ]; then
    MAGGOTUBA_ADAPTER_BRANCH=main
  elif [ -z "$internal_MAGGOTUBA_ADAPTER_BRANCH" ]; then
    echo "Using environment variable: MAGGOTUBA_ADAPTER_BRANCH= $MAGGOTUBA_ADAPTER_BRANCH"
  fi
  curl -fsSL https://gitlab.pasteur.fr/nyx/MaggotUBA-adapter/-/archive/${MAGGOTUBA_ADAPTER_BRANCH}/MaggotUBA-adapter-${MAGGOTUBA_ADAPTER_BRANCH}.tar.gz | tar zxv
  mv MaggotUBA-adapter-${MAGGOTUBA_ADAPTER_BRANCH} MaggotUBA #-adapter
  #ln -s $(realpath MaggotUBA-adapter) MaggotUBA
fi
# setting JULIA_PROJECT may not be necessary at this point
export JULIA_PROJECT=$(realpath TaggingBackends)

if [ -z "$internal_MAGGOTUBA_ADAPTER_FREE_DEPENDENCIES" ]; then
  if [ "`uname`" = "Darwin" ]; then
    # PyTorch requirements in the requirements.txt file are Cuda-based, suitable for Windows and Linux
    # while alternative libraries are used on macOS
    internal_MAGGOTUBA_ADAPTER_FREE_DEPENDENCIES=1
  else
    internal_MAGGOTUBA_ADAPTER_FREE_DEPENDENCIES=0
  fi
fi
if [ "$internal_MAGGOTUBA_ADAPTER_FREE_DEPENDENCIES" = "0" ]; then
  (cd MaggotUBA && activate && (cat requirements.txt | xargs -I % sh -c 'poetry add "%"' || true) && poetry install -v)
else
  (cd MaggotUBA && activate && poetry install -v)
fi
[ -d TaggingBackends ] && (cd MaggotUBA && activate && poetry remove taggingbackends && poetry add ../TaggingBackends)
[ -d MaggotUBA-core ] && (cd MaggotUBA && activate && poetry remove maggotuba-core && poetry add ../MaggotUBA-core)
(cd MaggotUBA && scripts/make_models.jl default) # julia version does not matter here

mkdir -p "$BIN_DIR"
cat <<EOF >"$BIN_DIR"/larvatagger
#!/usr/bin/env bash

if command -v juliaup &>/dev/null; then
  PREVIOUS_CHANNEL=\`juliaup status | grep '*' | cut -d'*' -f2 | cut -d\\  -f3\`
  juliaup default $JULIA_VERSION &>/dev/null
fi

JULIA_PROJECT="$(realpath "$LARVATAGGER_PATH/TaggingBackends")" BACKENDS_PATH="$LARVATAGGER_PATH" OPEN_BROWSER=1 "$LARVATAGGER_PATH/LarvaTagger.jl/scripts/larvatagger" \$@

if command -v juliaup &>/dev/null; then
  juliaup default \$PREVIOUS_CHANNEL &>/dev/null
fi
EOF

fi

chmod a+x "$BIN_DIR/larvatagger"

command -v larvatagger &>/dev/null || add_local_bin_to_path

##
popd

fi
fi