1
A = $(shell date)
B := $(shell date)

all:
  @echo $(A)
  sleep 2s;
  @echo $(A)
  sleep 2s;
  @echo $(A)
  @echo $(B)

As per the documentation that B is evaluated immediately when make is run and A is evaluated whenever A is encountered.

In this example I was expecting echo $(B) to have an early value. But what I get is all are printed with same value.

Wed Jun 11 12:44:46 CEST 2025
sleep 2s;
Wed Jun 11 12:44:46 CEST 2025
sleep 2s;
Wed Jun 11 12:44:46 CEST 2025
Wed Jun 11 12:44:46 CEST 2025

Make version tried, 3.1 and 4.1. Tried in Rocky OS and Ubuntu, same result.

4
  • I tried adding a sleep 1s before the first @echo $(A), and the result is the same as above. Commented Jun 11 at 10:49
  • By Early value I meant that the B's value is before A's value. Commented Jun 11 at 10:50
  • 1
    Make expands the recipe of rule all at once, before passing the result to the shell. So all your $(A) become the same date/time string before the shell sees the expanded recipe. With a higher resolution you can see the small delays during this expansion process performed by make but certainly not the 2 seconds delays because they are introduced after that. Commented Jun 11 at 14:07
  • Said differently, after expansion by make, your recipe becomes echo Wed Jun 11 12:44:46 CEST 2025 (first line), sleep 2s (second line), echo Wed Jun 11 12:44:46 CEST 2025 (third line)... And only after that expansion, the result is passed to the shell. Commented Jun 11 at 14:09

2 Answers 2

3

I managed to reproduce the issue and then modified the A assignment to...

A = $(shell date -Ins)

to give a more precise timestamp. That then shows...

2025-06-11T13:31:01,093108847+01:00
sleep 2s;
2025-06-11T13:31:01,094414902+01:00
sleep 2s;
2025-06-11T13:31:01,096214217+01:00
Wed 11 Jun 13:31:01 BST 2025

So there are slight differences but not the expected 2s.

Changing A further to...

A = $(shell echo 'Evaluating A' >&2 && date -Ins)

highlights the real issue: the output becomes...

Evaluating A
Evaluating A
Evaluating A
2025-06-11T13:33:17,699419455+01:00
sleep 2s;
2025-06-11T13:33:17,700834383+01:00
sleep 2s;
2025-06-11T13:33:17,702354865+01:00
Wed 11 Jun 13:33:17 BST 2025

So it appears that when make is about to run the commands associated with a recipe it evaluates all of the recursively expanded variables one after the other but before any of the commands are run.

(Note: I've written this up as a 'tentative' answer. Doubtless one of the more knowledgeable make users will provide a more complete answer at some point -- including the reasoning behind this feature.)

Edit 1: Having given this a bit more thought the behaviour actually makes perfect sense. The makefile in the question is using A as if it were a shell command. But it isn't -- it's a make variable and, as such, make is free to evaluate it prior to any use in a set of shell commands associated with a recipe.

Sign up to request clarification or add additional context in comments.

1 Comment

The conclusion of this answer is correct, and the behavior is indeed sensible. Unfortunately, neither the POSIX specification for make nor the GNU make manual (since that's the implementation the OP is using) actually specify the behavior to this level of detail. They agree that macro references in recipes are not expanded until after it is determined that the recipe needs to run, but they are ambiguous about whether expansion is performed all at once for the whole recipe, or on a line-by-line basis for each command line within, as the commands are run.
0

Recipes are stored as a single recursively expanded variable. They are only expanded at the point make wants to run the recipe.

After expansion, each line is passed, one at a time, to a separate invocation of the shell.

Thus executing the following recipe will never execute any shell command:

.PHONY: all
all:
    touch some-file
    $(error This line stops make at recipe expansion time)

Even though the $(error ...) appears at the end, it will be expanded at the point make is trying to discover the commands it needs to run.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.