#!/bin/bash ###### Usage ################################################################## # To use the bld build system in a bash script, some setup is required: # 1. Define paths: # root_path, opts_path, bld_path, local_path, src_path, # build_path, fuzz_path, program_pather # 2. Define local variables: # compiler, compile_mode, arch, linker, ctx_opts # 3. Then include this core file with "." prefix: # . $bld_path/bld_core.sh # # # The following bld functions form the core "commands": # bld_compile, bld_link, bld_lib, bld_unit # # And there are several more helper functions: # bld_print_implicit_opts, bld_load_local_opts # # # Signatures: # bld_compile # bld_link -- \ # # bld_lib -- \ # # bld_unit # # bld_print_implicit_opts (no arguments) # bld_load_local_opts (no arguments) # # # Shared notes for all: # + Options are gathered from the arguments, from the local context, and # in the case of the commands bld_compile and bld_unit, from the source file. # + Options gathered from local context are prefixed with their context # variable like so [var:val]. So for example, on windows the options list # includes [os:windows]. # + Options in source files are specified between the strings //$ and //. # + Object file extensions should be named based on whether they were # generated by a compiler or assembler. Compiled objects should have the # extension .cmp:o and assembled objects should have the extension # .asm:o. The system automatically changes the name to match the correct # extension given the context. # + Static library files should have the extension .lib. The system # automatically changes the name to match the correct extension given the # context. # + When the option "diagnostics" is included, extra details will be printed # from the bld commands. Showing the full list of options, invokation lines, # and the build path. # # bld_compile: # + Creates an object file from a single source file via the selected compiler. # # bld_link: # + Creates executables and shared binaries from source files, object files, # and static libraries. First uses the bld_compile command on the source # files. # + Uses the selected linker. # + If the short name for the linker does not work for any reason, the # full path to the linker may be specified in a filter file pointed to by # the setup variable `program_pather` # + The output is a shared binary (.dll or .so) when the option "dll" is # included. # # bld_lib: # + Creates a static library from source files and object files. First uses # the bld_compile command on the source files. Uses the OS to determine the # correct archiver. # # bld_unit: # + Creates an executable (or shared binary) from a single source file. # This command essentially does a single bld_compile then bld_link. With # two differences: # 1. The options from the source file are visible to the bld_link. # 2. The name of the executable is determined either from the first option # in the list or from the source file name if there are no options. # # bld_print_implicit_opts: # + Shows the implicit options loaded from the local parameters script. # # bld_load_local_opts: # + It is possible and sometimes useful to modify the local parameters after # they are loaded to create certain kinds of build scripts. This function # resets the local parameters to their original states by rerunning the # local parameters script. ###### Flags From Opts ######################################################## 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 bld_opts_from_src { ###### split file into tokens ############################################# 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 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[@]}" } ###### Has Opt ################################################################ 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 } ###### Load Local Options ##################################################### function bld_load_local_opts { ###### os cracking ######################################################## os="undefined" if [ "$OSTYPE" == "win32" ] || [ "$OSTYPE" == "msys" ]; then os="windows" elif [ "$OSTYPE" == "linux-gnu" ]; then os="linux" elif [ "$OSTYPE" == "darwin" ]; then os="mac" fi ###### load parameters from the local script ################################ if [ -f "$local_path/local_vars.sh" ]; then source "$local_path/local_vars.sh" fi } ###### Implicit Opts ########################################################## function bld_implicit_opts { local linker_opt="" if [[ "$linker" == "link" || "$linker" == "lld-link" ]]; then linker_opt="link:msvc" fi echo $ctx_opts cmp:$compiler mode:$compile_mode asm:$assembler echo link:$linker $linker_opt os:$os arch:$arch } ###### Print Implicit Options ################################################# function bld_print_implicit_opts { local opts=($(bld_implicit_opts)) local bracketed=() for ((i=0; i<=${#opts[@]}; i+=1)); do local opt="${opts[i]}" if [ "$opt" != "" ]; then bracketed+=("[${opts[i]}]") fi done echo "${bracketed[@]}" } ###### Print Help ############################################################# function bld_print_obj_note { echo "NOTE: the interface does not use standrd object file extensions" echo "NOTE: use 'cmp:o' for object files produced by a compiler" echo "NOTE: use 'asm:o' for object files produced by an assembler" } ###### Compile ################################################################ function bld_compile { local i=0 ###### parse arguments #################################################### local in_file=$1 local opts=() for ((i=2; i<=$#; i+=1)); do opts+=(${!i}) done if [ "$in_file" == "" ]; then echo "ERROR(compile): missing input file" return 1 fi ###### finish in file ##################################################### local final_in_file=$in_file ###### determine build kind ############################################### local ext="${final_in_file##*.}" local build_kind="undetermined" if [[ "$ext" == "c" || "$ext" == "cpp" ]]; then build_kind="compile" elif [[ "$ext" == "asm" ]]; then build_kind="assemble" else echo "ERROR(compile): unrecgonized source type $final_in_file" return 1 fi ###### finish options ##################################################### local src_opts=($(bld_opts_from_src $final_in_file)) local impl_opts=($(bld_implicit_opts)) local all_opts=($(bld_dedup ${impl_opts[@]} ${opts[@]} ${src_opts[@]})) ###### diagnostics ######################################################## local diagnostics=$(bld_has_opt diagnostics ${all_opts[@]}) ###### out file name ###################################################### local dot_ext_o="" if [[ "$build_kind" == "compile" ]]; then dot_ext_o=$(bld_ext_cmpo) elif [[ "$build_kind" == "assemble" ]]; then dot_ext_o=$(bld_ext_asmo) fi local file_base=${final_in_file##*/} local file_base_no_ext=${file_base%.*} local out_file="$file_base_no_ext$dot_ext_o" ###### get real flags ##################################################### local flags="" if [[ "$build_kind" == "compile" ]]; then flags=$(bld_flags_from_opts $opts_path/compiler_flags.txt ${all_opts[@]}) else flags=$(bld_flags_from_opts $opts_path/assembler_flags.txt ${all_opts[@]}) fi ###### move to output folder ############################################## mkdir -p "$build_path" cd $build_path if [ "$diagnostics" == "1" ]; then echo "build path: $build_path" fi ###### delete existing object file ######################################## rm -f "$out_file_base.o" rm -f "$out_file_base.obj" ###### final flags ######################################################## local final_flags="" if [[ "$build_kind" == "compile" ]]; then final_flags="-c -I$src_path ${flags}" elif [[ "$build_kind" == "assemble" ]]; then final_flags="-c ${flags}" fi ###### compile ############################################################ if [[ "$build_kind" == "compile" ]]; then if [ "$diagnostics" == "1" ]; then echo "cmp $final_in_file -- ${all_opts[@]}" echo $compiler "$final_in_file" $final_flags fi if [ "$compiler" == "clang" ]; then echo "$file_base" fi $compiler "$final_in_file" $final_flags elif [[ "$build_kind" == "assemble" ]]; then if [ "$diagnostics" == "1" ]; then echo "asm $final_in_file -- ${all_opts[@]}" echo $assembler $final_flags "$final_in_file" fi $assembler $final_flags "$final_in_file" fi # return of status from compiler is automatic here. } ###### Link ################################################################### function bld_link { local i=0 ###### 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 impl_opts=($(bld_implicit_opts)) local all_opts=($(bld_dedup ${opts[@]} ${impl_opts[@]})) ###### diagnostics ######################################################## local diagnostics=$(bld_has_opt diagnostics ${all_opts[@]}) ###### sort in files ###################################################### local in_src=() local in_cmpo=() local in_asmo=() 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" || "$ext" == "asm" ]]; then in_src+=($file) elif [[ "$ext" == "cmp:o" ]]; then in_cmpo+=($file) elif [[ "$ext" == "asm:o" ]]; then in_asmo+=($file) elif [[ "$ext" == "lib" ]]; then in_lib+=($file) else echo "WARNING: ignoring unrecgonized file type $file" if [[ "$ext" == "obj" || "$ext" == "o" ]]; then bld_print_obj_note fi fi done ###### auto correct compiled object files ################################# local dot_ext_cmpo=$(bld_ext_cmpo) for ((i=0; i<${#in_cmpo[@]}; i+=1)); do local file_name="${in_cmpo[i]}" local base_name="${file_name%.*}" in_cmpo[$i]="$base_name$dot_ext_cmpo" done ###### auto correct assembled object files ################################ local dot_ext_asmo=$(bld_ext_asmo) for ((i=0; i<${#in_asmo[@]}; i+=1)); do local file_name="${in_asmo[i]}" local base_name="${file_name%.*}" in_asmo[$i]="$base_name$dot_ext_asmo" done ###### combine object files ############################################### local in_obj=() for ((i=0; i<${#in_cmpo[@]}; i+=1)); do local file="${in_cmpo[i]}" in_obj+=($file) done for ((i=0; i<${#in_asmo[@]}; i+=1)); do local file="${in_asmo[i]}" in_obj+=($file) done ###### compile source files ############################################### for ((i=0; i<${#in_src[@]}; i+=1)); do local file="${in_src[i]}" bld_compile "$file" ${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%.*}" local ext="${base_name##*.}" if [[ "$ext" == "c" || "$ext" == "cpp" ]]; then interm_obj+=($base_name_no_ext$dot_ext_cmpo) elif [[ "$ext" == "asm" ]]; then interm_obj+=($base_name_no_ext$dot_ext_asmo) fi done ###### get real flags ##################################################### local flags=$(bld_flags_from_opts $opts_path/linker_flags.txt ${all_opts[@]}) ###### out file name ###################################################### local dot_ext_out="" local is_dll=$(bld_has_opt dll ${all_opts[@]}) if [ "$is_dll" == "0" ]; then dot_ext_out=$(bld_ext_exe) else dot_ext_out=$(bld_ext_dll) fi out_file="$out_name$dot_ext_out" ###### move to output folder ############################################## mkdir -p "$build_path" cd $build_path if [ "$diagnostics" == "1" ]; then echo "build path: $build_path" fi ###### final files to linker ############################################## local final_in_files="${interm_obj[@]} ${in_obj[@]} ${in_lib[@]}" ###### set first diagnostic string ######################################## local first_diagnostic_string="lnk $final_in_files -- ${all_opts[@]}" ###### linker executable name ############################################# local linker_exe=$linker local linker_rename=$(bld_flags_from_opts $program_pather "link:$linker") if [[ "$linker_rename" != "" ]]; then if [ "$diagnostics" == "1" ]; then echo "linker rename: $linker_rename" fi linker_exe=$linker_rename fi ###### link ############################################################### local status=0 local invokation="" if [[ "$linker" == "link" || "$linker" == "lld-link" ]]; then invokation="-OUT:$out_file $flags $final_in_files" elif [ "$linker" == "clang" ]; then invokation="-o $out_file $flags $final_in_files" else echo "ERROR(link): invokation not defined for this linker" status=1 fi if [ "$invokation" != "" ]; then if [ "$diagnostics" == "1" ]; then echo $first_diagnostic_string echo $linker_exe $invokation fi echo "$out_file" "$linker_exe" $invokation status=$? fi return $status } ###### Library ################################################################ function bld_lib { local i=0 ###### 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 impl_opts=($(bld_implicit_opts)) local all_opts=($(bld_dedup ${opts[@]} ${impl_opts[@]})) ###### diagnostics ######################################################## local diagnostics=$(bld_has_opt diagnostics ${all_opts[@]}) ###### sort in files ###################################################### local in_src=() local in_cmpo=() local in_asmo=() 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" == "cmp:o" ]]; then in_cmpo+=($file) elif [[ "$ext" == "asm:o" ]]; then in_asmo+=($file) else echo "WARNING: ignoring unrecgonized file type $file" if [[ "$ext" == "obj" || "$ext" == "o" ]]; then bld_print_obj_note fi fi done ###### auto correct compiled object files ################################# local dot_ext_cmpo=$(bld_ext_cmpo) for ((i=0; i<${#in_cmpo[@]}; i+=1)); do local file_name="${in_cmpo[i]}" local base_name="${file_name%.*}" in_cmpo[$i]=$base_name$dot_ext_cmpo done ###### auto correct assembled object files ################################ local dot_ext_asmo=$(bld_ext_asmo) for ((i=0; i<${#in_asmo[@]}; i+=1)); do local file_name="${in_asmo[i]}" local base_name="${file_name%.*}" in_asmo[$i]="$base_name$dot_ext_asmo" done ###### combine object files ############################################### local in_obj=() for ((i=0; i<${#in_cmpo[@]}; i+=1)); do local file="${in_cmpo[i]}" in_obj+=($file) done for ((i=0; i<${#in_asmo[@]}; i+=1)); do local file="${in_asmo[i]}" in_obj+=($file) done ###### compile source files ############################################### for ((i=0; i<${#in_src[@]}; i+=1)); do bld_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%.*}" local ext="${base_name##*.}" if [[ "$ext" == "c" || "$ext" == "cpp" ]]; then interm_obj+=($base_name_no_ext$dot_ext_cmpo) elif [[ "$ext" == "asm" ]]; then interm_obj+=($base_name_no_ext$dot_ext_asmo) fi 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(lib): static library output not defined for OS: $os" fi ###### final library build input files #################################### local final_in_files="${interm_obj[@]} ${in_obj[@]}" ###### move to output folder ############################################## mkdir -p "$build_path" cd $build_path if [ "$diagnostics" == "1" ]; then echo "build path: $build_path" fi ###### set first diagnostic string ######################################## local first_diagnostic_string="lib $final_in_files -- ${all_opts[@]}" ###### build library ###################################################### local status=0 if [ "$os" == "windows" ]; then if [ "$diagnostics" == "1" ]; then echo $first_diagnostic_string echo lib -nologo -OUT:"$out_file" $final_in_files fi echo "$out_file" lib -nologo -OUT:"$out_file" $final_in_files status=$? 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" status=1 else echo "ERROR(lib): static library invokation not defined for OS: $os" status=1 fi return $status } ###### Unit ################################################################### function bld_unit { local i=0 ###### parse arguments #################################################### local main_file=$1 local opts=() for ((i=2; i<=$#; i+=1)); do opts+=(${!i}) done if [ "$main_file" == "" ]; then echo "unit: missing main file" return 1 fi ###### set out name ####################################################### local out_name="" if [ "${#opts}" == "0" ]; then local file_base=${main_file##*/} local file_base_no_ext=${file_base%.*} out_name=$file_base_no_ext else out_name="${opts[0]}" fi ###### finish options ##################################################### local src_opts=$(bld_opts_from_src $main_file) local impl_opts=($(bld_implicit_opts)) local all_opts=($(bld_dedup $out_name ${opts[@]} ${src_opts[@]} ${impl_opts[@]})) ###### link ############################################################### bld_link $out_name $main_file ${in_files[@]} -- ${all_opts[@]} } ###### Special Ifs ############################################################ function bld_ext_cmpo { echo $(bld_flags_from_opts $opts_path/file_extensions.txt cmp:$compiler "cmp:o") } function bld_ext_asmo { echo $(bld_flags_from_opts $opts_path/file_extensions.txt asm:$assembler "asm:o") } function bld_ext_exe { echo $(bld_flags_from_opts $opts_path/file_extensions.txt os:$os exe) } function bld_ext_dll { echo $(bld_flags_from_opts $opts_path/file_extensions.txt os:$os dll) } ###### Load Locals ############################################################ bld_load_local_opts ############################################################################### # TODO: Notes for future itations on this sytem # - With the addition of the assembler, it is now becoming clear that the # bld_compile path is feeling a bit overloaded and that the mapping of # interface extensions to real extensions, and the mapping of interface # extensions to inferred build steps, are major complicating factors. # A new strategy for organizing multiple builders, and multiple conversion # paths that may be inferred from a set of possible starting points would # stand to simplify a lot of the system. # - Switching out object file extensions # - Subtlties of invoking different build kinds in bld_compile # - Tracking subkinds through bld_link and bld_lib