#!/usr/bin/env bash
# build.sh - libk build script
# TODO when compiling C code, first check
# if an assembly override exists for
# the target platform and if there's
# one, ignore the C and assemble the
# override file instead.
export to=${to:-out}
source global/common.sh
if test "$os$arch$bits" = ""; then
say "set the following environment variables to the appropriate atoms describing your system. for help determining the appropriate atoms, see libk.md"
say ' - $os={lin|fbsd|hai|osx…}'
say ' - $arch={x86|arm|ia64|mips…}'
say ' - $bits={|32|64…}'
exit 1
fi
if test "$1" = "-C"; then
say "precleaning repo"
./clean.sh
fi
# TODO: make it possible for user to change
# default set with environment vars
modules=(kcore kmem kstr kio kgraft kfile)
# compose an arch tuple. this is used in
# places, mostly to select the correct
# version of assembly files for a given
# platform (each has an arch tuple in its
# name, following the linkage specifier)
target=$arch.$os
if test "$bits" != ""; then
target=$target.$bits
fi
# determine whether we have unix or posix
# APIs depending on the operating system.
# presumably if the user is running a bash
# script there is some degree of posix
# support available, but we might still be
# building for windows from within cygwin
# or whatever
case $os in
lin|?bsd|and|dar|osx|nix) posix=yes; unix=yes;;
hai) posix=yes; unix=no;;
*) posix=no; unix=no;;
esac
case $os.$bits in
win.32) bin_fmt=win32;;
win.64) bin_fmt=win64;;
osx.32|dar.32) bin_fmt=macho32;;
osx.64|dar.64) bin_fmt=macho64;;
dos.*) bin_fmt=dosexe;;
none.*) bin_fmt=bin;;
*.32) bin_fmt=elf32;;
*.64) bin_fmt=elf64;;
*) say "cannot determine correct binary format to use for target $target"; exit 1;;
esac
# first, we establish the
# parameters of the build
has cc && _cc=cc
has clang && _cc=clang
has gcc && _cc=gcc # prefer gcc
cc=${cc:-$_cc}
has m4 && _m4=m4
m4=${m4:-$_m4}
has nasm && asm=nasm
has yasm && asm=yasm # prefer yasm
export cc
export m4
export gen=${gen:-gen}
library=${library:-static} # {static|shared|both}
export verbose=${verbose:-quiet} # {no|quiet|loud}
doc=${doc:-yes}
export doc_html=${doc_html:-yes}
export doc_pdf=${doc_pdf:-yes}
export doc_man=${doc_man:-yes}
case $library in
static) build_static_library=yes
build_shared_library=no;;
shared) build_static_library=no
build_shared_library=yes;;
both) build_static_library=yes
build_shared_library=yes;;
esac
check cc "your C compiler of choice"
check asm "an assembler that takes Intel syntax and nasm-style-macros"
check m4 "the path to your m4 installation"
export build=$(global/build-id.sh)
if test "$p_headers_errno" = ""; then
case $os in
lin) p_headers_errno="${p_headers_errno:-/usr/include/asm-generic/errno.h /usr/include/asm-generic/errno-base.h}";;
fbsd) p_headers_errno="${p_headers_errno:-/usr/include/errno.h}";;
esac
fi
check p_headers_errno \
'the location of a header defining the values of each errno symbol'
macro_compile_env="-Datom_target_arch=$arch -Datom_target_os=$os -Dtarget_posix=$posix -Dtarget_unix=$unix"
if test "$bits" != ""; then
macro_compile_env="$macro_compile_env -Datom_target_bits=$bits"
fi
comp_mac() {
local src=$1
local output=$2
local flags=$3
if test -e "$output"; then
if test ! "$output" -ot "$src"; then
return
fi
fi
if test "$debug" = yes; then
local dflag=-Dcompile_debug=yes
fi
$m4 $macro_compile_env $dflag -I "$gen" $flags "$src" > "$output"
announce $m4 $macro_compile_env $dflag -I "$gen" $flags "$src" \> "$output"
# yes, this is incredibly stupid. if you know a better way, feel
# free to submit a fix. the problem is there's no way to pass >
# to report in such a way that it'll do the right thing, and if
# you just write > it redirects *report's* output, instead of
# m4's. piece of shit that it is, m4 doesn't have any way to emit
# output into a fille - stdout only apparently. tl;dr i hate bash.
}
comp_asm() {
local src=$1
local output=$2
local flags=$3
if test -e "$output"; then
if test ! "$output" -ot "$src"; then
return
fi
fi
if test "$debug" = yes; then
local dflag="-g dwarf2"
fi
report $asm $flags $dflag "-f$bin_fmt" -i "$gen" -i "$PWD" "$src" -o "$output";
}
comp_co() { comp_c $1 $2 "-c -fPIC"; }
comp_c(){
local src=$1
local output=$2
local flags=$3
if test -e "$output"; then
if test ! "$output" -ot "$src"; then
return
fi
fi
# only rebuild the file if the source file is newer
if test "$debug" = yes; then
local dflag="-g"
else
local dflag="-O4"
fi
if test "$assembly" = yes; then
report $cc $src $3 $dflag -std=c11 -isystem "$to" -isystem "$gen" -isystem "arch/" -ffreestanding -nostdlib "-L$to" -masm=intel -fverbose-asm -S "-o$output.s"
else
report $cc $src $3 $dflag -std=c11 -isystem "$to" -isystem "$gen" -isystem "arch/" -ffreestanding -nostdlib "-L$to" "-o$output"
fi
}
say "commencing libk build $build at $(timestamp)"
# set -x
# get type data
mkdir -p $gen
report $cc -D_emit_m4_include arch/typesize.c -o $gen/typesize
$gen/typesize > gen/typesize.m
# generate syscall tables
if test $posix = yes; then
# on posix, we simply abuse CPP to garner a list of syscalls;
# the file arch/posix/syscalls contains a list of syscalls
# we wish to import; we use sed to transform this into a form
# that cpp will fill out for us, producing a table that the
# awk scripts can handle and turn into a list of constants.
echo '#include <sys/syscall.h>' > $gen/system_calls.t.1 # this is the magic part
cat arch/posix/syscalls > $gen/system_calls.t.2
sed -e 's;^\(.*\)$;\1 SYS_\1;' -i $gen/system_calls.t.2
cat $gen/system_calls.t.1 $gen/system_calls.t.2 | cpp -P >$gen/system_calls.tbl
# generate errno tables the same way
echo '#include <errno.h>' > $gen/error_codes.t.1 # this is the magic part
cat arch/posix/errnos > $gen/error_codes.t.2
sed -e 's;^\(.*\)$;k_platform_error_\1 \1;' -i $gen/error_codes.t.2
cat $gen/error_codes.t.1 $gen/error_codes.t.2 | cpp -P | grep "^k_platform_error" >$gen/error_codes.tbl # haaaack
else
case $os in
*) noimpl 'system call table generation';;
esac
fi
# take the OS-specific system call tables that have been prepared
# for us above and feed them into awk scripts to generate C headers
awk -f arch/syscall.awk -v out=s <$gen/system_calls.tbl>$gen/system_calls.s
awk -f arch/syscall.awk -v out=h <$gen/system_calls.tbl>$gen/system_calls.h
# do the same with our error codes table
awk -f arch/errtbl.awk <$gen/error_codes.tbl >$gen/error_table.h
# generate symbol tables for error handling functions
mkdir -p "$to/k"
awk -f global/gen-conds.awk <global/modules >$to/k/internal.egroup.h
awk -f global/gen-ident.awk <global/modules >$gen/internal.ident.c
comp_co $gen/internal.ident.c $to/internal.ident.o
# first pass: copy all headers into place,
# including ones we need to generate
for mod in ${modules[@]}; do
for h in $(scan mod/$mod '*.h'); do
base=$(basename $h)
cp "$h" "$to/k/$base"
done
for h in $(scan mod/$mod '*.h.m'); do
base=$(basename $h)
dest=${base%%.m}
comp_mac "$h" "$to/k/$dest"
done
done
# second pass: generate manpage, html, and pdf
# versions of the documentation from the md src
if test "$doc" = "yes"; then
global/build-manpage.sh libk.md
for mod in ${modules[@]}; do
for doc in $(scan mod/$mod '*.md'); do
base="$(basename $doc)"
stem="${base%%.md}"
report global/build-manpage.sh "$doc"
done
done
fi
# third pass: compile sources and save the
# resulting object files to $to, tracking
# which is a runtime or function unit. exe's
# will not be compiled until a later pass
fn_objects=()
rt_objects=()
data_objects=( $to/internal.ident.o )
for mod in ${modules[@]}; do
for fn in $(scan mod/$mod '*.fn.c'); do
base="$(basename $fn)"
dest="$to/$mod.${base%%.c}.o"
comp_co "$fn" "$dest"
fn_objects+=("$dest")
done
for rt in $(scan mod/$mod '*.rt.c'); do
base="$(basename $rt)"
dest="$to/$mod.${base%%.c}.o"
comp_co "$rt" "$dest"
rt_objects+=("$dest")
done
for fn in $(scan mod/$mod "*.fn.$target.s"); do
base="$(basename $fn)"
dest="$to/$mod.${base%%.s}.o"
comp_asm "$fn" "$dest"
fn_objects+=("$dest")
done
for rt in $(scan mod/$mod "*.rt.$target.s"); do
base="$(basename $rt)"
dest="$to/$mod.${base%%.s}.o"
comp_asm "$rt" "$dest"
rt_objects+=("$dest")
done
done
# fourth pass: link the libraries that are
# configured to be built
if test $build_static_library == yes; then
for obj in ${fn_objects[@]} ${rt_objects[@]} ${data_objects[@]}; do
report ar rc $to/libk.a $obj
done
report ranlib $to/libk.a
fi
if test $build_shared_library == yes; then
report ld -r ${rt_objects[@]} -o $to/boot.o
report ld -shared ${fn_objects[@]} -o $to/libk.so
fi
# fifth pass: compile the executable tools
# against the libraries created in pass 5
for mod in ${modules[@]}; do
for exe in $(scan mod/$mod '*.exe.c'); do
base="$(basename $exe)"
dest="$to/$mod.${base%%.exe.c}"
if test $build_shared_library == yes; then
comp_c "$to/boot.o $exe" "$dest" -lk
else
comp_c "$exe" "$dest" -lk
fi
done
done
set +x
say "all passes finished; build $build complete at $(timestamp)"