默认行为改为**不重编内核**(多数迭代只改应用/库),需要时显式加 --kernel;--app=hello,explorer 只编译列出的应用(逗号分隔),不带 --app 仍编译全部应用;新增 --libs 显式编译 Libs 下的 .so(带 --app 时默认跳过库,要一起编就加 --libs);应用/库编译改为表驱动(APP_LIST/LIB_LIST),--noapp 的拷贝逻辑抽到 CopyExisting()。
219 lines
8.8 KiB
Python
219 lines
8.8 KiB
Python
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()
|