#!/bin/bash set -eo pipefail ###### Usage ################################################################## # ./bld_core.sh [arguments ...] # # command: compile # ./bld_core.sh compile [opts ...] # # Creates object files from a single source file and options with a compiler. # Scans the source file for '//$' and '//' and pulls options out of the tokens # in between, combines options from the source file with the passed options. # # # command: link # ./bld_core.sh link [input-file ...] -- [opts ...] # # Creates binaries (executables and dynamic libraries) from objects, # libraries, and options with a linker. # First automatically compiles source files into objects. # Object files can be specified as '.o' or '.obj', and will be # automatically adjusted according to the selected compiler. # Library files should be specified with '.lib', and will be # automatically adjusted according to the selected linker. # If the option 'dll' appears in the list the linker will produce a dynamic # library (or "shared object"). # # # command: lib # ./bld_core.sh lib [input-file ...] -- [opts ...] # # Creates library archives (.lib or .a) from objects. # First automatically compiles source files into objects. # Object files can be specified as '.o' or '.obj', and will be # automatically adjusted according to the selected compiler. ###### Has Opt Function ####################################################### function bld_has_opt { ###### parse arguments #################################################### local key_opt=$1 local opts=() for ((i=2; i<=$#; i+=1)); do opts+=(${!i}) done ###### scan ############################################################### local has_key=0 for ((i=0;i<${#opts[@]};i+=1)); do local opt=${opts[i]} if [[ "$opt" == "$key_opt" ]]; then has_key=1 break fi done echo $has_key } ###### Flags From Opts Function ############################################### function bld_flags_from_opts { ###### parse arguments #################################################### local in_file=$1 local opts=() for ((i=2; i<=$#; i+=1)); do opts+=(${!i}) done ###### load file ########################################################## local flags_raw=() IFS=$'\r\n' GLOBIGNORE='*' command eval 'flags_raw=($(cat $in_file))' ###### filter ############################################################# local flags=() for ((i=0;i<${#flags_raw[@]};i+=1)); do local flag=${flags_raw[i]} ###### skip blanks and comments ####################################### if [[ -z "${flag// }" ]]; then continue fi if [[ "${flag:0:1}" == "#" ]]; then continue fi ###### parse line filters ############################################# local line_filters=() while [[ $flag = *">"* ]]; do line_filters+=("${flag%%>*}") flag="${flag#*>}" done ###### check filters ################################################## local can_include=1 for ((j=0;j<${#line_filters[@]};j+=1)); do can_include=0 for ((k=0;k<${#opts[@]};k+=1)); do if [[ ${opts[k]} = ${line_filters[j]} ]]; then can_include=1 break fi done if [[ "$can_include" = "0" ]]; then break fi done if [[ "$can_include" = "1" ]]; then flags+=("${flag}") fi done echo "${flags[@]}" } ###### Opts From Src Function ################################################# function bld_opts_from_src { ###### Split File Into Lines ############################################## local in_file=$1 local tokens=($(grep "//\\$" $in_file)) ###### Parse ############################################################## local in_params_range="0" local params=() for ((i=0; i<${#tokens[@]}; i+=1)); do local string="${tokens[i]}" if [[ "$in_params_range" == "0" ]]; then if [[ "$string" == "//$" ]]; then in_params_range="1" fi elif [[ "$in_params_range" == "1" ]]; then if [[ "${string:0:2}" == "//" ]]; then break fi params+=($string) fi done echo "${params[@]}" } ###### Dedup Function ######################################################### function bld_dedup { ###### parse arguments #################################################### local in=() for ((i=1; i<=$#; i+=1)); do in+=(${!i}) done ###### dedup ############################################################## local out=() for ((i=0; i<${#in[@]}; i+=1)); do local string=${in[i]} local is_dup="0" for ((j=0; j<${#out[@]}; j+=1)); do if [[ "$string" == "${out[j]}" ]]; then is_dup="1" break fi done if [[ "$is_dup" == "0" ]]; then out+=($string) fi done echo "${out[@]}" } ###### Compile Function ####################################################### function bld_compile { ###### parse arguments #################################################### local in_file=$1 local opts=() for ((i=2; i<=$#; i+=1)); do opts+=(${!i}) done if [ "$in_file" == "" ]; then echo "compile: missing input file" return 1 fi ###### finith in file ##################################################### local final_in_file=$root_path/$in_file ###### out file name ###################################################### local file_base=${final_in_file##*/} local file_base_no_ext=${file_base%.*} local out_file="$file_base_no_ext$dot_ext_obj" ###### finish options ##################################################### local src_opts=($(bld_opts_from_src $final_in_file)) local all_opts=($(bld_dedup $file_base ${opts[@]} ${src_opts[@]})) ###### diagnostics ######################################################## local diagnostics=$(bld_has_opt diagnostics ${all_opts[@]}) ###### get real flags ##################################################### local flags_file=$bin_path/compiler_flags.txt local flags=($(bld_flags_from_opts $flags_file ${all_opts[@]})) ###### get inc paths ###################################################### local paths_file=$bin_path/compiler_inc_paths.txt local paths=($(bld_flags_from_opts $paths_file ${all_opts[@]})) local incs=() for ((i=0; i<${#paths[@]}; i+=1)); do incs+=("-I$root_path/${paths[i]}") done ###### move to output folder ############################################## mkdir -p "$build_path" cd $build_path ###### delete existing object file ######################################## rm -f "$out_file_base.o" rm -f "$out_file_base.obj" ###### get flags ########################################################## local all_flags="-c -I$src_path ${incs[@]} ${flags[@]}" ###### diagnostic output ################################################## if [ "$diagnostics" == "1" ]; then echo "cmp $final_in_file -- ${all_opts[@]}" echo $compiler "$final_in_file" $all_flags fi ###### print source file (if the compiler doesn't do it automatically) #### if [ "$manually_print_target" == "1" ]; then echo "$file_base" fi ###### compile ############################################################ $compiler "$final_in_file" $all_flags } ###### Link Function ########################################################## function bld_link { ###### parse arguments #################################################### local out_name=$1 local in_files=() for ((i=2; i<=$#; i+=1)); do if [ "${!i}" == "--" ]; then break fi in_files+=(${!i}) done local opts=() for ((i+=1; i<=$#; i+=1)); do opts+=(${!i}) done if [ "$out_name" == "" ]; then echo "link: missing output name" return 1 fi if [ "${#in_files}" == "0" ]; then echo "link: missing input file(s)" return 1 fi ###### finish options ##################################################### local all_opts=($(bld_dedup ${opts[@]})) ###### diagnostics ######################################################## local diagnostics=$(bld_has_opt diagnostics ${all_opts[@]}) ###### sort in files ###################################################### local in_src=() local in_obj=() local in_lib=() for ((i=0; i<${#in_files[@]}; i+=1)); do local file="${in_files[i]}" local ext="${file##*.}" if [[ "$ext" == "c" || "$ext" == "cpp" ]]; then in_src+=($file) elif [[ "$ext" == "o" || "$ext" == "obj" ]]; then in_obj+=($file) elif [[ "$ext" == "lib" ]]; then in_lib+=($file) else echo "WARNING: ignoring unrecognized file type $file" fi done ###### auto correct object files ########################################## for ((i=0; i<${#in_obj[@]}; i+=1)); do local file_name="${in_obj[i]}" local base_name="${file_name%.*}" in_obj[$i]="$base_name$dot_ext_obj" done ###### compile source files ############################################### for ((i=0; i<${#in_src[@]}; i+=1)); do bld_compile "${in_src[i]}" ${all_opts[@]} status=$? if [ $status -ne 0 ]; then exit $status fi done ###### intermediate object files ########################################## local interm_obj=() for ((i=0; i<${#in_src[@]}; i+=1)); do local file_name="${in_src[i]}" local base_name="${file_name##*/}" local base_name_no_ext="${base_name%.*}" interm_obj+=($base_name_no_ext$dot_ext_obj) done ###### get real flags ##################################################### local flags_file=$bin_path/linker_flags.txt local flags=$(bld_flags_from_opts $flags_file ${all_opts[@]}) ###### link ############################################################### local linker_kind="link" if [ $linker == "clang" ]; then linker_kind="clang" fi ###### out file name ###################################################### local bin_kind="exe" for ((i=0; i<${#all_opts[@]}; i+=1)); do if [[ "${all_opts[i]}" == "dll" ]]; then bin_kind="dll" break fi done dot_ext_out="$dot_ext_exe" if [ "$bin_kind" == "dll" ]; then dot_ext_out="$dot_ext_dll" fi out_file="$out_name$dot_ext_out" ###### move to output folder ############################################## mkdir -p "$build_path" cd $build_path ###### diagnostic output ################################################## local final_in_files="${interm_obj[@]} ${in_obj[@]} ${in_lib[@]}" if [ "$diagnostics" == "1" ]; then echo "lnk $final_in_files -- ${all_opts[@]}" fi ###### link ############################################################### if [ "$linker_kind" == "link" ]; then if [ "$diagnostics" == "1" ]; then echo $linker -OUT:"$out_file" $flags $final_in_files fi echo "$out_file" $linker -OUT:"$out_file" $flags $final_in_files elif [ "$linker_kind" == "clang" ]; then if [ "$diagnostics" == "1" ]; then echo $linker -o "$out_file" $flags $final_in_files fi echo "$out_file" $linker -o "$out_file" $flags $final_in_files else echo "ERROR: invokation not defined for this linker" false fi status=$? return $status } ###### Library Function ####################################################### function bld_lib { ###### parse arguments #################################################### local out_name=$1 local in_files=() for ((i=2; i<=$#; i+=1)); do if [ "${!i}" == "--" ]; then break fi in_files+=(${!i}) done local opts=() for ((i+=1; i<=$#; i+=1)); do opts+=(${!i}) done if [ "$out_name" == "" ]; then echo "lib: missing output name" return 1 fi if [ "${#in_files}" == "0" ]; then echo "lib: missing input file(s)" return 1 fi ###### finish options ##################################################### local all_opts=($(bld_dedup ${opts[@]})) ###### diagnostics ######################################################## local diagnostics=$(bld_has_opt diagnostics ${all_opts[@]}) ###### sort in files ###################################################### local in_src=() local in_obj=() for ((i=0; i<${#in_files[@]}; i+=1)); do local file="${in_files[i]}" local ext="${file##*.}" if [[ "$ext" == "c" || "$ext" == "cpp" ]]; then in_src+=($file) elif [[ "$ext" == "o" || "$ext" == "obj" ]]; then in_obj+=($file) else echo "WARNING: ingnoring unrecgonized file type $file" fi done ###### auto correct object files ########################################## for ((i=0; i<${#in_obj[@]}; i+=1)); do local file_name="${in_obj[i]}" local base_name="${file_name%.*}" in_obj[$i]=$base_name$dot_ext_obj done ###### compile source files ############################################### for ((i=0; i<${#in_src[@]}; i+=1)); do compile "${in_src[i]}" ${all_opts[@]} local status=$? if [ $status -ne 0 ]; then return $status fi done ###### intermediate object files ########################################## local interm_obj=() for ((i=0; i<${#in_src[@]}; i+=1)); do local file_name="${in_src[i]}" local base_name="${file_name##*/}" local base_name_no_ext="${base_name%.*}" interm_obj+=($base_name_no_ext$dot_ext_obj) done ###### out file name ###################################################### local out_file="" if [ "$os" == "windows" ]; then out_file="$out_name.lib" elif [ "$os" == "linux" || "$os" == "mac" ]; then out_file="lib$out_name.a" else echo "ERROR: static library output not defined for OS: $os" fi ###### diagnostic output ################################################## local final_in_files="${interm_obj[@]} ${in_obj[@]}" if [ "$diagnostics" == "1" ]; then echo "lib $final_in_files -- ${all_opts[@]}" fi ###### print output file ################################################## echo "$out_file" ###### move to output folder ############################################## mkdir -p "$build_path" cd $build_path ###### build library ###################################################### if [ "$os" == "windows" ]; then lib -nologo -OUT:"$out_file" $final_in_files elif [ "$os" == "linux" || "$os" == "mac" ]; then # TODO(allen): invoke ar here - make sure to delete the original .a first # because ar does not (seem) to replace the output file, just append echo "TODO: implement ar path in bld_core.sh:bld_lib" false else echo "ERROR: static library invokation not defined for OS: $os" false fi status=$? return $status } ###### Unit Function ########################################################## function bld_unit { ###### parse arguments #################################################### local out_name=$1 local main_file=$2 in_files=() for ((i=3; i<=$#; i+=1)); do if [ "${!i}" == "--" ]; then break fi in_files+=(${!i}) done if [ "$out_name" == "" ]; then echo "unit: missing output name" return 1 fi if [ "$main_file" == "" ]; then echo "unit: missing main file" return 1 fi ###### finish options ##################################################### local src_opts=$(bld_opts_from_src $root_path/$main_file) local all_opts=($(bld_dedup $out_name ${src_opts[@]} ${implicit_opts[@]})) ###### link ############################################################### bld_link $out_name $main_file ${in_files[@]} -- ${all_opts[@]} status=$? return $status } ###### Show Ctx Function ###################################################### function bld_show_ctx { local mod_opts=() for ((i=0;i<${#implicit_opts[@]};i+=1)); do local mod_opt="[${implicit_opts[i]}]" mod_opts+=($mod_opt) done echo "${mod_opts[@]}" } ###### Get Paths ############################################################## og_path=$PWD cd "$(dirname "$0")" cd .. root_path=$PWD bin_path="$root_path/bin" local_path="$root_path/local" build_path="$root_path/build" src_path="$root_path/source" ###### Crack Operating System From Environment ################################ os="undefined" if [ "$OSTYPE" == "win32" ] || [ "$OSTYPE" == "msys" ]; then os="windows" elif [ "$OSTYPE" == "linux-gnu" ]; then os="linux" elif [[ "$OSTYPE" == "darwin"* ]]; then os="mac" fi ###### Implicit Options ####################################################### compiler=$($local_path/compiler.sh) linker=$($local_path/linker.sh) compile_mode=$($local_path/compile_mode.sh) arch=$($local_path/arch.sh) ctx=$($local_path/ctx.sh) ###### Apply Override ######################################################### parse_i="1" override_list=() if [ "${!parse_i}" == "override" ]; then for ((parse_i+=1; parse_i<=$#; parse_i+=1)); do arg="${!parse_i}" if [ "$arg" == "--" ]; then ((parse_i+=1)) break fi if [[ "$arg" == *":"* ]]; then arg_key="${arg%%:*}" arg_val="${arg#*:}" declare "${arg_key}=$arg_val" override_list+=("[$arg]") fi done fi ###### Finish Implicit Options ################################################ implicit_opts=($out_name $compiler $linker $compile_mode $os $arch $ctx) ###### Object File Extension ################################################## dot_ext_obj=".o" if [[ "$compiler" == "cl" ]]; then dot_ext_obj=".obj" fi ###### Binary File Extension ################################################## dot_ext_exe=".exe" dot_ext_dll="" if [ "$os" == "windows" ]; then dot_ext_dll=".dll" elif [ "$os" == "linux" ] || [ "$os" == "mac" ]; then dot_ext_dll=".so" else echo "ERROR: binary extension not defined for OS: $os" fi ###### Binary File Extension ################################################## manually_print_target="0" if [ "$compiler" == "clang" ]; then manually_print_target="1" fi ###### Diagnostics ############################################################ top_diagnostics=$(bld_has_opt diagnostics ${implicit_opts[@]}) ###### Overrides Diagnostics ################################################## if [[ "$top_diagnostics" == "1" && "${#override_list}" != "0" ]]; then echo "${override_list[@]} {" fi ###### Control ################################################################ command=${!parse_i} args=() for ((parse_i+=1; parse_i<=$#; parse_i+=1)); do args+=(${!parse_i}) done if [ "$command" == "compile" ]; then bld_compile ${args[@]} elif [ "$command" == "link" ]; then bld_link ${args[@]} elif [ "$command" == "lib" ]; then bld_lib ${args[@]} elif [ "$command" == "unit" ]; then bld_unit ${args[@]} elif [ "$command" == "show_ctx" ]; then bld_show_ctx else echo "'$command' not a recognized command" fi ###### Overrides Diagnostics ################################################## if [[ "$top_diagnostics" == "1" && "${#override_list}" != "0" ]]; then echo "}" fi ###### Restore Path ########################################################### cd $og_path