Sprinter: добавить отладку C-исходников и интеграцию VS Code
This commit is contained in:
@@ -0,0 +1,139 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Транзакционная debug-сборка: ошибочная линковка не публикует старый ihx."""
|
||||
from __future__ import annotations
|
||||
import fcntl
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
from sdbg.build import digest, write_json
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
|
||||
|
||||
def metadata(args):
|
||||
work, exe = map(Path, args[:2])
|
||||
layout = dict(zip(('mode', 'code', 'data', 'load', 'entry', 'stack'), args[2:]))
|
||||
units = [json.loads(p.read_text()) for p in sorted(work.glob('*.sdbg-unit.json'))]
|
||||
if not units:
|
||||
raise ValueError('Нет отладочных единиц трансляции')
|
||||
inputs = {}
|
||||
snapshots = work / 'sources'
|
||||
snapshots.mkdir()
|
||||
dependencies = {}
|
||||
for unit in units + [json.loads(p.read_text()) for p in work.glob('*.sdbg-input.json')]:
|
||||
for name, expected in unit['dependency_hashes'].items():
|
||||
if name in dependencies and dependencies[name] != expected:
|
||||
raise ValueError('Зависимость изменилась между TU: ' + name)
|
||||
dependencies[name] = expected
|
||||
for name, expected in dependencies.items():
|
||||
source = Path(name)
|
||||
sha = digest(source)
|
||||
if sha != expected:
|
||||
raise ValueError('Зависимость изменилась после компиляции: ' + name)
|
||||
snapshot = 'sources/' + sha + source.suffix
|
||||
shutil.copyfile(source, work / snapshot)
|
||||
inputs[name] = {'sha256': sha, 'snapshot': snapshot}
|
||||
artifacts = {str(p.relative_to(work)): digest(p) for p in work.rglob('*') if p.is_file()}
|
||||
write_json(work / 'manifest.json', {
|
||||
'schema_version': 1, 'build_id': digest(exe), 'executable_sha256': digest(exe),
|
||||
'executable': exe.name, 'layout': layout, 'units': units,
|
||||
'sources': inputs, 'artifacts': artifacts,
|
||||
'sdcc': subprocess.check_output([str(ROOT/'third_party/sdcc/bin/sdcc'), '--version'], text=True).strip(),
|
||||
'limitations': ['Нет карты библиотек', 'Нет location ranges локальных',
|
||||
'Только оффлайновая карта; образ в MAME не проверен'],
|
||||
})
|
||||
|
||||
|
||||
def run(wrapper, args):
|
||||
if '-o' not in args or args.index('-o') + 1 >= len(args):
|
||||
raise ValueError('Требуется -o FILE')
|
||||
if '--src-debug' in args and '--src-debug-file' in args:
|
||||
raise ValueError('--src-debug и --src-debug-file взаимоисключающие')
|
||||
# Валидируем выбранные TU до запуска compiler. Значения иных опций не TU.
|
||||
sources, selected = [], []
|
||||
i = 0
|
||||
valued = {'-o', '-I', '-L', '-E', '-S', '--code-loc', '--data-loc',
|
||||
'--memory', '--memory-manual', '--stack-size', '-Wl', '--mkexe',
|
||||
'--max-allocs', '--gfx'}
|
||||
while i < len(args):
|
||||
arg = args[i]
|
||||
if arg in valued | {'--bank', '--w3', '--src-debug-file'}:
|
||||
if i + 1 == len(args):
|
||||
raise ValueError(arg + ' требует аргумент')
|
||||
value = args[i+1]
|
||||
if arg == '--bank': sources.append(Path(value.split('=', 1)[-1]).resolve())
|
||||
if arg == '--w3': sources.append(Path(value).resolve())
|
||||
if arg == '--src-debug-file': selected.append(Path(value).resolve())
|
||||
i += 2
|
||||
elif arg == '--bank-data' and i+1 < len(args) and args[i+1].isdigit():
|
||||
i += 2
|
||||
else:
|
||||
if not arg.startswith('-'): sources.append(Path(arg).resolve())
|
||||
i += 1
|
||||
if any(p not in sources or not p.is_file() for p in selected):
|
||||
raise ValueError('--src-debug-file должен указывать на входной TU')
|
||||
output = Path(args[args.index('-o')+1]).resolve()
|
||||
output.parent.mkdir(parents=True, exist_ok=True)
|
||||
final_work = output.parent / ('.sprinter-cc-' + output.stem)
|
||||
locks = output.parent / '.resource-stamps'
|
||||
locks.mkdir(exist_ok=True)
|
||||
with (locks / (final_work.name + '.lock')).open('a') as lock:
|
||||
fcntl.flock(lock, fcntl.LOCK_EX)
|
||||
with tempfile.TemporaryDirectory(prefix='.sdbg-build-', dir=output.parent) as temporary:
|
||||
stage = Path(temporary)
|
||||
new_exe = stage / output.name
|
||||
command = list(args)
|
||||
command[command.index('-o')+1] = str(new_exe)
|
||||
env = dict(os.environ, SPRINTER_SDBG_ACTIVE='1', SPRINTER_PYTHON=sys.executable)
|
||||
subprocess.run([str(Path(wrapper).resolve()), *command], env=env, check=True)
|
||||
work = stage / final_work.name
|
||||
manifest = json.loads((work/'manifest.json').read_text())
|
||||
manifest['command'] = [str(Path(wrapper).resolve()), *args]
|
||||
manifest['working_directory'] = str(Path.cwd())
|
||||
manifest['selected_sources'] = [str(p) for p in selected or sources]
|
||||
manifest['input_sources'] = [str(p) for p in sources]
|
||||
manifest['executable_path'] = str(output)
|
||||
identity = {'executable': manifest['executable_sha256'],
|
||||
'sources': {p: v['sha256'] for p,v in manifest['sources'].items()},
|
||||
'command': manifest['command'], 'sdcc': manifest['sdcc']}
|
||||
manifest['build_id'] = hashlib.sha256(json.dumps(identity, sort_keys=True).encode()).hexdigest()
|
||||
write_json(work/'manifest.json', manifest)
|
||||
# Проверяем пакет до публикации. Старые exe/work переживают любой
|
||||
# сбой compiler/linker/парсера; manifest не создаётся из stale ihx.
|
||||
from sdbg.model import DebugMap
|
||||
model = DebugMap(work)
|
||||
index = work/(output.stem+'.sdbg.json')
|
||||
write_json(index, {'schema_version': 1, 'build_id': manifest['build_id'],
|
||||
'functions': model.functions, 'variables': model.variables,
|
||||
'instructions': list(model.instructions.values()),
|
||||
'markers': model.markers, 'logpoints': model.logpoints})
|
||||
manifest['artifacts'][index.name] = digest(index)
|
||||
write_json(work/'manifest.json', manifest)
|
||||
backup = stage/'previous-work'
|
||||
if final_work.exists(): final_work.rename(backup)
|
||||
try:
|
||||
work.rename(final_work)
|
||||
os.replace(new_exe, output)
|
||||
except BaseException:
|
||||
if final_work.exists(): shutil.rmtree(final_work)
|
||||
if backup.exists(): backup.rename(final_work)
|
||||
raise
|
||||
print('sdbg: пакет ' + str(final_work))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
try:
|
||||
if sys.argv[1:2] == ['--metadata']: metadata(sys.argv[2:])
|
||||
else: run(sys.argv[1], sys.argv[2:])
|
||||
except subprocess.CalledProcessError as error:
|
||||
print(f'sdbg: сборка не прошла (код {error.returncode}); предыдущий пакет сохранён',
|
||||
file=sys.stderr)
|
||||
sys.exit(1)
|
||||
except (ValueError, OSError) as error:
|
||||
print('sdbg: ' + str(error), file=sys.stderr)
|
||||
sys.exit(1)
|
||||
Reference in New Issue
Block a user