Files
ViperOS/build.py
T
TermiNexus 465ef4a910 feat(build): 新增 --app=a,b 只编指定应用;默认不重编内核
默认行为改为**不重编内核**(多数迭代只改应用/库),需要时显式加 --kernel;--app=hello,explorer 只编译列出的应用(逗号分隔),不带 --app 仍编译全部应用;新增 --libs 显式编译 Libs 下的 .so(带 --app 时默认跳过库,要一起编就加 --libs);应用/库编译改为表驱动(APP_LIST/LIB_LIST),--noapp 的拷贝逻辑抽到 CopyExisting()。
2026-10-16 22:41:28 +08:00

219 lines
8.8 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 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
only_run = '--onlyrun' in sys.argv
# --onlyapp:只编译库与应用(保留兼容;现在这已是默认行为)
only_app = '--onlyapp' in sys.argv
# --kernel:重编内核。**默认不重编内核**(多数迭代只改应用/库),显式传才编。
build_kernel = '--kernel' in sys.argv or kernel_only
# --libs:编译 Libs/ 下的动态库(不带 --app 时默认也会编)
libs = '--libs' in sys.argv
# --app=a,b,c:只编译列出的应用(逗号分隔、可多个)。不传则编译全部应用。
app_filter = None
for _arg in sys.argv:
if _arg.startswith('--app='):
app_filter = [x.strip().lower() for x in _arg[len('--app='):].split(',') if x.strip()]
def BuildKernel():
"""编译内核:链接器一次产出带符号 kernel.elf,再由 objcopy 二次处理成裸镜像 kernel.bin"""
cmd = r'..\..\ViperC\output\viperc.exe --project ./VKernel/project.vpj --clear-cache --rebuild-includes'
if clean:
cmd += ' --clean'
os.system(cmd)
# project.vpj 的 linker 已改为直接产出 ELF(保留符号表,去掉 --oformat binary/--strip-all),
# kernel.bin 由同一份 ELF 二次处理得到 —— 两者布局逐字节一致,日志里的 rip 可直接用
# objdump / nm / readelf 落到函数名,不必再猜地址(此前自行 glob .obj 去链会得到不同布局)。
# --set-section-flags .bss=alloc,load,contents:把 .bss 一并写进裸镜像(ELF 里 .bss 默认
# NOBITS、不占文件空间),这样 bootloader 按“文件大小”预留就能覆盖 .bss。
kernel_elf = "VKernel/__vpcbuild__/output/kernel.elf"
kernel_bin = "VKernel/__vpcbuild__/output/kernel.bin"
rc = os.system(f'llvm-objcopy.exe -O binary --set-section-flags .bss=alloc,load,contents "{kernel_elf}" "{kernel_bin}"')
if rc != 0:
print("[build] 警告: objcopy 生成 kernel.bin 失败")
shutil.copy(kernel_bin, "./disk/sys/kernel/x86_64/kernel.bin")
# 应用表:(--app 的筛选名, 工程目录, 产物文件名, disk 下的目标文件名)
APP_LIST = [
("hello", "Apps/HelloWorld", "helloworld.elf", "hello.elf"),
("terminal", "Apps/Terminal", "terminal.elf", "terminal.elf"),
("gargantua", "Apps/Gargantua", "gargantua.elf", "gargantua.elf"),
("scene3d", "Apps/Scene3D", "scene3d.elf", "scene3d.elf"),
("explorer", "Apps/Explorer", "explorer.elf", "explorer.elf"),
]
# 动态库表:(工程目录, 产物文件名)
LIB_LIST = [
("Libs/SerialLogger", "slog.so"),
("Libs/VPUI64", "vpui64.so"),
]
def BuildLibs():
"""编译 Libs/ 下的动态库(应用运行期经 LOAD_SO 加载)。"""
os.makedirs("./disk/libs", exist_ok=True)
for proj, out in LIB_LIST:
cmd = rf'..\..\ViperC\output\viperc.exe --project {proj}/project.vpj --clear-cache'
if clean:
cmd += ' --clean'
os.system(cmd)
src = f"{proj}/__vpcbuild__/output/{out}"
if os.path.exists(src):
shutil.copy(src, f"./disk/libs/{out}")
print(f"[build] lib {out} -> disk/libs/{out}")
def CopyExisting():
"""--noapp:不编译,直接把已有产物拷进 disk/(缺文件只警告)。"""
os.makedirs("./disk/apps", exist_ok=True)
for _, proj, out, dst in APP_LIST:
src = f"{proj}/__vpcbuild__/output/{out}"
if os.path.exists(src):
shutil.copy(src, f"./disk/apps/{dst}")
else:
print(f"[build] 警告: {src} 不存在,请先完整构建一次")
for proj, out in LIB_LIST:
src = f"{proj}/__vpcbuild__/output/{out}"
if os.path.exists(src):
shutil.copy(src, f"./disk/libs/{out}")
def BuildApps():
"""编译库与应用,并拷贝到 disk/。
· 不带 --app:编库 + 全部应用
· --app=a,b:只编列出的应用(库要一起编就再加 --libs)
"""
if noapp:
print("[build] --noapp: 跳过所有库与应用编译,复用已有缓存文件")
CopyExisting()
return
if app_filter is None or libs:
BuildLibs()
else:
print("[build] --app 已指定且未传 --libs:跳过库编译")
os.makedirs("./disk/apps", exist_ok=True)
for name, proj, out, dst in APP_LIST:
if app_filter is not None and name not in app_filter:
continue
cmd = rf'..\..\ViperC\output\viperc.exe --project {proj}/project.vpj --clear-cache'
if clean:
cmd += ' --clean'
os.system(cmd)
src = f"{proj}/__vpcbuild__/output/{out}"
shutil.copy(src, f"./disk/apps/{dst}")
print(f"[build] app {name}: {src} -> disk/apps/{dst}")
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}")
# OVMF 需要**可写**的 NVRAM。此前只传 code fd、且它是可写的 pflash ⇒ OVMF 把启动变量
# 写进 code 卷本身 ⇒ 每次启动都改动 edk2-x86_64-code.fd ⇒ 反复出现
# "pacman -Qkk 报 SHA256 校验和不一致 / 固件损坏"(重装只能好一阵,下次启动又被写)。
# 正确做法:code 只读 + 单独的一份可写 vars 副本(首次从 edk2-i386-vars.fd 拷贝生成)。
ovmf_code = r'D:\msys64\mingw64\share\qemu\edk2-x86_64-code.fd'
ovmf_vars_src = r'D:\msys64\mingw64\share\qemu\edk2-i386-vars.fd'
ovmf_vars_dir = os.path.abspath('build')
os.makedirs(ovmf_vars_dir, exist_ok=True)
ovmf_vars = os.path.join(ovmf_vars_dir, 'ovmf-vars.fd')
if not os.path.exists(ovmf_vars):
try:
shutil.copy(ovmf_vars_src, ovmf_vars)
print(f"[build] OVMF vars 副本已生成: {ovmf_vars}")
except Exception as e:
print(f"[build] 警告: 生成 OVMF vars 副本失败({e}),回退为旧的 -pflash code 用法")
ovmf_vars = None
qemu_cmd = [
'qemu-system-x86_64',
]
if ovmf_vars is not None:
qemu_cmd += ['-drive', f'if=pflash,format=raw,readonly=on,file={ovmf_code}',
'-drive', f'if=pflash,format=raw,file={ovmf_vars}']
else:
qemu_cmd += ['-pflash', ovmf_code]
qemu_cmd += [
'-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:
if kernel_only:
print("[build] --kernel-only: 只编译内核")
BuildKernel()
else:
if build_kernel:
BuildKernel()
else:
print("[build] 默认不重编内核(需要时加 --kernel)")
BuildApps()
BuildBootloader()
BuildDisk()
RunQemu()