#!/usr/bin/env python3 import argparse # Parsing command-line args import platform # Checking current OS import shutil import os import sys import pyjson5 dependencies = [ 'nasm', 'llvm-ar' ] # Parse command line arguments arg_parser = argparse.ArgumentParser('build.py') arg_parser.add_argument('--clean', action='store_true', help='Remove all object files and binaries') arg_parser.add_argument('--test', help='Compile ciabatta executable with given file') arg_parser.add_argument('--mode', '-m', choices=['debug', 'release'], default='debug', help='Select build configuration') arg_parser.add_argument('--target', '-t', help='Select target OS (default=host)') arg_parser.add_argument('--compiler', '-c', choices=['clang', 'cuik'], default='clang', help='Select compiler') arg_parser.add_argument('--cflags', nargs='*', default=[], help='Pass additional compiler flags') args = arg_parser.parse_args() compiler = args.compiler print('==> Performing basic checks') # Perform cleaning if required if args.clean: shutil.rmtree('bin') shutil.rmtree('lib') os.remove(os.path.join('src', 'ciabatta.c')) os.remove('a') sys.exit(0) # Check host OS target = args.target if target is None: host_os = platform.system().lower() print(f" -> Compiling for host OS: '{host_os}'") if not os.path.exists(os.path.join('src', host_os)): print(f" -> [ERROR] OS '{host_os}' isn't implemented.") sys.exit(1) target = host_os # Add compiler to dependencies dependencies.append(args.compiler) # Check dependencies print('==> Checking dependencies') for dependency in dependencies: if shutil.which(dependency) is None: print(f" -> [ERROR] Missing dependency: '{dependency}'") sys.exit(1) print(' -> Everything OK') # Figure out the flags includes = ['include'] cc = '' cc_defines = [] cc_flags = ['-nostdlib'] crt_file = 'crt.lib' lib_file = 'cia.lib' cc = args.compiler if args.mode == 'release': cc_flags.append('-O2') cc_defines.append('NDEBUG') else: # 'debug' cc_flags.append('-g') cc_flags.append('-O0') cc_defines.append('DEBUG') # Generate TinyRT headers for the target platform print(f"==> Generating TinyRT header for {target}") tinyrt_config_path = os.path.join('src', target, 'tinyrt.json') tinyrt_apis = [] try: print(f" -> Reading file '{tinyrt_config_path}'") with open(tinyrt_config_path, 'r') as tinyrt_config_file: tinyrt_config = pyjson5.load(tinyrt_config_file) except Exception as error: print(f" -> [ERROR] reading file '{tinyrt_config_path}'") print(f" * {error}") sys.exit(1) tinyrt_header_path = os.path.join('src', target, 'tinyrt-iface.h') try: print(f" -> Writing to file '{tinyrt_header_path}'") with open(tinyrt_header_path, 'w') as tinyrt_header_file: tinyrt_header_file.write('\n') tinyrt_header_file.write(f'// This file is AUTO-GENERATED from {tinyrt_config_path}\n') tinyrt_header_file.write('\n') for tinyrt_api in tinyrt_config: api_name = tinyrt_api is_defined = tinyrt_config[tinyrt_api] if is_defined: tinyrt_apis.append(api_name) is_defined_int = 1 if is_defined else 0 tinyrt_header_file.write(f'#define {api_name.upper()} {is_defined_int}\n') except Exception as error: print(f" -> [ERROR] writing to file '{tinyrt_header_path}'") print(f" * {error}") sys.exit(1) print(' -> TinyRT header generated') # Generate ciabatta header for the target platform and configuration print(f"==> Generating ciabatta.c") library_config_path = os.path.join('src', 'library.json') try: print(f" -> Reading file '{library_config_path}'") with open(library_config_path, 'r') as library_config_file: library_config = pyjson5.load(library_config_file) except Exception as error: print(f" -> [ERROR] reading file '{library_config_path}'") print(f" * {error}") sys.exit(1) ciabatta_header_path = os.path.join('src', 'ciabatta.c') try: print(f" -> Writing to file '{ciabatta_header_path}'") with open(ciabatta_header_path, 'w') as ciabatta_header: # Write heading ciabatta_header.write('\n') ciabatta_header.write('// This file is AUTO-GENERATED by library.json\n') ciabatta_header.write('\n') # Write includes for include in library_config['includes']: ciabatta_header.write(f'#include <{include}>\n') ciabatta_header.write('\n') # Write platform includes platform_config = None for platform in library_config['platforms']: if platform['name'] == target: platform_config = platform if platform_config is None: print(f" -> [ERROR] library config doesn't contain configuration for platform {target}") for include in platform_config['includes']: include_path = os.path.join(platform_config['path'], include) ciabatta_header.write(f'#include "{include_path}"\n') ciabatta_header.write(f'#include "{target}/tinyrt-iface.h"\n') ciabatta_header.write(f'#include "tinyrt.h"\n') for tinyrt_source in platform_config['tinyrt']: ciabatta_header.write(f'#include "{target}/{tinyrt_source}"\n') # Write API includes ciabatta_header.write('\n') for api in library_config['apis']: api_name = api['name'] api_path = api['path'] tinyrt_satisfied = True for req in api['reqs']: if not (req in tinyrt_apis): tinyrt_satisfied = False break if not tinyrt_satisfied: print(f" -> Not exporting API '{api_name}'") else: print(f" * Exporting API '{api_name}'") ciabatta_header.write(f'// Module {api_name}\n') for include in api['includes']: ciabatta_header.write(f'#include "{api_path}/{include}"\n') except Exception as error: print(f" -> [ERROR] writing file '{ciabatta_header_path}'") print(f" * {error}") sys.exit(1) def quote(s): return '"' + s + '"' def prefix(prefix): return (lambda s: prefix + s) def prefix_quote(prefix): return (lambda s: prefix + '"' + s + '"') cc_flags_str = ' '.join( cc_flags + list(map(prefix('-D '), cc_defines)) + list(map(prefix_quote('-I '), includes))) print(f"==> Compiling {lib_file}") print(' * Compiler flags:', cc_flags_str) def assemble(src, out): format = 'elf64' if target == 'windows': format = 'win64' cmdline = f'nasm -f "{format}" "{src}" -o "{out}"' print(' >', cmdline) code = os.system(cmdline) if code != 0: sys.exit(code) def compile(srcs, out, extra_flags = ''): flags = cc_flags_str + ' ' + extra_flags + ' '.join(args.cflags) inputs = ' '.join(map(quote, srcs)) cmdline = f'{compiler} {flags} {inputs} -o {quote(out)}' print(' >', cmdline) code = os.system(cmdline) if code != 0: sys.exit(code) def archive(srcs, out): inputs = ' '.join(map(quote, srcs)) cmdline = f'llvm-ar -rcs {quote(out)} {inputs}' print(' >', cmdline) code = os.system(cmdline) if code != 0: sys.exit(code) # Ciabatta build spec if not os.path.exists('lib'): os.mkdir('lib') if not os.path.exists('bin'): os.mkdir('bin') p = os.path.join assemble(p('src', 'linux', 'crt-entry.asm'), p('bin', 'crt-entry.o')) compile([p('src', 'linux', 'crt-ctors.c')], p('bin', 'crt-ctors.o'), '-fpic -c') compile([p('src', 'ciabatta.c')], p('bin', 'ciabatta.o'), '-fpic -c') archive([p('bin', 'ciabatta.o'), p('bin', 'crt-ctors.o'), p('bin', 'crt-entry.o')], p('lib', lib_file)) if args.test: compile([args.test, p('lib', lib_file)], 'a', '-pie')