AFAIK, you can't specify several outputs per rule in GNU Make without relying on horribly complex workarounds. It's actually the feature I miss the most in it.
firstly, the upcoming version of GNU Make will indeed support multiple outputs in non-pattern rules (pattern rules always supported this, but pattern rules, uhh, have their own problems), using &: instead of :, resolving a bug from 2004. don't you just love the solid pace of those old GNU projects.
secondly, working around this was never very hard, albeit ugly: pretend your rule builds just one of the targets and have the other products depend on that one and simply touch themselves up to its timestamp.
That's actually one rule which applies to multiple targets.
If you run without parallelism (the default, -j1), it'll work fine, since make will run the rule for one target, and then when it comes to the second one, make will notice that it's already up-to-date (based on file timestamp).
If you run with -j4 or something, it make will likely run the rule twice, simultaneously. That may or may not be a bad problem depending on how the rule writes the output file.
EDIT: man I must have really rushed reading your post... as you say, this does appear to be a heuristic for GNU Make when it's a pattern rule. clearly make is tricky and I end up learning stuff every time I use make...
GNU Make has a number of implicit rules. A blank target rule (or no target rule) in the Makefile (or no Makefile at all!) will cause `make` to try to create the specified target `foo` from `foo.c` or `foo.cpp` (or others) if they exist. More complete explanation here:
Note: I was initially annoyed with parent's "do this and see what happens" post and its lack of substantive communication, and responded poorly. This comment is substantially edited.
For completeness it should be noted that GNU make is able to express rules with multiple output files using pattern rules
The problem with this approach (aside from the fact that it's specific to GNU make) is that you need a shared (non-empty) stem in all output files; however, the following workaround appears to work:
foo bar baz : % : .dummy/../%
%/../foo %/../bar %/../baz : quux
@sleep 2
touch foo bar baz
Actually GNU make is not a good target. Historically make has been extremely careful about backwards compatibility. The "bug" here is that the Makefiles were relying on an undocumented feature.
In general yes it'd be good if developers paid more attention to backwards compatibility.
> If you have an atomic step that produces multiple targets, it confuses make, and encoding the fact properly in the Makefile is really not straightforward.
Make does have a lot of warts, but this isn’t one of them any more. Grouped targets were added in the most recent release of GNU Make, and they address exactly this problem.
Re: your other points, many of them boil down to “the syntax is weird”. I agree 100%, and also it’s not so tough once you get used to it. I use GNU Make for tons of things, and I like it a lot.
My bugbear with make (and even the better remake) is finding out why things are not happening. "No rule to make target" is such an uninformative error message. Could it not identify rules which would make the target were they not failing?
One thing that make appears completely unable to do cleanly is to build things twice, with different flags. You know, like 'build a release version and a debug version'. I've ended up having to write self-writing makefiles to do that. It's not as nasty as it sounds (but still pretty nasty): https://github.com/davidgiven/wordgrinder/blob/master/Makefi...
Plus, 'set a flag for just this file' is another thing that's appallingly broken. GNU Make has an extension to do that --- but if you use it, it makes your builds non-deterministic. (Flags propagate down the build chain; builds are not in a defined order; each file is built once regardless of the flags for that file; so if a file is reachable via more than one rule with different flags settings, it's pure luck which setting wins...)
Checking the GNU Make manual it might me .NOTPARALLEL flag that wasn’t recognized correctly.
Cannot provide precise issues with (as it was more then a year) but I encountered 3-4 problems after having complete Makefile which was kind of downer because I had to rewrite it to support “vanilla” make.
In the end I dropped parallel went with Justfile and it took fraction of time (but truth be told I had dynamic PHONY targets which aren’t super easy to setup in Make).
In modern GNU make it is generally advised to disable default rules anyways. I know some people try to do portable Make, but I am pretty over that idea myself, so if I do use Make, I'm using GNU extensions and GNUmakefile.
All his criticisms are correct except maybe #3 which I don't understand.
Another problem I've found is that Make doesn't consider the absence of a prequisite to mean the target is out of date. So if foo.html depends on foo.intermediate, and then you delete foo.intermediate, then "make foo.html", foo.html will be considered up to date. I guess this is part of the odd feature where Make deletes intermediaries, but even if you have .SECONDARY on, which I do, it still behaves this way.
The bottom line is that it's extraordinarily easy to write incorrect Makefiles -- either underspecifying or overspecifying dependencies -- and it's very difficult to debug those problems. My Makefile is still full of bugs, so I "make clean" when something goes wrong.
One thing that would go a long way is if it had a shorthand for referring to the prequisites in commands, like $P1 $P2 $P3, and if it actually enforced that you use those in the command lines! I don't want to create variables for every single file, and when I rename files, rules can grow invisible bugs easily.
IIRC, I got around the M:N problem with GNU make by defining a pattern-matching rule to do what I want. For example, I was using yacc, so I did this:
%.tab.h %.tab.c: %.y
stuff
Using pattern matching forces make (GNU make) to correctly treat this as one rule which outputs multiple files, instead of the default of it being multiple rules which each output one file. It's not perfect, but it is a simple fix for some of those issues.
> The target name `all` is special: When you run make with no target specified it will evaluate the `all` target by default.
This doesn't appear to be true, at least on GNU Make 3.81 (MacOS). Rather, the first target listed is the one that gets built on `make` with no arguments.
> A pet peeve of mind is having to "make clean" between a debug and a release build, and nearly all usages of Make have that problem, e.g. Python and bash's build system. You could say they are violating your rules because each artifact doesn't have a unique name on the file system (i.e. debug and release versions of the same object file.)
There are at least two ways that this problem can be addressed. One is to support out-of-tree builds, one side directory per configuration. Builds based on the autotools do this by default.
The other is to use a separate build directory per configuration within the build. My current project uses local in-tree directories named .host-release, .host-debug (including asan), .host-tsan, .cross-release, and .cross-debug. All of them are built in parallel with a single invocation of Make, and I use target-scoped variables to control the various toolchain options.
The engineer's incremental work factor to add another build configuration isn't quite constant time, since each top-level target needs to opt into each build configuration that is relevant for that target.
> I hit the multiple outputs problem
I wouldn't really classify that as a problem in GNU Make, as long as you can specify the rule as a pattern rule.
I hear you on the testing problem. Make certainly behaves as if the Makefile's correctness isn't decidable. Even if you levied the requirement that a Makefile's decidability was predicated on the recipes being well-behaved, I'm not sure that the correctness is decidable.
reply