meta: add sources from prototype meadow build system
This commit is contained in:
@@ -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
|
||||
Reference in New Issue
Block a user