Files
ardupilot/Tools/renode/build_bundle.py
T
Andrew Tridgell e2ef2d8dbb Tools: include process helpers in Renode runtime bundles
The bundled launcher and runner import process_utils, but the runtime file list omitted it. Include the dependency and exercise both entry points from the isolated bundle.
2026-09-05 20:44:29 +10:00

210 lines
7.2 KiB
Python
Executable File

#!/usr/bin/env python3
# AP_FLAKE8_CLEAN
"""Build a source-checkout-independent ArduPilot Renode runtime tree."""
import argparse
import hashlib
import json
import os
import shutil
import stat
import sys
import tempfile
from pathlib import Path
RUNTIME_FILES = (
'README.md',
'THIRD_PARTY_NOTICES.md',
'build_bundle.py',
'chibios_gdb.py',
'device_emulator.py',
'dfu.py',
'driver_catalog.py',
'extract_logs.py',
'fat_image.py',
'gen_board.py',
'launch.py',
'physics_protocol.py',
'process_utils.py',
'renode_data.py',
'renode_firmware.py',
'run.py',
'usbip_attach.py',
)
RUNTIME_TREES = {
'peripherals': {'.cs'},
'platforms': {'.repl'},
'scripts': {'.resc'},
}
HWDEF_SUFFIXES = {'.dat', '.inc', '.parm'}
HWDEF_SCRIPT_SUFFIXES = {'.py', '.h'}
DMAMUX_HEADERS = (
'modules/ChibiOS/os/hal/ports/STM32/STM32G4xx/stm32_dmamux.h',
'modules/ChibiOS/os/hal/ports/STM32/STM32H7xx/stm32_dmamux.h',
'modules/ChibiOS/os/hal/ports/STM32/STM32L4xx+/stm32_dmamux.h',
)
def _copy_file(root, destination, relative):
source = root / relative
if not source.is_file():
raise FileNotFoundError(source)
target = destination / relative
target.parent.mkdir(parents=True, exist_ok=True)
shutil.copy2(source, target)
def _copy_tree(source, target, suffixes=None):
if not source.is_dir():
raise FileNotFoundError(source)
for path in sorted(source.rglob('*')):
if (path.is_file() and
(suffixes is None or path.suffix.lower() in suffixes) and
'__pycache__' not in path.parts):
destination = target / path.relative_to(source)
destination.parent.mkdir(parents=True, exist_ok=True)
shutil.copy2(path, destination)
def _sha256(path):
digest = hashlib.sha256()
with path.open('rb') as stream:
for chunk in iter(lambda: stream.read(1024 * 1024), b''):
digest.update(chunk)
return digest.hexdigest()
def _manifest(destination, physics_name):
files = []
for path in sorted(destination.rglob('*')):
if path.is_file() and path.name != 'bundle.json':
files.append({
'path': path.relative_to(destination).as_posix(),
'size': path.stat().st_size,
'sha256': _sha256(path),
})
return {
'format': 1,
'launcher': 'Tools/renode/launch.py',
'physics': 'Tools/renode/bin/%s' % physics_name,
'files': files,
}
def _claim_output(destination):
destination.parent.mkdir(parents=True, exist_ok=True)
lock = destination.with_name('.%s.bundle-lock' % destination.name)
descriptor = os.open(lock, os.O_CREAT | os.O_RDWR, 0o600)
try:
if os.name == 'nt':
import msvcrt
if os.fstat(descriptor).st_size == 0:
os.write(descriptor, b'\0')
os.lseek(descriptor, 0, os.SEEK_SET)
msvcrt.locking(descriptor, msvcrt.LK_NBLCK, 1)
else:
import fcntl
fcntl.flock(descriptor, fcntl.LOCK_EX | fcntl.LOCK_NB)
except OSError as error:
os.close(descriptor)
raise FileExistsError(
'another bundle build owns %s' % lock) from error
try:
if destination.exists():
raise FileExistsError('%s already exists' % destination)
os.ftruncate(descriptor, 0)
os.lseek(descriptor, 0, os.SEEK_SET)
os.write(descriptor, ('pid=%u\n' % os.getpid()).encode())
except Exception:
os.close(descriptor)
raise
return descriptor
def create_bundle(root, destination, physics_binary):
"""Create an atomic runtime tree and return its generated manifest."""
root = Path(root).resolve()
destination = Path(destination).resolve()
physics_binary = Path(physics_binary).resolve()
if not physics_binary.is_file():
raise FileNotFoundError(physics_binary)
physics_name = ('renode-physics.exe'
if physics_binary.suffix.lower() == '.exe'
else 'renode-physics')
lock_descriptor = _claim_output(destination)
try:
temporary = Path(tempfile.mkdtemp(
prefix='.%s-' % destination.name, dir=destination.parent))
try:
renode_source = root / 'Tools' / 'renode'
for name in RUNTIME_FILES:
_copy_file(root, temporary, Path('Tools') / 'renode' / name)
for name, suffixes in RUNTIME_TREES.items():
_copy_tree(renode_source / name,
temporary / 'Tools' / 'renode' / name, suffixes)
hwdef = root / 'libraries' / 'AP_HAL_ChibiOS' / 'hwdef'
_copy_tree(hwdef, temporary / hwdef.relative_to(root),
HWDEF_SUFFIXES)
_copy_tree(hwdef / 'scripts',
temporary / hwdef.relative_to(root) / 'scripts',
HWDEF_SCRIPT_SUFFIXES)
_copy_tree(hwdef / 'common',
temporary / hwdef.relative_to(root) / 'common')
_copy_file(root, temporary,
Path('libraries/AP_HAL/hwdef/scripts/hwdef.py'))
_copy_file(root, temporary,
Path('Tools/AP_Bootloader/board_types.txt'))
for relative in DMAMUX_HEADERS:
_copy_file(root, temporary, Path(relative))
_copy_tree(root / 'Tools' / 'IO_Firmware',
temporary / 'Tools' / 'IO_Firmware', {'.bin'})
_copy_tree(root / 'Tools' / 'bootloaders',
temporary / 'Tools' / 'bootloaders', {'.bin'})
installed_physics = (temporary / 'Tools' / 'renode' / 'bin' /
physics_name)
installed_physics.parent.mkdir(parents=True, exist_ok=True)
shutil.copy2(physics_binary, installed_physics)
installed_physics.chmod(
installed_physics.stat().st_mode | stat.S_IXUSR)
manifest = _manifest(temporary, physics_name)
(temporary / 'bundle.json').write_text(
json.dumps(manifest, indent=2, sort_keys=True) + '\n')
if destination.exists():
raise FileExistsError('%s was created during the build' %
destination)
os.replace(temporary, destination)
return manifest
except Exception:
shutil.rmtree(temporary, ignore_errors=True)
raise
finally:
os.close(lock_descriptor)
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('output', help='new bundle directory to create')
parser.add_argument('--physics-binary', required=True,
help='native renode-physics executable to install')
args = parser.parse_args()
root = Path(__file__).resolve().parents[2]
try:
manifest = create_bundle(root, args.output, args.physics_binary)
except (FileExistsError, FileNotFoundError, OSError) as error:
parser.error(str(error))
total = sum(entry['size'] for entry in manifest['files'])
print('created %s with %u files (%u bytes)' %
(Path(args.output).resolve(), len(manifest['files']), total))
return 0
if __name__ == '__main__':
sys.exit(main())