From cd46baab90296a75e03c73ad5c1f6f5bc3eb6cb3 Mon Sep 17 00:00:00 2001 From: Dmitry Goncharov Date: Sun, 2 Apr 2023 10:50:17 -0400 Subject: [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. --- src/main.c | 37 +++++++++++---- src/remake.c | 40 ++++++++++++----- tests/scripts/targets/WAIT | 109 +++++++++++++++++++++++++++++++++++++++++++++ 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! -- cgit v1.2.1