#!/bin/bash
# This script allows to automatically build and test AspectC++ with multiple clang versions. It produces a compact
# report about AspectC++'s status in terms of supported clang versions.

### Optional arguments ###
# $1 clang_version_array (see below)
# $2 clang_dir_path_prefix (see below)
# $3 build target, i.e., make's TARGET argument
if [ $# -gt 3 ]
then
  echo "Error: Max. 3 arguments allowed."
  exit 1
fi

### Config ###

# Array of version strings. For each string, the corresponding clang version is used for testing AspectC++. Pre-built
# binaries/libraries can be downloaded from http://releases.llvm.org/download.html.
clang_version_array=${1:-"9.0.0 6.0.1 5.0.2 4.0.1 3.9.1 3.8.1 3.7.1 3.6.2 3.5.2"}

# Path to directory containing clang binary/library directories plus the prefix of all clang binary/library directory
# names.
# When concatenated with a version from the version array, it must result in a path to the root of a clang
# binary/library directory. From that path, "./bin/llvm-config" should point to llvm-config binary.
# For example, extract all downloaded pre-built binary/library files to your home and rename each extracted root
# directory to llvm-<version>. In this case, clang_dir_path_prefix should be "$HOME/llvm-"
# Note: Please provide absolute paths. Tilde (~) is not supported.
clang_dir_path_prefix=${2:-"$HOME/llvm-"}

# Number of cpus to use for building:
cpu_count_to_use=$(grep -c ^processor /proc/cpuinfo)

# AspectC++ build command without LLVMCONF argument:
build_target=${3:-"linux-debug"}
acpp_base_build_command="make SHARED=1 TARGET=$build_target -j$cpu_count_to_use FRONTEND=Clang"

### End of config ######################################################################################################
########################################################################################################################


run_command() {
  # $1 = command description
  # $2 = command to run
  # $3 = whether to continue on non-zero exit code (1) or not (0)

  echo -e "\n------------------------------\n$1 ($2) ..."
  # Run command:
  # Since we run two piped commands below, force bash to set the exit status $? to a non-zero exit code if any of the
  # commands fail:
  set -o pipefail
  # Direct command output to stdout/stderr while additionally storing stdout in the variable:
  exec 9>&1 # Duplicates &1 to 9 (9 is a mostly arbitrary chosen number)
  output=$($2 | tee >(cat - >&9))
  # Store the last exit code:
  exit_code=$?
  if [ $exit_code -ne 0 ]
  then
    echo "Error: $1 failed with exit code $exit_code."
    if [ -n "$3" ] && [ "$3" -eq 1 ] # if $3 is not empty ("-n" ^= "! -z") and equals 1
    then
      echo "... but continuing anyway."
    else
      exit $exit_code
    fi
  else
    echo "Done."
  fi
}

# If PUMA_CONFIG is not set and a default config from aspectc++ package is available, set PUMA_CONFIG to the default:
if [ -z "$PUMA_CONFIG" ] && [ -f "/etc/puma.config" ]
then
  export PUMA_CONFIG="/etc/puma.config"
  echo "Set PUMA_CONFIG to default config $PUMA_CONFIG."
fi

# Show a warning if tests are likely to fail:
if [ -z "$PUMA_CONFIG" ]
then
  echo "----------------------------------------------------------------"
  echo "WARNING: Neither /etc/puma.config exists nor PUMA_CONFIG is set."
  echo "         This may lead to failing tests due to failing includes."
  echo "         See README for more information."
  echo "----------------------------------------------------------------"
  sleep 2
fi

echo -e "Testing AspectC++ with clang versions $clang_version_array ..."

for clang_version in $clang_version_array
do
  echo -e "\n------------------------------\n| AspectC++ with Clang $clang_version |\n------------------------------"
  # In each loop, try to build and test with the current clang version. In all cases, clang_version_result_string
  # contains either the test results or a message explaining why the build failed.

  # Construct llvm-config path:
  llvm_conf_path="${clang_dir_path_prefix}$clang_version/bin/llvm-config"
  if [ ! -f "$llvm_conf_path" ]
  then
    echo "Error: Could not find Clang $clang_version llvm-config binary in $llvm_conf_path. Please check your clang directory paths and the config on top of this file. Skipping tests ..."
    clang_version_result_string="Unable to build AspectC++: $llvm_conf_path does not exist."
  else
    # clang/llvm binaries/libraries are available. Construct build, clean and test commands:
    acpp_build_command="$acpp_base_build_command LLVMCONF=$llvm_conf_path"
    acpp_clean_command="$acpp_build_command clean"
    acpp_testall_command="$acpp_build_command testall"
    # Try building AspectC++:
    run_command "Cleaning with Clang $clang_version" "$acpp_clean_command"
    run_command "Building with Clang $clang_version" "$acpp_build_command" "1" # do not exit script on non-zero exit code
    if [ $exit_code -ne 0 ]
    then
      clang_version_result_string="Unable to build AspectC++: Build command exited with non-zero exit code $exit_code."
    else
      # Build was successful: Run the tests:
      run_command "Running tests with Clang $clang_version" "$acpp_testall_command" "1"
      # Extract the test result summary line:
      clang_version_result_string=$(echo "$output" | sed -n 2p) # second line of test output only
    fi
  fi
  # Construct a string holding all clang versions' test result summaries:
  overall_test_results="$overall_test_results\n$clang_version: $clang_version_result_string"

  echo -e "\nTest results so far:$overall_test_results"
done
echo -e "\nOverall test results:$overall_test_results"
