#!/bin/bash

# Find all the directories we might need (based on
# heroku-buildpack-nodejs code).
BUILD_DIR=${1:-}
CACHE_DIR=${2:-}
ENV_DIR=${3:-}
BP_DIR=$(cd $(dirname ${0:-}); cd ..; pwd)

# Export DATABASE_URL at build time, mostly because Diesel is the best way to
# do SQL in Rust right now, and Diesel will use it to generate code for the
# database schema.
if [ -e "$ENV_DIR/DATABASE_URL" ]; then
  export DATABASE_URL="$(cat $ENV_DIR/DATABASE_URL)";
fi

# Set defaults for our configuration variables.  Stable Rust is sufficiently
# stable at this point that I think we can just default it.
VERSION=stable

# Set this to "1" in `RustConfig` to just install a Rust toolchain and not
# build `Cargo.toml`.  This is useful if you have a project written in Ruby
# or Node (for example) that needs to build extension modules using Rust.
RUST_SKIP_BUILD=0
# If your Rust code is not at the root directory of the repository, specify a
# `BUILD_PATH` to the correct directory in the `RustConfig`
BUILD_PATH=""
# Set this to "1" in `RustConfig` to install diesel at build time and copy it
# into the target directory, next to your app binary. This makes it easy to
# run migrations by adding a release step to your Procfile:
# `release: ./target/release/diesel migration run`
RUST_INSTALL_DIESEL=0
# These flags are passed to `cargo install diesel`, e.g. '--no-default-features --features postgres'
DIESEL_FLAGS=""
# Default build flags to pass to `cargo build`.
RUST_CARGO_BUILD_FLAGS="--release"

# Load our toolchain configuration, if any was specified.
if [ -f "$BUILD_DIR/rust-toolchain" ]; then
    VERSION="$(cat "$BUILD_DIR/rust-toolchain")"
fi

# Load our configuration variables, if any were specified.
if [ -f "$BUILD_DIR/RustConfig" ]; then
    . "$BUILD_DIR/RustConfig"
fi

# Standard paranoia.
set -eu

# Check our configuration options.
if [ -z ${VERSION+x} ]; then
  >&2 echo "failed: must set VERSION with rust-toolchain or RustConfig to indicate the Rust version."
  exit 1
fi

# Notify users running old, unstable versions of Rust about how to deploy
# successfully.
if [ $RUST_SKIP_BUILD -ne 1  ] && ( [ ! -z ${CARGO_URL+x} ] || [ ! -f "$BUILD_DIR/$BUILD_PATH/Cargo.toml" ] ); then
    >&2 cat <<EOF
To deploy a modern Rust app, make sure you have a Cargo.toml file, and that
you do not define CARGO_URL or CARGO_VERSION in RustConfig.  If you're
using an older version of Rust, and you need to re-deploy an existing
application, try setting your buildpack to:

  https://github.com/emk/heroku-buildpack-rust.git#old-rust
EOF
    exit 1
fi

# Handle people who still aren't using Rustup.
if [ ! -z ${URL+x} ]; then
    >&2 cat <<EOF
heroku-pack-rust has switched to using rustup.  You no longer need a
URL field in RustConfig. Instead, you should just specify a
rustup-compatible version (such as `1.11`, `stable` or `nighty`) in VERSION.
EOF
    exit 1
fi

# Record our Rust build environment configuration in an export file, in
# case another buildpack needs it to build Ruby gems that use Rust or
# something like that.
cat <<EOF > $BP_DIR/export
# Our rustup installation.
export RUSTUP_HOME="$CACHE_DIR/multirust"

# Our cargo installation.  We implicitly trust Rustup and Cargo
# to do the right thing when new versions are released.
export CARGO_HOME="$CACHE_DIR/cargo"

# Include binaries installed by cargo and rustup in our path.
PATH="\$CARGO_HOME/bin:\$PATH"
EOF

# Read our build environment back in and evaluate it so that we can use it.
. $BP_DIR/export

# Switch to our cache directory.
mkdir -p "$CACHE_DIR"
cd "$CACHE_DIR"

# Make sure we have an appropriate Rust toolchain installed.
if [ -d "$CARGO_HOME" ]; then
    echo "-----> Checking for new releases of Rust $VERSION channel"
    # It's possible that $VERSION has changed, or the `stable` channel has updated.
    rustup self update
    rustup update "$VERSION"
    rustup default "$VERSION"
else
    echo "-----> Downloading rustup"
    curl https://sh.rustup.rs -sSf > rustup.sh
    chmod u+x rustup.sh
    echo "-----> Using rustup to install Rust channel $VERSION"
    ./rustup.sh -y --default-toolchain "$VERSION"
    rm rustup.sh
fi
if [ ! -x "$CARGO_HOME/bin/rustc" ]; then
  echo "failed: Cannot find Rust binaries at $CARGO_HOME"
  exit 1
fi

# This is where we will cache our Rust output.  Note the suggestions at
# https://github.com/alexcrichton/cargo/commit/014765f788ca1c2476936835ca32cc2746f99b42
# which describe how this needs to be named.
export CARGO_TARGET_DIR="$CACHE_DIR/target"

if [ $RUST_SKIP_BUILD -ne 1 ]; then
  # Build our project (into CARGO_TARGET_DIR so we have caching) and copy it
  # back to the source tree.  In theory, we could probably just copy the
  # binary or do something clever with `cargo install`, but we haven't
  # figured that out yet.
  #
  # To debug git issues:
  #export RUST_LOG="cargo::sources::git=debug"
  # To debug compiler and linking issues, add `--verbose`.
  echo "-----> Building application using Cargo"
  cd "$BUILD_DIR/$BUILD_PATH"
  rm -rf target/
  cargo build $RUST_CARGO_BUILD_FLAGS
  mkdir -p target/release
  find "$CARGO_TARGET_DIR/release" -maxdepth 1 -type f -executable -exec cp -a -t target/release {} \;
else
  echo "-----> Skipping Cargo build"
fi

# Install diesel so we can use it for migrations
if [ $RUST_INSTALL_DIESEL -eq 1 ]; then
  echo "-----> Installing diesel"
  cargo install diesel_cli $DIESEL_FLAGS || echo "already installed"
  cp $(which diesel) target/release/
fi
