0

I have a tree where I have something like this :

libFoo/foo.c
libFoo/bar.c

resulting in libFoo.a being placed in an .out directory at the top of the directory tree. I then have an application like so:

StandAlone/Foo/foo.c

resulting in an executable Foo in the .out directory

What I want/need is for the Makefile in StandAlone/Foo to recognize that .out/Foo is dependent on .out/libFoo.a which is dependent on libFoo/foo.c such that if libFoo/foo.c is modified, only libFoo.a and the binary Foo are compiled. I realize the tree should be flat, and this would be easy, but for historical purposes that's just not the way it is. Is there a place in the GNU Make manual that I'm missing that describes a recipe for doing this?

I've tried -MD and -MMD and all they generate is deps on include/<whatever.h>. This is useful, but definitely not what I want. What I want is described in the [how do I do this] section. I can post snippets of Makefiles that may help, but the StandAlone/Foo directory has a very simple Makefile that looks like this:

check_libraries looks like this (which is ugly, but I didn't write it). It seems as if a modification here would get me what I want, but I really have dependencies across three different directories and I'm not sure how to make that work.

PROGS=Foo 
 
include ../../Makefile.macros 
include ../../Makefile.libs 
include ../Makefile.inc 
 
check: 
 $(call check_libraries) 
 
DEPENDS = .makedepend 
 
SRC := *.c *.ec 
 
$(DEPENDS): 
 @makedepend -- $(CFLAGS) -- $(SRC) $(INC_FLAGS) -f- 2>/dev/null >$(DEPENDS)
 
-include $(DEPENDS)

check_libraries looks like this:

define check_libraries 
 @for a in .obj ; do \ 
 build_flag=0 ; \ 
 lib="$${a}/`basename $$a`.a" ; \ 
 if [ ! -f ".out/$$lib" ] ; then \ 
 build_flag=1 ; \ 
 else \ 
 cd $$a; \ 
 for obj in `ar -t $$lib` ; do \ 
 fullpath_object="$${a}/$${obj}"; \ 
 if [ ! -f "$$fullpath_object" ] ; then \ 
 build_flag=1 ; \ 
 fi \ 
 done \ 
 fi; \ 
 if [ "$$build_flag" -eq 1 ] ; then \ 
 echo "Building library: $$lib"; \ 
 cd $$a; \ 
 make; \ 
 fi; \ 
 done 
 
endef

It seems that check_libraries could/should be modified to allow me to do that I want, I just can't figure out how to modify it (actually, the way I'd prefer is to have gcc -MMD be "smarter"

Robert
9,08661 gold badges194 silver badges215 bronze badges
asked Jul 12, 2024 at 14:23
10
  • 1
    I'm pretty confused. Why are we talking about -MMD etc. or makedepend? Those are used to generate prerequisite information for source files (e.g., .c) showing the header files (e.g., .h) that they depend on. That has nothing to do with what libraries etc. that an executable depends on (how could it, since it's a preprocessor option not a linker option?) Commented Jul 12, 2024 at 15:22
  • 1
    I also don't see how modifying the content of the check_libraries variable can help with your problem, because that's a recipe that's used to build a target. What you want is to explain to make itself which targets to run. But really this entire thing is so non-make-like that it's impossible to have it work well using make. It basically re-implements make's work using a shell script instead. Commented Jul 12, 2024 at 15:30
  • Yeah, ignore the makedepend. @MadScientist - Doesn't it have to do this because the source is in StandAlone/Foo/foo.c, the library is in .out/libFoo.a and those source files are in libFoo/whatever.c ? I'd really rather not flatten out the whole thing. Is the way gcc is built a way to help me figure this out? Commented Jul 12, 2024 at 16:24
  • StandAlone/Foo/foo is dependent on StandAlone/Foo/foo.c is dependent on .out/libFoo.a is dependent on libFoo/whatever.c. If I touch StandAlone/Foo/foo.c, I want to rebuild libFoo.a iff the libFoo/whatever.c is newer than .out/whatever.o Commented Jul 12, 2024 at 16:27
  • I'm not sure what you mean by "doesn't it have to do this". What is "it" and what is "this"? The point of make is that you declare the dependency relationships then make figures out what is out of date and how to build it and runs the commands. The shell script in check_libraries above is doing that same thing, except in a fatally limited way: it looks for files in various directories and if they don't exist it runs make. That's not well-designed because that's what make is supposed to do. Commented Jul 12, 2024 at 17:10

1 Answer 1

1

What I want/need is for the Makefile in StandAlone/Foo to recognize that .out/Foo is dependent on .out/libFoo.a which is dependent on libFoo/foo.c such that if libFoo/foo.c is modified, only libFoo.a and the binary Foo are compiled.

Taken in isolation, that is easy.

Taken in the context of your current build system, that probably means rewriting the current build system. Which just might be the best thing to do.

I realize the tree should be flat, and this would be easy, but for historical purposes that's just not the way it is.

The tree being flat is irrelevant. The issue appears rather to be the use of recursive make. One of the consequences (usually) is that no make process sees a complete dependency graph. Refer to the classic paper Recursive Make Considered Harmful for more information.

The layout of your source is orthogonal to the use of recursive make -- you can avoid recursive make even with a complex source tree, and you can use recursive make even with a flat tree.

Is there a place in the GNU Make manual that I'm missing that describes a recipe for doing this?

As far as make's dependency analysis goes, dependencies are a matter of rules, not recipes. In fact, they are a matter strictly of the parts of rules other than their recipes. If you want make to know that .out/Foo depends on .out/libFoo.a, then the latter should be a prerequisite of the former. If you want make to know that .out/libFoo.a depends on path/to/foo.c and path/to/bar.c then the latter should be prerequisites of the former.

If the rules for building .out/Foo and .out/libFoo.a are in separate makefiles then you have a problem, to wit:

  • if the makefile for .out/Foo does not convey its indirect dependencies on path/to/foo.c and path/to/bar.c, then .out/Foo will not be rebuilt on account of being out of date with respect to one of those.

  • it does not help for the makefile for .out/Foo to convey its indirect dependencies on path/to/foo.c and path/to/bar.c without doing so through a rule for building .out/libFoo.a (which we supposed it doesn't), because the required mitigation in that case involves rebuilding .out/libFoo.a.

    If there is some other reason that causes .out/libFoo.a to be rebuilt under the circumstances then we gain no benefit from dependencies on path/to/foo.c and path/to/bar.c. If there is not such a reason then make will run the recipe for .out/Foo without first (re)building .out/libFoo.a, which, if it succeeds at all, gives you a result that is timestamped later than those indirect dependencies, but which is not actually up to date with respect to them.

The way that problem is typically addressed in projects using recursive make is that the sequence of recursive make calls is structured carefully, so that .out/libFoo.a is always brought up to date with respect to its dependencies before any attempt is made to bring .out/Foo up to date. Also, either way, each target has only direct dependencies for prerequisites, not indirect ones. All this typically means that it is not safe to manually run make against subdirectory makefiles, and probably not against any but a few specific targets in the top-level makefile.

answered Jul 12, 2024 at 19:54
Sign up to request clarification or add additional context in comments.

1 Comment

That's and answer that only comes from a wealth of experience.

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

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.