Search code examples
makefilegnu-make

only rebuild when necessary even though we depend on a subproject with its own Makefile


Situation

I have a Makefile that depends on a few things from a subproject that has its own Makefile. In the subproject, the targets have dependencies which I was hoping my main project would not have to know about. So I am trying to use the subproject's Makefile to build anything I need from it when necessary. This is what I have in my sample project before building:

$ find . -type f | xargs head
==> ./subproject/Makefile <==
binary: source.txt
        echo subproject source: > $@
        date >> $@
        cat $< >> $@
clean:
        rm binary

==> ./subproject/source.txt <==
this is the subproject source code

==> ./Makefile <==
binary: source.txt subproject/binary
        echo main source: > $@
        date >> $@
        cat $^ >> $@
subproject/%:
        $(MAKE) -C $(@D) $(@F)
clean:
        rm binary
        $(MAKE) -C subproject clean


==> ./source.txt <==
this is the main source code

and this is what I have after building:

$ make
make -C subproject binary
make[1]: Entering directory '~/make-example/subproject'
echo subproject source: > binary
date >> binary
cat source.txt >> binary
make[1]: Leaving directory '~/make-example/subproject'
echo main source: > binary
date >> binary
cat source.txt subproject/binary >> binary
$ find . -type f | xargs head
==> ./subproject/Makefile <==
binary: source.txt
        echo subproject source: > $@
        date >> $@
        cat $< >> $@
clean:
        rm binary

==> ./subproject/binary <==
subproject source:
Di 10. Okt 14:01:03 CEST 2023
this is the subproject source code

==> ./subproject/source.txt <==
this is the subproject source code

==> ./Makefile <==
binary: source.txt subproject/binary
        echo main source: > $@
        date >> $@
        cat $^ >> $@
subproject/%:
        $(MAKE) -C $(@D) $(@F)
clean:
        rm binary
        $(MAKE) -C subproject clean


==> ./binary <==
main source:
Di 10. Okt 14:01:03 CEST 2023
this is the main source code
subproject source:
Di 10. Okt 14:01:03 CEST 2023
this is the subproject source code

==> ./source.txt <==
this is the main source code
alex@elephant:~/make-example$ 

Problem

When I change subproject/source.txt, and run make in the parent project, it still doesn't recompile.

I can almost fix that like this:

subproject/%: always
    $(MAKE) -C $(@D) $(@F)
always:

when make is run in the subproject, subproject/binary will not be recompiled. However, binary in the parent project will still get recompiled because one of its prereqs got run, even if the date on prereq file is old.


Question

So I think that the answer is probably that this is not how I am supposed to be doing it. I really wanted the parent project and subproject to know as little about one another as possible.

Is there any way to make the parent project check the date on subproject/binary and use that to determine whether to rebuild binary even if the subproject/binary formula was run?

I would like to be able to type make and build it, and then change subproject/source.txt and type make again in the main project, and have it recompile subproject/binary and then recompile binary as a result, and then right after, not change anything, and type make again, not recompile anything


Solution

  • You write:

    However, binary in the parent project will still get recompiled because one of its prereqs got run, even if the date on prereq file is old.

    That is not how make works. Just because a recipe is invoked does not mean that make always assumes that the target was modified; make will examine the modification time of the prerequisite and compare it, even if the prerequisite's recipe was invoked.

    So, if you're seeing binary always rebuilt then something else is happening here: maybe the sub-make is written in such a way that it always modifies the binary. You may want to use make --trace or make -d to understand why make decides to rebuild binary.

    Example:

    UPDATE = touch $@
    binary: prereq; touch $@
    prereq: FORCE ; : updating; $(UPDATE)
    FORCE:
    

    Now:

    $ make
    : updating; touch prereq
    touch binary
    
    $ make
    : updating; touch prereq
    touch binary
    

    as expected, but now if we don't actually change the prerequisite:

    $ make UPDATE=
    : updating;
    

    We can see that the prerequisite updating rule was run, but since it didn't actually change prereq, make didn't rebuild binary.