commit 7ce32b0d4888ff94dd1fd655d08da7e2682b00c3
Author: Dmitry Goncharov <dgoncharov@users.sf.net>
Date:   Thu Mar 16 10:27:06 2023 -0400

    [SV 63856] Fix pruning of double colon rules.
    
    * src/remake.c (update_file): Fix premature pruning of double colon rules.
    
    Given this makefile
    
    A::; @echo A-1 && sleep 1 && echo A-1 done
    A::; @echo A-2 && sleep 1 && echo A-2 done
    A::; @echo A-3 && sleep 1 && echo A-3 done
    B::; @echo B-1 && sleep 1 && echo B-1 done
    B::; @echo B-2 && sleep 1 && echo B-2 done
    B::; @echo B-3 && sleep 1 && echo B-3 done
    
    And running
    $ make -j8 A .WAIT B
    all 3 recipes for A should get started and complete sequentially before the 1st
    recipe for B gets started and then all 3 recipes for B should get started and
    complete sequentially.
    The change in update_file is needed, because update_file prunes A even
    when the current A recipe is already done and the recipe of the next double
    colon rule of the same target should be started. This incorrect pruning
    causes update_goal_chain to run a recipe of the next goal, even though there
    are still not started recipes of this goal.
    This patch modifies the pruning criteria such that for double colon rules
    pruning only takes place if the current recipe is still in progress.
    
    * tests/scripts/targets/WAIT: Test .WAIT with double colon rules.

diff --git a/src/remake.c b/src/remake.c
index 3d5a07f6..6bb5729e 100644
--- a/src/remake.c
+++ b/src/remake.c
@@ -357,9 +357,15 @@ update_file (struct file *file, unsigned int depth)
     {
       /* Check for the case where a target has been tried and failed but
          the diagnostics haven't been issued. If we need the diagnostics
-         then we will have to continue. */
+         then we will have to continue.
+         In the case of double colon rules, this file cannot be pruned if
+         this recipe finished (file->command_state == cs_finished) and there
+         are more double colon rules for this file. Instead the recipe of the
+         next double colon rule of this file should be run.  */
       if (!(f->updated && f->update_status > us_none
-            && !f->dontcare && f->no_diag))
+            && !f->dontcare && f->no_diag)
+            && (!file->double_colon || file->command_state != cs_finished ||
+                !f->prev))
         {
           DBF (DB_VERBOSE, _("Pruning file '%s'.\n"));
           return f->command_state == cs_finished ? f->update_status : us_success;
@@ -387,7 +393,8 @@ update_file (struct file *file, unsigned int depth)
       if (f->command_state == cs_running
           || f->command_state == cs_deps_running)
         /* Don't run other :: rules for this target until
-           this rule is finished.  */
+           this rule is finished.  Multiple recipes running in parallel and
+           updating the same target will corrupt the target.  */
         return us_success;
 
       if (new > status)
diff --git a/tests/scripts/targets/WAIT b/tests/scripts/targets/WAIT
index ae942fa9..dc481d62 100644
--- a/tests/scripts/targets/WAIT
+++ b/tests/scripts/targets/WAIT
@@ -282,5 +282,86 @@ all:;@:
 !,
               '', "#MAKEFILE#:2: .WAIT should not have prerequisites\n#MAKEFILE#:3: .WAIT should not have commands\n");
 
+# Wait for all double colon targets on the left, before building targets on the
+# right.
+# Subtest 1. First run this test without .WAIT and observe and A recipes
+# interleave with B recipes.
+# Then run the same test with .WAIT and observe that all A recipes get started
+# sequentially before the 1st B recipe gets started and then all B recipes get
+# started sequentially.
+
+run_make_test(q!
+all: A B
+A::; @#HELPER# -q out $@-1 sleep 1 out $@-1-done
+A::; @#HELPER# -q out $@-2 sleep 1 out $@-2-done
+A::; @#HELPER# -q out $@-3 sleep 1 out $@-3-done
+B::; @#HELPER# -q out $@-1 sleep 1 out $@-1-done
+B::; @#HELPER# -q out $@-2 sleep 1 out $@-2-done
+B::; @#HELPER# -q out $@-3 sleep 1 out $@-3-done
+
+# This is just here so we don't fail with older versions of make
+.WAIT:
+!,
+              '-j10', "A-1\nB-1\nA-1-done\nB-1-done\nA-2\nB-2\nA-2-done\nB-2-done\nA-3\nB-3\nA-3-done\nB-3-done\n");
+
+# Subtest 2.
+# Wait for all double colon targets on the left, before building targets on the
+# right.
+
+run_make_test(q!
+all: A .WAIT B
+A::; @#HELPER# -q out $@-1 sleep 1 out $@-1-done
+A::; @#HELPER# -q out $@-2 sleep 1 out $@-2-done
+A::; @#HELPER# -q out $@-3 sleep 1 out $@-3-done
+B::; @#HELPER# -q out $@-1 sleep 1 out $@-1-done
+B::; @#HELPER# -q out $@-2 sleep 1 out $@-2-done
+B::; @#HELPER# -q out $@-3 sleep 1 out $@-3-done
+
+# This is just here so we don't fail with older versions of make
+.WAIT:
+!,
+              '-j10', "A-1\nA-1-done\nA-2\nA-2-done\nA-3\nA-3-done\nB-1\nB-1-done\nB-2\nB-2-done\nB-3\nB-3-done\n");
+
+# sv 63856.
+# Wait for all double colon targets on the left, before building targets on the
+# right.
+# Subtest 1. First run this test without .WAIT and observe and A recipes
+# interleave with B recipes.
+# Then run the same test with .WAIT and observe that all A recipes get started
+# sequentially before the 1st B recipe gets started and then all B recipes get
+# started sequentially.
+# The targets are specified on the command line.
+
+run_make_test(q!
+A::; @#HELPER# -q out $@-1 sleep 1 out $@-1-done
+A::; @#HELPER# -q out $@-2 sleep 1 out $@-2-done
+A::; @#HELPER# -q out $@-3 sleep 1 out $@-3-done
+B::; @#HELPER# -q out $@-1 sleep 1 out $@-1-done
+B::; @#HELPER# -q out $@-2 sleep 1 out $@-2-done
+B::; @#HELPER# -q out $@-3 sleep 1 out $@-3-done
+
+# This is just here so we don't fail with older versions of make
+.WAIT:
+!,
+              '-j10 A B', "A-1\nB-1\nA-1-done\nB-1-done\nA-2\nB-2\nA-2-done\nB-2-done\nA-3\nB-3\nA-3-done\nB-3-done\n");
+
+# Subtest 2.
+# sv 63856.
+# Wait for all double colon targets on the left, before building targets on the
+# right. The targets are specified on the command line.
+
+run_make_test(q!
+A::; @#HELPER# -q out $@-1 sleep 1 out $@-1-done
+A::; @#HELPER# -q out $@-2 sleep 1 out $@-2-done
+A::; @#HELPER# -q out $@-3 sleep 1 out $@-3-done
+B::; @#HELPER# -q out $@-1 sleep 1 out $@-1-done
+B::; @#HELPER# -q out $@-2 sleep 1 out $@-2-done
+B::; @#HELPER# -q out $@-3 sleep 1 out $@-3-done
+
+# This is just here so we don't fail with older versions of make
+.WAIT:
+!,
+              '-j10 A .WAIT B', "A-1\nA-1-done\nA-2\nA-2-done\nA-3\nA-3-done\nB-1\nB-1-done\nB-2\nB-2-done\nB-3\nB-3-done\n");
+
 # This tells the test driver that the perl test script executed properly.
 1;
