I recently decided it was time to get a better understanding of how makefiles work, and after reading a few tutorials, ended up just reading the manual. It's long, but it's very, very well written (a good example of one of the Gnu projects biggest strengths), to the point where just starting at the top and reading gives an almost tutorial-like effect. Just read the manual!
FWIW I also read the GNU Make manual, and based some code for automatic deps off a profoundly ugly example it had. Then later people on HN showed me a better/simpler way to do it. https://news.ycombinator.com/item?id=15060149 https://www.gnu.org/software/make/manual/html_node/Automatic... After reading the manual and writing 3 substantial Makefiles from scratch, I still think Make is ugly and, by modern standards, no…
- Each build step has a unique artifact.
- That artifact is visible to Make as a file in the filesystem.
- That artifact is named $@ in the rule's recipe.
- Every time the recipe is executed, $@ is updated on success.
- If the recipe fails, it must return nonzero to Make.
- All of the dependencies of the artifact are represented in the Makefile
For example, here is how the format checks are run in my current project for some C code. Its mission: To verify that those source files which are under the aegis of clang-format are correctly formatted. BUILD_DIRS is a list of directories containing source code. CFORMATTER is the name of the formatting program. Not everything is under clang-format control, so FORMATTED_SRCS is used to opt-in to it.
BUILD_DIRS_FORMAT = $(addprefix .format-check/,$(BUILD_DIRS))
$(BUILD_DIRS_FORMAT): mkdir -p $@
# There aught to be a better way to control the suffix without becoming a match-anything # rule...
.format-check/%.c: %.c | $(BUILD_DIRS_FORMAT) $(CFORMATTER) $ $@
.format-check/%.h: %.h | $(BUILD_DIRS_FORMAT) $(CFORMATTER) $ $@
# Record the fact that each format check passed by touching a uniquely-named file. # note the call to `false` on error, since `echo` always succeeds.
.format-check/%.diffed: .format-check/% @(diff -u --color=always -- $* $check-formatting: $(addsuffix .diffed,$(addprefix .format-check/, $(FORMATTED_SRCS)))
It's artifacts are:
- A formatted source file for each repository source file
- An empty file in the filesystem for each formatted source file that is identical to the repository source file.
- A tree of directories for the above.
Each format check is run exactly once, and only when source files change. If anything fails, then `make` returns nonzero and the build fails. Its also fully parallelized, since there aren't any neck-down points in the dependency graph. Every one of our pre-commit checks are structured this way. Build verification is as parallel as possible for fresh builds. Engineers can resolve and verify their problems quickly and incrementally when they fail.