gf_ai_box/build_and_serve.py
2026-04-12 09:40:04 +00:00

303 lines
11 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
"""
build_and_serve.py — KL630 一鍵編譯 + 檢查 + HTTP server
使用方式:
python build_and_serve.py # 編譯 + 檢查 + 啟動 server
python build_and_serve.py --no-build # 跳過編譯,只啟動 server
python build_and_serve.py --port 9090
裝置端:
wget http://192.168.3.1:<port>/deploy.sh -O /tmp/deploy.sh && sh /tmp/deploy.sh
"""
import sys
import os
import struct
import shutil
import argparse
import subprocess
from pathlib import Path
from http.server import HTTPServer, SimpleHTTPRequestHandler
from threading import Thread
# ── ANSI colors ─────────────────────────────────────────────────────────────
def _c(code, text): return f"\033[{code}m{text}\033[0m" if sys.stdout.isatty() else text
def ok(t): return _c("32;1", t)
def fail(t): return _c("31;1", t)
def info(t): return _c("36", t)
def warn(t): return _c("33", t)
def head(t): return _c("35;1", t)
# ── Paths ────────────────────────────────────────────────────────────────────
SCRIPT_DIR = Path(__file__).resolve().parent
BUILD_DIR = SCRIPT_DIR / "build"
BINARY_NAME = "kp_firmware_host_stream"
BINARY_PATH = BUILD_DIR / BINARY_NAME
INI_SRC = SCRIPT_DIR / "ini" / "host_stream.ini"
DEPLOY_SRC = SCRIPT_DIR / "tools" / "device" / "deploy.sh"
DEMO_RTSP_SRC = SCRIPT_DIR / "tools" / "device" / "demo_rtsp.sh"
# Docker image name — change if yours is different
DOCKER_IMAGE = "kl630-dev"
# ── Step 0: Ensure Docker image exists ───────────────────────────────────────
def step_ensure_image() -> bool:
"""Build the Docker image from Dockerfile if it doesn't exist yet."""
print(head("\n=== [0/3] Docker Image Check ==="))
if shutil.which("docker") is None:
print(fail(" ERROR: 'docker' not found in PATH"))
return False
# Check if image already exists
r = subprocess.run(
["docker", "image", "inspect", DOCKER_IMAGE],
capture_output=True
)
if r.returncode == 0:
print(ok(f" Image '{DOCKER_IMAGE}' already exists, skipping build."))
return True
# Image not found — build it from Dockerfile
dockerfile = SCRIPT_DIR / "Dockerfile"
if not dockerfile.exists():
print(fail(f" ERROR: Dockerfile not found at {dockerfile}"))
return False
print(warn(f" Image '{DOCKER_IMAGE}' not found. Building from Dockerfile..."))
print(info(f" docker build -t {DOCKER_IMAGE} {SCRIPT_DIR}"))
print()
result = subprocess.run(
["docker", "build", "-t", DOCKER_IMAGE, str(SCRIPT_DIR)]
)
if result.returncode != 0:
print(fail(f"\n IMAGE BUILD FAILED (exit {result.returncode})"))
return False
print(ok(f"\n Image '{DOCKER_IMAGE}' built successfully."))
return True
# ── Step 1: Docker build ──────────────────────────────────────────────────────
def step_build() -> bool:
print(head("\n=== [1/3] Docker Build ==="))
# Check docker available
if shutil.which("docker") is None:
print(fail(" ERROR: 'docker' not found in PATH"))
return False
# Mount the full project directory — SDK/lib/include/src all come from here.
mount = str(SCRIPT_DIR).replace("\\", "/")
cmd = [
"docker", "run", "--rm",
"-v", f"{mount}:/workspace/kl630_build",
DOCKER_IMAGE,
"bash", "/workspace/kl630_build/compile.sh"
]
print(info(f" Image : {DOCKER_IMAGE}"))
print(info(f" Mount : {mount} -> /workspace/kl630_build"))
print()
result = subprocess.run(cmd)
if result.returncode != 0:
print(fail(f"\n BUILD FAILED (exit {result.returncode})"))
return False
print(ok("\n Build succeeded."))
return True
# ── Step 2: Check binary ──────────────────────────────────────────────────────
def _check_elf_arm(path: Path):
"""Return (is_arm_elf, description)."""
try:
with open(path, "rb") as f:
hdr = f.read(20)
if len(hdr) < 20:
return False, "file too small"
if hdr[:4] != b"\x7fELF":
return False, "not an ELF file"
e_machine = struct.unpack_from("<H", hdr, 18)[0]
arch_map = {0x28: "ARM (armv7)", 0xb7: "AArch64", 0x3e: "x86_64"}
arch = arch_map.get(e_machine, f"unknown machine 0x{e_machine:04x}")
return e_machine == 0x28, arch
except Exception as e:
return False, str(e)
def step_check() -> bool:
print(head("\n=== [2/3] Check Binary ==="))
if not BINARY_PATH.exists():
print(fail(f" FAIL: {BINARY_PATH} not found"))
return False
size = BINARY_PATH.stat().st_size
is_arm, arch = _check_elf_arm(BINARY_PATH)
size_ok = size > 100_000
arch_ok = is_arm
print(f" File : {BINARY_PATH}")
print(f" Size : {size/1024:.1f} KB {'' if size_ok else ''}")
print(f" Arch : {arch} {'' if arch_ok else ''}")
# Try readelf if available (in Docker or WSL)
if shutil.which("readelf"):
r = subprocess.run(
["readelf", "-d", str(BINARY_PATH)],
capture_output=True, text=True
)
needed = [l.split("(NEEDED)")[1].strip() for l in r.stdout.splitlines() if "(NEEDED)" in l]
if needed:
print(f" NEEDED: {', '.join(needed)}")
if "libc.so.0" in needed:
print(ok(" uClibc patch: OK (libc.so.0 present)"))
elif "libc.so.6" in needed:
print(fail(" uClibc patch: MISSING (libc.so.6 still present)"))
if not (size_ok and arch_ok):
print(fail("\n CHECK FAILED"))
return False
print(ok("\n Binary looks good."))
return True
# ── Step 3: Copy Binary To Network Share ──────────────────────
def step_copy_to_network(dst_dir: str) -> bool:
print(head("\n=== [copy] Copy Binary To Network Share ==="))
if not BINARY_PATH.exists():
print(fail(f" FAIL: source binary not found: {BINARY_PATH}"))
return False
try:
dst_path = Path(dst_dir)
dst_path.mkdir(parents=True, exist_ok=True)
dst_file = dst_path / BINARY_NAME
shutil.copy2(BINARY_PATH, dst_file)
print(ok(f" Copied: {BINARY_PATH}"))
print(ok(f" -> {dst_file}"))
return True
except Exception as e:
print(fail(f" COPY FAILED: {e}"))
return False
# ── Step 4: Prepare serve directory & start HTTP server ──────────────────────
def step_serve(port: int):
print(head("\n=== [3/3] Prepare & Serve ==="))
BUILD_DIR.mkdir(exist_ok=True)
# Copy INI
if INI_SRC.exists():
dst = BUILD_DIR / "host_stream.ini"
shutil.copy2(INI_SRC, dst)
print(ok(f" Copied : {INI_SRC.name} ({dst.stat().st_size} bytes)"))
else:
print(warn(f" WARN: {INI_SRC} not found, skipping"))
# Copy deploy.sh
if DEPLOY_SRC.exists():
dst = BUILD_DIR / "deploy.sh"
shutil.copy2(DEPLOY_SRC, dst)
print(ok(f" Copied : deploy.sh"))
else:
print(warn(f" WARN: {DEPLOY_SRC} not found, skipping"))
# Copy demo_rtsp.sh
if DEMO_RTSP_SRC.exists():
dst = BUILD_DIR / "demo_rtsp.sh"
shutil.copy2(DEMO_RTSP_SRC, dst)
print(ok(f" Copied : demo_rtsp.sh"))
else:
print(warn(f" WARN: {DEMO_RTSP_SRC} not found, skipping"))
# List what will be served
print(f"\n Serving from: {BUILD_DIR}")
for f in sorted(BUILD_DIR.iterdir()):
size_str = f"{f.stat().st_size/1024:.1f} KB"
print(f" {f.name:<40} {size_str:>10}")
# Start server
os.chdir(BUILD_DIR)
class _Handler(SimpleHTTPRequestHandler):
def log_message(self, fmt, *args):
# Show only non-304 responses
code = args[1] if len(args) > 1 else ""
if code != "304":
print(info(f" [{self.address_string()}] {fmt % args}"))
httpd = HTTPServer(("", port), _Handler)
print(head(f"\n=== HTTP Server on port {port} ==="))
print(f" URL : http://0.0.0.0:{port}/")
print()
print(warn(" 裝置端執行:"))
print(f" wget http://192.168.3.1:{port}/deploy.sh -O /tmp/deploy.sh && sh /tmp/deploy.sh")
print()
print(warn(" 或分步驟:"))
print(f" wget http://192.168.3.1:{port}/{BINARY_NAME} -O /mnt/flash/vienna/{BINARY_NAME}")
print(f" wget http://192.168.3.1:{port}/host_stream.ini -O $BIN_DIR/ini/host_stream.ini")
print()
print(" Ctrl+C 停止\n")
try:
httpd.serve_forever()
except KeyboardInterrupt:
print(info("\n Server stopped."))
# ── Main ─────────────────────────────────────────────────────────────────────
def main():
global DOCKER_IMAGE
parser = argparse.ArgumentParser(description="KL630 build + check + serve")
parser.add_argument("--no-build", action="store_true",
help="跳過 Docker build只 check + serve")
parser.add_argument("--no-check", action="store_true",
help="跳過 binary 檢查")
parser.add_argument("--copy-dst",default=r"\\192.168.0.122\public\nfs_share",
help="編譯成功後複製 binary 到指定網路資料夾")
parser.add_argument("--no-copy",action="store_true",
help="不要複製 binary 到網路資料夾")
parser.add_argument("--no-serve", action="store_true",
help="只 build + check不啟動 HTTP server")
parser.add_argument("--port", type=int, default=8080,
help="HTTP server port (default: 8080)")
parser.add_argument("--image", default=DOCKER_IMAGE,
help=f"Docker image name (default: {DOCKER_IMAGE})")
args = parser.parse_args()
DOCKER_IMAGE = args.image
print(head("KL630 Build & Serve"))
print(f" Root : {SCRIPT_DIR}")
print(f" Binary : {BINARY_PATH}")
print(f" Port : {args.port}")
print(f" Copy : {args.copy_dst}")
# ===== Step 1: Build =====
if not args.no_build:
if not step_ensure_image():
sys.exit(1)
if not step_build():
sys.exit(1)
# ===== Step 2: Check =====
if not args.no_check:
if not step_check():
if args.no_build:
print(warn(" (使用 --no-build 時 binary 可能是舊的)"))
else:
sys.exit(1)
# ===== Step 3: Copy重點=====
if not args.no_copy:
if not step_copy_to_network(args.copy_dst):
sys.exit(1)
# ===== Step 4: Serve =====
if not args.no_serve:
step_serve(args.port)
if __name__ == "__main__":
main()