How to load and export variables from an .env file in Makefile?
Asked Answered
B

7

37

What is the best way to use a .env in a Makefile, i.e. loading that file and exporting all variables for subshells in make?

It would be great if the proposed solution would work with make only, e.g. not using any third party tools. Also .env files support multiline variables like:

FOO="this\nis\na\nmultiline\nvar"

this is why this solution is probably not adequate.

Busboy answered 19/6, 2017 at 10:32 Comment(4)
include works.Hollerman
Not really. It might load the variables in most cases properly, but it does not automatically export them as environment for commands in sub shells right?Busboy
If you're talking about GNU make, there's probably some very complex way to achieve this using the string functions and $(eval ), but it doesn't sound like a good idea. There might be a much more straight-forward solution if you would state what's the goal you want to achieve by exporting these variables...Crenation
The idea is to follow the 12 factor app ideas, having an .env file that contains all the environment variables needed for build/deployment configuration. Since make is the tool that orchestrates the build/deployment, it would be nice if I could easily use this .env without external tools.Busboy
H
24

Make does not offer any way to read a content of the file to some variable. So, I consider it impossible to achieve the result without using external tools. However, if I am wrong, I'd be glad to learn some new trick.

So, let's assume there are two files, .env, being a technically correct shell file:

FOO=bar

BAR="notfoo" # comment
   #comment
MULTILINE="This\nis\nSparta!"
# comment

and script.sh:

#!/bin/bash
echo FOO=${FOO}
echo BAR=${BAR}
echo -e ${MULTILINE}

One solution is to include the .env file, then make sure variables are exported:

include .env

$(eval export $(shell sed -ne 's/ *#.*$$//; /./ s/=.*$$// p' .env))

all:
    ./script.sh

Because of different treatment of quotes by shell and make, you will see the quotes in output.

You can avoid that by reprocessing the variables by make:

include .env

VARS:=$(shell sed -ne 's/ *\#.*$$//; /./ s/=.*$$// p' .env )
$(foreach v,$(VARS),$(eval $(shell echo export $(v)="$($(v))")))

all:
    ./script.sh

but then the multiline variable will become a one-liner.

Finally, you can generate a temporary file to be processed by bash and source it before any command is run:

SHELL=bash

all: .env-export
    . .env-export && ./script.sh

.env-export: .env
    sed -ne '/^export / {p;d}; /.*=/ s/^/export / p' .env > .env-export

Oh, new lines got messed in this case in multiline variable. You need to additionally quote them.

Finally, you can add export to .env using above sed command, and do:

SHELL=bash
%: .env-export
    . .env-export && make -f secondary "$@"
Hollerman answered 19/6, 2017 at 18:7 Comment(3)
Alright, I think you convinced me to use an external tool. Thank you for the elaborate answer!Busboy
The whole treatment of quotes in shell vs make makes it really messy. One other solution seems to be to prepend each command with . .env; , i.e. . env; echo $FOOBusboy
In example given by you $FOO is expanded by the same shell sourcing .env file. To run . .env; ./script.sh variables need to be already exported in .env file, to be visisble inside ./script.sh.Hollerman
H
28

Found this and it worked great:

at top of makefile

ifneq (,$(wildcard ./.env))
    include .env
    export
endif

Then you have make variables for all your env, for example MY_VAR use as $(MY_VAR)

Ha answered 11/1, 2022 at 8:32 Comment(1)
this works, but I had to do some googling to understand how ... so for anyone else ... ifneq + wildcard is a typical way to check a file exists; include .env imports .env into makefile variables, and export without a parameter exports all variablesCaty
H
24

Make does not offer any way to read a content of the file to some variable. So, I consider it impossible to achieve the result without using external tools. However, if I am wrong, I'd be glad to learn some new trick.

So, let's assume there are two files, .env, being a technically correct shell file:

FOO=bar

BAR="notfoo" # comment
   #comment
MULTILINE="This\nis\nSparta!"
# comment

and script.sh:

#!/bin/bash
echo FOO=${FOO}
echo BAR=${BAR}
echo -e ${MULTILINE}

One solution is to include the .env file, then make sure variables are exported:

include .env

$(eval export $(shell sed -ne 's/ *#.*$$//; /./ s/=.*$$// p' .env))

all:
    ./script.sh

Because of different treatment of quotes by shell and make, you will see the quotes in output.

You can avoid that by reprocessing the variables by make:

include .env

VARS:=$(shell sed -ne 's/ *\#.*$$//; /./ s/=.*$$// p' .env )
$(foreach v,$(VARS),$(eval $(shell echo export $(v)="$($(v))")))

all:
    ./script.sh

but then the multiline variable will become a one-liner.

Finally, you can generate a temporary file to be processed by bash and source it before any command is run:

SHELL=bash

all: .env-export
    . .env-export && ./script.sh

.env-export: .env
    sed -ne '/^export / {p;d}; /.*=/ s/^/export / p' .env > .env-export

Oh, new lines got messed in this case in multiline variable. You need to additionally quote them.

Finally, you can add export to .env using above sed command, and do:

SHELL=bash
%: .env-export
    . .env-export && make -f secondary "$@"
Hollerman answered 19/6, 2017 at 18:7 Comment(3)
Alright, I think you convinced me to use an external tool. Thank you for the elaborate answer!Busboy
The whole treatment of quotes in shell vs make makes it really messy. One other solution seems to be to prepend each command with . .env; , i.e. . env; echo $FOOBusboy
In example given by you $FOO is expanded by the same shell sourcing .env file. To run . .env; ./script.sh variables need to be already exported in .env file, to be visisble inside ./script.sh.Hollerman
F
6

You can load specific .env file for each target by creating a function and target to use it with other targets when necessary. Here as an sample:

define setup_env
    $(eval ENV_FILE := $(1).env)
    @echo " - setup env $(ENV_FILE)"
    $(eval include $(1).env)
    $(eval export)
endef

devEnv: 
    $(call setup_env, dev)

prodEnv: 
    $(call setup_env, prod)

clean:
    rm -rf  bin/

build: clean
    GOOS=linux GOARCH=amd64 go build -o bin/ ./cmd/...

dev: build devEnv
    cd cmd/api && ./../../bin/api
 
migrate-dev-db: devEnv
    sh +x database/migration/migrate.sh dev

migrate-prod-db: prodEnv
    sh +x database/migration/migrate.sh


deploy: prodEnv
    sh +x script/deployment/production/ec2-deploy.sh
Fizzle answered 9/11, 2021 at 16:54 Comment(2)
Why does the dev target depend on devEnv and then also immediately call $(call setup_env,dev) too?Outlive
@MarkLakata Sorry for the mistake. I edited my answer.Fizzle
F
5

Here's a sample .env and makefile usage:

.env:

DATABASE_URL=postgres://postgres:[email protected]:5432/my_db_development?sslmode=disable

makefile:

include .env

migrate:
    migrate -source file://postgres/migrations \
            -database $(DATABASE_URL) up
Fruma answered 3/4, 2023 at 18:6 Comment(0)
C
1

I faced the same issue and here how I solve it.

  • First, I recommend checking if the .env file exists, otherwise the include will raise an exception.
  • Second, feel free to add the .env-example file to your repo.
    ENVFILE = .env
    ENVFILE_EXAMPLE = .env-example
    
    # Create the .env file if not exit.
    ifeq ("$(wildcard $(ENVFILE))","")
       $(shell cp $(ENVFILE_EXAMPLE) $(ENVFILE))
    endif
    # Load the environment variables from .env file
    include $(ENVFILE)
    export $(shell sed '/^\#/d; s/=.*//' $(ENVFILE)) 

FYI: this approach does handle comments also, which is not supposed by default by Makefile syntax.

Sanhoj - Thanks

Chapnick answered 19/5, 2023 at 15:4 Comment(0)
K
0

if you are willing to use an external tool you can try direnv. direnv loads a .env or .envrc file into your shell on entering a directory. As a result your .env file is loaded before entering make and is executed by your local shell.

  1. put a .env file in your directory and put your variables there
  2. install and setup direnv for your shell configure it to allow .env files
  3. run direnv allow . in your directory where the Makefile is installed
  4. from now running make will use your environment variables

I used this to test

Makefile:

all:
    echo "Hello ${FOO}"

.env:

FOO=alex

~/.config/direnv/direnv.toml:

load_dotenv=true
Keaton answered 1/5 at 9:3 Comment(0)
H
0

Have your .env file with the environment variables declared in the format:

USER_ID=123456
USER_KEY=johndoe

Then have your Makefile include the .env file and pass the variables when running your app:

include .env

run:
    USER_ID=$(USER_ID) \
    USER_KEY=$(USER_KEY) \
    node app.js
Homogenous answered 8/6 at 15:20 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.