Files
MyGoNavi/tools/complete-driver-release-assets.py
Syngnat d791303967 ️ perf(ci): 优化 DriverAgents 按需构建流程
- 增加 driver-agent 变更检测任务
- 跳过未变更 driver 的构建与 revision 生成
- 复用前端构建产物,减少矩阵任务重复耗时
2026-05-16 13:38:00 +08:00

219 lines
6.4 KiB
Python
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 python3
import argparse
import json
import os
import shutil
import sys
import tempfile
import urllib.error
import urllib.parse
import urllib.request
import zipfile
from pathlib import Path
DRIVERS = [
"mariadb",
"oceanbase",
"doris",
"starrocks",
"sphinx",
"sqlserver",
"sqlite",
"duckdb",
"dameng",
"kingbase",
"highgo",
"vastbase",
"opengauss",
"mongodb",
"tdengine",
"clickhouse",
]
BUNDLE_NAME = "GoNavi-DriverAgents.zip"
def required_assets():
assets = []
for driver in DRIVERS:
assets.extend(
[
("Windows", f"{driver}-driver-agent-windows-amd64.exe"),
("MacOS", f"{driver}-driver-agent-darwin-amd64"),
("MacOS", f"{driver}-driver-agent-darwin-arm64"),
("Linux", f"{driver}-driver-agent-linux-amd64"),
]
)
if driver == "duckdb":
assets.append(("Windows", "duckdb.dll"))
else:
assets.append(("Windows", f"{driver}-driver-agent-windows-arm64.exe"))
return assets
def github_headers():
headers = {
"Accept": "application/vnd.github+json",
"User-Agent": "GoNavi-CI",
}
token = os.environ.get("DRIVER_RELEASE_TOKEN") or os.environ.get("GITHUB_TOKEN")
if token:
headers["Authorization"] = f"Bearer {token}"
return headers
def fetch_json(url):
req = urllib.request.Request(url, headers=github_headers())
with urllib.request.urlopen(req, timeout=30) as resp:
return json.loads(resp.read().decode("utf-8"))
def download_asset(asset, destination):
headers = github_headers()
headers["Accept"] = "application/octet-stream"
req = urllib.request.Request(asset["url"], headers=headers)
with urllib.request.urlopen(req, timeout=120) as resp:
with open(destination, "wb") as out:
shutil.copyfileobj(resp, out)
def load_release(repo, source):
owner_repo = repo.strip()
if not owner_repo:
raise ValueError("repo is required")
if source == "latest":
api_url = f"https://api.github.com/repos/{owner_repo}/releases/latest"
else:
api_url = f"https://api.github.com/repos/{owner_repo}/releases/tags/{urllib.parse.quote(source, safe='')}"
try:
return fetch_json(api_url)
except urllib.error.HTTPError as exc:
if exc.code == 404:
print(f"未找到上一版 driver release{source}", file=sys.stderr)
return None
raise
def asset_map(release):
result = {}
for asset in release.get("assets", []):
name = str(asset.get("name", "")).strip()
if name:
result[name] = asset
return result
def copy_missing_from_bundle(bundle_path, target_root):
copied = 0
required = {
(Path(platform) / file_name).as_posix()
for platform, file_name in required_assets()
}
with zipfile.ZipFile(bundle_path) as zf:
members = {
Path(info.filename).as_posix(): info
for info in zf.infolist()
if not info.is_dir()
}
for item in sorted(required):
source = members.get(item)
if not source:
continue
relative = Path(item)
target = target_root / relative
if target.exists():
continue
target.parent.mkdir(parents=True, exist_ok=True)
with zf.open(source) as src, open(target, "wb") as out:
shutil.copyfileobj(src, out)
copied += 1
return copied
def copy_missing_standalone(release_assets, target_root):
copied = 0
with tempfile.TemporaryDirectory(prefix="gonavi-driver-assets-") as tmp:
tmp_root = Path(tmp)
for platform, file_name in required_assets():
target = target_root / platform / file_name
if target.exists():
continue
asset = release_assets.get(file_name)
if not asset:
continue
temp_path = tmp_root / file_name
download_asset(asset, temp_path)
target.parent.mkdir(parents=True, exist_ok=True)
shutil.copy2(temp_path, target)
copied += 1
return copied
def verify_complete(target_root):
missing = []
for platform, file_name in required_assets():
if not (target_root / platform / file_name).is_file():
missing.append(f"{platform}/{file_name}")
return missing
def main():
parser = argparse.ArgumentParser()
parser.add_argument("--assets-dir", required=True)
parser.add_argument("--repo", default="Syngnat/GoNavi-DriverAgents")
parser.add_argument(
"--source",
action="append",
required=True,
help="latest or release tag such as dev-latest/v1.0.0; may be passed more than once",
)
parser.add_argument("--require-complete", action="store_true")
args = parser.parse_args()
assets_dir = Path(args.assets_dir)
driver_root = assets_dir / "drivers"
driver_root.mkdir(parents=True, exist_ok=True)
found_source = False
total_copied = 0
for source in args.source:
release = load_release(args.repo, source)
if release is None:
continue
found_source = True
releases_assets = asset_map(release)
copied = 0
bundle_asset = releases_assets.get(BUNDLE_NAME)
if bundle_asset:
with tempfile.TemporaryDirectory(prefix="gonavi-driver-release-") as tmp:
bundle_path = Path(tmp) / BUNDLE_NAME
download_asset(bundle_asset, bundle_path)
copied += copy_missing_from_bundle(bundle_path, driver_root)
copied += copy_missing_standalone(releases_assets, driver_root)
total_copied += copied
print(f"已从 {source} 补齐 driver assets{copied} 个文件")
if not verify_complete(driver_root):
break
if not found_source:
print("未找到可补齐的上一版 driver release。", file=sys.stderr)
else:
print(f"driver assets 合计补齐:{total_copied} 个文件")
if args.require_complete:
missing = verify_complete(driver_root)
if missing:
print("driver assets 不完整:", file=sys.stderr)
for item in missing:
print(f" - {item}", file=sys.stderr)
return 1
return 0
if __name__ == "__main__":
raise SystemExit(main())