Dependency tracking
by Mithrandir
Let’s say you work on a project on your favourite language (for the purpose of this article, we will assume that that language is either C or Haskell, you’ll see why at the end of the article). Your project is complicated that you’ve split it across several libraries and several other files. Of course, you need a Makefile to build it. But, on that Makefile you need to be sure that all dependencies are listed as they should be, that they are up to date. The question arises: aren’t there any tools for automatic dependency tracking?
The answer is yes, there are (otherwise there wouldn’t be this article). I was asking myself that question while working on a C project until I saw that ghc has a -M option, described as follows:
-M Output Makefile rules recording the
dependencies of a list of Haskell files.
So, I go to the HaCoTeB working dir, delete the Makefile and run ghc -M HaCoTeB.hs. Then I look into the content of the Makefile:
# DO NOT DELETE: Beginning of Haskell dependencies
DataDefs.o : DataDefs.hs
TextParser.o : TextParser.hs
TextParser.o : DataDefs.hi
CodeParser.o : CodeParser.hs
CodeParser.o : DataDefs.hi
HaCoTeB.o : HaCoTeB.hs
HaCoTeB.o : CodeParser.hi
HaCoTeB.o : TextParser.hi
HaCoTeB.o : DataDefs.hi
# DO NOT DELETE: End of Haskell dependencies
Nice trick. Now, all I need to do here is to figure a way to do this automatically when invoking make. Solution is found at the Haskell Wiki: include the dependencies into a file and include that file into the Makefile (or, let ghc -M modify your Makefile)
Of course, ghc has another option, --make, which will make all of the above superfluous in the most cases. However, gcc doesn’t have it so that solution is necessary for C.
It turns out that it has a lot of -M options. -MM is what I was looking for, in conjunction with -MF to specify the output file.
That’s all that’s needed for automatic dependency tracking.
PS: Why have I excluded your language? Either because it is interpreted and there are no Makefiles needed or because the compiler is pretty smart to figure them by himself without requiring you to lift any more fingers.
PPS: Posted with a still bugged version of HaCoTeB.
I actually prefer the Makefile based dependency tracking to ghc –make, because make’s recompilation checker is much quicker than ghc –make when there’s nothing to do.
Also, I suggest putting the rules into a separate file (“depend” or “depends” are standard names I think) which you include, so that you don’t end up storing them in version control.
Thanks :)
Hi,
Probably I misunderstood, but what you have there looks like some lousy dep-generator – don’t know much about Haskell, but in the C world the C compilers have the -M option as well – -MM if you also want to go with the deps as deep as possible, into the system headers (those included with #include ).
Why is the output above lousy? Well, because it generates for each file more dependency lines – it’s ok with make, but not cool enough.
For solutions you can try
SOURCES=a.hs b.hs c.hs
include $(SOURCE:.hs=.d)
%d:%hs
ghc -M $ $@
If, instead, the ghc generates deps for the whole project, things can be slightly different. Still, the output looks rather bad, gcc -M is much nicer :)
The canonical reference for C is
http://mad-scientist.net/make/autodep.html
GNU CPP has gotten a bit smarter, so -MM or
-MMD leave out system headers, and -MP
will automatically add the dummy rules that
prevent complaints if headers are removed.
That’s why I didn’t present -M and jumped straight to the -MM :)
Ah, messed up those params. But anyway, the output of ghc -M is still ugly
One Problem you may still run in when using Makefiles, especially for compiling C, is when your dependencies are invalid because you’ve included another file, but did not update your dependency files.
Furthermore if you create your dependencies automatically your Makefile startup time will increase noticeable, plus you don’t want them to be generated always (e.g. when you want to clean your directory). I used to create very fancy like 200 and more lines Makefiles, dealing with all complications (including automatically finding sources and such).
For my projects I’ve switched to “rake”. Using “rake” I can specify in much finer detail when to generate and use dependency files, plus I can make the dependency files depend on the sources (and the object file on the dependency file). It is a little tricky, but it increased startup time enormously and I will never need to do a “make clean” anymore.
You can find one example Rakefile at: http://github.com/urso/CommandLineCocoaFramework . The Rakefile does a little more then is needed for normal C projects, because it will build a Cocoa Framework, but the idea should be clear. To defer dependency creation a task is used to create the tasks with the correct dependencies.
Thanks for the rake tip, will look into it too :)