2023-07-26 22:17:57 +00:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
|
|
|
|
import argparse # Parsing command-line args
|
|
|
|
import platform # Checking current OS
|
|
|
|
import shutil
|
|
|
|
import os
|
|
|
|
import sys
|
|
|
|
import pyjson5
|
2023-08-01 23:34:37 +00:00
|
|
|
import re
|
2023-07-26 22:17:57 +00:00
|
|
|
|
2023-07-30 22:43:01 +00:00
|
|
|
# Colors
|
|
|
|
class colors:
|
2023-08-01 23:34:37 +00:00
|
|
|
grey='\033[38;5;243m'
|
2023-07-30 22:43:01 +00:00
|
|
|
cyan='\033[38;5;80m'
|
|
|
|
red='\033[38;5;196m'
|
2023-08-01 23:34:37 +00:00
|
|
|
purple='\033[38;5;141m'
|
|
|
|
yellow='\033[38;5;220m'
|
2023-07-30 22:43:01 +00:00
|
|
|
green='\033[32m'
|
|
|
|
reset='\033[0m'
|
2023-07-26 22:17:57 +00:00
|
|
|
|
2023-08-01 23:34:37 +00:00
|
|
|
def print_step(step):
|
|
|
|
print(f'{colors.green}==>{colors.reset} {step}', end='')
|
|
|
|
|
2023-07-26 22:17:57 +00:00
|
|
|
# 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)
|
2023-07-26 22:17:57 +00:00
|
|
|
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')
|
2023-07-26 22:17:57 +00:00
|
|
|
sys.exit(0)
|
|
|
|
|
|
|
|
# Check host OS
|
2023-08-01 23:34:37 +00:00
|
|
|
print_step('Performing basic checks\n')
|
2023-07-26 22:17:57 +00:00
|
|
|
target = args.target
|
|
|
|
if target is None:
|
2023-07-30 22:43:01 +00:00
|
|
|
target = platform.system().lower()
|
2023-08-01 23:34:37 +00:00
|
|
|
target_colored = f'{colors.cyan}{target}{colors.reset}'
|
|
|
|
print(f" * Compiling for host OS: '{target_colored}'")
|
2023-07-30 22:43:01 +00:00
|
|
|
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)
|
2023-07-26 22:17:57 +00:00
|
|
|
|
|
|
|
# Add compiler to dependencies
|
2023-07-28 08:53:06 +00:00
|
|
|
dependencies = [
|
|
|
|
'nasm',
|
|
|
|
'llvm-ar'
|
|
|
|
]
|
2023-07-26 22:17:57 +00:00
|
|
|
dependencies.append(args.compiler)
|
|
|
|
|
|
|
|
|
|
|
|
# Figure out the flags
|
2023-08-01 01:20:43 +00:00
|
|
|
includes = ['src/include']
|
2023-07-28 08:53:06 +00:00
|
|
|
cc = args.compiler
|
2023-07-26 22:17:57 +00:00
|
|
|
cc_defines = []
|
|
|
|
cc_flags = ['-nostdlib']
|
|
|
|
crt_file = 'crt.lib'
|
|
|
|
lib_file = 'cia.lib'
|
2023-08-02 03:43:07 +00:00
|
|
|
dl_file = 'ld-cia.so'
|
2023-07-26 22:17:57 +00:00
|
|
|
if args.mode == 'release':
|
|
|
|
cc_flags.append('-O2')
|
|
|
|
cc_defines.append('NDEBUG')
|
|
|
|
else: # 'debug'
|
|
|
|
cc_flags.append('-g')
|
|
|
|
cc_flags.append('-O0')
|
2023-08-01 00:28:18 +00:00
|
|
|
cc_defines.append('_DEBUG')
|
2023-07-28 08:53:06 +00:00
|
|
|
if target != 'windows':
|
|
|
|
cc_flags.append('-fpic')
|
2023-07-26 22:17:57 +00:00
|
|
|
|
2023-07-28 08:53:06 +00:00
|
|
|
# Check dependencies
|
2023-08-01 23:34:37 +00:00
|
|
|
print_step('Checking dependencies... ')
|
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
|
|
|
|
2023-07-26 22:17:57 +00:00
|
|
|
# Generate TinyRT headers for the target platform
|
2023-08-01 23:34:37 +00:00
|
|
|
print_step(f'Generating TinyRT header for: {target_colored}... ')
|
2023-07-26 22:17:57 +00:00
|
|
|
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)
|
2023-07-26 22:17:57 +00:00
|
|
|
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)
|
2023-07-26 22:17:57 +00:00
|
|
|
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')
|
2023-07-26 22:17:57 +00:00
|
|
|
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)
|
2023-07-26 22:17:57 +00:00
|
|
|
sys.exit(1)
|
2023-07-30 22:43:01 +00:00
|
|
|
print(colors.green, 'OK', colors.reset)
|
2023-07-26 22:17:57 +00:00
|
|
|
|
|
|
|
# Generate ciabatta header for the target platform and configuration
|
2023-08-01 23:34:37 +00:00
|
|
|
print_step('Generating ciabatta.c\n')
|
2023-07-26 22:17:57 +00:00
|
|
|
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)
|
2023-07-26 22:17:57 +00:00
|
|
|
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)
|
2023-07-26 22:17:57 +00:00
|
|
|
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')
|
2023-07-26 22:17:57 +00:00
|
|
|
# Write platform includes
|
2023-07-28 09:55:41 +00:00
|
|
|
ciabatta_header.write('// Platform-dependent sources\n')
|
2023-07-26 22:17:57 +00:00
|
|
|
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='')
|
2023-07-26 22:17:57 +00:00
|
|
|
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')
|
2023-07-26 22:17:57 +00:00
|
|
|
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')
|
2023-07-26 22:17:57 +00:00
|
|
|
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 = []
|
2023-08-01 02:05:10 +00:00
|
|
|
requirements_satisfied = True
|
2023-07-26 22:17:57 +00:00
|
|
|
for api in library_config['apis']:
|
|
|
|
api_name = api['name']
|
|
|
|
api_path = api['path']
|
2023-08-01 02:05:10 +00:00
|
|
|
if api_name in library_config['export']:
|
|
|
|
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
|
|
|
|
if not reqs_satisfied:
|
|
|
|
print(colors.red, f" * Not exporting API '{api_name}'", colors.reset, sep='')
|
|
|
|
requirements_satisfied = False
|
|
|
|
else:
|
|
|
|
print(colors.green, f" * Exporting API '{api_name}'", colors.reset, sep='')
|
|
|
|
ciabatta_header.write(f'// Module {api_name}\n')
|
|
|
|
mod_exports.append(api_name)
|
|
|
|
for include in api['includes']:
|
|
|
|
ciabatta_header.write(f'#include "{api_path}/{include}"\n')
|
|
|
|
if not requirements_satisfied:
|
|
|
|
sys.exit(1)
|
2023-07-26 22:17:57 +00:00
|
|
|
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)
|
2023-07-26 22:17:57 +00:00
|
|
|
sys.exit(1)
|
2023-08-01 23:34:37 +00:00
|
|
|
print_step('Generating cia-conf.h\n')
|
2023-07-26 22:17:57 +00:00
|
|
|
|
2023-08-01 23:34:37 +00:00
|
|
|
def path(str):
|
|
|
|
l = str.split('/')
|
|
|
|
return os.path.join(*l)
|
2023-07-26 22:17:57 +00:00
|
|
|
def quote(s):
|
|
|
|
return '"' + s + '"'
|
|
|
|
def prefix(prefix):
|
|
|
|
return (lambda s: prefix + s)
|
|
|
|
|
|
|
|
cc_flags_str = ' '.join(
|
|
|
|
cc_flags +
|
|
|
|
list(map(prefix('-D '), cc_defines)) +
|
2023-08-01 23:34:37 +00:00
|
|
|
list(map(prefix('-I '), map(quote, includes))))
|
2023-07-26 22:17:57 +00:00
|
|
|
|
2023-08-01 01:20:43 +00:00
|
|
|
try:
|
|
|
|
with open(os.path.join('src', 'include', 'cia-conf.h'), 'w') as conf_file:
|
|
|
|
os_config = open(os.path.join('src', target, 'conf.h'))
|
|
|
|
conf_file.write('\n')
|
|
|
|
conf_file.write('// This file is AUTO-GENERATED\n')
|
|
|
|
conf_file.write('// See os folder (e.g. src/linux) for conf.h file\n')
|
|
|
|
conf_file.write(os_config.read())
|
|
|
|
os_config.close()
|
|
|
|
except Exception as error:
|
|
|
|
print(colors.red, f" ERROR writing file '{ciabatta_header_path}':", sep='')
|
|
|
|
print(f" {error}", colors.reset)
|
|
|
|
sys.exit(1)
|
2023-07-26 22:17:57 +00:00
|
|
|
|
2023-08-01 23:34:37 +00:00
|
|
|
def run(cmdline):
|
|
|
|
cmdline_colored = re.sub(r'"(.+?)"', f'{colors.grey}"\\1"{colors.reset}', cmdline)
|
|
|
|
cmdline_colored = re.sub(r'^([a-zA-Z0-9-_]*)', f'{colors.yellow}\\1{colors.reset}', cmdline_colored)
|
|
|
|
print(' $', cmdline_colored)
|
|
|
|
code = os.system(cmdline)
|
|
|
|
if code != 0:
|
|
|
|
sys.exit(code)
|
|
|
|
|
2023-07-26 22:17:57 +00:00
|
|
|
def assemble(src, out):
|
|
|
|
format = 'elf64'
|
|
|
|
if target == 'windows':
|
|
|
|
format = 'win64'
|
2023-08-01 23:34:37 +00:00
|
|
|
cmdline = f'nasm -f "{format}" "{path(src)}" -o "{path(out)}"'
|
|
|
|
run(cmdline)
|
2023-07-26 22:17:57 +00:00
|
|
|
|
|
|
|
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)
|
2023-08-01 23:34:37 +00:00
|
|
|
inputs = ' '.join(map(quote, map(path, srcs)))
|
|
|
|
cmdline = f'{cc} {flags} {inputs} -o {quote(path(out))}'
|
|
|
|
run(cmdline)
|
2023-07-26 22:17:57 +00:00
|
|
|
|
|
|
|
def archive(srcs, out):
|
|
|
|
inputs = ' '.join(map(quote, srcs))
|
|
|
|
cmdline = f'llvm-ar -rcs {quote(out)} {inputs}'
|
2023-08-01 23:34:37 +00:00
|
|
|
run(cmdline)
|
2023-07-26 22:17:57 +00:00
|
|
|
|
|
|
|
# Ciabatta build spec
|
|
|
|
if not os.path.exists('lib'):
|
|
|
|
os.mkdir('lib')
|
|
|
|
if not os.path.exists('bin'):
|
|
|
|
os.mkdir('bin')
|
|
|
|
|
2023-08-01 23:34:37 +00:00
|
|
|
cia_lib = f'lib/{lib_file}'
|
|
|
|
crt_lib = f'lib/{crt_file}'
|
2023-08-02 03:43:07 +00:00
|
|
|
dl_lib = f'lib/{dl_file}'
|
|
|
|
|
|
|
|
if target == 'linux':
|
|
|
|
print_step(f'Compiling {dl_lib}\n')
|
|
|
|
assemble('src/loader/loader-entry.asm', 'bin/loader-entry.o')
|
|
|
|
compile(['bin/loader-entry.o', 'src/loader/loader-entry.c'], dl_lib,
|
|
|
|
'-shared -nostdlib -Wl,-e,_dlstart -Wl,--sort-section,alignment -Wl,--sort-common -Wl,--gc-sections -Wl,--hash-style=both -Wl,--no-undefined -Wl,--exclude-libs=ALL -fno-stack-protector')
|
2023-07-28 08:53:06 +00:00
|
|
|
|
2023-08-01 23:34:37 +00:00
|
|
|
print_step(f'Compiling {crt_file}\n')
|
2023-07-28 08:53:06 +00:00
|
|
|
if target == 'linux':
|
2023-08-01 23:34:37 +00:00
|
|
|
assemble('src/linux/crt-entry.asm', 'bin/crt-entry.o')
|
|
|
|
compile(['src/linux/crt-ctors.c'], 'bin/crt-ctors.o', '-c')
|
|
|
|
archive(['bin/crt-ctors.o', 'bin/crt-entry.o'], crt_lib)
|
2023-07-28 08:53:06 +00:00
|
|
|
elif target == 'windows':
|
2023-08-01 23:34:37 +00:00
|
|
|
assemble('src/windows/chkstk.asm', 'bin/chkstk.o')
|
|
|
|
compile(['src/windows/crt-entry.c'], 'bin/crt-entry.o', '-c')
|
|
|
|
archive(['bin/crt-entry.o', 'bin/chkstk.o'], crt_lib)
|
|
|
|
|
|
|
|
print_step(f'Compiling {lib_file}\n')
|
|
|
|
compile(['src/ciabatta.c'], 'bin/ciabatta.o', '-c')
|
|
|
|
archive(['bin/ciabatta.o'], cia_lib)
|
2023-07-26 22:17:57 +00:00
|
|
|
|
|
|
|
if args.test:
|
2023-07-28 08:53:06 +00:00
|
|
|
if target == 'linux':
|
2023-08-02 03:43:07 +00:00
|
|
|
compile([args.test, crt_lib, cia_lib], 'a', f'-pie -Wl,-dynamic-linker,{dl_lib}')
|
2023-07-28 08:53:06 +00:00
|
|
|
elif target == 'windows':
|
2023-08-01 00:26:13 +00:00
|
|
|
compile([args.test, crt_lib, cia_lib], 'a.exe', '-lkernel32.lib')
|