Force make to find out-of-date condition from file

2019-08-10 05:18发布

问题:

(this is similar to GNU make: Execute target but take dependency from file but slightly different).

When I try to force to build a target, make automatically thinks that this target is out of date and forces a run of all targets which depend on it.

In my case, the target is a recursive make call which does a lot of work and might just return with "nothing to be done":

 .PHONY: intermediate.dat
 intermediate.dat:
      $(MAKE) expensive_chain_which_finally_creates_intermediate.dat

 step1.dat: intermediate.dat
      sleep 10

 step2.dat: step1.dat
      sleep 15

 step3.dat: step2.dat
      sleep 10

 all: step3.dat
      sleep 5

In this case, "make all" runs for 40 seconds although intermediate.dat might not have changed (recursive make returned "nothing to be done"). However, if the recursive make updated intermediate.dat, the target shall be out of date.

Is there really no way to do this?

Regards divB

回答1:

Make intermediate.dat depend on a phony target instead of being phony itself.

 .PHONY : always-remake

 intermediate.dat : always-remake

IIRC, the last time I solved the problem, the .PHONY didn't work as intended and I used:

 always-remake :
     @true

instead. However, I can't recall why, so try the .PHONY first.

The problem with making intermediate.dat itself phony is that make never checks the existence/date of a phony file, which is behaviour that you want. You only need to trigger the rebuild rule, which is done by a prerequisite that is out of date; a phony prerequisite is always out of date, so it does the job.



回答2:

Another trick is to use an order-only prerequisite, plus a recursive call to make. This can be useful when you want to implement some additional logic around when the intermediate.dat file needs to be created, or when you want to decouple a target from the usual exists && newer than check that make does.

For example:

Let's say that the file contains some date-sensitive material that subsequent tasks depend on, and that it expires after 12 hours. We only want to recreate the file when any of these is true:

  • The file is older than 12 hours
  • The file does not exist

To implement the make target & recipes, we'll create a .PHONY check-intermediate.dat target, which runs the time check, conditionally removes the file if it's expired, and then re-runs make for that file.

Note that the check-intermediate.dat target will be defined via the order-only prerequisite syntax (e.g. intermediate.dat: | check-intermediate.dat) which causes it to run always before the target (e.g. intermediate.dat). The time check is inexpensive, and we always want it to run and manage the file for us, but not invalidate the original make check on intermediate.dat if it already exists like a normal prerequisite .PHONY target would.

 .PHONY: check-intermediate.dat
 check-intermediate.dat:
    if [ -e intermediate.dat ]; then  find intermediate.dat -mmin +720 -exec bash -c 'rm -f "{}"; $(MAKE) intermediate.dat' \; ; fi

 intermediate.dat: | check-intermediate.dat
    echo run-expensive-tasks-here-to-create-intermediate.dat
 step1.dat: intermediate.dat
    sleep 10

 step2.dat: step1.dat
    sleep 15

 step3.dat: step2.dat
    sleep 10

 all: step3.dat
    sleep 5

Now, anything that depends on the intermediate.dat target will first run the check, remove the file if it's expired, and re-run make intermediate.dat before running the rest of the dependent targets. If the intermediate.dat file already exists, it will run the check, and if the check passes, it will continue without running the time expensive recreation task.

The problem with depending on a .PHONY target is that it will always run the tasks which depend on it, because of the way GNU Make handles .PHONY targets. This can cause the make run to always perform the time expensive operations even when it does not need to:

A phony target should not be a prerequisite of a real target file; if it is, its recipe will be run every time make goes to update that file. As long as a phony target is never a prerequisite of a real target, the phony target recipe will be executed only when the phony target is a specified goal (see Arguments to Specify the Goals).