#!/bin/bash
# This script creates a symlink named _codeql_detected_source_root, pointing to
# the directory that most likely contains the build system for this project.


. "$AUTOBUILD_ROOT/lib/build.sh"

echo

# This function creates the symlink named _codeql_detected_source_root,
# pointing to the first argument, and exits. Using this function helps to
# ensure that (1) the symlink gets the correct name and (2) we always exit
# after creating the symlink so later stages are not at risk of reading the
# symlink as part of their analysis.
function create_symlink_and_exit() {
  ln -s "$1" _codeql_detected_source_root
  echo
  exit
}

# Returns success if the first argument is a directory that appears to contain
# files for a major build system.
function convincingly_has_build_system() {
  ( cd "$1" &&
    [[
      -x configure || \
      -f configure.in || -f configure.ac || \
      -f CMakeLists.txt || \
      -f meson.build || \
      -f wscript || \
      -f SConstruct ||
      -f config.m4
    ]]
  )
}

# Returns success if the first argument is a directory that appears to contain
# a build system for other languages, which may however compile C/C++ code as
# a native dependency
function may_have_other_build_system_with_native_code() {
  (
    cd "$1" &&
    [[
      -f setup.py ||
      -f package-lock.json
    ]] &&
    # check if directory contains C/C++ compilable code
    find . -type f -name '*.c' -or -name '*.C' -or -name '*.cc' -or -name '*.cpp' -or -name '*.cxx' \
      | head -1 | grep '.\+' &>/dev/null
  )
}

# Returns success if the first argument is a directory that appears to contain
# files for some build system, possibly a home-made or minor one.
function may_have_build_system() {
  convincingly_has_build_system "$1" || \
  ( cd "$1" &&
    [[
      ( -f Kbuild && -f Kconfig ) ||
      -f Makefile || -f makefile || -f GNUmakefile ||
      "$(echo ./*.pro)" != "./*.pro" ||
      ( -x build && -f build ) || \
      -x build.sh
    ]]
  ) || \
  may_have_other_build_system_with_native_code "$1"
}

# Holds if the first argument is a directory that does not appear to contain
# third-party code, and contains C/C++ code
function is_allowed_dir() {
  ! [ -L "$1" ] && \
    [ -d "$1" ] && \
    echo "$1" | grep -vq '/\(thirdparty\|third_party\|third-party\|vendor\|external\|3rdparty\|_vendor\)$'
}

# Takes a list of file names as argument. If that list has length 1, and that
# one argument is a directory for which `is_allowed_dir` returns success, print
# that directory and return success. Otherwise, return failure.
function single_dir() {
  if [ $# -eq 1 ] && is_allowed_dir "$1"; then
    echo "$1"
  else
    false
  fi
}

# Takes a directory name as argument traverses down its subdirectories for as
# long as each directory is _trivial_, stopping when it reaches a non-trivial
# directory and printing that. A _trivial_ directory is one containing nothing
# but a single subdirectory (and possibly dot-files).
# This function will not traverse into a directory that seems to contain
# third-party code.
function first_nontrivial_dir() {
  curdir="$1"
  while the_single_dir="$(single_dir "$curdir"/*)"; do
    curdir="$the_single_dir"
  done
  echo "$curdir"
}

# First, find the top directory, which is usually the root of the repository.

top_candidate="$(first_nontrivial_dir .)"

if may_have_build_system "$top_candidate"; then
  log "Using build system found in '$top_candidate'"
  create_symlink_and_exit "$top_candidate"
fi

# If we are certain that the top directory cannot possibly contain a build
# system, check each subdirectory to see if there is a single one with a build
# system. We try to be careful not to confuse some random directory with a
# Makefile for the main source directory, so we call
# `convincingly_has_build_system` first instead of `may_have_build_system`.
# If that does not find anything, then we relax the constraint and use
# `may_have_build_system` instead.

preferred_subdir=""
for testing_function in convincingly_has_build_system may_have_build_system; do
  for below_top_candidate in "$top_candidate"/*; do
    is_allowed_dir "$below_top_candidate" || continue;
    subdir="$(first_nontrivial_dir "$below_top_candidate")"
    if $testing_function "$subdir"; then
      if [ -n "$preferred_subdir" ]; then
        log "At least these two subdirs have build system files in them:"
        log "  $preferred_subdir"
        log "  $subdir"
        log "To avoid ambiguity, build will be attempted from '$top_candidate'"
        log "even though no build system was found there."
        create_symlink_and_exit "$top_candidate"
      else
        preferred_subdir="$subdir"
      fi
    fi
  done
  [ -n "$preferred_subdir" ] && break
done

if [ -n "$preferred_subdir" ]; then
  log "Build will be attempted from '$preferred_subdir'"
  create_symlink_and_exit "$preferred_subdir"
else
  log "Build will be attempted from '$top_candidate' even though no build system was found there."
  create_symlink_and_exit "$top_candidate"
fi
