Files
MyGoNavi/tools/detect-changed-driver-agents.sh
Syngnat c66e8e7b49 ️ perf(ci): 优化 driver-agent 变更检测范围
- 按 driver token 和依赖路径归因共享脚本与 go.mod 变更
- 新增源码 diff 归因逻辑,减少无关 driver-agent 构建
- 保留无法归因场景的全量构建兜底,避免漏构建风险
2026-05-17 11:46:27 +08:00

637 lines
17 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$SCRIPT_DIR"
SCRIPT_DIR_WINDOWS="$(pwd -W 2>/dev/null || true)"
SCRIPT_DIR_WINDOWS="${SCRIPT_DIR_WINDOWS//\\//}"
DEFAULT_DRIVERS=(mariadb oceanbase doris starrocks sphinx sqlserver sqlite duckdb dameng kingbase highgo vastbase opengauss iris mongodb tdengine clickhouse)
TARGET_PLATFORMS=(darwin/amd64 darwin/arm64 windows/amd64 windows/arm64 linux/amd64)
usage() {
cat <<'EOF'
用法:
./tools/detect-changed-driver-agents.sh --base <ref> [--head <ref>]
输出:
逗号分隔的 driver-agent 列表;没有 driver-agent 相关变更时输出空行。
说明:
通过 go list -deps 计算每个 driver-agent 的真实源码依赖,再与 git diff 文件求交集。
如果无法解析基准或依赖分析失败,会保守输出全部 driver。
EOF
}
join_drivers() {
local IFS=,
echo "$*"
}
all_drivers_csv() {
join_drivers "${DEFAULT_DRIVERS[@]}"
}
is_dependency_source_file() {
case "$1" in
*.go|*.c|*.cc|*.cpp|*.cxx|*.h|*.hpp|*.m|*.mm|*.s|*.S|*.syso)
return 0
;;
*)
return 1
;;
esac
}
normalize_driver() {
local value
value="$(printf '%s' "${1:-}" | tr '[:upper:]' '[:lower:]' | tr -d '[:space:]')"
case "$value" in
doris|diros) echo "doris" ;;
open_gauss|open-gauss) echo "opengauss" ;;
mariadb|oceanbase|starrocks|sphinx|sqlserver|sqlite|duckdb|dameng|kingbase|highgo|vastbase|opengauss|iris|mongodb|tdengine|clickhouse)
echo "$value"
;;
*)
return 1
;;
esac
}
build_driver_name() {
case "$1" in
doris) echo "diros" ;;
*) echo "$1" ;;
esac
}
driver_build_tags() {
local driver="$1"
local platform="$2"
local goos="${platform%%/*}"
local goarch="${platform##*/}"
local build_driver tag
build_driver="$(build_driver_name "$driver")"
tag="gonavi_${build_driver}_driver"
if [[ "$driver" == "duckdb" && "$goos" == "windows" && "$goarch" == "amd64" ]]; then
tag="$tag duckdb_use_lib"
fi
echo "$tag"
}
all_driver_build_tags() {
local platform="$1"
local -a tags=()
local driver
for driver in "${DEFAULT_DRIVERS[@]}"; do
tags+=("$(driver_build_tags "$driver" "$platform")")
done
local IFS=" "
echo "${tags[*]}"
}
driver_cgo_enabled() {
# Detection only needs the Go dependency graph; keeping CGO off avoids
# cross-platform cgo toolchain requirements in the Ubuntu detection job.
echo 0
}
relative_repo_path() {
local path="$1"
path="${path//\\//}"
case "$path" in
"$SCRIPT_DIR"/*)
path="${path#$SCRIPT_DIR/}"
;;
esac
if [[ -n "$SCRIPT_DIR_WINDOWS" ]]; then
case "$path" in
"$SCRIPT_DIR_WINDOWS"/*)
path="${path#$SCRIPT_DIR_WINDOWS/}"
;;
esac
fi
path="${path#./}"
printf '%s\n' "$path"
}
add_analysis_platform() {
local platform="$1"
if [[ "$analysis_platform_seen" == *"|$platform|"* ]]; then
return 0
fi
analysis_platforms+=("$platform")
analysis_platform_seen="${analysis_platform_seen}${platform}|"
}
add_forced_driver() {
local driver
driver="$(normalize_driver "$1")" || return 0
if [[ "$forced_driver_seen" == *"|$driver|"* ]]; then
return 0
fi
forced_changed_drivers+=("$driver")
forced_driver_seen="${forced_driver_seen}${driver}|"
}
driver_tokens_from_text() {
local text
local emitted_seen
text="${1,,}"
emitted_seen="|"
case "$text" in *mariadb*) emit_driver_token mariadb ;; esac
case "$text" in *oceanbase*) emit_driver_token oceanbase ;; esac
case "$text" in *doris*|*diros*) emit_driver_token doris ;; esac
case "$text" in *starrocks*) emit_driver_token starrocks ;; esac
case "$text" in *sphinx*) emit_driver_token sphinx ;; esac
case "$text" in *sqlserver*) emit_driver_token sqlserver ;; esac
case "$text" in *sqlite*) emit_driver_token sqlite ;; esac
case "$text" in *duckdb*) emit_driver_token duckdb ;; esac
case "$text" in *dameng*) emit_driver_token dameng ;; esac
case "$text" in *kingbase*) emit_driver_token kingbase ;; esac
case "$text" in *highgo*) emit_driver_token highgo ;; esac
case "$text" in *vastbase*) emit_driver_token vastbase ;; esac
case "$text" in *opengauss*) emit_driver_token opengauss ;; esac
case "$text" in *iris*) emit_driver_token iris ;; esac
case "$text" in *mongodb*) emit_driver_token mongodb ;; esac
case "$text" in *tdengine*) emit_driver_token tdengine ;; esac
case "$text" in *clickhouse*) emit_driver_token clickhouse ;; esac
case "$text" in
*github.com/go-sql-driver/mysql*)
emit_driver_token mariadb
emit_driver_token oceanbase
emit_driver_token doris
emit_driver_token starrocks
emit_driver_token sphinx
;;
esac
case "$text" in *github.com/microsoft/go-mssqldb*) emit_driver_token sqlserver ;; esac
case "$text" in *modernc.org/sqlite*) emit_driver_token sqlite ;; esac
case "$text" in *github.com/duckdb/duckdb-go/v2*|*github.com/duckdb/duckdb-go-bindings*) emit_driver_token duckdb ;; esac
case "$text" in *gitee.com/chunanyong/dm*) emit_driver_token dameng ;; esac
case "$text" in *gitea.com/kingbase/gokb*) emit_driver_token kingbase ;; esac
case "$text" in *github.com/highgo/pq-sm3*|*third_party/highgo-pq*) emit_driver_token highgo ;; esac
case "$text" in
*github.com/lib/pq*)
emit_driver_token vastbase
emit_driver_token opengauss
;;
esac
case "$text" in *github.com/caretdev/go-irisnative*|*third_party/go-irisnative*) emit_driver_token iris ;; esac
case "$text" in *go.mongodb.org/mongo-driver*|*go.mongodb.org/mongo-driver/v2*) emit_driver_token mongodb ;; esac
case "$text" in *github.com/taosdata/driver-go/v3*) emit_driver_token tdengine ;; esac
case "$text" in *github.com/clickhouse/clickhouse-go/v2*|*github.com/clickhouse/ch-go*) emit_driver_token clickhouse ;; esac
}
emit_driver_token() {
local driver
driver="$(normalize_driver "$1")" || return 0
if [[ "$emitted_seen" == *"|$driver|"* ]]; then
return 0
fi
printf '%s\n' "$driver"
emitted_seen="${emitted_seen}${driver}|"
}
shared_file_driver_delta() {
local file="$1"
local line text tokens token added_seen removed_seen
local touched_seen emitted_seen
local saw_unattributed_change=false
added_seen="|"
removed_seen="|"
touched_seen="|"
while IFS= read -r line; do
case "$line" in
+++*|---*|@@*)
continue
;;
+*|-*)
text="${line:1}"
case "$text" in
*[![:space:]]*) ;;
*) continue ;;
esac
tokens="$(driver_tokens_from_text "$text")"
if [[ -z "$tokens" ]]; then
saw_unattributed_change=true
continue
fi
while IFS= read -r token; do
[[ -n "$token" ]] || continue
touched_seen="${touched_seen}${token}|"
case "$line" in
+*) added_seen="${added_seen}${token}|" ;;
-*) removed_seen="${removed_seen}${token}|" ;;
esac
done <<<"$tokens"
;;
esac
done < <(git diff --unified=0 "$base_commit" "$head_commit" -- "$file")
if [[ "$saw_unattributed_change" == "true" ]]; then
return 1
fi
if [[ "$file" == "go.mod" || "$file" == "go.sum" ]]; then
emitted_seen="|"
for driver in "${DEFAULT_DRIVERS[@]}"; do
if [[ "$touched_seen" == *"|$driver|"* && "$emitted_seen" != *"|$driver|"* ]]; then
printf '%s\n' "$driver"
emitted_seen="${emitted_seen}${driver}|"
fi
done
return 0
fi
emitted_seen="|"
for driver in "${DEFAULT_DRIVERS[@]}"; do
if [[ "$added_seen" == *"|$driver|"* && "$removed_seen" != *"|$driver|"* ]]; then
printf '%s\n' "$driver"
emitted_seen="${emitted_seen}${driver}|"
continue
fi
if [[ "$removed_seen" == *"|$driver|"* && "$added_seen" != *"|$driver|"* ]]; then
printf '%s\n' "$driver"
emitted_seen="${emitted_seen}${driver}|"
fi
done
if [[ "$emitted_seen" != "|" ]]; then
return 0
fi
for driver in "${DEFAULT_DRIVERS[@]}"; do
if [[ "$touched_seen" == *"|$driver|"* ]]; then
printf '%s\n' "$driver"
fi
done
}
source_file_driver_tokens() {
local file="$1"
local line text tokens token touched_seen emitted_seen
touched_seen="|"
while IFS= read -r line; do
case "$line" in
+++*|---*|@@*)
continue
;;
+*|-*)
text="${line:1}"
tokens="$(driver_tokens_from_text "$text")"
while IFS= read -r token; do
[[ -n "$token" ]] || continue
touched_seen="${touched_seen}${token}|"
done <<<"$tokens"
;;
esac
done < <(git diff --unified=0 "$base_commit" "$head_commit" -- "$file")
emitted_seen="|"
for driver in "${DEFAULT_DRIVERS[@]}"; do
if [[ "$touched_seen" == *"|$driver|"* && "$emitted_seen" != *"|$driver|"* ]]; then
printf '%s\n' "$driver"
emitted_seen="${emitted_seen}${driver}|"
fi
done
}
add_forced_drivers_from_tokens() {
local tokens="$1"
local driver
while IFS= read -r driver; do
[[ -n "$driver" ]] || continue
add_forced_driver "$driver"
done <<<"$tokens"
}
add_all_forced_drivers() {
local driver
for driver in "${DEFAULT_DRIVERS[@]}"; do
add_forced_driver "$driver"
done
}
is_ignored_driver_agent_source_file() {
case "$1" in
*_test.go|frontend/*|internal/app/*|internal/db/driver_agent_revisions_gen.go)
return 0
;;
esac
return 1
}
attribute_source_file_change() {
local file="$1"
local tokens
if is_ignored_driver_agent_source_file "$file"; then
return 0
fi
tokens="$(driver_tokens_from_text "$file")"
if [[ -n "$tokens" ]]; then
add_forced_drivers_from_tokens "$tokens"
return 0
fi
tokens="$(source_file_driver_tokens "$file")"
if [[ -n "$tokens" ]]; then
add_forced_drivers_from_tokens "$tokens"
return 0
fi
case "$file" in
cmd/optional-driver-agent/*.go|internal/db/*.go)
add_all_forced_drivers
return 0
;;
esac
return 1
}
list_dependency_files() {
local tags="$1"
local cgo_enabled="$2"
local goos="$3"
local goarch="$4"
local output="$5"
CGO_ENABLED="$cgo_enabled" GOOS="$goos" GOARCH="$goarch" GOTOOLCHAIN=auto \
go list -deps \
-tags "$tags" \
-f '{{if not .Standard}}{{range .GoFiles}}{{$.Dir}}/{{.}}{{"\n"}}{{end}}{{range .CgoFiles}}{{$.Dir}}/{{.}}{{"\n"}}{{end}}{{range .CFiles}}{{$.Dir}}/{{.}}{{"\n"}}{{end}}{{range .CXXFiles}}{{$.Dir}}/{{.}}{{"\n"}}{{end}}{{range .MFiles}}{{$.Dir}}/{{.}}{{"\n"}}{{end}}{{range .HFiles}}{{$.Dir}}/{{.}}{{"\n"}}{{end}}{{range .SFiles}}{{$.Dir}}/{{.}}{{"\n"}}{{end}}{{range .SysoFiles}}{{$.Dir}}/{{.}}{{"\n"}}{{end}}{{end}}' \
./cmd/optional-driver-agent | sort -u >"$output"
}
dependency_union_contains_changed_files() {
local platform goos goarch tags tmp file rel
local matched_any=false
for platform in "${TARGET_PLATFORMS[@]}"; do
goos="${platform%%/*}"
goarch="${platform##*/}"
tags="$(all_driver_build_tags "$platform")"
tmp="$(mktemp "${TMPDIR:-/tmp}/gonavi-driver-dep-union.XXXXXX")"
if ! list_dependency_files "$tags" 0 "$goos" "$goarch" "$tmp"; then
rm -f "$tmp"
return 2
fi
while IFS= read -r file; do
[[ -n "$file" ]] || continue
rel="$(relative_repo_path "$file")"
if [[ -n "${changed_file_set[$rel]:-}" ]]; then
dependency_matched_platforms["$rel"]="${dependency_matched_platforms[$rel]:-}|$platform|"
matched_any=true
fi
done <"$tmp"
rm -f "$tmp"
done
if [[ "$matched_any" == "true" ]]; then
return 0
fi
return 1
}
base_ref=""
head_ref="HEAD"
while [[ $# -gt 0 ]]; do
case "$1" in
--base)
base_ref="${2:-}"
shift 2
;;
--head)
head_ref="${2:-}"
shift 2
;;
-h|--help)
usage
exit 0
;;
*)
echo "未知参数:$1" >&2
usage >&2
exit 1
;;
esac
done
if [[ -z "$base_ref" ]]; then
echo "缺少 --base 参数。" >&2
usage >&2
exit 1
fi
if [[ "$base_ref" == "all" ]]; then
all_drivers_csv
exit 0
fi
if ! git rev-parse --verify "${head_ref}^{commit}" >/dev/null 2>&1; then
echo "无法解析 head ref$head_ref" >&2
exit 1
fi
head_commit="$(git rev-parse "${head_ref}^{commit}")"
if ! git rev-parse --verify "${base_ref}^{commit}" >/dev/null 2>&1; then
echo "无法解析 base ref$base_ref;保守构建全部 driver-agent。" >&2
all_drivers_csv
exit 0
fi
base_commit="$(git rev-parse "${base_ref}^{commit}")"
declare -A changed_file_set=()
while IFS= read -r -d '' file; do
file="$(relative_repo_path "$file")"
[[ -n "$file" ]] || continue
changed_file_set["$file"]=1
done < <(git diff --name-only -z "$base_commit" "$head_commit")
if [[ ${#changed_file_set[@]} -eq 0 ]]; then
echo ""
exit 0
fi
declare -a forced_changed_drivers=()
forced_driver_seen="|"
for file in "${!changed_file_set[@]}"; do
case "$file" in
go.mod|go.sum|build-driver-agents.sh|tools/generate-driver-agent-revisions.sh)
set +e
shared_delta="$(shared_file_driver_delta "$file")"
shared_status=$?
set -e
if [[ "$shared_status" -ne 0 ]]; then
echo "检测到共享 driver-agent 输入存在无法归因的变更;保守构建全部 driver-agent$file" >&2
all_drivers_csv
exit 0
fi
add_forced_drivers_from_tokens "$shared_delta"
;;
tools/compress-driver-artifact.sh)
echo "检测到 driver-agent 压缩脚本变更;保守构建全部 driver-agent$file" >&2
all_drivers_csv
exit 0
;;
tools/detect-changed-driver-agents.sh)
# This script only selects CI work; it is not embedded in driver-agent binaries.
;;
esac
done
for file in "${!changed_file_set[@]}"; do
case "$file" in
internal/db/duckdb_*.go)
add_forced_driver duckdb
;;
esac
done
has_unattributed_source_candidate=false
for file in "${!changed_file_set[@]}"; do
if is_dependency_source_file "$file"; then
if attribute_source_file_change "$file"; then
continue
fi
has_unattributed_source_candidate=true
break
fi
done
if [[ "$has_unattributed_source_candidate" != "true" ]]; then
join_drivers "${forced_changed_drivers[@]}"
exit 0
fi
while IFS= read -r -d '' file; do
file="$(relative_repo_path "$file")"
if is_dependency_source_file "$file"; then
if attribute_source_file_change "$file"; then
continue
fi
echo "检测到源码依赖候选文件被删除;保守构建全部 driver-agent$file" >&2
all_drivers_csv
exit 0
fi
done < <(git diff --name-only --diff-filter=D -z "$base_commit" "$head_commit")
declare -A dependency_matched_platforms=()
set +e
dependency_union_contains_changed_files
dependency_union_status=$?
set -e
case "$dependency_union_status" in
0)
;;
1)
if [[ ${#forced_changed_drivers[@]} -eq 0 ]]; then
echo ""
exit 0
fi
;;
*)
echo "分析 driver-agent 依赖全集失败;保守构建全部 driver-agent。" >&2
all_drivers_csv
exit 0
;;
esac
declare -a analysis_platforms=()
analysis_platform_seen="|"
for file in "${!dependency_matched_platforms[@]}"; do
matched_platforms="${dependency_matched_platforms[$file]}"
if [[ "$matched_platforms" == *"|linux/amd64|"* ]]; then
add_analysis_platform "linux/amd64"
continue
fi
for platform in "${TARGET_PLATFORMS[@]}"; do
if [[ "$matched_platforms" == *"|$platform|"* ]]; then
add_analysis_platform "$platform"
fi
done
done
if [[ ${#analysis_platforms[@]} -eq 0 ]]; then
analysis_platforms=("${TARGET_PLATFORMS[@]}")
fi
declare -a changed_drivers=()
driver_seen="|"
add_driver() {
local driver
driver="$(normalize_driver "$1")" || return 0
if [[ "$driver_seen" == *"|$driver|"* ]]; then
return 0
fi
changed_drivers+=("$driver")
driver_seen="${driver_seen}${driver}|"
}
for driver in "${forced_changed_drivers[@]}"; do
add_driver "$driver"
done
driver_depends_on_changed_files() {
local driver="$1"
local platform="$2"
local goos="${platform%%/*}"
local goarch="${platform##*/}"
local tags cgo_enabled file rel tmp
tags="$(driver_build_tags "$driver" "$platform")"
cgo_enabled="$(driver_cgo_enabled "$driver")"
tmp="$(mktemp "${TMPDIR:-/tmp}/gonavi-driver-deps.XXXXXX")"
if ! list_dependency_files "$tags" "$cgo_enabled" "$goos" "$goarch" "$tmp"; then
rm -f "$tmp"
return 2
fi
while IFS= read -r file; do
[[ -n "$file" ]] || continue
rel="$(relative_repo_path "$file")"
if [[ -n "${changed_file_set[$rel]:-}" ]]; then
rm -f "$tmp"
return 0
fi
done <"$tmp"
rm -f "$tmp"
return 1
}
for driver in "${DEFAULT_DRIVERS[@]}"; do
driver_changed=false
for platform in "${analysis_platforms[@]}"; do
set +e
driver_depends_on_changed_files "$driver" "$platform"
status=$?
set -e
case "$status" in
0)
add_driver "$driver"
driver_changed=true
break
;;
1)
;;
*)
echo "分析 $driver driver-agent 依赖失败;保守构建全部 driver-agent。" >&2
all_drivers_csv
exit 0
;;
esac
done
done
join_drivers "${changed_drivers[@]}"