Keywords: Makefile | variable validation | error function | build abort | conditional check
Abstract: This article provides an in-depth exploration of various methods for validating variable settings in Makefiles. It begins with the simple approach using GNU Make's built-in error function, then extends to a generic check_defined helper function supporting multiple variable checks and custom error messages. The paper analyzes the logic for determining variable definition status, compares the behaviors of the value and origin functions, and examines target-specific validation mechanisms, including in-recipe calls and implementation through special targets. Finally, it discusses the pros and cons of each method, offering practical recommendations for different scenarios.
In Makefile development, ensuring that critical variables are properly set is fundamental to build process reliability. When variables are undefined or empty, promptly aborting the build can prevent subsequent errors. This article systematically introduces multiple validation methods, from simple implementations to advanced techniques.
Basic Validation Using the error Function
GNU Make provides the error function, which can directly abort the build during parsing. This is the simplest and most direct validation method:
ifndef MY_FLAG
$(error MY_FLAG is not set)
endif
Key considerations: These lines must not be indented, meaning no tabs should precede them. In Makefiles, indentation indicates the start of a recipe, while conditional directives must be outside recipes.
Design of a Generic Validation Function
For scenarios requiring validation of multiple variables, defining a helper function is more efficient. The following check_defined function supports batch validation and custom error messages:
# Check that given variables are set and have non-empty values,
# die with an error otherwise.
# Params:
# 1. Variable name(s) to test.
# 2. (optional) Error message to print.
check_defined = \
$(strip $(foreach 1,$1, \
$(call __check_defined,$1,$(strip $(value 2)))))
__check_defined = \
$(if $(value $1),, \
$(error Undefined $1$(if $2, ($2))))
Usage examples:
$(call check_defined, MY_FLAG)
$(call check_defined, OUT_DIR, build directory)
$(call check_defined, \
LIB_INCLUDE_DIR \
LIB_SOURCE_DIR, \
library path)
Error output format: Makefile:17: *** Undefined OUT_DIR (build directory). Stop.
In-depth Analysis of Variable State Determination
The core validation logic $(if $(value $1),,$(error ...)) mimics the behavior of ifndef: treating empty-valued variables as "undefined." However, variable type influences this:
# These are considered UNDEFINED by ifndef and check_defined:
explicitly_empty =
simple_empty := $(explicitly_empty)
# This is considered OK (defined):
recursive_empty = $(explicitly_empty)
Recursive variables are considered defined even if they expand to empty. For more precise determination, the origin function can be used:
$(if $(filter undefined,$(origin $1)),$(error ...))
This method distinguishes between "undefined" and "defined as empty." Additionally, for directory path validation, the wildcard function can check actual existence.
Target-Specific Validation Mechanisms
Sometimes variables only need validation when specific targets are executed. Two main implementation approaches exist.
In-Recipe Validation Calls
Embed validation directly into target recipes:
foo :
@:$(call check_defined, BAR, baz value)
@ turns off command echoing, and : is a shell no-op command. The function can be enhanced to display the target name:
__check_defined = \
$(if $(value $1),, \
$(error Undefined $1$(if $2, ($2))$(if $(value @), \
required by target `$@')))
Error message becomes: Makefile:7: *** Undefined BAR (baz value) required by target `foo'. Stop.
Validation Through Special Targets
Define pattern rules as order-only prerequisites:
# Check that a variable specified through the stem is defined and has
# a non-empty value, die with an error otherwise.
check-defined-% : __check_defined_FORCE
@:$(call check_defined, $*, target-specific)
.PHONY : __check_defined_FORCE
__check_defined_FORCE :
Usage:
foo :|check-defined-BAR
The | indicates an order-only prerequisite, ensuring validation executes before the target recipe but does not affect dependency relationships. This approach offers cleaner syntax but has two limitations: inability to customize error messages; running make -t (instead of execution) creates check-defined-... files in the root directory, as pattern rules cannot be declared .PHONY.
Method Comparison and Selection Recommendations
For simple cases, directly using the error function is recommended, as it yields the most concise code. When multiple variables need validation, the check_defined function provides good abstraction. In target-specific validation, in-recipe calls offer more flexibility with custom error messages; the special target method has more elegant syntax but requires awareness of its limitations.
Practical selection should consider: validation timing (parsing phase vs. execution phase), error message detail level, and compatibility with existing Makefile structures. Regardless of the method, explicit variable validation significantly enhances build system robustness.