#!/usr/bin/env bash ## Download Drupal, CiviCRM, dependencies, and useful development tools. ## Setup basic symlinks. ## ## Maybe, when drush or composer is more mature, we can eliminate this. { # https://stackoverflow.com/a/21100710 ## Determine the absolute path of the directory with the file ## absdirname function absdirname() { pushd $(dirname $0) >> /dev/null pwd popd >> /dev/null } BINDIR=$(absdirname "$0") PRJDIR=$(dirname "$BINDIR") [[ -z "$CIVIBUILD_HOME" ]] && TMPDIR="$PRJDIR/app/tmp" || TMPDIR="$CIVIBUILD_HOME/.civibuild/tmp" LOCKFILE="$TMPDIR/civi-download-tools.lock" LOCKTIMEOUT=90 COMPOSER_VERSION="2.8.4" COMPOSER_URL="https://github.com/composer/composer/releases/download/${COMPOSER_VERSION}/composer.phar" IS_QUIET= IS_FORCE= ################################################## ## Parse arguments while [[ -n "$1" ]] ; do OPTION="$1" shift case "$OPTION" in -q|--quiet) ## Don't display advisory comments ## Only display messages if we're actually making changes IS_QUIET=1 ;; -f|--force) ## (Re)-download everything IS_FORCE=1 ;; --full) echo "ERROR: Support for full-system installation (apt-get) has been removed. Please see CiviCRM Developer Guide for current options." exit 2 ;; --dir) set -e [[ ! -d "$1" ]] && mkdir "$1" pushd "$1" >> /dev/null PRJDIR=$(pwd) popd >> /dev/null BINDIR="$PRJDIR/bin" [[ -z "$CIVIBUILD_HOME" ]] && TMPDIR="$PRJDIR/app/tmp" || TMPDIR="$CIVIBUILD_HOME/.civibuild/tmp" LOCKFILE="$TMPDIR/civi-download-tools.lock" set +e shift ;; *) echo "Unrecognized option: $OPTION" echo "Usage: $0 [-q|--quiet] [-f|--force] [--dir ]" ;; esac done ############################################################################### ## usage: download_url function download_url() { #php -r "echo file_get_contents('$1');" > $2 if which wget >> /dev/null ; then wget -O "$2" "$1" elif which curl >> /dev/null ; then curl -L -o "$2" "$1" else echo "error: failed to locate curl or wget" fi } ############################################################################### ## usage: echo_comment function echo_comment() { if [[ -z "$IS_QUIET" ]]; then echo "$@" fi } ############################################################################### ## Make a symlink... gently. ## usage: make_link function make_link() { local workdir="$1" local from="$2" local to="$3" pushd "$workdir" >> /dev/null if [[ -L "$to" ]]; then local oldLink=$(readlink "$to") if [ -n "$oldLink" -a "$oldLink" != "$from" ]; then rm -f "$to" fi fi if [[ ! -e "$to" ]]; then echo_comment "[[Create symlink $to in $workdir]]" ln -s "$from" "$to" fi ## FIXME: ideally, provide a notice if the file-exists and is *not* the expected link popd >> /dev/null } ############################################################################### ## Ensure that the current user has permission to write to a given folder. ## ## This addresses the issue where somone has erroneously executed `composer` ## or `npm` or `bower` or somesuch as `root`. ## usage: check_path_ownership function check_datafile_ownership() { local tgtdir="$1" if [[ ! -e "$tgtdir" ]]; then return fi local tgtuser=$(whoami) local files=$( find "$tgtdir" ! -user $tgtuser 2>&1 ) if [[ -n "$files" ]]; then echo "WARNING: The following data-files are not owned by your user, which may lead to permission issues. You may need to delete or chown them." >&2 echo "$ find "$tgtdir" ! -user $tgtuser" echo "$files" echo "" fi } ############################################################################### ## Ensure that a command is on the PATH. If missing, then give ## advice on possible resolutions and exit. ## usage: check_command [] function check_command() { local cmd="$1" local requirement="$2" local msg="$3" [[ -z "$msg" ]] && msg="Failed to locate command \"$cmd\". Please install it (and set the PATH appropriately)." cmdpath=$(which $cmd) if [[ -z "$cmdpath" ]]; then echo "$msg" show_command "$cmd" "It is possible that you have already installed \"$cmd\" in a non-standard location. If so, please update the PATH appropriately. Possible matches were found in:" if [ "$requirement" = "required" ]; then exit 3 fi fi } ############################################################################### ## Show a list of possible locations where the command can be found ## usage: show_command [] function show_command() { local cmd="$1" local msg="$2" local is_first=1 for altdir in \ /Applications/MAMP/Library/bin \ /Applications/MAMP/bin/php/php*/bin \ /{usr,opt}{,/local}/bin \ /{usr,opt}{,/local}/*/bin \ /{usr,opt}{,/local}/lib/*/bin do if [[ -f "$altdir/$cmd" ]]; then if [[ -n "$is_first" ]]; then echo $msg is_first= fi echo " * $altdir" fi done } ############################################################################### ## Debian.org's NodeJS package uses a non-standard name for the node binary. ## If necessary, setup an alias for the standard name. function nodejs_debian_workaround() { if which nodejs &> /dev/null ; then if ! which node &> /dev/null ; then echo "[[NodeJS binary appears to be misnamed. Creating 'node' alias.]]" ln -s "$(which nodejs)" "$BINDIR/node" fi fi } ############################################################################### ## Check if a PHP extension is enabled ## usage: check_php_ext [] ## ## Note: There's not much harm in calling check_php_ext for more requirements, ## but bear in mind that this only handles requirements for buildkit CLI. ## For civicrm-core runtime, the app should have its own checks. function check_php_ext() { local ext="$1" local requirement="$2" local msg="$3" if [ -z "$msg" -a "$requirement" = "required" ]; then msg="ERROR: Failed to find required PHP extension \"$ext\"." elif [ -z "$msg" -a "$requirement" = "recommended" ]; then msg="WARNING: Failed to find recommended PHP extension \"$ext\"." fi if php -r 'exit((int)in_array("'$ext'", get_loaded_extensions()));' ; then echo "$msg" if [ "$requirement" = "required" ]; then echo "" if [ `uname` = "Darwin" ]; then echo "TIP: In OS X, it is common to install an alternative PHP bundle, such as MAMP or XAMPP, which provides more extensions by default." show_command php "TIP: You may wish to configure a PATH to choose a different version of PHP. The following versions were found automatically:" fi if [ `uname` = "Linux" ]; then echo "TIP: In some systems, the PHP version used in CLI and web are different. Extensions should be active in both." fi exit 4 fi fi } function install_composer() { if [ -z "$IS_FORCE" -a -e "$PRJDIR/bin/composer" -a "$(cat $PRJDIR/extern/composer.txt)" == "$COMPOSER_VERSION" ]; then echo_comment "[[composer ($PRJDIR/bin/composer) already exists. Skipping.]]" else if [[ -z "$COMPOSER_URL" ]]; then echo "[[Skip composer. Could not determine binary.]]" else echo "[[Download composer]]" download_url "$COMPOSER_URL" "$PRJDIR/bin/composer" chmod ugo+x "$PRJDIR/bin/composer" echo "$COMPOSER_VERSION" > "$PRJDIR/extern/composer.txt" fi fi } ################################################## ## Validation if [[ -z "$IS_QUIET" ]]; then check_command php required check_command mysql required check_command mysqldump required check_command git required check_command tar required check_command bzip2 required check_command gzip required check_command unzip required check_command zip required check_php_ext Phar required check_php_ext SimpleXML required check_php_ext SPL required check_php_ext curl required check_php_ext date required check_php_ext json required check_php_ext libxml required check_php_ext pcre required check_php_ext pdo_mysql required check_php_ext xml required fi nodejs_debian_workaround if [[ ! -d "$TMPDIR" ]]; then mkdir -p "$TMPDIR" fi ################################################## ## Only allow one concurrent process if php -d xdebug.remote_enable=off $BINDIR/pidlockfile.php "$LOCKFILE" $$ 5 ; then ## we acquired lock quickly; no need to bug user with output true else OLDPID=$(cat "$LOCKFILE") echo "[[civi-download-tools: Already locked by PID $OLDPID; waiting up $LOCKTIMEOUT seconds]]" if php $BINDIR/pidlockfile.php "$LOCKFILE" $$ $LOCKTIMEOUT ; then echo "[[civi-download-tools: Lock acquired]]" else exit 1 fi fi ################################################## ## Begin execution set -e pushd $PRJDIR >> /dev/null ## Check that data folders/files are writeable. Since this is expensive, only do it on new systems. if [ -z "$IS_QUIET" -o ! -d vendor -o ! -d node_modules ]; then [[ -n "$COMPOSER_CACHE_DIR" ]] && check_datafile_ownership "$COMPOSER_CACHE_DIR" [[ -z "$COMPOSER_CACHE_DIR" ]] && check_datafile_ownership "$HOME/.composer" check_datafile_ownership "$HOME/.cache" check_datafile_ownership "$HOME/.npm" [[ -n "$AMPHOME" ]] && check_datafile_ownership "$AMPHOME" [[ -z "$AMPHOME" ]] && check_datafile_ownership "$HOME/.amp/apache.d" fi [[ ! -d "$PRJDIR/extern" ]] && mkdir "$PRJDIR/extern" ## Cleanup previous PHAR downloads before composer-downloads-plugin takes a crack at it. [[ -f "$PRJDIR/extern/_phpunit4.txt" ]] && rm -f "$PRJDIR/extern/phpunit4/phpunit4.phar" "$PRJDIR/extern/_phpunit4.txt" [[ -f "$PRJDIR/extern/_phpunit5.txt" ]] && rm -f "$PRJDIR/extern/phpunit5/phpunit5.phar" "$PRJDIR/extern/_phpunit5.txt" [[ -f "$PRJDIR/extern/_phpunit6.txt" ]] && rm -f "$PRJDIR/extern/phpunit6/phpunit6.phar" "$PRJDIR/extern/_phpunit6.txt" [[ -f "$PRJDIR/extern/amp.txt" ]] && rm -f "$PRJDIR/bin/amp" "$PRJDIR/extern/amp.txt" [[ -f "$PRJDIR/extern/box.txt" ]] && rm -f "$PRJDIR/bin/box" "$PRJDIR/extern/box.txt" [[ -f "$PRJDIR/extern/civistrings.txt" ]] && rm -f "$PRJDIR/bin/civistrings" "$PRJDIR/extern/civistrings.txt" [[ -f "$PRJDIR/extern/civix.txt" ]] && rm -f "$PRJDIR/bin/civix" "$PRJDIR/extern/civix.txt" [[ -f "$PRJDIR/extern/cv.txt" ]] && rm -f "$PRJDIR/bin/cv" "$PRJDIR/extern/cv.txt" [[ -f "$PRJDIR/extern/drush8.txt" ]] && rm -f "$PRJDIR/extern/drush8.phar" "$PRJDIR/extern/drush8.txt" [[ -f "$PRJDIR/extern/git-scan.txt" ]] && rm -f "$PRJDIR/bin/git-scan" "$PRJDIR/extern/git-scan.txt" [[ -f "$PRJDIR/extern/wp-cli.txt" ]] && rm -f "$PRJDIR/bin/wp" "$PRJDIR/extern/wp-cli.txt" [[ -f "$PRJDIR/extern/civici.txt" ]] && rm -f "$PRJDIR/bin/civici" "$PRJDIR/extern/civici.txt" [[ -f "$PRJDIR/extern/joomla.txt" ]] && rm -f "$PRJDIR/bin/joomla" "$PRJDIR/extern/joomla.txt" [[ -f "$PRJDIR/extern/joomlatools-console.txt" ]] && rm -f "$PRJDIR/bin/joomla" "$PRJDIR/extern/joomlatools-console.txt" [[ -d "$PRJDIR/extern/joomlatools-console" ]] && rm -rf "$PRJDIR/extern/joomlatools-console" [[ -f "$PRJDIR/extern/codecept-php5.txt" ]] && rm -f "$PRJDIR/bin/_codecept-php5.phar" "$PRJDIR/extern/codecept-php5.txt" [[ -f "$PRJDIR/extern/codecept-php7.txt" ]] && rm -f "$PRJDIR/bin/_codecept-php7.phar" "$PRJDIR/extern/codecept-php7.txt" [[ -f "$PRJDIR/extern/drush-lib-backdrop.txt" ]] && rm -rf "$PRJDIR/extern/drush-lib/backdrop" "$PRJDIR/extern/drush-lib-backdrop.txt" [[ -f "$PRJDIR/bin/_codecept-php5.phar" ]] && rm -f "$PRJDIR/bin/_codecept-php5.phar" [[ -f "$PRJDIR/bin/_codecept-php7.phar" ]] && rm -f "$PRJDIR/bin/_codecept-php7.phar" [ -f "$PRJDIR/bin/civi-test-job" -a ! -e "$PRJDIR/src/pogo/civi-test-job.php" ] && rm -f "$PRJDIR/bin/civi-test-job" [ -f "$PRJDIR/bin/civi-test-pr" -a ! -e "$PRJDIR/src/pogo/civi-test-pr.php" ] && rm -f "$PRJDIR/bin/civi-test-pr" [[ -L "$PRJDIR/bin/phpunit4" ]] && rm -f "$PRJDIR/bin/phpunit4" [[ -L "$PRJDIR/bin/phpunit5" ]] && rm -f "$PRJDIR/bin/phpunit5" [[ -L "$PRJDIR/bin/phpunit6" ]] && rm -f "$PRJDIR/bin/phpunit6" [[ -f "$PRJDIR/extern/phpunit4/phpunit4.phar" ]] && rm -f "$PRJDIR/extern/phpunit4/phpunit4.phar" [[ -f "$PRJDIR/extern/phpunit5/phpunit5.phar" ]] && rm -f "$PRJDIR/extern/phpunit5/phpunit5.phar" [[ -f "$PRJDIR/extern/phpunit6/phpunit6.phar" ]] && rm -f "$PRJDIR/extern/phpunit6/phpunit6.phar" [[ -f "$PRJDIR/extern/phpunit-xml-cleanup.php" ]] && rm -f "$PRJDIR/extern/phpunit-xml-cleanup.php" [[ -f "$PRJDIR/extern/hub.txt" ]] && rm -f "$PRJDIR/bin/hub" "$PRJDIR/extern/hub.txt" [[ -d "$PRJDIR/extern/hub" ]] && rm -rf "$PRJDIR/extern/hub" ## Cleanup misnamed files from past [[ -f "$PRJDIR/extern/phpunit4/phpunit6.phar" ]] && rm -f "$PRJDIR/extern/phpunit4/phpunit6.phar" "$PRJDIR/extern/phpunit4/phpunit6.phar" [[ -f "$PRJDIR/extern/phpunit5/phpunit6.phar" ]] && rm -f "$PRJDIR/extern/phpunit5/phpunit6.phar" "$PRJDIR/extern/phpunit5/phpunit6.phar" ## Download "composer" install_composer ## Download dependencies (via composer) COMPOSER_MD5=$(cat composer.json composer.lock phars.json | php -r 'echo md5(file_get_contents("php://stdin"));') touch "$TMPDIR/composer-data.md5" if [ -z "$IS_FORCE" -a "$(cat $TMPDIR/composer-data.md5)" == "$COMPOSER_MD5" ]; then echo_comment "[[composer dependencies already installed. Skipping.]]" else "$PRJDIR/bin/composer" install cat composer.json composer.lock phars.json | php -r 'echo md5(file_get_contents("php://stdin"));' > "$TMPDIR/composer-data.md5" fi ## Download dependencies (via npm) if which npm > /dev/null ; then PACKAGE_MD5=$(cat package.json | php -r 'echo md5(file_get_contents("php://stdin"));') touch "$TMPDIR/package-data.md5" if [ -z "$IS_FORCE" -a "$(cat $TMPDIR/package-data.md5)" == "$PACKAGE_MD5" -a -d "$PRJDIR/node_modules" ]; then echo_comment "[[npm dependencies already installed. Skipping.]]" else npm install cat package.json | php -r 'echo md5(file_get_contents("php://stdin"));' > "$TMPDIR/package-data.md5" fi for f in node_modules/bower/bin/bower node_modules/karma/bin/karma node_modules/jshint/bin/jshint node_modules/karma-phantomjs-launcher/node_modules/phantomjs/bin/phantomjs node_modules/protractor/bin/protractor node_modules/protractor/node_modules/webdriver-manager/bin/webdriver-manager node_modules/grunt-cli/bin/grunt ; do pushd "$PRJDIR/bin" >> /dev/null toolname=$(basename $f) if [ -f "../$f" -a ! -L "$toolname" ]; then ln -s ../$f $toolname fi popd >> /dev/null done fi ## Cleanup old civix tarballs [[ -d "$PRJDIR/extern/civix" ]] && rm -rf "$PRJDIR/extern/civix" ## Cleanup old phpunit files for OLDFILE in "$PRJDIR/bin/phpunit4" "$PRJDIR/bin/phpunit5" "$PRJDIR/extern/phpunit4.txt" "$PRJDIR/extern/phpunit5.txt"; do if [ -f "$OLDFILE" -a ! -L "$OLDFILE" ]; then echo_comment "[[Removing old file $OLDFILE]]" rm -f "$OLDFILE" fi done if [ ! -f "$PRJDIR/bin/drush8" -o "$PRJDIR/src/drush/drush8.tmpl" -nt "$PRJDIR/bin/drush8" ]; then ## drush8 was previously downloaded directly. This "cp" ensures we overwrite without git conflicts. echo "[[drush8 ($PRJDIR/bin/drush8): Generate wrapper]]" cp "$PRJDIR/src/drush/drush8.tmpl" "$PRJDIR/bin/drush8" fi ## Setup phpunit aliases for CLI usage make_link "$PRJDIR/bin" "drush8" "drush" make_link "$PRJDIR/bin" "../extern/phpunit7/phpunit7.phar" "phpunit7" make_link "$PRJDIR/bin" "../extern/phpunit8/phpunit8.phar" "phpunit8" make_link "$PRJDIR/bin" "../extern/phpunit9/phpunit9.phar" "phpunit9" make_link "$PRJDIR/bin" "../extern/phpunit10/phpunit10.phar" "phpunit10" popd >> /dev/null set +e ################################################## ## Recommendations ## ## Note: Non-fatal recommendations come at the end so that they appear as ## the last output (which is most likely to be read). if [[ -z "$IS_QUIET" ]]; then check_php_ext bcmath recommended check_php_ext gd recommended check_php_ext gettext recommended check_php_ext hash recommended check_php_ext intl recommended check_php_ext mbstring recommended check_php_ext mysqli recommended ## >= 4.7.12 check_php_ext openssl recommended check_php_ext session recommended check_php_ext zip recommended check_command node recommended "WARNING: Failed to locate command \"node\". NodeJS (http://nodejs.org/) is required for development of CiviCRM v4.6+." check_command npm recommended "WARNING: Failed to locate command \"npm\". NodeJS (http://nodejs.org/) is required for development of CiviCRM v4.6+." fi ################################################## ## Cleanup rm -f "$LOCKFILE" exit }