unmake
offers various checks to optimize your makefiles.
Note that unmake
does not evaluate makefiles, and therefore ignores quirks arising from macro expansions.
Most warnings feature line numbers with an approximate location of the issue in the makefile.
When a text file suddenly reaches End Of File (EOF) without a Line Feed (LF), then the file is said to not feature a final End Of Line (EOL).
UNIX text files expect each line to terminate in a final End Of Line, including the last line. Omitting a final EOF can cause subtle text processing errors.
PKG = curl<EOF>
PKG = curl<LF>
<EOF>
- Configure EditorConfig and text editors to apply a final EOL.
Prerequisites of this special target are targets themselves; these targets (known as phony targets) shall be considered always out-of-date when the make utility begins executing. If a phony target’s commands are executed, that phony target shall then be considered up-to-date until the execution of make completes. Subsequent occurrences of .PHONY shall also apply these rules to the additional targets. A .PHONY special target with no prerequisites shall be ignored. If the -t option is specified, phony targets shall not be touched. Phony targets shall not be removed if make receives one of the asynchronous events explicitly described in the ASYNCHRONOUS EVENTS section.
--POSIX 202x Issue 8/D3
Briefly, make assumes that most rule targets are actual filenames. However, conventional targets named all
, lint
, install
, uninstall
, publish
, test*
, or clean*
, are usually not actual filenames. These are logical targets.
When make is requested to perform these logical, top-level targets, then make needs to know not to apply the usual file-based caching. The way to do this is by declaring .PHONY:
special rules, whose prerequisites are your logical targets.
You may write logical target declarations as whitespace delimited prerequisites in a single .PHONY:
rule, or distribute logical target declarations among multiple .PHONY:
rules.
As well, aggregate targets like port: cross-compile archive
, that do not have any commands, are usually not actual filenames themselves. Aggregate, commandless targets are also logical targets. Which means that they should also have an entry as a prerequisite in a .PHONY:
special rule. However, make sometimes infers target trees based on common C filename patterns. So we cannot reliably warn on potentially .PHONY
worthy empty-command targets until a complete inferred rule system is implemented.
Due to the variance in artifact names, unmake
cannot automate checking for all possible targets deserving .PHONY
declarations. Neither make
nor unmake
knows this application-specific information. The makefile maintainer should supply this information, and configure any needed .PHONY
declarations accordingly.
all:
echo "Hello World!"
test: test-1 test-2
test-1:
echo "Hello World!"
test-2:
echo "Hi World!"
clean:
-rm -rf bin
.PHONY: all
all:
echo "Hello World!"
.PHONY: test test-1 test-2
test: test-1 test-2
test-1:
echo "Hello World!"
test-2:
echo "Hi World!"
.PHONY: clean
clean:
-rm -rf bin
.PHONY: empty
empty:;
.PHONY: port
port: cross-compile archive
empty:;
port: cross-compile archive
If the hypothetical cross-compile
and archive
targets are themselves merely logical targets rather than filenames, then they should be declared .PHONY
as well.
- Avoid using make build artifacts named
all
,test*
, orclean*
. - Declare any targets named
all
,lint
,install
,uninstall
,publish
,test*
, orclean*
as.PHONY
- Declare command-less rule targets as
.PHONY
- Note that POSIX usually requires a semicolon (
;
) when declaring rules without commands. - Note that special targets like
.NOTPARALLEL
,.PHONY
,.POSIX
,.WAIT
, etc., should not themselves be declared as.PHONY
By default, the following files shall be tried in sequence: ./makefile and ./Makefile.
--POSIX 202x Issue 8/D3
Using the lowercase filename makefile
loads slightly faster than the capitalized filename Makefile
. The lowercase naming reduces strain on filesystem requests.
Makefile:
PKG = curl
makefile:
PKG = curl
- Rename
Makefile
tomakefile
The CURDIR environment variable shall not affect the value of the CURDIR macro unless the -e option is specified. If the -e option is not specified, there is a CURDIR environment variable set, and its value is different from the CURDIR macro value, the environment variable value shall be set to the macro value. If CURDIR is defined in the makefile, present in the MAKEFLAGS environment variable, or specified on the command line, it shall replace the original value of the CURDIR macro in accordance with the logical order described above, but shall not cause make to change its current working directory.
Assignment to CURDIR
does not actually change the current working directory of the make execution.
CURDIR = build
- Avoid assigning the
CURDIR
macro - Note that some commands offer a built-in way to adjust the current directory, e.g.
tar -C <dir>
- Promote complex logic to a dedicated script
make often resets the working directory across successive commands, and across successive rules. Common commands for changing directories, such as cd
, pushd
, and popd
, may not have the desired effect.
Furthermore, push
and popd
are GNU bash extensions to the POSIX sh interpreter standard, and are likely to fail on other machines.
all:
cd foo
all:
pushd foo
all:
popd
- Avoid running makefile commands beginning with
cd
,pushd
, orpopd
- Reduce use of shell implementation-specific commands in makefiles
- Note that some commands offer a built-in way to adjust the current directory, e.g.
tar -C <dir>
- Promote complex logic to a dedicated script
When .WAIT appears as a target, it shall have no effect.
.WAIT
is intended for use as a pseudo-prerequisite marker, in order to customize synchronization logic. .WAIT
behaves as a useless no operation (NOP) when written as a target.
.WAIT:
test: test-1 test-2
test-1:
echo "Hello World!"
test-2:
echo "Hi World!"
test: test-1 .WAIT test-2
test-1:
echo "Hello World!"
test-2:
echo "Hi World!"
test: test-1 test-2
test-1:
echo "Hello World!"
test-2:
echo "Hi World!"
- Use
.WAIT
as an optional pseudo-prerequisite syncronization marker - Avoid declaring
.WAIT
as a target.
A .PHONY special target with no prerequisites shall be ignored.
.PHONY
with no prerequisites behaves as a useless no operation (NOP). When using the special target .PHONY
rule, specify at least one prerequisite.
.PHONY:
foo: foo.c
gcc -o foo foo.c
foo: foo.c
gcc -o foo foo.c
clean:
rm -rf bin
.PHONY: clean
foo: foo.c
gcc -o foo foo.c
clean:
rm -rf bin
- Use
.WAIT
as an optional pseudo-prerequisite syncronization marker - Avoid declaring
.WAIT
as a target.
The .WAIT
pseudo-prerequisite disables asynchronous processing between prerequisites of a specific rule.
.NOTPARALLEL:
disables asyncronous processing for all prerequisites in all rules.
Using both of these special targets simultaneously is unnecessary.
.NOTPARALLEL:
test: test-1 .WAIT test-2
test-1:
echo "Hello World!"
test-2:
echo "Hi World!"
test: test-1 .WAIT test-2
test-1:
echo "Hello World!"
test-2:
echo "Hi World!"
.NOTPARALLEL:
test: test-1 test-2
test-1:
echo "Hello World!"
test-2:
echo "Hi World!"
test: test-1 test-2
test-1:
echo "Hello World!"
test-2:
echo "Hi World!"
- Avoid using
.NOTPARALLEL:
with.WAIT
redundantly. - Redundancy of
.WAIT
with.NOTPARALLEL
is best avoided.
At (@
) elides an individual command from make output. This is useful for reducing log noise.
The .SILENT
special target also elides commands from make output. If the special rule .SILENT:
is declared with no prerequisites, then all make commands globally are silenced. If the special rule .SILENT:
is declared with prerequisite targets, then all commands for those specific targets are silenced.
Using both of these simultaneously is unnecessary.
.SILENT:
lint:
@unmake .
.SILENT: lint
lint:
@unmake .
.SILENT:
lint:
unmake .
lint:
@unmake .
lint:
unmake .
- Avoid using at (
@
) with.SILENT
redundantly. - Redundancy of
.SILENT
with at (@
) is best avoided.
Hyphen-minus (-
) continues makefile execution past soft failure exit codes of an individual command. This is useful for implementing cleanup tasks and other idempotent tasks.
The .IGNORE
special target also continues makefile execution past soft failures. If the special rule .SILENT:
is declared with prerequisite targets, then exit codes for commands for those specific targets are ignored. However, declaring .IGNORE:
with no prerequisites is likely to cause subtle build problems.
Using both -
and .IGNORE
simultaneously is unnecessary.
If the special rule .IGNORE:
is declared with no prerequisites, then exit codes of all make commands globally are ignored. Due to more severe issues with .IGNORE:
declared with no prerequisites, detailed in the GLOBAL_IGNORE
check, the REDUNDANT_IGNORE_MINUS
check does not provide an automatic check for redundant -
with a global .IGNORE:
declaration.
.IGNORE:
clean:
-rm -rf bin
.IGNORE: clean
clean:
-rm -rf bin
IGNORE: clean
clean:
rm -rf bin
clean:
-rm -rf bin
clean:
rm -rf bin
- Note that
.IGNORE:
declared with no prerequisites is likely to cause subtle build problems. - Avoid using hyphen-minus (
-
) with.IGNORE
redundantly. - Redundancy of
.IGNORE
with hyphen-minus (-
) is best avoided.
When the special target rule .IGNORE:
is declared with no prerequisites, then make ignores exit codes for all make commands, for all rules. This is hazardous, and tends to invite file corruption.
Caution: Avoid using .IGNORE:
this way. When using the special target .IGNORE
rule, declare at least one prerequisite.
.IGNORE:
foo: foo.c
gcc -o foo foo.c
clean:
rm -f foo
.IGNORE: clean
foo: foo.c
gcc -o foo foo.c
clean:
rm -f foo
foo: foo.c
gcc -o foo foo.c
clean:
-rm -f foo
foo: foo.c
gcc -o foo foo.c
clean:
rm -f foo
- Avoid using
.IGNORE:
without at least one prerequisite. - Optionally, apply hyphen-minus (
-
) to individual commands.
Using at (@
) or hyphen-minus (-
) command prefixes for several individual commands in a rule can be simplified to a .SILENT
or .IGNORE
declaration respectively.
Due to flexibility needs, this warning emits automatically for rules with at least two or more commands, where all of the commands feature the same at (@
) or hyphen-minus (-
) prefix. Rules with zero or one command, and rules with mixed command prefixes, may not trigger this warning.
We generally recommend using .SILENT
/ .IGNORE
over individual at (@
) / hyphen-minus (-
).
welcome:
-echo foo
-echo bar
-echo baz
welcome:
@echo foo
@echo bar
@echo baz
.IGNORE: welcome
welcome:
echo foo
echo bar
echo baz
.SILENT: welcome
welcome:
echo foo
echo bar
echo baz
.SILENT:
welcome:
echo foo
echo bar
echo baz
- Use
.SILENT
/.IGNORE
targets rather than individual at (@
) / hyphen-minus (-
) targets. - Note that
.IGNORE
may have poor behavior without at least one prerequisite.
The interpretation of targets containing the characters '%' and '"' is implementation-defined.
POSIX make has no portable semantic for percent signs (%
) or double-quotes ("
) in targets or prerequisites. Using these can vendor lock a makefile onto a specific make implementation, and/or trigger build failures.
all: foo%
foo%: foo.c
gcc -o foo% foo.c
all: "foo"
"foo": foo.c
gcc -o "foo" foo.c
- Avoid percents (
%
) and double-quotes ("
), in targets and prerequisites.
When a rule command contains a sharp (#
), then make forwards the comment to the shell interpreter. This can cause the command to fail in multiline commands. This can cause the command to fail in certain shell interpreters. This increases log noise.
foo: foo.c
#build foo
gcc -o foo foo.c
foo: foo.c
@#gcc -o foo foo.c
foo: foo.c
-#gcc -o foo foo.c
foo: foo.c
+#gcc -o foo foo.c
foo: foo.c
gcc \
#output file \
-o foo \
foo.c
foo: foo.c
#build foo
gcc -o foo foo.c
#build foo
foo: foo.c
gcc -o foo foo.c
#output file
foo: foo.c
gcc \
-o foo \
foo.c
foo: foo.c
gcc -o foo foo.c
#foo: foo.c
# gcc -o foo foo.c
<remove rule>
- Move comments up above multiline commands.
- Move comments to the leftmost column, fully dedented.
- Consider removing extraneous lines.
Supplying the same command prefix multiple times is wasteful.
test:
@@+-+--echo "Hello World!"
test:
@+-echo "Hello World!"
test:
echo "Hello World!"
(Any combination of @
, +
, and -
is fine as long as none of the prefix types are duplicated.)
- Remove redundant code.
- Code that is redundant should be removed.
Rule commands consisting of nothing more than at (@
), plus (+
), minus (-
) prefixes, and/or whitespace, can produce spurious results when the essentially empty command is executed. Without any prefixes, blank commands are likely to trigger parse errors.
Blank commands are distinct from blank lines, which normally act as comments.
Blank commands are distinct from rules that are reset to have no commands.
test:
@+-
test:
@+-echo "Hello World!"
test:
@+-echo "Hello World!"
test:;
#test:
<rule removed>
- Give the command something useful to do.
- Remove extraneous code.
After any optional @
/+
/-
prefix modifiers, whitespace leading a command is bad form. In commands, leading whitespace may be a sign of a typo in an earlier multiline instructions.
The earliest whitespace for a command, should consist mainly of the standard one-tab indentation.
Successive lines in a multiline make command commonly may use tabs for visual clarity.
foo:
<tab><space>gcc -o foo foo.c
foo:
<tab>@+-<space>gcc -o foo foo.c
foo:
<tab><no space>gcc -o foo foo.c
foo:
<tab>@+-<no space>gcc -o foo foo.c
foo:
gcc \
-o \
foo \
foo.c
- Verify multiline instruction syntax in earlier commands.
- Avoid inserting whitespace between
@
/+
/-
prefix modifiers and the rest of the command. - Generally, avoid starting commands with whitespace.
- Consider indenting successive lines in a multiline make command with 1 tab (prerequisites) or 2 tabs (commands), for visual clarity.
make generally expects a makefile to define at least one (non-special) rule to provide some action on when running make
. Excepting include files like sys.mk
or *.include.mk
.
makefile:
.POSIX:
PKG = curl
makefile:
.POSIX:
PKG = curl
all:
apt-get install -y $(PKG)
provision.include.mk:
PKG = curl
- Declare at least one non-special rule in most makefiles.
- Rename include files to
*.include.mk
.
make interprets the first non-special rule as the default rule. Apart from non-special targets, the top-most rule is conventionally named all
. This helps to avoid confusion and accidents.
Common include files like sys.mk
and *.include.mk
may export rules, named all
or something else. However, the top-most, non-special default rule semantic still applies, so order include
lines and rule declarations carefully.
makefile:
.POSIX:
build:
echo "Hello World!"
makefile:
.POSIX:
all:
echo "Hello World!"
Optionally, list subsequent rules as prerequisites for the all
target.
makefile:
.POSIX:
all: build
build:
echo "Hello World!"
foo.include.mk:
build:
echo "Hello World!"
- Name the top-most, non-special, default rule
all
. - Note that the order of
include
lines and rule declarations interacts with the top-most, non-special, default rule.
To receive exactly the behavior described in this section, the user shall ensure that a portable makefile shall:
• Include the special target .POSIX
• Omit any special target reserved for implementations (a leading period followed by uppercase letters) that has not been specified by this section
The behavior of make is unspecified if either or both of these conditions are not met.
It is good form to begin most makefiles with a .POSIX:
special target rule marker. This marker instructs make implementations to preserve processing semantics as defined in the POSIX standard without alteration. Omitting the marker may result in unknown behavior. So most makefiles benefit from more predictable behavior by leading with .POSIX:
. You can declare this marker at the very first line of a makefile, or after some blank/comment lines.
However, makefiles named *.include.mk
, designed for simple text inclusion into other makefiles, should omit the .POSIX:
marker.
Also, make distributions commonly install a sys.mk
include file that provides defines a foundational set of macros, include lines, and rules for make implementations. A .POSIX:
marker may not be necessary for make distribution files. As well, files named like GNUmakefile
, that are known to be implementation-specific, should not use this marker. But most any POSIX makefile not named to indicate its intention as an include file, should feature the .POSIX:
marker.
makefile:
PKG = curl
makefile:
.POSIX:
PKG = curl
provision.include.mk:
PKG = curl
GNUmakefile:
PKG = curl
Special targets like .POSIX
and .PHONY
are important, but they may be elided from other passing examples in this document, for brevity.
- Declare
.POSIX:
in most makefiles. - Rename makefiles intended for inclusion to
*.include.mk
. - Avoid declaring
.POSIX:
in makefiles for specific implementations likeGNUmakefile
.
Targets and prerequisites consisting of a leading
<period>
followed by the uppercase letters "POSIX" and then any other characters are reserved for future standardization. Targets and prerequisites consisting of a leading<period>
followed by one or more uppercase letters, that are not described above, are reserved for implementation extensions.
Other than certain special targets, POSIX reserves targets and prerequisites of the form .(A-Z)
... for either future POSIX use, or for implementation-specific extensions.
Generally, such targets are non-portable. However, the user may have simply mistyped a well-known POSIX special target name. Note that typos may trigger parse errors.
.POSIXX:
.TEST:
echo "Hello World!"
test: .TEST-UNIT .TEST-INTEGRATION
.POSIX:
foo: foo.c
gcc -o foo foo.c
test: foo
./foo
test: test-unit test-integration
- Avoid using reserved names in targets or prerequisites.
- Consider pair programming to spot typos.
Linter warnings concerning UB level portability issues tend to carry higher risk compared to other warnings. This is a consequence of the POSIX standard not specifying any particular error handling (or error detection) semantic for make implementations to follow.
In the case of UB, a makefile may trigger an error message during certain project builds, silently skip processing, corrupt files, segfault, fire missiles, and/or any number of undefined behaviors.
If it appears as the first non-comment line in the makefile, make shall process the makefile as specified by this section; otherwise, the behavior of make is unspecified.
When the .POSIX:
rule is used in a makefile, it must be the first thing in the makefile, apart from any blank or commented lines.
Note that common include files like sys.mk
or *.include.mk
, should not present .POSIX:
, as their text necessarily copies as late as some earlier include
line in an outer makefile.
makefile:
PKG = curl
.POSIX:
makefile:
.POSIX:
.POSIX:
provision.include.mk:
.POSIX:
PKG = curl
makefile:
.POSIX:
PKG = curl
provision.include.mk:
PKG = curl
- Move
.POSIX
to the first non-blank, non-commented line in the makefile. - Avoid mixing the
.POSIX
target with other targets in a single rule declaration. - Avoid declaring
.POSIX:
insys.mk
or*.include.mk
. - Avoid declaring
.POSIX:
multiple times.
This standard does not specify precedence between macro definition and include directives. Thus, the behavior of:
include =foo.mk
is unspecified.
Ambiguous include/macro instructions do not have a clear meaning. The instruction may behave as include
the path =foo.mk
, or behave as defining a macro with the name include
and the value foo.mk
. Parsing destabilizes.
include =foo.mk
include=foo.mk
include foo.mk
INCLUDE = include
$(INCLUDE) =foo.mk
PTH = =foo.mk
include $(PTH)
- Avoid using equals (
=
) in path names. - Avoid using lowercase
include
as a macro name. - Consider removing whitespace between macro names and assignment operators.
The result of setting MAKEFLAGS in the Makefile is unspecified.
The MAKEFLAGS
macro is designed as read-only, set aside for make implementations to store command line flags.
POSIX compliant make implementations automatically preserve command line flags with MAKEFLAGS
.
make implementations implicitly forward MAKEFLAGS
to any child $(MAKE)
invocations on behalf of the makefile user.
MAKEFLAGS = -j
all:
$(MAKE) $(MAKEFLAGS) foo.mk
all:
$(MAKE) foo.mk
- Avoid assigning to the
MAKEFLAGS
macro. - Move complex logic to a dedicated script.
The value of the SHELL environment variable shall not be used as a macro and shall not be modified by defining the SHELL macro in a makefile or on the command line.
SHELL
provides low level functionality to make implementation internals. Expanding or assigning this macro is discouraged.
make implementations that use SHELL
, tend to set useful defaults. Overriding the defaults may produce non-portable, fragile makefiles.
Some implementations do not define SHELL
. Assigning a value SHELL
can create an misleading, non-portable impression of makefile behavior.
Due to unmake
not evaluating macro expansions, expansion of the SHELL
macro is not implemented as an automatic check.
Some ancient platforms may present SHELL
with a cmd[.exe]
interpreter. But even Windows Command Prompt, the Chocolatey GNU make interpreter tends to default to a POSIX compliant sh
interpreter suitable for use with makefile commands.
SHELL = sh
all:
$(SHELL) script.sh
all:
${SHELL} script.sh
all:
./script.sh
sh -c "echo $$SHELL"
- Avoid assignments to the
SHELL
makefile macro. - Treat the
SHELL
makefile macro as a private, internal make macro - Note that a distinct
SHELL
environment variable may be available to commands, apart from theSHELL
make macro. - Move complex shell logic to a dedicated shell script.