#!/usr/bin/env bash

# bin/compile <build-dir> <cache-dir> <env-dir>

# fail hard
set -o pipefail
# fail harder
set -eu
# move hidden files too, just in case
shopt -s dotglob

STACK=${STACK:-cedar-14} # Anvil has none
build_dir=$1
cache_dir=$2/php
mkdir -p "$cache_dir"
env_dir=${3:-} # Anvil has none
bp_dir=$(cd $(dirname $0); cd ..; pwd)

# convenience functions
source $bp_dir/bin/util/common.sh

# for extensions that need special treatment
source $bp_dir/bin/util/newrelic.sh
source $bp_dir/bin/util/blackfire.sh

# if this is set it prevents Git clones (e.g. for Composer installs from source) during the build in some circumstances, and it is set in SSH Git deploys to Heroku
unset GIT_DIR

cd $build_dir

export_env_dir "$env_dir" '^COMPOSER$'
if [[ -n ${COMPOSER:-} ]]; then
    status "Using '$COMPOSER' (from "'$COMPOSER env var) for installation.'
else
    export COMPOSER="composer.json"
fi
export COMPOSER_LOCK=$(basename "$COMPOSER" ".json")".lock" # replace .json with .lock if it exists, append .lock otherwise

# a bunch of sanity checks first
if [[ -s "$COMPOSER" ]]; then
    cat "$COMPOSER" | python -mjson.tool &> /dev/null || error "Could not parse '$COMPOSER'; make sure it's valid!"
    if [[ ! -f "$COMPOSER_LOCK" ]]; then
        cat "$COMPOSER" | python -c 'import sys, json; sys.exit(bool(json.load(sys.stdin).get("require", {})))' 2> /dev/null || error "Your '$COMPOSER' lists dependencies inside 'require',
but no '$COMPOSER_LOCK' was found. Please run 'composer update' to
re-generate '$COMPOSER_LOCK' if necessary, and commit it into your
repository. For more information, please refer to the docs at
https://devcenter.heroku.com/articles/php-support#activation"
    else
        cat "$COMPOSER_LOCK" | python -mjson.tool &> /dev/null || error "Could not parse '$COMPOSER_LOCK'; make sure it's valid!"
        cat "$COMPOSER_LOCK" | python -c 'import sys, json; l = json.load(sys.stdin); sys.exit(not(l["minimum-stability"] == "stable"));' 2> /dev/null || warning "Your '$COMPOSER' contains a non-'stable' setting
for 'minimum-stability'. This may cause the installation of
unstable versions of runtimes and extensions during this deploy.
It is recommended that you always use stability flags instead,
even if you have 'prefer-stable' enabled. For more information,
see https://getcomposer.org/doc/01-basic-usage.md#stability"
    fi
else
    if [[ ! -f "$COMPOSER" ]]; then
        warning "No '$COMPOSER' found.
Using 'index.php' to declare app type as PHP is considered legacy
functionality and may lead to unexpected behavior."
    else
        notice "Your '$COMPOSER' is completely empty.
Please change its contents to at least '{}' so it is valid JSON."
    fi
    echo "{}" > $COMPOSER
fi

# PHP expects to be installed in /app/.heroku/php because of compiled paths, let's set that up!
mkdir -p /app/.heroku
# all system packages live in there
mkdir -p $build_dir/.heroku/php
# set up Composer
export COMPOSER_HOME=$cache_dir/.composer
mkdir -p $COMPOSER_HOME

# if the build dir is not "/app", we symlink in the .heroku/php subdir (and only that, to avoid problems with other buildpacks) so that PHP correctly finds its INI files etc
[[ $build_dir == '/app' ]] || ln -s $build_dir/.heroku/php /app/.heroku/php

status "Bootstrapping..."

s3_url="https://lang-php.s3.amazonaws.com/dist-${STACK}-stable/"
# prepend the default repo to the list configured by the user
# list of repositories to use is in ascening order of precedence
export_env_dir "$env_dir" '^HEROKU_PHP_PLATFORM_REPOSITORIES$'
HEROKU_PHP_PLATFORM_REPOSITORIES="${s3_url} ${HEROKU_PHP_PLATFORM_REPOSITORIES:-}"
if [[ "${HEROKU_PHP_PLATFORM_REPOSITORIES}" == *" - "* ]]; then
	# a single "-" in the user supplied string removes everything to the left of it; can be used to delete the default repo
	notice_inline "Default platform repository disabled."
	HEROKU_PHP_PLATFORM_REPOSITORIES=${HEROKU_PHP_PLATFORM_REPOSITORIES#*" - "}
	s3_url=$(echo "$HEROKU_PHP_PLATFORM_REPOSITORIES" | cut -f1 -d" " | sed 's/[^/]*$//')
	notice_inline "Bootstrapping using ${s3_url}..."
fi


# minimal PHP needed for installs, and make "composer" invocations use that for now
mkdir -p $build_dir/.heroku/php-min
ln -s $build_dir/.heroku/php-min /app/.heroku/php-min

curl_retry_on_18 --fail --silent --location -o $build_dir/.heroku/php-min.tar.gz "${s3_url}php-min-7.0.17.tar.gz" || error "Failed to download minimal PHP for bootstrapping.
Please try again, or contact support if this problem persists."
tar xzf $build_dir/.heroku/php-min.tar.gz -C $build_dir/.heroku/php-min
rm $build_dir/.heroku/php-min.tar.gz

curl_retry_on_18 --fail --silent --location -o $build_dir/.heroku/composer.tar.gz "${s3_url}composer-1.4.1.tar.gz" || error "Failed to download Composer. Please try again, or contact support if this problem persists."
tar xzf $build_dir/.heroku/composer.tar.gz -C $build_dir/.heroku/php
rm $build_dir/.heroku/composer.tar.gz

# this alias is just for now while we install platform packages
composer() {
    /app/.heroku/php-min/bin/php /app/.heroku/php/bin/composer "$@"
}
export -f composer

composer_vendordir=$(composer config vendor-dir)
composer_bindir=$(composer config bin-dir)

# packages that get installed will add to this file, it's both for us and for buildpacks that follow
# composer bin-dir goes last to avoid any conflicts
echo "export PATH=/app/.heroku/php/bin:\$PATH:/app/$composer_bindir" > $bp_dir/export
# make sure Composer and binaries for it are on the path at runtime
# composer bin-dir goes last to avoid any conflicts
mkdir -p $build_dir/.profile.d
echo "export PATH=\$HOME/.heroku/php/bin:\$PATH:\$HOME/$composer_bindir" > $build_dir/.profile.d/100-composer.sh

status "Installing platform packages..."

# extract requirements from composer.lock
/app/.heroku/php-min/bin/php $bp_dir/bin/util/platform.php "$bp_dir/support/installer/" $HEROKU_PHP_PLATFORM_REPOSITORIES 2>&1 >$build_dir/.heroku/php/composer.json | indent || error "Couldn't load '$COMPOSER_LOCK'; it must be a valid lock
file generated by Composer and be in a consistent state.
Check above for any parse errors and address them if necessary.
Run 'composer update', add/commit the change, then push again."

# reset COMPOSER for the platform install step
COMPOSER_bak="$COMPOSER"
export COMPOSER=composer.json

# pass export_file_path and profile_dir_path to composer install; they will be used by the installer plugin
# they are also used in later install attempts for add-on extensions (blackfire, newrelic, ...)
export export_file_path=$bp_dir/export
export profile_dir_path=$build_dir/.profile.d
if composer install -d "$build_dir/.heroku/php" ${HEROKU_PHP_INSTALL_DEV-"--no-dev"} 2>&1 | tee $build_dir/.heroku/php/install.log | grep --line-buffered -E '^  - (Instal|Enab)ling heroku-sys/' | sed -u -E -e 's/^  - (Instal|Enab)ling /- /' -e 's/heroku-sys\///g' | indent; then
    :
else
    error "Failed to install system packages.

Your platform requirements (for runtimes and extensions) could
not be resolved to an installable set of dependencies, or a
repository was unreachable.

Full error information from installation attempt:

$(cat $build_dir/.heroku/php/install.log | sed -e 's/heroku-sys\///g' -e 's/^Loading composer repositories with package information/Loading repositories with available runtimes and extensions/' -e 's/^Installing dependencies.*//' -e '/^Potential causes:/,$d' -e 's/^/> /')

Please verify that all requirements for runtime versions in
'$COMPOSER_LOCK' are compatible with the list below, and ensure
all required extensions are available for the desired runtimes.

For reference, the following runtimes are currently available:

PHP:  $(composer show -d "$build_dir/.heroku/php" --available heroku-sys/php 2>&1 | sed -n 's/^versions : //p' | fold -s -w 58 || true)
HHVM: $(composer show -d "$build_dir/.heroku/php" --available heroku-sys/hhvm 2>&1 | sed -n 's/^versions : //p' | fold -s -w 58 || true)

For a list of supported runtimes & extensions on Heroku, please
refer to: https://devcenter.heroku.com/articles/php-support"
fi

if composer show -d "$build_dir/.heroku/php" --installed --quiet heroku-sys/php 2>/dev/null; then
    engine="php"
    engine_r="php -r"
elif composer show -d "$build_dir/.heroku/php" --installed --quiet heroku-sys/hhvm 2>/dev/null; then
    engine="hhvm"
    engine_r="hhvm --php -r"
fi

# done with platform installs; restore COMPOSER from previous value
export COMPOSER="$COMPOSER_bak"
unset COMPOSER_bak

composer validate --no-check-publish --no-check-all --quiet "$COMPOSER" 2>/dev/null || warning "Your '$COMPOSER_LOCK' is not up to date with the latest
changes in '$COMPOSER'. To ensure you are not getting stale
dependencies, run 'composer update' on your machine and commit
any changes to Git before pushing again."

# clean up
rm -rf /app/.heroku/php-min $build_dir/.heroku/php-min
unset -f composer

# earlier we wrote at least one $PATH entry that we'll need now, and installed packages will likely have added to it too
source $bp_dir/export

composer() {
    $engine $(which composer) "$@"
}
export -f composer

status "Installing dependencies..."

# check if we should use a composer.phar version bundled with the project
if [[ -f "composer.phar" ]]; then
    [[ -x "composer.phar" ]] || error "File '/composer.phar' isn't executable; please 'chmod +x'!"
    $engine_r 'new Phar("composer.phar");' &> /dev/null || error "File '/composer.phar' is not a valid PHAR archive!"
    composer() {
        $engine composer.phar "$@"
    }
    export -f composer
    composer --version 2>&1 | grep "^Composer version" > /dev/null || error "File '/composer.phar' is not a Composer executable!"
    notice_inline "Using '/composer.phar' to install dependencies."
fi
# echo composer version for info purposes
# tail to get rid of outdated version warnings (Composer sends those to STDOUT instead of STDERR)
composer --version 2> /dev/null | tail -n 1 | indent

# throw a notice if people have added their vendor dir to Git; that's bad practice and makes everything slow and cluttered
if [[ -f "$composer_vendordir/autoload.php" && -d "$composer_vendordir/composer" ]]; then
    # we should not do this check separately; there is no reliable way of telling whether or not it really is the real Composer bin dir or if it comes from somewhere else
    composer_warn_bindir=""
    if [[ ! "$composer_bindir/" == "$composer_vendordir"/* && -d "$composer_bindir" ]]; then
        composer_warn_bindir="
Your Composer bin dir is configured to reside outside of vendor
dir, so please repeat the two steps above for '$composer_bindir/'."
    fi
    warning "Your Composer vendor dir is part of your Git repository.
This directory should not be under version control; only your
'$COMPOSER' and '$COMPOSER_LOCK' files should be added, which
will let Composer handle installation of dependencies on deploy.
To suppress this notice, first remove the folder from your index
by running 'git rm -r --cached $composer_vendordir/'.
Next, edit your project's '.gitignore' file and add the folder
'/$composer_vendordir/' to the list.$composer_warn_bindir
For more info, refer to the Composer FAQ: http://bit.ly/1rlCSZU"
fi

# handle custom oauth keys
export_env_dir "$env_dir" '^COMPOSER_GITHUB_OAUTH_TOKEN$'
COMPOSER_GITHUB_OAUTH_TOKEN=${COMPOSER_GITHUB_OAUTH_TOKEN:-}
if [[ -n "$COMPOSER_GITHUB_OAUTH_TOKEN" ]]; then
    if curl --fail --silent -H "Authorization: token $COMPOSER_GITHUB_OAUTH_TOKEN" https://api.github.com/rate_limit > /dev/null; then
        composer config -g github-oauth.github.com "$COMPOSER_GITHUB_OAUTH_TOKEN" &> /dev/null # redirect outdated version warnings (Composer sends those to STDOUT instead of STDERR)
        notice_inline 'Using $COMPOSER_GITHUB_OAUTH_TOKEN for GitHub OAuth.'
    else
        error 'Invalid $COMPOSER_GITHUB_OAUTH_TOKEN for GitHub OAuth!'
    fi
else
    # don't forget to remove any stored key if it's gone from the env
    composer config -g --unset github-oauth.github.com &> /dev/null # redirect outdated version warnings (Composer sends those to STDOUT instead of STDERR)
fi
# no need for the token to stay around in the env
unset COMPOSER_GITHUB_OAUTH_TOKEN

# install dependencies unless composer.json is completely empty (in which case it'd talk to packagist.org which may be slow and is unnecessary)
export_env_dir "$env_dir" '^[A-Z_][A-Z0-9_]*$' '^(HOME|PATH|GIT_DIR|CPATH|CPPATH|LD_PRELOAD|LIBRARY_PATH|LD_LIBRARY_PATH|STACK|REQUEST_ID|IFS|HEROKU_PHP_INSTALL_DEV)$'
cat "$COMPOSER" | python -c 'import sys,json; sys.exit(not json.load(sys.stdin));' && composer install ${HEROKU_PHP_INSTALL_DEV-"--no-dev"} --prefer-dist --optimize-autoloader --no-interaction 2>&1 | indent

composer show --installed heroku/heroku-buildpack-php &> /dev/null && error "Your '$COMPOSER' requires 'heroku/heroku-buildpack-php'.
This package may only be used as a dependency in 'require-dev'!"

if cat "$COMPOSER" | python -c 'import sys,json; sys.exit("compile" not in json.load(sys.stdin).get("scripts", {}));'; then
    status "Running 'composer compile'..."
    composer run-script ${HEROKU_PHP_INSTALL_DEV-"--no-dev"} --no-interaction compile 2>&1 | indent
fi

status "Preparing runtime environment..."

# install this buildpack like a composer package
# it will contain the apache/nginx/php configs and the boot script
# TODO: warn if require-dev has the package using a different branch
shopt -u dotglob # we don't want .git, .gitignore et al
# figure out the package dir name to write to and copy to it
hbpdir="$composer_vendordir/$(cat $bp_dir/composer.json | python -c 'import sys, json; print(json.load(sys.stdin)["name"])')"
mkdir -p "$build_dir/$hbpdir"
cp -r "$bp_dir"/* "$build_dir/$hbpdir/"
# make bin dir, just in case
mkdir -p "$build_dir/$composer_bindir"
# figure out shortest relative path from vendor/heroku/heroku-buildpack-php to vendor/bin (or whatever the bin dir is)
relbin=$(python -c "import os.path; print(os.path.relpath('$hbpdir', '$composer_bindir'))")
# collect bin names from composer.json
relbins=$(cat $bp_dir/composer.json | python -c 'from __future__ import print_function; import sys, json; { print(sys.argv[1]+"/"+bin) for bin in json.load(sys.stdin)["bin"] }' $relbin)
# link to bins
cd $build_dir/$composer_bindir
ln -fs $relbins .
cd $build_dir

if [[ ! -f "Procfile" ]]; then
    echo "web: heroku-$engine-apache2" > Procfile
    notice_inline "No Procfile, using 'web: heroku-$engine-apache2'."
fi

# reset COMPOSER for the platform install step
COMPOSER_bak="$COMPOSER"
export COMPOSER=composer.json

status "Checking for additional extensions to install..."
# special treatment for Blackfire; we enable it if we detect a server id and a server token for it
install_blackfire_ext
# special treatment for New Relic; we enable it if we detect a license key for it
install_newrelic_ext
install_newrelic_userini

# done with platform installs; restore COMPOSER from previous value
export COMPOSER="$COMPOSER_bak"
unset COMPOSER_bak
