qmk_firmware

QMK firmware for my keyboards (Corne, Sweep Ferris) and trackball (Ploopy Adept)
Log | Files | Refs | Submodules | LICENSE

Makefile (18308B)


      1 ifndef VERBOSE
      2 .SILENT:
      3 endif
      4 
      5 # Never run this makefile in parallel, as it could screw things up
      6 # It won't affect the submakes, so you still get the speedup from specifying -jx
      7 .NOTPARALLEL:
      8 
      9 # Allow the silent with lower caps to work the same way as upper caps
     10 ifdef silent
     11     SILENT = $(silent)
     12 endif
     13 
     14 ifdef SILENT
     15     SUB_IS_SILENT := $(SILENT)
     16 endif
     17 
     18 # We need to make sure that silent is always turned off at the top level
     19 # Otherwise the [OK], [ERROR] and [WARN] messages won't be displayed correctly
     20 override SILENT := false
     21 
     22 ifeq ($(shell git rev-parse --is-inside-work-tree 2>/dev/null),)
     23     export SKIP_GIT := yes
     24     export NOT_REPO := yes
     25 endif
     26 
     27 ifdef SKIP_VERSION
     28     export SKIP_GIT := yes
     29 endif
     30 
     31 ifndef SUB_IS_SILENT
     32 ifndef SKIP_GIT
     33     QMK_VERSION := $(shell git describe --abbrev=0 --tags 2>/dev/null)
     34 endif
     35 
     36 ifneq ($(QMK_VERSION),)
     37 $(info QMK Firmware $(QMK_VERSION))
     38 endif
     39 endif
     40 
     41 # Try to determine userspace from qmk config, if set.
     42 ifeq ($(QMK_USERSPACE),)
     43     QMK_USERSPACE = $(shell qmk config -ro user.overlay_dir | cut -d= -f2 | sed -e 's@^None$$@@g')
     44 endif
     45 
     46 # Determine which qmk cli to use
     47 QMK_BIN := qmk
     48 
     49 # avoid 'Entering|Leaving directory' messages
     50 MAKEFLAGS += --no-print-directory
     51 
     52 ON_ERROR := error_occurred=1
     53 
     54 BREAK_ON_ERRORS = no
     55 
     56 ROOT_DIR := $(dir $(lastword $(MAKEFILE_LIST)))
     57 ifeq ($(ROOT_DIR),)
     58     ROOT_DIR := .
     59 endif
     60 
     61 include paths.mk
     62 include $(BUILDDEFS_PATH)/support.mk
     63 
     64 TEST_OUTPUT_DIR := $(BUILD_DIR)/test
     65 ERROR_FILE := $(BUILD_DIR)/error_occurred
     66 
     67 .DEFAULT_GOAL := all:all
     68 
     69 
     70 # Compare the start of the RULE variable with the first argument($1)
     71 # If the rules equals $1 or starts with $1:, RULE_FOUND is set to true
     72 #     and $1 is removed from the RULE variable
     73 # Otherwise the RULE_FOUND variable is set to false, and RULE left as it was
     74 # The function is a bit tricky, since there's no built in $(startswith) function
     75 define COMPARE_AND_REMOVE_FROM_RULE_HELPER
     76     ifeq ($1,$$(RULE))
     77         RULE:=
     78         RULE_FOUND := true
     79     else
     80         STARTCOLON_REMOVED=$$(subst START$1:,,START$$(RULE))
     81         ifneq ($$(STARTCOLON_REMOVED),START$$(RULE))
     82             RULE_FOUND := true
     83             RULE := $$(STARTCOLON_REMOVED)
     84         else
     85             RULE_FOUND := false
     86         endif
     87     endif
     88 endef
     89 
     90 # This makes it easier to call COMPARE_AND_REMOVE_FROM_RULE, since it makes it behave like
     91 # a function that returns the value
     92 COMPARE_AND_REMOVE_FROM_RULE = $(eval $(call COMPARE_AND_REMOVE_FROM_RULE_HELPER,$1))$(RULE_FOUND)
     93 
     94 # Try to find a match for the start of the rule to be checked
     95 # $1 The list to be checked
     96 # If a match is found, then RULE_FOUND is set to true
     97 # and MATCHED_ITEM to the item that was matched
     98 define TRY_TO_MATCH_RULE_FROM_LIST_HELPER
     99     # Split on ":", padding with empty strings to avoid indexing issues
    100     TOKEN1:=$$(shell python3 -c "import sys; print((sys.argv[1].split(':',1)+[''])[0])" $$(RULE))
    101     TOKENr:=$$(shell python3 -c "import sys; print((sys.argv[1].split(':',1)+[''])[1])" $$(RULE))
    102 
    103     FOUNDx:=$$(shell echo $1 | tr " " "\n" | grep -Fx $$(TOKEN1))
    104     ifneq ($$(FOUNDx),)
    105         RULE := $$(TOKENr)
    106         RULE_FOUND := true
    107         MATCHED_ITEM := $$(TOKEN1)
    108     else
    109         RULE_FOUND := false
    110         MATCHED_ITEM :=
    111     endif
    112 endef
    113 
    114 # Make it easier to call TRY_TO_MATCH_RULE_FROM_LIST
    115 TRY_TO_MATCH_RULE_FROM_LIST = $(eval $(call TRY_TO_MATCH_RULE_FROM_LIST_HELPER,$1))$(RULE_FOUND)
    116 
    117 # As TRY_TO_MATCH_RULE_FROM_LIST_HELPER, but with additional
    118 # resolution of keyboard_aliases.hjson for provided rule 
    119 define TRY_TO_MATCH_RULE_FROM_LIST_HELPER_KB
    120     # Split on ":", padding with empty strings to avoid indexing issues
    121     TOKEN1:=$$(shell python3 -c "import sys; print((sys.argv[1].split(':',1)+[''])[0])" $$(RULE))
    122     TOKENr:=$$(shell python3 -c "import sys; print((sys.argv[1].split(':',1)+[''])[1])" $$(RULE))
    123 
    124     TOKEN1:=$$(shell $(QMK_BIN) resolve-alias --allow-unknown $$(TOKEN1))
    125 
    126     FOUNDx:=$$(shell echo $1 | tr " " "\n" | grep -Fx $$(TOKEN1))
    127     ifneq ($$(FOUNDx),)
    128         RULE := $$(TOKENr)
    129         RULE_FOUND := true
    130         MATCHED_ITEM := $$(TOKEN1)
    131     else
    132         RULE_FOUND := false
    133         MATCHED_ITEM :=
    134     endif
    135 endef
    136 
    137 # Make it easier to call TRY_TO_MATCH_RULE_FROM_LIST_KB
    138 TRY_TO_MATCH_RULE_FROM_LIST_KB = $(eval $(call TRY_TO_MATCH_RULE_FROM_LIST_HELPER_KB,$1))$(RULE_FOUND)
    139 
    140 define ALL_IN_LIST_LOOP
    141     OLD_RULE$1 := $$(RULE)
    142     $$(eval $$(call $1,$$(ITEM$1)))
    143     RULE := $$(OLD_RULE$1)
    144 endef
    145 
    146 define PARSE_ALL_IN_LIST
    147     $$(foreach ITEM$1,$2,$$(eval $$(call ALL_IN_LIST_LOOP,$1)))
    148 endef
    149 
    150 # The entry point for rule parsing
    151 # parses a rule in the format <keyboard>:<keymap>:<target>
    152 # but this particular function only deals with the first <keyboard> part
    153 define PARSE_RULE
    154     RULE := $1
    155     COMMANDS :=
    156     # If the rule starts with all, then continue the parsing from
    157     # PARSE_ALL_KEYBOARDS
    158     ifeq ($$(call COMPARE_AND_REMOVE_FROM_RULE,all),true)
    159         KEYBOARD_RULE=all
    160         $$(eval $$(call PARSE_ALL_KEYBOARDS))
    161     else ifeq ($$(call COMPARE_AND_REMOVE_FROM_RULE,test),true)
    162         $$(eval $$(call PARSE_TEST))
    163     # If the rule starts with the name of a known keyboard, then continue
    164     # the parsing from PARSE_KEYBOARD
    165     else ifeq ($$(call TRY_TO_MATCH_RULE_FROM_LIST_KB,$$(shell $(QMK_BIN) list-keyboards)),true)
    166         KEYBOARD_RULE=$$(MATCHED_ITEM)
    167         $$(eval $$(call PARSE_KEYBOARD,$$(MATCHED_ITEM)))
    168     else
    169         $$(info make: *** No rule to make target '$1'. Stop.)
    170         $$(info |)
    171         $$(info | QMK's make format is:)
    172         $$(info |     make keyboard_folder:keymap_folder[:target])
    173         $$(info |)
    174         $$(info | Where `keyboard_folder` is the path to the keyboard relative to)
    175         $$(info | `qmk_firmware/keyboards/`, and `keymap_folder` is the name of the)
    176         $$(info | keymap folder under that board's `keymaps/` directory.)
    177         $$(info |)
    178         $$(info | Examples:)
    179         $$(info |     keyboards/dz60, keyboards/dz60/keymaps/default)
    180         $$(info |       -> make dz60:default)
    181         $$(info |       -> qmk compile -kb dz60 -km default)
    182         $$(info |     keyboards/planck/rev6, keyboards/planck/keymaps/default)
    183         $$(info |       -> make planck/rev6:default:flash)
    184         $$(info |       -> qmk flash -kb planck/rev6 -km default)
    185         $$(info |)
    186     endif
    187 endef
    188 
    189 # $1 = Keyboard
    190 # Parses a rule in the format <keymap>:<target>
    191 # the keyboard is already known when entering this function
    192 define PARSE_KEYBOARD
    193     # If we want to compile the default subproject, then we need to
    194     # include the correct makefile to determine the actual name of it
    195     CURRENT_KB := $1
    196 
    197     # 5/4/3/2/1
    198     KEYBOARD_FOLDER_PATH_1 := $$(CURRENT_KB)
    199     KEYBOARD_FOLDER_PATH_2 := $$(patsubst %/,%,$$(dir $$(KEYBOARD_FOLDER_PATH_1)))
    200     KEYBOARD_FOLDER_PATH_3 := $$(patsubst %/,%,$$(dir $$(KEYBOARD_FOLDER_PATH_2)))
    201     KEYBOARD_FOLDER_PATH_4 := $$(patsubst %/,%,$$(dir $$(KEYBOARD_FOLDER_PATH_3)))
    202     KEYBOARD_FOLDER_PATH_5 := $$(patsubst %/,%,$$(dir $$(KEYBOARD_FOLDER_PATH_4)))
    203 
    204     KEYMAPS :=
    205     # get a list of all keymaps
    206     KEYMAPS += $$(notdir $$(patsubst %/.,%,$$(wildcard $(ROOT_DIR)/keyboards/$$(KEYBOARD_FOLDER_PATH_1)/keymaps/*/.)))
    207     KEYMAPS += $$(notdir $$(patsubst %/.,%,$$(wildcard $(ROOT_DIR)/keyboards/$$(KEYBOARD_FOLDER_PATH_2)/keymaps/*/.)))
    208     KEYMAPS += $$(notdir $$(patsubst %/.,%,$$(wildcard $(ROOT_DIR)/keyboards/$$(KEYBOARD_FOLDER_PATH_3)/keymaps/*/.)))
    209     KEYMAPS += $$(notdir $$(patsubst %/.,%,$$(wildcard $(ROOT_DIR)/keyboards/$$(KEYBOARD_FOLDER_PATH_4)/keymaps/*/.)))
    210     KEYMAPS += $$(notdir $$(patsubst %/.,%,$$(wildcard $(ROOT_DIR)/keyboards/$$(KEYBOARD_FOLDER_PATH_5)/keymaps/*/.)))
    211 
    212     ifneq ($(QMK_USERSPACE),)
    213         KEYMAPS += $$(notdir $$(patsubst %/.,%,$$(wildcard $(QMK_USERSPACE)/keyboards/$$(KEYBOARD_FOLDER_PATH_1)/keymaps/*/.)))
    214         KEYMAPS += $$(notdir $$(patsubst %/.,%,$$(wildcard $(QMK_USERSPACE)/keyboards/$$(KEYBOARD_FOLDER_PATH_2)/keymaps/*/.)))
    215         KEYMAPS += $$(notdir $$(patsubst %/.,%,$$(wildcard $(QMK_USERSPACE)/keyboards/$$(KEYBOARD_FOLDER_PATH_3)/keymaps/*/.)))
    216         KEYMAPS += $$(notdir $$(patsubst %/.,%,$$(wildcard $(QMK_USERSPACE)/keyboards/$$(KEYBOARD_FOLDER_PATH_4)/keymaps/*/.)))
    217         KEYMAPS += $$(notdir $$(patsubst %/.,%,$$(wildcard $(QMK_USERSPACE)/keyboards/$$(KEYBOARD_FOLDER_PATH_5)/keymaps/*/.)))
    218     endif
    219 
    220     KEYBOARD_LAYOUTS := $(shell $(QMK_BIN) list-layouts --keyboard $1)
    221     LAYOUT_KEYMAPS :=
    222     $$(foreach LAYOUT,$$(KEYBOARD_LAYOUTS),$$(eval LAYOUT_KEYMAPS += $$(notdir $$(patsubst %/.,%,$$(wildcard $(ROOT_DIR)/layouts/*/$$(LAYOUT)/*/.)))))
    223     ifneq ($(QMK_USERSPACE),)
    224         $$(foreach LAYOUT,$$(KEYBOARD_LAYOUTS),$$(eval LAYOUT_KEYMAPS += $$(notdir $$(patsubst %/.,%,$$(wildcard $(QMK_USERSPACE)/layouts/$$(LAYOUT)/*/.)))))
    225     endif
    226 
    227     KEYMAPS := $$(sort $$(KEYMAPS) $$(LAYOUT_KEYMAPS))
    228 
    229     # if the rule after removing the start of it is empty (we haven't specified a kemap or target)
    230     # compile all the keymaps
    231     ifeq ($$(RULE),)
    232         $$(eval $$(call PARSE_ALL_KEYMAPS))
    233     # The same if all was specified
    234     else ifeq ($$(call COMPARE_AND_REMOVE_FROM_RULE,all),true)
    235         $$(eval $$(call PARSE_ALL_KEYMAPS))
    236     # List all keymaps for the given keyboard
    237     else ifeq ($$(call COMPARE_AND_REMOVE_FROM_RULE,list-keymaps),true)
    238         $$(eval $$(call LIST_ALL_KEYMAPS))
    239     # Try to match the specified keyamp with the list of known keymaps
    240     else ifeq ($$(call TRY_TO_MATCH_RULE_FROM_LIST,$$(KEYMAPS)),true)
    241         $$(eval $$(call PARSE_KEYMAP,$$(MATCHED_ITEM)))
    242     # Otherwise try to match the keymap from the current folder, or arguments to the make command
    243     else ifneq ($$(KEYMAP),)
    244         $$(eval $$(call PARSE_KEYMAP,$$(KEYMAP)))
    245     # Otherwise if we are running make all:<user> just skip
    246     else ifeq ($$(KEYBOARD_RULE),all)
    247         # $$(info Skipping: No user keymap for $$(CURRENT_KB))
    248     # Otherwise, make all keymaps, again this is consistent with how it works without
    249     # any arguments
    250     else
    251         $$(eval $$(call PARSE_ALL_KEYMAPS))
    252     endif
    253 endef
    254 
    255 # if we are going to compile all keyboards, match the rest of the rule
    256 # for each of them
    257 define PARSE_ALL_KEYBOARDS
    258     $$(eval $$(call PARSE_ALL_IN_LIST,PARSE_KEYBOARD,$(shell $(QMK_BIN) list-keyboards)))
    259 endef
    260 
    261 # Prints a list of all known keymaps for the given keyboard
    262 define LIST_ALL_KEYMAPS
    263     COMMAND_true_LIST_KEYMAPS := \
    264         printf "$$(KEYMAPS)\n";
    265     COMMAND_false_LIST_KEYMAPS := \
    266         printf "$$(MSG_AVAILABLE_KEYMAPS)\n"; \
    267         printf "$$(KEYMAPS)\n";
    268     COMMANDS += LIST_KEYMAPS
    269 endef
    270 
    271 # $1 Keymap
    272 # This is the meat of compiling a keyboard, when entering this, everything is known
    273 # keyboard, subproject, and keymap
    274 # Note that we are not directly calling the command here, but instead building a list,
    275 # which will later be processed
    276 define PARSE_KEYMAP
    277     CURRENT_KM = $1
    278     # The rest of the rule is the target
    279     # Remove the leading ":" from the target, as it acts as a separator
    280     MAKE_TARGET := $$(patsubst :%,%,$$(RULE))
    281     # We need to generate an unique identifier to append to the COMMANDS list
    282     CURRENT_KB_UNDER := $$(subst /,_,$$(CURRENT_KB))
    283     COMMAND := COMMAND_KEYBOARD_$$(CURRENT_KB_UNDER)_KEYMAP_$$(CURRENT_KM)
    284     # If we are compiling a keyboard without a subproject, we want to display just the name
    285     # of the keyboard, otherwise keyboard/subproject
    286     KB_SP := $$(CURRENT_KB)
    287     # Format it in bold
    288     KB_SP := $(BOLD)$$(KB_SP)$(NO_COLOR)
    289     # Specify the variables that we are passing forward to submake
    290     MAKE_VARS := KEYBOARD=$$(CURRENT_KB) KEYMAP=$$(CURRENT_KM) QMK_BIN=$$(QMK_BIN)
    291     # And the first part of the make command
    292     MAKE_CMD := $$(MAKE) -r -R -C $(ROOT_DIR) -f $(BUILDDEFS_PATH)/build_keyboard.mk $$(MAKE_TARGET)
    293     # The message to display
    294     MAKE_MSG := $$(MSG_MAKE_KB)
    295     # We run the command differently, depending on if we want more output or not
    296     # The true version for silent output and the false version otherwise
    297     $$(eval $$(call BUILD))
    298 endef
    299 
    300 define BUILD
    301     MAKE_VARS += VERBOSE=$(VERBOSE) COLOR=$(COLOR)
    302     COMMANDS += $$(COMMAND)
    303     COMMAND_true_$$(COMMAND) := \
    304         printf "$$(MAKE_MSG)" | \
    305         $$(MAKE_MSG_FORMAT); \
    306         LOG=$$$$($$(MAKE_CMD) $$(MAKE_VARS) SILENT=true 2>&1) ; \
    307         if [ $$$$? -gt 0 ]; \
    308             then $$(PRINT_ERROR_PLAIN); \
    309         elif [ "$$$$LOG" = "skipped" ] ; \
    310             then $$(PRINT_SKIPPED_PLAIN); \
    311         elif [ "$$$$LOG" != "" ] ; \
    312             then $$(PRINT_WARNING_PLAIN); \
    313         else \
    314             $$(PRINT_OK); \
    315         fi;
    316     COMMAND_false_$$(COMMAND) := \
    317         printf "$$(MAKE_MSG)\n\n"; \
    318         $$(MAKE_CMD) $$(MAKE_VARS) SILENT=false; \
    319         if [ $$$$? -gt 0 ]; \
    320             then error_occurred=1; \
    321         fi;
    322 endef
    323 
    324 # Just parse all the keymaps for a specific keyboard
    325 define PARSE_ALL_KEYMAPS
    326     $$(eval $$(call PARSE_ALL_IN_LIST,PARSE_KEYMAP,$$(KEYMAPS)))
    327 endef
    328 
    329 define BUILD_TEST
    330     TEST_PATH := $1
    331     TEST_NAME := $$(notdir $$(TEST_PATH))
    332     TEST_FULL_NAME := $$(subst /,_,$$(patsubst $$(ROOT_DIR)tests/%,%,$$(TEST_PATH)))
    333     MAKE_TARGET := $2
    334     COMMAND := $1
    335     MAKE_CMD := $$(MAKE) -r -R -C $(ROOT_DIR) -f $(BUILDDEFS_PATH)/build_test.mk $$(MAKE_TARGET)
    336     MAKE_VARS := TEST=$$(TEST_NAME) TEST_OUTPUT=$$(TEST_FULL_NAME) TEST_PATH=$$(TEST_PATH) FULL_TESTS="$$(FULL_TESTS)"
    337     MAKE_MSG := $$(MSG_MAKE_TEST)
    338     $$(eval $$(call BUILD))
    339     ifneq ($$(MAKE_TARGET),clean)
    340         TEST_EXECUTABLE := $$(TEST_OUTPUT_DIR)/$$(TEST_FULL_NAME).elf
    341         TESTS += $$(TEST_FULL_NAME)
    342         TEST_MSG := $$(MSG_TEST)
    343         $$(TEST_FULL_NAME)_COMMAND := \
    344             printf "$$(TEST_MSG)\n"; \
    345             $$(TEST_EXECUTABLE); \
    346             if [ $$$$? -gt 0 ]; \
    347                 then error_occurred=1; \
    348             fi; \
    349             printf "\n";
    350     endif
    351 endef
    352 
    353 define LIST_TEST
    354     include $(BUILDDEFS_PATH)/testlist.mk
    355     FOUND_TESTS := $$(patsubst ./tests/%,%,$$(TEST_LIST))
    356     $$(info $$(FOUND_TESTS))
    357 endef
    358 
    359 define PARSE_TEST
    360     TESTS :=
    361     TEST_NAME := $$(firstword $$(subst :, ,$$(RULE)))
    362     TEST_TARGET := $$(subst $$(TEST_NAME),,$$(subst $$(TEST_NAME):,,$$(RULE)))
    363     include $(BUILDDEFS_PATH)/testlist.mk
    364     ifeq ($$(TEST_NAME),all)
    365         MATCHED_TESTS := $$(TEST_LIST)
    366     else
    367         MATCHED_TESTS := $$(foreach TEST, $$(TEST_LIST),$$(if $$(findstring x$$(TEST_NAME)x, x$$(patsubst ./tests/%,%,$$(TEST)x)), $$(TEST),))
    368     endif
    369     $$(foreach TEST,$$(MATCHED_TESTS),$$(eval $$(call BUILD_TEST,$$(TEST),$$(TEST_TARGET))))
    370 endef
    371 
    372 
    373 # Set the silent mode depending on if we are trying to compile multiple keyboards or not
    374 # By default it's on in that case, but it can be overridden by specifying silent=false
    375 # from the command line
    376 define SET_SILENT_MODE
    377     ifdef SUB_IS_SILENT
    378         SILENT_MODE := $(SUB_IS_SILENT)
    379     else ifeq ($$(words $$(COMMANDS)),1)
    380         SILENT_MODE := false
    381     else
    382         SILENT_MODE := true
    383     endif
    384 endef
    385 
    386 include $(BUILDDEFS_PATH)/message.mk
    387 
    388 ifeq ($(strip $(BREAK_ON_ERRORS)), yes)
    389 HANDLE_ERROR = exit 1
    390 else
    391 HANDLE_ERROR = echo $$error_occurred > $(ERROR_FILE)
    392 endif
    393 
    394 # The empty line is important here, as it will force a new shell to be created for each command
    395 # Otherwise the command line will become too long with a lot of keyboards and keymaps
    396 define RUN_COMMAND
    397 +error_occurred=0;\
    398 $(COMMAND_$(SILENT_MODE)_$(COMMAND))\
    399 if [ $$error_occurred -gt 0 ]; then $(HANDLE_ERROR); fi;
    400 
    401 
    402 endef
    403 define RUN_TEST
    404 +error_occurred=0;\
    405 $($(TEST)_COMMAND)\
    406 if [ $$error_occurred -gt 0 ]; then $(HANDLE_ERROR); fi;
    407 
    408 
    409 endef
    410 
    411 # Catch everything and parse the command line ourselves.
    412 .PHONY: %
    413 %:
    414 	# Ensure that $(QMK_BIN) works.
    415 	if ! $(QMK_BIN) hello 1> /dev/null 2>&1; then printf "$(MSG_PYTHON_MISSING)"; exit 1; fi
    416 ifdef NOT_REPO
    417 	printf "$(MSG_NOT_REPO)"
    418 endif
    419 ifndef SKIP_GIT
    420 	$(QMK_BIN) git-submodule --sync
    421 	# Check if the submodules are dirty, and display a warning if they are
    422 	if ! $(QMK_BIN) git-submodule --check 1> /dev/null 2>&1; then printf "$(MSG_SUBMODULE_DIRTY)"; fi
    423 endif
    424 	rm -f $(ERROR_FILE) > /dev/null 2>&1
    425 	$(eval $(call PARSE_RULE,$@))
    426 	$(eval $(call SET_SILENT_MODE))
    427 	# Run all the commands in the same shell, notice the + at the first line
    428 	# it has to be there to allow parallel execution of the submake
    429 	# This always tries to compile everything, even if error occurs in the middle
    430 	# But we return the error code at the end, to trigger travis failures
    431 	# The sort at this point is to remove duplicates
    432 	$(foreach COMMAND,$(sort $(COMMANDS)),$(RUN_COMMAND))
    433 	if [ -f $(ERROR_FILE) ]; then printf "$(MSG_ERRORS)" & exit 1; fi;
    434 	$(foreach TEST,$(sort $(TESTS)),$(RUN_TEST))
    435 	if [ -f $(ERROR_FILE) ]; then printf "$(MSG_ERRORS)" & exit 1; fi;
    436 
    437 lib/%:
    438 	git submodule sync $?
    439 	git submodule update --init $?
    440 
    441 .PHONY: git-submodule
    442 git-submodule:
    443 	$(QMK_BIN) git-submodule
    444 
    445 .PHONY: git-submodules
    446 git-submodules: git-submodule
    447 
    448 .PHONY: list-keyboards
    449 list-keyboards:
    450 	$(QMK_BIN) list-keyboards | tr '\n' ' '
    451 
    452 .PHONY: list-tests
    453 list-tests:
    454 	$(eval $(call LIST_TEST))
    455 
    456 .PHONY: generate-keyboards-file
    457 generate-keyboards-file:
    458 	$(QMK_BIN) list-keyboards
    459 
    460 .PHONY: clean
    461 clean:
    462 	echo -n 'Deleting .build/ ... '
    463 	rm -rf $(BUILD_DIR)
    464 	echo 'done.'
    465 
    466 .PHONY: distclean distclean_qmk
    467 distclean: distclean_qmk
    468 distclean_qmk: clean
    469 	echo -n 'Deleting *.bin, *.hex, and *.uf2 ... '
    470 	rm -f *.bin *.hex *.uf2
    471 	echo 'done.'
    472 
    473 ifneq ($(QMK_USERSPACE),)
    474 .PHONY: distclean_userspace
    475 distclean: distclean_userspace
    476 distclean_userspace: clean
    477 	echo -n 'Deleting userspace *.bin, *.hex, and *.uf2 ... '
    478 	rm -f $(QMK_USERSPACE)/*.bin $(QMK_USERSPACE)/*.hex $(QMK_USERSPACE)/*.uf2
    479 	echo 'done.'
    480 endif
    481 
    482 # Extra targets for formatting and/or pytest, running within the qmk/qmk_cli container to match GHA.
    483 CONTAINER_PREAMBLE := export HOME="/tmp"; export PATH="/tmp/.local/bin:\$$PATH"; python3 -m pip install --upgrade pip; python3 -m pip install -r requirements-dev.txt
    484 
    485 .PHONY: format-core
    486 format-core:
    487 	RUNTIME=docker ./util/docker_cmd.sh bash -lic "$(CONTAINER_PREAMBLE); qmk format-c --core-only -a && qmk format-python -a"
    488 
    489 .PHONY: pytest
    490 pytest:
    491 	RUNTIME=docker ./util/docker_cmd.sh bash -lic "$(CONTAINER_PREAMBLE); qmk pytest"
    492 
    493 .PHONY: format-and-pytest
    494 format-and-pytest:
    495 	RUNTIME=docker ./util/docker_cmd.sh bash -lic "$(CONTAINER_PREAMBLE); qmk format-c --core-only -a && qmk format-python -a && qmk pytest"