为何Makefile中被两个目标依赖的write目标仅执行一次?
I have this Makefile:
write: @echo `date` file_1: write file_2 @echo "file_1 begin" $(shell sleep 3) @echo "file_1 end" file_3: write file_4 @echo "file_3 begin" $(shell sleep 3) @echo "file_3 end" all: file_1 file_3 .PHONY: write
When I run make, the output is:
Sat Oct 9 15:22:45 CST 2021 file_1 begin file_3 begin file_1 end file_3 end
I noticed that the write target only runs once, even though it's a dependency for both file_1 and file_3. My goal is to use the write target to log start times so I can calculate the execution time of file_1 and file_3, but this behavior breaks that plan.
Why does this happen? And is there a better way to calculate the execution time of Makefile targets?
Great question! Let's break this down step by step.
Why write only runs once
This is core Make behavior: every target (including .PHONY ones) is executed exactly once per make session, no matter how many other targets list it as a dependency.
The .PHONY marker just tells Make that write isn't associated with a physical file, so it should always run the target's commands instead of checking if a file is up-to-date. But it doesn't override Make's fundamental rule of avoiding redundant work. When Make processes file_1, it runs write; when it later gets to file_3's dependencies, it sees write has already been executed in this session, so it skips running it again.
Better ways to calculate target execution time
If you want to track the start/end time (and elapsed duration) for each individual target, here are a few clean approaches:
1. Inline time tracking per target
Add start/end time capture directly in each target's command sequence. This is straightforward and easy to read:
file_1: file_2 @start_time=$$(date +%s); \ echo "file_1 begin at $$(date)"; \ sleep 3; \ end_time=$$(date +%s); \ echo "file_1 end at $$(date), elapsed time: $$((end_time - start_time)) seconds" file_3: file_4 @start_time=$$(date +%s); \ echo "file_3 begin at $$(date)"; \ sleep 3; \ end_time=$$(date +%s); \ echo "file_3 end at $$(date), elapsed time: $$((end_time - start_time)) seconds" all: file_1 file_3
2. Use the time command
If you just need a quick duration without custom logging, use the shell's built-in time command. Wrap your target's commands in parentheses to time the entire sequence:
file_1: file_2 @echo "file_1 begin" @time (sleep 3) @echo "file_1 end" file_3: file_4 @echo "file_3 begin" @time (sleep 3) @echo "file_3 end" all: file_1 file_3
This will output standard time stats (real, user, sys) after the target finishes.
3. Reusable Make function for consistency
If you have many targets to track, define a reusable function to avoid repetition:
# Define a function that takes target name and command as arguments define timed_target @start_time=$$(date +%s); \ echo "$(1) started at $$(date)"; \ $(2); \ end_time=$$(date +%s); \ echo "$(1) finished at $$(date) | Elapsed: $$((end_time - start_time)) seconds" endef file_1: file_2 $(call timed_target,file_1,sleep 3) file_3: file_4 $(call timed_target,file_3,sleep 3) all: file_1 file_3
4. Per-target start logging (if you still want per-target timestamps)
If you specifically want a "start time" log entry for each target (like your original write idea), don't use a shared dependency. Instead, call a logging snippet directly in each target:
# Helper to log start time for a target define log_start @echo "$$(date) - $(1) starting" endef file_1: file_2 $(call log_start,file_1) @sleep 3 @echo "file_1 end" file_3: file_4 $(call log_start,file_3) @sleep 3 @echo "file_3 end" all: file_1 file_3
This way, each target gets its own start timestamp, which you can pair with an end timestamp to calculate duration.
内容的提问来源于stack exchange,提问作者zcfh

