From 356baa1e38a98c44d4a7091d8a1651f26b50750a Mon Sep 17 00:00:00 2001 From: Syngnat Date: Fri, 12 Jun 2026 17:00:09 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20fix(ci/driver):=20=E7=A6=81?= =?UTF-8?q?=E7=94=A8=20Windows=20AMD=20=E9=A9=B1=E5=8A=A8=20UPX=20?= =?UTF-8?q?=E5=8E=8B=E7=BC=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在驱动产物压缩脚本中跳过 windows/amd64 平台 - 保留 Linux 驱动产物 UPX 压缩与 metadata 自检逻辑 - 补充 Windows amd64 跳过压缩且文件不变的脚本测试 --- .github/workflows/dev-build.yml | 103 +++++++++++------- .github/workflows/release.yml | 103 +++++++++++------- internal/app/methods_driver.go | 7 +- .../app/methods_driver_agent_revision_test.go | 16 +++ internal/app/methods_driver_version_test.go | 52 +++++++++ tools/compress-driver-artifact.sh | 6 +- tools/compress-driver-artifact.test.sh | 34 ++++-- tools/generate-driver-release-manifest.py | 26 +++-- .../generate-driver-release-manifest.test.py | 26 ++++- tools/validate-driver-release-assets.py | 91 +++++++++++++--- tools/validate-driver-release-assets.test.py | 89 ++++++++++++++- tools/verify-driver-agent-revisions.sh | 79 +++++++++----- 12 files changed, 478 insertions(+), 154 deletions(-) diff --git a/.github/workflows/dev-build.yml b/.github/workflows/dev-build.yml index c6433ef..9788ea3 100644 --- a/.github/workflows/dev-build.yml +++ b/.github/workflows/dev-build.yml @@ -601,55 +601,72 @@ jobs: echo "⚠️ 跳过 DuckDB driver(当前平台 ${GOOS}/${GOARCH} 不受支持,仅支持 windows/amd64)" continue fi - TAG="gonavi_${BUILD_DRIVER}_driver" - BUILD_TAGS="$TAG" - OUTPUT="${DRIVER}-driver-agent-${GOOS}-${GOARCH}" - if [ "$GOOS" = "windows" ]; then - OUTPUT="${OUTPUT}.exe" + DRIVER_VARIANTS=("") + if [ "$DRIVER" = "mongodb" ]; then + DRIVER_VARIANTS=("v1" "v2" "") fi - OUTPUT_PATH="${OUTDIR}/${OUTPUT}" - DUCKDB_LIB_DIR="" - if [ "$DRIVER" = "duckdb" ] && [ "$GOOS" = "windows" ] && [ "$GOARCH" = "amd64" ]; then - DUCKDB_LIB_DIR="$(prepare_duckdb_windows_library)" - BUILD_TAGS="${BUILD_TAGS} duckdb_use_lib" - fi - echo "🔧 构建 ${OUTPUT_PATH} (tags=${BUILD_TAGS})" - if [ "$DRIVER" = "duckdb" ]; then - if [ -n "$DUCKDB_LIB_DIR" ]; then - DUCKDB_LIB_DIR_GCC="$(cygpath -m "$DUCKDB_LIB_DIR")" - DUCKDB_LIB_DIR_PATH="$(cygpath -u "$DUCKDB_LIB_DIR")" - # cgo 会把每个 CGO_LDFLAGS 片段转成 //go:cgo_ldflag,-L 参数不能再额外包引号。 - DUCKDB_WINDOWS_CGO_LDFLAGS="-L${DUCKDB_LIB_DIR_GCC} -lduckdb -lstdc++ -lm -lws2_32 -lwsock32 -lrstrtmgr" - CGO_ENABLED=1 GOOS="$GOOS" GOARCH="$GOARCH" CGO_LDFLAGS="${DUCKDB_WINDOWS_CGO_LDFLAGS}" PATH="${DUCKDB_LIB_DIR_PATH}:$PATH" go build \ - -tags "${BUILD_TAGS}" \ - -trimpath \ - -ldflags "-s -w" \ - -o "${OUTPUT_PATH}" \ - ./cmd/optional-driver-agent - cp "$DUCKDB_LIB_DIR/duckdb.dll" "$OUTDIR/duckdb.dll" - bash ./tools/compress-driver-artifact.sh "$OUTDIR/duckdb.dll" "$TARGET_PLATFORM" "${{ matrix.os_name }}/duckdb.dll" + for DRIVER_VARIANT in "${DRIVER_VARIANTS[@]}"; do + TAG="gonavi_${BUILD_DRIVER}_driver" + BUILD_TAGS="$TAG" + OUTPUT_STEM="${DRIVER}-driver-agent" + if [ "$DRIVER" = "mongodb" ]; then + if [ "$DRIVER_VARIANT" = "v1" ]; then + BUILD_TAGS="gonavi_mongodb_driver_v1" + OUTPUT_STEM="mongodb-driver-agent-v1" + elif [ "$DRIVER_VARIANT" = "v2" ]; then + BUILD_TAGS="gonavi_mongodb_driver" + OUTPUT_STEM="mongodb-driver-agent-v2" + else + BUILD_TAGS="gonavi_mongodb_driver" + fi + fi + OUTPUT="${OUTPUT_STEM}-${GOOS}-${GOARCH}" + if [ "$GOOS" = "windows" ]; then + OUTPUT="${OUTPUT}.exe" + fi + OUTPUT_PATH="${OUTDIR}/${OUTPUT}" + DUCKDB_LIB_DIR="" + if [ "$DRIVER" = "duckdb" ] && [ "$GOOS" = "windows" ] && [ "$GOARCH" = "amd64" ]; then + DUCKDB_LIB_DIR="$(prepare_duckdb_windows_library)" + BUILD_TAGS="${BUILD_TAGS} duckdb_use_lib" + fi + echo "🔧 构建 ${OUTPUT_PATH} (tags=${BUILD_TAGS})" + if [ "$DRIVER" = "duckdb" ]; then + if [ -n "$DUCKDB_LIB_DIR" ]; then + DUCKDB_LIB_DIR_GCC="$(cygpath -m "$DUCKDB_LIB_DIR")" + DUCKDB_LIB_DIR_PATH="$(cygpath -u "$DUCKDB_LIB_DIR")" + # cgo 会把每个 CGO_LDFLAGS 片段转成 //go:cgo_ldflag,-L 参数不能再额外包引号。 + DUCKDB_WINDOWS_CGO_LDFLAGS="-L${DUCKDB_LIB_DIR_GCC} -lduckdb -lstdc++ -lm -lws2_32 -lwsock32 -lrstrtmgr" + CGO_ENABLED=1 GOOS="$GOOS" GOARCH="$GOARCH" CGO_LDFLAGS="${DUCKDB_WINDOWS_CGO_LDFLAGS}" PATH="${DUCKDB_LIB_DIR_PATH}:$PATH" go build \ + -tags "${BUILD_TAGS}" \ + -trimpath \ + -ldflags "-s -w" \ + -o "${OUTPUT_PATH}" \ + ./cmd/optional-driver-agent + cp "$DUCKDB_LIB_DIR/duckdb.dll" "$OUTDIR/duckdb.dll" + bash ./tools/compress-driver-artifact.sh "$OUTDIR/duckdb.dll" "$TARGET_PLATFORM" "${{ matrix.os_name }}/duckdb.dll" + else + CGO_ENABLED=1 GOOS="$GOOS" GOARCH="$GOARCH" go build \ + -tags "${BUILD_TAGS}" \ + -trimpath \ + -ldflags "-s -w" \ + -o "${OUTPUT_PATH}" \ + ./cmd/optional-driver-agent + fi else - CGO_ENABLED=1 GOOS="$GOOS" GOARCH="$GOARCH" go build \ + CGO_ENABLED=0 GOOS="$GOOS" GOARCH="$GOARCH" go build \ -tags "${BUILD_TAGS}" \ -trimpath \ -ldflags "-s -w" \ -o "${OUTPUT_PATH}" \ ./cmd/optional-driver-agent fi - else - CGO_ENABLED=0 GOOS="$GOOS" GOARCH="$GOARCH" go build \ - -tags "${BUILD_TAGS}" \ - -trimpath \ - -ldflags "-s -w" \ - -o "${OUTPUT_PATH}" \ - ./cmd/optional-driver-agent - fi - bash ./tools/compress-driver-artifact.sh "${OUTPUT_PATH}" "$TARGET_PLATFORM" "${{ matrix.os_name }}/${OUTPUT}" - if [ "$DRIVER" = "duckdb" ] && [ -n "$DUCKDB_LIB_DIR" ]; then - DUCKDB_ZIP_PATH="${OUTDIR}/duckdb-driver.zip" - export DUCKDB_ZIP_PATH - export DUCKDB_AGENT_PATH="${OUTPUT_PATH}" - export DUCKDB_DLL_PATH="${OUTDIR}/duckdb.dll" + bash ./tools/compress-driver-artifact.sh "${OUTPUT_PATH}" "$TARGET_PLATFORM" "${{ matrix.os_name }}/${OUTPUT}" + if [ "$DRIVER" = "duckdb" ] && [ -n "$DUCKDB_LIB_DIR" ]; then + DUCKDB_ZIP_PATH="${OUTDIR}/duckdb-driver.zip" + export DUCKDB_ZIP_PATH + export DUCKDB_AGENT_PATH="${OUTPUT_PATH}" + export DUCKDB_DLL_PATH="${OUTDIR}/duckdb.dll" python3 - <<'PY' import os import zipfile @@ -666,8 +683,10 @@ jobs: raise FileNotFoundError(src) zf.write(src, arcname) PY - echo "📦 已生成 DuckDB Windows 专属驱动包: ${DUCKDB_ZIP_PATH}" + echo "📦 已生成 DuckDB Windows 专属驱动包: ${DUCKDB_ZIP_PATH}" + fi fi + done done bash ./tools/verify-driver-agent-revisions.sh \ diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 11e0c54..e1b2726 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -599,55 +599,72 @@ jobs: echo "⚠️ 跳过 DuckDB driver(当前平台 ${GOOS}/${GOARCH} 不受支持,仅支持 windows/amd64)" continue fi - TAG="gonavi_${BUILD_DRIVER}_driver" - BUILD_TAGS="$TAG" - OUTPUT="${DRIVER}-driver-agent-${GOOS}-${GOARCH}" - if [ "$GOOS" = "windows" ]; then - OUTPUT="${OUTPUT}.exe" + DRIVER_VARIANTS=("") + if [ "$DRIVER" = "mongodb" ]; then + DRIVER_VARIANTS=("v1" "v2" "") fi - OUTPUT_PATH="${OUTDIR}/${OUTPUT}" - DUCKDB_LIB_DIR="" - if [ "$DRIVER" = "duckdb" ] && [ "$GOOS" = "windows" ] && [ "$GOARCH" = "amd64" ]; then - DUCKDB_LIB_DIR="$(prepare_duckdb_windows_library)" - BUILD_TAGS="${BUILD_TAGS} duckdb_use_lib" - fi - echo "🔧 构建 ${OUTPUT_PATH} (tags=${BUILD_TAGS})" - if [ "$DRIVER" = "duckdb" ]; then - if [ -n "$DUCKDB_LIB_DIR" ]; then - DUCKDB_LIB_DIR_GCC="$(cygpath -m "$DUCKDB_LIB_DIR")" - DUCKDB_LIB_DIR_PATH="$(cygpath -u "$DUCKDB_LIB_DIR")" - # cgo 会把每个 CGO_LDFLAGS 片段转成 //go:cgo_ldflag,-L 参数不能再额外包引号。 - DUCKDB_WINDOWS_CGO_LDFLAGS="-L${DUCKDB_LIB_DIR_GCC} -lduckdb -lstdc++ -lm -lws2_32 -lwsock32 -lrstrtmgr" - CGO_ENABLED=1 GOOS="$GOOS" GOARCH="$GOARCH" CGO_LDFLAGS="${DUCKDB_WINDOWS_CGO_LDFLAGS}" PATH="${DUCKDB_LIB_DIR_PATH}:$PATH" go build \ - -tags "${BUILD_TAGS}" \ - -trimpath \ - -ldflags "-s -w" \ - -o "${OUTPUT_PATH}" \ - ./cmd/optional-driver-agent - cp "$DUCKDB_LIB_DIR/duckdb.dll" "$OUTDIR/duckdb.dll" - bash ./tools/compress-driver-artifact.sh "$OUTDIR/duckdb.dll" "$TARGET_PLATFORM" "${{ matrix.os_name }}/duckdb.dll" + for DRIVER_VARIANT in "${DRIVER_VARIANTS[@]}"; do + TAG="gonavi_${BUILD_DRIVER}_driver" + BUILD_TAGS="$TAG" + OUTPUT_STEM="${DRIVER}-driver-agent" + if [ "$DRIVER" = "mongodb" ]; then + if [ "$DRIVER_VARIANT" = "v1" ]; then + BUILD_TAGS="gonavi_mongodb_driver_v1" + OUTPUT_STEM="mongodb-driver-agent-v1" + elif [ "$DRIVER_VARIANT" = "v2" ]; then + BUILD_TAGS="gonavi_mongodb_driver" + OUTPUT_STEM="mongodb-driver-agent-v2" + else + BUILD_TAGS="gonavi_mongodb_driver" + fi + fi + OUTPUT="${OUTPUT_STEM}-${GOOS}-${GOARCH}" + if [ "$GOOS" = "windows" ]; then + OUTPUT="${OUTPUT}.exe" + fi + OUTPUT_PATH="${OUTDIR}/${OUTPUT}" + DUCKDB_LIB_DIR="" + if [ "$DRIVER" = "duckdb" ] && [ "$GOOS" = "windows" ] && [ "$GOARCH" = "amd64" ]; then + DUCKDB_LIB_DIR="$(prepare_duckdb_windows_library)" + BUILD_TAGS="${BUILD_TAGS} duckdb_use_lib" + fi + echo "🔧 构建 ${OUTPUT_PATH} (tags=${BUILD_TAGS})" + if [ "$DRIVER" = "duckdb" ]; then + if [ -n "$DUCKDB_LIB_DIR" ]; then + DUCKDB_LIB_DIR_GCC="$(cygpath -m "$DUCKDB_LIB_DIR")" + DUCKDB_LIB_DIR_PATH="$(cygpath -u "$DUCKDB_LIB_DIR")" + # cgo 会把每个 CGO_LDFLAGS 片段转成 //go:cgo_ldflag,-L 参数不能再额外包引号。 + DUCKDB_WINDOWS_CGO_LDFLAGS="-L${DUCKDB_LIB_DIR_GCC} -lduckdb -lstdc++ -lm -lws2_32 -lwsock32 -lrstrtmgr" + CGO_ENABLED=1 GOOS="$GOOS" GOARCH="$GOARCH" CGO_LDFLAGS="${DUCKDB_WINDOWS_CGO_LDFLAGS}" PATH="${DUCKDB_LIB_DIR_PATH}:$PATH" go build \ + -tags "${BUILD_TAGS}" \ + -trimpath \ + -ldflags "-s -w" \ + -o "${OUTPUT_PATH}" \ + ./cmd/optional-driver-agent + cp "$DUCKDB_LIB_DIR/duckdb.dll" "$OUTDIR/duckdb.dll" + bash ./tools/compress-driver-artifact.sh "$OUTDIR/duckdb.dll" "$TARGET_PLATFORM" "${{ matrix.os_name }}/duckdb.dll" + else + CGO_ENABLED=1 GOOS="$GOOS" GOARCH="$GOARCH" go build \ + -tags "${BUILD_TAGS}" \ + -trimpath \ + -ldflags "-s -w" \ + -o "${OUTPUT_PATH}" \ + ./cmd/optional-driver-agent + fi else - CGO_ENABLED=1 GOOS="$GOOS" GOARCH="$GOARCH" go build \ + CGO_ENABLED=0 GOOS="$GOOS" GOARCH="$GOARCH" go build \ -tags "${BUILD_TAGS}" \ -trimpath \ -ldflags "-s -w" \ -o "${OUTPUT_PATH}" \ ./cmd/optional-driver-agent fi - else - CGO_ENABLED=0 GOOS="$GOOS" GOARCH="$GOARCH" go build \ - -tags "${BUILD_TAGS}" \ - -trimpath \ - -ldflags "-s -w" \ - -o "${OUTPUT_PATH}" \ - ./cmd/optional-driver-agent - fi - bash ./tools/compress-driver-artifact.sh "${OUTPUT_PATH}" "$TARGET_PLATFORM" "${{ matrix.os_name }}/${OUTPUT}" - if [ "$DRIVER" = "duckdb" ] && [ -n "$DUCKDB_LIB_DIR" ]; then - DUCKDB_ZIP_PATH="${OUTDIR}/duckdb-driver.zip" - export DUCKDB_ZIP_PATH - export DUCKDB_AGENT_PATH="${OUTPUT_PATH}" - export DUCKDB_DLL_PATH="${OUTDIR}/duckdb.dll" + bash ./tools/compress-driver-artifact.sh "${OUTPUT_PATH}" "$TARGET_PLATFORM" "${{ matrix.os_name }}/${OUTPUT}" + if [ "$DRIVER" = "duckdb" ] && [ -n "$DUCKDB_LIB_DIR" ]; then + DUCKDB_ZIP_PATH="${OUTDIR}/duckdb-driver.zip" + export DUCKDB_ZIP_PATH + export DUCKDB_AGENT_PATH="${OUTPUT_PATH}" + export DUCKDB_DLL_PATH="${OUTDIR}/duckdb.dll" python3 - <<'PY' import os import zipfile @@ -664,8 +681,10 @@ jobs: raise FileNotFoundError(src) zf.write(src, arcname) PY - echo "📦 已生成 DuckDB Windows 专属驱动包: ${DUCKDB_ZIP_PATH}" + echo "📦 已生成 DuckDB Windows 专属驱动包: ${DUCKDB_ZIP_PATH}" + fi fi + done done bash ./tools/verify-driver-agent-revisions.sh \ diff --git a/internal/app/methods_driver.go b/internal/app/methods_driver.go index fb480b1..eec5406 100644 --- a/internal/app/methods_driver.go +++ b/internal/app/methods_driver.go @@ -2812,7 +2812,7 @@ func readInstalledDriverPackage(downloadDir string, driverType string) (installe func optionalDriverAgentRevisionStatus(driverType string, pkg installedDriverPackage, packageMetaExists bool) (bool, string, string) { expected := db.OptionalDriverAgentRevision(driverType) - if strings.TrimSpace(expected) == "" || !packageMetaExists || !db.IsOptionalGoDriver(driverType) { + if strings.TrimSpace(expected) == "" || !packageMetaExists || !db.IsOptionalGoDriver(driverType) || !shouldVerifyOptionalDriverAgentRevision(driverType, pkg.Version) { return false, "", expected } actual := strings.TrimSpace(pkg.AgentRevision) @@ -4139,7 +4139,7 @@ func withEnvValue(env []string, key string, value string) []string { } func duckDBWindowsDynamicLibraryCGOLDFlags(libDir string) string { - normalizedDir := filepath.ToSlash(strings.TrimSpace(libDir)) + normalizedDir := strings.ReplaceAll(filepath.ToSlash(strings.TrimSpace(libDir)), `\`, `/`) parts := []string{ // cgo 会把每个 CGO_LDFLAGS 片段转成 //go:cgo_ldflag,带引号的 -L 在 windows/amd64 上会被当成非法参数。 fmt.Sprintf("-L%s", normalizedDir), @@ -4391,10 +4391,9 @@ func optionalDriverNameStemCandidates(driverType string, selectedVersion string) switch resolveMongoDriverMajorFromVersion(selectedVersion) { case 1: appendStem(base + "-v1") - appendStem(base) case 2: - appendStem(base) appendStem(base + "-v2") + appendStem(base) default: appendStem(base) } diff --git a/internal/app/methods_driver_agent_revision_test.go b/internal/app/methods_driver_agent_revision_test.go index a7f773b..4029d74 100644 --- a/internal/app/methods_driver_agent_revision_test.go +++ b/internal/app/methods_driver_agent_revision_test.go @@ -52,6 +52,22 @@ func TestOptionalDriverPackageUpdateStatusDetectsMongoV2WhenLegacyDefault(t *tes } } +func TestOptionalDriverPackageUpdateStatusAcceptsMongoV1WithoutRevision(t *testing.T) { + definition, ok := resolveDriverDefinition("mongodb") + if !ok { + t.Fatal("expected mongodb driver definition") + } + meta := installedDriverPackage{ + Version: "1.17.9", + AgentRevision: "", + } + + needsUpdate, reason, _ := optionalDriverPackageUpdateStatus(definition, meta, true) + if needsUpdate { + t.Fatalf("expected MongoDB v1 driver to skip revision mismatch prompts, reason=%q", reason) + } +} + func TestOptionalDriverAgentRevisionCurrentRejectsStaleMetadata(t *testing.T) { originalProbe := optionalDriverAgentMetadataProbe t.Cleanup(func() { diff --git a/internal/app/methods_driver_version_test.go b/internal/app/methods_driver_version_test.go index af992a4..ee861d7 100644 --- a/internal/app/methods_driver_version_test.go +++ b/internal/app/methods_driver_version_test.go @@ -266,6 +266,58 @@ func TestResolveOptionalDriverAgentDownloadURLsUsesMongoV1AssetForCompatibleDefa t.Fatalf("expected MongoDB v1 release asset %q in candidates, got %v", want, urls) } +func TestResolveOptionalDriverAgentDownloadURLsDoesNotUseMongoV2BaseForCompatibleDefault(t *testing.T) { + definition, ok := resolveDriverDefinition("mongodb") + if !ok { + t.Fatal("expected mongodb driver definition") + } + + originalVersion := AppVersion + AppVersion = "0.7.9" + t.Cleanup(func() { + AppVersion = originalVersion + }) + + baseAssetName := optionalDriverReleaseAssetNameForType("mongodb", runtime.GOOS, runtime.GOARCH) + seedReleaseAssetSizeCache(t, "tag:v0.7.9", map[string]int64{ + baseAssetName: 24 << 20, + }) + seedReleaseAssetSizeCache(t, "latest", map[string]int64{ + baseAssetName: 24 << 20, + }) + + urls := resolveOptionalDriverAgentDownloadURLs( + definition, + "builtin://activate/mongodb", + "1.17.9", + ) + for _, got := range urls { + if strings.Contains(got, baseAssetName) { + t.Fatalf("expected MongoDB v1 install not to use ambiguous base asset %q, got %v", baseAssetName, urls) + } + } +} + +func TestMongoDBVersionedAssetNamesDoNotFallbackToBaseForV1(t *testing.T) { + v1AssetName := mongoVersionedReleaseAssetName(1) + baseAssetName := optionalDriverReleaseAssetNameForType("mongodb", runtime.GOOS, runtime.GOARCH) + + v1Names := optionalDriverReleaseAssetNamesForVersion("mongodb", "1.17.9") + if len(v1Names) != 1 || v1Names[0] != v1AssetName { + t.Fatalf("expected MongoDB v1 to use only %q, got %v", v1AssetName, v1Names) + } + for _, name := range v1Names { + if name == baseAssetName { + t.Fatalf("MongoDB v1 must not fallback to ambiguous base asset %q", baseAssetName) + } + } + + v2Names := optionalDriverReleaseAssetNamesForVersion("mongodb", "2.5.0") + if len(v2Names) < 2 || v2Names[0] != mongoVersionedReleaseAssetName(2) || v2Names[1] != baseAssetName { + t.Fatalf("expected MongoDB v2 to prefer versioned asset then base compatibility asset, got %v", v2Names) + } +} + func TestResolveOptionalDriverAgentDownloadURLsSkipsBundleOnlyDamengAsset(t *testing.T) { definition, ok := resolveDriverDefinition("dameng") if !ok { diff --git a/tools/compress-driver-artifact.sh b/tools/compress-driver-artifact.sh index 7e5c148..bb937a6 100755 --- a/tools/compress-driver-artifact.sh +++ b/tools/compress-driver-artifact.sh @@ -59,7 +59,11 @@ goos="${platform%%/*}" goarch="${platform##*/}" case "$goos/$goarch" in - linux/amd64|linux/arm64|windows/amd64) + windows/amd64) + echo "ℹ️ Windows amd64 驱动产物不执行 UPX 压缩:$label" + exit 0 + ;; + linux/amd64|linux/arm64) ;; *) echo "ℹ️ UPX 跳过不支持的平台:$label ($platform)" diff --git a/tools/compress-driver-artifact.test.sh b/tools/compress-driver-artifact.test.sh index 99f1085..11065cc 100755 --- a/tools/compress-driver-artifact.test.sh +++ b/tools/compress-driver-artifact.test.sh @@ -2,16 +2,6 @@ set -euo pipefail -host_platform="$(go env GOOS)/$(go env GOARCH)" -case "$host_platform" in - linux/amd64|linux/arm64|windows/amd64) - ;; - *) - echo "skip compress-driver-artifact smoke test on unsupported host platform: $host_platform" - exit 0 - ;; -esac - repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" tmpdir="$(mktemp -d "${TMPDIR:-/tmp}/gonavi-compress-driver-artifact.XXXXXX")" cleanup() { @@ -19,6 +9,30 @@ cleanup() { } trap cleanup EXIT +windows_amd64_bin="$tmpdir/windows-driver-agent-windows-amd64.exe" +printf 'fake windows amd64 driver' >"$windows_amd64_bin" +windows_amd64_before="$(cat "$windows_amd64_bin")" +windows_skip_output="$(bash "$repo_root/tools/compress-driver-artifact.sh" "$windows_amd64_bin" "windows/amd64" "windows-amd64-driver" 2>&1)" +if [[ "$windows_skip_output" != *"Windows amd64 驱动产物不执行 UPX 压缩"* ]]; then + echo "expected Windows amd64 driver artifact UPX compression to be skipped" >&2 + echo "$windows_skip_output" >&2 + exit 1 +fi +if [[ "$(cat "$windows_amd64_bin")" != "$windows_amd64_before" ]]; then + echo "expected Windows amd64 driver artifact to remain unchanged when UPX is skipped" >&2 + exit 1 +fi + +host_platform="$(go env GOOS)/$(go env GOARCH)" +case "$host_platform" in + linux/amd64|linux/arm64) + ;; + *) + echo "skip compress-driver-artifact smoke test on unsupported host platform: $host_platform" + exit 0 + ;; +esac + suffix="" if [[ "$host_platform" == windows/* ]]; then suffix=".exe" diff --git a/tools/generate-driver-release-manifest.py b/tools/generate-driver-release-manifest.py index 1489633..9434c73 100644 --- a/tools/generate-driver-release-manifest.py +++ b/tools/generate-driver-release-manifest.py @@ -19,19 +19,25 @@ def parse_args(): def infer_driver_and_platform(file_name: str): suffixes = [ - "-driver-agent-darwin-amd64", - "-driver-agent-darwin-arm64", - "-driver-agent-linux-amd64", - "-driver-agent-windows-amd64.exe", - "-driver-agent-windows-arm64.exe", + ("-driver-agent-v1-darwin-amd64", "darwin/amd64"), + ("-driver-agent-v1-darwin-arm64", "darwin/arm64"), + ("-driver-agent-v1-linux-amd64", "linux/amd64"), + ("-driver-agent-v1-windows-amd64.exe", "windows/amd64"), + ("-driver-agent-v1-windows-arm64.exe", "windows/arm64"), + ("-driver-agent-v2-darwin-amd64", "darwin/amd64"), + ("-driver-agent-v2-darwin-arm64", "darwin/arm64"), + ("-driver-agent-v2-linux-amd64", "linux/amd64"), + ("-driver-agent-v2-windows-amd64.exe", "windows/amd64"), + ("-driver-agent-v2-windows-arm64.exe", "windows/arm64"), + ("-driver-agent-darwin-amd64", "darwin/amd64"), + ("-driver-agent-darwin-arm64", "darwin/arm64"), + ("-driver-agent-linux-amd64", "linux/amd64"), + ("-driver-agent-windows-amd64.exe", "windows/amd64"), + ("-driver-agent-windows-arm64.exe", "windows/arm64"), ] - for suffix in suffixes: + for suffix, platform in suffixes: if file_name.endswith(suffix): driver = file_name[: -len(suffix)] - platform_name = suffix.replace("-driver-agent-", "") - if platform_name.endswith(".exe"): - platform_name = platform_name.removesuffix(".exe") - platform = platform_name.replace("-", "/", 1) return driver, platform return None, None diff --git a/tools/generate-driver-release-manifest.test.py b/tools/generate-driver-release-manifest.test.py index 3b1fe6d..e0a452b 100644 --- a/tools/generate-driver-release-manifest.test.py +++ b/tools/generate-driver-release-manifest.test.py @@ -24,7 +24,7 @@ def expected_revision(revision_file: Path, driver: str): class GenerateDriverReleaseManifestTest(unittest.TestCase): - def _generate_revision_file(self, platform: str): + def _generate_revision_file(self, platform: str, drivers: str = "clickhouse"): worktree = Path(tempfile.mkdtemp(prefix="gonavi-release-manifest-worktree-")) subprocess.run( ["git", "worktree", "add", "--detach", str(worktree), "HEAD"], @@ -43,7 +43,7 @@ class GenerateDriverReleaseManifestTest(unittest.TestCase): ) ) subprocess.run( - ["bash", "./tools/generate-driver-agent-revisions.sh", "--platform", platform, "--drivers", "clickhouse"], + ["bash", "./tools/generate-driver-agent-revisions.sh", "--platform", platform, "--drivers", drivers], cwd=worktree, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, @@ -63,6 +63,8 @@ class GenerateDriverReleaseManifestTest(unittest.TestCase): assets_dir / "MacOS" / "clickhouse-driver-agent-darwin-arm64": b"darwin-binary", assets_dir / "Linux" / "clickhouse-driver-agent-linux-amd64": b"linux-binary", assets_dir / "Windows" / "clickhouse-driver-agent-windows-amd64.exe": b"MZfake-binary", + assets_dir / "Windows" / "mongodb-driver-agent-v1-windows-amd64.exe": b"MZfake-mongodb-v1", + assets_dir / "Windows" / "mongodb-driver-agent-v2-windows-amd64.exe": b"MZfake-mongodb-v2", } for path, content in fixtures.items(): path.write_bytes(content) @@ -78,12 +80,12 @@ class GenerateDriverReleaseManifestTest(unittest.TestCase): check=True, ) - self.assertIn("asset count: 3", proc.stdout) + self.assertIn("asset count: 5", proc.stdout) manifest = json.loads(output.read_text(encoding="utf-8")) assets = manifest["assets"] darwin_revision_file = self._generate_revision_file("darwin/arm64") linux_revision_file = self._generate_revision_file("linux/amd64") - windows_revision_file = self._generate_revision_file("windows/amd64") + windows_revision_file = self._generate_revision_file("windows/amd64", "clickhouse,mongodb") self.assertEqual( assets["clickhouse-driver-agent-darwin-arm64"]["revision"], expected_revision(darwin_revision_file, "clickhouse"), @@ -96,6 +98,22 @@ class GenerateDriverReleaseManifestTest(unittest.TestCase): assets["clickhouse-driver-agent-windows-amd64.exe"]["revision"], expected_revision(windows_revision_file, "clickhouse"), ) + self.assertEqual( + assets["mongodb-driver-agent-v1-windows-amd64.exe"]["driver"], + "mongodb", + ) + self.assertEqual( + assets["mongodb-driver-agent-v1-windows-amd64.exe"]["platform"], + "windows/amd64", + ) + self.assertEqual( + assets["mongodb-driver-agent-v1-windows-amd64.exe"]["revision"], + expected_revision(windows_revision_file, "mongodb"), + ) + self.assertEqual( + assets["mongodb-driver-agent-v2-windows-amd64.exe"]["driver"], + "mongodb", + ) if __name__ == "__main__": diff --git a/tools/validate-driver-release-assets.py b/tools/validate-driver-release-assets.py index 3919dd1..f1f23de 100644 --- a/tools/validate-driver-release-assets.py +++ b/tools/validate-driver-release-assets.py @@ -4,6 +4,7 @@ import argparse import hashlib import json import os +import platform import shutil import stat import subprocess @@ -61,6 +62,16 @@ def asset_map(release: dict): return result +def asset_sha256_digest(asset: dict): + digest = str(asset.get("digest") or "").strip().lower() + prefix = "sha256:" + if digest.startswith(prefix): + value = digest[len(prefix) :].strip() + if value: + return value + return "" + + def infer_asset_path(name: str): trimmed = str(name or "").strip() if not trimmed: @@ -69,15 +80,58 @@ def infer_asset_path(name: str): return "Windows/duckdb.dll" if trimmed == "duckdb-driver.zip": return None - if trimmed.endswith("-driver-agent-windows-amd64.exe") or trimmed.endswith("-driver-agent-windows-arm64.exe"): + if ( + trimmed.endswith("-driver-agent-windows-amd64.exe") + or trimmed.endswith("-driver-agent-windows-arm64.exe") + or trimmed.endswith("-driver-agent-v1-windows-amd64.exe") + or trimmed.endswith("-driver-agent-v1-windows-arm64.exe") + or trimmed.endswith("-driver-agent-v2-windows-amd64.exe") + or trimmed.endswith("-driver-agent-v2-windows-arm64.exe") + ): return f"Windows/{trimmed}" - if trimmed.endswith("-driver-agent-darwin-amd64") or trimmed.endswith("-driver-agent-darwin-arm64"): + if ( + trimmed.endswith("-driver-agent-darwin-amd64") + or trimmed.endswith("-driver-agent-darwin-arm64") + or trimmed.endswith("-driver-agent-v1-darwin-amd64") + or trimmed.endswith("-driver-agent-v1-darwin-arm64") + or trimmed.endswith("-driver-agent-v2-darwin-amd64") + or trimmed.endswith("-driver-agent-v2-darwin-arm64") + ): return f"MacOS/{trimmed}" - if trimmed.endswith("-driver-agent-linux-amd64"): + if ( + trimmed.endswith("-driver-agent-linux-amd64") + or trimmed.endswith("-driver-agent-v1-linux-amd64") + or trimmed.endswith("-driver-agent-v2-linux-amd64") + ): return f"Linux/{trimmed}" return None +def normalize_machine(value: str): + machine = str(value or "").strip().lower() + if machine in {"x86_64", "amd64"}: + return "amd64" + if machine in {"aarch64", "arm64"}: + return "arm64" + return machine + + +def current_runtime_platform(): + system = platform.system().lower() + if system == "darwin": + goos = "darwin" + elif system == "windows": + goos = "windows" + elif system == "linux": + goos = "linux" + else: + return "" + goarch = normalize_machine(platform.machine()) + if not goarch: + return "" + return f"{goos}/{goarch}" + + def sha256_file(path: Path): h = hashlib.sha256() with open(path, "rb") as fh: @@ -104,12 +158,16 @@ def probe_metadata_revision(path: Path): return str(((payload.get("data") or {}).get("agentRevision") or "")).strip() -def validate_release_assets(release: dict, manifest: dict): +def validate_release_assets(release: dict, manifest: dict, runtime_platform=None): assets = asset_map(release) manifest_assets = manifest.get("assets") or {} if not isinstance(manifest_assets, dict) or not manifest_assets: raise RuntimeError("manifest assets is empty") + if runtime_platform is None: + runtime_platform = current_runtime_platform() + runtime_platform = str(runtime_platform or "").strip().lower() + mismatches = [] skipped = [] with tempfile.TemporaryDirectory(prefix="gonavi-release-assets-") as tmp: @@ -124,20 +182,27 @@ def validate_release_assets(release: dict, manifest: dict): expected_sha = str(meta.get("sha256") or "").strip().lower() expected_revision = str(meta.get("revision") or "").strip() + asset_platform = str(meta.get("platform") or "").strip().lower() + + local_path = None + actual_sha = asset_sha256_digest(asset) + if expected_sha and not actual_sha: + local_path = tmp_root / name + download_url(str(asset.get("browser_download_url") or "").strip(), local_path) + actual_sha = sha256_file(local_path).lower() + if expected_sha and actual_sha != expected_sha: + mismatches.append((name, "sha256", actual_sha, expected_sha)) + continue + path_hint = infer_asset_path(name) if path_hint is None: skipped.append(name) continue - local_path = tmp_root / name - download_url(str(asset.get("browser_download_url") or "").strip(), local_path) - - actual_sha = sha256_file(local_path).lower() - if expected_sha and actual_sha != expected_sha: - mismatches.append((name, "sha256", actual_sha, expected_sha)) - continue - - if expected_revision: + if expected_revision and asset_platform == runtime_platform: + if local_path is None: + local_path = tmp_root / name + download_url(str(asset.get("browser_download_url") or "").strip(), local_path) actual_revision = probe_metadata_revision(local_path) if actual_revision != expected_revision: mismatches.append((name, "revision", actual_revision, expected_revision)) diff --git a/tools/validate-driver-release-assets.test.py b/tools/validate-driver-release-assets.test.py index 3b399ff..37ca4f1 100644 --- a/tools/validate-driver-release-assets.test.py +++ b/tools/validate-driver-release-assets.test.py @@ -24,6 +24,14 @@ class ValidateDriverReleaseAssetsTests(unittest.TestCase): MODULE.infer_asset_path("clickhouse-driver-agent-windows-arm64.exe"), "Windows/clickhouse-driver-agent-windows-arm64.exe", ) + self.assertEqual( + MODULE.infer_asset_path("mongodb-driver-agent-v1-windows-amd64.exe"), + "Windows/mongodb-driver-agent-v1-windows-amd64.exe", + ) + self.assertEqual( + MODULE.infer_asset_path("mongodb-driver-agent-v2-darwin-arm64"), + "MacOS/mongodb-driver-agent-v2-darwin-arm64", + ) self.assertEqual(MODULE.infer_asset_path("duckdb.dll"), "Windows/duckdb.dll") self.assertIsNone(MODULE.infer_asset_path("duckdb-driver.zip")) @@ -83,6 +91,7 @@ class ValidateDriverReleaseAssetsTests(unittest.TestCase): manifest = { "assets": { "clickhouse-driver-agent-darwin-arm64": { + "platform": "darwin/arm64", "revision": "src-expected", "sha256": hashlib.sha256(payload).hexdigest(), } @@ -101,7 +110,7 @@ class ValidateDriverReleaseAssetsTests(unittest.TestCase): try: MODULE.download_url = fake_download MODULE.probe_metadata_revision = lambda _path: "src-actual" - mismatches, skipped = MODULE.validate_release_assets(release, manifest) + mismatches, skipped = MODULE.validate_release_assets(release, manifest, runtime_platform="darwin/arm64") finally: MODULE.download_url = original_download MODULE.probe_metadata_revision = original_probe @@ -109,6 +118,84 @@ class ValidateDriverReleaseAssetsTests(unittest.TestCase): self.assertEqual(skipped, []) self.assertEqual(mismatches, [("clickhouse-driver-agent-darwin-arm64", "revision", "src-actual", "src-expected")]) + def test_validate_release_assets_skips_cross_platform_revision_probe(self): + release = { + "assets": [ + { + "name": "clickhouse-driver-agent-darwin-amd64", + "browser_download_url": "https://example.test/clickhouse-driver-agent-darwin-amd64", + } + ] + } + payload = b"darwin-binary" + manifest = { + "assets": { + "clickhouse-driver-agent-darwin-amd64": { + "platform": "darwin/amd64", + "revision": "src-expected", + "sha256": hashlib.sha256(payload).hexdigest(), + } + } + } + + with tempfile.TemporaryDirectory(prefix="gonavi-validate-release-assets-") as tmp: + payload_path = pathlib.Path(tmp) / "clickhouse-driver-agent-darwin-amd64" + payload_path.write_bytes(payload) + + def fake_download(url, destination): + destination.write_bytes(payload_path.read_bytes()) + + def fail_probe(_path): + raise AssertionError("cross-platform asset should not be executed") + + original_download = MODULE.download_url + original_probe = MODULE.probe_metadata_revision + try: + MODULE.download_url = fake_download + MODULE.probe_metadata_revision = fail_probe + mismatches, skipped = MODULE.validate_release_assets(release, manifest, runtime_platform="linux/amd64") + finally: + MODULE.download_url = original_download + MODULE.probe_metadata_revision = original_probe + + self.assertEqual(mismatches, []) + self.assertEqual(skipped, []) + + def test_validate_release_assets_uses_release_digest_without_download(self): + payload = b"darwin-binary" + digest = hashlib.sha256(payload).hexdigest() + release = { + "assets": [ + { + "name": "clickhouse-driver-agent-darwin-amd64", + "digest": f"sha256:{digest}", + "browser_download_url": "https://example.test/clickhouse-driver-agent-darwin-amd64", + } + ] + } + manifest = { + "assets": { + "clickhouse-driver-agent-darwin-amd64": { + "platform": "darwin/amd64", + "revision": "src-expected", + "sha256": digest, + } + } + } + + def fail_download(_url, _destination): + raise AssertionError("release digest should avoid downloading cross-platform assets") + + original_download = MODULE.download_url + try: + MODULE.download_url = fail_download + mismatches, skipped = MODULE.validate_release_assets(release, manifest, runtime_platform="linux/amd64") + finally: + MODULE.download_url = original_download + + self.assertEqual(mismatches, []) + self.assertEqual(skipped, []) + if __name__ == "__main__": unittest.main() diff --git a/tools/verify-driver-agent-revisions.sh b/tools/verify-driver-agent-revisions.sh index 7069b2c..b723661 100755 --- a/tools/verify-driver-agent-revisions.sh +++ b/tools/verify-driver-agent-revisions.sh @@ -101,7 +101,11 @@ expected_revision_for() { build_tags_for_driver() { local driver="$1" + local variant="${2:-}" local tags="gonavi_${driver}_driver" + if [[ "$driver" == "mongodb" && "$variant" == "v1" ]]; then + tags="gonavi_mongodb_driver_v1" + fi if [[ "$driver" == "duckdb" && "$host_goos" == "windows" && "$host_goarch" == "amd64" ]]; then tags="${tags} duckdb_use_lib" fi @@ -110,15 +114,29 @@ build_tags_for_driver() { agent_path_for() { local driver="$1" + local variant="${2:-}" local public_name asset public_name="$(public_driver_name "$driver")" - asset="${public_name}-driver-agent-${goos}-${goarch}" + if [[ "$driver" == "mongodb" && -n "$variant" ]]; then + asset="${public_name}-driver-agent-${variant}-${goos}-${goarch}" + else + asset="${public_name}-driver-agent-${goos}-${goarch}" + fi if [[ "$goos" == "windows" ]]; then asset="${asset}.exe" fi printf '%s\n' "${assets_dir%/}/${platform_dir}/${asset}" } +agent_variants_for() { + local driver="$1" + if [[ "$driver" == "mongodb" ]]; then + printf '%s\n' "v1" "v2" "" + return + fi + printf '%s\n' "" +} + probe_agent_revision() { local agent_path="$1" local request @@ -136,8 +154,9 @@ print(data.get("agentRevision", "")) probe_host_agent_revision() { local driver="$1" + local variant="${2:-}" local build_tags probe_dir probe_path revision - build_tags="$(build_tags_for_driver "$driver")" + build_tags="$(build_tags_for_driver "$driver" "$variant")" probe_dir="$(mktemp -d)" probe_path="${probe_dir}/probe-agent" if [[ "$host_goos" == "windows" ]]; then @@ -241,36 +260,42 @@ for raw_driver in "${raw_drivers[@]}"; do continue fi - agent_path="$(agent_path_for "$driver")" - if [[ ! -f "$agent_path" ]]; then - echo "❌ $driver 缺少 driver-agent 资产:$agent_path" - failed=1 - continue - fi - chmod +x "$agent_path" 2>/dev/null || true - - if [[ "$goos" == "windows" ]]; then - if ! validate_windows_pe_machine "$agent_path" "$goarch"; then - echo "❌ $driver Windows driver-agent 架构校验失败:asset=$agent_path target=$target_platform" + while IFS= read -r variant; do + agent_path="$(agent_path_for "$driver" "$variant")" + variant_label="$driver" + if [[ -n "$variant" ]]; then + variant_label="${driver}-${variant}" + fi + if [[ ! -f "$agent_path" ]]; then + echo "❌ $variant_label 缺少 driver-agent 资产:$agent_path" failed=1 continue fi - fi + chmod +x "$agent_path" 2>/dev/null || true - actual="" - if can_execute_target_binary; then - actual="$(probe_agent_revision "$agent_path" || true)" - else - echo "ℹ️ runner 平台 ${host_platform} 无法直接执行目标二进制 ${target_platform},已先完成目标资产架构校验,再用 host-native probe 校验相同 build tags 的 revision" - actual="$(probe_host_agent_revision "$driver" || true)" - fi + if [[ "$goos" == "windows" ]]; then + if ! validate_windows_pe_machine "$agent_path" "$goarch"; then + echo "❌ $variant_label Windows driver-agent 架构校验失败:asset=$agent_path target=$target_platform" + failed=1 + continue + fi + fi - if [[ "$actual" != "$expected" ]]; then - echo "❌ $driver driver-agent revision 不匹配:asset=$agent_path actual=${actual:-空} expected=$expected" - failed=1 - continue - fi - echo "✅ $driver driver-agent revision 校验通过:$actual" + actual="" + if can_execute_target_binary; then + actual="$(probe_agent_revision "$agent_path" || true)" + else + echo "ℹ️ runner 平台 ${host_platform} 无法直接执行目标二进制 ${target_platform},已先完成目标资产架构校验,再用 host-native probe 校验相同 build tags 的 revision" + actual="$(probe_host_agent_revision "$driver" "$variant" || true)" + fi + + if [[ "$actual" != "$expected" ]]; then + echo "❌ $variant_label driver-agent revision 不匹配:asset=$agent_path actual=${actual:-空} expected=$expected" + failed=1 + continue + fi + echo "✅ $variant_label driver-agent revision 校验通过:$actual" + done < <(agent_variants_for "$driver") done exit "$failed"