Search code examples
gomakefileenvironment-variables

Makefile: No such file or directory on an existing file, env variables not loading


I have a Makefile slightly modified to help explain the case better:

include $(wildcard .env)

DB_URL=postgres://$(POSTGRES_USER):$(POSTGRES_PASSWORD)@$(POSTGRES_HOST):$(POSTGRES_PORT)/$(POSTGRES_DB)?sslmode=disable&pool_max_conns=10

test:
    @echo $(DB_URL)
    @cat .env
    @-include .env
    @echo "Running tests"
    @DB=$(DB_URL) go test ./... -v -coverprofile=coverage.out

The first line shows the right DB URL being created, so it's importing the .env file as expected. The second line works too. The third line shows following error:

make: include: No such file or directory
make: [test] Error 1 (ignored)

Now, while I can see my DB URL being present, I don't understand why I cannot see anything in DB. go test showed DB being an empty string. If I export the hardcoded DB url, everything is working as expected.

It would be great if someone could help me understand why this is happening. I'm using MacOS Sonoma. I've been using the same Makefile for a couple weeks, and never changed .env or Makefile, but this stopped working a couple hours back.


Solution

  • Being outside a recipe, this ...

    include $(wildcard .env)
    

    ... is a directive to make.

    Being inside a recipe, this ...

        @-include .env
    

    is a shell command, modulo the @- that suppresses printing the command and does not terminate the recipe on errors. But include is not a shell built-in, and, unsurprisingly, there is no such command in the executable search path, hence:

    make: include: No such file or directory
    

    You continue ...

    Now, while I can see my DB URL being present, I don't understand why I cannot see anything in DB. go test showed DB being an empty string.

    You need to be careful about quoting. make expands $(DB_URL) to a string that ends with sslmode=disable&pool_max_conns=10. The & is significant to the shell as a command separator (that causes the preceding command to be run as a background job). So yes, go test will not see a value for DB in its environment, or at least not because the recipe sets one. The only addition it will see to its environment is pool_max_conns=10, which you didn't even mean for it to get.

    There could be other, similar issues if the URL contained other characters significant to the shell, such as ; or $, among others. As long as the URL does not contain any ' characters, you should be able to do this:

        DB='$(DB_URL)' go test ./... -v -coverprofile=coverage.out
    

    Side note: never suppress command echo in your recipes until you are sure they are working correctly. And maybe not even then.