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:
@@ -0,0 +1,139 @@
|
||||
diff --git a/bfd/config.bfd b/bfd/config.bfd
|
||||
index 4dde5321f23..94c1a871ec2 100644
|
||||
--- a/bfd/config.bfd
|
||||
+++ b/bfd/config.bfd
|
||||
@@ -242,7 +242,17 @@ esac
|
||||
|
||||
case "${targ}" in
|
||||
# START OF targmatch.h
|
||||
+ i[3-7]86-*-rosetta*)
|
||||
+ targ_defvec=i386_elf32_vec
|
||||
+ targ_selvecs=
|
||||
+ targ64_selvecs=x86_64_elf64_vec
|
||||
+ ;;
|
||||
#ifdef BFD64
|
||||
+ x86_64-*-rosetta*)
|
||||
+ targ_defvec=x86_64_elf64_vec
|
||||
+ targ_selvecs=i386_elf32_vec
|
||||
+ want64=true
|
||||
+ ;;
|
||||
aarch64-*-darwin*)
|
||||
targ_defvec=aarch64_mach_o_vec
|
||||
targ_selvecs="arm_mach_o_vec mach_o_le_vec mach_o_be_vec mach_o_fat_vec"
|
||||
diff --git a/config.sub b/config.sub
|
||||
index 3d35cde174d..19cd8f07cd7 100755
|
||||
--- a/config.sub
|
||||
+++ b/config.sub
|
||||
@@ -2102,6 +2102,7 @@ case $os in
|
||||
| rhapsody* \
|
||||
| riscix* \
|
||||
| riscos* \
|
||||
+ | rosetta* \
|
||||
| rtems* \
|
||||
| rtmk* \
|
||||
| rtu* \
|
||||
diff --git a/gas/configure.tgt b/gas/configure.tgt
|
||||
index f0ea55d9def..4f194e70501 100644
|
||||
--- a/gas/configure.tgt
|
||||
+++ b/gas/configure.tgt
|
||||
@@ -126,6 +126,7 @@ esac
|
||||
generic_target=${cpu_type}-$vendor-$os
|
||||
# Note: This table is alpha-sorted, please try to keep it that way.
|
||||
case ${generic_target} in
|
||||
+ i386-*-rosetta*) fmt=elf em=gnu;;
|
||||
aarch64*-*-elf*) fmt=elf;;
|
||||
aarch64*-*-fuchsia*) fmt=elf;;
|
||||
aarch64*-*-haiku*) fmt=elf em=haiku ;;
|
||||
diff --git a/ld/Makefile.am b/ld/Makefile.am
|
||||
index c219662d2e3..8d17fed7581 100644
|
||||
--- a/ld/Makefile.am
|
||||
+++ b/ld/Makefile.am
|
||||
@@ -153,6 +153,7 @@ endif
|
||||
|
||||
# These all start with e so 'make clean' can find them.
|
||||
ALL_EMULATION_SOURCES = \
|
||||
+ eelf_i386_rosetta.c \
|
||||
eaix5ppc.c \
|
||||
eaix5rs6.c \
|
||||
eaixppc.c \
|
||||
@@ -373,6 +374,7 @@ ALL_EMULATION_SOURCES = \
|
||||
ALL_EMULATIONS = $(ALL_EMULATION_SOURCES:.c=.@OBJEXT@)
|
||||
|
||||
ALL_64_EMULATION_SOURCES = \
|
||||
+ eelf_x86_64_rosetta.c \
|
||||
eaarch64elf.c \
|
||||
eaarch64elf32.c \
|
||||
eaarch64elf32b.c \
|
||||
diff --git a/ld/Makefile.in b/ld/Makefile.in
|
||||
index f1ba6af5269..a4d76c4cc0b 100644
|
||||
--- a/ld/Makefile.in
|
||||
+++ b/ld/Makefile.in
|
||||
@@ -668,6 +668,7 @@ LIBIBERTY = ../libiberty/libiberty.a
|
||||
|
||||
# These all start with e so 'make clean' can find them.
|
||||
ALL_EMULATION_SOURCES = \
|
||||
+ eelf_i386_rosetta.c \
|
||||
eaix5ppc.c \
|
||||
eaix5rs6.c \
|
||||
eaixppc.c \
|
||||
@@ -887,6 +888,7 @@ ALL_EMULATION_SOURCES = \
|
||||
|
||||
ALL_EMULATIONS = $(ALL_EMULATION_SOURCES:.c=.@OBJEXT@)
|
||||
ALL_64_EMULATION_SOURCES = \
|
||||
+ eelf_x86_64_rosetta.c \
|
||||
eaarch64elf.c \
|
||||
eaarch64elf32.c \
|
||||
eaarch64elf32b.c \
|
||||
@@ -1474,6 +1476,7 @@ distclean-compile:
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/eelf_i386_be.Po@am__quote@
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/eelf_i386_fbsd.Po@am__quote@
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/eelf_i386_haiku.Po@am__quote@
|
||||
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/eelf_i386_rosetta.Po@am__quote@
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/eelf_i386_sol2.Po@am__quote@
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/eelf_i386_vxworks.Po@am__quote@
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/eelf_iamcu.Po@am__quote@
|
||||
@@ -1482,6 +1485,7 @@ distclean-compile:
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/eelf_x86_64.Po@am__quote@
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/eelf_x86_64_fbsd.Po@am__quote@
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/eelf_x86_64_haiku.Po@am__quote@
|
||||
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/eelf_x86_64_rosetta.Po@am__quote@
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/eelf_x86_64_sol2.Po@am__quote@
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/eh8300elf.Po@am__quote@
|
||||
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/eh8300elf_linux.Po@am__quote@
|
||||
diff --git a/ld/configure.tgt b/ld/configure.tgt
|
||||
index 3e158913b89..53c091fb78f 100644
|
||||
--- a/ld/configure.tgt
|
||||
+++ b/ld/configure.tgt
|
||||
@@ -82,6 +82,13 @@ fi
|
||||
# Please try to keep this table more or less in alphabetic order - it
|
||||
# makes it much easier to lookup a specific archictecture.
|
||||
case "${targ}" in
|
||||
+i[3-7]86-*-rosetta*) targ_emul=elf_i386_rosetta
|
||||
+ targ_extra_emuls=elf_i386
|
||||
+ targ64_extra_emuls="elf_x86_64_rosetta elf_x86_64"
|
||||
+ ;;
|
||||
+x86_64-*-rosetta*) targ_emul=elf_x86_64_rosetta
|
||||
+ targ_extra_emuls="elf_i386_rosetta elf_x86_64 elf_i386"
|
||||
+ ;;
|
||||
aarch64_be-*-elf) targ_emul=aarch64elfb
|
||||
targ_extra_emuls="aarch64elf aarch64elf32 aarch64elf32b armelfb armelf"
|
||||
;;
|
||||
diff --git a/ld/emulparams/elf_i386_rosetta.sh b/ld/emulparams/elf_i386_rosetta.sh
|
||||
new file mode 100644
|
||||
index 00000000000..002e918bc53
|
||||
--- /dev/null
|
||||
+++ b/ld/emulparams/elf_i386_rosetta.sh
|
||||
@@ -0,0 +1,4 @@
|
||||
+source_sh ${srcdir}/emulparams/elf_i386.sh
|
||||
+TEXT_START_ADDR=0x08000000
|
||||
+GENERATE_SHLIB_SCRIPT=yes
|
||||
+GENERATE_PIE_SCRIPT=yes
|
||||
diff --git a/ld/emulparams/elf_x86_64_rosetta.sh b/ld/emulparams/elf_x86_64_rosetta.sh
|
||||
new file mode 100644
|
||||
index 00000000000..4ddde5ab2a5
|
||||
--- /dev/null
|
||||
+++ b/ld/emulparams/elf_x86_64_rosetta.sh
|
||||
@@ -0,0 +1,3 @@
|
||||
+source_sh ${srcdir}/emulparams/elf_x86_64.sh
|
||||
+GENERATE_SHLIB_SCRIPT=yes
|
||||
+GENERATE_PIE_SCRIPT=yes
|
||||
@@ -0,0 +1,14 @@
|
||||
[command]
|
||||
name = 'build-compiler'
|
||||
description = 'Build a GCC cross-compiler that targets the Rosetta operating system'
|
||||
|
||||
[dependency]
|
||||
seeds = [
|
||||
'libc-headers',
|
||||
'magenta-headers',
|
||||
]
|
||||
|
||||
[exec]
|
||||
args = [ 'src:build.py']
|
||||
|
||||
# vim: ft=toml
|
||||
@@ -0,0 +1,31 @@
|
||||
import os
|
||||
|
||||
class Builder:
|
||||
def get_prefix(self):
|
||||
return os.environ.get('MEADOW_NATIVE_ROOT', None)
|
||||
|
||||
|
||||
def get_target(self):
|
||||
env = os.environ
|
||||
target = os.environ.get('MEADOW_TARGET_PLATFORM', None)
|
||||
parts = target.split('-')
|
||||
return f'{parts[0]}-rosetta'
|
||||
|
||||
|
||||
def download(self):
|
||||
pass
|
||||
|
||||
|
||||
def configure(self):
|
||||
pass
|
||||
|
||||
|
||||
def build(self):
|
||||
pass
|
||||
|
||||
|
||||
def install(self):
|
||||
pass
|
||||
|
||||
|
||||
print(f'building cross compiler for {get_gcc_target()}, installed to {get_prefix()}')
|
||||
@@ -0,0 +1 @@
|
||||
../binutils-gdb/configure --target=x86_64-rosetta --prefix=/opt/cross-rosetta --with-sysroot=/opt/cross-rosetta --disable-nls --disable-werror --enable-default-execstack=no --with-gmp=/opt/homebrew --enable-shared
|
||||
@@ -0,0 +1 @@
|
||||
../gcc/configure --target=x86_64-rosetta --prefix=/opt/cross-rosetta --with-sysroot=/Users/wash/projects/rosetta/build/target-root --disable-nls --enable-languages=c,c++ --enable-shared --enable-initfini-array --with-gmp=/opt/homebrew
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,42 @@
|
||||
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 nr_args > 0:
|
||||
nr_args -= 1
|
||||
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 nr_args > 0 or not found_name:
|
||||
return None
|
||||
|
||||
if len(values) == 1:
|
||||
return values[0]
|
||||
|
||||
return values
|
||||
@@ -0,0 +1,146 @@
|
||||
import os
|
||||
import shutil
|
||||
from env import Environment
|
||||
from path import Path
|
||||
from pathlib import Path as SysPath
|
||||
|
||||
|
||||
class UnknownBuildSystemError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class BuildConfigurationError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class BuildError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class BuildSystem:
|
||||
def __init__(self, **kwargs):
|
||||
self.__name = None
|
||||
self.__source_dir = None
|
||||
self.__bin_dir = None
|
||||
self.__parent = None
|
||||
self.__install = None
|
||||
|
||||
if 'name' in kwargs:
|
||||
self.__name = kwargs['name']
|
||||
if 'source_dir' in kwargs:
|
||||
self.__source_dir = kwargs['source_dir']
|
||||
if 'bin_dir' in kwargs:
|
||||
self.__bin_dir = kwargs['bin_dir']
|
||||
if 'parent' in kwargs:
|
||||
self.__parent = kwargs['parent']
|
||||
if 'install' in kwargs:
|
||||
self.__install = kwargs['install']
|
||||
|
||||
|
||||
def name(self):
|
||||
return self.__name
|
||||
|
||||
|
||||
def source_dir(self):
|
||||
return self.__source_dir
|
||||
|
||||
|
||||
def binary_dir(self):
|
||||
return self.__bin_dir
|
||||
|
||||
|
||||
def parent(self):
|
||||
return self.__parent
|
||||
|
||||
|
||||
def install_manifest(self):
|
||||
return self.__install
|
||||
|
||||
|
||||
def build_for(self):
|
||||
return 'target'
|
||||
|
||||
|
||||
def get_path_namespaces(self):
|
||||
ns = {
|
||||
'src': os.path.abspath(self.source_dir()),
|
||||
'root-src': Environment.source_root(),
|
||||
'bin': os.path.abspath(self.binary_dir()),
|
||||
}
|
||||
|
||||
if self.build_for() == 'host':
|
||||
ns['install'] = Environment.native_root()
|
||||
else:
|
||||
ns['install'] = Environment.system_root()
|
||||
|
||||
return ns
|
||||
|
||||
|
||||
def check_out_of_date(self):
|
||||
self.__config_out_of_date = self._is_configuration_out_of_date()
|
||||
self.__build_out_of_date = self._is_build_out_of_date()
|
||||
|
||||
|
||||
def is_configuration_out_of_date(self):
|
||||
return self.__config_out_of_date
|
||||
|
||||
|
||||
def is_build_out_of_date(self):
|
||||
return self.__build_out_of_date
|
||||
|
||||
|
||||
def configure(self):
|
||||
return
|
||||
|
||||
|
||||
def build(self):
|
||||
return
|
||||
|
||||
|
||||
def __copy_dir(self, src, dst):
|
||||
dst_dir = os.path.dirname(dst)
|
||||
SysPath(dst_dir).mkdir(parents=True, exist_ok=True)
|
||||
shutil.copytree(src, dst, dirs_exist_ok=True)
|
||||
|
||||
|
||||
def __copy_file(self, src, dst):
|
||||
dst_dir = os.path.dirname(dst)
|
||||
SysPath(dst_dir).mkdir(parents=True, exist_ok=True)
|
||||
shutil.copyfile(src, dst)
|
||||
|
||||
|
||||
def install_from_seed(self):
|
||||
manifest = self.install_manifest()
|
||||
if manifest is None:
|
||||
return
|
||||
|
||||
ns = self.get_path_namespaces()
|
||||
source_dir = self.source_dir()
|
||||
|
||||
dest_dir = Environment.system_root()
|
||||
if self.build_for() == 'host':
|
||||
dest_dir = Environment.native_root()
|
||||
|
||||
for dest, src in manifest.items():
|
||||
if dest[0] != '/':
|
||||
continue
|
||||
|
||||
dest = Path(dest).remove_root().get_path()
|
||||
|
||||
for p in src:
|
||||
path = Path(p)
|
||||
files = path.expand_path(relative_to=ns)
|
||||
|
||||
for p2 in files:
|
||||
file_name = os.path.basename(p2)
|
||||
rel_path = os.path.relpath(p2, start=source_dir)
|
||||
src_path = p2
|
||||
dst_path = os.path.join(dest_dir, dest, file_name)
|
||||
if os.path.isdir(src_path):
|
||||
self.__copy_dir(src_path, dst_path)
|
||||
else:
|
||||
self.__copy_file(src_path, dst_path)
|
||||
|
||||
|
||||
def install(self):
|
||||
return
|
||||
@@ -0,0 +1,237 @@
|
||||
import os
|
||||
import shutil
|
||||
from command import Command
|
||||
from config import Config
|
||||
from cache import Cache
|
||||
from seed import Seed
|
||||
from target import Target
|
||||
from env import Environment
|
||||
from platform import Platform
|
||||
from colorama import Fore, Style
|
||||
|
||||
|
||||
class SetCommand(Command):
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
name='set',
|
||||
description='Set a configuration key value.')
|
||||
|
||||
|
||||
def run(self, args):
|
||||
key = args.get_positional(0)
|
||||
value = args.get_positional(1)
|
||||
|
||||
if key is None or value is None:
|
||||
print('usage: meadow set <key> <value>')
|
||||
return -1
|
||||
|
||||
try:
|
||||
value_i = int(value)
|
||||
Config.set(key, value_i)
|
||||
except:
|
||||
Config.set(key, value)
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
class SeedsCommand(Command):
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
name='seeds',
|
||||
description='List all available seeds.')
|
||||
|
||||
|
||||
def write_dot(self, path):
|
||||
f = open(path, mode='w')
|
||||
f.write('digraph seeds {\n')
|
||||
f.write(' node [shape=Mrecord];\n')
|
||||
nodes = {}
|
||||
|
||||
seeds = Seed.all_seeds()
|
||||
targets = Target.all_targets()
|
||||
for s in seeds.values():
|
||||
if s.name() in nodes:
|
||||
continue
|
||||
|
||||
sname = s.name().replace('-', '_')
|
||||
nodes[s.name()] = s
|
||||
f.write(' {} [label="{}"];\n'.format(sname, s.name()))
|
||||
|
||||
for d in s.dependencies().values():
|
||||
continue
|
||||
|
||||
|
||||
for t in targets.values():
|
||||
if t.name() in nodes:
|
||||
continue
|
||||
|
||||
t.resolve()
|
||||
tname = t.name().replace('-', '_')
|
||||
nodes[t.name()] = t
|
||||
f.write(' {} [label="{}",shape=hexagon];\n'.format(tname, t.name()))
|
||||
|
||||
for s in seeds.values():
|
||||
for k, d in s.dependencies().items():
|
||||
if d.name() not in nodes:
|
||||
nodes[d.name()] = d
|
||||
dname = d.name().replace('-', '_')
|
||||
f.write(' {} [label="{}",shape=diamond];\n'.format(dname, d.name()))
|
||||
|
||||
sname = s.name().replace('-', '_')
|
||||
dname = d.name().replace('-', '_')
|
||||
|
||||
if d.is_resolved():
|
||||
f.write(' {} -> {};\n'.format(dname, sname))
|
||||
else:
|
||||
f.write(' {} -> {}[style=dotted];\n'.format(dname, sname))
|
||||
|
||||
for t in targets.values():
|
||||
for k, d in t.dependencies().items():
|
||||
tname = t.name().replace('-', '_')
|
||||
dname = d.name().replace('-', '_')
|
||||
|
||||
if d.is_resolved():
|
||||
f.write(' {} -> {};\n'.format(dname, tname))
|
||||
else:
|
||||
f.write(' {} -> {}[style=dotted];\n'.format(dname, tname))
|
||||
|
||||
f.write('}\n')
|
||||
f.close()
|
||||
|
||||
|
||||
def run(self, args):
|
||||
seeds = Seed.all_seeds()
|
||||
|
||||
dot_path = args.get_named('to-dot-file', 0, 1)
|
||||
if dot_path is not None:
|
||||
self.write_dot(dot_path)
|
||||
return 0
|
||||
|
||||
for s in seeds.values():
|
||||
print('{}'.format(s.name()))
|
||||
|
||||
|
||||
class PlatformsCommand(Command):
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
name='platforms',
|
||||
description='List all available platforms')
|
||||
|
||||
|
||||
def run(self, args):
|
||||
print('Supported platforms:')
|
||||
|
||||
platforms = Platform.all_platforms()
|
||||
current_platform = Platform.current_platform()
|
||||
|
||||
for p in platforms.values():
|
||||
if current_platform is not None and p.name() == current_platform.name():
|
||||
print(f'* {Fore.GREEN}{p.name()}{Style.RESET_ALL}')
|
||||
else:
|
||||
print(f' {p.name()}')
|
||||
|
||||
|
||||
class HelpCommand(Command):
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
name='help',
|
||||
description='List all available commands.')
|
||||
|
||||
|
||||
def run(self, args):
|
||||
print('Available commands:')
|
||||
|
||||
commands = Command.all_commands()
|
||||
max_len = 0
|
||||
|
||||
for c in commands.values():
|
||||
if len(c.name()) > max_len:
|
||||
max_len = len(c.name())
|
||||
|
||||
max_len += 5
|
||||
for c in commands.values():
|
||||
print(' ', end='')
|
||||
print(c.name(), end='')
|
||||
|
||||
for i in range(len(c.name()), max_len):
|
||||
print(' ', end='')
|
||||
|
||||
print(c.description())
|
||||
|
||||
|
||||
class BuildCommand(Command):
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
name='build',
|
||||
description='Build the currently selected target.')
|
||||
|
||||
|
||||
def run(self, args):
|
||||
verbose = args.get_named('verbose', 0, 0)
|
||||
|
||||
current_target = Target.current_target()
|
||||
if current_target is None:
|
||||
print(f'{Fore.RED}Err:{Style.RESET_ALL} no build target has been set. Use `meadow set target.name <...>` to specify which target to build')
|
||||
return -1
|
||||
|
||||
current_platform = Platform.current_platform()
|
||||
if current_platform is None:
|
||||
print(f'{Fore.RED}Err:{Style.RESET_ALL} no target platform has been set. Use `meadow set target.platform <...>` to specify which platform to target')
|
||||
return -1
|
||||
|
||||
components = current_target.resolve()
|
||||
|
||||
for c in components:
|
||||
build_system = None
|
||||
if isinstance(c, Seed):
|
||||
build_system = c.build_system()
|
||||
build_system.check_out_of_date()
|
||||
|
||||
for c in components:
|
||||
build_system = None
|
||||
if isinstance(c, Seed):
|
||||
build_system = c.build_system()
|
||||
|
||||
if build_system is not None:
|
||||
out_of_date = \
|
||||
build_system.is_configuration_out_of_date() \
|
||||
or build_system.is_build_out_of_date()
|
||||
|
||||
if out_of_date:
|
||||
print(f'{Fore.GREEN}>>>{Style.RESET_ALL} Building {c.name()}')
|
||||
|
||||
if build_system.is_configuration_out_of_date():
|
||||
print(f'{Fore.CYAN} >>{Style.RESET_ALL} Configuring build...')
|
||||
build_system.configure(verbose=verbose)
|
||||
|
||||
if out_of_date:
|
||||
print(f'{Fore.CYAN} >>{Style.RESET_ALL} Building...')
|
||||
build_system.build(verbose=verbose)
|
||||
print(f'{Fore.CYAN} >>{Style.RESET_ALL} Installing...')
|
||||
build_system.install(verbose=verbose)
|
||||
|
||||
print(f'{Style.BRIGHT}{Fore.GREEN}>>> Build Complete!{Style.RESET_ALL}')
|
||||
|
||||
|
||||
class CleanCommand(Command):
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
name='clean',
|
||||
description='Delete all build-related files.')
|
||||
|
||||
|
||||
def run(self, args):
|
||||
build_dir = Environment.build_root()
|
||||
|
||||
for i in os.listdir(build_dir):
|
||||
if i.startswith('meadow') and ('cache' not in i):
|
||||
continue
|
||||
|
||||
ipath = os.path.join(build_dir, i)
|
||||
if os.path.isdir(ipath):
|
||||
shutil.rmtree(ipath)
|
||||
else:
|
||||
os.remove(ipath)
|
||||
|
||||
Cache.reset()
|
||||
return 0
|
||||
@@ -0,0 +1,69 @@
|
||||
import json
|
||||
import os
|
||||
|
||||
|
||||
cache_path = 'build/meadow-cache.json'
|
||||
cache_data = None
|
||||
|
||||
|
||||
class Cache:
|
||||
def load():
|
||||
global cache_data
|
||||
|
||||
if not os.path.exists(cache_path):
|
||||
cache_data = {}
|
||||
return
|
||||
|
||||
with open(cache_path, mode='r') as f:
|
||||
cache_data = json.load(f)
|
||||
|
||||
|
||||
def reset():
|
||||
global cache_data
|
||||
cache_data = {}
|
||||
|
||||
|
||||
def save():
|
||||
global cache_data
|
||||
|
||||
with open(cache_path, mode='w') as f:
|
||||
json.dump(cache_data, f, indent=4)
|
||||
|
||||
|
||||
def get(path):
|
||||
parts = path.split('.')
|
||||
container = cache_data
|
||||
|
||||
for p in parts[:-1]:
|
||||
if p not in container:
|
||||
return None
|
||||
|
||||
if not isinstance(container[p], dict):
|
||||
print('Err: element "{}" in cache key "{}" is not a dictionary'.format(p, path))
|
||||
return
|
||||
|
||||
container = container[p]
|
||||
|
||||
|
||||
if parts[-1] in container:
|
||||
return container[parts[-1]]
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def set(path, value):
|
||||
parts = path.split('.')
|
||||
container = cache_data
|
||||
|
||||
for p in parts[:-1]:
|
||||
if p not in container:
|
||||
container[p] = {}
|
||||
|
||||
if not isinstance(container[p], dict):
|
||||
print('Err: element "{}" in cache key "{}" is not a dictionary'.format(p, path))
|
||||
return
|
||||
|
||||
container = container[p]
|
||||
|
||||
|
||||
container[parts[-1]] = value
|
||||
@@ -0,0 +1,316 @@
|
||||
import os
|
||||
import json
|
||||
import subprocess
|
||||
from build import *
|
||||
from pathlib import Path as SysPath
|
||||
from cache import Cache
|
||||
from env import Environment
|
||||
from platform import Platform
|
||||
from target import Target
|
||||
from path import Path
|
||||
|
||||
class CMakeBuildSystem(BuildSystem):
|
||||
def __init__(self, **kwargs):
|
||||
name = kwargs.get('name', None)
|
||||
source_dir = kwargs.get('source_dir', None)
|
||||
bin_dir = kwargs.get('bin_dir', None)
|
||||
parent = kwargs.get('parent', None)
|
||||
self.__data = kwargs.get('data', None)
|
||||
|
||||
super().__init__(**kwargs)
|
||||
|
||||
|
||||
def build_for(self):
|
||||
if self.__data is None or 'build_for' not in self.__data:
|
||||
return 'target'
|
||||
return self.__data['build_for']
|
||||
|
||||
|
||||
def __get_watched_files(self):
|
||||
result = []
|
||||
ns = self.get_path_namespaces()
|
||||
|
||||
if 'watch_files' in self.__data:
|
||||
watch_list = self.__data['watch_files']
|
||||
for p in watch_list:
|
||||
path = Path(p)
|
||||
|
||||
real_paths = path.expand_path(relative_to=ns)
|
||||
for p2 in real_paths:
|
||||
result.append(p2)
|
||||
|
||||
manifest_path = os.path.join(self.binary_dir(), 'compile_commands.json')
|
||||
manifest_file = None
|
||||
manifest = None
|
||||
try:
|
||||
manifest_file = open(manifest_path, mode='r')
|
||||
except:
|
||||
return result
|
||||
|
||||
try:
|
||||
manifest = json.load(manifest_file)
|
||||
except:
|
||||
return result
|
||||
|
||||
for v in manifest:
|
||||
if 'file' not in v:
|
||||
continue
|
||||
|
||||
path = v['file']
|
||||
result.append(path)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def __get_build_mtime(self):
|
||||
files = self.__get_watched_files()
|
||||
result = 0
|
||||
|
||||
for path in files:
|
||||
mtime = os.path.getmtime(path)
|
||||
|
||||
if mtime > result:
|
||||
result = mtime
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def __get_configure_mtime(self):
|
||||
source_dir = os.path.abspath(self.source_dir())
|
||||
result = 0
|
||||
for root, dirs, files in os.walk(source_dir):
|
||||
for f in files:
|
||||
if f != 'CMakeLists.txt' and not f.endswith('.cmake'):
|
||||
continue
|
||||
|
||||
target_path = os.path.join(root, f)
|
||||
mtime = os.path.getmtime(target_path)
|
||||
if mtime > result:
|
||||
result = mtime
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def _is_configuration_out_of_date(self):
|
||||
cache_file = os.path.join(self.binary_dir(), 'CMakeCache.txt')
|
||||
if not os.path.isfile(cache_file):
|
||||
return True
|
||||
|
||||
current_mtime = self.__get_configure_mtime()
|
||||
target_mtime = Cache.get(f'component.{self.name()}.configure-mtime')
|
||||
if target_mtime is None:
|
||||
target_mtime = 0
|
||||
|
||||
if current_mtime > target_mtime:
|
||||
return True
|
||||
|
||||
for d in self.parent().collect_dependencies():
|
||||
build_system = d.build_system()
|
||||
if build_system is None:
|
||||
continue
|
||||
|
||||
if build_system.is_build_out_of_date() or build_system.is_configuration_out_of_date():
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def _is_build_out_of_date(self):
|
||||
current_mtime = self.__get_build_mtime()
|
||||
target_mtime = Cache.get(f'component.{self.name()}.build-mtime')
|
||||
if target_mtime is None:
|
||||
target_mtime = 0
|
||||
|
||||
if current_mtime > target_mtime:
|
||||
return True
|
||||
|
||||
for d in self.parent().collect_dependencies():
|
||||
build_system = d.build_system()
|
||||
if build_system is None:
|
||||
continue
|
||||
|
||||
if build_system.is_build_out_of_date() or build_system.is_configuration_out_of_date():
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def __get_published_items(self):
|
||||
published = {}
|
||||
|
||||
for d in self.parent().collect_dependencies():
|
||||
build_sys = d.build_system()
|
||||
ns = {}
|
||||
if build_sys is not None:
|
||||
ns = build_sys.get_path_namespaces()
|
||||
|
||||
p = d.publish()
|
||||
if p is None:
|
||||
continue
|
||||
|
||||
path = p.path()
|
||||
if path is not None:
|
||||
if 'path' not in published:
|
||||
published['path'] = []
|
||||
|
||||
path = Path.expand_all(path, relative_to=ns)
|
||||
published['path'].extend(path)
|
||||
|
||||
cmake = p.cmake()
|
||||
if cmake is not None:
|
||||
if 'cmake' not in published:
|
||||
published['cmake'] = {}
|
||||
|
||||
if 'module_paths' in cmake:
|
||||
if 'module_paths' not in published['cmake']:
|
||||
published['cmake']['module_paths'] = []
|
||||
|
||||
module_paths = cmake['module_paths']
|
||||
module_paths = Path.expand_all(module_paths, relative_to=ns)
|
||||
published['cmake']['module_paths'].extend(module_paths)
|
||||
|
||||
return published
|
||||
|
||||
|
||||
def configure(self, **kwargs):
|
||||
verbose = kwargs.get('verbose', False)
|
||||
ns = self.get_path_namespaces()
|
||||
stdout = None
|
||||
if not verbose:
|
||||
stdout = subprocess.DEVNULL
|
||||
|
||||
target = Target.current_target()
|
||||
platform = Platform.current_platform()
|
||||
cmake_platform = platform.get_component('cmake')
|
||||
cmake_args = [
|
||||
'cmake',
|
||||
'-DCMAKE_EXPORT_COMPILE_COMMANDS=ON',
|
||||
'--no-warn-unused-cli'
|
||||
]
|
||||
|
||||
if 'configure_args' in self.__data:
|
||||
cmake_args.extend(self.__data['configure_args'])
|
||||
|
||||
published = self.__get_published_items()
|
||||
env = os.environ.copy()
|
||||
if 'path' in published:
|
||||
path = ''
|
||||
if 'PATH' in env:
|
||||
path = env['PATH']
|
||||
|
||||
if len(path):
|
||||
path += ':'
|
||||
|
||||
path += ':'.join(published['path'])
|
||||
env['PATH'] = path
|
||||
|
||||
module_paths = []
|
||||
if 'cmake' in published:
|
||||
cmake_publish = published['cmake']
|
||||
for p in cmake_publish.get('module_paths', []):
|
||||
path = Path(p)
|
||||
for p2 in path.expand_path(relative_to=ns):
|
||||
module_paths.append(p2)
|
||||
|
||||
install_details = self.parent().install()
|
||||
install_prefix = ''
|
||||
if self.build_for() == 'host':
|
||||
install_prefix = Environment.native_root()
|
||||
cmake_platform = None
|
||||
seed_install_prefix = '/'
|
||||
if install_details is not None:
|
||||
seed_install_prefix = install_details.get('prefix', seed_install_prefix)
|
||||
seed_install_prefix = Path(seed_install_prefix).remove_root().get_path()
|
||||
install_prefix = os.path.join(install_prefix, seed_install_prefix)
|
||||
else:
|
||||
install_prefix = Environment.system_root()
|
||||
env['ROSETTA_SYSROOT'] = Environment.system_root()
|
||||
|
||||
seed_install_prefix = target.install_prefix()
|
||||
if install_details is not None:
|
||||
seed_install_prefix = install_details.get('prefix', seed_install_prefix)
|
||||
seed_install_prefix = Path(seed_install_prefix).remove_root().get_path()
|
||||
install_prefix = os.path.join(install_prefix, seed_install_prefix)
|
||||
|
||||
cmake_args.append(f'-DCMAKE_INSTALL_PREFIX={install_prefix}')
|
||||
|
||||
if cmake_platform is not None:
|
||||
if 'module_paths' in cmake_platform:
|
||||
for p in cmake_platform['module_paths']:
|
||||
module_paths.append(os.path.join(platform.base_directory(), p))
|
||||
if 'toolchain_file' in cmake_platform:
|
||||
toolchain_file = os.path.join(platform.base_directory(), cmake_platform['toolchain_file'])
|
||||
cmake_args.append(f'-DCMAKE_TOOLCHAIN_FILE={toolchain_file}')
|
||||
if 'system_name' in cmake_platform:
|
||||
cmake_args.append(f'-DCMAKE_SYSTEM_NAME={cmake_platform['system_name']}')
|
||||
|
||||
if 'module_paths' in self.__data:
|
||||
for p in self.__data['module_paths']:
|
||||
path = Path(p)
|
||||
for p2 in path.expand_path(relative_to=ns):
|
||||
module_paths.append(p2)
|
||||
|
||||
if len(module_paths) > 0:
|
||||
cmake_args.append(f'-DCMAKE_MODULE_PATH={';'.join(module_paths)}')
|
||||
|
||||
source_dir = os.path.abspath(self.source_dir())
|
||||
bin_dir = os.path.abspath(self.binary_dir())
|
||||
cmake_args.append(source_dir)
|
||||
|
||||
SysPath(bin_dir).mkdir(parents=True, exist_ok=True)
|
||||
result = subprocess.run(cmake_args, cwd=bin_dir, env=env, stdout=stdout)
|
||||
|
||||
if result.returncode == 0:
|
||||
Cache.set(f'component.{self.name()}.configure-mtime', self.__get_configure_mtime())
|
||||
else:
|
||||
raise BuildConfigurationError(f'CMake configuration for {self.name()} failed. See log for details')
|
||||
|
||||
|
||||
def __find_build_command(self):
|
||||
bin_dir = os.path.abspath(self.binary_dir())
|
||||
if os.path.isfile(os.path.join(bin_dir, 'build.ninja')):
|
||||
return 'ninja'
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def build(self, **kwargs):
|
||||
verbose = kwargs.get('verbose', False)
|
||||
stdout = None
|
||||
if not verbose:
|
||||
stdout = subprocess.DEVNULL
|
||||
|
||||
bin_dir = os.path.abspath(self.binary_dir())
|
||||
build_cmd = self.__find_build_command()
|
||||
if build_cmd is None:
|
||||
raise BuildError(f'Cannot determine which command to use to build {self.name()}')
|
||||
|
||||
env = os.environ.copy()
|
||||
if self.build_for() != 'host':
|
||||
env['ROSETTA_SYSROOT'] = Environment.system_root()
|
||||
|
||||
args = [ build_cmd ]
|
||||
result = subprocess.run(args, cwd=bin_dir, stdout=stdout, env=env)
|
||||
|
||||
if result.returncode != 0:
|
||||
raise BuildConfigurationError(f'{build_cmd} build for {self.name()} failed. See log for details')
|
||||
|
||||
|
||||
def install(self, **kwargs):
|
||||
self.install_from_seed()
|
||||
|
||||
verbose = kwargs.get('verbose', False)
|
||||
stdout = None
|
||||
if not verbose:
|
||||
stdout = subprocess.DEVNULL
|
||||
|
||||
bin_dir = os.path.abspath(self.binary_dir())
|
||||
build_cmd = self.__find_build_command()
|
||||
if build_cmd is None:
|
||||
raise BuildError(f'Cannot determine which command to use to build {self.name()}')
|
||||
|
||||
args = [ build_cmd, 'install' ]
|
||||
result = subprocess.run(args, cwd=bin_dir, stdout=stdout)
|
||||
|
||||
if result.returncode == 0:
|
||||
Cache.set(f'component.{self.name()}.build-mtime', self.__get_build_mtime())
|
||||
@@ -0,0 +1,205 @@
|
||||
import os
|
||||
import sys
|
||||
import tomli
|
||||
import tempfile
|
||||
import subprocess
|
||||
from dependency import *
|
||||
from colorama import Fore, Style
|
||||
from env import Environment
|
||||
from path import Path
|
||||
from config import Config
|
||||
|
||||
|
||||
commands = {}
|
||||
|
||||
|
||||
class CommandConfigurationError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class CommandExecutionError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class Command(DependencyItem):
|
||||
def all_commands():
|
||||
return commands
|
||||
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__()
|
||||
|
||||
if 'name' in kwargs:
|
||||
self.__name = kwargs['name']
|
||||
if 'description' in kwargs:
|
||||
self.__description = kwargs['description']
|
||||
|
||||
|
||||
def name(self):
|
||||
return self.__name
|
||||
|
||||
|
||||
def description(self):
|
||||
return self.__description
|
||||
|
||||
|
||||
def dependencies(self):
|
||||
return None
|
||||
|
||||
|
||||
def register(cmd):
|
||||
commands[cmd.name()] = cmd
|
||||
|
||||
|
||||
def get(name):
|
||||
if name in commands:
|
||||
return commands[name]
|
||||
return None
|
||||
|
||||
|
||||
def run(self, args):
|
||||
return
|
||||
|
||||
|
||||
class UserCommand(Command):
|
||||
def load_file(self, path):
|
||||
with open(path, mode='rb') as f:
|
||||
self.__data = tomli.load(f)
|
||||
|
||||
|
||||
def __init_dependencies(self):
|
||||
if 'dependency' not in self.__data:
|
||||
return
|
||||
|
||||
dependency = self.__data['dependency']
|
||||
self.__dependencies = {}
|
||||
if 'seeds' in dependency:
|
||||
seeds = dependency['seeds']
|
||||
for s in seeds:
|
||||
self.__dependencies[s] = SeedDependency(parent=self.name(), name=s)
|
||||
|
||||
if 'commands' in dependency:
|
||||
commands = dependency['commands']
|
||||
for c in commands:
|
||||
self.__dependencies[c] = ProgramDependency(parent=self.name(), name=c)
|
||||
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
if 'path' not in kwargs:
|
||||
return
|
||||
|
||||
self.__tmpdir = tempfile.TemporaryDirectory()
|
||||
self.__path = kwargs['path']
|
||||
self.load_file(self.__path)
|
||||
cmd = self.__data['command']
|
||||
super().__init__(name=cmd['name'], description=cmd['description'])
|
||||
|
||||
self.__init_dependencies()
|
||||
|
||||
|
||||
def dependencies(self):
|
||||
return self.__dependencies
|
||||
|
||||
|
||||
def scan_directory(paths):
|
||||
for p in paths:
|
||||
for root, dirs, files in os.walk(p):
|
||||
for f in files:
|
||||
if not f.endswith('.command'):
|
||||
continue
|
||||
cmd_path = os.path.join(root, f)
|
||||
cmd = None
|
||||
try:
|
||||
cmd = UserCommand(path=cmd_path)
|
||||
except tomli.TOMLDecodeError as e:
|
||||
print('Err: Failed to parse command file {}\n {}'.format(seed_path, e))
|
||||
return -1
|
||||
|
||||
Command.register(cmd)
|
||||
|
||||
|
||||
def get_path_namespaces(self):
|
||||
ns = {
|
||||
'src': os.path.dirname(self.__path),
|
||||
'root-src': Environment.source_root(),
|
||||
'install': Environment.system_root(),
|
||||
}
|
||||
|
||||
return ns
|
||||
|
||||
|
||||
def get_environment(self):
|
||||
return {
|
||||
'MEADOW_SYSROOT': Environment.system_root(),
|
||||
'MEADOW_NATIVE_ROOT': Environment.native_root(),
|
||||
'MEADOW_SOURCE_ROOT': Environment.source_root(),
|
||||
'MEADOW_TEMP': self.__tmpdir.name,
|
||||
'MEADOW_TARGET_PLATFORM': Config.get('target.platform'),
|
||||
}
|
||||
|
||||
|
||||
def run(self, args):
|
||||
from seed import Seed
|
||||
deps = self.resolve()
|
||||
|
||||
for c in deps:
|
||||
build_system = None
|
||||
if isinstance(c, Seed):
|
||||
build_system = c.build_system()
|
||||
build_system.check_out_of_date()
|
||||
|
||||
for c in deps:
|
||||
build_system = None
|
||||
if isinstance(c, Seed):
|
||||
build_system = c.build_system()
|
||||
|
||||
if build_system is not None:
|
||||
out_of_date = \
|
||||
build_system.is_configuration_out_of_date() \
|
||||
or build_system.is_build_out_of_date()
|
||||
|
||||
if out_of_date:
|
||||
print(f'{Fore.GREEN}>>>{Style.RESET_ALL} Building {c.name()}')
|
||||
|
||||
if build_system.is_configuration_out_of_date():
|
||||
print(f'{Fore.CYAN} >>{Style.RESET_ALL} Configuring build...')
|
||||
build_system.configure(verbose=False)
|
||||
|
||||
if out_of_date:
|
||||
print(f'{Fore.CYAN} >>{Style.RESET_ALL} Building...')
|
||||
build_system.build(verbose=False)
|
||||
print(f'{Fore.CYAN} >>{Style.RESET_ALL} Installing...')
|
||||
build_system.install(verbose=False)
|
||||
|
||||
exec_info = self.__data.get('exec', None)
|
||||
if exec_info is None:
|
||||
raise CommandConfigurationError(f'Command {self.name()} has no executable configured')
|
||||
|
||||
args = exec_info.get('args', None)
|
||||
if args is None or len(args) == 0:
|
||||
raise CommandConfigurationError(f'Command {self.name()} has no executable configured')
|
||||
|
||||
ns = self.get_path_namespaces()
|
||||
expanded_args = []
|
||||
for p in args:
|
||||
parts = p.split(':')
|
||||
if len(parts) < 2 or parts[0] not in ns:
|
||||
expanded_args.append(p)
|
||||
continue
|
||||
|
||||
paths = Path(p).expand_path(relative_to=ns)
|
||||
expanded_args.extend(paths)
|
||||
|
||||
if expanded_args[0].endswith('.py'):
|
||||
expanded_args.insert(0, sys.executable)
|
||||
|
||||
env = os.environ | self.get_environment()
|
||||
result = None
|
||||
|
||||
try:
|
||||
result = subprocess.run(expanded_args, env=env)
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
|
||||
if result is not None and result.returncode != 0:
|
||||
raise CommandExecutionError(f'Execution of {self.name()} failed. See log for details')
|
||||
@@ -0,0 +1,64 @@
|
||||
import json
|
||||
import os
|
||||
|
||||
|
||||
config_path = 'build/meadow-config.json'
|
||||
config_data = None
|
||||
|
||||
|
||||
class Config:
|
||||
def load():
|
||||
global config_data
|
||||
|
||||
if not os.path.exists(config_path):
|
||||
config_data = {}
|
||||
return
|
||||
|
||||
with open(config_path, mode='r') as f:
|
||||
config_data = json.load(f)
|
||||
|
||||
|
||||
def save():
|
||||
global config_data
|
||||
|
||||
with open(config_path, mode='w') as f:
|
||||
json.dump(config_data, f, indent=4)
|
||||
|
||||
|
||||
def get(path):
|
||||
parts = path.split('.')
|
||||
container = config_data
|
||||
|
||||
for p in parts[:-1]:
|
||||
if p not in container:
|
||||
return None
|
||||
|
||||
if not isinstance(container[p], dict):
|
||||
print('Err: element "{}" in config key "{}" is not a dictionary'.format(p, path))
|
||||
return
|
||||
|
||||
container = container[p]
|
||||
|
||||
|
||||
if parts[-1] in container:
|
||||
return container[parts[-1]]
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def set(path, value):
|
||||
parts = path.split('.')
|
||||
container = config_data
|
||||
|
||||
for p in parts[:-1]:
|
||||
if p not in container:
|
||||
container[p] = {}
|
||||
|
||||
if not isinstance(container[p], dict):
|
||||
print('Err: element "{}" in config key "{}" is not a dictionary'.format(p, path))
|
||||
return
|
||||
|
||||
container = container[p]
|
||||
|
||||
|
||||
container[parts[-1]] = value
|
||||
@@ -0,0 +1,156 @@
|
||||
import os
|
||||
|
||||
|
||||
class DependencyResolutionError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class DependencyItem:
|
||||
def __init__(self):
|
||||
self.__target = None
|
||||
self.__dep_list = None
|
||||
|
||||
|
||||
def name(self):
|
||||
return None
|
||||
|
||||
|
||||
def target(self):
|
||||
return self.__target
|
||||
|
||||
|
||||
def dependencies(self):
|
||||
return None
|
||||
|
||||
|
||||
def dependency_list(self):
|
||||
return self.__dep_list
|
||||
|
||||
|
||||
def publish(self):
|
||||
return None
|
||||
|
||||
|
||||
def reset_resolution(self):
|
||||
self.__dep_list = None
|
||||
|
||||
|
||||
def resolve(self, **kwargs):
|
||||
if self.__dep_list is not None:
|
||||
return self.__dep_list
|
||||
|
||||
ctx = None
|
||||
dep_list = None
|
||||
if 'ctx' in kwargs:
|
||||
ctx = kwargs['ctx']
|
||||
else:
|
||||
ctx = {}
|
||||
|
||||
if 'dep_list' in kwargs:
|
||||
dep_list = kwargs['dep_list']
|
||||
else:
|
||||
dep_list = []
|
||||
|
||||
if self.name() in ctx:
|
||||
return
|
||||
|
||||
ctx[self.name()] = self
|
||||
|
||||
deps = self.dependencies()
|
||||
if deps is None:
|
||||
return
|
||||
|
||||
for d in deps.values():
|
||||
d_value = d.resolve_self()
|
||||
if d_value is not None:
|
||||
d_value.resolve(ctx=ctx, dep_list=dep_list)
|
||||
|
||||
dep_list.append(self)
|
||||
return dep_list
|
||||
|
||||
|
||||
class Dependency:
|
||||
def __init__(self, parent):
|
||||
self.__parent = parent
|
||||
|
||||
|
||||
def name(self, parent):
|
||||
return None
|
||||
|
||||
|
||||
def parent_name(self):
|
||||
return self.__parent
|
||||
|
||||
|
||||
def resolve_self(self):
|
||||
return None
|
||||
|
||||
|
||||
def is_resolved(self):
|
||||
return False
|
||||
|
||||
|
||||
class SeedDependency(Dependency):
|
||||
def __init__(self, **kwargs):
|
||||
self.__target_seed = None
|
||||
if 'name' in kwargs:
|
||||
self.__name = kwargs['name']
|
||||
if 'parent' in kwargs:
|
||||
super().__init__(kwargs['parent'])
|
||||
|
||||
|
||||
def name(self):
|
||||
return self.__name
|
||||
|
||||
|
||||
def target(self):
|
||||
return self.__target_seed
|
||||
|
||||
|
||||
def resolve_self(self):
|
||||
from seed import Seed
|
||||
if self.__target_seed is not None:
|
||||
return self.__target_seed
|
||||
|
||||
target = Seed.get(self.__name)
|
||||
if target is None:
|
||||
raise DependencyResolutionError(
|
||||
'{} depends on seed "{}", but the required seed cannot be found'.format(self.parent_name(), self.__name))
|
||||
|
||||
self.__target_seed = target
|
||||
return self.__target_seed
|
||||
|
||||
|
||||
def is_resolved(self):
|
||||
return self.__target_seed is not None
|
||||
|
||||
|
||||
class ProgramDependency(Dependency):
|
||||
def __init__(self, **kwargs):
|
||||
if 'name' in kwargs:
|
||||
self.__name = kwargs['name']
|
||||
if 'parent' in kwargs:
|
||||
super().__init__(kwargs['parent'])
|
||||
|
||||
|
||||
def __is_exe(self, fpath):
|
||||
return os.path.isfile(fpath) and os.access(fpath, os.X_OK)
|
||||
|
||||
|
||||
def name(self):
|
||||
return self.__name
|
||||
|
||||
|
||||
def resolve_self(self):
|
||||
if self.__is_exe(self.__name):
|
||||
return None
|
||||
|
||||
path_string = os.environ['PATH']
|
||||
path_list = path_string.split(':')
|
||||
for d in path_list:
|
||||
exe_path = os.path.join(d, self.__name)
|
||||
if self.__is_exe(exe_path):
|
||||
return None
|
||||
|
||||
raise DependencyResolutionError(f'{self.parent_name()} depends on command `{self.__name}`, but the command cannot be found. Make sure that the named command exists, the containing directory is in $PATH if necessary, and the executable file has the correct permissions.')
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import os
|
||||
|
||||
|
||||
source_root = None
|
||||
|
||||
|
||||
class Environment:
|
||||
def set_source_root(path):
|
||||
global source_root
|
||||
source_root = path
|
||||
|
||||
|
||||
def source_root():
|
||||
global source_root
|
||||
return source_root
|
||||
|
||||
|
||||
def build_root():
|
||||
global source_root
|
||||
return os.path.join(source_root, 'build')
|
||||
|
||||
|
||||
def system_root():
|
||||
return os.path.join(Environment.build_root(), 'target-root')
|
||||
|
||||
|
||||
def native_root():
|
||||
return os.path.join(Environment.build_root(), 'host-root')
|
||||
@@ -0,0 +1,140 @@
|
||||
import os
|
||||
import json
|
||||
import shutil
|
||||
import subprocess
|
||||
from build import *
|
||||
from pathlib import Path as SysPath
|
||||
from cache import Cache
|
||||
from env import Environment
|
||||
from platform import Platform
|
||||
from target import Target
|
||||
from path import Path
|
||||
|
||||
class GenericBuildSystem(BuildSystem):
|
||||
def __init__(self, **kwargs):
|
||||
self.__data = kwargs.get('data', None)
|
||||
super().__init__(**kwargs)
|
||||
|
||||
|
||||
def __get_watched_files(self):
|
||||
result = []
|
||||
ns = self.get_path_namespaces()
|
||||
|
||||
if 'watch_files' in self.__data:
|
||||
watch_list = self.__data['watch_files']
|
||||
for p in watch_list:
|
||||
path = Path(p)
|
||||
|
||||
real_paths = path.expand_path(relative_to=ns)
|
||||
for p2 in real_paths:
|
||||
result.append(p2)
|
||||
|
||||
manifest_path = os.path.join(self.binary_dir(), 'compile_commands.json')
|
||||
manifest_file = None
|
||||
manifest = None
|
||||
try:
|
||||
manifest_file = open(manifest_path, mode='r')
|
||||
except:
|
||||
return result
|
||||
|
||||
try:
|
||||
manifest = json.load(manifest_file)
|
||||
except:
|
||||
return result
|
||||
|
||||
for v in manifest:
|
||||
if 'file' not in v:
|
||||
continue
|
||||
|
||||
path = v['file']
|
||||
result.append(path)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def __get_mtime(self):
|
||||
files = self.__get_watched_files()
|
||||
result = 0
|
||||
|
||||
for path in files:
|
||||
mtime = os.path.getmtime(path)
|
||||
|
||||
if mtime > result:
|
||||
result = mtime
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def _is_configuration_out_of_date(self):
|
||||
return False
|
||||
|
||||
|
||||
def _is_build_out_of_date(self):
|
||||
current_mtime = self.__get_mtime()
|
||||
target_mtime = Cache.get(f'component.{self.name()}.build-mtime')
|
||||
if target_mtime is None:
|
||||
return True
|
||||
|
||||
if current_mtime > target_mtime:
|
||||
return True
|
||||
|
||||
for d in self.parent().collect_dependencies():
|
||||
build_system = d.build_system()
|
||||
if build_system is None:
|
||||
continue
|
||||
|
||||
if build_system.is_build_out_of_date() or build_system.is_configuration_out_of_date():
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def __run_command(self, name, **kwargs):
|
||||
verbose = kwargs.get('verbose', False)
|
||||
ns = self.get_path_namespaces()
|
||||
stdout = None
|
||||
if not verbose:
|
||||
stdout = subprocess.DEVNULL
|
||||
|
||||
cmd = self.__data.get(name, None)
|
||||
if cmd is None:
|
||||
return
|
||||
|
||||
args = cmd.get('args', None)
|
||||
if args is None:
|
||||
raise BuildConfigurationError(f'{self.name()} is incorrectly configured. build_command has no args defined')
|
||||
|
||||
|
||||
expanded_args = []
|
||||
for p in args:
|
||||
parts = p.split(':')
|
||||
if len(parts) < 2 or parts[0] not in ns:
|
||||
expanded_args.append(p)
|
||||
continue
|
||||
|
||||
paths = Path(p).expand_path(relative_to=ns)
|
||||
expanded_args.extend(paths)
|
||||
|
||||
result = subprocess.run(expanded_args, stdout=stdout)
|
||||
if result.returncode != 0:
|
||||
raise BuildConfigurationError(f'Execution of {name} for {self.name()} failed. See log for details')
|
||||
|
||||
|
||||
def configure(self, **kwargs):
|
||||
self.__run_command('configure_command', **kwargs)
|
||||
|
||||
|
||||
def build(self, **kwargs):
|
||||
self.__run_command('build_command', **kwargs)
|
||||
|
||||
|
||||
def install(self, **kwargs):
|
||||
self.install_from_seed()
|
||||
|
||||
previous_mtime = Cache.get(f'component.{self.name()}.build-mtime')
|
||||
if previous_mtime is None:
|
||||
previous_mtime = 0
|
||||
|
||||
new_mtime = self.__get_mtime()
|
||||
if new_mtime > previous_mtime:
|
||||
Cache.set(f'component.{self.name()}.build-mtime', new_mtime)
|
||||
@@ -0,0 +1,95 @@
|
||||
import sys
|
||||
import os
|
||||
import tomli
|
||||
from seed import Seed
|
||||
from target import Target
|
||||
from platform import Platform
|
||||
from config import Config
|
||||
from cache import Cache
|
||||
from command import Command, UserCommand
|
||||
from args import Args
|
||||
from env import Environment
|
||||
from dependency import SeedDependency, DependencyResolutionError
|
||||
from builtin import *
|
||||
from colorama import init as colorama_init
|
||||
from colorama import Fore, Style
|
||||
|
||||
|
||||
def main():
|
||||
colorama_init()
|
||||
Command.register(SetCommand())
|
||||
Command.register(SeedsCommand())
|
||||
Command.register(PlatformsCommand())
|
||||
Command.register(HelpCommand())
|
||||
Command.register(BuildCommand())
|
||||
Command.register(CleanCommand())
|
||||
Config.load()
|
||||
Cache.load()
|
||||
Environment.set_source_root(os.getcwd())
|
||||
|
||||
Seed.scan_directory([
|
||||
'base',
|
||||
'external',
|
||||
'interface',
|
||||
'toolchain',
|
||||
'lib',
|
||||
'programs',
|
||||
'services',
|
||||
'sys'
|
||||
])
|
||||
|
||||
UserCommand.scan_directory([
|
||||
'toolchain'
|
||||
])
|
||||
|
||||
Target.scan_directory('./target')
|
||||
platforms = Platform.scan_directory('./arch')
|
||||
|
||||
current_platform = Platform.current_platform()
|
||||
if current_platform:
|
||||
current_platform.load_extensions()
|
||||
|
||||
if len(sys.argv) < 2:
|
||||
print(f'{Fore.CYAN}Usage:{Style.RESET_ALL} {sys.argv[0]} <command> [args...]')
|
||||
return -1
|
||||
|
||||
command_name = sys.argv[1]
|
||||
command = Command.get(command_name)
|
||||
if command is None:
|
||||
print(f'{Fore.RED}Err:{Style.RESET_ALL} unknown command "{command_name}"')
|
||||
return -1
|
||||
|
||||
current_target = Target.current_target()
|
||||
seeds = Seed.all_seeds()
|
||||
|
||||
if current_target is not None:
|
||||
system_dep_names = current_target.base_components()
|
||||
|
||||
for s in seeds.values():
|
||||
if s.name() in system_dep_names or not s.should_include_standard_dependencies():
|
||||
continue
|
||||
|
||||
for n in system_dep_names:
|
||||
s.add_dependency(n, SeedDependency(parent=s.name(), name=n))
|
||||
|
||||
try:
|
||||
current_target.resolve()
|
||||
except DependencyResolutionError as e:
|
||||
print(f'{Fore.RED}Err:{Style.RESET_ALL} {e}')
|
||||
return -1
|
||||
|
||||
args = Args(sys.argv[2:])
|
||||
result = None
|
||||
try:
|
||||
result = command.run(args)
|
||||
except Exception as e:
|
||||
print(f'{Fore.RED}Err:{Style.RESET_ALL} {e}')
|
||||
finally:
|
||||
Cache.save()
|
||||
Config.save()
|
||||
|
||||
return result
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
exit(main())
|
||||
@@ -0,0 +1,88 @@
|
||||
import glob
|
||||
import os
|
||||
|
||||
|
||||
class Path:
|
||||
def __init__(self, value):
|
||||
self.__path = value
|
||||
self.__namespace = None
|
||||
|
||||
parts = value.split(':')
|
||||
|
||||
if len(parts) != 2:
|
||||
return
|
||||
|
||||
if len(parts[0]) < 3:
|
||||
return
|
||||
|
||||
self.__namespace = parts[0]
|
||||
self.__path = parts[1]
|
||||
|
||||
|
||||
def get_path(self):
|
||||
return self.__path
|
||||
|
||||
|
||||
def remove_root(self):
|
||||
while len(self.__path) > 0 and self.__path[0] == '/':
|
||||
self.__path = self.__path [1:]
|
||||
|
||||
if len(self.__path) == 0:
|
||||
self.__path= '.'
|
||||
|
||||
return self
|
||||
|
||||
|
||||
def expand_path(self, **kwargs):
|
||||
relative_to = None
|
||||
|
||||
if 'relative_to' in kwargs:
|
||||
relative_to = kwargs['relative_to']
|
||||
|
||||
if isinstance(relative_to, dict):
|
||||
if self.__namespace is not None:
|
||||
relative_to = relative_to[self.__namespace]
|
||||
else:
|
||||
relative_to = None
|
||||
|
||||
paths = glob.glob(self.__path, root_dir=relative_to, recursive=True)
|
||||
if len(paths) == 0 and '*' not in self.__path:
|
||||
if relative_to is not None:
|
||||
return [os.path.join(relative_to, self.__path)]
|
||||
return [self.__path]
|
||||
|
||||
if relative_to is not None:
|
||||
abs_paths = []
|
||||
|
||||
for p in paths:
|
||||
abs_paths.append(os.path.join(relative_to, p))
|
||||
|
||||
return abs_paths
|
||||
|
||||
return paths
|
||||
|
||||
|
||||
def expand_all(paths, **kwargs):
|
||||
relative_to = None
|
||||
|
||||
if 'relative_to' in kwargs:
|
||||
relative_to = kwargs['relative_to']
|
||||
|
||||
result = []
|
||||
for p in paths:
|
||||
if not isinstance(p, Path):
|
||||
p = Path(p)
|
||||
r = p.expand_path(relative_to=relative_to)
|
||||
result.extend(r)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def get_namespace(self, **kwargs):
|
||||
if self.__namespace is not None:
|
||||
return self.__namespace
|
||||
|
||||
if 'default' in kwargs:
|
||||
return kwargs['default']
|
||||
|
||||
return None
|
||||
@@ -0,0 +1,119 @@
|
||||
import os
|
||||
import sys
|
||||
import tomli
|
||||
from config import Config
|
||||
from command import Command, UserCommand
|
||||
|
||||
|
||||
all_platforms = {}
|
||||
|
||||
|
||||
class Platform:
|
||||
def all_platforms():
|
||||
return all_platforms
|
||||
|
||||
|
||||
def current_platform():
|
||||
name = Config.get('target.platform')
|
||||
if not name or name not in all_platforms:
|
||||
return None
|
||||
|
||||
return all_platforms[name]
|
||||
|
||||
|
||||
def load_file(self, path):
|
||||
self.__path = path
|
||||
with open(path, mode='rb') as f:
|
||||
self.__data = tomli.load(f)
|
||||
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
if 'path' in kwargs:
|
||||
self.__platform_file = os.path.abspath(kwargs['path'])
|
||||
self.load_file(kwargs['path'])
|
||||
|
||||
|
||||
def base_directory(self):
|
||||
return os.path.dirname(self.__platform_file)
|
||||
|
||||
|
||||
def name(self):
|
||||
if 'platform' not in self.__data:
|
||||
return None
|
||||
|
||||
platform = self.__data['platform']
|
||||
if 'name' in platform:
|
||||
return platform['name']
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def arch(self):
|
||||
if 'platform' not in self.__data:
|
||||
return None
|
||||
|
||||
platform = self.__data['platform']
|
||||
if 'arch' in platform:
|
||||
return platform['arch']
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def load_extensions(self):
|
||||
from seed import Seed
|
||||
if 'extension' not in self.__data:
|
||||
return
|
||||
|
||||
|
||||
all_seeds = Seed.all_seeds()
|
||||
|
||||
extension_dir = os.path.dirname(self.__path)
|
||||
extension = self.__data['extension']
|
||||
|
||||
if 'seeds' in extension:
|
||||
seeds = extension['seeds']
|
||||
for seed_filename in seeds:
|
||||
seed_path = os.path.join(extension_dir, seed_filename)
|
||||
seed = None
|
||||
try:
|
||||
seed = Seed(path=seed_path)
|
||||
except tomli.TOMLDecodeError as e:
|
||||
print('Err: Failed to parse seed file {}\n {}'.format(seed_path, e))
|
||||
return -1
|
||||
|
||||
all_seeds[seed.name()] = seed
|
||||
|
||||
if 'commands' in extension:
|
||||
commands = extension['commands']
|
||||
for cmd_filename in commands:
|
||||
cmd_path = os.path.join(extension_dir, cmd_filename)
|
||||
cmd = None
|
||||
try:
|
||||
cmd = UserCommand(path=cmd_path)
|
||||
except tomli.TOMLDecodeError as e:
|
||||
print('Err: Failed to parse command file {}\n {}'.format(seed_path, e))
|
||||
return -1
|
||||
|
||||
Command.register(cmd)
|
||||
|
||||
|
||||
def get_component(self, name):
|
||||
if name in self.__data:
|
||||
return self.__data[name]
|
||||
return None
|
||||
|
||||
|
||||
def scan_directory(root_path):
|
||||
for root, dirs, files in os.walk(root_path):
|
||||
for f in files:
|
||||
if not f.endswith('.platform'):
|
||||
continue
|
||||
platform_path = os.path.join(root, f)
|
||||
platform = None
|
||||
try:
|
||||
platform = Platform(path=platform_path)
|
||||
except tomli.TOMLDecodeError as e:
|
||||
print('Err: Failed to parse platform file {}\n {}'.format(platform_path, e))
|
||||
continue
|
||||
|
||||
all_platforms[platform.name()] = platform
|
||||
@@ -0,0 +1,21 @@
|
||||
import os
|
||||
|
||||
|
||||
class Publish:
|
||||
def __init__(self, **kwargs):
|
||||
self.__data = None
|
||||
self.__install_root = None
|
||||
|
||||
if 'data' in kwargs:
|
||||
self.__data = kwargs['data']
|
||||
|
||||
if 'install_path' in kwargs:
|
||||
self.__install_root = kwargs['install_path']
|
||||
|
||||
|
||||
def path(self):
|
||||
return self.__data.get('path', None)
|
||||
|
||||
|
||||
def cmake(self):
|
||||
return self.__data.get('cmake', None)
|
||||
@@ -0,0 +1,2 @@
|
||||
tomli
|
||||
colorama
|
||||
@@ -0,0 +1,219 @@
|
||||
import os
|
||||
import sys
|
||||
import tomli
|
||||
from dependency import *
|
||||
from cmake import CMakeBuildSystem
|
||||
from publish import Publish
|
||||
from build import UnknownBuildSystemError
|
||||
from env import Environment
|
||||
from source_only import SourceOnlyBuildSystem
|
||||
from generic import GenericBuildSystem
|
||||
|
||||
|
||||
all_seeds = {}
|
||||
|
||||
|
||||
class SeedParseError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class Seed(DependencyItem):
|
||||
def all_seeds():
|
||||
return all_seeds
|
||||
|
||||
|
||||
def get(name):
|
||||
if name in all_seeds:
|
||||
return all_seeds[name]
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def load_file(self, path):
|
||||
with open(path, mode='rb') as f:
|
||||
self.__data = tomli.load(f)
|
||||
|
||||
|
||||
def __init_dependencies(self):
|
||||
self.__dependencies = {}
|
||||
|
||||
if 'dependency' not in self.__data:
|
||||
return
|
||||
|
||||
dependency = self.__data['dependency']
|
||||
if 'seeds' in dependency:
|
||||
seeds = dependency['seeds']
|
||||
self.__dependencies = {}
|
||||
for s in seeds:
|
||||
self.__dependencies[s] = SeedDependency(parent=self.name(), name=s)
|
||||
|
||||
|
||||
def __init_build_system(self):
|
||||
self.__build_system = None
|
||||
if 'build' not in self.__data:
|
||||
return
|
||||
|
||||
build = self.__data['build']
|
||||
if 'build_system' not in build:
|
||||
return
|
||||
|
||||
source_dir = os.path.dirname(self.__seed_path)
|
||||
bin_dir = os.path.join(Environment.build_root(), self.name())
|
||||
install = self.__data.get('install', None)
|
||||
|
||||
if 'source_dir' in build:
|
||||
source_dir = build['source_dir']
|
||||
|
||||
match build['build_system']:
|
||||
case 'none':
|
||||
self.__build_system = None
|
||||
case 'cmake':
|
||||
self.__build_system = CMakeBuildSystem(
|
||||
parent=self,
|
||||
name=self.name(),
|
||||
source_dir=source_dir,
|
||||
bin_dir=bin_dir,
|
||||
install=install,
|
||||
data=build)
|
||||
case 'source-only':
|
||||
self.__build_system = SourceOnlyBuildSystem(
|
||||
parent=self,
|
||||
name=self.name(),
|
||||
source_dir=source_dir,
|
||||
bin_dir=bin_dir,
|
||||
install=install,
|
||||
data=build)
|
||||
case 'generic':
|
||||
self.__build_system = GenericBuildSystem(
|
||||
parent=self,
|
||||
name=self.name(),
|
||||
source_dir=source_dir,
|
||||
bin_dir=bin_dir,
|
||||
install=install,
|
||||
data=build)
|
||||
case _:
|
||||
raise UnknownBuildSystemError(
|
||||
'Seed {} has unrecognised build system "{}"'.format(self.name(), build['build_system']))
|
||||
|
||||
|
||||
def __init_publish(self):
|
||||
install_path = Environment.system_root()
|
||||
if 'build' in self.__data and 'build_for' in self.__data['build']:
|
||||
build_for = self.__data['build']['build_for']
|
||||
if build_for == 'host':
|
||||
install_path = Environment.native_root()
|
||||
|
||||
self.__publish = None
|
||||
if 'publish' not in self.__data:
|
||||
return
|
||||
|
||||
self.__publish = Publish(install_path=install_path, data=self.__data['publish'])
|
||||
|
||||
|
||||
def add_dependency(self, name, dep):
|
||||
self.__dependencies[name] = dep
|
||||
|
||||
|
||||
def collect_dependencies(self, **kwargs):
|
||||
ctx = None
|
||||
if 'ctx' in kwargs:
|
||||
ctx = kwargs['ctx']
|
||||
else:
|
||||
ctx = {}
|
||||
|
||||
for k, d in self.__dependencies.items():
|
||||
if k in ctx:
|
||||
continue
|
||||
|
||||
ctx[d.name()] = d.target()
|
||||
if isinstance(d.target(), Seed):
|
||||
d.target().collect_dependencies(ctx=ctx)
|
||||
|
||||
return list(ctx.values())
|
||||
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__()
|
||||
|
||||
if not 'path' in kwargs:
|
||||
return
|
||||
|
||||
self.__seed_path = kwargs['path']
|
||||
self.load_file(kwargs['path'])
|
||||
self.__init_dependencies()
|
||||
self.__init_build_system()
|
||||
self.__init_publish()
|
||||
|
||||
|
||||
def seed_path(self):
|
||||
return self.__seed_path
|
||||
|
||||
|
||||
def name(self):
|
||||
if not 'seed' in self.__data:
|
||||
return None
|
||||
|
||||
s = self.__data['seed']
|
||||
if 'name' in s:
|
||||
return s['name']
|
||||
return None
|
||||
|
||||
|
||||
def type(self):
|
||||
if not 'seed' in self.__data:
|
||||
return None
|
||||
|
||||
s = self.__data['seed']
|
||||
if 'type' in s:
|
||||
return s['type']
|
||||
return 'component'
|
||||
|
||||
|
||||
def dependencies(self):
|
||||
return self.__dependencies
|
||||
|
||||
|
||||
def publish(self):
|
||||
return self.__publish
|
||||
|
||||
|
||||
def install(self):
|
||||
return self.__data.get('install', None)
|
||||
|
||||
|
||||
def should_include_standard_dependencies(self):
|
||||
if 'dependency' not in self.__data:
|
||||
return True
|
||||
|
||||
s = self.__data['dependency']
|
||||
if 'no_standard_dependencies' in s:
|
||||
return not s['no_standard_dependencies']
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def build_system(self):
|
||||
return self.__build_system
|
||||
|
||||
|
||||
def scan_directory(paths):
|
||||
for p in paths:
|
||||
for root, dirs, files in os.walk(p):
|
||||
for f in files:
|
||||
if not f.endswith('.seed'):
|
||||
continue
|
||||
seed_path = os.path.join(root, f)
|
||||
seed = None
|
||||
try:
|
||||
seed = Seed(path=seed_path)
|
||||
except tomli.TOMLDecodeError as e:
|
||||
raise SeedParseError('Err: Failed to parse seed file {}: {}'.format(seed_path, e))
|
||||
|
||||
all_seeds[seed.name()] = seed
|
||||
|
||||
|
||||
def resolve_all():
|
||||
for s in all_seeds.values():
|
||||
deps = s.dependencies()
|
||||
for k, v in deps.items():
|
||||
v.resolve()
|
||||
@@ -0,0 +1,119 @@
|
||||
import os
|
||||
import json
|
||||
import shutil
|
||||
import subprocess
|
||||
from build import *
|
||||
from pathlib import Path as SysPath
|
||||
from cache import Cache
|
||||
from env import Environment
|
||||
from platform import Platform
|
||||
from target import Target
|
||||
from path import Path
|
||||
|
||||
class SourceOnlyBuildSystem(BuildSystem):
|
||||
def __init__(self, **kwargs):
|
||||
self.__data = kwargs.get('data', None)
|
||||
super().__init__(**kwargs)
|
||||
|
||||
|
||||
def __get_watched_files(self):
|
||||
result = []
|
||||
ns = self.get_path_namespaces()
|
||||
|
||||
if 'watch_files' in self.__data:
|
||||
watch_list = self.__data['watch_files']
|
||||
for p in watch_list:
|
||||
path = Path(p)
|
||||
|
||||
real_paths = path.expand_path(relative_to=ns)
|
||||
for p2 in real_paths:
|
||||
result.append(p2)
|
||||
|
||||
manifest_path = os.path.join(self.binary_dir(), 'compile_commands.json')
|
||||
manifest_file = None
|
||||
manifest = None
|
||||
try:
|
||||
manifest_file = open(manifest_path, mode='r')
|
||||
except:
|
||||
return result
|
||||
|
||||
try:
|
||||
manifest = json.load(manifest_file)
|
||||
except:
|
||||
return result
|
||||
|
||||
for v in manifest:
|
||||
if 'file' not in v:
|
||||
continue
|
||||
|
||||
path = v['file']
|
||||
result.append(path)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def __get_mtime(self):
|
||||
files = self.__get_watched_files()
|
||||
result = 0
|
||||
|
||||
for path in files:
|
||||
mtime = os.path.getmtime(path)
|
||||
|
||||
if mtime > result:
|
||||
result = mtime
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def _is_configuration_out_of_date(self):
|
||||
parent = self.parent()
|
||||
seed_path = parent.seed_path()
|
||||
seed_mtime = os.path.getmtime(seed_path)
|
||||
target_mtime = Cache.get(f'component.{self.name()}.configure-mtime')
|
||||
if target_mtime is None:
|
||||
return True
|
||||
|
||||
return seed_mtime > target_mtime
|
||||
|
||||
|
||||
def _is_build_out_of_date(self):
|
||||
current_mtime = self.__get_mtime()
|
||||
target_mtime = Cache.get(f'component.{self.name()}.build-mtime')
|
||||
if target_mtime is None:
|
||||
return True
|
||||
|
||||
if current_mtime > target_mtime:
|
||||
return True
|
||||
|
||||
for d in self.parent().collect_dependencies():
|
||||
build_system = d.build_system()
|
||||
if build_system is None:
|
||||
continue
|
||||
|
||||
if build_system.is_build_out_of_date() or build_system.is_configuration_out_of_date():
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def configure(self, **kwargs):
|
||||
parent = self.parent()
|
||||
seed_path = parent.seed_path()
|
||||
seed_mtime = os.path.getmtime(seed_path)
|
||||
Cache.set(f'component.{self.name()}.configure-mtime', seed_mtime)
|
||||
|
||||
|
||||
def build(self, **kwargs):
|
||||
pass
|
||||
|
||||
|
||||
def install(self, **kwargs):
|
||||
self.install_from_seed()
|
||||
|
||||
previous_mtime = Cache.get(f'component.{self.name()}.build-mtime')
|
||||
if previous_mtime is None:
|
||||
previous_mtime = 0
|
||||
|
||||
new_mtime = self.__get_mtime()
|
||||
if new_mtime > previous_mtime:
|
||||
Cache.set(f'component.{self.name()}.build-mtime', new_mtime)
|
||||
@@ -0,0 +1,105 @@
|
||||
import os
|
||||
import sys
|
||||
import tomli
|
||||
from dependency import *
|
||||
from config import Config
|
||||
from path import Path
|
||||
|
||||
|
||||
all_targets = {}
|
||||
|
||||
|
||||
class Target(DependencyItem):
|
||||
def all_targets():
|
||||
global all_targets
|
||||
return all_targets
|
||||
|
||||
|
||||
def current_target():
|
||||
name = Config.get('target.name')
|
||||
if not name or name not in all_targets:
|
||||
return None
|
||||
|
||||
return all_targets[name]
|
||||
|
||||
|
||||
def load_file(self, path):
|
||||
with open(path, mode='rb') as f:
|
||||
self.__data = tomli.load(f)
|
||||
|
||||
|
||||
def __init_dependencies(self):
|
||||
self.__dependencies = {}
|
||||
|
||||
if 'dependency' not in self.__data:
|
||||
return
|
||||
|
||||
dependency = self.__data['dependency']
|
||||
if 'seeds' in dependency:
|
||||
seeds = dependency['seeds']
|
||||
self.__dependencies = {}
|
||||
for s in seeds:
|
||||
self.__dependencies[s] = SeedDependency(parent=self.name(), name=s)
|
||||
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__()
|
||||
|
||||
if 'path' not in kwargs:
|
||||
return
|
||||
|
||||
self.load_file(kwargs['path'])
|
||||
self.__init_dependencies()
|
||||
|
||||
|
||||
def name(self):
|
||||
if not 'target' in self.__data:
|
||||
return None
|
||||
|
||||
s = self.__data['target']
|
||||
if 'name' in s:
|
||||
return s['name']
|
||||
return None
|
||||
|
||||
|
||||
def dependencies(self):
|
||||
return self.__dependencies
|
||||
|
||||
|
||||
def base_components(self):
|
||||
if not 'target' in self.__data:
|
||||
return None
|
||||
|
||||
s = self.__data['target']
|
||||
if 'base_components' in s:
|
||||
return s['base_components']
|
||||
|
||||
return []
|
||||
|
||||
|
||||
def install_prefix(self, **kwargs):
|
||||
make_relative = kwargs.get('make_relative', False)
|
||||
path = self.__data.get('target', {}).get('default_prefix', '/')
|
||||
|
||||
if make_relative:
|
||||
path = Path(path).remove_root().get_path
|
||||
|
||||
return path
|
||||
|
||||
|
||||
def scan_directory(root_path):
|
||||
global all_targets
|
||||
|
||||
for root, dirs, files in os.walk(root_path):
|
||||
for f in files:
|
||||
if not f.endswith('.target'):
|
||||
continue
|
||||
target_path = os.path.join(root, f)
|
||||
target = None
|
||||
try:
|
||||
target = Target(path=target_path)
|
||||
except tomli.TOMLDecodeError as e:
|
||||
print('Err: Failed to parse target file {}\n {}'.format(target_path, e))
|
||||
continue
|
||||
|
||||
all_targets[target.name()] = target
|
||||
@@ -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
|
||||
@@ -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()
|
||||
@@ -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
|
||||
|
||||
@@ -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())
|
||||
Executable
+18
@@ -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 "$@"
|
||||
@@ -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)
|
||||
@@ -0,0 +1,2 @@
|
||||
tomli
|
||||
colorama
|
||||
@@ -1,3 +1,9 @@
|
||||
cmake_minimum_required(VERSION 3.31)
|
||||
project(xpcg C)
|
||||
|
||||
find_package(FX REQUIRED COMPONENTS
|
||||
fx.runtime fx.collections fx.cmdline fx.serial fx.io fx.term)
|
||||
|
||||
file(GLOB sources
|
||||
*.c *.h
|
||||
backend/c-mpc/*.c backend/c-mpc/*.h)
|
||||
@@ -5,3 +11,10 @@ file(GLOB sources
|
||||
add_executable(xpcg ${sources})
|
||||
|
||||
target_link_libraries(xpcg FX::Runtime FX::Collections FX::Cmdline FX::Io)
|
||||
install(TARGETS xpcg)
|
||||
|
||||
set_target_properties(
|
||||
xpcg
|
||||
PROPERTIES
|
||||
INSTALL_RPATH
|
||||
"${CMAKE_INSTALL_RPATH};${CMAKE_INSTALL_PREFIX}/lib")
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
[seed]
|
||||
name = 'xpcg'
|
||||
|
||||
[dependency]
|
||||
no_standard_dependencies = true
|
||||
seeds = [
|
||||
'libfx-host',
|
||||
]
|
||||
|
||||
[build]
|
||||
build_for = 'host'
|
||||
build_system = 'cmake'
|
||||
|
||||
[publish]
|
||||
path = [ 'install:bin/' ]
|
||||
|
||||
# vim: ft=toml
|
||||
Reference in New Issue
Block a user