Luigit
repositories / bugabinga.net

bugabinga.net

personal infrastructure for bugabinga!

owned by admin

scripts/local/go.sh

Raw
#!/usr/bin/env bash
set -euo pipefail

export GOTOOLCHAIN=local

repo_root="${REPO_ROOT:-$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)}"
repo_root="$(realpath "${repo_root}")"
cd "${repo_root}"

usage() {
  echo "usage: go.sh check [read|write] [PATH...] | race [PATH...] | staticcheck [PATH...] | vuln [PATH...]" >&2
  exit 2
}

path_is_excluded() {
  local path="$1" relative
  path="$(realpath -m -- "${path}")"
  relative="${path#"${repo_root}"/}"
  [[ "${path}" != "${repo_root}" && "${relative}" == "${path}" ]] && return 1
  [[ "/${relative}/" =~ /(\.git|\.terraform|\.go-cache|\.go-modcache|target|generated|vendor)/ ]] && return 0
  git -C "${repo_root}" check-ignore -q --no-index -- "${relative}" 2>/dev/null
}

add_module() {
  local module="$1"
  [[ " ${selected_modules[*]} " == *" ${module} "* ]] || selected_modules+=("${module}")
}

discover_modules() {
  local mod
  while IFS= read -r mod; do
    mod="$(dirname "${mod}")"
    path_is_excluded "${mod}" || modules+=("${mod}")
  done < <(
    find "${repo_root}" \
      \( -type d \( -name .git -o -name .terraform -o -name .go-cache -o -name .go-modcache -o -name target -o -name generated -o -name vendor \) -prune \) -o \
      \( -type f -name go.mod -print \) | sort
  )
}

select_modules() {
  local path absolute mod selected longest
  if [[ "$#" -eq 0 ]]; then
    selected_modules=("${modules[@]}")
    return
  fi

  for path in "$@"; do
    absolute="$(realpath -m -- "${path}")"
    [[ "${absolute}" == "${repo_root}" || "${absolute}" == "${repo_root}/"* ]] || {
      echo "path outside repository: ${path}" >&2
      exit 2
    }
    path_is_excluded "${absolute}" && continue

    case "${absolute#"${repo_root}"/}" in
      scripts/local/go.sh | scripts/local/check.sh | scripts/local/check-staged.sh | scripts/local/verify.sh | scripts/local/test-go-tooling.sh | lefthook.yml | justfile)
        selected_modules=("${modules[@]}")
        continue
        ;;
      .ci/*)
        for mod in "${modules[@]}"; do
          [[ "${mod}" == "${repo_root}/services/luci" ]] && add_module "${mod}"
        done
        continue
        ;;
    esac

    if [[ -d "${absolute}" ]]; then
      for mod in "${modules[@]}"; do
        if [[ "${absolute}" == "${mod}" || "${absolute}" == "${mod}/"* || "${mod}" == "${absolute}/"* ]]; then
          add_module "${mod}"
        fi
      done
      continue
    fi

    selected=""
    longest=0
    for mod in "${modules[@]}"; do
      if [[ "${absolute}" == "${mod}/go.mod" || "${absolute}" == "${mod}/"* ]] && [[ "${#mod}" -gt "${longest}" ]]; then
        selected="${mod}"
        longest="${#mod}"
      fi
    done
    [[ -z "${selected}" ]] || add_module "${selected}"
  done
}

module_packages() {
  local import_path dir listing
  listing="$(go list -e -f '{{if .Dir}}{{.ImportPath}}{{"\t"}}{{.Dir}}{{end}}' ./...)" || return 1
  while IFS=$'\t' read -r import_path dir; do
    [[ -n "${import_path}" ]] || continue
    path_is_excluded "${dir}" || printf '%s\n' "${import_path}"
  done <<<"${listing}"
}

module_files() {
  local file owner longest mod
  while IFS= read -r file; do
    path_is_excluded "${file}" && continue
    owner=""
    longest=0
    for mod in "${modules[@]}"; do
      if [[ "${file}" == "${mod}/"* ]] && [[ "${#mod}" -gt "${longest}" ]]; then
        owner="${mod}"
        longest="${#mod}"
      fi
    done
    [[ "${owner}" == "$1" ]] && printf '%s\n' "${file}"
  done < <(
    find "$1" \
      \( -type d \( -name .git -o -name .terraform -o -name .go-cache -o -name .go-modcache -o -name target -o -name generated -o -name vendor \) -prune \) -o \
      \( -type f -name '*.go' -print \) | sort
  )
}

run_module() {
  local action="$1" module="$2" mode="${3:-}" format_diff package_output
  local -a files packages
  mapfile -t files < <(module_files "${module}")

  case "${action}" in
    check)
      if [[ "${mode}" == "read" ]]; then
        if [[ "${#files[@]}" -gt 0 ]]; then
          format_diff="$(gofmt -d "${files[@]}")"
          if [[ -n "${format_diff}" ]]; then
            printf '%s\n' "${format_diff}"
            return 1
          fi
        fi
      else
        [[ "${#files[@]}" -eq 0 ]] || gofmt -w "${files[@]}"
      fi
      ;;
  esac

  [[ "${#files[@]}" -gt 0 ]] || return 0

  (
    cd "${module}"
    package_output="$(module_packages)" || exit 1
    [[ -n "${package_output}" ]] || exit 0
    mapfile -t packages <<<"${package_output}"
    case "${action}" in
      check)
        go vet "${packages[@]}"
        go test "${packages[@]}"
        ;;
      race)
        go test -race "${packages[@]}"
        ;;
      staticcheck)
        go tool staticcheck "${packages[@]}"
        ;;
      vuln)
        go tool govulncheck "${packages[@]}"
        ;;
    esac
  )
}

action="${1:-}"
[[ -n "${action}" ]] || usage
shift

mode=""
case "${action}" in
  check)
    mode="write"
    if [[ "${1:-}" == "read" || "${1:-}" == "write" ]]; then
      mode="$1"
      shift
    fi
    ;;
  race | staticcheck | vuln) ;;
  *)
    usage
    ;;
esac

modules=()
selected_modules=()
discover_modules
select_modules "$@"

if [[ "${#selected_modules[@]}" -eq 0 ]]; then
  echo "no Go modules selected"
  exit 0
fi

if [[ "${action}" == "race" ]]; then
  [[ "$(go env CGO_ENABLED)" == "1" ]] || {
    echo "race tests require CGO_ENABLED=1" >&2
    exit 1
  }
  compiler="${CC:-$(go env CC)}"
  command -v "${compiler%% *}" >/dev/null 2>&1 || {
    echo "race tests require C compiler: ${compiler}" >&2
    exit 1
  }
fi

printf 'go modules:'
printf ' %s' "${selected_modules[@]#"${repo_root}"/}"
printf '\n'
for module in "${selected_modules[@]}"; do
  run_module "${action}" "${module}" "${mode}"
done