Created
November 24, 2017 05:18
-
-
Save technosaurus/de0311f5f1c5c3b43a4e84c394cfee1e to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/sh | |
addline() | |
{ | |
NEXT="$(printf "%s% $((50-${#LASTNAME}))d% 10d %10d" "$LASTNAME" "$OLD" "$NEW" "$DELTA")" | |
[ -z "$STUFF" ] && | |
STUFF="$NEXT" || | |
STUFF="$(printf "%s\n%s" "$STUFF" "$NEXT")" | |
} | |
bloatcheck() | |
{ | |
if [ $# -ne 2 ] | |
then | |
echo "usage: bloatcheck old new" | |
exit 1 | |
fi | |
DIFF1=`mktemp base.XXXXXXX` | |
DIFF2=`mktemp bloat.XXXXXXX` | |
trap "rm $DIFF1 $DIFF2" EXIT | |
nm --size-sort "$1" | sort -k3,3 > $DIFF1 | |
nm --size-sort "$2" | sort -k3,3 > $DIFF2 | |
diff -U 0 $DIFF1 $DIFF2 | tail -n +3 | sed -n 's/^\([-+]\)/\1 /p' \ | |
| sort -k4,4 | do_bloatcheck | |
} | |
change_sh() | |
{ | |
# build each command as a standalone executable | |
NOBUILD=1 make_sh > /dev/null && | |
${HOSTCC:-cc} -I . scripts/install.c -o generated/instlist && | |
export PREFIX=${PREFIX:-change/} && | |
mkdir -p "$PREFIX" || exit 1 | |
# Build all the commands standalone except: | |
# sh - shell builtins like "cd" and "exit" need the multiplexer | |
# help - needs to know what other commands are enabled (use command --help) | |
for i in $(generated/instlist | egrep -vw "sh|help") | |
do | |
echo -n " $i" && | |
single_sh $i > /dev/null 2>$PREFIX/${i}.bad && | |
rm $PREFIX/${i}.bad || echo -n '*' | |
done | |
echo | |
} | |
configure(){ | |
# Toybox configuration file. | |
# This sets environment variables used by scripts/make.sh | |
# A synonym. | |
[ -z "$CROSS_COMPILE" ] && CROSS_COMPILE="$CROSS" | |
# CFLAGS and OPTIMIZE are different so you can add extra CFLAGS without | |
# disabling default optimizations | |
[ -z "$CFLAGS" ] && CFLAGS="-Wall -Wundef -Wno-char-subscripts -Werror=implicit-function-declaration" | |
# Required for our expected ABI. we're 8-bit clean thus "char" must be unsigned. | |
CFLAGS="$CFLAGS -funsigned-char" | |
[ -z "$OPTIMIZE" ] && OPTIMIZE="-Os -ffunction-sections -fdata-sections -fno-asynchronous-unwind-tables -fno-strict-aliasing" | |
# We accept LDFLAGS, but by default don't have anything in it | |
[ -z "$LDOPTIMIZE" ] && LDOPTIMIZE="-Wl,--gc-sections" | |
# The makefile provides defaults for these, so this only gets used if | |
# you call scripts/make.sh and friends directly. | |
[ -z "$CC" ] && CC=cc | |
# If HOSTCC needs CFLAGS or LDFLAGS, just add them to the variable | |
# ala HOSTCC="blah-cc --static" | |
[ -z "$HOSTCC" ] && HOSTCC=cc | |
} | |
do_bloatcheck() | |
{ | |
LASTNAME= | |
DELTA=0 | |
TOTAL=0 | |
OLD=0 | |
NEW=0 | |
STUFF= | |
printf "name% 46s% 10s% 11s\n" old new delta | |
echo "-----------------------------------------------------------------------" | |
while read a b c d | |
do | |
THISNAME=$(echo "$d" | sed 's/[.][0-9]*$//') | |
if [ "$LASTNAME" != "$THISNAME" ] | |
then | |
TOTAL=$(($TOTAL+$DELTA)) | |
[ $DELTA -ne 0 ] && addline | |
LASTNAME="$THISNAME" | |
DELTA=0 | |
OLD=0 | |
NEW=0 | |
fi | |
SIZE=$(printf "%d" "0x$b") | |
if [ "$a" == "-" ] | |
then | |
OLD=$(($OLD+$SIZE)) | |
SIZE=$((-1*$SIZE)) | |
else | |
NEW=$(($NEW+$SIZE)) | |
fi | |
DELTA=$(($DELTA+$SIZE)) | |
done | |
TOTAL=$(($TOTAL+$DELTA)) | |
[ $DELTA -ne 0 ] && addline | |
echo "$STUFF" | sort -k4,4nr | |
echo "-----------------------------------------------------------------------" | |
printf "% 71d total\n" "$TOTAL" | |
} | |
# Set up a chroot environment and run commands within it. | |
# Needed commands listed on command line | |
# Script fed to stdin. | |
dochroot() | |
{ | |
mkdir tmpdir4chroot | |
mount -t ramfs tmpdir4chroot tmpdir4chroot | |
mkdir -p tmpdir4chroot/{etc,sys,proc,tmp,dev} | |
cp -L testing.sh tmpdir4chroot | |
# Copy utilities from command line arguments | |
echo -n "Setup chroot" | |
mkchroot tmpdir4chroot $* | |
echo | |
mknod tmpdir4chroot/dev/tty c 5 0 | |
mknod tmpdir4chroot/dev/null c 1 3 | |
mknod tmpdir4chroot/dev/zero c 1 5 | |
# Copy script from stdin | |
cat > tmpdir4chroot/test.sh | |
chmod +x tmpdir4chroot/test.sh | |
chroot tmpdir4chroot /test.sh | |
umount -l tmpdir4chroot | |
rmdir tmpdir4chroot | |
} | |
do_loudly() | |
{ | |
[ ! -z "$V" ] && echo "$@" || echo -n "$DOTPROG" | |
"$@" | |
} | |
do_test() | |
{ | |
CMDNAME="${1##*/}" | |
CMDNAME="${CMDNAME%.test}" | |
if [ -z "$TEST_HOST" ] | |
then | |
[ -z "$2" ] && C="$(readlink -f ../$CMDNAME)" || C="$(which $CMDNAME)" | |
else | |
C="$CMDNAME" | |
fi | |
if [ ! -z "$C" ] | |
then | |
. "$1" | |
else | |
echo "$CMDNAME disabled" | |
fi | |
} | |
findglobals_sh() | |
{ | |
# Quick and dirty check to see if anybody's leaked global variables. | |
# We should have this, toy_list, toybuf, and toys. | |
nm toybox_unstripped | grep '[0-9A-Fa-f]* [BCDGRS]' | cut -d ' ' -f 3 | |
} | |
genbuildsh() | |
{ | |
# Write a canned build line for use on crippled build machines. | |
echo "#!/bin/sh" | |
echo | |
echo "BUILD='$BUILD'" | |
echo | |
echo "FILES='$LIBFILES $TOYFILES'" | |
echo | |
echo "LINK='$LINK'" | |
echo | |
echo | |
echo '$BUILD $FILES $LINK' | |
} | |
genconfig() | |
{ | |
# Reverse sort puts posix first, examples last. | |
for j in $(ls toys/*/README | sort -s -r) | |
do | |
DIR="$(dirname "$j")" | |
[ $(ls "$DIR" | wc -l) -lt 2 ] && continue | |
echo "menu \"$(head -n 1 $j)\"" | |
echo | |
# extract config stanzas from each source file, in alphabetical order | |
for i in $(ls -1 $DIR/*.c) | |
do | |
# Grab the config block for Config.in | |
echo "# $i" | |
sed -n '/^\*\//q;/^config [A-Z]/,$p' $i || return 1 | |
echo | |
done | |
echo endmenu | |
done | |
} | |
genconfig_sh() | |
{ | |
# This has to be a separate function from make_sh so it can be called | |
# before menuconfig. (It's called again from make_sh just to be sure.) | |
mkdir -p generated | |
. configure | |
probeconfig > generated/Config.probed || rm generated/Config.probed | |
genconfig > generated/Config.in || rm generated/Config.in | |
WORKING= | |
PENDING= | |
toys toys/*/*.c | ( | |
while IFS=":" read FILE NAME | |
do | |
[ "$NAME" == help ] && continue | |
[ "$NAME" == install ] && continue | |
echo -e "$NAME: $FILE *.[ch] lib/*.[ch]\n\tscripts/toybox.sh single_sh $NAME\n" | |
echo -e "test_$NAME:\n\tscripts/toybox.sh test_sh $NAME\n" | |
[ "${FILE/pending//}" != "$FILE" ] && | |
PENDING="$PENDING $NAME" || | |
WORKING="$WORKING $NAME" | |
done && | |
echo -e "clean::\n\trm -f $WORKING $PENDING" && | |
echo -e "list:\n\t@echo $(echo $WORKING | tr ' ' '\n' | sort | xargs)" && | |
echo -e "list_pending:\n\t@echo $(echo $PENDING | tr ' ' '\n' | sort | xargs)" && | |
echo -e ".PHONY: $WORKING $PENDING" | sed 's/ \([^ ]\)/ test_\1/g' | |
) > .singlemake | |
} | |
# Extract global structure definitions and flag definitions from toys/*/*.c | |
getglobals() | |
{ | |
for i in toys/*/*.c | |
do | |
NAME="$(echo $i | $SED 's@.*/\(.*\)\.c@\1@')" | |
DATA="$($SED -n -e '/^GLOBALS(/,/^)/b got;b;:got' \ | |
-e 's/^GLOBALS(/struct '"$NAME"'_data {/' \ | |
-e 's/^)/};/' -e 'p' $i)" | |
[ ! -z "$DATA" ] && echo -e "// $i\n\n$DATA\n" | |
done | |
} | |
install_sh() | |
{ | |
# Grab default values for $CFLAGS and such. | |
. ./configure | |
[ -z "$PREFIX" ] && PREFIX="/usr/toybox" | |
# Parse command line arguments. | |
LONG_PATH="" | |
while [ ! -z "$1" ] | |
do | |
# Create symlinks instead of hardlinks? | |
[ "$1" == "--symlink" ] && LINK_TYPE="-s" | |
# Uninstall? | |
[ "$1" == "--uninstall" ] && UNINSTALL=Uninstall | |
# Delete destination command if it exists? | |
[ "$1" == "--force" ] && DO_FORCE="-f" | |
# Use {,usr}/{bin,sbin} paths instead of all files in one directory? | |
[ "$1" == "--long" ] && LONG_PATH="bin/" | |
# Symlink host toolchain binaries to destination to create cross compile $PATH | |
[ "$1" == "--airlock" ] && AIRLOCK=1 | |
shift | |
done | |
echo "Compile instlist..." | |
NOBUILD=1 make_sh | |
$DEBUG $HOSTCC -I . scripts/install.c -o generated/instlist || exit 1 | |
COMMANDS="$(generated/instlist $LONG_PATH)" | |
echo "${UNINSTALL:-Install} commands..." | |
# Copy toybox itself | |
if [ -z "$UNINSTALL" ] | |
then | |
mkdir -p "${PREFIX}/${LONG_PATH}" && | |
rm -f "${PREFIX}/${LONG_PATH}/toybox" && | |
cp toybox ${PREFIX}/${LONG_PATH} || exit 1 | |
else | |
rm -f "${PREFIX}/${LONG_PATH}/toybox" 2>/dev/null | |
fi | |
cd "$PREFIX" || exit 1 | |
# Make links to toybox | |
EXIT=0 | |
for i in $COMMANDS | |
do | |
# Figure out target of link | |
if [ -z "$LONG_PATH" ] | |
then | |
DOTPATH="" | |
else | |
# Create subdirectory for command to go in (if necessary) | |
DOTPATH="$(dirname "$i")"/ | |
if [ -z "$UNINSTALL" ] | |
then | |
mkdir -p "$DOTPATH" || exit 1 | |
fi | |
if [ -z "$LINK_TYPE" ] | |
then | |
DOTPATH="bin/" | |
else | |
if [ "$DOTPATH" != "$LONG_PATH" ] | |
then | |
# For symlinks we need ../../bin style relative paths | |
DOTPATH="$(echo $DOTPATH | sed -e 's@[^/]*/@../@g')"$LONG_PATH | |
else | |
DOTPATH="" | |
fi | |
fi | |
fi | |
# Create link | |
if [ -z "$UNINSTALL" ] | |
then | |
ln $DO_FORCE $LINK_TYPE ${DOTPATH}toybox $i || EXIT=1 | |
else | |
rm -f $i || EXIT=1 | |
fi | |
done | |
[ -z "$AIRLOCK" ] && exit 0 | |
# --airlock creates a single directory you can point the $PATH to for cross | |
# compiling, which contains just toybox and symlinks to toolchain binaries. | |
# This not only means you're building with a known set of tools (insulated from | |
# variations in the host distro), but that everything else is NOT in your PATH | |
# and thus various configure stages won't find things on thie host that won't | |
# be there on the target (such as the distcc build noticing the host has | |
# python and deciding to #include Python.h). | |
# The following are commands toybox should provide, but doesn't yet. | |
# For now symlink the host version. This list must go away by 1.0. | |
PENDING="bunzip2 bzcat dd diff expr ftpd ftpget ftpput gunzip less ping route tar test tr vi wget zcat awk bzip2 fdisk gzip sh sha512sum unxz xzcat bc" | |
# "gcc" should go away for llvm, but some things still hardwire it | |
TOOLCHAIN="ar as nm cc make ld gcc objdump" | |
if [ ! -z "$AIRLOCK" ] | |
then | |
# Tools needed to build packages | |
for i in $TOOLCHAIN $PENDING $HOST_EXTRA | |
do | |
if [ ! -f "$i" ] | |
then | |
# Loop through each instance, populating fallback directories (used by | |
# things like distcc, which require multiple instances of the same binary | |
# in a known order in the $PATH). | |
X=0 | |
FALLBACK="$PREFIX" | |
which -a "$i" | while read j | |
do | |
if [ ! -e "$FALLBACK/$i" ] | |
then | |
mkdir -p "$FALLBACK" && | |
ln -sf "$j" "$FALLBACK/$i" || exit 1 | |
fi | |
X=$[$X+1] | |
FALLBACK="$PREFIX/fallback-$X" | |
done | |
if [ ! -f "$PREFIX/$i" ] | |
then | |
echo "Toolchain component missing: $i" >&2 | |
[ -z "$PEDANTIC" ] || EXIT=1 | |
fi | |
fi | |
done | |
fi | |
exit $EXIT | |
} | |
# Is anything under directory $2 newer than file $1 | |
isnewer() | |
{ | |
CHECK="$1" | |
shift | |
[ ! -z "$(find "$@" -newer "$CHECK" 2>/dev/null || echo yes)" ] | |
} | |
# Process config.h and newtoys.h to generate FLAG_x macros. Note we must | |
# always #define the relevant macro, even when it's disabled, because we | |
# allow multiple NEWTOY() in the same C file. (When disabled the FLAG is 0, | |
# so flags&0 becomes a constant 0 allowing dead code elimination.) | |
make_flagsh() | |
{ | |
# Parse files through C preprocessor twice, once to get flags for current | |
# .config and once to get flags for allyesconfig | |
for I in A B | |
do | |
( | |
# define macros and select header files with option string data | |
echo "#define NEWTOY(aa,bb,cc) aa $I bb" | |
echo '#define OLDTOY(...)' | |
if [ "$I" == A ] | |
then | |
cat generated/config.h | |
else | |
$SED '/USE_.*([^)]*)$/s/$/ __VA_ARGS__/' generated/config.h | |
fi | |
echo '#include "lib/toyflags.h"' | |
cat generated/newtoys.h | |
# Run result through preprocessor, glue together " " gaps leftover from USE | |
# macros, delete comment lines, print any line with a quoted optstring, | |
# turn any non-quoted opstring (NULL or 0) into " " (because fscanf can't | |
# handle "" with nothing in it, and mkflags uses that). | |
) | ${CROSS_COMPILE}${CC} -E - | \ | |
$SED -n -e 's/" *"//g;/^#/d;t clear;:clear;s/"/"/p;t;s/\( [AB] \).*/\1 " "/p' | |
# Sort resulting line pairs and glue them together into triplets of | |
# command "flags" "allflags" | |
# to feed into mkflags C program that outputs actual flag macros | |
# If no pair (because command's disabled in config), use " " for flags | |
# so allflags can define the appropriate zero macros. | |
done | sort -s | $SED -n -e 's/ A / /;t pair;h;s/\([^ ]*\).*/\1 " "/;x' \ | |
-e 'b single;:pair;h;n;:single;s/[^ ]* B //;H;g;s/\n/ /;p' | \ | |
tee generated/flags.raw | generated/mkflags > generated/flags.h || exit 1 | |
} | |
make_sh() | |
{ | |
# Grab default values for $CFLAGS and such. | |
export LANG=c | |
export LC_ALL=C | |
set -o pipefail | |
. ./configure | |
[ -z "$KCONFIG_CONFIG" ] && KCONFIG_CONFIG=.config | |
[ -z "$OUTNAME" ] && OUTNAME=toybox | |
UNSTRIPPED="generated/unstripped/$(basename "$OUTNAME")" | |
# Since each cc invocation is short, launch half again as many processes | |
# as we have processors so they don't exit faster than we can start them. | |
[ -z "$CPUS" ] && CPUS=$(($(nproc)+1)) | |
if [ -z "$SED" ] | |
then | |
[ ! -z "$(which gsed 2>/dev/null)" ] && SED=gsed || SED=sed | |
fi | |
# Respond to V= by echoing command lines as well as running them | |
DOTPROG= | |
echo "Generate headers from toys/*/*.c..." | |
mkdir -p generated/unstripped | |
if isnewer generated/Config.in toys | |
then | |
echo "Extract configuration information from toys/*.c files..." | |
genconfig_sh | |
fi | |
# Create a list of all the commands toybox can provide. Note that the first | |
# entry is out of order on purpose (the toybox multiplexer command must be the | |
# first element of the array). The rest must be sorted in alphabetical order | |
# for fast binary search. | |
if isnewer generated/newtoys.h toys | |
then | |
echo -n "generated/newtoys.h " | |
echo "USE_TOYBOX(NEWTOY(toybox, NULL, TOYFLAG_STAYROOT))" > generated/newtoys.h | |
$SED -n -e 's/^USE_[A-Z0-9_]*(/&/p' toys/*/*.c \ | |
| $SED 's/\(.*TOY(\)\([^,]*\),\(.*\)/\2 \1\2,\3/' | sort -s -k 1,1 \ | |
| $SED 's/[^ ]* //' >> generated/newtoys.h | |
[ $? -ne 0 ] && exit 1 | |
fi | |
[ ! -z "$V" ] && echo "Which C files to build..." | |
# Extract a list of toys/*/*.c files to compile from the data in $KCONFIG_CONFIG | |
# (First command names, then filenames with relevant {NEW,OLD}TOY() macro.) | |
[ -d ".git" ] && GITHASH="$(git describe --tags --abbrev=12 2>/dev/null)" | |
[ ! -z "$GITHASH" ] && GITHASH="-DTOYBOX_VERSION=\"$GITHASH\"" | |
TOYFILES="$($SED -n 's/^CONFIG_\([^=]*\)=.*/\1/p' "$KCONFIG_CONFIG" | xargs | tr ' [A-Z]' '|[a-z]')" | |
TOYFILES="$(egrep -l "TOY[(]($TOYFILES)[ ,]" toys/*/*.c)" | |
CFLAGS="$CFLAGS $(cat generated/cflags)" | |
BUILD="$(echo ${CROSS_COMPILE}${CC} $CFLAGS -I . $OPTIMIZE $GITHASH)" | |
LIBFILES="$(ls lib/*.c | grep -v lib/help.c)" | |
TOYFILES="lib/help.c main.c $TOYFILES" | |
if [ "${TOYFILES/pending//}" != "$TOYFILES" ] | |
then | |
echo -e "\n\033[1;31mwarning: using unfinished code from toys/pending\033[0m" | |
fi | |
if ! cmp -s <(genbuildsh | head -n 3) \ | |
<(head -n 3 generated/build.sh 2>/dev/null) | |
then | |
echo -n "Library probe" | |
# We trust --as-needed to remove each library if we don't use any symbols | |
# out of it, this loop is because the compiler has no way to ignore a library | |
# that doesn't exist, so we have to detect and skip nonexistent libraries | |
# for it. | |
> generated/optlibs.dat | |
for i in util crypt m resolv selinux smack attr rt crypto z log | |
do | |
echo "int main(int argc, char *argv[]) {return 0;}" | \ | |
${CROSS_COMPILE}${CC} $CFLAGS -xc - -o generated/libprobe -Wl,--as-needed -l$i > /dev/null 2>/dev/null && | |
echo -l$i >> generated/optlibs.dat | |
echo -n . | |
done | |
rm -f generated/libprobe | |
echo | |
fi | |
# LINK needs optlibs.dat, above | |
LINK="$(echo $LDOPTIMIZE $LDFLAGS -o "$UNSTRIPPED" -Wl,--as-needed $(cat generated/optlibs.dat))" | |
genbuildsh > generated/build.sh && chmod +x generated/build.sh || exit 1 | |
#TODO: "make $SED && make" doesn't regenerate config.h because diff .config | |
if true #isnewer generated/config.h "$KCONFIG_CONFIG" | |
then | |
echo "Make generated/config.h from $KCONFIG_CONFIG." | |
# This long and roundabout sed invocation is to make old versions of sed | |
# happy. New ones have '\n' so can replace one line with two without all | |
# the branches and tedious mucking about with hold space. | |
$SED -n \ | |
-e 's/^# CONFIG_\(.*\) is not set.*/\1/' \ | |
-e 't notset' \ | |
-e 's/^CONFIG_\(.*\)=y.*/\1/' \ | |
-e 't isset' \ | |
-e 's/^CONFIG_\([^=]*\)=\(.*\)/#define CFG_\1 \2/p' \ | |
-e 'd' \ | |
-e ':notset' \ | |
-e 'h' \ | |
-e 's/.*/#define CFG_& 0/p' \ | |
-e 'g' \ | |
-e 's/.*/#define USE_&(...)/p' \ | |
-e 'd' \ | |
-e ':isset' \ | |
-e 'h' \ | |
-e 's/.*/#define CFG_& 1/p' \ | |
-e 'g' \ | |
-e 's/.*/#define USE_&(...) __VA_ARGS__/p' \ | |
$KCONFIG_CONFIG > generated/config.h || exit 1 | |
fi | |
if [ generated/mkflags -ot scripts/mkflags.c ] | |
then | |
do_loudly $HOSTCC scripts/mkflags.c -o generated/mkflags || exit 1 | |
fi | |
if isnewer generated/flags.h toys "$KCONFIG_CONFIG" | |
then | |
echo -n "generated/flags.h " | |
make_flagsh | |
fi | |
if isnewer generated/globals.h toys | |
then | |
echo -n "generated/globals.h " | |
GLOBSTRUCT="$(getglobals)" | |
( | |
echo "$GLOBSTRUCT" | |
echo | |
echo "extern union global_union {" | |
echo "$GLOBSTRUCT" | \ | |
$SED -n 's/struct \(.*\)_data {/ struct \1_data \1;/p' | |
echo "} this;" | |
) > generated/globals.h | |
fi | |
if [ generated/mktags -ot scripts/mktags.c ] | |
then | |
do_loudly $HOSTCC scripts/mktags.c -o generated/mktags || exit 1 | |
fi | |
if isnewer generated/tags.h toys | |
then | |
echo -n "generated/tags.h " | |
$SED -n '/TAGGED_ARRAY(/,/^)/{s/.*TAGGED_ARRAY[(]\([^,]*\),/\1/;p}' \ | |
toys/*/*.c lib/*.c | generated/mktags > generated/tags.h | |
fi | |
if [ generated/config2help -ot scripts/config2help.c ] | |
then | |
do_loudly $HOSTCC scripts/config2help.c -I . lib/xwrap.c lib/llist.c \ | |
lib/lib.c lib/portability.c -o generated/config2help || exit 1 | |
fi | |
if isnewer generated/help.h generated/Config.in | |
then | |
echo "generated/help.h" | |
generated/config2help Config.in $KCONFIG_CONFIG > generated/help.h || exit 1 | |
fi | |
[ ! -z "$NOBUILD" ] && exit 0 | |
echo -n "Compile toybox" | |
[ ! -z "$V" ] && echo | |
DOTPROG=. | |
# This is a parallel version of: do_loudly $BUILD $FILES $LINK || exit 1 | |
# Any headers newer than the oldest generated/obj file? | |
X="$(ls -1t generated/obj/* 2>/dev/null | tail -n 1)" | |
# TODO: redo this | |
if [ ! -e "$X" ] || [ ! -z "$(find toys -name "*.h" -newer "$X")" ] | |
then | |
rm -rf generated/obj && mkdir -p generated/obj || exit 1 | |
else | |
rm -f generated/obj/{main,lib_help}.o || exit 1 | |
fi | |
# build each generated/obj/*.o file in parallel | |
PENDING= | |
LNKFILES= | |
DONE=0 | |
COUNT=0 | |
CLICK= | |
for i in $LIBFILES click $TOYFILES | |
do | |
[ "$i" == click ] && CLICK=1 && continue | |
X=${i/lib\//lib_} | |
X=${X##*/} | |
OUT="generated/obj/${X%%.c}.o" | |
LNKFILES="$LNKFILES $OUT" | |
# $LIBFILES doesn't need to be rebuilt if newer than .config, $TOYFILES does | |
[ "$OUT" -nt "$i" ] && [ -z "$CLICK" -o "$OUT" -nt "$KCONFIG_CONFIG" ] && | |
continue | |
do_loudly $BUILD -c $i -o $OUT & | |
PENDING="$PENDING $!" | |
COUNT=$(($COUNT+1)) | |
# ratelimit to $CPUS many parallel jobs, detecting errors | |
for j in $PENDING | |
do | |
[ "$COUNT" -lt "$CPUS" ] && break; | |
wait $j | |
DONE=$(($DONE+$?)) | |
COUNT=$(($COUNT-1)) | |
PENDING="${PENDING## $j}" | |
done | |
[ $DONE -ne 0 ] && break | |
done | |
# wait for all background jobs, detecting errors | |
for i in $PENDING | |
do | |
wait $i | |
DONE=$(($DONE+$?)) | |
done | |
[ $DONE -ne 0 ] && exit 1 | |
do_loudly $BUILD $LNKFILES $LINK || exit 1 | |
if [ ! -z "$NOSTRIP" ] || | |
! do_loudly ${CROSS_COMPILE}strip "$UNSTRIPPED" -o "$OUTNAME" | |
then | |
echo "strip failed, using unstripped" && cp "$UNSTRIPPED" "$OUTNAME" || | |
exit 1 | |
fi | |
# gcc 4.4's strip command is buggy, and doesn't set the executable bit on | |
# its output the way SUSv4 suggests it do so. While we're at it, make sure | |
# we don't have the "w" bit set so things like bzip2's "cp -f" install don't | |
# overwrite our binary through the symlink. | |
do_loudly chmod 555 "$OUTNAME" || exit 1 | |
echo | |
} | |
minicom_sh() | |
{ | |
# If you want to use toybox netcat to talk to a serial port, use this. | |
if [ ! -c "$1" ] | |
then | |
echo "Usage: minicom_sh /dev/ttyS0" | |
exit 1 | |
fi | |
SPEED="$2" | |
[ -z "$SPEED" ] && SPEED=115200 | |
stty $SPEED -F "$1" | |
stty raw -echo -ctlecho -F "$1" | |
stty raw -echo # Need to do it on stdin, too. | |
./toybox netcat -f "$1" | |
stty cooked echo # Put stdin back. | |
} | |
# Recursively grab an executable and all the libraries needed to run it. | |
# Source paths beginning with / will be copied into destpath, otherwise | |
# the file is assumed to already be there and only its library dependencies | |
# are copied. | |
mkchroot() | |
{ | |
[ $# -lt 2 ] && return | |
echo -n . | |
dest=$1 | |
shift | |
for i in "$@" | |
do | |
case "$i" in | |
/*);; | |
*)i=`which $i`;; | |
esac | |
[ -f "$dest/$i" ] && continue | |
if [ -e "$i" ] | |
then | |
d=`echo "$i" | grep -o '.*/'` && | |
mkdir -p "$dest/$d" && | |
cat "$i" > "$dest/$i" && | |
chmod +x "$dest/$i" | |
else | |
echo "Not found: $i" | |
fi | |
mkchroot "$dest" $(ldd "$i" | egrep -o '/.* ') | |
done | |
} | |
optional() | |
{ | |
option=`echo "$OPTIONFLAGS" | egrep "(^|:)$1(:|\$)"` | |
# Not set? | |
if [ -z "$1" ] || [ -z "$OPTIONFLAGS" ] || [ ${#option} -ne 0 ] | |
then | |
SKIP="" | |
return | |
fi | |
SKIP=1 | |
} | |
probecc() | |
{ | |
${CROSS_COMPILE}${CC} $CFLAGS -xc -o /dev/null $1 - | |
} | |
probeconfig() | |
{ | |
> generated/cflags | |
# llvm produces its own really stupid warnings about things that aren't wrong, | |
# and although you can turn the warning off, gcc reacts badly to command line | |
# arguments it doesn't understand. So probe. | |
[ -z "$(probecc -Wno-string-plus-int <<< \#warn warn 2>&1 | grep string-plus-int)" ] && | |
echo -Wno-string-plus-int >> generated/cflags | |
# Probe for container support on target | |
probesymbol TOYBOX_CONTAINER << EOF | |
#include <linux/sched.h> | |
int x=CLONE_NEWNS|CLONE_NEWUTS|CLONE_NEWIPC|CLONE_NEWNET; | |
int main(int argc, char *argv[]) { setns(0,0); return unshare(x); } | |
EOF | |
probesymbol TOYBOX_FIFREEZE -c << EOF | |
#include <linux/fs.h> | |
#ifndef FIFREEZE | |
#error nope | |
#endif | |
EOF | |
# Work around some uClibc limitations | |
probesymbol TOYBOX_ICONV -c << EOF | |
#include "iconv.h" | |
EOF | |
probesymbol TOYBOX_FALLOCATE << EOF | |
#include <fcntl.h> | |
int main(int argc, char *argv[]) { return posix_fallocate(0,0,0); } | |
EOF | |
# Android and some other platforms miss utmpx | |
probesymbol TOYBOX_UTMPX -c << EOF | |
#include <utmpx.h> | |
#ifndef BOOT_TIME | |
#error nope | |
#endif | |
int main(int argc, char *argv[]) { | |
struct utmpx *a; | |
if (0 != (a = getutxent())) return 0; | |
return 1; | |
} | |
EOF | |
# Android is missing shadow.h | |
probesymbol TOYBOX_SHADOW -c << EOF | |
#include <shadow.h> | |
int main(int argc, char *argv[]) { | |
struct spwd *a = getspnam("root"); return 0; | |
} | |
EOF | |
# Some commands are android-specific | |
probesymbol TOYBOX_ON_ANDROID -c << EOF | |
#ifndef __ANDROID__ | |
#error nope | |
#endif | |
EOF | |
probesymbol TOYBOX_ANDROID_SCHEDPOLICY << EOF | |
#include <cutils/sched_policy.h> | |
int main(int argc,char *argv[]) { get_sched_policy_name(0); } | |
EOF | |
# nommu support | |
probesymbol TOYBOX_FORK << EOF | |
#include <unistd.h> | |
int main(int argc, char *argv[]) { return fork(); } | |
EOF | |
echo -e '\tdepends on !TOYBOX_MUSL_NOMMU_IS_BROKEN' | |
probesymbol TOYBOX_PRLIMIT << EOF | |
#include <sys/time.h> | |
#include <sys/resource.h> | |
int main(int argc, char *argv[]) { prlimit(0, 0, 0, 0); } | |
EOF | |
} | |
# Probe for a single config symbol with a "compiles or not" test. | |
# Symbol name is first argument, flags second, feed C file to stdin | |
probesymbol() | |
{ | |
probecc $2 2>/dev/null && DEFAULT=y || DEFAULT=n | |
rm a.out 2>/dev/null | |
echo -e "config $1\n\tbool" || exit 1 | |
echo -e "\tdefault $DEFAULT\n" || exit 1 | |
} | |
runtest_sh() | |
{ | |
# Simple test harness infrastructure | |
# | |
# Copyright 2005 by Rob Landley | |
# This file defines two main functions, "testcmd" and "optional". The | |
# first performs a test, the second enables/disables tests based on | |
# configuration options. | |
# The following environment variables enable optional behavior in "testing": | |
# DEBUG - Show every command run by test script. | |
# VERBOSE - Print the diff -u of each failed test case. | |
# If equal to "fail", stop after first failed test. | |
# | |
# The "testcmd" function takes five arguments: | |
# $1) Description to display when running command | |
# $2) Command line arguments to command | |
# $3) Expected result (on stdout) | |
# $4) Data written to file "input" | |
# $5) Data written to stdin | |
# | |
# The "testing" function is like testcmd but takes a complete command line | |
# (I.E. you have to include the command name.) The variable $C is an absolute | |
# path to the command being tested, which can bypass shell builtins. | |
# | |
# The exit value of testcmd is the exit value of the command it ran. | |
# | |
# The environment variable "FAILCOUNT" contains a cumulative total of the | |
# number of failed tests. | |
# | |
# The "optional" function is used to skip certain tests (by setting the | |
# environment variable SKIP), ala: | |
# optional CFG_THINGY | |
# | |
# The "optional" function checks the environment variable "OPTIONFLAGS", | |
# which is either empty (in which case it always clears SKIP) or | |
# else contains a colon-separated list of features (in which case the function | |
# clears SKIP if the flag was found, or sets it to 1 if the flag was not found). | |
export FAILCOUNT=0 | |
export SKIP= | |
# Helper functions | |
# Check config to see if option is enabled, set SKIP if not. | |
SHOWPASS=PASS | |
SHOWFAIL=FAIL | |
SHOWSKIP=SKIP | |
if tty -s <&1 | |
then | |
SHOWPASS="$(echo -e "\033[1;32m${SHOWPASS}\033[0m")" | |
SHOWFAIL="$(echo -e "\033[1;31m${SHOWFAIL}\033[0m")" | |
SHOWSKIP="$(echo -e "\033[1;33m${SHOWSKIP}\033[0m")" | |
fi | |
} | |
showasm() | |
{ | |
# Copyright 2006 Rob Landley <[email protected]> | |
# Dumb little utility function to print out the assembly dump of a single | |
# function, or list the functions so dumpable in an executable. You'd think | |
# there would be a way to get objdump to do this, but I can't find it. | |
[ $# -lt 1 ] || [ $# -gt 2 ] && { echo "usage: showasm file function"; exit 1; } | |
[ ! -f $1 ] && { echo "File $1 not found"; exit 1; } | |
if [ $# -eq 1 ] | |
then | |
objdump -d $1 | sed -n -e 's/^[0-9a-fA-F]* <\(.*\)>:$/\1/p' | |
exit 0 | |
fi | |
objdump -d $1 | sed -n -e '/./{H;$!d}' -e "x;/^.[0-9a-fA-F]* <$2>:/p" | |
} | |
# Build a standalone toybox command | |
single_sh(){ | |
if [ -z "$1" ] | |
then | |
echo "usage: single_sh command..." >&2 | |
exit 1 | |
fi | |
# Harvest TOYBOX_* symbols from .config | |
if [ ! -e .config ] | |
then | |
echo "Need .config for toybox global settings. Run defconfig/menuconfig." >&2 | |
exit 1 | |
fi | |
# Force dependencies to rebuild headers if we build multiplexer after this. | |
touch -c .config | |
export KCONFIG_CONFIG=.singleconfig | |
for i in "$@" | |
do | |
echo -n "$i:" | |
TOYFILE="$(egrep -l "TOY[(]($i)[ ,]" toys/*/*.c)" | |
if [ -z "$TOYFILE" ] | |
then | |
echo "Unknown command '$i'" >&2 | |
exit 1 | |
fi | |
# Enable stuff this command depends on | |
DEPENDS="$(sed -n "/^config *$i"'$/,/^$/{s/^[ \t]*depends on //;T;s/[!][A-Z0-9_]*//g;s/ *&& */|/g;p}' $TOYFILE | xargs | tr ' ' '|')" | |
NAME=$(echo $i | tr a-z- A-Z_) | |
make allnoconfig > /dev/null && | |
sed -ri -e '/CONFIG_TOYBOX/d' \ | |
-e "s/# (CONFIG_($NAME|${NAME}_.*${DEPENDS:+|$DEPENDS})) is not set/\1=y/" \ | |
"$KCONFIG_CONFIG" && | |
echo "# CONFIG_TOYBOX is not set" >> "$KCONFIG_CONFIG" && | |
grep "CONFIG_TOYBOX_" .config >> "$KCONFIG_CONFIG" && | |
rm -f "$PREFIX$i" && | |
OUTNAME="$PREFIX$i" make_sh || exit 1 | |
done | |
} | |
test_sh(){ | |
TOPDIR="$PWD" | |
FILES="$PWD"/tests/files | |
trap 'kill $(jobs -p) 2>/dev/null; exit 1' INT | |
rm -rf generated/testdir | |
mkdir -p generated/testdir/testdir | |
if [ -z "$TEST_HOST" ] | |
then | |
if [ $# -ne 0 ] | |
then | |
PREFIX=generated/testdir/ single_sh "$@" || exit 1 | |
else | |
make install_flat PREFIX=generated/testdir || exit 1 | |
fi | |
fi | |
cd generated/testdir | |
PATH="$PWD:$PATH" | |
cd testdir | |
export LC_COLLATE=C | |
runtest_sh | |
[ -f "$TOPDIR/generated/config.h" ] && export OPTIONFLAGS=:$(echo $(sed -nr 's/^#define CFG_(.*) 1/\1/p' "$TOPDIR/generated/config.h") | sed 's/ /:/g') | |
if [ $# -ne 0 ] | |
then | |
for i in "$@" | |
do | |
do_test "$TOPDIR"/tests/$i.test | |
done | |
else | |
for i in "$TOPDIR"/tests/*.test | |
do | |
if [ -z "$TEST_HOST" ] | |
then | |
do_test "$i" 1 | |
else | |
rm -rf testdir && mkdir testdir && cd testdir || exit 1 | |
do_test "$i" | |
cd .. | |
fi | |
done | |
fi | |
} | |
testcmd() | |
{ | |
wrong_args "$@" | |
testing "$1" "$C $2" "$3" "$4" "$5" | |
} | |
# The testing function | |
testing() | |
{ | |
wrong_args "$@" | |
NAME="$CMDNAME $1" | |
[ -z "$1" ] && NAME=$2 | |
[ -n "$DEBUG" ] && set -x | |
if [ -n "$SKIP" ] || ( [ -n "$SKIP_HOST" ] && [ -n "$TEST_HOST" ]) | |
then | |
[ ! -z "$VERBOSE" ] && echo "$SHOWSKIP: $NAME" | |
return 0 | |
fi | |
echo -ne "$3" > expected | |
echo -ne "$4" > input | |
echo -ne "$5" | ${EVAL:-eval} "$2" > actual | |
RETVAL=$? | |
# Catch segfaults | |
[ $RETVAL -gt 128 ] && [ $RETVAL -lt 255 ] && | |
echo "exited with signal (or returned $RETVAL)" >> actual | |
DIFF="$(diff -au${NOSPACE:+b} expected actual)" | |
if [ ! -z "$DIFF" ] | |
then | |
FAILCOUNT=$[$FAILCOUNT+1] | |
echo "$SHOWFAIL: $NAME" | |
if [ -n "$VERBOSE" ] | |
then | |
[ ! -z "$4" ] && echo "echo -ne \"$4\" > input" | |
echo "echo -ne '$5' |$EVAL $2" | |
echo "$DIFF" | |
[ "$VERBOSE" == fail ] && exit 1 | |
fi | |
else | |
echo "$SHOWPASS: $NAME" | |
fi | |
rm -f input expected actual | |
[ -n "$DEBUG" ] && set +x | |
return 0 | |
} | |
# Find names of commands that can be built standalone in these C files | |
toys() | |
{ | |
grep 'TOY(.*)' "$@" | grep -v TOYFLAG_NOFORK | grep -v "0))" | \ | |
sed -rn 's/([^:]*):.*(OLD|NEW)TOY\( *([a-zA-Z][^,]*) *,.*/\1:\3/p' | |
} | |
wrong_args() | |
{ | |
if [ $# -ne 5 ] | |
then | |
echo "Test $NAME has the wrong number of arguments ($# $*)" >&2 | |
exit | |
fi | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment