summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDmitry Goncharov <dgoncharov@users.sf.net>2023-04-02 10:50:17 -0400
committerPaul Smith <psmith@gnu.org>2023-04-02 11:12:11 -0400
commitcd46baab90296a75e03c73ad5c1f6f5bc3eb6cb3 (patch)
tree904138f41717618eb5cfda0c1f2d3062dea0f0d8
parent54b3202f8da0761c7d5a3410313f0a32eb63ef8b (diff)
downloadmake-git-cd46baab90296a75e03c73ad5c1f6f5bc3eb6cb3.tar.gz
[SV 63856] Implement .WAIT on the command line
* src/main.c (handle_non_switch_argument): Return 1 if arg is .WAIT. (decode_switches): Set wait_here for a goal that follows .WAIT. * src/remake.c (update_goal_chain): Honor wait_here for a command line goal. Don't allow double-colon targets to continue if .WAIT is given for one of them. * tests/scripts/targets/WAIT: Add .WAIT tests.
-rw-r--r--src/main.c37
-rw-r--r--src/remake.c40
-rw-r--r--tests/scripts/targets/WAIT109
3 files changed, 168 insertions, 18 deletions
diff --git a/src/main.c b/src/main.c
index 8587de61..7b8e91cf 100644
--- a/src/main.c
+++ b/src/main.c
@@ -2975,15 +2975,17 @@ init_switches (void)
}
-/* Non-option argument. It might be a variable definition. */
-static void
+/* Non-option argument. It might be a variable definition.
+ Returns 1 if the argument we read was .WAIT, else 0.
+ */
+static unsigned int
handle_non_switch_argument (const char *arg, enum variable_origin origin)
{
struct variable *v;
if (arg[0] == '-' && arg[1] == '\0')
/* Ignore plain '-' for compatibility. */
- return;
+ return 0;
#if MK_OS_VMS
{
@@ -3036,7 +3038,12 @@ handle_non_switch_argument (const char *arg, enum variable_origin origin)
Enter it as a file and add it to the dep chain of goals.
Check ARG[0] because if the top makefile resets MAKEOVERRIDES
then ARG points to an empty string in the submake. */
- struct file *f = enter_file (strcache_add (expand_command_line_file (arg)));
+ struct file *f;
+
+ if (strcmp (arg, ".WAIT") == 0)
+ return 1;
+
+ f = enter_file (strcache_add (expand_command_line_file (arg)));
f->cmd_target = 1;
if (goals == 0)
@@ -3077,6 +3084,7 @@ handle_non_switch_argument (const char *arg, enum variable_origin origin)
define_variable_cname ("MAKECMDGOALS", value, o_default, 0);
}
}
+ return 0;
}
/* Called if the makefile resets the MAKEFLAGS variable. */
@@ -3098,6 +3106,7 @@ decode_switches (int argc, const char **argv, enum variable_origin origin)
struct command_switch *cs;
struct stringlist *sl;
int c;
+ unsigned int found_wait = 0;
/* getopt does most of the parsing for us.
First, get its vectors set up. */
@@ -3120,15 +3129,22 @@ decode_switches (int argc, const char **argv, enum variable_origin origin)
if (c == EOF)
/* End of arguments, or "--" marker seen. */
break;
- else if (c == 1)
- /* An argument not starting with a dash. */
- handle_non_switch_argument (coptarg, origin);
else if (c == '?')
/* Bad option. We will print a usage message and die later.
But continue to parse the other options so the user can
see all he did wrong. */
bad = 1;
+ else if (c == 1)
+ {
+ /* An argument not starting with a dash. */
+ const unsigned int prior_found_wait = found_wait;
+ found_wait = handle_non_switch_argument (coptarg, origin);
+ if (prior_found_wait && lastgoal)
+ /* If the argument before this was .WAIT, wait here. */
+ lastgoal->wait_here = 1;
+ }
else
+ /* An option starting with a dash. */
for (cs = switches; cs->c != '\0'; ++cs)
if (cs->c == c)
{
@@ -3320,7 +3336,12 @@ decode_switches (int argc, const char **argv, enum variable_origin origin)
to be returned in order, this only happens when there is a "--"
argument to prevent later arguments from being options. */
while (optind < argc)
- handle_non_switch_argument (argv[optind++], origin);
+ {
+ const int prior_found_wait = found_wait;
+ found_wait = handle_non_switch_argument (argv[optind++], origin);
+ if (prior_found_wait && lastgoal)
+ lastgoal->wait_here = 1;
+ }
if (bad && origin == o_command)
print_usage (bad);
diff --git a/src/remake.c b/src/remake.c
index 04daf49c..dec4667c 100644
--- a/src/remake.c
+++ b/src/remake.c
@@ -119,6 +119,7 @@ update_goal_chain (struct goaldep *goaldeps)
unsigned long last_cmd_count = 0;
int t = touch_flag, q = question_flag, n = just_print_flag;
enum update_status status = us_none;
+ const unsigned int depth = rebuilding_makefiles ? 1 : 0;
/* Duplicate the chain so we can remove things from it. */
struct dep *goals_orig = copy_dep_chain ((struct dep *)goaldeps);
@@ -137,6 +138,7 @@ update_goal_chain (struct goaldep *goaldeps)
while (goals != 0)
{
struct dep *gu, *g, *lastgoal;
+ int running = 0, wait = 0;
/* Start jobs that are waiting for the load to go down. */
@@ -154,16 +156,14 @@ update_goal_chain (struct goaldep *goaldeps)
while (gu != 0)
{
/* Iterate over all double-colon entries for this file. */
- struct file *file;
+ struct file *file, *dchead;
int stop = 0, any_not_updated = 0;
g = gu->shuf ? gu->shuf : gu;
goal_dep = g;
-
- for (file = g->file->double_colon ? g->file->double_colon : g->file;
- file != NULL;
- file = file->prev)
+ dchead = g->file->double_colon ? g->file->double_colon : g->file;
+ for (file = dchead; file != NULL; file = file->prev)
{
unsigned int ocommands_started;
enum update_status fail;
@@ -188,8 +188,24 @@ update_goal_chain (struct goaldep *goaldeps)
actually run. */
ocommands_started = commands_started;
- fail = update_file (file, rebuilding_makefiles ? 1 : 0);
+ stop = 0;
+
+ /* In the case of double colon rules, only the recipe of the 1st
+ rule should be blocked by .WAIT. The recipes of all subsequent
+ rules for the same file will execute sequentially in order
+ after the 1st. */
+ wait = file == dchead && g->wait_here && running;
+ if (wait)
+ {
+ DBF (DB_VERBOSE, _(".WAIT is blocking '%s'.\n"));
+ break;
+ }
+
+ fail = update_file (file, depth);
check_renamed (file);
+ running |= (file->command_state == cs_running
+ || file->command_state == cs_deps_running);
+
/* Set the goal's 'changed' flag if any commands were started
by calling update_file above. We check this flag below to
@@ -197,7 +213,6 @@ update_goal_chain (struct goaldep *goaldeps)
if (commands_started > ocommands_started)
g->changed = 1;
- stop = 0;
if ((fail || file->updated) && status < us_question)
{
/* We updated this goal. Update STATUS and decide whether
@@ -249,6 +264,9 @@ update_goal_chain (struct goaldep *goaldeps)
/* Reset FILE since it is null at the end of the loop. */
file = g->file;
+ if (wait)
+ break;
+
if (stop || !any_not_updated)
{
/* If we have found nothing whatever to do for the goal,
@@ -285,8 +303,9 @@ update_goal_chain (struct goaldep *goaldeps)
}
/* If we reached the end of the dependency graph update CONSIDERED
- for the next pass. */
- if (gu == 0)
+ for the next pass. In the case of waiting, increment CONSIDERED to
+ prevent the same file from getting pruned over and over again. */
+ if (gu == 0 || wait)
++considered;
}
@@ -384,7 +403,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 b981023f..343b87ac 100644
--- a/tests/scripts/targets/WAIT
+++ b/tests/scripts/targets/WAIT
@@ -56,6 +56,73 @@ pre2: ; @#HELPER# -q out $@
run_make_test(undef, '-j10 pre2', "pre2\n");
+# sv 63856.
+# .WAIT on the command line.
+
+run_make_test(q!
+pre1: ; @#HELPER# -q out start-$@ sleep 1 out end-$@
+pre2: ; @#HELPER# -q out $@
+
+# This is just here so we don't fail with older versions of make
+.WAIT:
+!,
+ '-j10 pre1 .WAIT pre2', "start-pre1\nend-pre1\npre2\n");
+
+# Multiple consecutive .WAITs.
+
+run_make_test(q!
+all : pre1 .WAIT .WAIT .WAIT pre2
+pre1: ; @#HELPER# -q out start-$@ sleep 1 out end-$@
+pre2: ; @#HELPER# -q out $@
+
+# This is just here so we don't fail with older versions of make
+.WAIT:
+!,
+ '-j10', "start-pre1\nend-pre1\npre2\n");
+
+# First and last prerequsites are .WAIT.
+
+run_make_test(q!
+all : .WAIT pre1 .WAIT pre2 .WAIT
+pre1: ; @#HELPER# -q out start-$@ sleep 1 out end-$@
+pre2: ; @#HELPER# -q out $@
+
+# This is just here so we don't fail with older versions of make
+.WAIT:
+!,
+ '-j10', "start-pre1\nend-pre1\npre2\n");
+
+# All prerequisites are .WAITs.
+
+run_make_test(q!
+all : .WAIT .WAIT .WAIT
+
+# This is just here so we don't fail with older versions of make
+.WAIT:
+!,
+ '-j10', "#MAKE#: Nothing to be done for 'all'.\n");
+
+run_make_test(q!
+all:
+!,
+ '-j10 .WAIT', "#MAKE#: Nothing to be done for 'all'.\n");
+
+# Wait between the duplicate goals.
+
+run_make_test(q!
+all: hello.tsk .WAIT hello.tsk
+hello.tsk:; $(info $@)
+!,
+ '-j10', "hello.tsk\n#MAKE#: Nothing to be done for 'all'.\n");
+
+# Wait between the duplicate command line goals.
+
+run_make_test(q!
+hello.tsk:; $(info $@)
+!,
+ '-j10 hello.tsk .WAIT hello.tsk', "hello.tsk\n#MAKE#: 'hello.tsk' is up to date.\n#MAKE#: 'hello.tsk' is up to date.\n");
+
+
# Ensure .WAIT doesn't wait between all targets
run_make_test(q!
@@ -71,6 +138,20 @@ pre3: ; @#HELPER# -q wait TWO out $@ file THREE
unlink(qw(TWO THREE));
+# Ensure .WAIT on the command line doesn't wait between all targets.
+
+run_make_test(q!
+pre1: ; @#HELPER# -q out start-$@ sleep 1 out end-$@
+pre2: ; @#HELPER# -q out start-$@ file TWO wait THREE out end-$@
+pre3: ; @#HELPER# -q wait TWO out $@ file THREE
+
+# This is just here so we don't fail with older versions of make
+.WAIT:
+!,
+ '-j10 pre1 .WAIT pre2 pre3', "start-pre1\nend-pre1\nstart-pre2\npre3\nend-pre2\n");
+
+unlink(qw(TWO THREE));
+
# Ensure .WAIT waits for ALL targets on the left before ANY targets on the right
run_make_test(q!
@@ -88,6 +169,23 @@ post2: ; @#HELPER# -q file POST2 wait POST1 out $@
unlink(qw(PRE1 PRE2 POST1 POST2));
+# Ensure .WAIT on the command line waits for ALL targets on the left before ANY
+# targets on the right.
+
+run_make_test(q!
+pre1: ; @#HELPER# -q out start-$@ file PRE1 wait PRE2 sleep 1 out end-$@
+pre2: ; @#HELPER# -q wait PRE1 out $@ file PRE2
+
+post1: ; @#HELPER# -q wait POST2 out $@ file POST1
+post2: ; @#HELPER# -q file POST2 wait POST1 out $@
+
+# This is just here so we don't fail with older versions of make
+.WAIT:
+!,
+ '-j10 pre1 pre2 .WAIT post1 post2', "start-pre1\npre2\nend-pre1\npost1\npost2\n");
+
+unlink(qw(PRE1 PRE2 POST1 POST2));
+
# See if .WAIT takes effect between different lists of prereqs
# In the current implementation, .WAIT waits only between two prerequisites
# in a given target. These same two targets might be run in a different
@@ -180,6 +278,17 @@ pre2: ; @#HELPER# -q out $@
!,
'-j10 --shuffle=reverse', "start-pre1\nend-pre1\npre2\n");
+# Ensure we don't shuffle if .WAIT is set on the command line.
+
+run_make_test(q!
+pre1: ; @#HELPER# -q out start-$@ sleep 1 out end-$@
+pre2: ; @#HELPER# -q out $@
+
+# This is just here so we don't fail with older versions of make
+.WAIT:
+!,
+ '-j10 --shuffle=reverse pre1 .WAIT pre2', "start-pre1\nend-pre1\npre2\n");
+
# Warn about invalid .WAIT definitions
run_make_test(q!