Skip to content

Latest commit

 

History

History
1150 lines (769 loc) · 24.5 KB

WARNINGS.md

File metadata and controls

1150 lines (769 loc) · 24.5 KB

WARNINGS

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.

General

MISSING_FINAL_EOL

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.

Fail

PKG = curl<EOF>

Pass

PKG = curl<LF>
<EOF>

Mitigation

  • Configure EditorConfig and text editors to apply a final EOL.

PHONY_TARGET

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.

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.

Fail

all:
	echo "Hello World!"
test: test-1 test-2

test-1:
	echo "Hello World!"

test-2:
	echo "Hi World!"
clean:
	-rm -rf bin
empty:;
port: cross-compile archive

Pass

.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

If cross-compile and archive are also logical targets, then they should be declared .PHONY as well.

Mitigation

  • Avoid using make build artifacts named all, test*, or clean*.
  • Declare any targets named all, lint, install, uninstall, publish, test*, or clean* 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

MAKEFILE_PRECEDENCE

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.

Fail

Makefile:

PKG = curl

Pass

makefile:

PKG = curl

Mitigation

  • Rename Makefile to makefile

CURDIR_ASSIGNMENT_NOP

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.

Fail

CURDIR = build

Mitigation

  • 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

WD_NOP

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.

Fail

all:
	cd foo
all:
	pushd foo
all:
	popd

Mitigation

  • Avoid running makefile commands beginning with cd, pushd, or popd
  • 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

WAIT_NOP

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.

Fail

.WAIT:

test: test-1 test-2

test-1:
	echo "Hello World!"

test-2:
	echo "Hi World!"

Pass

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!"

Mitigation

  • Use .WAIT as an optional pseudo-prerequisite syncronization marker
  • Avoid declaring .WAIT as a target.

PHONY_NOP

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.

Fail

.PHONY:

foo: foo.c
	gcc -o foo foo.c

Pass

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

Mitigation

  • Use .WAIT as an optional pseudo-prerequisite syncronization marker
  • Avoid declaring .WAIT as a target.

REDUNDANT_NOTPARALLEL_WAIT

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.

Fail

.NOTPARALLEL:

test: test-1 .WAIT test-2

test-1:
	echo "Hello World!"

test-2:
	echo "Hi World!"

Pass

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!"

Mitigation

  • Avoid using .NOTPARALLEL: with .WAIT redundantly.
  • Redundancy of .WAIT with .NOTPARALLEL is best avoided.

REDUNDANT_SILENT_AT

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.

Fail

.SILENT:

lint:
	@unmake .
.SILENT: lint

lint:
	@unmake .

Pass

.SILENT:

lint:
	unmake .
lint:
	@unmake .
lint:
	unmake .

Mitigation

  • Avoid using at (@) with .SILENT redundantly.
  • Redundancy of .SILENT with at (@) is best avoided.

REDUNDANT_IGNORE_MINUS

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.

Fail

.IGNORE:

clean:
	-rm -rf bin
.IGNORE: clean

clean:
	-rm -rf bin

Pass

IGNORE: clean

clean:
	rm -rf bin
clean:
	-rm -rf bin
clean:
	rm -rf bin

Mitigation

  • 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.

GLOBAL_IGNORE

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.

Fail

.IGNORE:

foo: foo.c
	gcc -o foo foo.c

clean:
	rm -f foo

Pass

.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

Mitigation

  • Avoid using .IGNORE: without at least one prerequisite.
  • Optionally, apply hyphen-minus (-) to individual commands.

SIMPLIFY_AT / SIMPLIFY_MINUS

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 (-).

Fail

welcome:
	-echo foo
    -echo bar
    -echo baz
welcome:
	@echo foo
    @echo bar
    @echo baz

Pass

.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

Mitigation

  • Use .SILENT / .IGNORE targets rather than individual at (@) / hyphen-minus (-) targets.
  • Note that .IGNORE may have poor behavior without at least one prerequisite.

IMPLEMENTATION_DEFINED_TARGET

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.

Fail

all: foo%

foo%: foo.c
	gcc -o foo% foo.c
all: "foo"

"foo": foo.c
	gcc -o "foo" foo.c

Mitigation

  • Avoid percents (%) and double-quotes ("), in targets and prerequisites.

COMMAND_COMMENT

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.

Fail

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

Pass

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>

Mitigation

  • Move comments up above multiline commands.
  • Move comments to the leftmost column, fully dedented.
  • Consider removing extraneous lines.

REPEATED_COMMAND_PREFIX

Supplying the same command prefix multiple times is wasteful.

Fail

test:
	@@+-+--echo "Hello World!"

Pass

test:
	@+-echo "Hello World!"
test:
	echo "Hello World!"

(Any combination of @, +, and - is fine as long as none of the prefix types are duplicated.)

Mitigation

  • Remove redundant code.
  • Code that is redundant should be removed.

BLANK_COMMAND

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.

Fail

test:
	@+-

Pass

test:
	@+-echo "Hello World!"
test:
	@+-echo "Hello World!"
test:;
#test:
<rule removed>

Mitigation

  • Give the command something useful to do.
  • Remove extraneous code.

WHITESPACE_LEADING_COMMAND

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.

Fail

foo:
<tab><space>gcc -o foo foo.c
foo:
<tab>@+-<space>gcc -o foo foo.c

Pass

foo:
<tab><no space>gcc -o foo foo.c
foo:
<tab>@+-<no space>gcc -o foo foo.c
foo:
	gcc \
		-o \
		foo \
		foo.c

Mitigation

  • 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.

NO_RULES

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.

Fail

makefile:

.POSIX:
PKG = curl

Pass

makefile:

.POSIX:
PKG = curl

all:
	apt-get install -y $(PKG)

provision.include.mk:

PKG = curl

Mitigation

  • Declare at least one non-special rule in most makefiles.
  • Rename include files to *.include.mk.

RULE_ALL

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.

Fail

makefile:

.POSIX:

build:
	echo "Hello World!"

Pass

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!"

Mitigation

  • 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.

STRICT_POSIX

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.

Fail

makefile:

PKG = curl

Pass

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.

Mitigation

  • Declare .POSIX: in most makefiles.
  • Rename makefiles intended for inclusion to *.include.mk.
  • Avoid declaring .POSIX: in makefiles for specific implementations like GNUmakefile.

RESERVED_TARGET

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.

Fail

.POSIXX:
.TEST:
	echo "Hello World!"
test: .TEST-UNIT .TEST-INTEGRATION

Pass

.POSIX:
foo: foo.c
	gcc -o foo foo.c

test: foo
	./foo
test: test-unit test-integration

Mitigation

  • Avoid using reserved names in targets or prerequisites.
  • Consider pair programming to spot typos.

Undefined Behavior (UB)

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.

UB_LATE_POSIX_MARKER

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.

Fail

makefile:

PKG = curl
.POSIX:

makefile:

.POSIX:
.POSIX:

provision.include.mk:

.POSIX:
PKG = curl

Pass

makefile:

.POSIX:
PKG = curl

provision.include.mk:

PKG = curl

Mitigation

  • 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: in sys.mk or *.include.mk.
  • Avoid declaring .POSIX: multiple times.

UB_AMBIGUOUS_INCLUDE

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.

Fail

include =foo.mk

Pass

include=foo.mk
include foo.mk
INCLUDE = include
$(INCLUDE) =foo.mk
PTH = =foo.mk
include $(PTH)

Mitigation

  • Avoid using equals (=) in path names.
  • Avoid using lowercase include as a macro name.
  • Consider removing whitespace between macro names and assignment operators.

UB_MAKEFLAGS_ASSIGNMENT

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.

Fail

MAKEFLAGS = -j

all:
	$(MAKE) $(MAKEFLAGS) foo.mk

Pass

all:
	$(MAKE) foo.mk

Mitigation

  • Avoid assigning to the MAKEFLAGS macro.
  • Move complex logic to a dedicated script.

UB_SHELL_MACRO

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.

Fail

SHELL = sh
all:
	$(SHELL) script.sh
all:
	${SHELL} script.sh

Pass

all:
	./script.sh
	sh -c "echo $$SHELL"

Mitigation

  • 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 the SHELL make macro.
  • Move complex shell logic to a dedicated shell script.