Add the remake and configure infrastructure.
authorKim Nguyễn <kn@lri.fr>
Wed, 17 Apr 2013 06:08:10 +0000 (08:08 +0200)
committerKim Nguyễn <kn@lri.fr>
Wed, 17 Apr 2013 06:08:10 +0000 (08:08 +0200)
Remakefile.in [new file with mode: 0644]
configure.in [new file with mode: 0644]
remake.cpp [new file with mode: 0644]
tools/odeps.sh [new file with mode: 0755]
tools/osort.sh [new file with mode: 0755]

diff --git a/Remakefile.in b/Remakefile.in
new file mode 100644 (file)
index 0000000..74fef80
--- /dev/null
@@ -0,0 +1,134 @@
+OCAMLFINDPACKAGES = "ulex,unix,expat,camlp4.macro"
+OCAMLFINDSYNTAX = camlp4o
+OCAMLFINDPPOPTS = $(addprefix "-ppopt ", @CAMLP4FLAGS@ -I include)
+OCAMLFINDINCLUDES = $(addprefix "-I ", src)
+OCAMLFINDFLAGS = -syntax $(OCAMLFINDSYNTAX) -package $(OCAMLFINDPACKAGES)  \
+       $(OCAMLFINDPPOPTS) $(OCAMLFINDINCLUDES)
+OCAMLFINDLINKFLAGS = -linkpkg
+ODEP=tools/odeps.sh @OCAMLDEP@
+BIN=@PACKAGE_TARNAME@
+EXE=@EXE@
+
+
+
+Remakefile: Remakefile.in config.status
+       ./config.status Remakefile
+
+configure config.status: configure.in
+       autoconf
+       ./config.status --recheck
+
+src/%.native:
+       base=${1%.native}
+       src=${base}.ml
+       obj=${base}.cmx
+       dir=$(dirname $1)
+       ./remake -v OCAMLDEPNATIVE=-native "$obj"
+       SRCS=$(ls "$dir"/*.cmx | sed 's/\.cmx/.ml/g')
+       OBJS=$(@OCAMLDEP@ $OCAMLDEPNATIVE $OCAMLFINDFLAGS -modules $SRCS | tools/osort.sh cmx)
+       @OCAMLOPT@ -o "$1"  @OCAMLFLAGS@ @OCAMLOPTFLAGS@ $OCAMLFINDLINKFLAGS $OCAMLFINDFLAGS $OBJS
+
+src/%.byte:
+       base=${1%.byte}
+       src=${base}.ml
+       obj=${base}.cmo
+       dir=$(dirname $1)
+       ./remake "$obj"
+       OBJS=""
+       for i in "$dir"/*.cmi; do
+               ibase=${i%.cmi}
+               if test -f "${ibase}.ml" -o -d "${ibase}" -o -f "${ibase}.mly" -o -f "${ibase}.mll"; then
+                       OBJS="$OBJS ${ibase}.cmo"
+               fi
+       done
+       ./remake $OBJS
+       SRCS=$(ls "$dir"/*.cmo | sed 's/\.cmo/.ml/g')
+       SORTED_OBJS=$(@OCAMLDEP@ $OCAMLFINDFLAGS -modules $SRCS | tools/osort.sh cmo)
+       @OCAMLC@ -o "$1"  @OCAMLFLAGS@ @OCAMLCFLAGS@ $OCAMLFINDLINKFLAGS $OCAMLFINDFLAGS $SORTED_OBJS
+
+%.cmx:
+       target="$1"
+       base="${target%.cmx}"
+       src="${base}.ml"
+       if [ -f "$src" ]; then
+               ./remake "$src"
+               DEPS=$( $ODEP -native $OCAMLFINDFLAGS $PACKINCLUDE "$src" )
+               echo "$DEPS" | ./remake -v PACKINCLUDE="$PACKINCLUDE" -v OCAMLDEPNATIVE=-native -v OCAMLFORPACK="$OCAMLFORPACK" -r "$target"
+               @OCAMLOPT@ -o "$target" -c @OCAMLFLAGS@ @OCAMLOPTFLAGS@ $OCAMLFORPACK $OCAMLFINDFLAGS $PACKINCLUDE "$src"
+       elif [ -d "$base" ];    then
+               modname=$(basename "$base")
+               packname=$(echo "$modname" | sed 's/\(.*\)/\u\1/')
+               OBJS=$(ls "$base"/*.ml | sed 's/\.ml/.cmx/g')
+               ./remake -v PACKINCLUDE="-I $base" -v OCAMLFORPACK="-for-pack $packname" $OBJS
+               SORTED_OBJS=$(@OCAMLDEP@ -native $OCAMLFINDFLAGS -I $base -modules "$base"/*.ml | tools/osort.sh cmx)
+               @OCAMLOPT@ -o "$target" -pack $SORTED_OBJS
+       elif [ -f "$base".mly ]; then
+               ./remake "$base".mly
+               @OCAMLYACC@ "$base".mly
+                $ODEP $OCAMLDEPNATIVE $OCAMLFINDFLAGS "$src" | \
+                       ./remake -v PACKINCLUDE="$PACKINCLUDE" -v OCAMLDEPNATIVE=-native -v OCAMLFORPACK="$OCAMLFORPACK" -r "$target"
+               @OCAMLOPT@ -o "$target" -c @OCAMLFLAGS@ @OCAMLOPTFLAGS@ $OCAMLFORPACK $OCAMLFINDFLAGS $PACKINCLUDE "$src"
+       fi
+
+
+%.cmo:
+       target="$1"
+       base="${target%.cmo}"
+       src="${base}.ml"
+       if [ -f "$src" ]; then
+               ./remake "$src"
+               DEPS=$( $ODEP $OCAMLFINDFLAGS $PACKINCLUDE "$src" )
+               echo "$DEPS" | ./remake -v PACKINCLUDE="$PACKINCLUDE" -r "$target"
+               @OCAMLC@ -o "$target" -c @OCAMLFLAGS@ @OCAMLCFLAGS@ $OCAMLFINDFLAGS $PACKINCLUDE "$src"
+       elif [ -d "$base" ];    then
+               modname=$(basename "$base")
+               packname=$(echo "$modname" | sed 's/\(.*\)/\u\1/')
+               OBJS=$(ls "$base"/*.ml | sed 's/\.ml/.cmo/g')
+               ./remake -v PACKINCLUDE="-I $base" $OBJS
+               SORTED_OBJS=$(@OCAMLDEP@ $OCAMLFINDFLAGS -I $base -modules "$base"/*.ml | tools/osort.sh cmo)
+               @OCAMLC@ -o "$target" -pack $SORTED_OBJS
+       elif [ -f "$base".mly ]; then
+               ./remake "$base".mly
+               @OCAMLYACC@ "$base".mly
+                $ODEP $OCAMLFINDFLAGS $PACKINCLUDE "$src" | ./remake -v PACKINCLUDE="$PACKINCLUDE" -r "$target"
+               @OCAMLC@ -o "$target" -c @OCAMLFLAGS@ @OCAMLCFLAGS@ $OCAMLFINDFLAGS $PACKINCLUDE "$src"
+       fi
+
+
+
+%.cmi:
+       target="$1"
+       base=${target%.cmi}
+       if test -f "$base".mli; then
+               ./remake "$base".mli
+               src=${base}.mli
+               DEPS=$($ODEP $OCAMLDEPNATIVE $OCAMLFINDFLAGS $PACKINCLUDE "$src")
+               echo "$DEPS" | ./remake -v PACKINCLUDE="$PACKINCLUDE" -v OCAMLDEPNATIVE="$OCAMLDEPNATIVE" -v OCAMLFORPACK="$OCAMLFORPACK" -r "$target"
+               if test -z "$OCAMLDEPNATIVE"; then
+                       @OCAMLC@ -o "$target" -c @OCAMLFLAGS@ @OCAMLCFLAGS@ $OCAMLFINDFLAGS $PACKINCLUDE "$src"
+               else
+                       @OCAMLOPT@ -o "$target" -c @OCAMLFLAGS@ @OCAMLOPTFLAGS@ $OCAMLFINDFLAGS $PACKINCLUDE "$src"
+               fi
+       else
+               if test -z "$OCAMLDEPNATIVE"; then
+                 obj=${base}.cmo
+               else
+                 obj=${base}.cmx
+               fi
+               ./remake -v PACKINCLUDE="$PACKINCLUDE" -v OCAMLDEPNATIVE="$OCAMLDEPNATIVE" -v OCAMLFORPACK="$OCAMLFORPACK" "$obj"
+       fi
+
+clean:
+       find src -name '*.cm*' -o -name '*.o' -o -name '*.byte' -o -name '*.native' -o -name '*.mll' -o -name '*.mly' | while read file; do
+       case "$file" in
+               *.mll)
+                       rm -f "${file%.mll}.ml"
+                       ;;
+               *.mly)
+                       rm -f "${file%.mly}.ml" "${file%.mly}.mli"
+                       ;;
+               *)
+                       rm -f "$file"
+                       ;;
+       esac
+       done
diff --git a/configure.in b/configure.in
new file mode 100644 (file)
index 0000000..fb09327
--- /dev/null
@@ -0,0 +1,253 @@
+AC_INIT([TAToo],
+        [0.0.1],
+        [Kim Nguyễn <kn@lri.fr>],
+        [tatoo])
+
+
+#detect ocamlfind
+OCAMLFIND=ocamlfind
+AC_ARG_WITH([ocamlfind],
+            AS_HELP_STRING([--with-ocamlfind=PATH], [location of the ocamlfind binary]),
+        [OCAMLFIND="$withval"])
+AC_MSG_CHECKING([for ocamlfind ($OCAMLFIND)])
+$OCAMLFIND list >/dev/null 2>&1
+if test $? ; then
+   AC_MSG_RESULT([found])
+   OCAMLC="$OCAMLFIND ocamlc"
+   OCAMLOPT="$OCAMLFIND ocamlopt"
+   OCAMLDEP="$OCAMLFIND ocamldep"
+else
+   AC_MSG_RESULT([not found])
+   AC_MSG_ERROR([Cannot find ocamlfind.])
+fi
+
+AC_SUBST([OCAMLFIND])
+
+#check ocamlc
+AC_MSG_CHECKING([for $OCAMLC])
+$OCAMLC >/dev/null 2>&1
+if test $? ; then
+        AC_MSG_RESULT([ok])
+else
+        AC_MSG_RESULT([failed])
+       AC_MSG_ERROR(Cannot find ocamlc.)
+fi
+AC_SUBST([OCAMLC])
+
+#check ocamlopt
+AC_MSG_CHECKING([for $OCAMLOPT])
+$OCAMLOPT >/dev/null 2>&1
+if test $? ; then
+        AC_MSG_RESULT([ok])
+else
+        AC_MSG_RESULT([failed])
+       AC_MSG_ERROR(Cannot find ocamlopt.)
+fi
+AC_SUBST([OCAMLOPT])
+
+#check ocamldep
+AC_MSG_CHECKING([for $OCAMLDEP])
+$OCAMLDEP >/dev/null 2>&1
+if test $? ; then
+        AC_MSG_RESULT([ok])
+else
+        AC_MSG_RESULT([failed])
+       AC_MSG_ERROR(Cannot find ocamldep.)
+fi
+AC_SUBST([OCAMLDEP])
+
+#ocaml version
+OCAMLVERSION=$($OCAMLC -version)
+AC_MSG_CHECKING([ocaml version])
+AC_MSG_RESULT([$OCAMLVERSION])
+
+case "$OCAMLVERSION" in
+     3.12.*) ;; 4.*) ;;
+     *) AC_MSG_ERROR([OCaml $OCAMLVERSION is too old])
+     ;;
+esac
+
+AC_SUBST(OCAMLVERSION)
+
+#detect ocamlyacc
+OCAMLYACC=ocamlyacc
+AC_ARG_WITH([ocamlyacc],
+            AS_HELP_STRING([--with-ocamlyacc=PATH], [location of the ocamlyacc binary]),
+        [OCAMLYACC="$withval"])
+AC_MSG_CHECKING([for ocamlyacc ($OCAMLYACC)])
+OCAMLYACC_VERSION=$($OCAMLYACC -version 2>/dev/null || echo foo)
+case "$OCAMLYACC_VERSION" in
+     *$OCAMLVERSION)
+     AC_MSG_RESULT([ok])
+     ;;
+     foo)
+     AC_MSG_RESULT([failed])
+     AC_MSG_ERROR([Cannot find ocamlyacc])
+     ;;
+     *)
+     AC_MSG_RESULT([failed])
+     AC_MSG_ERROR([Bad ocamlyacc version])
+     ;;
+esac
+AC_SUBST([OCAMLYACC])
+
+#detect ocamllex
+OCAMLLEX=ocamllex
+AC_ARG_WITH([ocamllex],
+            AS_HELP_STRING([--with-ocamllex=PATH], [location of the ocamllex binary]),
+        [OCAMLLEX="$withval"])
+AC_MSG_CHECKING([for ocamllex ($OCAMLLEX)])
+OCAMLLEX_VERSION=$($OCAMLLEX -version 2>/dev/null || echo foo)
+case "$OCAMLLEX_VERSION" in
+     *$OCAMLVERSION)
+     AC_MSG_RESULT([ok])
+     ;;
+     foo)
+     AC_MSG_RESULT([failed])
+     AC_MSG_ERROR([Cannot find ocamllex])
+     ;;
+     *)
+     AC_MSG_RESULT([failed])
+     AC_MSG_ERROR([Bad ocamllex version])
+     ;;
+esac
+AC_SUBST([OCAMLLEX])
+
+
+#detect camlp4
+CAMLP4=camlp4
+AC_ARG_WITH([camlp4],
+            AS_HELP_STRING([--with-camlp4=PATH], [location of the camlp4 binary]),
+        [CAMLP4="$withval"])
+AC_MSG_CHECKING([for camlp4 ($CAMLP4)])
+CAMLP4_VERSION=$($CAMLP4 -version 2>/dev/null || echo foo)
+case "$CAMLP4_VERSION" in
+     *$OCAMLVERSION)
+     AC_MSG_RESULT([ok])
+     ;;
+     foo)
+     AC_MSG_RESULT([failed])
+     AC_MSG_ERROR([Cannot find camlp4])
+     ;;
+     *)
+     AC_MSG_RESULT([failed])
+     AC_MSG_ERROR([Bad camlp4 version])
+     ;;
+esac
+AC_SUBST([CAMLP4])
+
+# platform
+AC_MSG_CHECKING([platform])
+OCAML_PLATFORM=$(echo 'print_endline Sys.os_type;;' | ocaml -noprompt | grep '^@<:@A-Z@:>@')
+AC_MSG_RESULT([$OCAML_PLATFORM])
+if test "$OCAML_PLATFORM" = "Win32"; then
+         EXE=.exe
+else
+         EXE=
+fi
+AC_SUBST(OCAML_PLATFORM)
+AC_SUBST(EXE)
+
+# required libraries
+AC_MSG_CHECKING([for ulex])
+$OCAMLFIND query ulex >/dev/null 2>&1
+if test $? ; then
+   AC_MSG_RESULT([found])
+else
+   AC_MSG_RESULT([not found])
+   AC_MSG_ERROR([Cannot find ulex.])
+fi
+
+AC_MSG_CHECKING([for expat])
+$OCAMLFIND query ulex >/dev/null 2>&1
+if test $? ; then
+   AC_MSG_RESULT([found])
+else
+   AC_MSG_RESULT([not found])
+   AC_MSG_ERROR([Cannot find ulex.])
+fi
+
+#compilation options
+#debugging mode
+OCAMLFLAGS=$OCAMLFLAGS
+CAMLP4FLAGS=$CAMLP4FLAGS
+OCAMLCFLAGS=$OCAMLCFLAGS
+OCAMLOPTFLAGS=$OCAMLOPTFLAGS
+
+AC_ARG_ENABLE([debug],
+              [  --enable-debug          build in debug mode],
+              [DEBUG=$enableval],
+              [DEBUG=no])
+
+if test "x$DEBUG" = "xyes"; then
+   OCAMLFLAGS="$OCAMLFLAGS -g"
+   CAMLP4FLAGS="$CAMLP4FLAGS -DDEBUG"
+fi
+
+#profiling
+AC_ARG_ENABLE([profile],
+              [  --enable-profile       compile in profiling code],
+              [PROFILE=$enableval],
+              [PROFILE=no])
+
+if test "x$PROFILE" = "xyes"; then
+   OCAMLOPTFLAGS="$OCAMLOPTFLAGS -p"
+   OCAMLC="$OCAMLFIND ocamlcp"
+fi
+
+#tracing code
+AC_ARG_ENABLE([trace],
+              [  --enable-trace         add tracing code],
+              [TRACE=$enableval],
+              [TRACE=no])
+
+if test "x$TRACE" = "xyes"; then
+   CAMLP4FLAGS="$CAMLP4FLAGS -DHTMLTRACE"
+fi
+
+#inlining
+
+AC_ARG_ENABLE([inline],
+              [  --enable-inline         set OCaml inlining level (default=100)],
+              [INLINE=$enableval],
+              [INLINE=100])
+
+#unsafe
+AC_ARG_ENABLE([unsafe],
+              [  --enable-unsafe         use unsafe array and string accesses],
+              [UNSAFE=1],
+              [UNSAFE=0])
+
+if test "x$UNSAFE" = "xyes"; then
+   CAMLP4FLAGS="$CAMLP4FLAGS -unsafe"
+fi
+
+
+AC_PROG_CXX()
+
+
+AC_SUBST([REMAKE], [./remake$EXE])
+if test ! -x  "$REMAKE" -o "$REMAKE" -ot remake.cpp; then
+AC_MSG_NOTICE([creating $REMAKE])
+case $(uname -s) in
+MINGW*)
+        $CXX -Wall -O2 -o remake.exe remake.cpp -lws2_32
+        if test $? != 0; then AC_MSG_FAILURE([failed]); fi
+        ;;
+*)
+        $CXX -Wall -O2 -o remake remake.cpp
+        if test $? != 0; then AC_MSG_FAILURE([failed]); fi
+        ;;
+esac
+else
+AC_MSG_NOTICE([$REMAKE exists, not rebuilding])
+fi
+
+
+AC_SUBST(INLINE)
+AC_SUBST(OCAMLFLAGS)
+AC_SUBST(OCAMLCFLAGS)
+AC_SUBST(OCAMLOPTFLAGS)
+AC_SUBST(CAMLP4FLAGS)
+AC_CONFIG_FILES(Remakefile)
+AC_OUTPUT
diff --git a/remake.cpp b/remake.cpp
new file mode 100644 (file)
index 0000000..5d8dc3d
--- /dev/null
@@ -0,0 +1,2318 @@
+/**
+@mainpage Remake, a build system that bridges the gap between make and redo.
+
+As with <b>make</b>, <b>remake</b> uses a centralized rule file, which is
+named <b>Remakefile</b>. It contains rules with a <em>make</em>-like
+syntax:
+
+@verbatim
+target1 target2 ... : dependency1 dependency2 ...
+       shell script
+       that builds
+       the targets
+@endverbatim
+
+A target is known to be up-to-date if all its dependencies are. If it
+has no known dependencies yet the file already exits, it is assumed to
+be up-to-date. Obsolete targets are rebuilt thanks to the shell script
+provided by the rule.
+
+As with <b>redo</b>, <b>remake</b> supports dynamic dependencies in
+addition to these static dependencies. Whenever a script executes
+<tt>remake dependency4 dependency5 ...</tt>, these dependencies are
+rebuilt if they are obsolete. (So <b>remake</b> acts like
+<b>redo-ifchange</b>.) Moreover, these dependencies are stored in file
+<b>.remake</b> so that they are remembered in subsequent runs. Note that
+dynamic dependencies from previous runs are only used to decide whether a
+target is obsolete; they are not automatically rebuilt when they are
+obsolete yet a target depends on them. They will only be rebuilt once the
+dynamic call to <b>remake</b> is executed.
+
+In other words, the following two rules have almost the same behavior.
+
+@verbatim
+target1 target2 ... : dependency1 dependency2 ...
+       shell script
+
+target1 target2 ... :
+       remake dependency1 dependency2 ...
+       shell script
+@endverbatim
+
+(There is a difference if the targets already exist, have never been
+built before, and the dependencies are either younger or obsolete, since
+the targets will not be rebuilt in the second case.)
+
+The above usage of dynamic dependencies is hardly useful. Their strength
+lies in the fact that they can be computed on the fly:
+
+@verbatim
+%.o : %.c
+       gcc -MMD -MF $1.d -o $1 -c ${1%.o}.c
+       remake -r < $1.d
+       rm $1.d
+
+%.cmo : %.ml
+       ocamldep ${1%.cmo}.ml | remake -r $1
+       ocamlc -c ${1%.cmo}.ml
+
+after.xml: before.xml rules.xsl
+       xsltproc --load-trace -o after.xml rules.xsl before.xml 2> deps
+       remake $(sed -n -e "\\,//,! s,^.*URL=\"\\([^\"]*\\).*\$,\\1,p" deps)
+       rm deps
+@endverbatim
+
+Note that the first rule fails if any of the header files included by
+a C source file has to be automatically generated. In that case, one
+should perform a first call to <b>remake</b> them before calling the
+compiler. (Dependencies from several calls to <b>remake</b> are
+cumulative, so they will all be remembered the next time.)
+
+\section sec-usage Usage
+
+Usage: <tt>remake <i>options</i> <i>targets</i></tt>
+
+Options:
+
+- <tt>-d</tt>: Echo script commands.
+- <tt>-j[N]</tt>, <tt>--jobs=[N]</tt>: Allow N jobs at once; infinite jobs
+  with no argument.
+- <tt>-k</tt>, <tt>--keep-going</tt>: Keep going when some targets cannot be made.
+- <tt>-r</tt>: Look up targets from the dependencies on standard input.
+- <tt>-s</tt>, <tt>--silent</tt>, <tt>--quiet</tt>: Do not echo targets.
+
+\section sec-syntax Syntax
+
+Lines starting with a space character or a tabulation are assumed to be rule
+scripts. They are only allowed after a rule header.
+
+Lines starting with <tt>#</tt> are considered to be comments and are ignored.
+They do interrupt rule scripts though.
+
+Any other line is either a rule header or a variable definition. If such a
+line ends with a backslash, the following line break is ignored and the line
+extends to the next one.
+
+Rule headers are a nonempty list of names, followed by a colon, followed by
+another list of names, possibly empty. Variable definitions are a single
+name followed by equal followed by a list of names, possibly empty. Basically,
+the syntax of a rule is as follows:
+
+@verbatim
+targets : prerequisites
+       shell script
+@endverbatim
+
+List of names are space-separated sequences of names. If a name contains a
+space character, it should be put into double quotes. Names can not be any
+of the following special characters <tt>:$(),="</tt>. Again, quotation
+should be used. Quotation marks can be escaped by a backslash inside
+quoted names.
+
+\subsection sec-variables Variables
+
+Variables can be used to factor lists of targets or dependencies. They are
+expanded as they are encountered during <b>Remakefile</b> parsing.
+
+@verbatim
+VAR1 = c d
+VAR2 = a $(VAR1) b
+$(VAR2) e :
+@endverbatim
+
+Variables can be used inside rule scripts; they are available as non-exported
+shell variables there.
+
+\subsection sec-functions Built-in functions
+
+<b>remake</b> also supports a few built-in functions inspired from <b>make</b>.
+
+- <tt>$(addprefix <i>prefix</i>, <i>list</i>)</tt> returns the list obtained
+  by prepending its first argument to each element of its second argument.
+- <tt>$(addsuffix <i>suffix</i>, <i>list</i>)</tt> returns the list obtained
+  by appending its first argument to each element of its second argument.
+
+Note that functions are ignored inside scripts.
+
+\section sec-semantics Semantics
+
+\subsection src-obsolete When are targets obsolete?
+
+A target is obsolete:
+
+- if there is no file corresponding to the target, or to one of its siblings
+  in a multi-target rule,
+- if any of its dynamic dependencies from a previous run or any of its static
+  prerequisites is obsolete,
+- if the latest file corresponding to its siblings or itself is older than any
+  of its dynamic dependencies or static prerequisites.
+
+In all the other cases, it is assumed to be up-to-date (and so are all its
+siblings). Note that the last rule above says "latest" and not "earliest". While
+it might cause some obsolete targets to go unnoticed in corner cases, it allows
+for the following kind of rules:
+
+@verbatim
+config.h stamp-config_h: config.h.in config.status
+       ./config.status config.h
+       touch stamp-config_h
+@endverbatim
+
+A <tt>config.status</tt> file generally does not update header files (here
+<tt>config.h</tt>) if they would not change. As a consequence, if not for the
+<tt>stamp-config_h</tt> file above, a header would always be considered obsolete
+once one of its prerequisites is modified. Note that touching <tt>config.h</tt>
+rather than <tt>stamp-config_h</tt> would defeat the point of not updating it
+in the first place, since the program files would need to be rebuilt.
+
+Once all the static prerequisites of a target have been rebuilt, <b>remake</b>
+checks if the target still needs to be built. If it was obsolete only because
+its dependencies needed to be rebuilt and none of them changed, the target is
+assumed to be up-to-date.
+
+\subsection sec-rules How are targets (re)built?
+
+There are two kinds of rules. If any of the targets or prerequisites contains
+a <tt>%</tt> character, the rule is said to be <em>generic</em>. All the
+targets of the rule shall then contain a single <tt>%</tt> character. All the
+other rules are said to be <em>specific</em>.
+
+A rule is said to <em>match</em> a given target:
+
+- if it is specific and the target appears inside its target list,
+- if it is generic and there is a way to replace the <tt>%</tt> character
+  from one of its targets so that it matches the given target.
+
+When <b>remake</b> tries to build a given target, it looks for a specific rule
+that matches it. If there is one and its script is nonempty, it uses it to
+rebuild the target.
+
+Otherwise, it looks for a generic rule that match the target. If there are
+several matching rules, it chooses the one with the shortest pattern (and if
+there are several ones, the earliest one). <b>remake</b> then looks for
+specific rules that match each target of the generic rule. All the
+prerequisites of these specific rules are added to those of the generic rule.
+The script of the generic rule is used to build the target.
+
+Example:
+
+@verbatim
+t%1 t2%: p1 p%2
+       commands building t%1 and t2%
+
+t2z: p4
+       commands building t2z
+
+ty1: p3
+
+# t2x is built by the first rule (which also builds tx1) and its prerequisites are p1, px2
+# t2y is built by the first rule (which also builds ty1) and its prerequisites are p1, py2, p3
+# t2z is built by the second rule and its prerequisite is p4
+@endverbatim
+
+The set of rules from <b>Remakefile</b> is ill-formed:
+
+- if any specific rule matching a target of the generic rule has a nonempty script,
+- if any target of the generic rule is matched by a generic rule with a shorter pattern.
+
+\section sec-compilation Compilation
+
+- On Linux, MacOSX, and BSD: <tt>g++ -o remake remake.cpp</tt>
+- On Windows: <tt>g++ -o remake.exe remake.cpp -lws2_32</tt>
+
+Installing <b>remake</b> is needed only if <b>Remakefile</b> does not
+specify the path to the executable for its recursive calls. Thanks to its
+single source file, <b>remake</b> can be shipped inside other packages and
+built at configuration time.
+
+\section sec-differences Differences with other build systems
+
+Differences with <b>make</b>:
+
+- Dynamic dependencies are supported.
+- For rules with multiple targets, the shell script is executed only once
+  and is assumed to build all the targets. There is no need for
+  convoluted rules that are robust enough for parallel builds. For generic
+  rules, this is similar to the behavior of pattern rules from <b>gmake</b>.
+- As with <b>redo</b>, only one shell is run when executing a script,
+  rather than one per script line. Note that the shells are run with
+  option <tt>-e</tt>, thus causing them to exit as soon as an error is
+  encountered.
+- The dependencies of generic rules (known as implicit rules in make lingo)
+  are not used to decide between several of them. <b>remake</b> does not
+  select one for which it could satisfy the dependencies.
+- Variables and built-in functions are expanded as they are encountered
+  during <b>Remakefile</b> parsing.
+
+Differences with <b>redo</b>:
+
+- As with <b>make</b>, it is possible to write the following kind of rules
+  in <b>remake</b>.
+@verbatim
+Remakefile: Remakefile.in ./config.status
+       ./config.status Remakefile
+@endverbatim
+- If a target is already built the first time <b>remake</b> runs, it still
+  uses the static prerequisites of rules mentioning it to check whether it
+  needs to be rebuilt. It does not assume it to be up-to-date. As with
+  <b>redo</b> though, if its obsolete status would be due to a dynamic
+  dependency, it will go unnoticed; it should be removed beforehand.
+- <b>remake</b> has almost no features: no checksum-based dependencies, no
+  compatibility with token servers, etc.
+
+Differences with both <b>make</b> and <b>redo</b>:
+
+- Multiple targets are supported.
+- When executing shell scripts, positional variables <tt>$1</tt>,
+  <tt>$2</tt>, etc, point to the target names of the rule obtained after
+  substituting <tt>%</tt>. No other variables are defined.
+
+\section sec-limitations Limitations
+
+- When the user or a script calls <b>remake</b>, the current working
+  directory should be the one containing <b>Remakefile</b> (and thus
+  <b>.remake</b> too).
+- Some cases of ill-formed rules are not caught by <b>remake</b> and can
+  thus lead to unpredictable behaviors.
+
+\section sec-links Links
+
+@see http://cr.yp.to/redo.html for the philosophy of <b>redo</b> and
+https://github.com/apenwarr/redo for an implementation and some comprehensive documentation.
+
+\section sec-licensing Licensing
+
+@author Guillaume Melquiond
+@version 0.5
+@date 2012-2013
+@copyright
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+\n
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+*/
+
+#ifdef _WIN32
+#define WIN32_LEAN_AND_MEAN
+#define WINDOWS
+#endif
+
+#include <fstream>
+#include <iostream>
+#include <list>
+#include <map>
+#include <set>
+#include <sstream>
+#include <string>
+#include <vector>
+#include <cassert>
+#include <cstdlib>
+#include <ctime>
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#ifdef __APPLE__
+#define MACOSX
+#endif
+
+#ifdef __linux__
+#define LINUX
+#endif
+
+#ifdef WINDOWS
+#include <windows.h>
+#include <winbase.h>
+#include <winsock2.h>
+#define pid_t HANDLE
+typedef SOCKET socket_t;
+#else
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <sys/wait.h>
+typedef int socket_t;
+enum { INVALID_SOCKET = -1 };
+#endif
+
+#if defined(WINDOWS) || defined(MACOSX)
+enum { MSG_NOSIGNAL = 0 };
+#endif
+
+typedef std::list<std::string> string_list;
+
+typedef std::set<std::string> string_set;
+
+/**
+ * Reference-counted shared object.
+ * @note The default constructor delays the creation of the object until it
+ *       is first dereferenced.
+ */
+template<class T>
+struct ref_ptr
+{
+       struct content
+       {
+               size_t cnt;
+               T val;
+               content(): cnt(1) {}
+               content(T const &t): cnt(1), val(t) {}
+       };
+       mutable content *ptr;
+       ref_ptr(): ptr(NULL) {}
+       ref_ptr(T const &t): ptr(new content(t)) {}
+       ref_ptr(ref_ptr const &p): ptr(p.ptr) { if (ptr) ++ptr->cnt; }
+       ~ref_ptr() { if (ptr && --ptr->cnt == 0) delete ptr; }
+       ref_ptr &operator=(ref_ptr const &p)
+       {
+               if (ptr == p.ptr) return *this;
+               if (ptr && --ptr->cnt == 0) delete ptr;
+               ptr = p.ptr;
+               if (ptr) ++ptr->cnt;
+               return *this;
+       }
+       T &operator*() const
+       {
+               if (!ptr) ptr = new content;
+               return ptr->val;
+       }
+       T *operator->() const { return &**this; }
+};
+
+struct dependency_t
+{
+       string_list targets;
+       string_set deps;
+};
+
+typedef std::map<std::string, ref_ptr<dependency_t> > dependency_map;
+
+typedef std::map<std::string, string_list> variable_map;
+
+/**
+ * Build status of a target.
+ */
+enum status_e
+{
+       Uptodate, ///< Target is up-to-date.
+       Todo,     ///< Target is missing or obsolete.
+       Recheck,  ///< Target has an obsolete dependency.
+       Running,  ///< Target is being rebuilt.
+       Remade,   ///< Target was successfully rebuilt.
+       Failed    ///< Build failed for target.
+};
+
+/**
+ * Build status of a target.
+ */
+struct status_t
+{
+       status_e status; ///< Actual status.
+       time_t last;     ///< Last-modified date.
+};
+
+typedef std::map<std::string, status_t> status_map;
+
+/**
+ * A rule loaded from Remakefile.
+ */
+struct rule_t
+{
+       string_list targets; ///< Files produced by this rule.
+       string_list deps;    ///< Files used for an implicit call to remake at the start of the script.
+       std::string script;  ///< Shell script for building the targets.
+};
+
+typedef std::list<rule_t> rule_list;
+
+typedef std::map<std::string, ref_ptr<rule_t> > rule_map;
+
+typedef std::map<int, string_list> job_targets_map;
+
+typedef std::map<int, variable_map> job_variables_map;
+
+typedef std::map<pid_t, int> pid_job_map;
+
+/**
+ * Client waiting for a request complete.
+ *
+ * There are two kinds of clients:
+ * - real clients, which are instances of remake created by built scripts,
+ * - pseudo clients, which are created by the server to build specific targets.
+ *
+ * Among pseudo clients, there are two categories:
+ * - original clients, which are created for the targets passed on the
+ *   command line by the user or for the initial regeneration of the rule file,
+ * - dependency clients, which are created to handle rules that have
+ *   explicit dependencies and thus to emulate a call to remake.
+ */
+struct client_t
+{
+       socket_t socket;     ///< Socket used to reply to the client (invalid for pseudo clients).
+       int job_id;          ///< Job for which the built script called remake and spawned the client (negative for original clients).
+       bool failed;         ///< Whether some targets failed in mode -k.
+       string_list pending; ///< Targets not yet started.
+       string_set running;  ///< Targets being built.
+       rule_t *delayed;     ///< Rule that implicitly created a dependency client, and which script has to be started on request completion.
+        variable_map variables; ///< Variable that are passed on the command line of a remake invocation.
+       client_t(): socket(INVALID_SOCKET), job_id(-1), failed(false), delayed(NULL) {}
+};
+
+typedef std::list<client_t> client_list;
+
+/**
+ * Map from variable names to their content.
+ */
+static variable_map variables;
+
+/**
+ * Precomputed variable assignments for shell usage.
+ */
+static std::string variable_block;
+
+/**
+ * Map from targets to their known dependencies.
+ */
+static dependency_map dependencies;
+
+/**
+ * Map from targets to their build status.
+ */
+static status_map status;
+
+/**
+ * Set of generic rules loaded from Remakefile.
+ */
+static rule_list generic_rules;
+
+/**
+ * Map from targets to specific rules loaded from Remakefile.
+ */
+static rule_map specific_rules;
+
+/**
+ * Map from jobs to targets being built.
+ */
+static job_targets_map job_targets;
+
+/**
+ * Map from jobs to job specific variables
+ */
+static job_variables_map job_variables;
+
+/**
+ * Map from jobs to shell pids.
+ */
+static pid_job_map job_pids;
+
+/**
+ * List of clients waiting for a request to complete.
+ * New clients are put to front, so that the build process is depth-first.
+ */
+static client_list clients;
+
+/**
+ * Maximum number of parallel jobs (non-positive if unbounded).
+ * Can be modified by the -j option.
+ */
+static int max_active_jobs = 1;
+
+/**
+ * Whether to keep building targets in case of failure.
+ * Can be modified by the -k option.
+ */
+static bool keep_going = false;
+
+/**
+ * Number of jobs currently running:
+ * - it increases when a process is created in #run_script,
+ * - it decreases when a completion message is received in #finalize_job.
+ *
+ * @note There might be some jobs running while #clients is empty.
+ *       Indeed, if a client requested two targets to be rebuilt, if they
+ *       are running concurrently, if one of them fails, the client will
+ *       get a failure notice and might terminate before the other target
+ *       finishes.
+ */
+static int running_jobs = 0;
+
+/**
+ * Number of jobs currently waiting for a build request to finish:
+ * - it increases when a build request is received in #accept_client
+ *   (since the client is presumably waiting for the reply),
+ * - it decreases when a reply is sent in #complete_request.
+ */
+static int waiting_jobs = 0;
+
+/**
+ * Global counter used to produce increasing job numbers.
+ * @see job_targets
+ */
+static int job_counter = 0;
+
+/**
+ * Socket on which the server listens for client request.
+ */
+static socket_t socket_fd;
+
+/**
+ * Whether the request of an original client failed.
+ */
+static bool build_failure;
+
+/**
+ * Name of the server socket in the file system.
+ */
+static char *socket_name;
+
+/**
+ * Name of the first target of the first specific rule, used for default run.
+ */
+static std::string first_target;
+
+/**
+ * Whether a short message should be displayed for each target.
+ */
+static bool show_targets = true;
+
+/**
+ * Whether script commands are echoed.
+ */
+static bool echo_scripts = false;
+
+static time_t now = time(NULL);
+
+static std::string working_dir;
+
+#ifndef WINDOWS
+static volatile sig_atomic_t got_SIGCHLD = 0;
+
+static void child_sig_handler(int)
+{
+       got_SIGCHLD = 1;
+}
+#endif
+
+struct log
+{
+       bool active, open;
+       int depth;
+       log(): active(false), open(false), depth(0)
+       {
+       }
+       std::ostream &operator()()
+       {
+               if (open) std::cerr << std::endl;
+               assert(depth >= 0);
+               std::cerr << std::string(depth * 2, ' ');
+               open = false;
+               return std::cerr;
+       }
+       std::ostream &operator()(bool o)
+       {
+               if (o && open) std::cerr << std::endl;
+               if (!o) --depth;
+               assert(depth >= 0);
+               if (o || !open) std::cerr << std::string(depth * 2, ' ');
+               if (o) ++depth;
+               open = o;
+               return std::cerr;
+       }
+};
+
+log debug;
+
+struct log_auto_close
+{
+       bool still_open;
+       log_auto_close(): still_open(true)
+       {
+       }
+       ~log_auto_close()
+       {
+               if (debug.active && still_open) debug(false) << "done\n";
+       }
+};
+
+#define DEBUG if (debug.active) debug()
+#define DEBUG_open log_auto_close auto_close; if (debug.active) debug(true)
+#define DEBUG_close if ((auto_close.still_open = false), debug.active) debug(false)
+
+/**
+ * Return the original string if it does not contain any special characters,
+ * a quoted and escaped string otherwise.
+ */
+static std::string escape_string(std::string const &s)
+{
+       char const *quoted_char = ",: '";
+       char const *escaped_char = "\"\\$!";
+       bool need_quotes = false;
+       size_t len = s.length(), nb = len;
+       for (size_t i = 0; i < len; ++i)
+       {
+               if (strchr(quoted_char, s[i])) need_quotes = true;
+               if (strchr(escaped_char, s[i])) ++nb;
+       }
+       if (nb != len) need_quotes = true;
+       if (!need_quotes) return s;
+       std::string t(nb + 2, '\\');
+       t[0] = '"';
+       for (size_t i = 0, j = 1; i < len; ++i, ++j)
+       {
+               if (strchr(escaped_char, s[i])) ++j;
+               t[j] = s[i];
+       }
+       t[nb + 1] = '"';
+       return t;
+}
+
+/**
+ * Initialize #working_dir.
+ */
+void init_working_dir()
+{
+       char buf[1024];
+       char *res = getcwd(buf, sizeof(buf));
+       if (!res)
+       {
+               perror("Failed to get working directory");
+               exit(EXIT_FAILURE);
+       }
+       working_dir = buf;
+}
+
+/**
+ * Normalize an absolute path with respect to the working directory.
+ * Paths outside the working subtree are left unchanged.
+ */
+static std::string normalize_abs(std::string const &s)
+{
+       size_t l = working_dir.length();
+       if (s.compare(0, l, working_dir)) return s;
+       size_t ll = s.length();
+       if (ll == l) return ".";
+       if (s[l] != '/')
+       {
+               size_t pos = s.rfind('/', l);
+               assert(pos != std::string::npos);
+               return s.substr(pos + 1);
+       }
+       if (ll == l + 1) return ".";
+       return s.substr(l + 1);
+}
+
+/**
+ * Normalize a target name.
+ */
+static std::string normalize(std::string const &s)
+{
+#ifdef WINDOWS
+       char const *delim = "/\\";
+#else
+       char delim = '/';
+#endif
+       size_t prev = 0, len = s.length();
+       size_t pos = s.find_first_of(delim);
+       if (pos == std::string::npos) return s;
+       bool absolute = pos == 0;
+       string_list l;
+       for (;;)
+       {
+               if (pos != prev)
+               {
+                       std::string n = s.substr(prev, pos - prev);
+                       if (n == "..")
+                       {
+                               if (!l.empty()) l.pop_back();
+                               else if (!absolute)
+                                       return normalize(working_dir + '/' + s);
+                       }
+                       else if (n != ".")
+                               l.push_back(n);
+               }
+               ++pos;
+               if (pos >= len) break;
+               prev = pos;
+               pos = s.find_first_of(delim, prev);
+               if (pos == std::string::npos) pos = len;
+       }
+       string_list::const_iterator i = l.begin(), i_end = l.end();
+       if (i == i_end) return absolute ? "/" : ".";
+       std::string n;
+       if (absolute) n.push_back('/');
+       n.append(*i);
+       for (++i; i != i_end; ++i)
+       {
+               n.push_back('/');
+               n.append(*i);
+       }
+       if (absolute) return normalize_abs(n);
+       return n;
+}
+
+/**
+ * Normalize the content of a list of targets.
+ */
+static void normalize_list(string_list &l)
+{
+       for (string_list::iterator i = l.begin(),
+            i_end = l.end(); i != i_end; ++i)
+       {
+               *i = normalize(*i);
+       }
+}
+
+/**
+ * Skip spaces.
+ */
+static void skip_spaces(std::istream &in)
+{
+       char c;
+       while (strchr(" \t", (c = in.get()))) {}
+       if (in.good()) in.putback(c);
+}
+
+/**
+ * Skip end of line.
+ */
+static void skip_eol(std::istream &in)
+{
+       char c;
+       while (strchr("\r\n", (c = in.get()))) {}
+       if (in.good()) in.putback(c);
+}
+
+enum token_e { Word, Eol, Eof, Colon, Equal, Dollar, Rightpar, Comma };
+
+/**
+ * Skip spaces and return the kind of the next token.
+ */
+static token_e next_token(std::istream &in)
+{
+       while (true)
+       {
+               skip_spaces(in);
+               char c = in.peek();
+               if (!in.good()) return Eof;
+               switch (c)
+               {
+               case ':': return Colon;
+               case ',': return Comma;
+               case '=': return Equal;
+               case '$': return Dollar;
+               case ')': return Rightpar;
+               case '\r':
+               case '\n':
+                       return Eol;
+               case '\\':
+                       in.ignore(1);
+                       c = in.peek();
+                       if (c != '\r' && c != '\n')
+                       {
+                               in.putback('\\');
+                               return Word;
+                       }
+                       skip_eol(in);
+                       break;
+               default:
+                       return Word;
+               }
+       }
+}
+
+/**
+ * Read a (possibly quoted) word.
+ */
+static std::string read_word(std::istream &in)
+{
+       int c = in.get();
+       std::string res;
+       if (!in.good()) return res;
+       char const *separators = " \t\r\n:$(),=\"";
+       bool quoted = c == '"';
+       if (!quoted)
+       {
+               if (strchr(separators, c))
+               {
+                       in.putback(c);
+                       return res;
+               }
+               res += c;
+       }
+       while (true)
+       {
+               c = in.get();
+               if (!in.good()) return res;
+               if (quoted)
+               {
+                       if (c == '\\')
+                               res += in.get();
+                       else if (c == '"')
+                               return res;
+                       else
+                               res += c;
+               }
+               else
+               {
+                       if (strchr(separators, c))
+                       {
+                               in.putback(c);
+                               return res;
+                       }
+                       res += c;
+               }
+       }
+}
+
+static string_list read_words(std::istream &in);
+
+/**
+ * Execute a built-in function @a name and append its result to @a dest.
+ */
+static void execute_function(std::istream &in, std::string const &name, string_list &dest)
+{
+       if (false)
+       {
+               error:
+               std::cerr << "Failed to load rules: syntax error" << std::endl;
+               exit(EXIT_FAILURE);
+       }
+       skip_spaces(in);
+       string_list fix = read_words(in);
+       if (next_token(in) != Comma) goto error;
+       in.ignore(1);
+       string_list names = read_words(in);
+       if (next_token(in) != Rightpar) goto error;
+       in.ignore(1);
+       size_t fixl = fix.size();
+       if (name == "addprefix")
+       {
+               for (string_list::const_iterator i = names.begin(),
+                    i_end = names.end(); i != i_end; ++i)
+               {
+                       if (!fixl)
+                       {
+                               dest.push_back(*i);
+                               continue;
+                       }
+                       string_list::const_iterator k = fix.begin();
+                       for (size_t j = 1; j != fixl; ++j)
+                       {
+                               dest.push_back(*k++);
+                       }
+                       dest.push_back(*k++ + *i);
+               }
+       }
+       else if (name == "addsuffix")
+       {
+               for (string_list::const_iterator i = names.begin(),
+                    i_end = names.end(); i != i_end; ++i)
+               {
+                       if (!fixl)
+                       {
+                               dest.push_back(*i);
+                               continue;
+                       }
+                       string_list::const_iterator k = fix.begin();
+                       dest.push_back(*i + *k++);
+                       for (size_t j = 1; j != fixl; ++j)
+                       {
+                               dest.push_back(*k++);
+                       }
+               }
+       }
+       else goto error;
+}
+
+/**
+ * Read a list of words, possibly executing functions.
+ */
+static string_list read_words(std::istream &in)
+{
+       if (false)
+       {
+               error:
+               std::cerr << "Failed to load rules: syntax error" << std::endl;
+               exit(EXIT_FAILURE);
+       }
+       string_list res;
+       while (true)
+       {
+               switch (next_token(in))
+               {
+               case Word:
+                       res.push_back(read_word(in));
+                       break;
+               case Dollar:
+               {
+                       in.ignore(1);
+                       if (in.get() != '(') goto error;
+                       std::string name = read_word(in);
+                       if (name.empty()) goto error;
+                       token_e tok = next_token(in);
+                       if (tok == Rightpar)
+                       {
+                               in.ignore(1);
+                               variable_map::const_iterator i = variables.find(name);
+                               if (i != variables.end())
+                                       res.insert(res.end(), i->second.begin(), i->second.end());
+                       }
+                       else execute_function(in, name, res);
+                       break;
+               }
+               default:
+                       return res;
+               }
+       }
+}
+
+/**
+ * Serialize a variable map.
+ */
+std::string serialize_variables(variable_map &vars, char sep = ' ')
+{
+  std::ostringstream buf;
+  for(variable_map::const_iterator i = vars.begin(),
+        i_end = vars.end(); i != i_end; ++i)
+  {
+    buf << i->first << '=';
+    bool first = true;
+    std::string val;
+    for (string_list::const_iterator j = i->second.begin(),
+           j_end = i->second.end(); j != j_end; ++j)
+    {
+      if (first) first = false;
+      else val += " ";
+      val += *j;
+    }
+    buf << escape_string(val) << sep;
+  }
+  std::string s = buf.str();
+  return s;
+}
+
+/**
+ * Load dependencies from @a in.
+ */
+static void load_dependencies(std::istream &in)
+{
+       while (!in.eof())
+       {
+               string_list targets = read_words(in);
+               if (targets.empty()) return;
+               DEBUG << "reading dependencies of target " << targets.front() << std::endl;
+               if (in.get() != ':')
+               {
+                       std::cerr << "Failed to load database" << std::endl;
+                       exit(EXIT_FAILURE);
+               }
+               ref_ptr<dependency_t> dep;
+               dep->targets = targets;
+               string_list d = read_words(in);
+               dep->deps.insert(d.begin(), d.end());
+               for (string_list::const_iterator i = targets.begin(),
+                    i_end = targets.end(); i != i_end; ++i)
+               {
+                       dependencies[*i] = dep;
+               }
+               skip_eol(in);
+       }
+}
+
+/**
+ * Load known dependencies from file <tt>.remake</tt>.
+ */
+static void load_dependencies()
+{
+       DEBUG_open << "Loading database... ";
+       std::ifstream in(".remake");
+       if (!in.good())
+       {
+               DEBUG_close << "not found\n";
+               return;
+       }
+       load_dependencies(in);
+}
+
+/**
+ * Read a rule starting with target @a first, if nonempty.
+ * Store into #generic_rules or #specific_rules depending on its genericity.
+ */
+static void load_rule(std::istream &in, std::string const &first)
+{
+       DEBUG_open << "Reading rule for target " << first << "... ";
+       if (false)
+       {
+               error:
+               DEBUG_close << "failed\n";
+               std::cerr << "Failed to load rules: syntax error" << std::endl;
+               exit(EXIT_FAILURE);
+       }
+       rule_t rule;
+
+       // Read targets and check genericity.
+       string_list targets = read_words(in);
+       if (!first.empty()) targets.push_front(first);
+       else if (targets.empty()) goto error;
+       else DEBUG << "actual target: " << targets.front() << std::endl;
+       bool generic = false;
+       normalize_list(targets);
+       for (string_list::const_iterator i = targets.begin(),
+            i_end = targets.end(); i != i_end; ++i)
+       {
+               if (i->empty()) goto error;
+               if ((i->find('%') != std::string::npos) != generic)
+               {
+                       if (i == targets.begin()) generic = true;
+                       else goto error;
+               }
+       }
+       std::swap(rule.targets, targets);
+       skip_spaces(in);
+       if (in.get() != ':') goto error;
+
+       // Read dependencies.
+       rule.deps = read_words(in);
+       normalize_list(rule.deps);
+       skip_spaces(in);
+       char c = in.get();
+       if (c != '\r' && c != '\n') goto error;
+       skip_eol(in);
+
+       // Read script.
+       std::ostringstream buf;
+       while (true)
+       {
+               char c = in.get();
+               if (!in.good()) break;
+               if (c == '\t' || c == ' ')
+               {
+                       in.get(*buf.rdbuf());
+                       if (in.fail() && !in.eof()) in.clear();
+               }
+               else if (c == '\r' || c == '\n')
+                       buf << c;
+               else
+               {
+                       in.putback(c);
+                       break;
+               }
+       }
+       rule.script = buf.str();
+
+       // Add generic rules to the correct set.
+       if (generic)
+       {
+               generic_rules.push_back(rule);
+               return;
+       }
+
+       // Rules with a nonempty script lump all their targets in the same
+       // dependency set, while other rules behave as if they had been
+       // replicated for each of their targets.
+       if (!rule.script.empty())
+       {
+               ref_ptr<dependency_t> dep;
+               dep->targets = rule.targets;
+               dep->deps.insert(rule.deps.begin(), rule.deps.end());
+               for (string_list::const_iterator i = rule.targets.begin(),
+                    i_end = rule.targets.end(); i != i_end; ++i)
+               {
+                       ref_ptr<dependency_t> &d = dependencies[*i];
+                       dep->deps.insert(d->deps.begin(), d->deps.end());
+                       d = dep;
+               }
+       }
+       else
+       {
+               for (string_list::const_iterator i = rule.targets.begin(),
+                    i_end = rule.targets.end(); i != i_end; ++i)
+               {
+                       ref_ptr<dependency_t> &dep = dependencies[*i];
+                       if (dep->targets.empty()) dep->targets.push_back(*i);
+                       dep->deps.insert(rule.deps.begin(), rule.deps.end());
+               }
+       }
+
+       if (first_target.empty())
+               first_target = rule.targets.front();
+
+       ref_ptr<rule_t> r(rule);
+       for (string_list::const_iterator i = rule.targets.begin(),
+            i_end = rule.targets.end(); i != i_end; ++i)
+       {
+               std::pair<rule_map::iterator,bool> j =
+                       specific_rules.insert(std::make_pair(*i, r));
+               if (j.second) continue;
+               std::cerr << "Failed to load rules: " << *i
+                       << " cannot be the target of several rules" << std::endl;
+               exit(EXIT_FAILURE);
+       }
+}
+
+/**
+ * Save all the dependencies in file <tt>.remake</tt>.
+ */
+static void save_dependencies()
+{
+       DEBUG_open << "Saving database... ";
+       std::ofstream db(".remake");
+       while (!dependencies.empty())
+       {
+               ref_ptr<dependency_t> dep = dependencies.begin()->second;
+               for (string_list::const_iterator i = dep->targets.begin(),
+                    i_end = dep->targets.end(); i != i_end; ++i)
+               {
+                       db << escape_string(*i) << ' ';
+                       dependencies.erase(*i);
+               }
+               db << ':';
+               for (string_set::const_iterator i = dep->deps.begin(),
+                    i_end = dep->deps.end(); i != i_end; ++i)
+               {
+                       db << ' ' << escape_string(*i);
+               }
+               db << std::endl;
+       }
+}
+
+/**
+ * Load rules.
+ * If some rules have dependencies and non-generic targets, add these
+ * dependencies to the targets.
+ */
+static void load_rules()
+{
+       DEBUG_open << "Loading rules... ";
+       if (false)
+       {
+               error:
+               std::cerr << "Failed to load rules: syntax error" << std::endl;
+               exit(EXIT_FAILURE);
+       }
+       std::ifstream in("Remakefile");
+       if (!in.good())
+       {
+               std::cerr << "Failed to load rules: no Remakefile found" << std::endl;
+               exit(EXIT_FAILURE);
+       }
+       skip_eol(in);
+
+       // Read rules
+       while (in.good())
+       {
+               char c = in.peek();
+               if (c == '#')
+               {
+                       while (in.get() != '\n') {}
+                       skip_eol(in);
+                       continue;
+               }
+               if (c == ' ' || c == '\t') goto error;
+               token_e tok = next_token(in);
+               if (tok == Word)
+               {
+                       std::string name = read_word(in);
+                       if (name.empty()) goto error;
+                       if (next_token(in) == Equal)
+                       {
+                               in.ignore(1);
+                               DEBUG << "Assignment to variable " << name << std::endl;
+                               variables[name] = read_words(in);
+                               skip_eol(in);
+                       }
+                       else load_rule(in, name);
+               }
+               else if (tok == Dollar)
+                       load_rule(in, std::string());
+               else goto error;
+       }
+
+       // Generate script for variable assignment
+       std::ostringstream buf;
+       for (variable_map::const_iterator i = variables.begin(),
+            i_end = variables.end(); i != i_end; ++i)
+       {
+               std::ostringstream var;
+               bool first = true;
+               for (string_list::const_iterator j = i->second.begin(),
+                    j_end = i->second.end(); j != j_end; ++j)
+               {
+                       if (first) first = false;
+                       else var << ' ';
+                       var << *j;
+               }
+               buf << i->first << '=' << escape_string(var.str()) << std::endl;
+       }
+       variable_block = buf.str();
+}
+
+/**
+ * Substitute a pattern into a list of strings.
+ */
+static void substitute_pattern(std::string const &pat, string_list const &src, string_list &dst)
+{
+       for (string_list::const_iterator i = src.begin(),
+            i_end = src.end(); i != i_end; ++i)
+       {
+               size_t pos = i->find('%');
+               if (pos == std::string::npos)dst.push_back(*i);
+               else dst.push_back(i->substr(0, pos) + pat + i->substr(pos + 1));
+       }
+}
+
+/**
+ * Find a generic rule matching @a target:
+ * - the one leading to shorter matches has priority,
+ * - among equivalent rules, the earliest one has priority.
+ */
+static rule_t find_generic_rule(std::string const &target)
+{
+       size_t tlen = target.length(), plen = tlen + 1;
+       rule_t rule;
+       for (rule_list::const_iterator i = generic_rules.begin(),
+            i_end = generic_rules.end(); i != i_end; ++i)
+       {
+               for (string_list::const_iterator j = i->targets.begin(),
+                    j_end = i->targets.end(); j != j_end; ++j)
+               {
+                       size_t len = j->length();
+                       if (tlen < len) continue;
+                       if (plen <= tlen - (len - 1)) continue;
+                       size_t pos = j->find('%');
+                       if (pos == std::string::npos) continue;
+                       size_t len2 = len - (pos + 1);
+                       if (j->compare(0, pos, target, 0, pos) ||
+                           j->compare(pos + 1, len2, target, tlen - len2, len2))
+                               continue;
+                       plen = tlen - (len - 1);
+                       std::string pat = target.substr(pos, plen);
+                       rule = rule_t();
+                       rule.script = i->script;
+                       substitute_pattern(pat, i->targets, rule.targets);
+                       substitute_pattern(pat, i->deps, rule.deps);
+                       break;
+               }
+       }
+       return rule;
+}
+
+/**
+ * Find a specific rule matching @a target. Return a generic one otherwise.
+ * If there is both a specific rule with an empty script and a generic rule, the
+ * generic one is returned after adding the dependencies of the specific one.
+ */
+static rule_t find_rule(std::string const &target)
+{
+       rule_map::const_iterator i = specific_rules.find(target),
+               i_end = specific_rules.end();
+       // If there is a specific rule with a script, return it.
+       if (i != i_end && !i->second->script.empty()) return *i->second;
+       rule_t grule = find_generic_rule(target);
+       // If there is no generic rule, return the specific rule (no script), if any.
+       if (grule.targets.empty())
+       {
+               if (i != i_end) return *i->second;
+               return grule;
+       }
+       // Optimize the lookup when there is only one target (alread looked up).
+       if (grule.targets.size() == 1)
+       {
+               if (i != i_end)
+                       grule.deps.insert(grule.deps.end(),
+                               i->second->deps.begin(), i->second->deps.end());
+               return grule;
+       }
+       // Add the dependencies of the specific rules of every target to the
+       // generic rule. If any of those rules has a nonempty script, error out.
+       for (string_list::const_iterator j = grule.targets.begin(),
+            j_end = grule.targets.end(); j != j_end; ++j)
+       {
+               i = specific_rules.find(*j);
+               if (i == i_end) continue;
+               if (!i->second->script.empty()) return rule_t();
+               grule.deps.insert(grule.deps.end(),
+                       i->second->deps.begin(), i->second->deps.end());
+       }
+       return grule;
+}
+
+/**
+ * Compute and memoize the status of @a target:
+ * - if the file does not exist, the target is obsolete,
+ * - if any dependency is obsolete or younger than the file, it is obsolete,
+ * - otherwise it is up-to-date.
+ *
+ * @note With multiple targets, they all share the same status. (If one is
+ *       obsolete, they all are.) For the second rule above, the latest target
+ *       is chosen, not the oldest!
+ */
+static status_t const &get_status(std::string const &target)
+{
+       std::pair<status_map::iterator,bool> i =
+               status.insert(std::make_pair(target, status_t()));
+       status_t &ts = i.first->second;
+       if (!i.second) return ts;
+       DEBUG_open << "Checking status of " << target << "... ";
+       dependency_map::const_iterator j = dependencies.find(target);
+       if (j == dependencies.end())
+       {
+               struct stat s;
+               if (stat(target.c_str(), &s) != 0)
+               {
+                       DEBUG_close << "missing\n";
+                       ts.status = Todo;
+                       ts.last = 0;
+                       return ts;
+               }
+               DEBUG_close << "up-to-date\n";
+               ts.status = Uptodate;
+               ts.last = s.st_mtime;
+               return ts;
+       }
+       dependency_t const &dep = *j->second;
+       status_e st = Uptodate;
+       time_t latest = 0;
+       for (string_list::const_iterator k = dep.targets.begin(),
+            k_end = dep.targets.end(); k != k_end; ++k)
+       {
+               struct stat s;
+               if (stat(k->c_str(), &s) != 0)
+               {
+                       if (st == Uptodate) DEBUG_close << *k << " missing\n";
+                       s.st_mtime = 0;
+                       st = Todo;
+               }
+               status[*k].last = s.st_mtime;
+               if (s.st_mtime > latest) latest = s.st_mtime;
+       }
+       if (st == Todo) goto update;
+       for (string_set::const_iterator k = dep.deps.begin(),
+            k_end = dep.deps.end(); k != k_end; ++k)
+       {
+               status_t const &ts_ = get_status(*k);
+               if (latest < ts_.last)
+               {
+                       DEBUG_close << "older than " << *k << std::endl;
+                       st = Todo;
+                       goto update;
+               }
+               if (ts_.status == Uptodate) continue;
+               if (st == Uptodate)
+                       DEBUG << "obsolete dependency " << *k << std::endl;
+               st = Recheck;
+       }
+       if (st == Uptodate) DEBUG_close << "all siblings up-to-date\n";
+       update:
+       for (string_list::const_iterator k = dep.targets.begin(),
+            k_end = dep.targets.end(); k != k_end; ++k)
+       {
+               status[*k].status = st;
+       }
+       return ts;
+}
+
+/**
+ * Change the status of @a target to #Remade or #Uptodate depending on whether
+ * its modification time changed.
+ */
+static void update_status(std::string const &target)
+{
+       DEBUG_open << "Rechecking status of " << target << "... ";
+       status_map::iterator i = status.find(target);
+       assert (i != status.end());
+       status_t &ts = i->second;
+       ts.status = Remade;
+       if (ts.last >= now)
+       {
+               DEBUG_close << "possibly remade\n";
+               return;
+       }
+       struct stat s;
+       if (stat(target.c_str(), &s) != 0)
+       {
+               DEBUG_close << "missing\n";
+               ts.last = 0;
+       }
+       else if (s.st_mtime != ts.last)
+       {
+               DEBUG_close << "remade\n";
+               ts.last = s.st_mtime;
+       }
+       else
+       {
+               DEBUG_close << "unchanged\n";
+               ts.status = Uptodate;
+       }
+}
+
+/**
+ * Check if all the prerequisites of @a target ended being up-to-date.
+ */
+static bool still_need_rebuild(std::string const &target)
+{
+       DEBUG_open << "Rechecking obsoleteness of " << target << "... ";
+       status_map::const_iterator i = status.find(target);
+       assert (i != status.end());
+       if (i->second.status != Recheck) return true;
+       dependency_map::const_iterator j = dependencies.find(target);
+       assert(j != dependencies.end());
+       dependency_t const &dep = *j->second;
+       for (string_set::const_iterator k = dep.deps.begin(),
+            k_end = dep.deps.end(); k != k_end; ++k)
+       {
+               if (status[*k].status != Uptodate) return true;
+       }
+       for (string_list::const_iterator k = dep.targets.begin(),
+            k_end = dep.targets.end(); k != k_end; ++k)
+       {
+               status[*k].status = Uptodate;
+       }
+       DEBUG_close << "no longer obsolete\n";
+       return false;
+}
+
+/**
+ * Handle job completion.
+ */
+static void complete_job(int job_id, bool success)
+{
+       DEBUG_open << "Completing job " << job_id << "... ";
+       job_targets_map::iterator i = job_targets.find(job_id);
+       assert(i != job_targets.end());
+       string_list const &targets = i->second;
+       if (success)
+       {
+               for (string_list::const_iterator j = targets.begin(),
+                    j_end = targets.end(); j != j_end; ++j)
+               {
+                       update_status(*j);
+               }
+       }
+       else
+       {
+               DEBUG_close << "failed\n";
+               std::cerr << "Failed to build";
+               for (string_list::const_iterator j = targets.begin(),
+                    j_end = targets.end(); j != j_end; ++j)
+               {
+                       status[*j].status = Failed;
+                       std::cerr << ' ' << *j;
+                       remove(j->c_str());
+               }
+               std::cerr << std::endl;
+       }
+       job_targets.erase(i);
+}
+
+/**
+ * Execute the script from @a rule.
+ */
+static bool run_script(int job_id, rule_t const &rule)
+{
+       if (show_targets)
+       {
+               std::cout << "Building";
+               for (string_list::const_iterator i = rule.targets.begin(),
+                    i_end = rule.targets.end(); i != i_end; ++i)
+               {
+                       std::cout << ' ' << *i;
+               }
+               std::cout << std::endl;
+       }
+
+       ref_ptr<dependency_t> dep;
+       dep->targets = rule.targets;
+       dep->deps.insert(rule.deps.begin(), rule.deps.end());
+       for (string_list::const_iterator i = rule.targets.begin(),
+            i_end = rule.targets.end(); i != i_end; ++i)
+       {
+               dependencies[*i] = dep;
+       }
+
+       DEBUG_open << "Starting script for job " << job_id << " with variables ("
+                   << (job_id >= 0 ? serialize_variables(job_variables[job_id], ' ') : "")
+                   << ") ... ";
+#ifdef WINDOWS
+       HANDLE pfd[2];
+       if (false)
+       {
+               error2:
+               CloseHandle(pfd[0]);
+               CloseHandle(pfd[1]);
+               error:
+               DEBUG_close << "failed\n";
+               complete_job(job_id, false);
+               return false;
+       }
+       if (!CreatePipe(&pfd[0], &pfd[1], NULL, 0))
+               goto error;
+       if (!SetHandleInformation(pfd[0], HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT))
+               goto error2;
+       STARTUPINFO si;
+       ZeroMemory(&si, sizeof(STARTUPINFO));
+       si.cb = sizeof(STARTUPINFO);
+       si.hStdError = GetStdHandle(STD_ERROR_HANDLE);
+       si.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
+       si.hStdInput = pfd[0];
+       si.dwFlags |= STARTF_USESTDHANDLES;
+       PROCESS_INFORMATION pi;
+       ZeroMemory(&pi, sizeof(PROCESS_INFORMATION));
+       std::ostringstream buf;
+       buf << job_id;
+       if (!SetEnvironmentVariable("REMAKE_JOB_ID", buf.str().c_str()))
+               goto error2;
+       std::ostringstream argv;
+       argv << "SH.EXE -e -s";
+       if (echo_scripts) argv << " -v";
+       for (string_list::const_iterator i = rule.targets.begin(),
+            i_end = rule.targets.end(); i != i_end; ++i)
+       {
+               argv << " \"" << escape_string(*i) << '"';
+       }
+       if (!CreateProcess(NULL, (char *)argv.str().c_str(), NULL, NULL,
+           true, 0, NULL, NULL, &si, &pi))
+       {
+               goto error2;
+       }
+       CloseHandle(pi.hThread);
+       std::string script = variable_block
+                + (job_id >= 0 ? serialize_variables(job_variables[job_id], '\n') : "")
+                + rule.script;
+       DWORD len = script.length(), wlen;
+       if (!WriteFile(pfd[1], script.c_str(), len, &wlen, NULL) || wlen < len)
+               std::cerr << "Unexpected failure while sending script to shell" << std::endl;
+       CloseHandle(pfd[0]);
+       CloseHandle(pfd[1]);
+       ++running_jobs;
+       job_pids[pi.hProcess] = job_id;
+       return true;
+#else
+       int pfd[2];
+       if (false)
+       {
+               error2:
+               close(pfd[0]);
+               close(pfd[1]);
+               error:
+               DEBUG_close << "failed\n";
+               complete_job(job_id, false);
+               return false;
+       }
+       if (pipe(pfd) == -1)
+               goto error;
+       if (pid_t pid = fork())
+       {
+               if (pid == -1) goto error2;
+               std::string script = variable_block
+                        + (job_id >= 0 ? serialize_variables(job_variables[job_id], '\n') : "")
+                        + rule.script;
+               ssize_t len = script.length();
+               if (write(pfd[1], script.c_str(), len) < len)
+                       std::cerr << "Unexpected failure while sending script to shell" << std::endl;
+               close(pfd[0]);
+               close(pfd[1]);
+               ++running_jobs;
+               job_pids[pid] = job_id;
+               return true;
+       }
+       // Child process starts here.
+       std::ostringstream buf;
+       buf << job_id;
+       if (setenv("REMAKE_JOB_ID", buf.str().c_str(), 1))
+               _exit(EXIT_FAILURE);
+       int num = echo_scripts ? 4 : 3;
+       char const **argv = new char const *[num + rule.targets.size() + 1];
+       argv[0] = "sh";
+       argv[1] = "-e";
+       argv[2] = "-s";
+       if (echo_scripts) argv[3] = "-v";
+       for (string_list::const_iterator i = rule.targets.begin(),
+            i_end = rule.targets.end(); i != i_end; ++i, ++num)
+       {
+               argv[num] = i->c_str();
+       }
+       argv[num] = NULL;
+       if (pfd[0] != 0)
+       {
+               dup2(pfd[0], 0);
+               close(pfd[0]);
+       }
+       close(pfd[1]);
+       execv("/bin/sh", (char **)argv);
+       _exit(EXIT_FAILURE);
+#endif
+}
+
+/**
+ * Create a job for @a target according to the loaded rules.
+ * Mark all the targets from the rule as running and reset their dependencies.
+ * If the rule has dependencies, create a new client to build them just
+ * before @a current, and change @a current so that it points to it.
+ */
+static bool start(std::string const &target, client_list::iterator &current)
+{
+       DEBUG_open << "Starting job " << job_counter << " for " << target << "... ";
+       rule_t rule = find_rule(target);
+       if (rule.targets.empty())
+       {
+               status[target].status = Failed;
+               DEBUG_close << "failed\n";
+               std::cerr << "No rule for building " << target << std::endl;
+               return false;
+       }
+       for (string_list::const_iterator i = rule.targets.begin(),
+            i_end = rule.targets.end(); i != i_end; ++i)
+       {
+               status[*i].status = Running;
+       }
+       int job_id = job_counter++;
+       job_targets[job_id] = rule.targets;
+        DEBUG << "Setting variables of job: " << job_id << " (spawn by: "
+              << current->job_id << ") to "
+              << serialize_variables(current->variables, ' ') << std::endl;
+        job_variables[job_id] = current->variables;
+       if (!rule.deps.empty())
+       {
+                DEBUG << "Current client has job_id: " << job_id
+                      << " and variables " << serialize_variables(current->variables, ' ')
+                      << std::endl;
+                client_t dep_client = client_t();
+                dep_client.variables = current->variables;
+               current = clients.insert(current, dep_client);
+               current->job_id = job_id;
+               current->pending = rule.deps;
+               current->delayed = new rule_t(rule);
+               return true;
+       }
+       return run_script(job_id, rule);
+}
+
+/**
+ * Send a reply to a client then remove it.
+ * If the client was a dependency client, start the actual script.
+ */
+static void complete_request(client_t &client, bool success)
+{
+       DEBUG_open << "Completing request from client of job " << client.job_id << "... ";
+       if (client.delayed)
+       {
+               assert(client.socket == INVALID_SOCKET);
+               if (success)
+               {
+                       if (still_need_rebuild(client.delayed->targets.front()))
+                               run_script(client.job_id, *client.delayed);
+                       else complete_job(client.job_id, true);
+               }
+               else complete_job(client.job_id, false);
+               delete client.delayed;
+       }
+       else if (client.socket != INVALID_SOCKET)
+       {
+               char res = success ? 1 : 0;
+               send(client.socket, &res, 1, 0);
+       #ifdef WINDOWS
+               closesocket(client.socket);
+       #else
+               close(client.socket);
+       #endif
+               --waiting_jobs;
+       }
+
+       if (client.job_id < 0 && !success) build_failure = true;
+}
+
+/**
+ * Return whether there are slots for starting new jobs.
+ */
+static bool has_free_slots()
+{
+       if (max_active_jobs <= 0) return true;
+       return running_jobs - waiting_jobs < max_active_jobs;
+}
+
+/**
+ * Update clients as long as there are free slots:
+ * - check for running targets that have finished,
+ * - start as many pending targets as allowed,
+ * - complete the request if there are neither running nor pending targets
+ *   left or if any of them failed.
+ */
+static void update_clients()
+{
+       DEBUG_open << "Updating clients... ";
+       for (client_list::iterator i = clients.begin(), i_next = i,
+            i_end = clients.end(); i != i_end && has_free_slots(); i = i_next)
+       {
+               ++i_next;
+               DEBUG_open << "Handling client from job " << i->job_id << "... ";
+               if (false)
+               {
+                       failed:
+                       complete_request(*i, false);
+                       clients.erase(i);
+                       DEBUG_close << "failed\n";
+                       continue;
+               }
+
+               // Remove running targets that have finished.
+               for (string_set::iterator j = i->running.begin(), j_next = j,
+                    j_end = i->running.end(); j != j_end; j = j_next)
+               {
+                       ++j_next;
+                       status_map::const_iterator k = status.find(*j);
+                       assert(k != status.end());
+                       switch (k->second.status)
+                       {
+                       case Running:
+                               break;
+                       case Failed:
+                               if (!keep_going) goto failed;
+                               i->failed = true;
+                               // no break
+                       case Uptodate:
+                       case Remade:
+                               i->running.erase(j);
+                               break;
+                       case Recheck:
+                       case Todo:
+                               assert(false);
+                       }
+               }
+
+               // Start pending targets.
+               while (!i->pending.empty())
+               {
+                       std::string target = i->pending.front();
+                       i->pending.pop_front();
+                       switch (get_status(target).status)
+                       {
+                       case Running:
+                               i->running.insert(target);
+                               break;
+                       case Failed:
+                               pending_failed:
+                               if (!keep_going) goto failed;
+                               i->failed = true;
+                               // no break
+                       case Uptodate:
+                       case Remade:
+                               break;
+                       case Recheck:
+                       case Todo:
+                               client_list::iterator j = i;
+                               if (!start(target, i)) goto pending_failed;
+                               j->running.insert(target);
+                               if (!has_free_slots()) return;
+                               // Job start might insert a dependency client.
+                               i_next = i;
+                               ++i_next;
+                               break;
+                       }
+               }
+
+               // Try to complete request.
+               // (This might start a new job if it was a dependency client.)
+               if (i->running.empty())
+               {
+                       if (i->failed) goto failed;
+                       complete_request(*i, true);
+                       clients.erase(i);
+                       DEBUG_close << "finished\n";
+               }
+       }
+}
+
+/**
+ * Create a named unix socket that listens for build requests. Also set
+ * the REMAKE_SOCKET environment variable that will be inherited by all
+ * the job scripts.
+ */
+static void create_server()
+{
+       if (false)
+       {
+               error:
+               perror("Failed to create server");
+#ifndef WINDOWS
+               error2:
+#endif
+               exit(EXIT_FAILURE);
+       }
+       DEBUG_open << "Creating server... ";
+
+#ifdef WINDOWS
+       // Prepare a windows socket.
+       struct sockaddr_in socket_addr;
+       socket_addr.sin_family = AF_INET;
+       socket_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
+       socket_addr.sin_port = 0;
+
+       // Create and listen to the socket.
+       socket_fd = socket(AF_INET, SOCK_STREAM, 0);
+       if (socket_fd < 0) goto error;
+       if (!SetHandleInformation((HANDLE)socket_fd, HANDLE_FLAG_INHERIT, 0))
+               goto error;
+       if (bind(socket_fd, (struct sockaddr *)&socket_addr, sizeof(sockaddr_in)))
+               goto error;
+       int len = sizeof(sockaddr_in);
+       if (getsockname(socket_fd, (struct sockaddr *)&socket_addr, &len))
+               goto error;
+       std::ostringstream buf;
+       buf << socket_addr.sin_port;
+       if (!SetEnvironmentVariable("REMAKE_SOCKET", buf.str().c_str()))
+               goto error;
+       if (listen(socket_fd, 1000)) goto error;
+#else
+       // Set a handler for SIGCHLD then block the signal (unblocked during select).
+       sigset_t sigmask;
+       sigemptyset(&sigmask);
+       sigaddset(&sigmask, SIGCHLD);
+       if (sigprocmask(SIG_BLOCK, &sigmask, NULL) == -1) goto error;
+       struct sigaction sa;
+       sa.sa_flags = 0;
+       sa.sa_handler = &child_sig_handler;
+       sigemptyset(&sa.sa_mask);
+       if (sigaction(SIGCHLD, &sa, NULL) == -1) goto error;
+
+       // Prepare a named unix socket in temporary directory.
+       socket_name = tempnam(NULL, "rmk-");
+       if (!socket_name) goto error2;
+       struct sockaddr_un socket_addr;
+       size_t len = strlen(socket_name);
+       if (len >= sizeof(socket_addr.sun_path) - 1) goto error2;
+       socket_addr.sun_family = AF_UNIX;
+       strcpy(socket_addr.sun_path, socket_name);
+       len += sizeof(socket_addr.sun_family);
+       if (setenv("REMAKE_SOCKET", socket_name, 1)) goto error;
+
+       // Create and listen to the socket.
+#ifdef LINUX
+       socket_fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
+       if (socket_fd < 0) goto error;
+#else
+       socket_fd = socket(AF_UNIX, SOCK_STREAM, 0);
+       if (socket_fd < 0) goto error;
+       if (fcntl(socket_fd, F_SETFD, FD_CLOEXEC) < 0) goto error;
+#endif
+       if (bind(socket_fd, (struct sockaddr *)&socket_addr, len))
+               goto error;
+       if (listen(socket_fd, 1000)) goto error;
+#endif
+}
+
+/**
+ * Accept a connection from a client, get the job it spawned from,
+ * get the targets, and mark them as dependencies of the job targets.
+ */
+void accept_client()
+{
+       DEBUG_open << "Handling client request... ";
+
+       // Accept connection.
+#ifdef WINDOWS
+       socket_t fd = accept(socket_fd, NULL, NULL);
+       if (fd == INVALID_SOCKET) return;
+       if (!SetHandleInformation((HANDLE)fd, HANDLE_FLAG_INHERIT, 0))
+       {
+               error2:
+               std::cerr << "Unexpected failure while setting connection with client" << std::endl;
+               closesocket(fd);
+               return;
+       }
+       // WSAEventSelect puts sockets into nonblocking mode, so disable it here.
+       u_long nbio = 0;
+       if (ioctlsocket(fd, FIONBIO, &nbio)) goto error2;
+#elif defined(LINUX)
+       int fd = accept4(socket_fd, NULL, NULL, SOCK_CLOEXEC);
+       if (fd < 0) return;
+#else
+       int fd = accept(socket_fd, NULL, NULL);
+       if (fd < 0) return;
+       if (fcntl(fd, F_SETFD, FD_CLOEXEC) < 0) return;
+#endif
+       clients.push_front(client_t());
+       client_list::iterator proc = clients.begin();
+
+       if (false)
+       {
+               error:
+               DEBUG_close << "failed\n";
+               std::cerr << "Received an ill-formed client message" << std::endl;
+       #ifdef WINDOWS
+               closesocket(fd);
+       #else
+               close(fd);
+       #endif
+               clients.erase(proc);
+               return;
+       }
+
+       // Receive message. Stop when encountering two nuls in a row.
+       std::vector<char> buf;
+       size_t len = 0;
+       while (len < sizeof(int) + 2 || buf[len - 1] || buf[len - 2])
+       {
+               buf.resize(len + 1024);
+               ssize_t l = recv(fd, &buf[0] + len, 1024, 0);
+               if (l <= 0) goto error;
+               len += l;
+       }
+
+       // Parse job that spawned the client.
+       int job_id;
+       memcpy(&job_id, &buf[0], sizeof(int));
+       proc->socket = fd;
+       proc->job_id = job_id;
+       job_targets_map::const_iterator i = job_targets.find(job_id);
+       if (i == job_targets.end()) goto error;
+       DEBUG << "receiving request from job " << job_id << std::endl;
+
+       // Parse the targets and mark them as dependencies from the job targets.
+       dependency_t &dep = *dependencies[job_targets[job_id].front()];
+       char const *p = &buf[0] + sizeof(int);
+       while (true)
+       {
+               len = strlen(p);
+               if (len == 1 && p[0] == 1)
+               {
+                        //Finished parsing targets.
+                        p += 2;
+                       ++waiting_jobs;
+                        break;
+               }
+               std::string target(p, p + len);
+               DEBUG << "adding dependency " << target << " to job " << job_id << std::endl;
+               proc->pending.push_back(target);
+               dep.deps.insert(target);
+               p += len + 1;
+       }
+
+        while (true)
+        {
+               len = strlen(p);
+                if (len == 0) return;
+               std::string line(p, p + len);
+                std::istringstream in (line);
+                std::string name = read_word(in);
+                if (next_token(in) != Equal) {
+                  std::cerr << '\'' << line << "'" << std::endl;
+                  goto error;
+                }
+                in.ignore(1); // ignore =.
+                DEBUG << "adding variable " << line << " to job " << job_id << std::endl;
+                string_list l = read_words(in);
+                proc->variables[name] = l;
+               p += len + 1;
+        }
+
+
+}
+
+/**
+ * Loop until all the jobs have finished.
+ */
+void server_loop()
+{
+       while (true)
+       {
+               update_clients();
+               if (running_jobs == 0)
+               {
+                       assert(clients.empty());
+                       break;
+               }
+               DEBUG_open << "Handling events... ";
+       #ifdef WINDOWS
+               size_t len = job_pids.size() + 1;
+               HANDLE h[len];
+               int num = 0;
+               for (pid_job_map::const_iterator i = job_pids.begin(),
+                    i_end = job_pids.end(); i != i_end; ++i, ++num)
+               {
+                       h[num] = i->first;
+               }
+               WSAEVENT aev = WSACreateEvent();
+               h[num] = aev;
+               WSAEventSelect(socket_fd, aev, FD_ACCEPT);
+               DWORD w = WaitForMultipleObjects(len, h, false, INFINITE);
+               WSAEventSelect(socket_fd, aev, 0);
+               WSACloseEvent(aev);
+               if (w < WAIT_OBJECT_0 || WAIT_OBJECT_0 + len <= w)
+                       continue;
+               if (w == WAIT_OBJECT_0 + len - 1)
+               {
+                       accept_client();
+                       continue;
+               }
+               pid_t pid = h[w - WAIT_OBJECT_0];
+               DWORD s = 0;
+               bool res = GetExitCodeProcess(pid, &s) && s == 0;
+               CloseHandle(pid);
+               pid_job_map::iterator i = job_pids.find(pid);
+               assert(i != job_pids.end());
+               int job_id = i->second;
+               job_pids.erase(i);
+               --running_jobs;
+               complete_job(job_id, res);
+       #else
+               sigset_t emptymask;
+               sigemptyset(&emptymask);
+               fd_set fdset;
+               FD_ZERO(&fdset);
+               FD_SET(socket_fd, &fdset);
+               int ret = pselect(socket_fd + 1, &fdset, NULL, NULL, NULL, &emptymask);
+               if (ret > 0 /* && FD_ISSET(socket_fd, &fdset)*/) accept_client();
+               if (!got_SIGCHLD) continue;
+               got_SIGCHLD = 0;
+               pid_t pid;
+               int status;
+               while ((pid = waitpid(-1, &status, WNOHANG)) > 0)
+               {
+                       bool res = WIFEXITED(status) && WEXITSTATUS(status) == 0;
+                       pid_job_map::iterator i = job_pids.find(pid);
+                       assert(i != job_pids.end());
+                       int job_id = i->second;
+                       job_pids.erase(i);
+                       --running_jobs;
+                       complete_job(job_id, res);
+               }
+       #endif
+       }
+}
+
+/**
+ * Load dependencies and rules, listen to client requests, and loop until
+ * all the requests have completed.
+ * If Remakefile is obsolete, perform a first run with it only, then reload
+ * the rules, and perform a second with the original clients.
+ */
+void server_mode(string_list const &targets)
+{
+       load_dependencies();
+       load_rules();
+       create_server();
+       if (get_status("Remakefile").status != Uptodate)
+       {
+               clients.push_back(client_t());
+               clients.back().pending.push_back("Remakefile");
+               server_loop();
+               if (build_failure) goto early_exit;
+               variables.clear();
+               specific_rules.clear();
+               generic_rules.clear();
+               first_target.clear();
+               load_rules();
+       }
+       clients.push_back(client_t());
+       if (!targets.empty()) clients.back().pending = targets;
+       else if (!first_target.empty())
+               clients.back().pending.push_back(first_target);
+       server_loop();
+       early_exit:
+       close(socket_fd);
+       remove(socket_name);
+       save_dependencies();
+       exit(build_failure ? EXIT_FAILURE : EXIT_SUCCESS);
+}
+
+/**
+ * Connect to the server @a socket_name, send a build request for @a targets,
+ * and exit with the status returned by the server.
+ */
+void client_mode(char *socket_name, string_list const &targets)
+{
+       if (false)
+       {
+               error:
+               perror("Failed to send targets to server");
+               exit(EXIT_FAILURE);
+       }
+       if (targets.empty()) exit(EXIT_SUCCESS);
+       DEBUG_open << "Connecting to server... ";
+
+       // Connect to server.
+#ifdef WINDOWS
+       struct sockaddr_in socket_addr;
+       socket_fd = socket(AF_INET, SOCK_STREAM, 0);
+       if (socket_fd < 0) goto error;
+       socket_addr.sin_family = AF_INET;
+       socket_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
+       socket_addr.sin_port = atoi(socket_name);
+       if (connect(socket_fd, (struct sockaddr *)&socket_addr, sizeof(sockaddr_in)))
+               goto error;
+#else
+       struct sockaddr_un socket_addr;
+       size_t len = strlen(socket_name);
+       if (len >= sizeof(socket_addr.sun_path) - 1) exit(EXIT_FAILURE);
+       socket_fd = socket(AF_UNIX, SOCK_STREAM, 0);
+       if (socket_fd < 0) goto error;
+       socket_addr.sun_family = AF_UNIX;
+       strcpy(socket_addr.sun_path, socket_name);
+       if (connect(socket_fd, (struct sockaddr *)&socket_addr, sizeof(socket_addr.sun_family) + len))
+               goto error;
+#ifdef MACOSX
+       int set_option = 1;
+       if (setsockopt(socket_fd, SOL_SOCKET, SO_NOSIGPIPE, &set_option, sizeof(set_option)))
+               goto error;
+#endif
+#endif
+
+       // Send current job id.
+       char *id = getenv("REMAKE_JOB_ID");
+       int job_id = id ? atoi(id) : -1;
+       if (send(socket_fd, (char *)&job_id, sizeof(job_id), MSG_NOSIGNAL) != sizeof(job_id))
+               goto error;
+
+       // Send targets.
+       for (string_list::const_iterator i = targets.begin(),
+            i_end = targets.end(); i != i_end; ++i)
+       {
+               DEBUG_open << "Sending " << *i << "... ";
+               ssize_t len = i->length() + 1;
+               if (send(socket_fd, i->c_str(), len, MSG_NOSIGNAL) != len)
+                       goto error;
+       }
+        // send 10 as as separator between targets and variables.
+        char result = 1;
+       if (send(socket_fd, &result, 1, MSG_NOSIGNAL) != 1) goto error;
+        result = 0;
+       if (send(socket_fd, &result, 1, MSG_NOSIGNAL) != 1) goto error;
+        // Send variables.
+        // (maybe split vars in small chunks and send... seems like an overkil)
+        std::string vars = serialize_variables(variables, 0);
+        ssize_t sent = vars.size();
+        DEBUG << "Sending variables: '" << vars << "' to the server" << std::endl;
+        if (send(socket_fd, vars.data(), sent, MSG_NOSIGNAL) != sent)
+          goto error;
+
+       // Send terminating nul and wait for reply.
+       if (send(socket_fd, &result, 1, MSG_NOSIGNAL) != 1) goto error;
+       if (recv(socket_fd, &result, 1, 0) != 1) exit(EXIT_FAILURE);
+       exit(result ? EXIT_SUCCESS : EXIT_FAILURE);
+}
+
+/**
+ * Display usage and exit with @a exit_status.
+ */
+void usage(int exit_status)
+{
+       std::cerr << "Usage: remake [options] [target] ...\n"
+               "Options\n"
+               "  -d                     Echo script commands.\n"
+               "  -d -d                  Print lots of debugging information.\n"
+               "  -h, --help             Print this message and exit.\n"
+               "  -j[N], --jobs=[N]      Allow N jobs at once; infinite jobs with no arg.\n"
+               "  -k                     Keep going when some targets cannot be made.\n"
+               "  -r                     Look up targets from the dependencies on standard input.\n"
+                "  -v V=X, --var V=X      Initialize variable V with X"
+               "  -s, --silent, --quiet  Do not echo targets.\n";
+       exit(exit_status);
+}
+
+/**
+ * This program behaves in two different ways.
+ *
+ * - If the environment contains the REMAKE_SOCKET variable, the client
+ *   connects to this socket and sends to the server its build targets.
+ *   It exits once it receives the server reply.
+ *
+ * - Otherwise, it creates a server that waits for build requests. It
+ *   also creates a pseudo-client that requests the targets passed on the
+ *   command line.
+ */
+int main(int argc, char *argv[])
+{
+       init_working_dir();
+
+       string_list targets;
+       bool indirect_targets = false;
+
+       // Parse command-line arguments.
+       for (int i = 1; i < argc; ++i)
+       {
+               std::string arg = argv[i];
+               if (arg.empty()) usage(EXIT_FAILURE);
+               if (arg == "-h" || arg == "--help") usage(EXIT_SUCCESS);
+               if (arg == "-d")
+                       if (echo_scripts) debug.active = true;
+                       else echo_scripts = true;
+               else if (arg == "-k" || arg =="--keep-going")
+                       keep_going = true;
+               else if (arg == "-s" || arg == "--silent" || arg == "--quiet")
+                       show_targets = false;
+               else if (arg == "-r")
+                       indirect_targets = true;
+               else if (arg.compare(0, 2, "-j") == 0)
+                       max_active_jobs = atoi(arg.c_str() + 2);
+               else if (arg.compare(0, 7, "--jobs=") == 0)
+                       max_active_jobs = atoi(arg.c_str() + 7);
+                else if (arg == "-v" || arg == "--var")
+                {
+                  ++i;
+                  if (i == argc) usage(EXIT_FAILURE);
+                  arg = argv[i];
+                  std::istringstream in (arg);
+                  std::string name = read_word(in);
+                  if (next_token(in) != Equal) {
+                    std::cerr << "Invalid variable '" << arg << "'" << std::endl;
+                    exit(EXIT_FAILURE);
+                  }
+                  in.ignore(1);
+                  string_list l = read_words(in);
+                  variables[name] = l;
+                }
+               else
+               {
+                       if (arg[0] == '-') usage(1);
+                       targets.push_back(normalize(arg));
+                       DEBUG << "New target: " << arg << '\n';
+               }
+       }
+
+       if (indirect_targets)
+       {
+               load_dependencies(std::cin);
+               string_list l;
+               targets.swap(l);
+               if (l.empty() && !dependencies.empty())
+               {
+                       l.push_back(dependencies.begin()->second->targets.front());
+               }
+               for (string_list::const_iterator i = l.begin(),
+                    i_end = l.end(); i != i_end; ++i)
+               {
+                       dependency_map::const_iterator j = dependencies.find(*i);
+                       if (j == dependencies.end()) continue;
+                       dependency_t const &dep = *j->second;
+                       for (string_set::const_iterator k = dep.deps.begin(),
+                            k_end = dep.deps.end(); k != k_end; ++k)
+                       {
+                               targets.push_back(normalize(*k));
+                       }
+               }
+               dependencies.clear();
+       }
+
+#ifdef WINDOWS
+       WSADATA wsaData;
+       if (WSAStartup(MAKEWORD(2,2), &wsaData))
+       {
+               std::cerr << "Unexpected failure while initializing Windows Socket" << std::endl;
+               return 1;
+       }
+#endif
+
+       // Run as client if REMAKE_SOCKET is present in the environment.
+       if (char *sn = getenv("REMAKE_SOCKET")) client_mode(sn, targets);
+
+       // Otherwise run as server.
+       server_mode(targets);
+}
diff --git a/tools/odeps.sh b/tools/odeps.sh
new file mode 100755 (executable)
index 0000000..2443c21
--- /dev/null
@@ -0,0 +1,87 @@
+#!/bin/sh
+#Save the parameters
+
+PROG="$0"
+CMDLINE="$*"
+usage() {
+    echo "$PROG [ocamldep ... ]"
+}
+
+OPTIONS=""
+INCLUDES=""
+ARGS=""
+NATIVE=0
+while true; do
+    case $1 in
+        -ppopt)
+            OPTIONS="$OPTIONS $1 $2"
+            shift
+            ;;
+        -I)
+            OPTIONS="$OPTIONS $1 $2"
+            INCLUDES="$INCLUDES $2"
+            shift
+            ;;
+        -native)
+            NATIVE=1
+            OPTIONS="$OPTIONS $1"
+            ;;
+        *.ml*)
+            ARGS="$ARGS $1"
+            ;;
+        *)
+            OPTIONS="$OPTIONS $1"
+            ;;
+    esac
+    if test "$#" -eq 0; then break; else shift; fi
+done
+
+if test "$NATIVE" -eq 1; then
+    OEXT="cmx"
+else
+    OEXT="cmo"
+fi
+IEXT="cmi"
+
+for src in $ARGS; do
+    $OPTIONS -modules "$src" | while read file deps; do
+        if test -z "$file"; then continue; fi
+        file=${file%:}
+        if test "$file" != "${file%.mli}"; then
+            ext="$IEXT"
+            base="${file%.mli}"
+            src_ext="mli"
+        else
+            ext="$OEXT"
+            base="${file%.ml}"
+            src_ext="ml"
+        fi
+        dependencies=""
+        for dep in $deps; do
+            modbase=$(echo $dep | sed 's/\(.*\)/\l\1/')
+            for dir in $INCLUDES; do
+                dir=${dir%/} #remove trailing slash, if any
+                #Interfaces or bytecode objects depends on other compile interfaces
+                #if they exists, otherwise they depend on the object
+                if test \( "$ext" = "cmi" -o "$ext" = "cmo" \) -a -f "$dir"/"$modbase".mli ; then
+                    dependencies="$dependencies $dir/$modbase".cmi
+                    break
+                elif test -f "$dir"/"$modbase".ml -o -f "$dir"/"$modbase".mly -o -f "$dir"/"$modbase".mll -o -d "$dir"/"$modbase"; then
+                    dependencies="$dependencies $dir/$modbase"."$ext"
+                    break
+                fi
+            done
+        done
+        #finally add the cmi as dependency of the cmo/cmx,
+        #if the .mli exists
+        if test "$ext" != "cmi" -a -f "$base".mli ; then
+            dependencies=" ${base}.cmi${dependencies}"
+        fi
+        #output a phony dependency between the cmi and the cmx/cmo if there is no .mli
+        if test "$ext" != "cmi" -a ! -f "$base".mli; then
+            echo "${base}.cmi: ${base}.${ext}"
+        fi
+        echo "${base}.${ext}:${dependencies}"
+    done
+
+done
diff --git a/tools/osort.sh b/tools/osort.sh
new file mode 100755 (executable)
index 0000000..b967ee3
--- /dev/null
@@ -0,0 +1,49 @@
+#!/bin/sh
+
+EXT="$1"
+if [ -z "$EXT" ]
+then
+    EXT="cmx"
+fi
+
+TARGET="$2"
+
+DEPS=$(
+while read target deps
+do
+    target="${target%.ml:}.$EXT"
+    dir=$(dirname "$target")
+    echo -n "$target":" "
+    for d in $deps
+    do
+        base=$(echo "$d" | sed 's/\(.*\)/\l\1/g' )
+        if [ -f "$dir"/"$base".ml -o -d "$dir"/"$base" ]
+        then
+            echo -n "$dir"/"$base"".$EXT "
+        fi
+    done
+    echo
+done)
+
+SORTED=""
+while true
+do
+    ROOT=$(echo "$DEPS" | grep '^[^ ]*.'$EXT': *$' | cut -f 1 -d: )
+    NDEPS=$(echo "$DEPS" | grep -v '^[^ ]*.'$EXT': *$')
+    if [ "$DEPS" = "$NDEPS" ]
+    then
+        SORTED="$SORTED $(echo $DEPS | cut -f 2 -d :)"
+        SORTED="$SORTED $(echo $DEPS | cut -f 1 -d :)"
+        break
+    fi
+    SORTED="$SORTED $ROOT"
+    for r in $ROOT
+    do
+        DEPS=$(echo "$DEPS" | sed "s|$r:\?||g")
+    done
+done
+for i in $SORTED
+do
+echo -n "$i "
+done
+echo