From 6dae51f4d2b1f32aee025dabe26ee5f75aa64608 Mon Sep 17 00:00:00 2001 From: Avery Pennarun Date: Sun, 3 Feb 2019 01:14:51 -0500 Subject: [PATCH] Experimental new redoconf C/C++ build/autoconfiguration system. To test it out, try this: ./do -j10 build cd docs/cookbook/c redo -j10 test It should detect all the compilers on your system and make three separate builds for each one: normal, debug, and optimized. Then it tries to run a test program under each one. If there are windows cross compilers and you also have 'wine' installed, it'll try running the test program under wine as well. redoconf currently has no documentation other than the example program. We'll fix that later. --- docs/cookbook/all.do | 2 +- docs/cookbook/c/.gitignore | 6 + docs/cookbook/c/all.do | 1 + docs/cookbook/c/all.h | 4 + docs/cookbook/c/all.hpp | 4 + docs/cookbook/c/all.od | 1 + docs/cookbook/c/all.rc.od | 23 +++ docs/cookbook/c/allconfig.do | 27 +++ docs/cookbook/c/arches.do | 14 ++ docs/cookbook/c/clean.od | 6 + docs/cookbook/c/configure | 3 + docs/cookbook/c/configure.help | 17 ++ docs/cookbook/c/default.each.do | 10 + docs/cookbook/c/flagtest.c | 10 + docs/cookbook/c/flagtest.o.od | 9 + docs/cookbook/c/hello world.list.od | 30 +++ docs/cookbook/c/libhello/hello.c | 6 + docs/cookbook/c/libhello/hello.h | 6 + docs/cookbook/c/libhello/lib hello.list | 1 + docs/cookbook/c/libhello/lib hello.ver | 1 + docs/cookbook/c/main.c | 22 ++ docs/cookbook/c/main.h | 19 ++ docs/cookbook/c/monotime.c | 58 ++++++ docs/cookbook/c/monotime.h | 6 + docs/cookbook/c/rc/extra.rc.od | 5 + docs/cookbook/c/rc/posix.rc.od | 16 ++ docs/cookbook/c/rc/printf_lld.rc.od | 16 ++ docs/cookbook/c/redoconf | 1 + docs/cookbook/c/slow.cc | 10 + docs/cookbook/c/test.do | 1 + docs/cookbook/c/test.od | 16 ++ docs/cookbook/c/when.c.do | 7 + redoconf/_all.rc.od | 51 +++++ redoconf/_compile.od | 14 ++ redoconf/compile.od | 51 +++++ redoconf/configure.sh | 200 ++++++++++++++++++ redoconf/default.do.sh | 181 ++++++++++++++++ redoconf/default.precompile.od | 31 +++ redoconf/link-shlib.od | 36 ++++ redoconf/link.od | 22 ++ redoconf/rc.sh | 240 ++++++++++++++++++++++ redoconf/rc/CC.rc.od | 68 ++++++ redoconf/rc/CXX.rc.od | 35 ++++ redoconf/rc/Wall.rc.od | 9 + redoconf/rc/Wextra.rc.od | 9 + redoconf/rc/_init.rc.od | 8 + redoconf/rc/default.autolib.rc.od | 18 ++ redoconf/rc/default.func.rc.od | 13 ++ redoconf/rc/default.h.precompiled.rc.od | 23 +++ redoconf/rc/default.h.rc.od | 13 ++ redoconf/rc/default.hpp.precompiled.rc.od | 23 +++ redoconf/rc/libgl.rc.od | 12 ++ redoconf/rc/libgtk2.rc.od | 12 ++ redoconf/rc/libm.rc.od | 25 +++ redoconf/rc/libpng.rc.od | 12 ++ redoconf/rc/libqt4.rc.od | 10 + redoconf/rc/libsdl.rc.od | 12 ++ redoconf/rc/libx11.rc.od | 12 ++ redoconf/rc/pkg-config.rc.od | 17 ++ redoconf/rc/run.rc.od | 27 +++ redoconf/rc/shlib.rc.od | 21 ++ redoconf/rc/zdefs.rc.od | 11 + redoconf/rc_vars.od | 25 +++ redoconf/redoconf.h.od | 34 +++ redoconf/run.od | 16 ++ redoconf/trycompile | 71 +++++++ redoconf/utils.sh | 58 ++++++ 67 files changed, 1777 insertions(+), 1 deletion(-) create mode 100644 docs/cookbook/c/.gitignore create mode 100644 docs/cookbook/c/all.do create mode 100644 docs/cookbook/c/all.h create mode 100644 docs/cookbook/c/all.hpp create mode 100644 docs/cookbook/c/all.od create mode 100644 docs/cookbook/c/all.rc.od create mode 100644 docs/cookbook/c/allconfig.do create mode 100644 docs/cookbook/c/arches.do create mode 100644 docs/cookbook/c/clean.od create mode 100755 docs/cookbook/c/configure create mode 100644 docs/cookbook/c/configure.help create mode 100644 docs/cookbook/c/default.each.do create mode 100644 docs/cookbook/c/flagtest.c create mode 100644 docs/cookbook/c/flagtest.o.od create mode 100644 docs/cookbook/c/hello world.list.od create mode 100644 docs/cookbook/c/libhello/hello.c create mode 100644 docs/cookbook/c/libhello/hello.h create mode 100644 docs/cookbook/c/libhello/lib hello.list create mode 100644 docs/cookbook/c/libhello/lib hello.ver create mode 100644 docs/cookbook/c/main.c create mode 100644 docs/cookbook/c/main.h create mode 100644 docs/cookbook/c/monotime.c create mode 100644 docs/cookbook/c/monotime.h create mode 100644 docs/cookbook/c/rc/extra.rc.od create mode 100644 docs/cookbook/c/rc/posix.rc.od create mode 100644 docs/cookbook/c/rc/printf_lld.rc.od create mode 120000 docs/cookbook/c/redoconf create mode 100644 docs/cookbook/c/slow.cc create mode 100644 docs/cookbook/c/test.do create mode 100644 docs/cookbook/c/test.od create mode 100644 docs/cookbook/c/when.c.do create mode 100644 redoconf/_all.rc.od create mode 100644 redoconf/_compile.od create mode 100644 redoconf/compile.od create mode 100644 redoconf/configure.sh create mode 100644 redoconf/default.do.sh create mode 100644 redoconf/default.precompile.od create mode 100644 redoconf/link-shlib.od create mode 100644 redoconf/link.od create mode 100644 redoconf/rc.sh create mode 100644 redoconf/rc/CC.rc.od create mode 100644 redoconf/rc/CXX.rc.od create mode 100644 redoconf/rc/Wall.rc.od create mode 100644 redoconf/rc/Wextra.rc.od create mode 100644 redoconf/rc/_init.rc.od create mode 100644 redoconf/rc/default.autolib.rc.od create mode 100644 redoconf/rc/default.func.rc.od create mode 100644 redoconf/rc/default.h.precompiled.rc.od create mode 100644 redoconf/rc/default.h.rc.od create mode 100644 redoconf/rc/default.hpp.precompiled.rc.od create mode 100644 redoconf/rc/libgl.rc.od create mode 100644 redoconf/rc/libgtk2.rc.od create mode 100644 redoconf/rc/libm.rc.od create mode 100644 redoconf/rc/libpng.rc.od create mode 100644 redoconf/rc/libqt4.rc.od create mode 100644 redoconf/rc/libsdl.rc.od create mode 100644 redoconf/rc/libx11.rc.od create mode 100644 redoconf/rc/pkg-config.rc.od create mode 100644 redoconf/rc/run.rc.od create mode 100644 redoconf/rc/shlib.rc.od create mode 100644 redoconf/rc/zdefs.rc.od create mode 100644 redoconf/rc_vars.od create mode 100644 redoconf/redoconf.h.od create mode 100644 redoconf/run.od create mode 100644 redoconf/trycompile create mode 100644 redoconf/utils.sh 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")" +}