Подготовить автономную сборку и запуск перед разделением проектов
This commit is contained in:
Executable
+242
@@ -0,0 +1,242 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Упаковщик FAT12-дискеты Sprinter. Образ принадлежит вызывающему проекту."""
|
||||
|
||||
import struct
|
||||
import sys
|
||||
import os
|
||||
|
||||
def create_floppy_image(output_file, files_to_add):
|
||||
"""Создаёт FAT12 образ дискеты 1.44MB и добавляет файлы"""
|
||||
|
||||
# Размер дискеты 1.44MB
|
||||
SECTOR_SIZE = 512
|
||||
TOTAL_SECTORS = 2880
|
||||
FAT_SIZE = 9
|
||||
ROOT_DIR_ENTRIES = 224
|
||||
|
||||
# Проверяем весь набор до открытия выходного файла: ошибочная упаковка
|
||||
# не должна уничтожать предыдущий пригодный носитель приложения.
|
||||
capacity = TOTAL_SECTORS - 1 - 2 * FAT_SIZE - ROOT_DIR_ENTRIES * 32 // SECTOR_SIZE
|
||||
needed = 0
|
||||
names = set()
|
||||
if len(files_to_add) > ROOT_DIR_ENTRIES:
|
||||
raise ValueError("слишком много файлов для корневого каталога FAT12")
|
||||
for filename, src_path in files_to_add:
|
||||
name, ext = os.path.splitext(filename)
|
||||
short_name = (name[:8].upper(), ext[1:4].upper())
|
||||
# Исторические тесты SDK могут иметь длинное имя; на носителе оно
|
||||
# сокращается до 8.3. Коллизия после сокращения — настоящая ошибка.
|
||||
if not name or short_name in names:
|
||||
raise ValueError(f"пустое или повторное имя FAT 8.3: {filename}")
|
||||
short_name[0].encode('ascii')
|
||||
short_name[1].encode('ascii')
|
||||
names.add(short_name)
|
||||
needed += max(1, (os.path.getsize(src_path) + SECTOR_SIZE - 1) // SECTOR_SIZE)
|
||||
if needed > capacity:
|
||||
raise ValueError(f"данные не помещаются на дискету: {needed} > {capacity} секторов")
|
||||
|
||||
os.makedirs(os.path.dirname(os.path.abspath(output_file)), exist_ok=True)
|
||||
|
||||
# Создаём пустой образ
|
||||
with open(output_file, 'wb') as f:
|
||||
f.write(b'\x00' * (TOTAL_SECTORS * SECTOR_SIZE))
|
||||
|
||||
# Читаем и модифицируем образ
|
||||
with open(output_file, 'r+b') as f:
|
||||
# BPB (BIOS Parameter Block) - начинаем с смещения 0
|
||||
# Jump instruction
|
||||
f.seek(0)
|
||||
f.write(b'\xEB\x3C\x90')
|
||||
# OEM name
|
||||
f.write(b'MSDOS5.0')
|
||||
# Bytes per sector
|
||||
f.write(struct.pack('<H', SECTOR_SIZE))
|
||||
# Sectors per cluster
|
||||
f.write(struct.pack('B', 1))
|
||||
# Reserved sectors
|
||||
f.write(struct.pack('<H', 1))
|
||||
# Number of FATs
|
||||
f.write(struct.pack('B', 2))
|
||||
# Root directory entries
|
||||
f.write(struct.pack('<H', ROOT_DIR_ENTRIES))
|
||||
# Total sectors (16-bit)
|
||||
f.write(struct.pack('<H', 0))
|
||||
# Media descriptor
|
||||
f.write(struct.pack('B', 0xF0))
|
||||
# Sectors per FAT
|
||||
f.write(struct.pack('<H', FAT_SIZE))
|
||||
# Sectors per track
|
||||
f.write(struct.pack('<H', 18))
|
||||
# Heads
|
||||
f.write(struct.pack('<H', 2))
|
||||
# Hidden sectors
|
||||
f.write(struct.pack('<I', 0))
|
||||
# Total sectors (32-bit)
|
||||
f.write(struct.pack('<I', TOTAL_SECTORS))
|
||||
|
||||
# Extended BPB
|
||||
# Drive number
|
||||
f.write(struct.pack('B', 0))
|
||||
# Reserved
|
||||
f.write(struct.pack('B', 0))
|
||||
# Extended boot signature
|
||||
f.write(struct.pack('B', 0x29))
|
||||
# Volume serial number
|
||||
f.write(struct.pack('<I', 0x12345678))
|
||||
# Volume label
|
||||
f.write(b'NO NAME ')
|
||||
# File system type
|
||||
f.write(b'FAT12 ')
|
||||
|
||||
# Boot signature
|
||||
f.seek(510)
|
||||
f.write(struct.pack('<H', 0xAA55))
|
||||
|
||||
# FAT таблицы
|
||||
fat_start = 1 * SECTOR_SIZE
|
||||
fat1_start = fat_start
|
||||
fat2_start = fat_start + FAT_SIZE * SECTOR_SIZE
|
||||
|
||||
# Инициализация FAT
|
||||
# Первые две записи зарезервированы
|
||||
fat = [0xFFF8, 0xFFFF]
|
||||
|
||||
# Добавляем файлы
|
||||
root_dir_start = (1 + 2 * FAT_SIZE) * SECTOR_SIZE
|
||||
data_start = root_dir_start + ROOT_DIR_ENTRIES * 32
|
||||
|
||||
cluster = 2 # Первый кластер данных
|
||||
|
||||
for filename, src_path in files_to_add:
|
||||
# Читаем файл
|
||||
with open(src_path, 'rb') as src:
|
||||
file_data = src.read()
|
||||
|
||||
file_size = len(file_data)
|
||||
print(f"Добавляем файл: {filename}, размер: {file_size} байт (0x{file_size:04X})")
|
||||
|
||||
# Вычисляем количество кластеров
|
||||
clusters_needed = max(1, (file_size + SECTOR_SIZE - 1) // SECTOR_SIZE)
|
||||
|
||||
# Добавляем записи в FAT
|
||||
first_cluster = cluster
|
||||
for i in range(clusters_needed):
|
||||
if i == clusters_needed - 1:
|
||||
fat.append(0xFFFF) # Последний кластер
|
||||
else:
|
||||
fat.append(cluster + 1)
|
||||
cluster += 1
|
||||
|
||||
# Записываем данные файла
|
||||
file_offset = (first_cluster - 2) * SECTOR_SIZE
|
||||
f.seek(data_start + file_offset)
|
||||
f.write(file_data)
|
||||
|
||||
# Создаём запись в корневом каталоге
|
||||
# Имя файла (8.3 формат)
|
||||
name, ext = os.path.splitext(filename)
|
||||
name = name.upper().ljust(8, ' ')[:8]
|
||||
# ext comes from splitext as ".XYZ" (with leading dot) or "".
|
||||
# Strip the dot first, then space-pad to 3 chars. The old form
|
||||
# `ext.upper().ljust(3, ' ')[1:4]` lost padding for 2-char
|
||||
# extensions like ".MD" → "MD" (2 bytes), corrupting the dir
|
||||
# entry layout.
|
||||
ext = ext[1:].upper().ljust(3, ' ')[:3] if ext else ' '
|
||||
|
||||
# Атрибуты
|
||||
attr = 0x20 # Archive
|
||||
print(f"Атрибуты: 0x{attr:02X}")
|
||||
|
||||
# Время и дата (упрощённо)
|
||||
time_val = 0x0000
|
||||
date_val = 0x0021
|
||||
|
||||
# Первый кластер (FAT12: младший байт по смещению 26, старший по 27)
|
||||
first_cluster_lo = first_cluster & 0xFF
|
||||
first_cluster_hi = (first_cluster >> 8) & 0xFF
|
||||
|
||||
# Размер файла (пробуем big-endian для Sprinter)
|
||||
file_size_bytes = struct.pack('<I', file_size)
|
||||
print(f"Размер файла в записи: {file_size_bytes.hex()}")
|
||||
|
||||
# Записываем в корневой каталог (32 байта)
|
||||
dir_entry = bytearray(32)
|
||||
dir_entry[0:8] = name.encode('ascii')
|
||||
dir_entry[8:11] = ext.encode('ascii')
|
||||
dir_entry[11] = attr
|
||||
dir_entry[12:22] = b'\x00' * 10 # Reserved
|
||||
dir_entry[22:24] = struct.pack('<H', time_val)
|
||||
dir_entry[24:26] = struct.pack('<H', date_val)
|
||||
dir_entry[26] = first_cluster_lo
|
||||
dir_entry[27] = first_cluster_hi
|
||||
dir_entry[28:32] = file_size_bytes
|
||||
|
||||
print(f"Атрибуты в записи: 0x{dir_entry[11]:02X}")
|
||||
print(f"Запись каталога: {dir_entry.hex()}")
|
||||
|
||||
# Находим свободное место в корневом каталоге
|
||||
f.seek(root_dir_start)
|
||||
found = False
|
||||
for i in range(ROOT_DIR_ENTRIES):
|
||||
entry_pos = root_dir_start + i * 32
|
||||
f.seek(entry_pos)
|
||||
first_byte = f.read(1)
|
||||
if first_byte == b'\x00' or first_byte == b'\xE5':
|
||||
f.seek(entry_pos)
|
||||
f.write(dir_entry)
|
||||
found = True
|
||||
print(f"Записан в корневой каталог, запись #{i}, позиция 0x{entry_pos:04X}")
|
||||
break
|
||||
|
||||
if not found:
|
||||
print(f"Ошибка: нет места в корневом каталоге для {filename}")
|
||||
return False
|
||||
|
||||
# Записываем FAT
|
||||
# FAT12 кодирование: 3 байта на 2 записи
|
||||
fat_bytes = bytearray()
|
||||
for i in range(0, len(fat), 2):
|
||||
if i + 1 < len(fat):
|
||||
val1 = fat[i] & 0xFFF
|
||||
val2 = fat[i + 1] & 0xFFF
|
||||
fat_bytes.extend([
|
||||
val1 & 0xFF,
|
||||
((val2 & 0xF) << 4) | ((val1 >> 8) & 0xF),
|
||||
(val2 >> 4) & 0xFF
|
||||
])
|
||||
else:
|
||||
val1 = fat[i] & 0xFFF
|
||||
fat_bytes.extend([
|
||||
val1 & 0xFF,
|
||||
(val1 >> 8) & 0xF,
|
||||
0x00
|
||||
])
|
||||
|
||||
# Записываем FAT1
|
||||
f.seek(fat1_start)
|
||||
f.write(fat_bytes[:FAT_SIZE * SECTOR_SIZE])
|
||||
|
||||
# Записываем FAT2
|
||||
f.seek(fat2_start)
|
||||
f.write(fat_bytes[:FAT_SIZE * SECTOR_SIZE])
|
||||
|
||||
print(f"Создан образ: {output_file}")
|
||||
print(f"Использовано кластеров: {cluster - 2}")
|
||||
return True
|
||||
|
||||
if __name__ == '__main__':
|
||||
if len(sys.argv) < 3:
|
||||
print("Использование: python3 make_disk.py <output.img> <file1> [file2 ...]")
|
||||
sys.exit(1)
|
||||
|
||||
output_file = sys.argv[1]
|
||||
files = []
|
||||
for i in range(2, len(sys.argv)):
|
||||
files.append((os.path.basename(sys.argv[i]), sys.argv[i]))
|
||||
|
||||
try:
|
||||
ok = create_floppy_image(output_file, files)
|
||||
except (OSError, ValueError, UnicodeEncodeError) as error:
|
||||
print(f"make_disk: {error}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
sys.exit(0 if ok else 1)
|
||||
+14
-1
@@ -7,6 +7,7 @@
|
||||
# переносит всё дерево под DIR, например games/sprpop.
|
||||
#
|
||||
# Требует: mtools (mpartition/mformat/mcopy) + chdman (из MAME).
|
||||
# CHDMAN_BIN может указывать на установленный chdman вне PATH.
|
||||
# Sprinter DSS штатно монтирует стандартный MBR+FAT16 (подтверждено).
|
||||
#
|
||||
# Использование:
|
||||
@@ -48,6 +49,18 @@ if [ $# -lt 2 ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
for command in mpartition mformat mcopy mmd mdir; do
|
||||
if ! command -v "$command" >/dev/null 2>&1; then
|
||||
echo "make_hdd.sh: не найден host-инструмент $command (mtools)" >&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
CHDMAN_BIN=${CHDMAN_BIN:-chdman}
|
||||
if ! command -v "$CHDMAN_BIN" >/dev/null 2>&1; then
|
||||
echo "make_hdd.sh: не найден chdman: $CHDMAN_BIN" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
OUT_CHD="$1"; shift
|
||||
|
||||
# DSS использует обратный слэш, но mtools — прямой. Ведущие/замыкающие
|
||||
@@ -145,6 +158,6 @@ mt mdir -/ z:
|
||||
|
||||
# RAW -> CHD (без сжатия = быстро; та же геометрия).
|
||||
echo "--- конвертация RAW -> CHD ---"
|
||||
chdman createhd -i "$RAW" -o "$OUT_CHD" -c none \
|
||||
"$CHDMAN_BIN" createhd -i "$RAW" -o "$OUT_CHD" -c none \
|
||||
-chs $CYLS,$HEADS,$SECS -ss 512 -f
|
||||
echo "готово: $OUT_CHD"
|
||||
|
||||
@@ -14,10 +14,11 @@
|
||||
# - libc/include/ : public headers
|
||||
# - libc/{io,stdio,mem,gfx}/ : libc sources (assembled into sprinter.lib)
|
||||
# - lib/Makefile + lib/sprinter.lib : prebuilt library + recipe to rebuild
|
||||
# - examples/ : 27 ready-to-build programs
|
||||
# - app.mk + disk/run tools : standalone application integration
|
||||
# - libbgi/ + lib/bgi256*.lib : graphics library
|
||||
# - third_party/sdcc/ : vendored SDCC 4.5
|
||||
# - release_docs/ → docs/ : user-facing documentation
|
||||
# - examples.mk-style root Makefile, README, RELEASE_NOTES, LICENSE
|
||||
# - root Makefile, README, RELEASE_NOTES, LICENSE
|
||||
#
|
||||
# NOT included (developer-only):
|
||||
# - docs/ (research notes, original PetersPlus reference material)
|
||||
@@ -50,22 +51,30 @@ mkdir -p "$STAGE"
|
||||
# --- WHITELIST: only these directories/files end up in the tarball ---
|
||||
|
||||
# Top-level metadata
|
||||
cp README.md RELEASE_NOTES.md LICENSE Makefile "$STAGE/"
|
||||
cp README.md RELEASE_NOTES.md LICENSE Makefile app.mk "$STAGE/"
|
||||
|
||||
# Toolchain
|
||||
cp -R bin runtime libc examples "$STAGE/"
|
||||
# toolchain/ — only mkexe (prebuilt + sources) and check_banks.py
|
||||
cp -R bin runtime libc libbgi "$STAGE/"
|
||||
# Core tools and source debugger backend needed by sprinter-cc/app.mk.
|
||||
mkdir -p "$STAGE/toolchain/mkexe"
|
||||
cp toolchain/mkexe/mkexe "$STAGE/toolchain/mkexe/"
|
||||
cp toolchain/mkexe/mkexe.c "$STAGE/toolchain/mkexe/"
|
||||
cp toolchain/mkexe/Makefile "$STAGE/toolchain/mkexe/"
|
||||
[[ -f toolchain/mkexe/check.sh ]] && cp toolchain/mkexe/check.sh "$STAGE/toolchain/mkexe/"
|
||||
cp toolchain/check_banks.py "$STAGE/toolchain/"
|
||||
cp toolchain/check_bank_calls.py toolchain/check_w0_isr.py \
|
||||
toolchain/sdbg_build.py toolchain/sdbg_driver.py toolchain/sdbg_dap.py \
|
||||
toolchain/sdbg_launcher.py toolchain/mame_interactive.py \
|
||||
toolchain/mame_profile.py toolchain/make_disk.py \
|
||||
toolchain/make_hdd.sh toolchain/run_sprinter_mame.py "$STAGE/toolchain/"
|
||||
cp -R toolchain/sdbg "$STAGE/toolchain/"
|
||||
cp -R toolchain/mcp "$STAGE/toolchain/"
|
||||
|
||||
# Lib — keep Makefile and the prebuilt archive only; sources are in libc/
|
||||
mkdir -p "$STAGE/lib"
|
||||
cp lib/Makefile "$STAGE/lib/"
|
||||
cp lib/sprinter.lib "$STAGE/lib/"
|
||||
cp lib/sprinter_safe.lib lib/bgi256.lib lib/bgi256_safe.lib "$STAGE/lib/"
|
||||
|
||||
# Vendored SDCC — follow symlinks so the real content lands in the tarball
|
||||
mkdir -p "$STAGE/third_party"
|
||||
@@ -77,7 +86,7 @@ mkdir -p "$STAGE/third_party"
|
||||
# NOT shipped.
|
||||
[[ -d release_docs ]] && cp -R release_docs "$STAGE/docs"
|
||||
|
||||
# --- Strip build artefacts that crept in via examples/ ---
|
||||
# --- Strip build artefacts that crept in via sources ---
|
||||
echo ">> stripping build artefacts and macOS metadata"
|
||||
find "$STAGE" \( \
|
||||
-name '.sprinter-cc-*' -o \
|
||||
@@ -105,4 +114,5 @@ echo "Try it:"
|
||||
echo " tar xzf $(basename "$TARBALL")"
|
||||
echo " cd $NAME"
|
||||
echo " make all"
|
||||
echo " bin/sprinter-cc -o hello.exe examples/hello/hello.c"
|
||||
echo " bin/sprinter-cc -o hello.exe hello.c"
|
||||
echo " Examples are released as a separate repository"
|
||||
|
||||
@@ -20,7 +20,7 @@ mame_interactive.py — автотест .exe в MAME без участия че
|
||||
автоповтор (typematic) и слипание scancode'ов.
|
||||
- загрузка должна встать на приглашении C:\> (system.bat не должен
|
||||
автозапускать Flex Navigator/приложение).
|
||||
- перед запуском проверяем, что нет висящих копий MAME.
|
||||
- тестовый носитель, конфигурация и состояние MAME изолированы от других сессий.
|
||||
|
||||
Использование:
|
||||
python3 toolchain/mame_interactive.py [exe] [--data f ...] \
|
||||
@@ -53,27 +53,16 @@ mame_interactive.py — автотест .exe в MAME без участия че
|
||||
|
||||
import argparse
|
||||
import os
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import uuid
|
||||
|
||||
from mame_profile import MameProfile, add_arguments, from_arguments, write_keyboard_config
|
||||
|
||||
PROJECT_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
MAME_DIR = os.path.join(PROJECT_ROOT, "mame", "v306")
|
||||
IMG_DIR = os.path.join(MAME_DIR, "IMG")
|
||||
MC_IMG = os.path.join(IMG_DIR, "mc.img")
|
||||
SNAP_DIR = os.path.join(MAME_DIR, "snap_auto")
|
||||
LUA_SCRIPT = os.path.join(MAME_DIR, "_interactive_gen.lua")
|
||||
|
||||
COMMON_ARGS = [
|
||||
"-skip_gameinfo", "-video", "opengl", "-window", "-nofilter",
|
||||
"-beta:wd179x:0", "35hd", "-beta:wd179x:1", "35hd",
|
||||
"-flop1", MC_IMG, "-flop2", os.path.join(IMG_DIR, "dss171u.img"),
|
||||
"-isa0", "zxbus_adapter", "-isa0:zxbus_adapter:card", "neogs",
|
||||
"-hard1", os.path.join(IMG_DIR, "sp_hdd_sys.chd"),
|
||||
"-hard2", os.path.join(IMG_DIR, "sp_hdd_media.chd"),
|
||||
"-ata2:0", "cdrom", "-cdrom", os.path.join(IMG_DIR, "SprinterCD.iso"),
|
||||
"-bios", "v3.06",
|
||||
]
|
||||
|
||||
# --- Раскладка AT-клавиатуры :kbd:ms_naturl (tag, mask) ---
|
||||
# Снято дампом ioport-полей в MAME (см. docs/mame-autotest.md).
|
||||
@@ -207,21 +196,7 @@ emu.register_periodic(function()
|
||||
end
|
||||
end)
|
||||
"""
|
||||
with open(LUA_SCRIPT, "w") as f:
|
||||
f.write(lua)
|
||||
|
||||
|
||||
def check_no_stray_mame():
|
||||
# Ловим только запущенный ЭМУЛЯТОР (mame/mame.arm/mame.exe c машиной
|
||||
# sprinter), а не всё, где в командной строке встречается «mame»:
|
||||
# голый паттерн ложно срабатывал на параллельную сборку MAME из
|
||||
# исходников (clang/make с путями .../src/mame/...).
|
||||
out = subprocess.run(["pgrep", "-fl", r"mame[^ ]* sprinter( |$)"],
|
||||
capture_output=True, text=True).stdout
|
||||
if out.strip():
|
||||
print("ОШИБКА: есть запущенные копии MAME — закройте их:")
|
||||
print(out)
|
||||
sys.exit(1)
|
||||
return lua
|
||||
|
||||
|
||||
def parse_step(s):
|
||||
@@ -234,6 +209,7 @@ def parse_step(s):
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser()
|
||||
add_arguments(ap)
|
||||
ap.add_argument("exe", nargs="?", help=".exe на дискету A: (авто-запуск)")
|
||||
ap.add_argument("--data", nargs="*", default=[], help="доп. файлы на дискету")
|
||||
ap.add_argument("--launch-at", type=float, default=8.0,
|
||||
@@ -242,9 +218,15 @@ def main():
|
||||
help="'T:TEXT' — в момент T сек напечатать TEXT (можно много)")
|
||||
ap.add_argument("--snap", default=None, help="секунды для скриншотов, через запятую")
|
||||
ap.add_argument("--timeout", type=float, default=None, help="сек эмуляции до выхода")
|
||||
ap.add_argument("--snapshot-dir", help="каталог кадров, по умолчанию build/mame-autotest")
|
||||
args = ap.parse_args()
|
||||
|
||||
check_no_stray_mame()
|
||||
profile = from_arguments(args)
|
||||
if profile.binary is None and (Path(PROJECT_ROOT)/"mame/v306/mame.arm").is_file():
|
||||
profile = MameProfile.resolve({"MAME_HOME":str(Path(PROJECT_ROOT)/"mame/v306")})
|
||||
try:
|
||||
profile.validate(dss=True)
|
||||
except ValueError as error:
|
||||
ap.error(str(error))
|
||||
|
||||
# Собираем шаги ввода: авто-запуск exe (печать пути) + пользовательские.
|
||||
steps = list(args.step)
|
||||
@@ -272,7 +254,7 @@ def main():
|
||||
snap_times = [last_ev_t + pad, last_ev_t + pad + 2.0]
|
||||
timeout = args.timeout if args.timeout is not None else (max(snap_times) + 1.5)
|
||||
|
||||
gen_lua(events, snap_times, timeout)
|
||||
lua_source = gen_lua(events, snap_times, timeout)
|
||||
|
||||
# Дискета БЕЗ авто-запускаемого bat → приглашение C:\>.
|
||||
disk_files = []
|
||||
@@ -280,34 +262,67 @@ def main():
|
||||
disk_files.append(exe_path)
|
||||
disk_files += [os.path.abspath(p) for p in args.data]
|
||||
|
||||
if os.path.exists(MC_IMG):
|
||||
shutil.copy(MC_IMG, MC_IMG + ".bak")
|
||||
make_disk_cmd = [sys.executable, os.path.join(MAME_DIR, "make_disk.py"), MC_IMG]
|
||||
subprocess.run(make_disk_cmd + disk_files, check=True, cwd=MAME_DIR)
|
||||
snapshot_base = Path(args.snapshot_dir or Path.cwd()/"build/mame-autotest")
|
||||
snapshot_root = snapshot_base.resolve()/uuid.uuid4().hex[:10]
|
||||
snapshot_root.mkdir(parents=True)
|
||||
with tempfile.TemporaryDirectory(prefix="sprinter-mame-autotest-") as directory:
|
||||
state = Path(directory)
|
||||
for name in ("cfg", "nvram", "diff"):
|
||||
(state/name).mkdir()
|
||||
write_keyboard_config(state/"cfg")
|
||||
disk = state/"test.img"
|
||||
subprocess.run([sys.executable, str(Path(PROJECT_ROOT)/"toolchain/make_disk.py"),
|
||||
str(disk), *disk_files], check=True)
|
||||
lua_script = state/"interactive.lua"
|
||||
lua_script.write_text(lua_source, encoding="utf-8")
|
||||
cmd = [str(profile.binary), "sprinter", "-noreadconfig",
|
||||
"-rompath", str(profile.rompath), "-bios", profile.bios,
|
||||
"-kbd", "ms_naturl,bios=sp2k", "-skip_gameinfo",
|
||||
"-video", "soft", "-window", "-beta:wd179x:0", "35hd",
|
||||
"-beta:wd179x:1", "35hd", "-flop1", str(disk),
|
||||
"-flop2", str(profile.dss_image),
|
||||
"-hard1", str(profile.system_hdd_image),
|
||||
"-isa0", "zxbus_adapter", "-isa0:zxbus_adapter:card", "neogs",
|
||||
"-snapshot_directory", str(snapshot_root),
|
||||
"-autoboot_script", str(lua_script)]
|
||||
for name in ("cfg", "nvram", "diff"):
|
||||
cmd += ["-"+name+"_directory", str(state/name)]
|
||||
if profile.home:
|
||||
media = profile.home/"IMG/sp_hdd_media.chd"
|
||||
optical = profile.home/"IMG/SprinterCD.iso"
|
||||
if media.is_file():
|
||||
test_media = state/"media.chd"
|
||||
shutil.copyfile(media, test_media)
|
||||
cmd += ["-hard2", str(test_media)]
|
||||
if optical.is_file():
|
||||
cmd += ["-ata2:0", "cdrom", "-cdrom", str(optical)]
|
||||
print("Ввод по шагам:", [(round(t, 2), repr(txt)) for t, txt in steps])
|
||||
print("Скриншоты (сек):", snap_times, " таймаут:", timeout)
|
||||
try:
|
||||
result = subprocess.run(cmd, cwd=state, capture_output=True,
|
||||
text=True, timeout=timeout+8)
|
||||
except subprocess.TimeoutExpired as error:
|
||||
print("MAME: истёк лимит времени", file=sys.stderr)
|
||||
output = error.stdout or ""
|
||||
stderr = error.stderr or ""
|
||||
if isinstance(output, bytes):
|
||||
output = output.decode(errors="replace")
|
||||
if isinstance(stderr, bytes):
|
||||
stderr = stderr.decode(errors="replace")
|
||||
result = subprocess.CompletedProcess(cmd, 124, output, stderr)
|
||||
for line in result.stdout.splitlines():
|
||||
if "[interactive]" in line:
|
||||
print(line)
|
||||
if result.returncode not in (0, 124):
|
||||
print("STDERR:", result.stderr[-2000:])
|
||||
|
||||
snap_sub = os.path.join(SNAP_DIR, "sprinter")
|
||||
if os.path.isdir(snap_sub):
|
||||
shutil.rmtree(snap_sub)
|
||||
|
||||
cmd = ["timeout", str(int(timeout) + 8), "./mame.arm", "sprinter"] + COMMON_ARGS + [
|
||||
"-snapshot_directory", SNAP_DIR,
|
||||
"-autoboot_script", LUA_SCRIPT,
|
||||
]
|
||||
print("Ввод по шагам:", [(round(t, 2), repr(txt)) for t, txt in steps])
|
||||
print("Скриншоты (сек):", snap_times, " таймаут:", timeout)
|
||||
result = subprocess.run(cmd, cwd=MAME_DIR, capture_output=True, text=True)
|
||||
for line in result.stdout.splitlines():
|
||||
if "[interactive]" in line:
|
||||
print(line)
|
||||
if result.returncode not in (0, 124):
|
||||
print("STDERR:", result.stderr[-2000:])
|
||||
|
||||
if os.path.isdir(snap_sub):
|
||||
snap_sub = snapshot_root/"sprinter"
|
||||
if snap_sub.is_dir():
|
||||
print("Скриншоты:")
|
||||
for s in sorted(os.listdir(snap_sub)):
|
||||
print(" ", os.path.join(snap_sub, s))
|
||||
for shot in sorted(snap_sub.iterdir()):
|
||||
print(" ", shot)
|
||||
else:
|
||||
print("Скриншотов не создано.")
|
||||
print("Скриншотов не создано. Каталог:", snapshot_root)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Пути к установленному MAME; исходники fork приложению не нужны."""
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class MameProfile:
|
||||
home: Path | None
|
||||
binary: Path | None
|
||||
rompath: Path | None
|
||||
dss_image: Path | None
|
||||
system_hdd_image: Path | None
|
||||
bios: str
|
||||
|
||||
@classmethod
|
||||
def resolve(cls, overrides: dict[str, str | None] | None = None) -> "MameProfile":
|
||||
values = dict(os.environ)
|
||||
values.update({key: value for key, value in (overrides or {}).items()
|
||||
if value is not None})
|
||||
home = _path(values.get("MAME_HOME"))
|
||||
|
||||
def selected(key: str, suffix: str) -> Path | None:
|
||||
return _path(values.get(key)) or (home / suffix if home else None)
|
||||
|
||||
return cls(home=home,
|
||||
binary=selected("MAME_BIN", "mame.arm"),
|
||||
rompath=selected("MAME_ROMPATH", "roms"),
|
||||
dss_image=selected("MAME_DSS_IMAGE", "IMG/dss171u.img"),
|
||||
system_hdd_image=selected("MAME_SYSTEM_HDD_IMAGE", "IMG/sp_hdd_sys.chd"),
|
||||
bios=values.get("MAME_BIOS") or "v3.06")
|
||||
|
||||
def validate(self, *, dss: bool = False, system_hdd: bool = True) -> None:
|
||||
paths = {"MAME_BIN": self.binary, "MAME_ROMPATH": self.rompath}
|
||||
if dss:
|
||||
paths["MAME_DSS_IMAGE"] = self.dss_image
|
||||
if system_hdd:
|
||||
paths["MAME_SYSTEM_HDD_IMAGE"] = self.system_hdd_image
|
||||
for variable, path in paths.items():
|
||||
if path is None:
|
||||
raise ValueError(f"задайте MAME_HOME или {variable}")
|
||||
if not path.exists():
|
||||
raise ValueError(f"{variable}: файл/каталог не найден: {path}")
|
||||
if self.binary and not self.binary.is_file():
|
||||
raise ValueError(f"MAME_BIN не является файлом: {self.binary}")
|
||||
if self.rompath and not self.rompath.is_dir():
|
||||
raise ValueError(f"MAME_ROMPATH не является каталогом: {self.rompath}")
|
||||
|
||||
|
||||
def _path(value: str | None) -> Path | None:
|
||||
if not value:
|
||||
return None
|
||||
return Path(os.path.expanduser(value)).resolve()
|
||||
|
||||
|
||||
def add_arguments(parser) -> None:
|
||||
"""Одинаковые аргументы для обычного запуска, автотеста и DAP."""
|
||||
for option in ("mame-home", "mame-bin", "mame-rompath", "mame-dss-image",
|
||||
"mame-system-hdd-image", "mame-bios"):
|
||||
parser.add_argument("--" + option, default=None)
|
||||
|
||||
|
||||
def from_arguments(args) -> MameProfile:
|
||||
names = ("MAME_HOME", "MAME_BIN", "MAME_ROMPATH", "MAME_DSS_IMAGE",
|
||||
"MAME_SYSTEM_HDD_IMAGE", "MAME_BIOS")
|
||||
return MameProfile.resolve({name: getattr(args, name.lower(), None)
|
||||
for name in names})
|
||||
|
||||
|
||||
def write_keyboard_config(cfg_directory: Path) -> None:
|
||||
"""Включить обе клавиатуры Sprinter, включая прямой ввод в DSS."""
|
||||
cfg_directory.mkdir(parents=True, exist_ok=True)
|
||||
(cfg_directory / "sprinter.cfg").write_text(
|
||||
'<?xml version="1.0"?>\n'
|
||||
'<mameconfig version="10">\n'
|
||||
' <system name="sprinter">\n'
|
||||
' <input>\n'
|
||||
' <keyboard tag=":" enabled="1" />\n'
|
||||
' <keyboard tag=":kbd:ms_naturl" enabled="1" />\n'
|
||||
' </input>\n'
|
||||
' </system>\n'
|
||||
'</mameconfig>\n', encoding="utf-8")
|
||||
@@ -0,0 +1,77 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Обычный запуск Sprinter в MAME с локальным образом приложения."""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
from mame_profile import add_arguments, from_arguments, write_keyboard_config
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
add_arguments(parser)
|
||||
parser.add_argument("--floppy", help="локальный образ приложения для A:")
|
||||
parser.add_argument("--hdd", help="локальный образ приложения для D:")
|
||||
parser.add_argument("--video", default="soft")
|
||||
parser.add_argument("--script", help="Lua-сценарий запуска/теста")
|
||||
parser.add_argument("--snapshot-dir", help="постоянный каталог снимков")
|
||||
parser.add_argument("--timeout", type=float, help="лимит работы MAME в секундах")
|
||||
parser.add_argument("--dry-run", action="store_true")
|
||||
args = parser.parse_args()
|
||||
try:
|
||||
profile = from_arguments(args)
|
||||
profile.validate(dss=True)
|
||||
if not args.floppy and not args.hdd:
|
||||
raise ValueError("для запуска укажите --floppy или --hdd")
|
||||
media = {name: Path(value).resolve() for name, value in
|
||||
(("floppy", args.floppy), ("hdd", args.hdd)) if value}
|
||||
for name, path in media.items():
|
||||
if not path.is_file():
|
||||
raise ValueError(f"локальный образ {name} не найден: {path}")
|
||||
script = Path(args.script).resolve() if args.script else None
|
||||
if script and not script.is_file():
|
||||
raise ValueError(f"Lua-сценарий не найден: {script}")
|
||||
except ValueError as error:
|
||||
print(f"run_sprinter_mame: {error}", file=sys.stderr)
|
||||
return 2
|
||||
|
||||
with tempfile.TemporaryDirectory(prefix="sprinter-mame-run-") as directory:
|
||||
state = Path(directory)
|
||||
for name in ("cfg", "nvram", "diff", "snapshot"):
|
||||
(state / name).mkdir()
|
||||
snapshots = Path(args.snapshot_dir).resolve() if args.snapshot_dir else state / "snapshot"
|
||||
snapshots.mkdir(parents=True, exist_ok=True)
|
||||
write_keyboard_config(state / "cfg")
|
||||
command = [str(profile.binary), "sprinter", "-noreadconfig",
|
||||
"-rompath", str(profile.rompath), "-bios", profile.bios,
|
||||
"-kbd", "ms_naturl,bios=sp2k", "-video", args.video,
|
||||
"-window", "-skip_gameinfo", "-beta:wd179x:0", "35hd",
|
||||
"-flop2", str(profile.dss_image),
|
||||
"-hard1", str(profile.system_hdd_image)]
|
||||
if "floppy" in media:
|
||||
command += ["-flop1", str(media["floppy"])]
|
||||
if "hdd" in media:
|
||||
command += ["-hard2", str(media["hdd"])]
|
||||
if script:
|
||||
command += ["-autoboot_script", str(script)]
|
||||
for name in ("cfg", "nvram", "diff"):
|
||||
command += ["-" + name + "_directory", str(state / name)]
|
||||
command += ["-snapshot_directory", str(snapshots)]
|
||||
print("MAME:", profile.binary, "ROM:", profile.rompath,
|
||||
"DSS:", profile.dss_image, "BIOS:", profile.bios, flush=True)
|
||||
if args.dry_run:
|
||||
print("Команда:", command)
|
||||
return 0
|
||||
try:
|
||||
return subprocess.run(command, cwd=state, timeout=args.timeout).returncode
|
||||
except subprocess.TimeoutExpired:
|
||||
print("run_sprinter_mame: истёк лимит времени", file=sys.stderr)
|
||||
return 124
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -95,6 +95,17 @@ class Protocol:
|
||||
'--build', build, '--socket', socket_path]
|
||||
if arguments.get('mame'):
|
||||
command.extend(['--mame', arguments['mame']])
|
||||
profile_arguments = {
|
||||
'mameHome': '--mame-home',
|
||||
'mameBin': '--mame-bin',
|
||||
'mameRompath': '--mame-rompath',
|
||||
'mameDssImage': '--mame-dss-image',
|
||||
'mameSystemHddImage': '--mame-system-hdd-image',
|
||||
'mameBios': '--mame-bios',
|
||||
}
|
||||
for key, option in profile_arguments.items():
|
||||
if arguments.get(key):
|
||||
command.extend([option, arguments[key]])
|
||||
if arguments.get('debugger'):
|
||||
command.extend(['--debugger', arguments['debugger']])
|
||||
if arguments.get('launchAt') is not None:
|
||||
|
||||
+29
-27
@@ -4,7 +4,6 @@ from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
from contextlib import redirect_stdout
|
||||
import importlib.util
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
@@ -19,6 +18,9 @@ import uuid
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
sys.path.insert(0, str(ROOT/'toolchain'))
|
||||
from mame_interactive import basename_83, build_events
|
||||
from make_disk import create_floppy_image
|
||||
from mame_profile import (MameProfile, add_arguments, from_arguments,
|
||||
write_keyboard_config)
|
||||
from sdbg.image import read_ihx
|
||||
from sdbg.model import DebugMap
|
||||
|
||||
@@ -38,20 +40,6 @@ def disk_entries(exe: Path, data) -> list[tuple[str, str]]:
|
||||
return entries
|
||||
|
||||
|
||||
def write_keyboard_config(cfg_directory: Path) -> None:
|
||||
"""Включить обе клавиатуры Sprinter для физического ввода в DSS."""
|
||||
(cfg_directory/'sprinter.cfg').write_text(
|
||||
'<?xml version="1.0"?>\n'
|
||||
'<mameconfig version="10">\n'
|
||||
' <system name="sprinter">\n'
|
||||
' <input>\n'
|
||||
' <keyboard tag=":" enabled="1" />\n'
|
||||
' <keyboard tag=":kbd:ms_naturl" enabled="1" />\n'
|
||||
' </input>\n'
|
||||
' </system>\n'
|
||||
'</mameconfig>\n', encoding='utf-8')
|
||||
|
||||
|
||||
def lua_script(path, ready, main_address, signature, events, launch_at,
|
||||
dss_timeout=30):
|
||||
rows = ',\n'.join('{'+f'{t},"{tag}",{mask},{value}'+'}'
|
||||
@@ -165,6 +153,7 @@ end)
|
||||
|
||||
def main():
|
||||
parser=argparse.ArgumentParser(description=__doc__)
|
||||
add_arguments(parser)
|
||||
parser.add_argument('--build',required=True)
|
||||
parser.add_argument('--socket',required=True)
|
||||
parser.add_argument('--data',action='append',default=[])
|
||||
@@ -172,7 +161,8 @@ def main():
|
||||
help='не начинать ввод раньше этой секунды эмуляции')
|
||||
parser.add_argument('--dss-timeout',type=float,default=30,
|
||||
help='таймаут появления стабильного prompt DSS')
|
||||
parser.add_argument('--mame',default=str(ROOT/'mame/v306/mame.arm'))
|
||||
parser.add_argument('--mame',default=None,
|
||||
help='совместимый alias для --mame-bin')
|
||||
parser.add_argument('--debugger',default='sdbg',
|
||||
help='OSD debugger provider (по умолчанию sdbg из project patch)')
|
||||
args=parser.parse_args()
|
||||
@@ -188,8 +178,13 @@ def main():
|
||||
function['start'],min(function['end'],function['start']+24)))
|
||||
if not signature:
|
||||
raise ValueError('У main нет проверяемой сигнатуры')
|
||||
mame=Path(args.mame).resolve()
|
||||
mame_dir=mame.parent
|
||||
if args.mame and not args.mame_bin:
|
||||
args.mame_bin=args.mame
|
||||
profile=from_arguments(args)
|
||||
if profile.binary is None and (ROOT/'mame/v306/mame.arm').is_file():
|
||||
profile=MameProfile.resolve({'MAME_HOME':str(ROOT/'mame/v306')})
|
||||
profile.validate(dss=True)
|
||||
mame=profile.binary
|
||||
stopping=False
|
||||
def stop(*_):
|
||||
nonlocal stopping
|
||||
@@ -202,13 +197,11 @@ def main():
|
||||
for name in ('nvram','cfg','diff','snapshot'): (state/name).mkdir()
|
||||
write_keyboard_config(state/'cfg')
|
||||
system=state/'system.chd'
|
||||
shutil.copyfile(mame_dir/'IMG/sp_hdd_sys.chd',system)
|
||||
shutil.copyfile(profile.system_hdd_image,system)
|
||||
disk_path=state/'debug.img'
|
||||
spec=importlib.util.spec_from_file_location('make_disk',mame_dir/'make_disk.py')
|
||||
disk=importlib.util.module_from_spec(spec);spec.loader.exec_module(disk)
|
||||
files=disk_entries(exe,args.data)
|
||||
with redirect_stdout(sys.stderr):
|
||||
if not disk.create_floppy_image(str(disk_path),files):
|
||||
if not create_floppy_image(str(disk_path),files):
|
||||
raise RuntimeError('Не удалось создать debug-дискету')
|
||||
command_text='a:\\'+basename_83(exe.name).replace(' ','')+'\n'
|
||||
events=build_events([(0,command_text)])
|
||||
@@ -218,11 +211,20 @@ def main():
|
||||
args.dss_timeout)
|
||||
session_id=uuid.uuid4().hex
|
||||
environment=dict(os.environ,SDBG_IPC_DIR=str(ipc),SDBG_SESSION_ID=session_id)
|
||||
command=[str(mame),'sprinter','-noreadconfig','-rompath',str(mame_dir/'roms'),
|
||||
'-bios','v3.06','-kbd','ms_naturl,bios=sp2k','-video','soft','-window',
|
||||
plugin_paths=[str(ROOT/'toolchain/mcp')]
|
||||
installed_plugins=profile.home.parent/'plugins' if profile.home else None
|
||||
if installed_plugins and installed_plugins.is_dir():
|
||||
plugin_paths.append(str(installed_plugins))
|
||||
legacy_plugins=ROOT/'mame/sources/MAME/plugins'
|
||||
if legacy_plugins.is_dir():
|
||||
plugin_paths.append(str(legacy_plugins))
|
||||
command=[str(mame),'sprinter','-noreadconfig','-rompath',str(profile.rompath),
|
||||
'-bios',profile.bios,'-kbd','ms_naturl,bios=sp2k','-video','soft','-window',
|
||||
'-sound','none','-skip_gameinfo','-beta:wd179x:0','35hd',
|
||||
'-flop1',str(disk_path),'-hard1',str(system),'-debug','-debugger',args.debugger,
|
||||
'-plugin','sdbgbridge','-pluginspath',str(ROOT/'toolchain/mcp')+';'+str(ROOT/'mame/sources/MAME/plugins'),
|
||||
'-beta:wd179x:1','35hd','-flop1',str(disk_path),
|
||||
'-flop2',str(profile.dss_image),'-hard1',str(system),
|
||||
'-debug','-debugger',args.debugger,
|
||||
'-plugin','sdbgbridge','-pluginspath',';'.join(plugin_paths),
|
||||
'-autoboot_delay','0','-autoboot_script',str(lua)]
|
||||
for name in ('nvram','cfg','diff','snapshot'):
|
||||
command.extend(['-'+name+'_directory',str(state/name)])
|
||||
@@ -250,7 +252,7 @@ def main():
|
||||
'--socket',args.socket],stdout=subprocess.PIPE,stderr=sys.stderr,text=True)
|
||||
line=server.stdout.readline()
|
||||
if not line:
|
||||
raise RuntimeError('Session server не запустился')
|
||||
raise RuntimeError('Session server не запустился'+mame_diagnostics())
|
||||
server_ready=json.loads(line)
|
||||
print(json.dumps({'ready':True,'socket':args.socket,'pid':mame_process.pid,
|
||||
'entry':entry,'build_id':server_ready['build_id']},
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2026 Sprinter C Compiler contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -30,7 +30,7 @@ function projectForLaunch(configuration, workspace) {
|
||||
|
||||
function isSprinterMakefile(filename) {
|
||||
try {
|
||||
return /include\s+\$\(PROJ_ROOT\)\/app\.mk/.test(
|
||||
return /include\s+(?:\$\((?:PROJ_ROOT|SPRINTER_ROOT)\)\/app\.mk|\$\(CURDIR\)\/(?:\.\.\/){0,2}sdk\.mk)/.test(
|
||||
fs.readFileSync(filename, 'utf8'));
|
||||
} catch (_) {
|
||||
return false;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
const path = require('path');
|
||||
const vscode = require('vscode');
|
||||
const {resolvePython, pyenvEnvironment} = require('./runtime');
|
||||
const {resolvePython, pyenvEnvironment, resolveSdkRoot} = require('./runtime');
|
||||
const {projectForLaunch, isSprinterMakefile, taskLabel, makeCommand} =
|
||||
require('./build');
|
||||
|
||||
@@ -13,10 +13,9 @@ class SprinterAdapterFactory {
|
||||
const settings = vscode.workspace.getConfiguration('sprinterDebugger');
|
||||
const folder = session.workspaceFolder || vscode.workspace.workspaceFolders?.[0];
|
||||
const configured = session.configuration.adapterPath;
|
||||
if (!configured && !folder) {
|
||||
throw new Error('Откройте workspace C-Compiler или задайте adapterPath');
|
||||
}
|
||||
const adapter = configured || path.join(folder.uri.fsPath, 'toolchain', 'sdbg_dap.py');
|
||||
const sdk = configured ? null : resolveSdkRoot(
|
||||
settings.get('sdkRoot'), folder?.uri.fsPath);
|
||||
const adapter = configured || path.join(sdk, 'toolchain', 'sdbg_dap.py');
|
||||
const runtime = resolvePython({
|
||||
command: settings.get('pythonCommand', 'auto'),
|
||||
args: settings.get('pythonArguments', []),
|
||||
@@ -45,8 +44,10 @@ class SprinterTaskProvider {
|
||||
const definition = definitionOverride ||
|
||||
{type: 'sprinter', project: relative || '.'};
|
||||
const options = {cwd: project};
|
||||
const env = pyenvEnvironment(runtime.command, project) ||
|
||||
pyenvEnvironment(runtime.command, folder.uri.fsPath);
|
||||
const sdk = resolveSdkRoot(settings.get('sdkRoot'), folder.uri.fsPath);
|
||||
const env = {SPRINTER_ROOT: sdk, ...(
|
||||
pyenvEnvironment(runtime.command, project) ||
|
||||
pyenvEnvironment(runtime.command, folder.uri.fsPath) || {})};
|
||||
if (env) options.env = env;
|
||||
const task = new vscode.Task(
|
||||
definition, folder, taskLabel(project, folder.uri.fsPath), 'sprinter',
|
||||
@@ -134,6 +135,9 @@ class SprinterConfigurationProvider {
|
||||
const workspace = folder?.uri.fsPath ||
|
||||
vscode.workspace.workspaceFolders?.[0]?.uri.fsPath;
|
||||
if (!workspace) return configuration;
|
||||
const mameHome = vscode.workspace.getConfiguration('sprinterDebugger')
|
||||
.get('mameHome');
|
||||
if (!configuration.mameHome && mameHome) configuration.mameHome = mameHome;
|
||||
try {
|
||||
const project = projectForLaunch(configuration, workspace);
|
||||
if (project && isSprinterMakefile(path.join(project, 'Makefile'))) {
|
||||
|
||||
@@ -52,6 +52,16 @@
|
||||
"items": {"type": "string"},
|
||||
"default": [],
|
||||
"description": "Аргументы пользовательской Python-команды перед путём DAP-адаптера"
|
||||
},
|
||||
"sprinterDebugger.sdkRoot": {
|
||||
"type": "string",
|
||||
"default": "",
|
||||
"description": "Путь к Sprinter-CC для внешнего проекта; можно задать SPRINTER_ROOT"
|
||||
},
|
||||
"sprinterDebugger.mameHome": {
|
||||
"type": "string",
|
||||
"default": "",
|
||||
"description": "Подготовленная среда MAME для launch, если не указана в launch.json"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -84,7 +94,32 @@
|
||||
},
|
||||
"mame": {
|
||||
"type": "string",
|
||||
"description": "Путь к mame.arm"
|
||||
"description": "Совместимый alias пути к mame.arm"
|
||||
},
|
||||
"mameHome": {
|
||||
"type": "string",
|
||||
"description": "Среда MAME: стандартные пути к бинарнику, ROM и DSS"
|
||||
},
|
||||
"mameBin": {
|
||||
"type": "string",
|
||||
"description": "Переопределить бинарник MAME (stock или sdbg)"
|
||||
},
|
||||
"mameRompath": {
|
||||
"type": "string",
|
||||
"description": "Переопределить каталог ROM/BIOS MAME"
|
||||
},
|
||||
"mameDssImage": {
|
||||
"type": "string",
|
||||
"description": "Переопределить образ DSS-дискеты"
|
||||
},
|
||||
"mameSystemHddImage": {
|
||||
"type": "string",
|
||||
"description": "Переопределить системный HDD-образ Sprinter"
|
||||
},
|
||||
"mameBios": {
|
||||
"type": "string",
|
||||
"default": "v3.06",
|
||||
"description": "Имя варианта BIOS для -bios"
|
||||
},
|
||||
"debugger": {
|
||||
"type": "string",
|
||||
@@ -166,5 +201,5 @@
|
||||
"check": "node --check extension.js && node --check build.js && node --test runtime.test.js build.test.js extension.test.js"
|
||||
},
|
||||
"files": ["extension.js", "runtime.js", "build.js", "README.md"],
|
||||
"license": "BSD-3-Clause"
|
||||
"license": "MIT"
|
||||
}
|
||||
|
||||
@@ -80,5 +80,18 @@ function pyenvEnvironment(command, workspace, home = os.homedir()) {
|
||||
return version ? {PYENV_VERSION: version} : undefined;
|
||||
}
|
||||
|
||||
function resolveSdkRoot(configured, workspace, environment = process.env) {
|
||||
const candidates = [configured, environment.SPRINTER_ROOT, workspace]
|
||||
.filter(Boolean).map(value => path.resolve(value));
|
||||
const found = candidates.find(root =>
|
||||
fs.existsSync(path.join(root, 'toolchain', 'sdbg_dap.py')) &&
|
||||
fs.existsSync(path.join(root, 'bin', 'sprinter-cc')));
|
||||
if (!found) {
|
||||
throw new Error('Не найден Sprinter-CC: задайте sprinterDebugger.sdkRoot ' +
|
||||
'или SPRINTER_ROOT');
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
module.exports = {resolvePython, localPythonVersion, installedPyenvPythons,
|
||||
pyenvEnvironment};
|
||||
pyenvEnvironment, resolveSdkRoot};
|
||||
|
||||
Reference in New Issue
Block a user