Files
ViperOS/build.py
T
TermiNexus 59ba295532 feat(build): build.py 支持 --onlyrun(跳过编译/打包,复用现有 disk.img 反复跑 QEMU)
为反复复现偶发写坏提供快速通道:脚本拆成 BuildKernel / BuildApps / BuildBootloader /
BuildDisk 与 RunQemu 五段,--onlyrun 时只执行 RunQemu(清空 serial.log → 启动 QEMU →
打印串口日志),不再重编内核/应用/引导器、也不再重新打包 disk.img。行为与原来完全一致,
只是把“构建段”与“运行段”分开,便于跳过前者。用法可直接循环:python build.py --onlyrun
2026-10-14 15:01:30 +08:00

207 lines
8.9 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.
import shutil
import os
import re
import sys
import subprocess
# 解析参数
clean = '--noclean' not in sys.argv
kernel_only = '--kernel-only' in sys.argv
noapp = '--noapp' in sys.argv
bootloader = '--bootloader' in sys.argv
# --onlyrun:跳过全部编译/打包步骤,只用现有 disk.img 反复启动 QEMU 复现问题。
# 典型用法:python build.py --onlyrun(连跑多次,观察偶发写坏是否复现并留下 [FATAL])
only_run = '--onlyrun' in sys.argv
def BuildKernel():
"""编译内核 + 产出带符号 kernel.elf(仅供分析)"""
cmd = r'..\..\ViperC\output\viperc.exe --project ./VKernel/project.vpj --clear-cache --rebuild-includes'
if clean:
cmd += ' --clean'
os.system(cmd)
shutil.copy("VKernel/__vpcbuild__/output/kernel.bin", "./disk/sys/kernel/x86_64/kernel.bin")
# 额外产出带符号的 kernel.elf(仅供分析:objdump / nm / readelf 定位崩溃 rip)。
# kernel.bin 是 --oformat binary --strip-all 的裸镜像、无符号表,不便把日志里的
# rip 映射回函数。要点:**对象顺序决定段内布局**,自行 glob 一组 .obj 去链会得到
# 与 kernel.bin 不同的地址(实测前 247KB 里 230KB 不同),没法对上日志。
# 因此用 viperc 自己的 --phase2command:它把 llc/链接命令按“精确顺序”写进 .bat,
# 我们只把该链接行里的 --oformat binary/--strip-all 去掉、输出改为 kernel.elf 再执行。
kernel_elf = "VKernel/__vpcbuild__/output/kernel.elf"
p2c_bat = "VKernel/__vpcbuild__/phase2cmd.bat"
os.system(r'..\..\ViperC\output\viperc.exe --project ./VKernel/project.vpj --phase2command ' + p2c_bat)
link_line = None
if os.path.exists(p2c_bat):
with open(p2c_bat, "r", encoding="utf-8", errors="replace") as f:
for line in f:
low = line.lower()
if "ld.lld" in low and (".bin" in low or "output" in low):
link_line = line.rstrip("\r\n")
if link_line is not None:
# 稳健改写:--oformat 可能是 "--oformat binary" 或 "--oformat=binary";输出可能是
# "-o <path>" 或 "--output=<path>"。任一没匹配上就放弃(绝不去执行原样命令,否则会把
# kernel.bin 覆盖成非预期产物)。
elf_line = re.sub(r"--oformat[ =]binary", "", link_line)
elf_line = re.sub(r"--strip-all", "", elf_line)
elf_line = re.sub(r"(-o\s+)\S+", r"\1" + kernel_elf, elf_line)
elf_line = re.sub(r"(--output=)\S+", r"\1" + kernel_elf, elf_line)
if elf_line == link_line or kernel_elf not in elf_line:
print("[build] 警告: 未能改写链接命令,跳过 kernel.elf(避免误覆盖 kernel.bin)")
else:
print("[build] linking kernel.elf for analysis ...")
if os.system(elf_line) != 0:
print("[build] 警告: kernel.elf 链接失败(不影响 kernel.bin)")
else:
print("[build] 警告: 未从 phase2command 取到链接命令,跳过 kernel.elf")
def BuildApps():
"""编译库与应用,并拷贝到 disk/"""
# --noapp 优先级高于 --kernel-only:只编译内核,不编译所有库和应用
if noapp:
print("[build] --noapp: 跳过所有库与应用编译,复用已有缓存文件")
# 检查必要文件是否存在
required_libs = [
("Libs/SerialLogger/__vpcbuild__/output/slog.so", "./disk/libs/slog.so"),
("Libs/VPUI64/__vpcbuild__/output/vpui64.so", "./disk/libs/vpui64.so"),
]
required_apps = [
("Apps/HelloWorld/__vpcbuild__/output/helloworld.elf", "./disk/apps/hello.elf"),
("Apps/Terminal/__vpcbuild__/output/terminal.elf", "./disk/apps/terminal.elf"),
("Apps/Gargantua/__vpcbuild__/output/gargantua.elf", "./disk/apps/gargantua.elf"),
("Apps/Scene3D/__vpcbuild__/output/scene3d.elf", "./disk/apps/scene3d.elf"),
]
os.makedirs("./disk/apps", exist_ok=True)
os.makedirs("./disk/libs", exist_ok=True)
for src, dst in required_libs + required_apps:
if os.path.exists(src):
shutil.copy(src, dst)
else:
print(f"[build] 警告: {src} 不存在,请先完整构建一次")
elif kernel_only:
# 仅 --kernel-only:只跳过应用,不跳过库(原有逻辑保留)
print("[build] --kernel-only: 跳过其他应用和库的编译")
if not os.path.exists("./disk/apps/hello.elf"):
print("[build] 警告: hello.elf 不存在,请先不带 --kernel-only 完整构建一次")
else:
# 完整编译所有库+应用
so_cmd = r'..\..\ViperC\output\viperc.exe --project Libs/SerialLogger/project.vpj --clear-cache'
if clean:
so_cmd += ' --clean'
os.system(so_cmd)
vpui_cmd = r'..\..\ViperC\output\viperc.exe --project Libs/VPUI64/project.vpj --clear-cache'
if clean:
vpui_cmd += ' --clean'
os.system(vpui_cmd)
app_cmd = r'..\..\ViperC\output\viperc.exe --project Apps/HelloWorld/project.vpj --clear-cache'
if clean:
app_cmd += ' --clean'
os.system(app_cmd)
os.makedirs("./disk/apps", exist_ok=True)
shutil.copy("Apps/HelloWorld/__vpcbuild__/output/helloworld.elf", "./disk/apps/hello.elf")
term_cmd = r'..\..\ViperC\output\viperc.exe --project Apps/Terminal/project.vpj --clear-cache'
if clean:
term_cmd += ' --clean'
os.system(term_cmd)
shutil.copy("Apps/Terminal/__vpcbuild__/output/terminal.elf", "./disk/apps/terminal.elf")
garg_cmd = r'..\..\ViperC\output\viperc.exe --project Apps/Gargantua/project.vpj --clear-cache'
if clean:
garg_cmd += ' --clean'
os.system(garg_cmd)
shutil.copy("Apps/Gargantua/__vpcbuild__/output/gargantua.elf", "./disk/apps/gargantua.elf")
scene3d_cmd = r'..\..\ViperC\output\viperc.exe --project Apps/Scene3D/project.vpj --clear-cache'
if clean:
scene3d_cmd += ' --clean'
os.system(scene3d_cmd)
shutil.copy("Apps/Scene3D/__vpcbuild__/output/scene3d.elf", "./disk/apps/scene3d.elf")
os.makedirs("./disk/libs", exist_ok=True)
so_path = os.path.abspath("Libs/SerialLogger/__vpcbuild__/output/slog.so")
vpui_path = os.path.abspath("Libs/VPUI64/__vpcbuild__/output/vpui64.so")
if os.path.exists(so_path):
shutil.copy(so_path, "./disk/libs/slog.so")
if os.path.exists(vpui_path):
shutil.copy(vpui_path, "./disk/libs/vpui64.so")
def BuildBootloader():
"""编译 bootloader(GNU-EFI:MSYS make → WSL Ubuntu);
bootx64.efi 不由 viperc 产出,容易忘记重编而一直用旧引导器(改了 boot.c / 内核预留逻辑
却仍在用旧件)。显式传 --bootloader 才重编;未传时若 boot.c 比产物新则给出提醒。"""
boot_src = "boot/boot.c"
boot_out = "disk/EFI/BOOT/bootx64.efi"
if bootloader:
print("[build] building bootloader (make -C boot)...")
boot_rc = os.system('make -C boot')
if boot_rc != 0:
print("[build] 警告: bootloader 构建失败(需 MSYS make + WSL Ubuntu + _UNCADK/tools/gnu-efi)")
elif os.path.exists(boot_src) and os.path.exists(boot_out) and os.path.getmtime(boot_src) > os.path.getmtime(boot_out):
print("[build] 警告: boot/boot.c 比 disk/EFI/BOOT/bootx64.efi 新 → bootloader 已过期,请用 --bootloader 重编")
def BuildDisk():
"""把 disk/ 目录内容打包成 disk.img"""
os.system('powershell -ExecutionPolicy Bypass -File ./Scripts/disk.ps1')
def RunQemu():
"""清空 serial.log 并启动 QEMU,退出后打印串口日志"""
serial_log = os.path.abspath("serial.log")
if os.path.exists(serial_log):
os.remove(serial_log)
print(f"[build] CWD: {os.getcwd()}")
print(f"[build] serial_log: {serial_log}")
qemu_cmd = [
'qemu-system-x86_64',
'-pflash', r'D:\msys64\mingw64\share\qemu\edk2-x86_64-code.fd',
'-drive', 'file=disk.img,format=raw,if=ide',
'-m', '256m',
'-vga', 'std',
'-usb',
'-device', 'usb-kbd',
'-device', 'usb-mouse',
'-accel', 'tcg,tb-size=128',
'-serial', f'file:{serial_log}',
'-no-reboot'
]
print(f"[build] QEMU cmd: {' '.join(qemu_cmd)}")
proc = subprocess.Popen(qemu_cmd)
proc.wait()
print(f"[build] serial_log path: {serial_log}")
print(f"[build] file exists: {os.path.exists(serial_log)}")
if os.path.exists(serial_log):
with open(serial_log, "r", encoding="utf-8", errors="replace") as f:
content = f.read()
print(f"[build] serial_log size: {len(content)} bytes")
print(content)
else:
print("[build] serial.log not found!")
if only_run:
print("[build] --onlyrun: 跳过编译/打包,直接用现有 disk.img 启动 QEMU")
else:
BuildKernel()
BuildApps()
BuildBootloader()
BuildDisk()
RunQemu()