ciabatta/build.py

266 lines
9.4 KiB
Python
Raw Normal View History

#!/usr/bin/env python3
import argparse # Parsing command-line args
import platform # Checking current OS
import shutil
import os
import sys
import pyjson5
2023-07-30 22:43:01 +00:00
# Colors
class colors:
grey='\033[38;5;247m'
cyan='\033[38;5;80m'
red='\033[38;5;196m'
green='\033[32m'
reset='\033[0m'
# 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
# Perform cleaning if required
2023-07-28 08:53:06 +00:00
def rm(path):
if os.path.exists(path):
os.remove(path)
if args.clean:
2023-07-28 08:53:06 +00:00
if os.path.exists('bin'):
shutil.rmtree('bin')
if os.path.exists('lib'):
shutil.rmtree('lib')
rm(os.path.join('src', 'ciabatta.c'))
rm('a')
rm('a.exe')
rm('a.ilk')
rm('a.pdb')
sys.exit(0)
# Check host OS
2023-07-30 22:43:01 +00:00
print(colors.grey, '==> Performing basic checks', colors.reset, sep='')
target = args.target
if target is None:
2023-07-30 22:43:01 +00:00
target = platform.system().lower()
print(f" * Compiling for host OS: '{colors.cyan}{target}{colors.reset}'")
if not os.path.exists(os.path.join('src', target)):
print(colors.red, f" ERROR: OS '{colors.cyan}{target}{colors.red}' isn't implemented.", colors.reset)
sys.exit(1)
# Add compiler to dependencies
2023-07-28 08:53:06 +00:00
dependencies = [
'nasm',
'llvm-ar'
]
dependencies.append(args.compiler)
# Figure out the flags
includes = ['include']
2023-07-28 08:53:06 +00:00
cc = args.compiler
cc_defines = []
cc_flags = ['-nostdlib']
crt_file = 'crt.lib'
lib_file = 'cia.lib'
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')
2023-07-28 08:53:06 +00:00
if target != 'windows':
cc_flags.append('-fpic')
cc_defines.append(f'_CIA_OS_{target.upper()}')
2023-07-28 08:53:06 +00:00
# Check dependencies
2023-07-30 22:43:01 +00:00
print(colors.grey, '==> Checking dependencies...', colors.reset, end='', sep='')
2023-07-28 08:53:06 +00:00
for dependency in dependencies:
if shutil.which(dependency) is None:
2023-07-30 22:43:01 +00:00
print(colors.red, f"\n -> [ERROR] Missing dependency: '{dependency}'", colors.reset)
2023-07-28 08:53:06 +00:00
sys.exit(1)
2023-07-30 22:43:01 +00:00
print(colors.green, 'OK', colors.reset)
2023-07-28 08:53:06 +00:00
# Generate TinyRT headers for the target platform
2023-07-30 22:43:01 +00:00
print(colors.grey, f"==> Generating TinyRT header for {target}...", colors.reset, end='', sep='')
tinyrt_config_path = os.path.join('src', target, 'tinyrt.json')
tinyrt_apis = []
try:
with open(tinyrt_config_path, 'r') as tinyrt_config_file:
2023-07-28 09:53:20 +00:00
string = tinyrt_config_file.read()
json = '{' + string + '}'
tinyrt_config = pyjson5.loads(json)
except Exception as error:
2023-07-30 22:43:01 +00:00
print(colors.red, f"\n -> [ERROR] reading file '{tinyrt_config_path}'")
print(f" * {error}", colors.reset)
sys.exit(1)
tinyrt_header_path = os.path.join('src', target, 'tinyrt-iface.h')
try:
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
2023-07-27 13:04:24 +00:00
tinyrt_header_file.write(f'#define _{api_name.upper()} {is_defined_int}\n')
except Exception as error:
2023-07-30 22:43:01 +00:00
print(colors.red, f" -> [ERROR] writing to file '{tinyrt_header_path}'")
print(f" * {error}", colors.reset)
sys.exit(1)
2023-07-30 22:43:01 +00:00
print(colors.green, 'OK', colors.reset)
# Generate ciabatta header for the target platform and configuration
2023-07-30 22:43:01 +00:00
print(colors.grey, f"==> Generating ciabatta.c", colors.reset, sep='')
library_config_path = os.path.join('src', 'library.json')
try:
with open(library_config_path, 'r') as library_config_file:
2023-07-28 09:53:20 +00:00
string = library_config_file.read()
json = '{' + string + '}'
library_config = pyjson5.loads(json)
except Exception as error:
2023-07-30 22:43:01 +00:00
print(colors.red, f" ERROR when reading file '{library_config_path}':")
print(f" {error}", colors.reset)
sys.exit(1)
ciabatta_header_path = os.path.join('src', 'ciabatta.c')
try:
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')
2023-07-27 13:49:53 +00:00
# Write main include
ciabatta_header.write('#include <cia-def.h>\n')
2023-07-28 09:55:41 +00:00
ciabatta_header.write('\n')
# Write platform includes
2023-07-28 09:55:41 +00:00
ciabatta_header.write('// Platform-dependent sources\n')
platform_config = None
for platform in library_config['platforms']:
if platform['name'] == target:
platform_config = platform
if platform_config is None:
2023-07-30 22:43:01 +00:00
print(colors.red, f" ERROR: library config doesn't contain configuration for platform {target}", colors.reset, sep='')
for include in platform_config['includes']:
2023-07-28 08:53:06 +00:00
include_path = platform_config['path']
ciabatta_header.write(f'#include "{include_path}/{include}"\n')
ciabatta_header.write(f'#include "{target}/tinyrt-iface.h"\n')
2023-07-27 13:49:53 +00:00
ciabatta_header.write(f'#include <tinyrt.h>\n')
for tinyrt_source in platform_config['tinyrt']:
ciabatta_header.write(f'#include "{target}/{tinyrt_source}"\n')
2023-07-28 09:55:41 +00:00
ciabatta_header.write('\n')
2023-07-27 13:49:53 +00:00
# Write module includes
2023-07-28 09:55:41 +00:00
ciabatta_header.write('// Forward declarations')
2023-07-27 13:49:53 +00:00
for include in library_config['includes']:
ciabatta_header.write(f'#include <{include}>\n')
ciabatta_header.write('\n')
# Write module sources
2023-07-28 10:52:12 +00:00
mod_exports = []
for api in library_config['apis']:
api_name = api['name']
api_path = api['path']
2023-07-28 10:52:12 +00:00
reqs_satisfied = True
# Check API dependencies
for req in api['reqs']:
if req in tinyrt_apis:
continue
elif req in mod_exports:
continue
reqs_satisfied = False
break
2023-07-28 10:52:12 +00:00
if not reqs_satisfied:
2023-07-30 22:43:01 +00:00
print(colors.red, f" * Not exporting API '{api_name}'", colors.reset, sep='')
else:
2023-07-30 22:43:01 +00:00
print(colors.green, f" * Exporting API '{api_name}'", colors.reset, sep='')
ciabatta_header.write(f'// Module {api_name}\n')
2023-07-28 10:52:12 +00:00
mod_exports.append(api_name)
for include in api['includes']:
ciabatta_header.write(f'#include "{api_path}/{include}"\n')
except Exception as error:
2023-07-30 22:43:01 +00:00
print(colors.red, f" ERROR writing file '{ciabatta_header_path}':", sep='')
print(f" {error}", colors.reset)
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)))
2023-07-30 22:43:01 +00:00
print(colors.grey, f"==> Compiling {lib_file}", colors.reset, sep='')
def assemble(src, out):
format = 'elf64'
if target == 'windows':
format = 'win64'
cmdline = f'nasm -f "{format}" "{src}" -o "{out}"'
2023-07-30 22:43:01 +00:00
print(' $', cmdline)
code = os.system(cmdline)
if code != 0:
sys.exit(code)
def compile(srcs, out, extra_flags = ''):
2023-07-28 09:16:09 +00:00
if cc == 'cuik' and out.endswith('.o'):
out = out[:-2]
2023-07-31 15:05:05 +00:00
flags = cc_flags_str + ' ' + extra_flags + ' ' + ' '.join(args.cflags)
inputs = ' '.join(map(quote, srcs))
2023-07-28 08:53:06 +00:00
cmdline = f'{cc} {flags} {inputs} -o {quote(out)}'
2023-07-30 22:43:01 +00:00
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}'
2023-07-30 22:43:01 +00:00
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')
2023-07-28 08:53:06 +00:00
def p(path):
l = path.split('/')
return os.path.join(*l)
cia_lib = p(f'lib/{lib_file}')
crt_lib = p(f'lib/{crt_file}')
ciabatta_c = p('src/ciabatta.c')
ciabatta_o = p('bin/ciabatta.o')
if target == 'linux':
assemble(p('src/linux/crt-entry.asm'), p('bin/crt-entry.o'))
compile([p('src/linux/crt-ctors.c')], p('bin/crt-ctors.o'), '-c')
2023-07-28 11:41:53 +00:00
archive([p('bin/crt-ctors.o'), p('bin/crt-entry.o')], crt_lib)
2023-07-28 08:53:06 +00:00
elif target == 'windows':
assemble(p('src/windows/chkstk.asm'), p('bin/chkstk.o'))
compile([p('src/windows/crt-entry.c')], p('bin/crt-entry.o'), '-c')
2023-07-28 09:41:37 +00:00
archive([p('bin/crt-entry.o'), p('bin/chkstk.o')], crt_lib)
2023-07-28 08:53:06 +00:00
compile([ciabatta_c], ciabatta_o, '-c')
archive([ciabatta_o], cia_lib)
if args.test:
2023-07-28 08:53:06 +00:00
if target == 'linux':
compile([args.test, cia_lib, crt_lib], 'a', '-pie')
elif target == 'windows':
2023-07-30 20:55:59 +00:00
compile([args.test, cia_lib, crt_lib], 'a.exe', '-lkernel32.lib')