I know at least three reasons why makefiles are not working well for common use cases. Many people seem unaware of these cases, even though they seem to come up quite often.
• make(1) has a hard time rebuilding target files when source files have changed. The problem is that users have to declare the dependencies of a target before the build, but due to file search paths, it can be that dependencies are only known after building a target. To know about all dependencies, make(1) would have to build any target at least twice.
• make(1) by default does not rebuild a target when its build rules change. While this seems really weird to me, it seems to be a consequence of having a single makefile. When a build rule is changed, the makefile changes. So should all targets depend on the makefile implicitly? One could argue that they should – but then each change in a makefile would rebuild all targets.
• make(1) can not handle non-existence dependencies. Imagine a file foo.h being searched for by a compiler in directories bar and baz (in that order). If the header file baz/foo.h is found, then bar/foo.h should be considered a non-existence dependency: If at any future point in time it exists, the target should be rebuilt.
I think that all of these are limitations of not only make(1), but all utilities that expect dependencies to be fully known before a target is built. What makes you think they are not?
How would you solve all three problems I listed using make(1)?
A makefile consists of rules. Each rule contains a dependency line which defines a target and an enumeration of prerequisites. This means that the dependencies have to be known before the target is built. By design, it is impossible for a single-pass make(1) invocation to derive dependencies for a C program, as dependencies are output by the compiler.
By contrast, redo builds the target first and then records what was used to build it. For example, when compiling a C file with “gcc -M”, gcc will output dependency information. With redo, you normally record those dependencies after the target has been built. With make(1), that information has to end up in the makefile somehow, possibly leading to further builds.
I mentioned m4 before as a way of using more passes, and is how the Linux kernel approaches this, but looking deeper at make, I'm not even sure you need it.
Because you mention `foo.o' but do not give a rule for it, make will automatically look for an implicit rule that tells how to update it. This happens whether or not the file `foo.o' currently exists.
I do not understand. I know that make(1) has many implicit rules – but which of the problems I mentioned does this solve and how? Please be specific about your solution.
Because "limiting" has a broader meaning than what is merely possible with enough effort. The experience of practically everybody who hasn't already put in the effort to learn make is that they get much further much faster in more modern build systems - indeed, framework specific build systems often do exactly what you need them to do with no or very little configuration at all. That is a feeling of not being limited by the tool.
Make runs on Windows, OS X, Linux and Android.
How is a Makefile quite limited?