meta: replace cmake mono-build system with a custom build co-ordinator

every system component now has its own self-contained build system, and a
seed file describing the component and its build system and dependencies.

a new tool, meadow, collects and uses these seed files to build the
components into a full system. meadow also manages the target system root
and a host prefix where toolchain tools are installed to.

the build system has also been updated to use a new $ARCH-rosetta-gcc
compiler, and the patches files necessary to build it have been
added to toolchain/cross-compiler.
This commit is contained in:
2026-07-19 13:34:32 +01:00
parent 59034be9e6
commit 1ec33767ab
104 changed files with 7111 additions and 257 deletions
+41
View File
@@ -0,0 +1,41 @@
class Args:
def __init__(self, argv):
self.__argv = argv
def get_positional(self, index):
if len(self.__argv) > index:
return self.__argv[index]
return None
def get_named(self, name, index, nr_args):
found_name = False
values = []
for s in self.__argv:
if found_name:
if len(values) < nr_args:
values.append(s)
continue
else:
break
if s == '--{}'.format(name):
if index > 0:
index -= 1
continue
found_name = True
continue
if nr_args == 0:
return found_name
if len(values) < nr_args or not found_name:
return None
if len(values) == 1:
return values[0]
return values
+98
View File
@@ -0,0 +1,98 @@
import os
import struct
import tarfile
from pathlib import Path
from elf import ELF64Image
class BSPWriteError(Exception):
pass
class BSP:
def __init__(self, out_path, **kwargs):
if os.path.exists(out_path):
os.remove(out_path)
self.__bootstrap_exec_path = None
self.__path = out_path
self.__sysroot = kwargs.get('sysroot', None)
out_dir = os.path.dirname(out_path)
Path(out_dir).mkdir(parents=True, exist_ok=True)
self.__tarfile = tarfile.open(name=out_path, mode='x')
def add_file(self, src, dst):
if self.__sysroot is not None:
while src[0] == '/':
src = src[1:]
src = os.path.join(self.__sysroot, src)
print(f'tarfile.add({src}, arcname={dst})')
self.__tarfile.add(src, arcname=dst)
def set_bootstrap_program(self, path):
if self.__sysroot is not None:
while path[0] == '/':
path = path[1:]
path = os.path.join(self.__sysroot, path)
self.__bootstrap_exec_path = path
def __write_trailer(self):
bootstrap_offset = os.path.getsize(self.__path)
print(f'bootstrap_offset was {bootstrap_offset}')
bsp = open(self.__path, mode='ab')
prog = open(self.__bootstrap_exec_path, mode='rb')
padding = 0x1000 - (bootstrap_offset % 0x1000)
bsp.write(b'\0' * padding)
bootstrap_offset += padding
prog_len = 0
print(f'bootstrap_offset is now {bootstrap_offset}')
while True:
data = prog.read(1024)
bsp.write(data)
prog_len += len(data)
if len(data) < 1024:
break
prog.close()
prog_elf = ELF64Image()
if prog_elf.load(self.__bootstrap_exec_path) != 0:
raise BSPWriteError(f'{self.__bootstrap_exec_path} is not a valid ELF binary')
for ph in prog_elf.ph_list:
print('{}: {}/{} ({}/{} bytes) {}'.format(
ph.type,
ph.offset, ph.vaddr,
ph.filesz, ph.memsz, ph.flags))
trailer = struct.pack('>IQIQIQQQQQQQ',
0xcafebabe,
0, bootstrap_offset,
bootstrap_offset, prog_len,
prog_elf.text.offset, prog_elf.text.vaddr, prog_elf.text.memsz,
prog_elf.data.offset, prog_elf.data.vaddr, prog_elf.data.memsz,
prog_elf.entry)
bsp.write(trailer)
bsp.close()
prog.close()
def __del__(self):
self.close()
def close(self):
self.__tarfile.close()
if self.__bootstrap_exec_path is None:
return
self.__write_trailer()
+102
View File
@@ -0,0 +1,102 @@
from enum import Enum, Flag
import struct
class ELFPType(Enum):
NULL = 0
LOAD = 1
DYNAMIC = 2
INTERP = 3
NOTE = 4
SHLIB = 5
PHDR = 6
TLS = 7
class ELFPFlag(Flag):
NONE = 0
EXEC = 1
WRITE = 2
READ = 4
class ELFEndian(Enum):
LITTLE = 1
BIG = 2
class ELF64Phdr:
def __init__(self):
self.type = ELFPType.NULL
self.flags = 0
self.offset = 0
self.vaddr = 0
self.paddr = 0
self.filesz = 0
self.memsz = 0
self.align = 0
return
def parse(self, buf, endian):
endian_marker = '>' if endian == ELFEndian.BIG else '<'
entry = struct.unpack('{}IIQQQQQQ'.format(endian_marker), buf)
self.type = ELFPType(entry[0])
self.flags = ELFPFlag.NONE
self.offset = entry[2]
self.vaddr = entry[3]
self.paddr = entry[4]
self.filesz = entry[5]
self.memsz = entry[6]
self.align = entry[7]
if entry[1] & ELFPFlag.READ.value: self.flags |= ELFPFlag.READ
if entry[1] & ELFPFlag.WRITE.value: self.flags |= ELFPFlag.WRITE
if entry[1] & ELFPFlag.EXEC.value: self.flags |= ELFPFlag.EXEC
class ELF64Image:
def __init__(self):
self.ph_list = []
self.text = None
self.data = None
self.entry = None
def load(self, path):
f = open(path, mode='rb')
ident_bytes = f.read(16)
ident = struct.unpack('BBBBBBBBBBBBBBBB', ident_bytes)
if ident[0] != 0x7F or ident[1] != ord('E') or ident[2] != ord('L') or ident[3] != ord('F'):
f.close()
return -1
endian = ELFEndian(ident[5])
endian_marker = '>' if endian == ELFEndian.BIG else '<'
hdr_bytes = f.read(48)
hdr = struct.unpack('{}HHIQQQIHHHHHH'.format(endian_marker), hdr_bytes)
self.entry = hdr[3]
phoff = hdr[4]
phnum = hdr[9]
phentsize = hdr[8]
for i in range(0, phnum):
f.seek(phoff + (i * phentsize))
phdr_bytes = f.read(phentsize)
phdr = ELF64Phdr()
phdr.parse(phdr_bytes, endian)
if phdr.flags == ELFPFlag.READ | ELFPFlag.EXEC:
self.text = phdr
if phdr.flags == ELFPFlag.READ | ELFPFlag.WRITE:
self.data = phdr
self.ph_list.append(phdr)
f.close()
return 0
+46
View File
@@ -0,0 +1,46 @@
import sys
import os
import tomli
from colorama import init as colorama_init
from colorama import Fore, Style
from args import Args
from preset import Preset
from bsp import BSP
def main():
colorama_init()
args = Args(sys.argv)
preset_path = args.get_named('preset', 0, 1)
if preset_path is None:
print(f'{Fore.RED}Err:{Style.RESET_ALL} no preset specified')
return -1
out_path = args.get_named('out', 0, 1)
if out_path is None:
print(f'{Fore.RED}Err:{Style.RESET_ALL} no output path specified')
return -1
sysroot_path = args.get_named('sysroot', 0, 1)
if sysroot_path is None:
sysroot_path = '/'
preset = Preset(preset_path)
bsp = BSP(out_path, sysroot=sysroot_path)
for dst, src in preset.contents().items():
for src_filepath in src:
src_filename = os.path.basename(src_filepath)
dst_filepath = os.path.join(dst, src_filename)
bsp.add_file(src_filepath, dst_filepath)
bsp.set_bootstrap_program(preset.bootstrap_program())
bsp.close()
return 0
if __name__ == '__main__':
exit(main())
+18
View File
@@ -0,0 +1,18 @@
#!/bin/bash
SOURCE_ROOT="$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )/../.."
if [ ! -d "$SOURCE_ROOT/build" ]; then
mkdir build
fi
if [ ! -d "$SOURCE_ROOT/build/mkbsp_venv" ]; then
echo 'Initialising mkbsp...'
python3 -m venv $SOURCE_ROOT/build/mkbsp_venv
source $SOURCE_ROOT/build/mkbsp_venv/bin/activate
pip install -r $SOURCE_ROOT/toolchain/mkbsp/requirements.txt
else
source $SOURCE_ROOT/build/mkbsp_venv/bin/activate
fi
python3 $SOURCE_ROOT/toolchain/mkbsp/main.py "$@"
+66
View File
@@ -0,0 +1,66 @@
import os
import sys
import json
import tomli
class PresetParseError(Exception):
pass
class Preset:
def __collect_preset_files(self):
result = []
for root, dirs, files in os.walk(self.__path):
for f in files:
name = f
path = os.path.join(root, f)
result.append((name, path))
return result
def __apply_preset_file(self, data):
bootstrap = data.get('bootstrap', None)
contents = data.get('contents', None)
if bootstrap is not None:
self.__bootstrap = bootstrap
if contents is not None:
for dest, src in contents.items():
dest_list = self.__contents.get(dest, [])
for p in src:
dest_list.append(p)
self.__contents[dest] = dest_list
def __read_preset_files(self):
for n, p in self.__files:
data = None
try:
f = open(p, mode='rb')
data = tomli.load(f)
except Exception as e:
raise ParseError('Err: Failed to parse seed file {}: {}'.format(seed_path, e))
self.__apply_preset_file(data)
def __init__(self, path):
self.__bootstrap = {}
self.__contents = {}
self.__path = path
self.__files = self.__collect_preset_files()
self.__files = sorted(self.__files, key=lambda tup: tup[0])
self.__read_preset_files()
def contents(self):
return self.__contents
def bootstrap_program(self):
return self.__bootstrap.get('bootstrap_exec', None)
+2
View File
@@ -0,0 +1,2 @@
tomli
colorama