| git.druid.rocks | index | druid520 | sc | sc.sh |
sc.sh
#!/usr/bin/env sh
set -e
conf="sc.conf"
if [ -f "$conf" ]; then
. ./"$conf"
else
echo "err: sc.conf does not exist." >&2
exit 1
fi
: "${bin:=}"
: "${libs:=}"
if [ -z "$bin" ] && [ -z "$libs" ]; then
echo "err: sc.conf must set bin=\"name ...\" and/or libs=\"name ...\" (see sc.conf.example)." >&2
exit 1
fi
: "${src:=src}"
: "${out:=out}"
: "${docsdir:=docs}"
: "${logdir:=logs}"
: "${prefix:=${PREFIX:-/usr/local}}"
# reserved: the six standardized mk/ targets -- a custom h() in sc.conf
# can't be allowed to shadow any of these on disk.
reserved="b c dm dh d dc"
# accumulated across every hard/soft cflag, ldflag and passed lib probe
# below -- becomes ccflgs/ldflgs in the generated mk.conf. -I. up front
# so "#include \"config.h\"" resolves from any source file, regardless
# of which dir under $src it lives in.
cfg_ccflags="-I."
cfg_ldflags=""
cfgh_tmp="config.h.new"
cat > "$cfgh_tmp" <<EOF
/* generated by sc.sh -- do not edit by hand, re-run sc.sh instead. */
#ifndef SC_CONFIG_H
#define SC_CONFIG_H
EOF
# uppercase, strip leading dashes (so "-Wall" -> "WALL", not "_WALL"),
# then turn everything else that isn't [A-Za-z0-9_] into an underscore
# (so "sys/types.h" -> "SYS_TYPES_H", "libxml-2.0" -> "LIBXML_2_0", a
# valid, collision-resistant C identifier fragment either way).
_sc_sanitize() {
printf '%s' "$1" | sed -e 's/^-*//' -e 's/[^A-Za-z0-9_]/_/g' | tr 'a-z' 'A-Z'
}
_sc_macro() {
printf '%s_%s_C' "$1" "$(_sc_sanitize "$2")"
}
# resolves "$hard_libs $hard_libs_$os" (etc) via eval -- the only
# POSIX-portable way to do "indirect" variable lookups.
_sc_osvar() {
eval "printf '%s' \"\${$1:-} \${${1}_${os}:-}\""
}
# lib: pkg-config --exists, and on success folds its --cflags/--libs
# into the flags every source file gets built with.
_sc_check_lib() {
name="$1"; hardness="$2"
echo "lib: checking $name exists..."
if pkg-config --exists "$name" 2>/dev/null; then found="yes"; else found="no"; fi
echo "lib: $name exists? $found."
if [ "$found" = "no" ]; then
if [ "$hardness" = "hard" ]; then
echo "err: please install $name." >&2
exit 1
fi
val=0
else
cfg_ccflags="$cfg_ccflags $(pkg-config --cflags "$name")"
cfg_ldflags="$cfg_ldflags $(pkg-config --libs "$name")"
val=1
fi
echo "#define $(_sc_macro LIB "$name") $val" >> "$cfgh_tmp"
}
# header: exists if cpp can preprocess a bare #include of it.
_sc_check_header() {
name="$1"; hardness="$2"
echo "header: checking $name exists..."
if echo "#include <$name>" | cpp $cfg_ccflags >/dev/null 2>&1; then found="yes"; else found="no"; fi
echo "header: $name exists? $found."
if [ "$found" = "no" ]; then
if [ "$hardness" = "hard" ]; then
echo "err: please install $name." >&2
exit 1
fi
val=0
else
val=1
fi
echo "#define $(_sc_macro HEADER "$name") $val" >> "$cfgh_tmp"
}
# cflag: -Werror makes an unrecognized flag an actual failure instead of
# a silently-ignored warning (some compilers, clang especially, accept
# any -f/-W flag they don't recognize unless told to error on it).
_sc_check_cflag() {
name="$1"; hardness="$2"
echo "cflag: checking $name exists..."
if echo "int main(void) { return 0; }" | $cc $cfg_ccflags $name -Werror -x c -o /dev/null - >/dev/null 2>&1; then
found="yes"
else
found="no"
fi
echo "cflag: $name exists? $found."
if [ "$found" = "no" ]; then
if [ "$hardness" = "hard" ]; then
echo "err: please get support for $name." >&2
exit 1
fi
val=0
else
cfg_ccflags="$cfg_ccflags $name"
val=1
fi
echo "#define $(_sc_macro CFLAG "$name") $val" >> "$cfgh_tmp"
}
_sc_check_ldflag() {
name="$1"; hardness="$2"
echo "ldflag: checking $name exists..."
if echo "int main(void) { return 0; }" | $cc -Werror -x c -o /dev/null - $cfg_ldflags $name >/dev/null 2>&1; then
found="yes"
else
found="no"
fi
echo "ldflag: $name exists? $found."
if [ "$found" = "no" ]; then
if [ "$hardness" = "hard" ]; then
echo "err: please get support for $name." >&2
exit 1
fi
val=0
else
cfg_ldflags="$cfg_ldflags $name"
val=1
fi
echo "#define $(_sc_macro LDFLAG "$name") $val" >> "$cfgh_tmp"
}
# func: proves the symbol exists at link time -- declared with no real
# prototype and only its address taken, so this works regardless of the
# function's actual signature (no header needed, no call made).
_sc_check_func() {
name="$1"; hardness="$2"
echo "func: checking $name exists..."
if printf '#include <stddef.h>\nchar %s(void);\nint main(void) { return (int)(size_t)&%s; }\n' "$name" "$name" \
| $cc $cfg_ccflags -x c -o /dev/null - $cfg_ldflags >/dev/null 2>&1; then
found="yes"
else
found="no"
fi
echo "func: $name exists? $found."
if [ "$found" = "no" ]; then
if [ "$hardness" = "hard" ]; then
echo "err: please provide $name." >&2
exit 1
fi
val=0
else
val=1
fi
echo "#define $(_sc_macro FUNC "$name") $val" >> "$cfgh_tmp"
}
_sc_run_category() {
kind="$1"; checker="$2"
for item in $(_sc_osvar "hard_$kind"); do "$checker" "$item" hard; done
for item in $(_sc_osvar "soft_$kind"); do "$checker" "$item" soft; done
}
: "${oss:?err: sc.conf must set oss=\"os1 os2 ...\"}"
os=$(uname -s | tr '[:upper:]' '[:lower:]')
echo "os: checking if $os is supported..."
supported="no"
# every entry in $oss is tried before giving up -- decided only after the
# loop ends, not inside it, so an os further down the list than the first
# still matches instead of being missed by an early exit.
for _os in $oss; do
if [ "$_os" = "$os" ]; then
supported="yes"
break
fi
done
echo "os: os supported? $supported."
if [ "$supported" = "no" ]; then
echo "err: please use a supported os out of $oss." >&2
exit 1
fi
echo "#define $(_sc_macro OS "$os") 1" >> "$cfgh_tmp"
# a $cc is only actually REQUIRED when something here will really use
# one: a $bin name resolving to a compiled kind (a "$src/<name>/"
# subdirectory, or a "$src/<name>.c"/".s"/".S" -- see mk/b.sh's own,
# fuller version of this same per-name resolution order for what each
# kind means), the single-bin fallback shape (a lone $bin name with
# NONE of its own sources at all, meaning every .c/.s/.S directly under
# $src compiles into it -- sc's original shape, predating any other
# kind), or any hard_/soft_ cflags/libs/ldflags/funcs probe (every one
# of those compiles a trial program to test with). a project made
# entirely of "$bin" names that are perl/sh/generic scripts, with none
# of those probes declared, never touches a C toolchain at all and
# should not be forced to have one installed just to configure.
needs_cc="no"
for name in $bin; do
if [ -d "$src/$name" ] || [ -f "$src/$name.c" ] || [ -f "$src/$name.s" ] || [ -f "$src/$name.S" ]; then
needs_cc="yes"
elif [ ! -f "$src/$name.pl" ] && [ ! -f "$src/$name.sh" ] && [ ! -f "$src/$name" ]; then
needs_cc="yes"
fi
done
# libs=, unlike bin=, has no script-kind fallback at all -- a "library"
# is inherently a compiled-C concept, so ANY libs= entry means a C
# toolchain (and, further below, ar) is required, full stop.
[ -n "$libs" ] && needs_cc="yes"
# _sc_osvar's own return format has a fixed space between its two
# interpolated values (see its own definition above), so even a
# completely unset variable never comes back as a genuinely empty
# string -- checking "-n" directly against its output (or, worse, a
# concatenation of several) is unconditionally true regardless of
# whether anything was actually declared. an unquoted "echo $x" is the
# standard POSIX trick to collapse "nothing but whitespace" down to
# nothing at all: word-splitting drops empty fields, so a value with no
# real words in it becomes truly empty once rejoined.
_allprobes="$(_sc_osvar hard_cflags) $(_sc_osvar soft_cflags) $(_sc_osvar hard_libs) $(_sc_osvar soft_libs) $(_sc_osvar hard_ldflags) $(_sc_osvar soft_ldflags) $(_sc_osvar hard_funcs) $(_sc_osvar soft_funcs)"
if [ -n "$(echo $_allprobes)" ]; then
needs_cc="yes"
fi
cc="${CC:-cc}"
if [ "$needs_cc" = "yes" ]; then
command -v "$cc" >/dev/null 2>&1 || { echo "err: $cc not found on \$PATH." >&2; exit 1; }
fi
# ar: only libs= (the .a archive step) ever needs it -- bin= never links
# a static archive of its own, so a pure-binary project has no reason to
# need one installed.
ar="${AR:-ar}"
if [ -n "$libs" ]; then
command -v "$ar" >/dev/null 2>&1 || { echo "err: $ar not found on \$PATH." >&2; exit 1; }
fi
_alllibs="$(_sc_osvar hard_libs) $(_sc_osvar soft_libs)"
if [ -n "$(echo $_alllibs)" ]; then
command -v pkg-config >/dev/null 2>&1 || { echo "err: pkg-config not found on \$PATH." >&2; exit 1; }
fi
# order matters: cflags first (base compiler flags/macros everything
# after builds with) -> libs (folds pkg-config --cflags/--libs into the
# accumulator) -> headers (benefits from both) -> ldflags -> funcs (the
# most demanding probe, gets the full accumulated cflags AND ldflags).
_sc_run_category cflags _sc_check_cflag
_sc_run_category libs _sc_check_lib
_sc_run_category headers _sc_check_header
_sc_run_category ldflags _sc_check_ldflag
_sc_run_category funcs _sc_check_func
echo >> "$cfgh_tmp"
echo "#endif" >> "$cfgh_tmp"
# a name() { ... } defined in sc.conf becomes mk/name.sh, parsed
# statically out of sc.conf's own text (not via shell introspection,
# which isn't portable) so mk/name.sh gets exactly the body as written.
# brace-depth counted so a body with its own if/case/loop braces still
# extracts whole -- a literal "}" inside a string or comment would still
# confuse it, same known limit as any awk-level brace counter.
_sc_extract_funcs() {
awk '
/^[A-Za-z_][A-Za-z0-9_]*\(\)[ \t]*\{/ {
line = $0
name = line
sub(/\(\).*/, "", name)
rest = line
sub(/^[^{]*\{/, "", rest)
depth = 1
body = ""
tmp = rest
depth += gsub(/\{/, "{", tmp) - gsub(/\}/, "}", tmp)
if (rest != "") body = body rest "\n"
while (depth > 0) {
if ((getline line) <= 0) break
tmp = line
o = gsub(/\{/, "{", tmp)
c = gsub(/\}/, "}", tmp)
depth += o - c
if (depth <= 0) {
sub(/\}[ \t]*$/, "", line)
if (line != "") body = body line "\n"
} else {
body = body line "\n"
}
}
print "SC_FUNC_BEGIN " name
printf "%s", body
print "SC_FUNC_END"
}
' "$conf"
}
rm -rf mk.new
mkdir mk.new
custom_targets=""
current=""
_sc_extract_funcs | while IFS= read -r line; do
case "$line" in
"SC_FUNC_BEGIN "*)
current="${line#SC_FUNC_BEGIN }"
case " $reserved " in
*" $current "*)
echo "err: sc.conf: '$current' is a reserved target name." >&2
exit 1
;;
esac
{
echo "#!/usr/bin/env sh"
echo "set -e"
echo
} > "mk.new/$current.sh"
echo "$current" >> mk.new/.targets
;;
"SC_FUNC_END")
current=""
;;
*)
[ -n "$current" ] && echo "$line" >> "mk.new/$current.sh"
;;
esac
done
[ -f mk.new/.targets ] && custom_targets="$(cat mk.new/.targets; rm -f mk.new/.targets)"
cat > mk.new/b.sh <<'BSH'
. ./mk.conf
set -e
_b_main() {
mkdir -p "$out"
# compiler identification/verbose info -- straight into build.log only
# (a direct append, never through the terminal-visible pipe _sc_capture
# wraps this whole function in), once per build, not per file: exactly
# the kind of thing worth having on hand later without cluttering every
# normal build's terminal output with it. skipped outright when $cc
# isn't even a real command -- a project made entirely of perl/sh/
# generic $bin names has no reason to need one installed at all (see
# sc.sh's own needs_cc check), so this can't assume "-v" will work.
if command -v "$cc" >/dev/null 2>&1; then
{ echo "compiler info ($cc):"; $cc -v 2>&1; echo; } >> "$logfile"
fi
# $bin is one or more space-separated names, each independently
# resolved to a KIND by checking, in order:
# "$src/<name>/" compiled, a whole subdirectory (several files).
# "$src/<name>.c" compiled, one C file.
# "$src/<name>.s"/".S" compiled, one assembly file ($cc accepts .s/.S
# directly -- no separate assembler needed)
# "$src/<name>.pl" a Perl script -- syntax-checked ("perl -c"),
# then installed to "$out/<name>" as-is, no
# linking: the script itself IS the artifact.
# "$src/<name>.sh" a POSIX sh script -- same idea, checked with
# "sh -n".
# "$src/<name>" a bare, extension-less file -- a pre-built
# binary or a script this project doesn't want
# sc to try to compile OR syntax-check at all
# (unrecognized kind), just installed as-is.
# a lone $bin name with NONE of the above is sc's original single-
# binary shape: every .c/.s/.S directly under "$src" is SHARED (built
# once, linked into every compiled-kind name in $bin) and there is
# nothing name-specific to add, so that one binary is built from all of
# "$src" same as always -- every kind added since changes nothing for
# that existing, common layout. a .c/.s/.S directly under "$src" whose
# OWN basename matches some OTHER $bin name is that name's own compiled
# entry point instead, not shared (see _is_bin_name below) -- give a
# shared file and a per-binary entry point files different basenames if
# both exist, or one silently shadows the other's object.
_is_bin_name() {
for _b in $bin; do
[ "$_b" = "$1" ] && return 0
done
return 1
}
# same idea, for libs= -- needed so a source file whose basename matches
# a LIBRARY name is treated as that library's own entry point (below),
# not folded into the generic shared pool either, the identical
# reasoning _is_bin_name already has for $bin names.
_is_lib_name() {
for _l in $libs; do
[ "$_l" = "$1" ] && return 0
done
return 1
}
# f=source o=object d=depfile x=extra cflags (optional -- $picflag for a
# libs= shared-object variant, empty/omitted for a normal, plain object)
# -- sets $need to yes/no and, if yes, actually compiles. each source
# gets its own .o via "$cc -MMD -MP -c" (works for .c, .s, and .S
# alike), which drops a matching .d listing every header it pulled in
# (empty/absent for assembly, which is fine -- the "-f $d" guard below
# just skips it) -- an object is only recompiled if it's missing, older
# than its source, or older than any header the .d says it depends on.
# re-running this (via "mk . b"/"make b" too, all three run this same
# file) with nothing changed then does no work at all beyond each
# binary's/library's final link check. $x is appended, not prepended --
# so it can override an earlier $ccflgs setting (e.g. -fPIC after a
# stray -fno-PIC some hard_cflags= probe added) the same way a
# rightmost flag always wins with a real compiler.
_compile() {
f="$1"; o="$2"; d="$3"; x="${4:-}"
need="no"
if [ ! -f "$o" ]; then
need="yes"
elif [ "$f" -nt "$o" ]; then
need="yes"
elif [ -f "$d" ]; then
for dep in $(sed -e 's/^[^:]*://' -e 's/\\$//' "$d"); do
[ -f "$dep" ] || continue
if [ "$dep" -nt "$o" ]; then
need="yes"
break
fi
done
fi
if [ "$need" = "yes" ]; then
echo "cc: $f"
_sc_run $cc $ccflgs $x -MMD -MP -c "$f" -o "$o"
fi
}
# f=source dest=installed-path checker="checking command"|"" -- for the
# three non-compiled kinds (perl/sh/generic script). no object, no
# link: $dest IS the final artifact, so this is the whole job. $checker
# runs (via _sc_run, so a real syntax error still shows up on the
# terminal live, exactly like a real compile error does) only when
# $dest actually needs updating, same "missing or older than its
# source" rule _compile uses -- re-checking a script's syntax on every
# single build regardless of whether it changed would be needless work
# for a large script. empty $checker (the bare, unrecognized-kind case)
# skips straight to installing -- there is no known way to validate an
# arbitrary pre-built binary or foreign-language script.
_install_script() {
f="$1"; dest="$2"; checker="$3"
need="no"
if [ ! -f "$dest" ]; then
need="yes"
elif [ "$f" -nt "$dest" ]; then
need="yes"
fi
if [ "$need" = "yes" ]; then
if [ -n "$checker" ]; then
echo "check: $f"
_sc_run $checker "$f"
fi
echo "cp: $f -> $dest"
_sc_run cp "$f" "$dest"
chmod +x "$dest"
fi
}
shared_objs=""
shared_relink="no"
for f in "$src"/*.c "$src"/*.s "$src"/*.S; do
[ -e "$f" ] || continue
base="$(basename "$f")"; base="${base%.*}"
_is_bin_name "$base" && continue
_is_lib_name "$base" && continue
o="$out/$base.o"
_compile "$f" "$o" "$out/$base.d"
[ "$need" = "yes" ] && shared_relink="yes"
shared_objs="$shared_objs $o"
done
# the identical shared pool, a second time, as -fPIC objects -- only
# ever built at all when libs= is non-empty (a plain bin=-only project
# never needs a PIC variant of anything, so this stays free for it).
# separate object suffix (.pic.o/.pic.d, not .o/.d) so the two variants
# of the same source never fight over one .d/.o pair -- both may need
# rebuilding independently of each other (a bin= relink never touches
# this pool, and vice versa).
shared_pic_objs=""
shared_pic_relink="no"
if [ -n "$libs" ]; then
for f in "$src"/*.c "$src"/*.s "$src"/*.S; do
[ -e "$f" ] || continue
base="$(basename "$f")"; base="${base%.*}"
_is_bin_name "$base" && continue
_is_lib_name "$base" && continue
o="$out/$base.pic.o"
_compile "$f" "$o" "$out/$base.pic.d" "$picflag"
[ "$need" = "yes" ] && shared_pic_relink="yes"
shared_pic_objs="$shared_pic_objs $o"
done
fi
for name in $bin; do
own_objs=""
relink="$shared_relink"
kind=""
if [ -d "$src/$name" ]; then
kind="compiled"
mkdir -p "$out/$name"
for f in "$src/$name"/*.c "$src/$name"/*.s "$src/$name"/*.S; do
[ -e "$f" ] || continue
base="$(basename "$f")"; base="${base%.*}"
o="$out/$name/$base.o"
_compile "$f" "$o" "$out/$name/$base.d"
[ "$need" = "yes" ] && relink="yes"
own_objs="$own_objs $o"
done
elif [ -f "$src/$name.c" ]; then
kind="compiled"; ownsrc="$src/$name.c"
elif [ -f "$src/$name.s" ]; then
kind="compiled"; ownsrc="$src/$name.s"
elif [ -f "$src/$name.S" ]; then
kind="compiled"; ownsrc="$src/$name.S"
elif [ -f "$src/$name.pl" ]; then
kind="script"; ownsrc="$src/$name.pl"; checker="perl -c"
elif [ -f "$src/$name.sh" ]; then
kind="script"; ownsrc="$src/$name.sh"; checker="sh -n"
elif [ -f "$src/$name" ]; then
kind="script"; ownsrc="$src/$name"; checker=""
fi
if [ "$kind" = "script" ]; then
_install_script "$ownsrc" "$out/$name" "$checker"
continue
fi
if [ "$kind" = "compiled" ] && [ -n "$ownsrc" ]; then
o="$out/$name.o"
_compile "$ownsrc" "$o" "$out/$name.d"
[ "$need" = "yes" ] && relink="yes"
own_objs="$o"
fi
# an EMPTY $kind here is NOT automatically an error: it's exactly
# what the single-bin fallback shape looks like (nothing of its own
# at all under any per-kind name), which is fine as long as
# $shared_objs has something for it to link against -- sc's
# original, most common shape, unchanged by any kind added since.
# it only becomes a real error once $shared_objs is ALSO empty,
# meaning there is truly nothing anywhere to build this binary from
# (a plain "src=" pointing at a nonexistent/misconfigured directory
# falls into this same case for every $bin name at once, and gets
# exactly this same error, once per name).
if [ -z "$shared_objs$own_objs" ]; then
_sc_die "no source found for binary '$name' -- looked for $src/$name/, $src/$name.{c,s,S,pl,sh}, a bare $src/$name, and anything shared directly under $src."
fi
if [ "$relink" = "yes" ] || [ ! -f "$out/$name" ]; then
echo "ld: $out/$name"
_sc_run $cc $ccflgs -o "$out/$name" $shared_objs $own_objs $ldflgs
fi
done
# libs=: same per-name resolution order as $bin (a "$src/<name>/"
# subdirectory, or a "$src/<name>.c"/".s"/".S"), minus the three
# script kinds -- a "library" is inherently something compiled, there
# is no equivalent of installing a perl/sh script as one. unlike a
# binary, EVERY name here needs both a plain object (for the .a) and a
# -fPIC one (for the .so) built in parallel, since the archive and the
# shared object can't share objects with each other.
for name in $libs; do
own_objs=""; own_pic_objs=""
relink="$shared_relink"; relink_pic="$shared_pic_relink"
if [ -d "$src/$name" ]; then
mkdir -p "$out/$name"
for f in "$src/$name"/*.c "$src/$name"/*.s "$src/$name"/*.S; do
[ -e "$f" ] || continue
base="$(basename "$f")"; base="${base%.*}"
o="$out/$name/$base.o"
_compile "$f" "$o" "$out/$name/$base.d"
[ "$need" = "yes" ] && relink="yes"
own_objs="$own_objs $o"
opic="$out/$name/$base.pic.o"
_compile "$f" "$opic" "$out/$name/$base.pic.d" "$picflag"
[ "$need" = "yes" ] && relink_pic="yes"
own_pic_objs="$own_pic_objs $opic"
done
else
ownsrc=""
for ext in c s S; do
if [ -f "$src/$name.$ext" ]; then ownsrc="$src/$name.$ext"; break; fi
done
if [ -n "$ownsrc" ]; then
o="$out/$name.o"
_compile "$ownsrc" "$o" "$out/$name.d"
[ "$need" = "yes" ] && relink="yes"
own_objs="$o"
opic="$out/$name.pic.o"
_compile "$ownsrc" "$opic" "$out/$name.pic.d" "$picflag"
[ "$need" = "yes" ] && relink_pic="yes"
own_pic_objs="$opic"
fi
fi
# same "empty own_objs is fine as long as the shared pool has
# something" fallback bin= already has (see its own comment above)
# -- a libs= name with nothing of its own draws from the same
# $src/*.c shared pool bin= does, PIC variant included.
if [ -z "$shared_objs$own_objs" ]; then
_sc_die "no source found for library '$name' -- looked for $src/$name/, $src/$name.{c,s,S}, and anything shared directly under $src."
fi
# ${name}_version=/${name}_desc=, an indirect (eval'd) lookup for
# the exact same reason _sc_osvar's own comment gives: $name is only
# known at runtime, so the variable to read ("${name}_version", not
# a literal name) can't be spelled out in advance. both default to
# something usable even if sc.conf never set them at all -- mk.conf
# always defines both (empty string if unset in sc.conf), so this
# ":-" fallback is really "empty vs. actually-set", not "unset vs.
# set" (a distinction with no observable difference here).
ver="$(eval "printf '%s' \"\${${name}_version:-0.0.0}\"")"
vmajor="${ver%%.*}"
soname="lib$name.so.$vmajor"
sofull="lib$name.so.$ver"
if [ "$relink" = "yes" ] || [ ! -f "$out/lib$name.a" ]; then
echo "ar: $out/lib$name.a"
rm -f "$out/lib$name.a"
_sc_run $ar rcs "$out/lib$name.a" $shared_objs $own_objs
fi
# soname versioning: $soname (the "-Wl,-soname" baked into the .so
# itself, and what anything linking "-l$name" resolves to at
# runtime) carries only the MAJOR version, so a minor/patch bump
# ships as a drop-in runtime replacement -- $sofull (the real
# filename) carries the complete version, and $soname/lib$name.so
# are just symlinks onto it, same convention every real shared
# library on this system already follows.
if [ "$relink_pic" = "yes" ] || [ ! -f "$out/$sofull" ]; then
echo "ld: $out/$sofull"
_sc_run $cc $ccflgs -shared -Wl,-soname,"$soname" -o "$out/$sofull" $shared_pic_objs $own_pic_objs $ldflgs
ln -sf "$sofull" "$out/$soname"
ln -sf "$soname" "$out/lib$name.so"
fi
# .pc: $prefix is baked in as-is (see sc.sh's own PREFIX=/"${prefix:=
# ...}" comment) -- whatever the CALLER of sc.sh set it to (a port's
# own install: phase, typically) is where this library will actually
# live by the time anything reads this file, so that's what belongs
# here, not sc's own build-time $out. regenerated whenever either
# object set actually rebuilt, same as the .a/.so themselves, so a
# stale prefix from a previous configure never lingers silently.
pcfile="$out/lib$name.pc"
desc="$(eval "printf '%s' \"\${${name}_desc:-$name library}\"")"
if [ "$relink" = "yes" ] || [ "$relink_pic" = "yes" ] || [ ! -f "$pcfile" ]; then
echo "pc: $pcfile"
cat > "$pcfile" <<PCEOF
prefix=$prefix
libdir=\${prefix}/lib
includedir=\${prefix}/include
Name: $name
Description: $desc
Version: $ver
Libs: -L\${libdir} -l$name
Cflags: -I\${includedir}
PCEOF
fi
done
}
_sc_capture build.log "$out" _b_main
BSH
cat > mk.new/c.sh <<'CSH'
. ./mk.conf
set -e
_c_main() {
# only ever removes $out -- config.h/mk.conf/makefile are configure-
# time output, re-run sc.sh to regenerate or remove those instead.
echo "rm: $out"
_sc_run rm -rf "$out"
}
_sc_capture clean.log "$out" _c_main
CSH
cat > mk.new/dh.sh <<'DHSH'
. ./mk.conf
set -e
_dh_main() {
# renders $docsdir/*.btft (recursively, any subfolder included) into
# $docsdir/html/, mirroring the relative path each source file has
# under $docsdir -- run from the repo root, same as every other
# mk/*.sh (via "mk . dh" if the mk tool is installed, or plain
# "sh mk/dh.sh"/"make dh" otherwise).
#
# resolved through PATH, same as mk.conf's cc="${CC:-cc}" -- never a
# hardcoded install location, so this works wherever btf2html
# actually is (a different PREFIX, a user-local install, anywhere
# on $PATH). never the plain generic name "btf" -- that monolithic
# tool doesn't exist anymore, and even if some other "btf" happened
# to be on $PATH, it wouldn't take the same flags.
btf2html="${BTF2HTML:-btf2html}"
[ -f "$docsdir/style.btfs" ] || _sc_die "$docsdir/style.btfs missing."
command -v "$btf2html" >/dev/null 2>&1 || _sc_die "$btf2html not found on \$PATH."
echo "rm: $docsdir/html"
_sc_run rm -rf "$docsdir/html"
mkdir -p "$docsdir/html"
# -o takes an outdir relative to the CURRENT directory, not to the
# dir being rendered -- "$docsdir/html", not the tool's own "html"
# default, or this lands one level up (repo root) instead of inside
# $docsdir/.
echo "btf2html: $docsdir -> $docsdir/html"
_sc_run "$btf2html" -f -o "$docsdir/html" "$docsdir"
}
_sc_capture docshtml.log "$docsdir/html" _dh_main
DHSH
cat > mk.new/dm.sh <<'DMSH'
. ./mk.conf
set -e
_dm_main() {
# renders $docsdir/*.btft into $docsdir/man/ as troff man pages --
# no style.btfs involved, a man page has no concept of a stylesheet.
btf2man="${BTF2MAN:-btf2man}"
command -v "$btf2man" >/dev/null 2>&1 || _sc_die "$btf2man not found on \$PATH."
echo "rm: $docsdir/man"
_sc_run rm -rf "$docsdir/man"
mkdir -p "$docsdir/man"
echo "btf2man: $docsdir -> $docsdir/man"
_sc_run "$btf2man" -o "$docsdir/man" "$docsdir"
}
_sc_capture docsman.log "$docsdir/man" _dm_main
DMSH
# "d" (docs, both forms at once) is deliberately just this -- two plain
# sequential calls into dm.sh/dh.sh, not a real make prerequisite graph
# (see this file's own note further down on why mk/*.sh, not make's
# dependency resolution, is the one real interface): each already
# fully manages its own log/manifest independently, so "d" needs
# neither of its own.
cat > mk.new/d.sh <<'DSH'
set -e
sh mk/dm.sh
sh mk/dh.sh
DSH
cat > mk.new/dc.sh <<'DCSH'
. ./mk.conf
set -e
_dc_main() {
echo "rm: $docsdir/html $docsdir/man"
_sc_run rm -rf "$docsdir/html" "$docsdir/man"
}
_sc_capture docsclean.log "$docsdir" _dc_main
DCSH
chmod +x mk.new/*.sh
cat > mk.conf.new <<EOF
bin="$bin"
libs="$libs"
src="$src"
out="$out"
docsdir="$docsdir"
logdir="$logdir"
prefix="$prefix"
cc="\${CC:-$cc}"
ar="\${AR:-$ar}"
ccflgs="$cfg_ccflags"
ldflgs="$cfg_ldflags"
# hardcoded, not probed: -fPIC is universal across every real ELF
# target this tool (and everything it builds) has ever run on -- see
# oss= in sc.conf.example -- so a whole hard_cflags="-fPIC"-style probe
# round-trip through the compiler would only ever confirm what's
# already known. only ever used for libs='s own -fPIC object variant
# (see mk/b.sh's own _compile calls); a bin=-only project never
# references this at all.
picflag="-fPIC"
EOF
# ${name}_version=/${name}_desc=, one pair of lines per libs= name,
# appended (not part of the fixed heredoc above, since $libs's own
# names -- and therefore how many of these lines exist at all -- are
# only known at this point, not fixed in advance). always written, even
# when sc.conf never set either for a given name, so mk/b.sh's own
# "${name}_version:-0.0.0" fallback is choosing between "set" and
# "deliberately empty", never hitting a genuinely undefined variable
# (which "set -e"-style strict shells, and sc's own eval-based indirect
# lookup, both handle far less gracefully than an empty string).
for _l in $libs; do
_lver="$(eval "printf '%s' \"\${${_l}_version:-}\"")"
_ldesc="$(eval "printf '%s' \"\${${_l}_desc:-}\"")"
printf '%s_version="%s"\n' "$_l" "$_lver" >> mk.conf.new
printf '%s_desc="%s"\n' "$_l" "$_ldesc" >> mk.conf.new
done
cat >> mk.conf.new <<EOF
# the default state _sc_capture leaves every mk/*.sh target running in:
# fd 2 pointed straight at the log file (never through the terminal-
# visible pipe) with "set -x" on, so every single shell command the
# script runs from here on -- every "[ -e ... ]", every "basename",
# every variable assignment -- traces into the log, fully expanded,
# with zero terminal noise. the ONLY reason this is safe (and doesn't
# also hide a real compiler error the way redirecting fd 2 for the
# whole run would) is that _sc_run below temporarily undoes exactly
# this, just long enough to run one real external command, for exactly
# the commands that can produce output someone actually needs to see.
_sc_die() {
# for the script's OWN deliberate "err: ..." messages -- these must
# reach the terminal unconditionally, the same as _sc_run's wrapped
# commands, and for the identical reason (fd 2 defaults to log-only).
exec 2>&1
echo "err: \$*" >&2
exit 1
}
# runs one real external command (a compiler, btf, rm, ...) with fd 2
# pointed back at the terminal (exec 2>&1: fd 2 -> wherever fd 1 already
# goes, the pipe to tee) and tracing paused, so its own stdout/stderr --
# a real compile error very much included -- shows up live exactly as
# it always did, with no "+ ..." trace line of its own leaking out
# alongside it. the exact command line still lands in the log either
# way, via the explicit "+ ..." append below (BEFORE the toggle, so it's
# always recorded even if the command itself never touches stderr) --
# tracing resumes, and fd 2 goes back to log-only, immediately after.
_sc_run() {
printf '+ %s\n' "\$*" >> "\$logfile"
# "set +x" first, "exec 2>&1" second: reversed, "set +x" would trace
# ITSELF (a command is traced before it runs, and disabling tracing
# only takes effect once it actually runs) to fd 2 -- which by then
# would already be pointed at the terminal by "exec 2>&1", leaking a
# stray "+ set +x" line out. this order keeps that trace (if any) on
# the log, where fd 2 still points at the moment it fires.
set +x
exec 2>&1
"\$@"
rc=\$?
exec 2>>"\$logfile"
set -x
return "\$rc"
}
# every mk/*.sh target runs its own body through this: \$1 is the log
# file's name under \$logdir, \$2 a directory to report added/removed
# files under (empty string to skip that section entirely -- "mk . c"'s
# ONLY job already IS deleting \$out, so its own removed-files list is
# exactly its point), the rest is a function (defined earlier in that
# same script) to run. sets the global \$logfile before calling it, so
# _sc_run/_sc_die (and, for _b_main, its one-time compiler-info dump)
# know where to append, then hands it "set -x" and fd 2 already pointed
# at the log (see _sc_run's own comment for why that's safe).
#
# the terminal sees exactly what it always did: the target's own
# "cc: ...", "ld: ...", "err: ..." lines (fd 1, untouched, piped to tee
# below) plus whatever a real command _sc_run ran chose to print. run as
# a plain top-level statement (never inside if/&&/||/while, which would
# silence "set -e" for everything inside "\$@"), so a real failure still
# aborts the calling script exactly as before this existed; the real
# exit status survives the "| tee" below via \$rcfile, since a
# pipeline's own \$? would otherwise be tee's, not the function's.
_sc_capture() {
name="\$1"; mandir="\$2"; shift 2
mkdir -p "\$logdir"
logfile="\$logdir/\$name"
: > "\$logfile"
if [ -n "\$mandir" ]; then
if [ -d "\$mandir" ]; then find "\$mandir" -type f 2>/dev/null | sort > "\$logdir/.manifest-before"
else : > "\$logdir/.manifest-before"
fi
fi
rcfile="\$logdir/.rc.\$\$"
( ( set -e; exec 2>>"\$logfile"; set -x; "\$@" ); echo "\$?" > "\$rcfile" ) | tee -a "\$logfile"
rc="\$(cat "\$rcfile" 2>/dev/null)"; rc="\${rc:-1}"
rm -f "\$rcfile"
if [ -n "\$mandir" ]; then
if [ -d "\$mandir" ]; then find "\$mandir" -type f 2>/dev/null | sort > "\$logdir/.manifest-after"
else : > "\$logdir/.manifest-after"
fi
added="\$(comm -13 "\$logdir/.manifest-before" "\$logdir/.manifest-after")"
removed="\$(comm -23 "\$logdir/.manifest-before" "\$logdir/.manifest-after")"
# awk, not "wc -l": some wc implementations right-pad a bare count
# with leading whitespace for column alignment (harmless in a
# table of several files, but stray -- and inconsistent between
# a single- and double-digit count, so it LOOKS random -- spliced
# straight into a one-line sentence like this one).
nadd=0; [ -n "\$added" ] && nadd="\$(printf '%s\\n' "\$added" | awk 'END{print NR}')"
nrem=0; [ -n "\$removed" ] && nrem="\$(printf '%s\\n' "\$removed" | awk 'END{print NR}')"
{
[ -n "\$added" ] && printf '%s\\n' "\$added" | sed 's/^/+ /'
[ -n "\$removed" ] && printf '%s\\n' "\$removed" | sed 's/^/- /'
} >> "\$logfile"
if [ "\$nadd" -gt 0 ] || [ "\$nrem" -gt 0 ]; then
echo "ok: \$nadd added, \$nrem removed under \$mandir." | tee -a "\$logfile"
fi
rm -f "\$logdir/.manifest-before" "\$logdir/.manifest-after"
fi
return "\$rc"
}
EOF
# every target below is exactly "sh mk/<name>.sh", nothing more -- no
# prerequisites, no $(OBJS), no per-object rules, no -include *.d. mk/
# is the real interface; this file exists purely so "make b" works for
# people/tools without the mk wrapper installed. "make b", "mk . b" and
# "sh mk/b.sh" run the exact same script and MUST stay equivalent --
# never add a real Make dependency graph here, that logic belongs in
# mk/b.sh itself (it already does its own incremental rebuild via .d
# files). lowercase "makefile" (POSIX make's first search name, ahead
# of "Makefile") and strictly POSIX make syntax only -- no GNU-isms
# (.PHONY, $(wildcard ...), ifeq, %-patterns, include) and no BSD-isms
# (.if, .include, ${VAR:M...}). every recipe below must keep working
# under "pdpmake --posix", not just GNU make.
{
echo "b:"
echo " sh mk/b.sh"
echo
echo "c:"
echo " sh mk/c.sh"
echo
echo "dm:"
echo " sh mk/dm.sh"
echo
echo "dh:"
echo " sh mk/dh.sh"
echo
echo "d:"
echo " sh mk/d.sh"
echo
echo "dc:"
echo " sh mk/dc.sh"
for t in $custom_targets; do
echo
echo "$t:"
echo " sh mk/$t.sh"
done
echo
echo "default: b"
echo "build: b"
echo "clean: c"
echo "docsman: dm"
echo "docshtml: dh"
echo "docs: d"
echo "docsclean: dc"
} > makefile.new
mv -f config.h.new config.h
mv -f mk.conf.new mk.conf
mv -f makefile.new makefile
rm -rf mk
mv mk.new mk
echo "sc: configured. run 'make' (or 'mk . b'/'sh mk/b.sh') to build."