diff --git a/docs/cookbook/all.do b/docs/cookbook/all.do index 7d12d02..6bd494d 100644 --- a/docs/cookbook/all.do +++ b/docs/cookbook/all.do @@ -1,4 +1,4 @@ export NO_SLOW_TESTS=1 -for d in */all.do; do +for d in */all.do */test.do; do echo "${d%.do}" done | xargs redo-ifchange diff --git a/docs/cookbook/c/.gitignore b/docs/cookbook/c/.gitignore new file mode 100644 index 0000000..0703d8b --- /dev/null +++ b/docs/cookbook/c/.gitignore @@ -0,0 +1,6 @@ +hello +/when.c +/allconfig +/arches +/out +/out.* diff --git a/docs/cookbook/c/all.do b/docs/cookbook/c/all.do new file mode 100644 index 0000000..c359ac3 --- /dev/null +++ b/docs/cookbook/c/all.do @@ -0,0 +1 @@ +redo-ifchange all.each diff --git a/docs/cookbook/c/all.h b/docs/cookbook/c/all.h new file mode 100644 index 0000000..d5fdaa0 --- /dev/null +++ b/docs/cookbook/c/all.h @@ -0,0 +1,4 @@ +#include "main.h" +#include "libhello/hello.h" +#include "monotime.h" +#include diff --git a/docs/cookbook/c/all.hpp b/docs/cookbook/c/all.hpp new file mode 100644 index 0000000..8979800 --- /dev/null +++ b/docs/cookbook/c/all.hpp @@ -0,0 +1,4 @@ +#include +#include +#include +#include diff --git a/docs/cookbook/c/all.od b/docs/cookbook/c/all.od new file mode 100644 index 0000000..94f4a47 --- /dev/null +++ b/docs/cookbook/c/all.od @@ -0,0 +1 @@ +redo-ifchange "hello world" diff --git a/docs/cookbook/c/all.rc.od b/docs/cookbook/c/all.rc.od new file mode 100644 index 0000000..179fc92 --- /dev/null +++ b/docs/cookbook/c/all.rc.od @@ -0,0 +1,23 @@ +. ./redoconf.rc +rc_include \ + rc/CC.rc \ + rc/CXX.rc \ + rc/libqt4.rc \ + rc/libgtk2.rc \ + rc/Wextra.rc \ + rc/Wall.rc \ + rc/libm.rc \ + rc/rt.autolib.rc \ + rc/libpng.rc \ + rc/clock_gettime.func.rc \ + rc/mach_time.h.rc \ + rc/windows.h.rc \ + rc/posix.rc \ + rc/printf_lld.rc \ + rc/extra.rc \ + rc/all.h.precompiled.rc \ + rc/all.hpp.precompiled.rc + +rc_appendln LIBS "$LIBRT" +rc_appendln LIBS "$LIBM" +rc_save diff --git a/docs/cookbook/c/allconfig.do b/docs/cookbook/c/allconfig.do new file mode 100644 index 0000000..c659d30 --- /dev/null +++ b/docs/cookbook/c/allconfig.do @@ -0,0 +1,27 @@ +redo-ifchange arches configure redoconf/utils.sh + +config() { + local dir="$1" arch="$2" + shift + shift + [ -d "$dir" ] || mkdir "$dir" + ( + cd "$dir" && + ../configure --host="$arch" "$@" && + redo-ifchange rc/CC.rc && + echo "$dir" + ) || : +} + +for d in $(cat arches); do + if [ "$d" = "native" ]; then + arch="" + else + arch="$d" + fi + config "out.$d" "$arch" & + config "out.$d.static" "$arch" "--enable-static" & + config "out.$d.opt" "$arch" "--enable-optimization" & +done + +wait diff --git a/docs/cookbook/c/arches.do b/docs/cookbook/c/arches.do new file mode 100644 index 0000000..a779ebb --- /dev/null +++ b/docs/cookbook/c/arches.do @@ -0,0 +1,14 @@ +IFS=: +echo native >$3 +if [ -z "$NO_SLOW_TESTS" ]; then + for dir in $PATH; do + for d in "$dir"/*-cc "$dir"/*-gcc; do + base=${d##*/} + arch=${base%-*} + if [ -x "$d" ]; then echo "$arch"; fi + done + done >>$3 +fi + +redo-always +redo-stamp <$3 diff --git a/docs/cookbook/c/clean.od b/docs/cookbook/c/clean.od new file mode 100644 index 0000000..70a02f2 --- /dev/null +++ b/docs/cookbook/c/clean.od @@ -0,0 +1,6 @@ +# runs from the output directory +rm -f *~ .*~ *.rc *.log *.gch *.stamp \ + *.[oa] *.deps \ + *.so *.so.* *.ver \ + *.exe *.list \ + hello diff --git a/docs/cookbook/c/configure b/docs/cookbook/c/configure new file mode 100755 index 0000000..736916f --- /dev/null +++ b/docs/cookbook/c/configure @@ -0,0 +1,3 @@ +#!/bin/sh +S="$(dirname "$0")" +. "$S/redoconf/configure.sh" diff --git a/docs/cookbook/c/configure.help b/docs/cookbook/c/configure.help new file mode 100644 index 0000000..ca36761 --- /dev/null +++ b/docs/cookbook/c/configure.help @@ -0,0 +1,17 @@ +# Automatically generated by redoconf/_all.rc.od - do not edit +ARCH Architecture prefix for output (eg. i686-w64-mingw32-) +CC C compiler name (cc) +CPPFLAGS Extra C preprocessor flags (eg. -I... -D...) +CFLAGS Extra C compiler flags (eg. -O2 -g) +OPTFLAGS C/C++ compiler flag overrides (eg. -g0) +LINK Linker name (cc) +LDFLAGS Extra linker options (eg. -s -static) +LIBS Extra libraries to always link against (eg. -lsocket) +STATIC Link libraries and binaries statically +CXX C++ compiler name (c++) +CXXFLAGS Extra C++ compiler flags (eg. -O2 -g) +LIBQT4 Extra linker options for 'QtCore' +LIBGTK2 Extra linker options for 'gtk+-2.0 gio-2.0 gdk-2.0 gdk-pixbuf-2.0' +LIBM Extra linker options for 'libm' +LIBPNG Extra linker options for 'libpng' +PREFIX Change installation prefix (usually /usr/local) diff --git a/docs/cookbook/c/default.each.do b/docs/cookbook/c/default.each.do new file mode 100644 index 0000000..0738694 --- /dev/null +++ b/docs/cookbook/c/default.each.do @@ -0,0 +1,10 @@ +# redo $2 in each of the registered output dirs. +# This way you can run commands or depend on targets like: +# redo clean.each.do +# redo all.each.do +# etc. +redo-ifchange allconfig + +for dir in $(cat allconfig); do + echo "$dir/$2" +done | xargs redo-ifchange diff --git a/docs/cookbook/c/flagtest.c b/docs/cookbook/c/flagtest.c new file mode 100644 index 0000000..34f9453 --- /dev/null +++ b/docs/cookbook/c/flagtest.c @@ -0,0 +1,10 @@ +#include "main.h" +#include + +#ifdef EXTRA_RC_INCLUDED +#error "rc/extra.rc should not be included when compiling flagtest.c" +#endif + +void flag_test(void) { + printf("flagtest included\n"); +} diff --git a/docs/cookbook/c/flagtest.o.od b/docs/cookbook/c/flagtest.o.od new file mode 100644 index 0000000..67b5c88 --- /dev/null +++ b/docs/cookbook/c/flagtest.o.od @@ -0,0 +1,9 @@ +# Demonstrate how to compile .o files using nonstandard +# compiler flags. You could also do this for a whole +# directory using default.o.od. +. ./redoconf.rc +rc_include all.rc + +src="$S/${1%.o}.c" +redo-ifchange "_compile" "$src" +CC="$CC" CPPFLAGS="-DFLAGTEST_SET=42" ./_compile "$3" "$1.deps" "$src" diff --git a/docs/cookbook/c/hello world.list.od b/docs/cookbook/c/hello world.list.od new file mode 100644 index 0000000..d9f5200 --- /dev/null +++ b/docs/cookbook/c/hello world.list.od @@ -0,0 +1,30 @@ +# This script is run from the output dir, +# which contains a src/ symlink to the source dir. + +. ./redoconf.rc +rc_include all.rc + +( + cd "$S" + echo "main.c" + echo "monotime.c" + echo "when.c" # auto-generated source + echo "flagtest.c" # source with different compiler flags + + if [ -n "$CXX" ]; then + echo "slow.cc" + fi + + # This is unnecessarily fancy. + # We're just using it as an example of + # how to dynamically generate a .list + # file. + for d in lib*/*.list lib*/*.list.od; do + [ -e "$d" ] && echo "${d%%.*}.so" + done | uniq + + printf '%s\n' "$LIBGTK2" "$LIBQT4" +) >$3 + +redo-always +redo-stamp <$3 diff --git a/docs/cookbook/c/libhello/hello.c b/docs/cookbook/c/libhello/hello.c new file mode 100644 index 0000000..5a88be9 --- /dev/null +++ b/docs/cookbook/c/libhello/hello.c @@ -0,0 +1,6 @@ +#include "hello.h" +#include + +void hello(void) { + printf("Hello, world!\n"); +} \ No newline at end of file diff --git a/docs/cookbook/c/libhello/hello.h b/docs/cookbook/c/libhello/hello.h new file mode 100644 index 0000000..94dbff4 --- /dev/null +++ b/docs/cookbook/c/libhello/hello.h @@ -0,0 +1,6 @@ +#ifndef __HELLO_H +#define __HELLO_H + +void hello(void); + +#endif /* __HELLO_H */ \ No newline at end of file diff --git a/docs/cookbook/c/libhello/lib hello.list b/docs/cookbook/c/libhello/lib hello.list new file mode 100644 index 0000000..9c36258 --- /dev/null +++ b/docs/cookbook/c/libhello/lib hello.list @@ -0,0 +1 @@ +hello.c diff --git a/docs/cookbook/c/libhello/lib hello.ver b/docs/cookbook/c/libhello/lib hello.ver new file mode 100644 index 0000000..c813fe1 --- /dev/null +++ b/docs/cookbook/c/libhello/lib hello.ver @@ -0,0 +1 @@ +1.2.5 diff --git a/docs/cookbook/c/main.c b/docs/cookbook/c/main.c new file mode 100644 index 0000000..6fc2d28 --- /dev/null +++ b/docs/cookbook/c/main.c @@ -0,0 +1,22 @@ +#include "main.h" +#include "libhello/hello.h" +#include "monotime.h" +#include "redoconf.h" +#include + +#if EXTRA_RC_INCLUDED != 1 +#error "EXTRA_RC was not included!" +#endif + +int main() { + hello(); + printf("Timestamp: %s\n", stamp_time()); + printf("Monotime: %lld\n", monotime()); +#ifdef CXX + printf("Length of 'hello world': %d\n", cpp_test()); +#else + printf("No C++ compiler found.\n"); +#endif + flag_test(); + return 0; +} diff --git a/docs/cookbook/c/main.h b/docs/cookbook/c/main.h new file mode 100644 index 0000000..d2d07e4 --- /dev/null +++ b/docs/cookbook/c/main.h @@ -0,0 +1,19 @@ +#ifndef __MAIN_H +#define __MAIN_H + +#ifdef __cplusplus +#define CDEF extern "C" +#else +#define CDEF +#endif + +/* when.c */ +CDEF const char *stamp_time(void); + +/* slow.cc */ +CDEF int cpp_test(void); + +/* flagtest.c */ +CDEF void flag_test(void); + +#endif /* __MAIN_H */ \ No newline at end of file diff --git a/docs/cookbook/c/monotime.c b/docs/cookbook/c/monotime.c new file mode 100644 index 0000000..784f577 --- /dev/null +++ b/docs/cookbook/c/monotime.c @@ -0,0 +1,58 @@ +#define __GNU_SOURCE +/* + * Returns the kernel monotonic timestamp in microseconds. + * This function never returns the value 0; it returns 1 instead, so that + * 0 can be used as a magic value. + */ +#include "monotime.h" +#include "redoconf.h" + +#if HAVE_CLOCK_GETTIME + +#include +#include +#include + +long long monotime(void) { + struct timespec ts; + if (clock_gettime(CLOCK_MONOTONIC, &ts) < 0) { + perror("clock_gettime"); + exit(98); /* really should never happen, so don't try to recover */ + } + long long result = ts.tv_sec * 1000000LL + ts.tv_nsec / 1000; + return !result ? 1 : result; +} + +#elif HAVE_MACH_TIME_H + +#include +#include + +long long monotime(void) { + static mach_timebase_info_data_t timebase; + if (!timebase.denom) mach_timebase_info(&timebase); + long long result = (mach_absolute_time() * timebase.numer / + timebase.denom / 1000); + return !result ? 1 : result; +} + +#elif HAVE_WINDOWS_H + +#include + +/* WARNING: Not carefully tested. It might wrap around unexpectedly. + * Based on suggestions from: + * https://stackoverflow.com/questions/211257/windows-monotonic-clock + */ +long long monotime(void) { + LARGE_INTEGER tps, t; + QueryPerformanceFrequency(&tps); + QueryPerformanceCounter(&t); + return t.QuadPart * 1000000LL / tps.QuadPart; +} + +#else + +#error "No monotonic time function is available" + +#endif diff --git a/docs/cookbook/c/monotime.h b/docs/cookbook/c/monotime.h new file mode 100644 index 0000000..fca38e7 --- /dev/null +++ b/docs/cookbook/c/monotime.h @@ -0,0 +1,6 @@ +#ifndef __MONOTIME_H +#define __MONOTIME_H + +long long monotime(void); + +#endif /* __MONOTIME_H */ diff --git a/docs/cookbook/c/rc/extra.rc.od b/docs/cookbook/c/rc/extra.rc.od new file mode 100644 index 0000000..1075a71 --- /dev/null +++ b/docs/cookbook/c/rc/extra.rc.od @@ -0,0 +1,5 @@ +. ./redoconf.rc +rc_include + +rc_appendln CPPFLAGS "-DEXTRA_RC_INCLUDED=1" +rc_save diff --git a/docs/cookbook/c/rc/posix.rc.od b/docs/cookbook/c/rc/posix.rc.od new file mode 100644 index 0000000..546fc8a --- /dev/null +++ b/docs/cookbook/c/rc/posix.rc.od @@ -0,0 +1,16 @@ +. ./redoconf.rc +rc_include rc/CC.rc + +prog=' +#include + +struct timespec x; +' + +x= +if ! rc_compile cc link "$prog"; then + x="-D_XOPEN_SOURCE=500" + rc_appendln CPPFLAGS "$x" + rc_compile cc link "$prog" +fi +rc_save diff --git a/docs/cookbook/c/rc/printf_lld.rc.od b/docs/cookbook/c/rc/printf_lld.rc.od new file mode 100644 index 0000000..dd1650e --- /dev/null +++ b/docs/cookbook/c/rc/printf_lld.rc.od @@ -0,0 +1,16 @@ +. ./redoconf.rc +rc_include rc/CC.rc rc/windows.h.rc rc/Wall.rc + +appendln CPPFLAGS "-Werror" # abort if any warnings +prog=' +#include +void test() { printf("%lld", (long long)1); } +' + +x= +if ! rc_compile cc link "$prog"; then + x="-D__USE_MINGW_ANSI_STDIO=1" + rc_appendln CPPFLAGS "$x" + rc_compile cc link "$prog" +fi +rc_save diff --git a/docs/cookbook/c/redoconf b/docs/cookbook/c/redoconf new file mode 120000 index 0000000..f0a8b6b --- /dev/null +++ b/docs/cookbook/c/redoconf @@ -0,0 +1 @@ +../../../redoconf \ No newline at end of file diff --git a/docs/cookbook/c/slow.cc b/docs/cookbook/c/slow.cc new file mode 100644 index 0000000..d854f9d --- /dev/null +++ b/docs/cookbook/c/slow.cc @@ -0,0 +1,10 @@ +#include +#include +#include +#include +#include "main.h" + +int cpp_test() { + std::string a = "hello ", b = "world"; + return a.length() + b.length(); +} diff --git a/docs/cookbook/c/test.do b/docs/cookbook/c/test.do new file mode 100644 index 0000000..54ef47b --- /dev/null +++ b/docs/cookbook/c/test.do @@ -0,0 +1 @@ +redo-ifchange test.each diff --git a/docs/cookbook/c/test.od b/docs/cookbook/c/test.od new file mode 100644 index 0000000..f128358 --- /dev/null +++ b/docs/cookbook/c/test.od @@ -0,0 +1,16 @@ +exec >&2 +redo-always +redo-ifchange all run +if [ -x ./run ]; then + echo "Running: ./run ./hello\\ world" + ./run './hello world' >"$1.out" 2>&1 + cat "$1.out" + if grep -F -q 'Hello, world!' "$1.out"; then + echo "-- Test successful." + else + echo "-- Test failed." + exit 1 + fi +else + echo "Non-native platform: test skipped." +fi diff --git a/docs/cookbook/c/when.c.do b/docs/cookbook/c/when.c.do new file mode 100644 index 0000000..dafe985 --- /dev/null +++ b/docs/cookbook/c/when.c.do @@ -0,0 +1,7 @@ +cat >$3 <<-EOF + const char *stamp_time(void) { + return "$(date +%Y-%m-%d)"; + } +EOF +redo-always +redo-stamp <$3 diff --git a/redoconf/_all.rc.od b/redoconf/_all.rc.od new file mode 100644 index 0000000..71a73f5 --- /dev/null +++ b/redoconf/_all.rc.od @@ -0,0 +1,51 @@ +. ./redoconf.rc + +# replace the placeholder for this function with +# one that will save help messages for later. +HELP="configure.help.new" +rm -f "$HELP" +echo '# Automatically generated by redoconf/_all.rc.od - do not edit' >"$HELP" +helpmsg() { + printf '%-11s %s\n' "$1" "$2" >>"$HELP" + rc_hook "$1" +} + +# Remember initial set of keys provided by ./configure +orig_keys="" +rc_hook() { + contains_line "$orig_keys" "$1" || orig_keys="$orig_keys$NL$1" +} + +# Include the rest of the necessary .rc files +keys="" +rc_hook() { + contains_line "$new_keys" "$1" || new_keys="$new_keys$NL$1" +} +allrc= +if [ -e "$S/all.rc.od" ]; then + allrc=all.rc +else + allrc= + redo-ifcreate "$S/all.rc.od" +fi +rc_include rc/_init.rc rc/CC.rc rc/zdefs.rc $allrc + +rc_helpmsg PREFIX "Change installation prefix (usually /usr/local)" + +IFS="$NL" +unused= +for d in $orig_keys; do + if ! contains_line "$new_keys" "$d"; then + unused=1 + xecho "Error: '$d' was given to ./configure but not used." >&2 + fi +done +[ -z "$unused" ] || exit 1 + +rc_save + +# Now that all the rc files have run, update $S/configure.help with the +# newly-generated help text, so it's available to new users. +# Even if multiple output dirs are building at once, this replaces the +# file atomically, so it should be safe. +mv "$HELP" "$S/configure.help" diff --git a/redoconf/_compile.od b/redoconf/_compile.od new file mode 100644 index 0000000..4a237b9 --- /dev/null +++ b/redoconf/_compile.od @@ -0,0 +1,14 @@ +# See compile.od for more explanation. +cat >$3 <<-EOF + #!/bin/sh -e + # Run the C/C++ compiler. + # Assumes config variables (CFLAGS, etc) are already set. + t="\$1" d="\$2" i="\$3" + IFS="$NL" + set -f + \$CC -o "\$t" -c "\$i" \\ + -MMD -MF "\$d" \\ + \$CPPFLAGS \$CFLAGS \$CXXFLAGS \$FLAGS_PCH \$xCFLAGS \$OPTFLAGS +EOF +chmod a+x "$3" +redo-stamp <$3 diff --git a/redoconf/compile.od b/redoconf/compile.od new file mode 100644 index 0000000..f379ad8 --- /dev/null +++ b/redoconf/compile.od @@ -0,0 +1,51 @@ +. ./redoconf.rc +rc_include _all.rc +redo-ifchange _compile + +# Subtle: +# - un-backslashed $ expansions ($foo, $(cmd)) are +# done *now*, while writing the script contents. +# - backslashed $ expansions (\$foo) are written +# verbatim into the script, to be interpreted at +# the time the script is run. +# +# We want to insert the variable contents into the +# script near the top, making sure they are not +# split or interpreted at that point (hence the +# $(shquote)). +# +# Further down, we want to disable wildcard expansion +# (set -f) and split on $NL (so we change $IFS), +# so we use backslash escapes but *not* quoting. + +cat >$3 <<-EOF + #!/bin/sh -e + # Run the C/++ compiler. + t="\$1" d="\$2" i="\$3" + CPPFLAGS=$(shquote "$CPPFLAGS") + OPTFLAGS=$(shquote "$OPTFLAGS") + case \$i in + *.c|*.h) + CC=$(shquote "$CC") + CFLAGS=$(shquote "$CFLAGS") + CXXFLAGS= + PCH1=$(shquote "$CFLAGS_PCH") + PCH2=$(shquote "$CFLAGS_PCH_FPIC") + ;; + *) + CC=$(shquote "$CXX") + [ -n "\$CC" ] || (echo "No C++ compiler available." >&2; exit 1) + CFLAGS= + CXXFLAGS=$(shquote "$CXXFLAGS") + PCH1=$(shquote "$CXXFLAGS_PCH") + PCH2=$(shquote "$CXXFLAGS_PCH_FPIC") + ;; + esac + case \$PCH in + 1) FLAGS_PCH=\$PCH1 ;; + 2) FLAGS_PCH=\$PCH2 ;; + esac + . ./_compile +EOF +chmod a+x "$3" +redo-stamp <$3 diff --git a/redoconf/configure.sh b/redoconf/configure.sh new file mode 100644 index 0000000..83d77a1 --- /dev/null +++ b/redoconf/configure.sh @@ -0,0 +1,200 @@ +#!/bin/sh -e +if [ -z "$S" ]; then + exec >&2 + echo "configure.sh: must include this from a 'configure' script" + exit 99 +fi +if [ -e "configure" ] || [ -e "rc.sh" ]; then + exec >&2 + echo "$0: run this script from an empty output directory." + echo " For example:" + echo " (mkdir out && cd out && ../configure && redo -j10)" + exit 99 +fi + +S="$(dirname "$0")" +rm -f src Makefile +echo "$S" >src + +# Don't regenerate these files unless they're missing. Otherwise redo +# will treat it as a changed dependency and rebuild a bunch of files +# unnecessarily. +[ -e redoconf.rc ] || cat >redoconf.rc <<-EOF + # Automatically generated by $0 + read -r S Makefile <<'EOF' +# A wrapper for people who like to type 'make' instead of 'redo' +all $(filter-out all,$(MAKECMDGOALS)): + +redo "$@" +.PHONY: $(MAKECMDGOALS) all +EOF + +# Don't include rc.sh here, because that might call redo-ifchange, +# and we're not ready for that yet. +. "$S/redoconf/utils.sh" + +usage() { + exec >&2 + printf 'Usage: %s %s' "$0" \ +'[options...] [KEY=value] [--with-key=value] + + --prefix= Change installation prefix (usually /usr/local) + --host= Architecture prefix for output (eg. i686-w64-mingw32-) + --enable-static Link libraries and binaries statically + --{dis,en}able-optimization Disable/enable optimization for C/C++ + --{dis,en}able-debug Disable/enable debugging flags for C/C++ + -h, --help This help message +' + if [ -e "$S/configure.help" ]; then + printf '\nProject-specific flags:\n' + sort "$S/configure.help" | + while read k msg; do + # skip blanks and comments + [ -n "${k%%\#*}" ] || continue + # skip options with syntax already explained above + [ "$k" != "ARCH" ] || continue + [ "$k" != "PREFIX" ] || continue + [ "$k" != "STATIC" ] || continue + printf ' %-15s %s\n' "$k=..." "$msg" + done + else + printf '\nNo extra help yet: configure.help is missing.\n' + fi + exit 1 +} + +upper() { + xecho "$1" | tr 'a-z' 'A-Z' | sed 's/[^A-Z0-9]/_/g' +} + +lower() { + xecho "$1" | tr 'A-Z' 'a-z' | sed 's/[^a-z0-9]/-/g' +} + +emit() { + xecho "replaceln" "$1" "$(shquote "$(rc_splitwords "$2")")" +} + +emit_append() { + xecho "appendln" "$1" "$(shquote "$(rc_splitwords "$2")")" +} + +for k in ARCH CC CXX CPPFLAGS CFLAGS CXXFLAGS \ + OPTFLAGS LINK LDFLAGS LIBS xCFLAGS; do + eval v=\$$k + if [ -n "$v" ]; then + echo "$0: Fatal: '$k' environment variable is set." >&2 + echo " This can cause inconsistent builds." >&2 + echo " Pass variables as arguments to $0 instead." >&2 + echo " Example: $0 $k=$(shquote "$v")" >&2 + exit 4 + fi +done + +rm -f _flags.tmp +echo "# Auto-generated by $0" >_flags.tmp +for d in "$@"; do + case $d in + --help|-h|-\?) + usage + ;; + --host=*) + v="${d#*=}" + emit "ARCH" "$v" + ;; + --enable-static) + emit "STATIC" "1" + ;; + --enable-optimization) + emit_append OPTFLAGS "-O2" + ;; + --disable-optimization) + emit_append OPTFLAGS "-O0" + ;; + --enable-debug) + emit_append OPTFLAGS "-g" + ;; + --disable-debug) + emit_append OPTFLAGS "-g0" + ;; + --prefix=*) + v="${d#*=}" + emit "PREFIX" "$v" + ;; + --with-[A-Za-z]*) + if [ "$d" = "${d%%=*}" ]; then + xecho "$0: in $(shquote "$d"):" \ + "must supply '--with-='" >&2 + exit 3 + fi + k="${d%%=*}" + k="${k#--with-}" + v="${d#*=}" + kl=$(lower "$k") + if [ "$k" != "$kl" ]; then + xecho "$0: in $(shquote "--with-$k=..."):" \ + "must be all lowercase and dashes" >&2 + exit 3 + fi + ku=$(upper "$k") + emit "$ku" "$v" + ;; + --without-[A-Za-z]*) + if [ "$d" != "${d%%=*}" ]; then + xecho "$0: in $(shquote "$d"):" \ + "do not supply '=...' with --without" >&2 + exit 3 + fi + k="${d#--without-}" + kl=$(lower "$k") + if [ "$k" != "$kl" ]; then + xecho "$0: in $(shquote "$d"):" \ + "must be all lowercase and dashes" >&2 + exit 3 + fi + ku=$(upper "$k") + # This causes tests to try to link using an extra + # option "NONE", which will fail, thus making the + # package appear missing. + emit "$ku" "NONE" + ;; + [A-Za-z_]*=*) + k="${d%%=*}" + v="${d#*=}" + ku=$(upper "$k") + if [ "$k" != "$ku" ]; then + xecho "$0: in $(shquote "$d"):" \ + "invalid KEY=value;" \ + "must be all caps and underscores" >&2 + exit 3 + fi + emit "$ku" "$v" + ;; + *) + xecho "$0: in $(shquote "$d"):" \ + "invalid option; use --help for help." >&2 + exit 2 + ;; + esac +done >>_flags.tmp + +# Avoid replacing the file if it's identical, so we don't trigger +# unnecessary rebuilds. redo-stamp can do this for a redo target, +# but _flags is being generated by this configure script, which is +# not a redo target. +if [ -e _flags ] && cmp _flags.tmp _flags >/dev/null; then + rm -f _flags.tmp +else + rm -f _flags + mv _flags.tmp _flags +fi diff --git a/redoconf/default.do.sh b/redoconf/default.do.sh new file mode 100644 index 0000000..6c4de48 --- /dev/null +++ b/redoconf/default.do.sh @@ -0,0 +1,181 @@ +# This script starts with $PWD=output dir, $S=input dir. +read -r S &2 + exit 99 +fi + +NL=" +" + +_mkdir_of() { + local dir="${1%/*}" + [ "$dir" = "$1" ] || + [ -z "$dir" ] || + [ -d "$dir" ] || + mkdir -p "$dir" +} + +# Delegate to .od files specifically for this target, if any. +_base1=${1##*/} +_dir1=${1%"$_base1"} +_missing="" +for d in "$S/$1.od" \ + "$S/${_dir1}default.${_base1#*.}.od" \ + "$REDOCONF/$1.od" \ + "$REDOCONF/${_dir1}default.${_base1#*.}.od"; do + if [ -e "$d" ]; then + redo-ifchange "$d" + _mkdir_of "$3" + ( PS4="$PS4[$d] "; . "$d" ) + exit + else + missing="$missing$NL$d" + fi +done + +_add_missing() { + [ -n "$missing" ] && (IFS="$NL"; set -f; redo-ifcreate $missing) + missing= +} + +_pick_src() { + # Look for the source file corresponding to a given .o file. + # If the source file is missing, we can also build it from + # eg. a .c.do script. + # + # Returns the matching source file in $src, the compiler + # mode in $lang, and appends any redo-ifcreate targets to + # $missing. + lang=cc + for src in "$1.c"; do + [ -e "$src" ] && return + [ -e "$src.do" ] && return + missing="$missing$NL$src" + done + lang=cxx + for src in "$1.cc" "$1.cpp" "$1.cxx" "$1.C" "$1.c++"; do + [ -e "$src" ] && return + [ -e "$src.do" ] && return + missing="$missing$NL$src" + done + echo "default.do.sh: _pick_src: no source file found for '$1.*'" >&2 + return 1 +} + +_objlist() { + local suffix="$1" list="$2" base="${2##*/}" + local dir="${2%"$base"}" + sed -Ee 's/\.(c|cc|cpp|cxx|C|c\+\+)$/'"$suffix/" <"$2" | + while read -r d; do + [ "$d" = "${d#-}" ] || continue + echo "$dir$d" + done +} + +_flaglist() { + while read -r d; do + [ "$d" != "${d#-}" ] || continue + echo "$d" + done <"$1" +} + +compile() { + redo-ifchange compile "$src" $dep + rm -f "$1.deps" + _mkdir_of "$3" + xCFLAGS="$xCFLAGS" PCH="$PCH" ./compile "$3" "$1.deps" "$src" + # TODO: make work with dependency filenames containing whitespace. + # gcc writes whitespace-containing filenames with spaces + # prefixed by backslash. read (without -r) will remove the + # backslashes but still use spaces for word splitting, so + # it loses the distinction. rc_splitwords() is the right + # function, but currently has a max word limit. + read deps <"$2.deps" + redo-ifchange ${deps#*:} +} + +case $1 in + *.fpic.o) + _pick_src "$S/${1%.fpic.o}" + _add_missing + xCFLAGS="-fPIC" PCH="2" dep="$lang-fpic.precompile" compile "$@" + exit # fast path: exit as early as possible + ;; + *.o) + _pick_src "$S/${1%.o}" + _add_missing + xCFLAGS="" PCH="1" dep="$lang.precompile" compile "$@" + exit # fast path: exit as early as possible + ;; + *.h.fpic.gch|*.hpp.fpic.gch) + src="$S/${1%.fpic.gch}" + xCFLAGS="-fPIC" PCH="" dep="" compile "$@" + # precompiled header is "unchanged" if its component + # headers are unchanged. + cat ${deps#*:} | tee $1.stamp | redo-stamp + ;; + *.h.gch|*.hpp.gch) + src="$S/${1%.gch}" + xCFLAGS="" PCH="" dep="" compile "$@" + # precompiled header is "unchanged" if its component + # headers are unchanged. + cat ${deps#*:} | tee $1.stamp | redo-stamp + ;; + *.a) + listf="${1%.a}.list" + redo-ifchange "$listf" + files=$(_objlist .o "$listf") + IFS="$NL" + redo-ifchange $files + ar q "$3" $files + ;; + *.so) + verf="${1%.so}.ver" + listf="${1%.so}.list" + redo-ifchange link-shlib "$verf" "$listf" + read ver <"$verf" + files=$(_objlist .fpic.o "$listf") + xLIBS=$(_flaglist "$listf") + IFS="$NL" + redo-ifchange $files + xLIBS="$xLIBS" ./link-shlib "$1.$ver" $files + ln -s "$(basename "$1.$ver")" "$3" + ln -sf "$1.$ver" . + ;; + *.list|*.ver) + if [ -e "$S/$1" ]; then + redo-ifchange "$S/$1" + _mkdir_of "$3" + cp "$S/$1" "$3" + else + echo "default.do.sh: no rule to build '$1'" >&2 + exit 99 + fi + ;; + *) + # In unix, binary names typically don't have any special filename + # pattern, so we have to handle them in a catch-all here. + # We detect that it's intended as a binary, and not just a typo, + # by the existence of a .list or .list.od file with the same name, + # defining the list of input files. + # + # Some people like to name their binaries *.exe, eg. on Windows, + # even though that doesn't matter anymore, even on Windows. + # So use the same rules for generating a .exe as a non-.exe. + bin="${1%.exe}" + if [ -e "$S/$bin.list" -o -e "$S/$bin.list.od" ]; then + # a final program binary + redo-ifchange link "$bin.list" + files=$(_objlist .o "$bin.list") + xLIBS=$(_flaglist "$bin.list") + IFS="$NL" + redo-ifchange $files + xLIBS="$xLIBS" ./link "$3" $files + else + echo "default.do.sh: no rule to build '$1' or '$1.list'" >&2 + exit 99 + fi + ;; +esac diff --git a/redoconf/default.precompile.od b/redoconf/default.precompile.od new file mode 100644 index 0000000..b52eb24 --- /dev/null +++ b/redoconf/default.precompile.od @@ -0,0 +1,31 @@ +# Run the extra steps necessary before compiling +# C/C++ programs of the specified type. +# +# Notably, we have to precompile any precompiled +# headers. We also generate redoconf.h in case +# programs want to include it. +. ./redoconf.rc +rc_include _all.rc + +case ${1%.precompile} in + cc) pch_files="$PRE_CC_TARGETS" ;; + cc-fpic) pch_files="$PRE_CC_TARGETS_FPIC" ;; + cxx) pch_files="$PRE_CXX_TARGETS" ;; + cxx-fpic) pch_files="$PRE_CXX_TARGETS_FPIC" ;; + *) exit 42 ;; +esac + +IFS="$NL" +set -f +redo-ifchange "redoconf.h" $pch_files + +# Subtle: +# Don't consider this target to have changed unless +# the precompiled header's stamp has changed. +# We generate redoconf.h, in case +# a C program wants to include it, but we +# don't care if it has changed, because the C program +# will have its own dependency on that file. +for d in $t; do + cat "$d.stamp" +done | redo-stamp diff --git a/redoconf/link-shlib.od b/redoconf/link-shlib.od new file mode 100644 index 0000000..e3cb976 --- /dev/null +++ b/redoconf/link-shlib.od @@ -0,0 +1,36 @@ +. ./redoconf.rc +rc_include _all.rc rc/shlib.rc + +# Tricky quoting: see _compile.od for details. +if [ "$HAVE_SHLIB" = 1 ]; then + cat >$3 <<-EOF + #!/bin/sh -e + LINK=$(shquote "$LINK") + LDFLAGS=$(shquote "$LDFLAGS") + OPTFLAGS=$(shquote "$OPTFLAGS") + LIBS=$(shquote "$LIBS") + o="\$1" + ob="\${o#*/}" + shift + IFS="$NL" + set -f + \$LINK -shared -o "\$o" \\ + -Wl,-soname,"\$ob" \\ + \$LDFLAGS \$OPTFLAGS \\ + "\$@" \\ + \$LIBS + EOF +else + # If no shared library support and we try to build one, + # compensate by building a static library instead in the + # same place. + cat >$3 <<-EOF + #!/bin/sh -e + o="\$1" + shift + rm -f "\$o" + ar q "\$o" "\$@" + EOF +fi +chmod a+x "$3" +redo-stamp <$3 diff --git a/redoconf/link.od b/redoconf/link.od new file mode 100644 index 0000000..e416539 --- /dev/null +++ b/redoconf/link.od @@ -0,0 +1,22 @@ +. ./redoconf.rc +rc_include _all.rc + +# Tricky quoting: see _compile.od for details. +cat >$3 <<-EOF + #!/bin/sh -e + LINK=$(shquote "$LINK") + LDFLAGS=$(shquote "$LDFLAGS") + OPTFLAGS=$(shquote "$OPTFLAGS") + LIBS=$(shquote "$LIBS") + o="\$1" + shift + IFS="$NL" + set -f + \$LINK -o "\$o" \\ + -Wl,-rpath,'\$ORIGIN' \\ + \$LDFLAGS \$OPTFLAGS \\ + "\$@" \\ + \$LIBS +EOF +chmod a+x "$3" +redo-stamp <$3 diff --git a/redoconf/rc.sh b/redoconf/rc.sh new file mode 100644 index 0000000..a5a3cd6 --- /dev/null +++ b/redoconf/rc.sh @@ -0,0 +1,240 @@ +# This script starts with $PWD=output dir, $S=input dir. +REDOCONF="$S/redoconf" +if [ ! -d "$S" ] || [ ! -f "$REDOCONF/default.do.sh" ]; then + echo "default.do.sh: \$S is not set correctly." >&2 + exit 99 +fi +. "$REDOCONF/utils.sh" +RC_TARGET="$1" +rm -f "$RC_TARGET.log" + +redo-ifchange "$REDOCONF/rc.sh" "$REDOCONF/utils.sh" + +_rc_exit_check() { + if [ -z "$RC_INCLUDE_RAN" ]; then + echo "Fatal: used redoconf/rc.sh but didn't call rc_include." >&2 + exit 99 + elif [ -n "$RC_QUEUE" ]; then + echo "Fatal: must call rc_save or rc_undo before ending." >&2 + exit 99 + fi +} +trap _rc_exit_check EXIT + +rc_hook() { + # nothing by default; can be overridden + : +} + +# Declare that a variable *named* $1 is used +# as input for the current script, and provide +# a help message in $2 for use with +# configure --help-flags. +helpmsg() { + # Nothing to do by default + rc_hook "$1" +} + +# Assign the string $2 to the global variable +# *named* by $1. +replaceln() { + rc_hook "$1" + eval $1=\$2 +} + +# If $2 is nonempty, append a newline and $2 to +# the global variable *named* by $1 +appendln() { + rc_hook "$1" + eval local tmp=\"\$$1\$NL\$2\" + eval $1='${tmp#$NL}' +} + +# Write a command line that calls "uses $1", +# including proper sh-escaping. +rc_helpmsg() { + local cmd="helpmsg" + cmd="helpmsg $1 $(shquote "$2")" + eval "$cmd" + appendln RC_HELP_QUEUE "$cmd" +} + +_rc_record() { + if ! contains_line "$RC_CHANGED" "$1"; then + local oldv= + eval oldv="\$$1" + eval "old$1=\$oldv" + RC_CHANGED="$RC_CHANGED$NL$1" + fi +} + +# Write a command line that calls "appendln $1 $2", +# including properly sh-escaping $2. +rc_appendln() { + RC_LOG="$RC_LOG $1 += $(shquote "$2")$NL" + _rc_record "$1" + local cmd="appendln $1 $(shquote "$2")" + eval "$cmd" + appendln RC_QUEUE "$cmd" +} + +# Write a command line that replaces $1 with $2, +# including properly sh-escaping $2. +# This is good for variables like CC and CXX, where +# appending isn't what we want. +rc_replaceln() { + RC_LOG="$RC_LOG $1 = $(shquote "$2")$NL" + _rc_record "$1" + local cmd="replaceln $1 $(shquote "$2")" + eval "$cmd" + appendln RC_QUEUE "$cmd" +} + +# Read compiler variables from _init.rc and the listed .rc files. +# Runs redo-ifchange to generate the .rc files as needed. +rc_include() { + local d="" want="" target="$RC_TARGET" ops4="$PS4" + RC_INCLUDE_RAN=1 + for d in rc/_init.rc "$@"; do + if [ "$d" = "${d%.rc}" ]; then + xecho "$0: rc_include: '$d' must end in .rc" >&2 + exit 99 + fi + if ! contains_line "$RC_INCLUDES" "$d"; then + want="$want$NL$d" + RC_INCLUDES="$RC_INCLUDES$NL$d" + fi + done + want="${want#$NL}" + if [ -n "$want" ]; then + xIFS="$IFS" + IFS="$NL" + set -f + redo-ifchange $want + for d in $want; do + IFS="$xIFS" + set +f + RC_TARGET="$d" + PS4="$PS4[$d] " + [ ! -e "$d" ] || { :; . "./$d"; } + PS4="$ops4" + done + IFS="$xIFS" + set +f + fi + unset RC_QUEUE + PS4="$ops4" + RC_TARGET="$target" +} + +# Undo the currently enqueued rc_appendln and rc_replaceln +# actions, restoring the affected variables back to their +# original values. +rc_undo() { + local xIFS="$IFS" v= + IFS="$NL" + for k in $RC_CHANGED; do + eval v="\$old$k" + eval "$k=\$v" + done + unset RC_CHANGED + unset RC_QUEUE + unset RC_LOG +} + +# Write the currently active rc_include, +# rc_replaceln, and rc_appendln commands to stdout, +# to produce the final .rc file. +rc_save() { + printf '%s' "${RC_LOG}" >&2 + if [ -n "$RC_INCLUDES" ]; then + ( + xIFS="$IFS" + IFS="$NL" + printf 'rc_include ' + for d in $RC_INCLUDES; do + printf '%s ' "$d" + done + printf '\n' + ) + fi + xecho "$RC_HELP_QUEUE" + xecho "$RC_QUEUE" + unset RC_QUEUE + unset RC_CHANGED +} + +rc_compile() { + redo-ifchange "$REDOCONF/trycompile" + ( . "$REDOCONF/trycompile" "$@" ) >>"$RC_TARGET.log" 2>&1 +} + +_pkg_config() { + ( + IFS="$NL" + set -f + $PKG_CONFIG $PKG_CONFIG_FLAGS "$@" + ) +} + +_rc_pkg_add() { + local var="$1" vvar= want= + shift + if [ -n "$HAVE_PKG_CONFIG" ]; then + for d in "$@"; do + if _pkg_config --exists "$d"; then + want="$want $d" + fi + done + if [ -n "$want" ]; then + rc_appendln CPPFLAGS \ + "$(rc_splitwords \ + "$(_pkg_config --cflags $want)")" + vvar="$(rc_splitwords \ + "$(_pkg_config --libs $want)")" + rc_appendln "$var" "$vvar" + fi + else + echo "(pkg-config is missing)" >&2 + fi +} + +# Determine whether packages listed in $2 all exists and are functional, +# by finding them using pkg-config and running the command in $3..$n. +# +# If the package works: +# HAVE_$1 is set to 1 +# CPPFLAGS and the variable named by $1 are updated with compiler and +# linker flags, respectively. +# else: +# HAVE_$1 is set to blank. +# +# Returns success in either case. Check detection results in the +# HAVE_$1 variable if needed. +rc_pkg_detect() { + if ! contains_line "$RC_INCLUDES" rc/pkg-config.rc; then + echo "Error: include pkg-config.rc before using rc_pkg_*" >&2 + return 1 + fi + if [ "$#" -lt 3 ]; then + echo "Error: rc_pkg_detect needs a command to test." >&2 + return 1 + fi + local var="$1" vvar="" pkgs="$2" + shift + shift + eval vvar="\$$var" + rc_helpmsg "$var" "Extra linker options for '$pkgs'" + if [ -z "$vvar" ]; then + _rc_pkg_add "$var" $pkgs + eval vvar="\$$var" + fi + appendln LIBS "$vvar" + if ("$@"); then + rc_replaceln "HAVE_$var" 1 + else + rc_undo + rc_replaceln "HAVE_$var" "" + rc_replaceln "$var" "" + fi +} diff --git a/redoconf/rc/CC.rc.od b/redoconf/rc/CC.rc.od new file mode 100644 index 0000000..505ee6e --- /dev/null +++ b/redoconf/rc/CC.rc.od @@ -0,0 +1,68 @@ +. ./redoconf.rc +rc_include + +rc_helpmsg ARCH "Architecture prefix for output (eg. i686-w64-mingw32-)" +rc_helpmsg CC "C compiler name (cc)" +rc_helpmsg CPPFLAGS "Extra C preprocessor flags (eg. -I... -D...)" +rc_helpmsg CFLAGS "Extra C compiler flags (eg. -O2 -g)" +rc_helpmsg OPTFLAGS "C/C++ compiler flag overrides (eg. -g0)" +rc_helpmsg LINK "Linker name (cc)" +rc_helpmsg LDFLAGS "Extra linker options (eg. -s -static)" +rc_helpmsg LIBS "Extra libraries to always link against (eg. -lsocket)" +rc_helpmsg STATIC "Link libraries and binaries statically" + +if [ -n "$CC" ]; then + set -- "$CC" +else + if [ -n "$ARCH" ] && [ "$ARCH" = "${ARCH%-}" ]; then + # Make sure arch name includes trailing dash if nonempty + ARCH="$ARCH-" + fi + set -- \ + "${ARCH}cc" "${ARCH}gcc" \ + "${ARCH}clang" "/usr/bin/${ARCH}clang"-[0-9]* \ + "${ARCH}c++" "${ARCH}g++" \ + "${ARCH}clang++" "/usr/bin/${ARCH}clang++"-[0-9]* +fi + +rc_appendln CPPFLAGS "-I." +[ -n "$STATIC" ] && rc_appendln LDFLAGS "-static" + +for d in "$@"; do + echo "Trying C compiler: '$d'" >&2 + if CC="$d" CXX="" LINK="$d" rc_compile cc link 'extern int i;'; then + rc_replaceln CC "$d" + rc_replaceln LINK "$d" + + # mingw32 (not mingw64) generates binaries that need + # a dynamic libgcc at runtime for certain operations + # (like 64-bit integer division), which is non-obvious. + # Include it statically if possible, but only on Windows, + # which we'll detect by whether windows.h exists. + # + # If adding the option fails, maybe it's some + # compiler other than gcc, so that's fine. + x="-static-libgcc$NL-static-libstdc++" + appendln LDFLAGS "$x" + prog='#include ' + if CC="$d" LINK="$d" rc_compile cc link "$prog"; then + rc_appendln LDFLAGS "$x" + fi + + # Set ARCH= to the right compiler prefix, either empty + # (use native compiler) or something like "i686-w64-mingw32-" + # (terminating dash), based on the compiler name. + x="-${CC##*/}" + x="${x%-*}" + x="${x#-}" + [ -n "$x" ] && x="$x-" + rc_replaceln ARCH "$x" + + rc_save + exit 0 + fi +done + +echo "Can't find a working C compiler." >&2 +rc_undo +exit 1 diff --git a/redoconf/rc/CXX.rc.od b/redoconf/rc/CXX.rc.od new file mode 100644 index 0000000..4859244 --- /dev/null +++ b/redoconf/rc/CXX.rc.od @@ -0,0 +1,35 @@ +. ./redoconf.rc +rc_include rc/CC.rc + +rc_helpmsg CXX "C++ compiler name (c++)" +rc_helpmsg CXXFLAGS "Extra C++ compiler flags (eg. -O2 -g)" + +if [ -n "$CXX" ]; then + set -- "$CXX" +else + # Note: $ARCH has already been set correctly by CC.rc + set -- \ + "${ARCH}c++" "${ARCH}g++" \ + "${ARCH}clang++" "/usr/bin/${ARCH}clang++"-[0-9]* +fi + +for d in "$@"; do + [ -n "$d" ] || continue + echo "Trying C++ compiler: '$d'" >&2 + if CC="" CXX="$d" LINK="$d" rc_compile cxx link 'class A {};'; then + rc_replaceln CXX "$d" + # If the project activates CXX.rc, then we + # replace the C linker with C++. This causes + # it to include -lstdc++, etc. + # A future .rc could override this again. + rc_replaceln LINK "$d" + rc_save + exit 0 + fi +done + +echo "Warning: Can't find a working C++ compiler." >&2 +rc_undo +rc_replaceln CXX "" +rc_save +exit 0 diff --git a/redoconf/rc/Wall.rc.od b/redoconf/rc/Wall.rc.od new file mode 100644 index 0000000..35d3b36 --- /dev/null +++ b/redoconf/rc/Wall.rc.od @@ -0,0 +1,9 @@ +. ./redoconf.rc +rc_include rc/CC.rc + +rc_appendln CPPFLAGS "-Wall" +if rc_compile cc nolink; then + rc_save +else + rc_undo +fi diff --git a/redoconf/rc/Wextra.rc.od b/redoconf/rc/Wextra.rc.od new file mode 100644 index 0000000..ec6cd3e --- /dev/null +++ b/redoconf/rc/Wextra.rc.od @@ -0,0 +1,9 @@ +. ./redoconf.rc +rc_include rc/CC.rc + +rc_appendln CPPFLAGS "-Wextra" +if rc_compile cc nolink; then + rc_save +else + rc_undo +fi diff --git a/redoconf/rc/_init.rc.od b/redoconf/rc/_init.rc.od new file mode 100644 index 0000000..f197b14 --- /dev/null +++ b/redoconf/rc/_init.rc.od @@ -0,0 +1,8 @@ +if [ -e _flags ]; then + redo-ifchange _flags + cat _flags >$3 + redo-stamp <_flags +else + redo-ifcreate _flags + redo-stamp &2 + rc_undo +fi diff --git a/redoconf/rc/default.h.rc.od b/redoconf/rc/default.h.rc.od new file mode 100644 index 0000000..0057d77 --- /dev/null +++ b/redoconf/rc/default.h.rc.od @@ -0,0 +1,13 @@ +. ./redoconf.rc +rc_include rc/CC.rc + +base="${1#*/}" +h="${base%.rc}" +H=$(echo "$h" | tr 'a-z.' 'A-Z_') + +if rc_compile cc nolink "#include <$h>"; then + rc_replaceln "HAVE_$H" 1 +else + rc_replaceln "HAVE_$H" "" +fi +rc_save diff --git a/redoconf/rc/default.hpp.precompiled.rc.od b/redoconf/rc/default.hpp.precompiled.rc.od new file mode 100644 index 0000000..41fc712 --- /dev/null +++ b/redoconf/rc/default.hpp.precompiled.rc.od @@ -0,0 +1,23 @@ +. ./redoconf.rc +rc_include rc/CXX.rc + +base="${1#rc/}" +src="${base%.hpp.precompiled.rc}" + +# The existence of the specific gcc warning about +# precompiled headers is a pretty good indicator +# that they are supported in the way we expect. +appendln CXXFLAGS "-Winvalid-pch" +if rc_compile cxx nolink; then + rc_appendln CXXFLAGS "$x" + + rc_appendln CXXFLAGS_PCH "-include$NL$src.hpp" + rc_appendln CXXFLAGS_PCH_FPIC "-include$NL$src.hpp.fpic" + + rc_appendln PRE_CXX_TARGETS "$src.hpp.gch" + rc_appendln PRE_CXX_TARGETS_FPIC "$src.hpp.fpic.gch" + rc_save +else + echo "Precompiled C++ headers not supported." >&2 + rc_undo +fi diff --git a/redoconf/rc/libgl.rc.od b/redoconf/rc/libgl.rc.od new file mode 100644 index 0000000..5a98829 --- /dev/null +++ b/redoconf/rc/libgl.rc.od @@ -0,0 +1,12 @@ +. ./redoconf.rc +rc_include rc/CC.rc rc/pkg-config.rc + +prog=' +#include +int x = GL_VERSION_1_1; +void f() { glPopMatrix(); } +' + +rc_pkg_detect LIBGL "gl" \ + rc_compile cc link "$prog" +rc_save diff --git a/redoconf/rc/libgtk2.rc.od b/redoconf/rc/libgtk2.rc.od new file mode 100644 index 0000000..f8f55e1 --- /dev/null +++ b/redoconf/rc/libgtk2.rc.od @@ -0,0 +1,12 @@ +. ./redoconf.rc +rc_include rc/CC.rc rc/pkg-config.rc + +prog=' +#include +int x = GTK_MAJOR_VERSION; +void f() { gtk_widget_child_focus(0, 0); } +' + +rc_pkg_detect LIBGTK2 "gtk+-2.0 gio-2.0 gdk-2.0 gdk-pixbuf-2.0" \ + rc_compile cc link "$prog" +rc_save diff --git a/redoconf/rc/libm.rc.od b/redoconf/rc/libm.rc.od new file mode 100644 index 0000000..87cc3ed --- /dev/null +++ b/redoconf/rc/libm.rc.od @@ -0,0 +1,25 @@ +. ./redoconf.rc +rc_include rc/CC.rc rc/pkg-config.rc + +prog=' +#include +int x = FP_NORMAL; +volatile float y; +void f() { y = sin(y); } +' + +rc_pkg_detect LIBM libm \ + rc_compile cc link "$prog" +if [ -z "$HAVE_LIBM" ]; then + rc_undo + rc_replaceln HAVE_LIBM 1 + rc_appendln LIBM "-lm" + appendln LIBS "$LIBM" + if ! rc_compile cc link "$prog"; then + rc_undo + rc_replaceln HAVE_LIBM "" + rc_replaceln LIBM "" + fi +fi + +rc_save diff --git a/redoconf/rc/libpng.rc.od b/redoconf/rc/libpng.rc.od new file mode 100644 index 0000000..43a11c6 --- /dev/null +++ b/redoconf/rc/libpng.rc.od @@ -0,0 +1,12 @@ +. ./redoconf.rc +rc_include rc/CC.rc rc/pkg-config.rc + +prog=' +#include +const char *x = PNG_LIBPNG_VER_STRING; +void f() { png_access_version_number(); } +' + +rc_pkg_detect LIBPNG libpng \ + rc_compile cc link "$prog" +rc_save diff --git a/redoconf/rc/libqt4.rc.od b/redoconf/rc/libqt4.rc.od new file mode 100644 index 0000000..e7a02eb --- /dev/null +++ b/redoconf/rc/libqt4.rc.od @@ -0,0 +1,10 @@ +. ./redoconf.rc +rc_include rc/CXX.rc rc/pkg-config.rc + +prog=' +#include +' + +rc_pkg_detect LIBQT4 QtCore \ + rc_compile cxx link "$prog" +rc_save diff --git a/redoconf/rc/libsdl.rc.od b/redoconf/rc/libsdl.rc.od new file mode 100644 index 0000000..1494d07 --- /dev/null +++ b/redoconf/rc/libsdl.rc.od @@ -0,0 +1,12 @@ +. ./redoconf.rc +rc_include rc/CC.rc rc/pkg-config.rc + +prog=' +#include +int x = SDL_INIT_TIMER; +void f() { SDL_Init(0); } +' + +rc_pkg_detect LIBSDL "sdl" \ + rc_compile cc link "$prog" +rc_save diff --git a/redoconf/rc/libx11.rc.od b/redoconf/rc/libx11.rc.od new file mode 100644 index 0000000..7450467 --- /dev/null +++ b/redoconf/rc/libx11.rc.od @@ -0,0 +1,12 @@ +. ./redoconf.rc +rc_include rc/CC.rc rc/pkg-config.rc + +prog=' +#include +int x = XYBitmap; +void f() { XCreateRegion(); } +' + +rc_pkg_detect LIBX11 "x11" \ + rc_compile cc link "$prog" +rc_save diff --git a/redoconf/rc/pkg-config.rc.od b/redoconf/rc/pkg-config.rc.od new file mode 100644 index 0000000..6c3f0cc --- /dev/null +++ b/redoconf/rc/pkg-config.rc.od @@ -0,0 +1,17 @@ +. ./redoconf.rc +rc_include + +for d in "$PKG_CONFIG" pkg-config; do + rc_undo + if "$d" --version >/dev/null 2>&1; then + rc_replaceln PKG_CONFIG "$d" + rc_replaceln HAVE_PKG_CONFIG 1 + rc_save + exit 0 + fi +done + +# Failed +rc_replaceln HAVE_PKG_CONFIG "" +rc_replaceln PKG_CONFIG "" +rc_save diff --git a/redoconf/rc/run.rc.od b/redoconf/rc/run.rc.od new file mode 100644 index 0000000..6f3f82f --- /dev/null +++ b/redoconf/rc/run.rc.od @@ -0,0 +1,27 @@ +. ./redoconf.rc +rc_include rc/CC.rc rc/windows.h.rc + +consider() { + echo "Considering RUN=$(shquote "$1")" >&2 + if [ -z "$1" ] || type "$1" >/dev/null 2>&1; then + rc_undo + rc_replaceln RUN "$1" + if RUN="$RUN" rc_compile cc run ""; then + rc_replaceln CAN_RUN 1 + rc_save + exit 0 + fi + fi +} + +consider "" +if [ -n "$HAVE_WINDOWS_H" ]; then + consider "wine64" + consider "wine" + consider "wine32" +fi + +rc_undo +rc_replaceln RUN "" +rc_replaceln CAN_RUN "" +rc_save diff --git a/redoconf/rc/shlib.rc.od b/redoconf/rc/shlib.rc.od new file mode 100644 index 0000000..35c938f --- /dev/null +++ b/redoconf/rc/shlib.rc.od @@ -0,0 +1,21 @@ +. ./redoconf.rc +rc_include rc/CC.rc + +appendln CFLAGS "-fPIC" +appendln LDFLAGS "-shared" + +prog=' +#include +void f() { atoi(""); } +' + +if [ -n "$STATIC" ]; then + echo "--enable-static specified; not building shared libraries." >&2 + rc_replaceln HAVE_SHLIB "" +elif RCC_NO_MAIN=1 rc_compile cc link "$prog"; then + rc_replaceln HAVE_SHLIB 1 +else + echo "Not building shared libraries on this platform." >&2 + rc_replaceln HAVE_SHLIB "" +fi +rc_save diff --git a/redoconf/rc/zdefs.rc.od b/redoconf/rc/zdefs.rc.od new file mode 100644 index 0000000..7684a5f --- /dev/null +++ b/redoconf/rc/zdefs.rc.od @@ -0,0 +1,11 @@ +. ./redoconf.rc +rc_include rc/CC.rc + +x="-Wl,-z,defs" +rc_appendln LDFLAGS "$x" +if rc_compile cc link; then + rc_save +else + echo "'$x' doesn't work on this platform; skipped." >&2 + rc_undo +fi diff --git a/redoconf/rc_vars.od b/redoconf/rc_vars.od new file mode 100644 index 0000000..c6cf4b3 --- /dev/null +++ b/redoconf/rc_vars.od @@ -0,0 +1,25 @@ +. ./redoconf.rc + +# this is called by each call to replaceln() and appendln(). +RC_KEYS="RC_KEYS" +rc_hook() { + contains_line "$RC_KEYS" "$1" || RC_KEYS="$RC_KEYS$NL$1" +} +rc_include _all.rc + +# Escape double-quote and backslash so they can +# be included as a C-style double-quoted string. +cquote() { + local v="$(xecho "$1" | sed -e 's,[\"\\],\\&,g' -e 's,$, \\,')" + printf '"%s"' "${v% \\}" +} + +( + echo "# Automatically generated by autovars.od" + for k in $(echo "$RC_KEYS" | sort); do + [ "$k" != "RC_INCLUDES" ] || continue + eval v=\$$k + echo "$k=$(shquote "$v")" + done +) >$3 +redo-stamp <$3 diff --git a/redoconf/redoconf.h.od b/redoconf/redoconf.h.od new file mode 100644 index 0000000..197a21e --- /dev/null +++ b/redoconf/redoconf.h.od @@ -0,0 +1,34 @@ +redo-ifchange rc_vars +. ./rc_vars + +NL=" +" + +# Escape double-quote and backslash so they can +# be included as a C-style double-quoted string. +cquote() { + local v="$(printf '%s' "$1" | sed -e 's,[\"\\],\\&,g' -e 's,$, \\,')" + printf '"%s"' "${v% \\}" +} + +is_number() { + expr "$1" + 1 >/dev/null 2>&1 +} + +( + echo "/* Automatically generated by redoconf.h.od */" + IFS="$NL" + for k in $RC_KEYS; do + [ "$k" != "RC_KEYS" ] || continue + [ "$k" != "RC_INCLUDES" ] || continue + eval v=\$$k + if [ -z "$v" ]; then + echo "#undef $k" + elif is_number "$v"; then + echo "#define $k $v" + else + echo "#define $k $(cquote "$v")" + fi + done +) >$3 +redo-stamp <$3 diff --git a/redoconf/run.od b/redoconf/run.od new file mode 100644 index 0000000..104b0c1 --- /dev/null +++ b/redoconf/run.od @@ -0,0 +1,16 @@ +. ./redoconf.rc +rc_include rc/run.rc + +if [ -n "$CAN_RUN" ]; then + cat >$3 <<-EOF + #!/bin/sh -e + # Run the given program, possibly under an emulator. + [ -n "\$1" ] + unset DISPLAY + exec $RUN "\$@" + EOF + chmod a+x "$3" + redo-stamp <$3 +else + echo "Cannot run programs; not creating run script." >&2 +fi diff --git a/redoconf/trycompile b/redoconf/trycompile new file mode 100644 index 0000000..b36ac10 --- /dev/null +++ b/redoconf/trycompile @@ -0,0 +1,71 @@ +#!/bin/sh -e +die() { + echo "$0: trycompile: $*" >&2 + exit 99 +} + +ctype=$1 +linktype=$2 +code=$3 +case $ctype in + cc) + [ -n "$CC" ] || die 'must set $CC first.' + useCC="$CC" + useCF="$CFLAGS" + useExt=".c" + ;; + cxx) + [ -n "$CXX" ] || die 'must set $CXX first.' + useCC="$CXX" + useCF="$CXXFLAGS" + useExt=".cc" + ;; + *) + die "unknown compile type '$ctype'" + ;; +esac +case $linktype in + link|run) + [ -n "$LINK" ] || die 'must set $LINK first.' + ;; + nolink) + ;; + *) + die "unknown link type '$linktype'" + ;; +esac +base="try.$$.tmp" +out="$base.o" +out2="$base.exe" +src="$base$useExt" +rm -f "$src" "$out" "$out2" +set -x +: "[trycompile]" "$@" +main= +[ -n "$RCC_NO_MAIN" ] || main="int main() { return 0; }" +printf '%s' " + $code + + $main +" >"$src" +NL=" +" +IFS="$NL" +set +e +set -f +# We intentionally want to split the variables here, +# splitting on $NL, so we don't quote them. +# 'set -f' prevents interpreting wildcards, which +# we don't want to treat as special. +( + $useCC $CPPFLAGS $useCF -o "$out" -c "$src" || exit + if [ "$linktype" = "link" -o "$linktype" = "run" ]; then + $LINK $LDFLAGS -o "$out2" "$out" $LIBS || exit + fi + if [ "$linktype" = "run" ]; then + $RUN "./$out2" || exit + fi +) +rv=$? +rm -f "$src" "$out" "$out2" +exit "$rv" diff --git a/redoconf/utils.sh b/redoconf/utils.sh new file mode 100644 index 0000000..9dcedeb --- /dev/null +++ b/redoconf/utils.sh @@ -0,0 +1,58 @@ +NL=" +" + +# Like 'echo', but never processes backslash escapes. +# (Some shells' builtin echo do, and some don't, so this +# is safer.) +xecho() { + printf '%s\n' "$*" +} + +# Returns true if string $1 contains the line $2. +# Lines are delimited by $NL. +contains_line() { + case "$NL$1$NL" in + *"$NL$2$NL"*) return 0 ;; + *) return 1 ;; + esac +} + +# Split the first (up to) 20 words from $1, +# returning a string where the words are separated +# by $NL instead. +# +# To allow words including whitespace, you can backslash +# escape the whitespace (eg. hello\ world). Backslashes +# will be removed from the output string. +# +# We can use this to read pkg-config output, among other +# things. +# +# TODO: find a POSIX sh way to eliminate the word limit. +# I couldn't find an easy way to split on non-backslashed +# whitespace without a fork-exec, which is too slow. +# If we resorted to bashisms, we could use 'read -a', +# but that's not portable. +rc_splitwords() { + xecho "$1" | ( + read v0 v1 v2 v3 v4 v5 v6 v7 v8 v9 \ + v10 v11 v12 v13 v14 v15 v16 v17 v18 v19 \ + x + if [ -n "$x" ]; then + echo "rc_splitwords: too many words" >&2 + exit 97 + fi + for d in "$v0" "$v1" "$v2" "$v3" "$v4" \ + "$v5" "$v6" "$v7" "$v8" "$v9" \ + "$v10" "$v11" "$v12" "$v13" "$v14" \ + "$v15" "$v16" "$v17" "$v18" "$v19"; do + [ -z "$d" ] || xecho "$d" + done + ) +} + +# Escape single-quote characters so they can +# be included as a sh-style single-quoted string. +shquote() { + printf "'%s'" "$(xecho "$1" | sed -e "s,','\\\\'',g")" +}