Codebase list frei0r / master .github / scripts / detect-new-plugins.sh
master

Tree @master (Download .tar.gz)

detect-new-plugins.sh @masterraw · history · blame

#!/usr/bin/env bash

set -euo pipefail

base_sha=${1:?base sha required}
head_sha=${2:?head sha required}

mode=full
reason="changed files require the full suite"
declare -a plugin_dirs=()
declare -a targets=()
declare -a plugin_paths=()

fatal() {
  printf 'error: %s\n' "$1" >&2
  exit 1
}

is_plugin_tree() {
  case "$1" in
    src/filter/*|src/mixer2/*|src/mixer3/*) return 0 ;;
    *) return 1 ;;
  esac
}

plugin_dir_from_path() {
  local path=$1
  IFS=/ read -r top category name _ <<< "$path"
  if [[ "$top" == "src" && -n "${category:-}" && -n "${name:-}" ]]; then
    printf '%s/%s/%s\n' "$top" "$category" "$name"
    return 0
  fi
  return 1
}

contains_dir() {
  local needle=$1
  shift || true
  local item
  for item in "$@"; do
    if [[ "$item" == "$needle" ]]; then
      return 0
    fi
  done
  return 1
}

dir_exists_in_commit() {
  local commit=$1
  local path=$2
  git cat-file -e "${commit}:${path}" 2>/dev/null
}

parent_cmake_registers_plugin() {
  local commit=$1
  local plugin_dir=$2
  local category=${plugin_dir#src/}
  category=${category%%/*}
  local plugin_name=${plugin_dir##*/}
  local parent_cmake="src/${category}/CMakeLists.txt"

  local parent_text
  parent_text=$(git show "${commit}:${parent_cmake}" 2>/dev/null) || {
    return 1
  }

  printf '%s\n' "$parent_text" | grep -Eq "^[[:space:]]*add_subdirectory[[:space:]]*\\([[:space:]]*${plugin_name}[[:space:]]*\\)"
}

extract_target_name() {
  local commit=$1
  local plugin_dir=$2
  local plugin_name=${plugin_dir##*/}
  local cmake_file="${plugin_dir}/CMakeLists.txt"

  local cmake_text
  cmake_text=$(git show "${commit}:${cmake_file}" 2>/dev/null) || {
    return 1
  }

  local target
  target=$(printf '%s\n' "$cmake_text" | sed -nE 's/^[[:space:]]*set[[:space:]]*\([[:space:]]*TARGET[[:space:]]+([^[:space:])]+).*/\1/p' | head -n1)
  if [[ -n "$target" ]]; then
    printf '%s\n' "$target"
    return 0
  fi

  target=$(printf '%s\n' "$cmake_text" | sed -nE 's/^[[:space:]]*add_library[[:space:]]*\(([[:alnum:]_.+-]+)[[:space:]]+MODULE.*/\1/p' | head -n1)
  if [[ -n "$target" ]]; then
    printf '%s\n' "$target"
    return 0
  fi

  printf '%s\n' "$plugin_name"
}

while IFS=$'\t' read -r status path new_path; do
  [[ -n "${status:-}" ]] || continue

  if [[ "$status" == A* ]] && is_plugin_tree "$path"; then
    plugin_dir=$(plugin_dir_from_path "$path") || true
    if [[ -n "${plugin_dir:-}" ]] && dir_exists_in_commit "$base_sha" "$plugin_dir"; then
      reason="PR adds files inside existing plugin directory: $plugin_dir"
      plugin_dirs=()
      break
    fi

    if [[ -n "${plugin_dir:-}" ]] && ! contains_dir "$plugin_dir" "${plugin_dirs[@]}"; then
      plugin_dirs+=("$plugin_dir")
    fi
    continue
  fi

  case "$path" in
    src/filter/CMakeLists.txt|src/mixer2/CMakeLists.txt|src/mixer3/CMakeLists.txt)
      continue
      ;;
    *)
      reason="PR touches non-new-plugin files: $path"
      plugin_dirs=()
      break
      ;;
  esac
done < <(git diff --name-status --find-renames "$base_sha" "$head_sha")

if [[ ${#plugin_dirs[@]} -gt 0 ]]; then
  mode=targeted
  reason="PR only adds new plugin directories"

  for plugin_dir in "${plugin_dirs[@]}"; do
    if ! parent_cmake_registers_plugin "$head_sha" "$plugin_dir"; then
      fatal "parent CMakeLists.txt does not register ${plugin_dir}. Add add_subdirectory(${plugin_dir##*/}) to the category CMakeLists.txt."
    fi

    target=$(extract_target_name "$head_sha" "$plugin_dir") || {
      mode=full
      reason="could not resolve target for $plugin_dir"
      targets=()
      plugin_paths=()
      break
    }

    plugin_paths+=("build/${plugin_dir}/${target}.so")
    targets+=("$target")
  done
fi

printf 'mode=%s\n' "$mode"
printf 'reason=%s\n' "$reason"

if [[ "$mode" == "targeted" ]]; then
  {
    printf 'targets<<EOF\n'
    printf '%s\n' "${targets[*]}"
    printf 'EOF\n'
    printf 'plugin_paths<<EOF\n'
    printf '%s\n' "${plugin_paths[*]}"
    printf 'EOF\n'
    printf 'plugin_dirs<<EOF\n'
    printf '%s\n' "${plugin_dirs[*]}"
    printf 'EOF\n'
  }
else
  {
    printf 'targets=\n'
    printf 'plugin_paths=\n'
    printf 'plugin_dirs=\n'
  }
fi