#!/bin/bash
variable() {
local name=$1
shift
echo "$name = $*"
}
declare -A rule_implicit_dependencies
rule() {
local name=$1
shift
local command=
local deps=
local depfile=
local implicit_dependencies=
local generator=
while (( "$#" )); do
local arg=$1
shift
if [[ $arg = --command ]]; then
command=$1
shift
elif [[ $arg = --generator ]]; then
generator=1
elif [[ $arg = --implicit ]]; then
implicit_dependencies="$implicit_dependencies $1"
shift
elif [[ $arg = --deps ]]; then
deps="$1"
shift
elif [[ $arg = --depfile ]]; then
depfile="$1"
shift
fi
done
rule_implicit_dependencies[$name]=$implicit_dependencies
echo "rule $name"
if [[ ! -z $command ]]; then
echo " command = $command"
fi
if [[ ! -z $deps ]]; then
echo " deps = $deps"
fi
if [[ ! -z $depfile ]]; then
echo " depfile = $depfile"
fi
if [[ ! -z $generator ]]; then
echo " generator = 1"
fi
}
build() {
local output=$1
shift
local _rule=$1
shift
local inputs=
local implicit_dependencies=${rule_implicit_dependencies[$_rule]}
local flags=
while (( "$#" )); do
local arg=$1
shift
if [[ $arg = --implicit ]]; then
implicit_dependencies="$implicit_dependencies $1"
shift
elif [[ $arg = --flags ]]; then
flags=$1
shift
else
inputs="$inputs $arg"
fi
done
implicit_dependencies=$(echo $implicit_dependencies | uniq)
if [[ ! -z "$implicit_dependencies" ]]; then
implicit_dependencies="| $implicit_dependencies"
fi
echo "build $output : $_rule $inputs $implicit_dependencies"
if [[ ! -z "$flags" ]]; then
echo " flags = $flags"
fi
}
PKG_CONFIG_lnx=pkg-config
CC_lnx=gcc
LINK_lnx=gcc
EXE_lnx=
CFLAGS_lnx=
LFLAGS_lnx=
AR_lnx=ar
PKG_CONFIG_win=x86_64-w64-mingw32-pkg-config
CC_win=x86_64-w64-mingw32-gcc
LINK_win=x86_64-w64-mingw32-gcc
EXE_win=".exe"
CFLAGS_win=
LFLAGS_win=
AR_win=ar
declare -A target_defined target_include_directories target_defines target_cflags target_lflags target_implicit_dependencies target_link_dependencies
target() {
local name=$1
shift
local platform=lnx
local sources=
local objects=
local executable=
local phony=
local include_directories=
local defines=
local private_defines=
local cflags=
local lflags=
local implicit_dependencies=
local link_dependencies=
local no_c=
local PKG_CONFIG=PKG_CONFIG_${platform}
while (( "$#" )); do
local arg=$1
shift
if [[ $arg = --executable ]]; then
executable=1
elif [[ $arg = --phony ]]; then
phony=1
elif [[ $arg = --include-directory ]]; then
include_directories="$include_directories -I$1"
shift
elif [[ $arg = --cflag ]]; then
cflags="$cflags $1"
shift
elif [[ $arg = --lflag ]]; then
lflags="$lflags $1"
shift
elif [[ $arg = --implicit ]]; then
implicit_dependencies="$implicit_dependencies $1"
shift
elif [[ $arg = --define ]]; then
defines="$defines -D$1"
shift
elif [[ $arg = --define-private ]]; then
private_defines="$private_defines -D$1"
shift
elif [[ $arg = --pkg-config ]]; then
PKG_CONFIG=PKG_CONFIG_${platform}
PKG_CONFIG_CMD="${!PKG_CONFIG}"
out_cflags=$($PKG_CONFIG_CMD --cflags $1)
out_lflags=$($PKG_CONFIG_CMD --libs $1)
cflags="$cflags $out_cflags"
lflags="$lflags $out_lflags"
shift
elif [[ $arg = --platform ]]; then
platform=$1
shift
else
local tgt=${target_defined[$arg]}
if [[ -z "$tgt" ]]; then
if [[ -z "$phony" ]]; then
sources="$sources $arg"
no_c=${arg%.c}
[ $arg = $no_c ] && echo "only c sources can be used, got $arg"
objects="$objects \$builddir/${platform}-${no_c//\//-}.o"
else
phony="$phony $arg"
fi
else
include_directories="$include_directories ${target_include_directories[$arg]}"
defines="$defines ${target_defines[$arg]}"
cflags="$cflags ${target_cflags[$arg]}"
lflags="$lflags ${target_lflags[$arg]}"
implicit_dependencies="$implicit_dependencies ${target_implicit_dependencies[$arg]}"
link_dependencies="$link_dependencies ${target_link_dependencies[$arg]}"
fi
fi
done
local PKG_CONFIG=PKG_CONFIG_${platform}
local CC=CC_${platform}
local LINK=LINK_${platform}
local EXE=EXE_${platform}
local CFLAGS=CFLAGS_${platform}
local LFLAGS=LFLAGS_${platform}
local AR=AR_${platform}
if [[ ! -z "$phony" ]]; then
implicit_dependencies="$implicit_dependencies $name"
build $name phony ${phony:1}
fi
implicit_dependencies=$(echo $implicit_dependencies | uniq)
if [[ ! -z "$sources" ]]; then
RULE_DEFINES=$(echo $defines $private_defines | uniq)
rule ${name}_cc \
--command "${!CC} -MD -MF \$out.d $include_directories $RULE_DEFINES ${!CFLAGS} $cflags -c \$in -o \$out" \
--depfile "\$out.d" \
--deps "gcc"
for src in $sources; do
no_c=${src%.c}
build "\$builddir/${platform}-${no_c//\//-}.o" ${name}_cc $src --implicit "$implicit_dependencies"
done
if [[ ! -z "$executable" ]]; then
# ⍷⌾⌽ in BQN. Deduplicate _under_ Reverse
cleaned_lflags=$(tr ' ' '\n' <<<"$lflags" | tac | awk '!u[$0]++' | tac | tr '\n' ' ')
cleaned_link_dependencies=$(tr ' ' '\n' <<<"$link_dependencies" | tac | awk '!u[$0]++' | tac | tr '\n' ' ')
rule ${name}_link \
--command "${!LINK} ${!CFLAGS} $cflags \$in ${!LFLAGS} -L\$builddir/${platform}-lib $cleaned_lflags -o \$out"
build \$builddir/$name${!EXE} ${name}_link $objects --implicit "$cleaned_link_dependencies"
else
rule ${name}_ar \
--command "rm -f \$out && ${!AR} crs \$out \$in"
build \$builddir/${platform}-lib/lib${name}.a ${name}_ar $objects
lflags="-l$name $lflags" # break append convention so libraries are linked from child to parent
link_dependencies="$link_dependencies \$builddir/${platform}-lib/lib${name}.a"
fi
fi
target_defined[$name]=1
target_include_directories[$name]=$(echo $include_directories | uniq)
target_defines[$name]=$(echo $defines | uniq)
target_cflags[$name]=$(echo $cflags | uniq)
target_lflags[$name]=$(echo $lflags | uniq)
target_implicit_dependencies[$name]=$implicit_dependencies
target_link_dependencies[$name]=$link_dependencies
}
phony() {
local name=$1
shift
target $name --phony $*
}
library() {
target $*
}
executable() {
local name=$1
shift
target $name --executable $*
}
PLATFORMS=( lnx win )
per_platform_0() {
local platform=$1
shift
local cmd=$1
shift
local name=$1
shift
if [[ $name == ___-* ]]; then
name="${name:4}"
fi
local args=
while (( "$#" )); do
local arg=$1
shift
if [[ $arg == ___-* ]]; then
arg="$platform-${arg:4}"
fi
args="$args $arg"
done
$cmd $platform-$name --platform $platform $args
}
per_platform() {
for PLATFORM in "${PLATFORMS[@]}"
do
per_platform_0 $PLATFORM $@
done
}