makefile

make test prints: test is up to date


Why is my make test not working?

I have this simple makefile and when running

# make test
make: 'test' is up to date.

Where comes 'test' is up to date. from!?

makefile

BINARY_HTTP=http

build:
    @go build -o bin/${BINARY_HTTP} cmd/${BINARY_HTTP}/main.go

run: build
ifdef skip-user
    @./bin/${BINARY_HTTP}/main -skip-user
else
    @./bin/${BINARY_HTTP}/main
endif

test:
    @echo "test"
    @go test -count=1 ./test

If the test command in the makefile is changed to something else like wee the command works? Why??

# make wee
test

makefile

BINARY_HTTP=http

build:
    @go build -o bin/${BINARY_HTTP} cmd/${BINARY_HTTP}/main.go

run: build
ifdef skip-user
    @./bin/${BINARY_HTTP}/main -skip-user
else
    @./bin/${BINARY_HTTP}/main
endif

wee:
    @echo "test"
    @go test -count=1 ./test

Solution

  • As evidenced by the last line of your Makefile, you have a file called test lying next to it.

    For make, a rule specifies a target output file, in your case: test. Your test rule also doesn't have any prerequisites.

    So what will happen is when you call make test is that make will check if there's already a file called test which is newer than its prerequisites (there are none). This condition is satisfied and make will happily tell you that it doesn't need to do any work at all.

    Once you rename your rule's target into anything that's not an existing file, the rule will run in a futile attempt to create a file wee. Your recipe doesn't create such a file so this will always run.

    The correct solution is to add the following line to your Makefile:

    .PHONY: test
    

    Quoting from make's doc:

    A phony target is one that is not really the name of a file; rather it is just a name for a recipe to be executed when you make an explicit request. There are two reasons to use a phony target: to avoid a conflict with a file of the same name, and to improve performance.

    Running on request it what you want to do. Avoiding conflict with a file is what you need to do.

    The same logic applies, by the way, to the run target in your Makefile. And your build target should rather specify the output file as in the following untested file! I also omitted the BINARY_HTTP variable for clarity. Please see this as a possible next step towards a clean and idiomatic makefile, not as the end result.

    bin/http: cmd/http/main.go
        @go build -o $@ $<
    
    .PHONY: run
    run: bin/http
    ifdef skip-user
        @./bin/${BINARY_HTTP}/main -skip-user
    else
        @./bin/${BINARY_HTTP}/main
    endif
    
    .PHONY: test
    test: bin/http
        @echo "test"
        @go test -count=1 ./test
    

    Also see What is the purpose of .PHONY in a Makefile?