views:

192

answers:

4

If foo_user.cpp depends on foo.h, then foo_user.cpp is built, and then foo.h's modification time is set to further in the past, make will not rebuild foo_user.cpp (because foo.cpp is 'newer'). I'd prefer it if make recorded the modification times of dependencies, and if they changed at all (newer or older), to consider targets of that dependency to be out of date. Can GNU make do this? If not, is there an easy alternative?

In case you're curious how this situation arises: foo.h resides in a symlinked folder. The symlink may point to the foolib-1.0 folder, the foolib-2.0 folder, etc. When the symlink points at a different version of the library, even an older version, foo_user.cpp should be rebuilt. If I simply specifiy symlinkfolder/foo.h as a dependency of foo_user.cpp, make only pays attention to the timestamp of foo.h, not the timestamp of the symlink'd directory through which foo.h is accessed. I can't add the symlink itself as a dependency, because the make rule is generated by the compiler (GCC has a special flag that when given causes it to output a make rule for all the headers a source file depends on).

+2  A: 

No, Make does not support this. You may wish to consider using another build system such as SCons, which does not rely solely on the timestamp but actually computes the MD5 hash of source files and bases its decisions on the hashes.

From "What makes SCons better?" on its web site:

  • Reliable detection of build changes using MD5 signatures; optional, configurable support for traditional timestamps.
Greg Hewgill
I was just thinking if I made my own build system I'd do this instead, that's pretty awesome. Curious how well it scales to large code bases though. Using boost can pull in 1,000+ header files it needs to md5.
Joseph Garvin
SCons doesn't re-hash included files if the timestamp is *exactly* the same (by default, you can change this behaviour if you like).
Greg Hewgill
@Greg: Hmmm... it sounds like this default behavior might be aimed at preventing the performance problem that Joseph is curious about. But at the same time, doesn't this default behavior negate the primary advantage of using the MD5 hash (i.e. not relying on timestamps which, technically, don't guarantee the content hasn't changed)? Call me old-fashioned, but sometimes you just gotta leave good enough alone.
Dan Moulding
Right, one could change the content and leave the timestamp the same. SCons won't detect that by default, but you can tell it to. Naturally, that will be slower. The big win comes when you've got a series of steps like A -> B -> C, and you change something in A (like a comment) that doesn't affect B. The A -> B step runs, SCons notices that B is the same *content* as the old B, and then doesn't need to run the B -> C step. Make would run both steps.
Greg Hewgill
+3  A: 

I'm trying to understand why you can't just add the symlink as a dependency. I imagine your automatic dependencies are on one line, but you can have as many as you want.

x.o: a.h b.h    
x.o: c.h    
x.o: d.h

But having said that, it seems likely that make will stat the symlink's target, and not the symlink itself, so that may not DTRT. I suppose you could just touch a file somewhere whenever you make the symlink, but I also suppose you've already thought of that...

You could have a rule that runs ls -id link/. > test, which will put the inode number of the link target directory in test. You could then cmp test save, where save is from the last run. You could then have that make rule do make clean && make target if they are different.

targetwrapper: 
    ls -id link/. > test
    cmp test save || make clean
    make realtarget
    cp test save

clean:
    echo cleaned

realtarget:
    echo made
DigitalRoss
"I can't add the symlink itself as a dependency, because the make rule is generated by the compiler (GCC has a special flag that when given causes it to output a make rule for all the headers a source file depends on)." <-- unclear or did you just miss it? ;)
Joseph Garvin
I still don't understand why you can't *add* a dependency, even if the rule is auto-generated, but I will take your word for it.
DigitalRoss
Dependencies and rules don't need to be on the same line...
DigitalRoss
The real problem with this solution is now in a comment on my original post above in response to Beta.
Joseph Garvin
So the objective is really: *automated generation of the individual symlink dependencies*, that makes sense. I think you will need to process the auto-generated dependency Makefile in your favorite scripting language. Using one of the earlier ideas, extract the directory dependency information from the header prerequisite data already in the Makefile, and then make those targets depend on an inode file or some trick as earlier outlined.
DigitalRoss
+1  A: 

While make doesn't support it out of the box, you can program it.

include more_deps

ifneq ($(MAKE_RESTARTS),)

more_deps:
  if (foolink.old differs from what foolink points to) ; then \
    readlink foolink > foolink.old ; \
    echo "foo_user: foolink_trigger" > more_deps ; \
    touch foolink_trigger ; \
  else \
    echo "" > more_deps ;\
  fi

endif

foo_user: foo_user.cpp
  g++ $^ -o $@

Here you include makefile more_deps which sometimes will include the dependency on the symlink's trigger. Trigger is a special intermediate flie, all the meaningful informaion in which is its timestamp. When the symlink changes, the timestamp of the trigger is updated to current time (see touch), thus making foo_user outdated and it is the rebuilt.

include and MAKE_RESTARTS are needed to restart make after calculating the dependency described above. If the makefile being included is a target itself, the target is considered to be rebuilt, is rebuilt and then make restarts and re-reads makefile. But when it reads makefile for the second time, it doesn't see more_deps as a target, because MAKE_RESTARTS variable expands to non-empty string.

In fact, the line with if can sound like this:

more_deps:
  if (any condition you want with $(VARIABLES) possible!) ; then \
     update a file that holds the previous state ;\ 
     ...
Pavel Shved
This seems like a powerful technique, but I think it needs polishing. Will it ever make more_deps? And is the "else" statement needed?
Beta
It does need polishing, Beta, but it seems that only you and me care :-(
Pavel Shved
Take heart, and remember the Mad Scientist's Maxim: *"The fools! One day I'll destroy them all!"* How about if(condition) <br> target:update <br> endif where update is PHONY? Small if statement, and no inclusion needed.
Beta
@Beta, *small*? Rather, it's large; it is the same I wrote: you should store the information somewhere if it was updated. And small `if` statement doesn't cause makefiles to be re-read, which most liekly will be needed if you do a "soft recursion" to the subdirectories.
Pavel Shved
Let me try again (semicolon means carriage return): if(foolink and foolink.old differ); foo_user: update; endif; .PHONY: update; update: ; readlink foolink > foolink.old; I meant that the if *loop* is small-- the if statement itself is exactly yours (but it's a make if, not a shell if). I'll have to think about recursing into subdirs, but I don't see any problem...
Beta
@Beta, hm, this seems to be working better than my stuff!
Pavel Shved
A: 

Through which process do you change the symlink? You could add a make clean type of action to the script that changes the symlink.

You could also set up a "header working folder" in with you let make copy your header files, where the copied header files are dependent on their original and the symlink. The dependencies generated by GCC only take the working headers into account and won't clash with your copy headers into the working folder part of your Makefile.

rsp