# Runs make in all recursive subdirectories with a Makefile, passing the make target to each. # Directories are make'ed in top down order. # ex: make (runs DEFAULT_GOAL) # ex: make clean (runs clean) # ex: make install (runs install) mkfile_dir = $(patsubst %/,%,$(dir $(realpath $(lastword $(MAKEFILE_LIST))))) # if IS_RECURSIVE_MAKE is set, then this is being invoked by another recursive.mk. # in that case, we don't want any targets ifndef IS_RECURSIVE_MAKE .DEFAULT_GOAL := help # make with VERBOSE=1 to print all outputs of recursive makes VERBOSE ?= 0 RECURSIVE_TARGETS = clean test check lock install # You can pass in a list of files or directories to retain when running `clean/git-clean` # ex: make clean GIT_CLEAN_RETAIN=".env .data" # As always with make, you can also set this as an environment variable GIT_CLEAN_RETAIN ?= .env GIT_CLEAN_EXTRA_ARGS = $(foreach v,$(GIT_CLEAN_RETAIN),--exclude !$(v)) ifeq ($(VERBOSE),0) GIT_CLEAN_EXTRA_ARGS += --quiet endif .PHONY: git-clean git-clean: git clean -dffX . $(GIT_CLEAN_EXTRA_ARGS) FILTER_OUT = $(foreach v,$(2),$(if $(findstring $(1),$(v)),,$(v))) MAKE_FILES = $(shell find . -mindepth 2 -name Makefile) ALL_MAKE_DIRS = $(sort $(filter-out ./,$(dir $(MAKE_FILES)))) ifeq ($(suffix $(SHELL)),.exe) MAKE_FILES = $(shell dir Makefile /b /s) ALL_MAKE_DIRS = $(sort $(filter-out $(subst /,\,$(abspath ./)),$(patsubst %\,%,$(dir $(MAKE_FILES))))) endif MAKE_DIRS := $(call FILTER_OUT,site-packages,$(call FILTER_OUT,node_modules,$(ALL_MAKE_DIRS))) .PHONY: .clean-error-log .print-error-log MAKE_CMD_MESSAGE = $(if $(MAKECMDGOALS), $(MAKECMDGOALS),) .clean-error-log: @$(rm_file) $(call fix_path,$(mkfile_dir)/make*.log) $(ignore_output) $(ignore_failure) .print-error-log: ifeq ($(suffix $(SHELL)),.exe) @if exist $(call fix_path,$(mkfile_dir)/make_error_dirs.log) ( \ echo Directories failed to make$(MAKE_CMD_MESSAGE): && \ type $(call fix_path,$(mkfile_dir)/make_error_dirs.log) && \ ($(rm_file) $(call fix_path,$(mkfile_dir)/make*.log) $(ignore_output) $(ignore_failure)) && \ exit 1 \ ) else @if [ -e $(call fix_path,$(mkfile_dir)/make_error_dirs.log) ]; then \ echo "\n\033[31;1mDirectories failed to make$(MAKE_CMD_MESSAGE):\033[0m\n"; \ cat $(call fix_path,$(mkfile_dir)/make_error_dirs.log); \ echo ""; \ $(rm_file) $(call fix_path,$(mkfile_dir)/make*.log) $(ignore_output) $(ignore_failure); \ exit 1; \ fi endif .PHONY: $(RECURSIVE_TARGETS) $(MAKE_DIRS) clean: git-clean $(RECURSIVE_TARGETS): .clean-error-log $(MAKE_DIRS) .print-error-log $(MAKE_DIRS): ifdef FAIL_ON_ERROR $(MAKE) -C $@ $(MAKECMDGOALS) IS_RECURSIVE_MAKE=1 else @$(rm_file) $(call fix_path,$@/make*.log) $(ignore_output) $(ignore_failure) @echo make -C $@ $(MAKECMDGOALS) ifeq ($(suffix $(SHELL)),.exe) @$(MAKE) -C $@ $(MAKECMDGOALS) IS_RECURSIVE_MAKE=1 1>$(call fix_path,$@/make.log) $(stderr_redirect_stdout) || \ ( \ (findstr /c:"*** No" $(call fix_path,$@/make.log) ${ignore_output}) || ( \ echo $@ >> $(call fix_path,$(mkfile_dir)/make_error_dirs.log) && \ $(call touch,$@/make_error.log) \ ) \ ) @if exist $(call fix_path,$@/make_error.log) echo make -C $@$(MAKE_CMD_MESSAGE) failed: @if exist $(call fix_path,$@/make_error.log) $(call touch,$@/make_print.log) @if "$(VERBOSE)" neq "0" $(call touch,$@/make_print.log) @if exist $(call fix_path,$@/make_print.log) type $(call fix_path,$@/make.log) else @$(MAKE) -C $@ $(MAKECMDGOALS) IS_RECURSIVE_MAKE=1 1>$(call fix_path,$@/make.log) $(stderr_redirect_stdout) || \ ( \ grep -qF "*** No" $(call fix_path,$@/make.log) || ( \ echo "\t$@" >> $(call fix_path,$(mkfile_dir)/make_error_dirs.log) ; \ $(call touch,$@/make_error.log) ; \ ) \ ) @if [ -e $(call fix_path,$@/make_error.log) ]; then \ echo "\n\033[31;1mmake -C $@$(MAKE_CMD_MESSAGE) failed:\033[0m\n" ; \ fi @if [ "$(VERBOSE)" != "0" -o -e $(call fix_path,$@/make_error.log) ]; then \ cat $(call fix_path,$@/make.log); \ fi endif @$(rm_file) $(call fix_path,$@/make*.log) $(ignore_output) $(ignore_failure) endif # ifdef FAIL_ON_ERROR endif # ifndef IS_RECURSIVE_MAKE include $(mkfile_dir)/shell.mk