#!/usr/bin/env python3 """Compile and debug real native programs from the immutable development SYSTEM.""" import base64 import json from pathlib import Path import shlex import subprocess import sys import tarfile import tempfile import time project = Path(__file__).resolve().parents[2] sys.path.insert(0, str(project / 'tools')) from image_formats import digest from vm_test import VM work = Path(tempfile.mkdtemp(prefix='m12-development.', dir=project / 'out')) rootfs = (project / 'out/rootfs-development.tar').resolve(strict=True) image = (project / 'out/fds-system-development.img').resolve(strict=True) with tarfile.open(rootfs) as archive: assert archive.extractfile('usr/share/fds/image-profile').read().strip() == b'development' before = digest(image) (work / 'inputs.sha256').write_text(f'{digest(rootfs)} {rootfs}\n{before} {image}\n') with VM(work, 'toolchain', image) as vm: vm.expect(rb'FDS> ') assert vm.capture('id -u') == '1000' vm.capture('test "$(date -u +%s)" -ge "$(cat /usr/share/fds/build-epoch)"') clock = json.loads(vm.capture('cat /run/fds/clock.json')) assert clock['source'] == 'image_floor' and clock['previous_unix_seconds'] < clock['image_epoch'], clock (work / 'clock.json').write_text(json.dumps(clock, indent=2) + '\n') vm.capture('fds-boottrace clock-floor', ok=False) vm.capture('fds-release --version') versions = vm.capture('for tool in gcc g++ make cmake meson ninja pkg-config rustc cargo git gdb strace vim; do "$tool" --version | head -n 1; done') (work / 'tool-versions.txt').write_text(versions + '\n') vm.capture('mkdir -p /tmp/development/c /tmp/development/rust/src') files = { '/tmp/development/c/main.c': '#include \nint main(void) { puts("FDS C OK"); return 0; }\n', '/tmp/development/c/main.cpp': '#include \nint main() { std::cout << "FDS C++ OK\\n"; }\n', '/tmp/development/c/CMakeLists.txt': 'cmake_minimum_required(VERSION 3.20)\nproject(fds_development C CXX)\nadd_executable(fds_c main.c)\nadd_executable(fds_cpp main.cpp)\n', '/tmp/development/c/meson.build': "project('fds-development', 'c')\nexecutable('fds_meson', 'main.c')\n", '/tmp/development/c/Makefile': 'fds_make: main.c\n\t$(CC) -g -O0 -o $@ $<\n', '/tmp/development/rust/Cargo.toml': '[package]\nname="fds-development-test"\nversion="0.1.0"\nedition="2024"\n', '/tmp/development/rust/src/main.rs': 'fn main() { println!("FDS RUST OK"); }\n', } for path, text in files.items(): # Serial input passes through interactive Bash readline. Encode the # bytes so a Makefile tab cannot be consumed as tab completion. encoded = base64.b64encode(text.encode()).decode('ascii') vm.capture('printf %s ' + shlex.quote(encoded) + ' | base64 -d >' + shlex.quote(path)) vm.capture('cmake -S /tmp/development/c -B /tmp/development/cmake -G Ninja && cmake --build /tmp/development/cmake', timeout=600) assert vm.capture('/tmp/development/cmake/fds_c') == 'FDS C OK' assert vm.capture('/tmp/development/cmake/fds_cpp') == 'FDS C++ OK' vm.capture('meson setup /tmp/development/meson /tmp/development/c && ninja -C /tmp/development/meson', timeout=600) assert vm.capture('/tmp/development/meson/fds_meson') == 'FDS C OK' vm.capture('make -C /tmp/development/c', timeout=180) assert vm.capture('/tmp/development/c/fds_make') == 'FDS C OK' vm.capture('cd /tmp/development/rust && cargo build --offline', timeout=600) assert vm.capture('/tmp/development/rust/target/debug/fds-development-test') == 'FDS RUST OK' debug = vm.capture("gdb -q -batch -ex 'break main' -ex run -ex bt -ex continue --args /tmp/development/c/fds_make", timeout=180) assert 'Breakpoint 1' in debug and 'main' in debug and 'FDS C OK' in debug, debug (work / 'gdb.txt').write_text(debug + '\n') vm.capture('strace -o /tmp/development/strace.txt -e trace=write /tmp/development/c/fds_make && grep -q "FDS C OK" /tmp/development/strace.txt') vm.capture('git -C /tmp/development init && git -C /tmp/development add c/main.c && git -C /tmp/development -c user.name=FDS -c user.email=test.invalid@example.invalid commit -m "Local development acceptance"') vm.capture("vim -Nu NONE -n -es /tmp/development/editor.txt -c 'call setline(1, \"FDS EDITOR OK\")' -c wq") assert vm.capture('cat /tmp/development/editor.txt') == 'FDS EDITOR OK' vm.capture('pkg-config --version') vm.capture('test "$(findmnt -nro FSTYPE /)" = erofs && findmnt -nro OPTIONS / | grep -qw ro') vm.capture('touch /etc/development-write-probe', ok=False) vm.capture('! pgrep -x Xorg && ! pgrep -x dhcpcd && ! pgrep -x sshd && ! pgrep -x dbus-daemon') boot = json.loads(vm.capture('fds --json boot-profile')) (work / 'boot-profile.json').write_text(json.dumps(boot, indent=2) + '\n') deadline = time.monotonic() + 60 while vm.capture('test -S /run/fds/control.sock && echo ready || echo pending') != 'ready': assert time.monotonic() < deadline vm.send('fds poweroff') vm.expect(rb'reboot: Power down') assert vm.child.wait(timeout=20) == 0 assert digest(image) == before link = project / 'out/m12-development-latest.next' link.symlink_to(work.name) link.replace(project / 'out/m12-development-latest') print(f'PASS: native C/C++/Rust compilation and execution, CMake/Meson/Ninja/Make, GDB/strace, Git/editor, read-only SYSTEM and native shutdown: {work}') print('SKIP: Pi compiler throughput and physical boot timing require hardware')