jesugmz
jesugmz

Reputation: 2670

Export environment variables from Makefile to userland environment

I'm looking how to export from a Makefile environment variables to be exposed in the userland environment so exporting these variables from the Makefile should be accessible from the user shell.

I have tried make's export but as I understand and have tried does not export to outside of Makefile.

The idea of this is to populate Docker Compose environment variables in a elegant way and have these variables ready to use in the user shell also.

This is a fragment of what I've tried with make's export:

include docker.env
export $(shell sed -n '/=/p' docker.env)

SHELL := /bin/bash

run:
    @docker-compose -f my-service.yml up -d

Upvotes: 2

Views: 3099

Answers (3)

tripleee
tripleee

Reputation: 189597

Make cannot change the calling shell's environment without its cooperation. Of course, if you are in control, you can make the calling shell cooperate.

In broad terms, you could replace the make command with a shell alias or function which runs the real make and also sets the environment variables from the result. I will proceed to describe in more detail one way to implement this.

Whether you call this alias or function of yours make or e.g. compose is up to you really. To wrap the real make is marginally harder -- inside the function, you need to say command make, because just make would cause an infinite loop with the alias or function calling itself recursively -- so I will demonstrate this. Let's define a function (aliases suck);

make () {
    # run the real make, break out on failure
    command make "$@" || return
    # if there is no env for us to load, we are done
    test -f ./docker.env || return 0
    # still here? load it
    . ./docker.env
}

If you want even stricter control, maybe define a variable in the function and check inside the Makefile that the variable is set.

$(ifneq '${_composing}','function_make')
    $(error Need to use the wrapper function to call make)
$(endif)

The error message is rather bewildering if you haven't read this discussion, so maybe it needs to be improved, and/or documented in a README or something. You would change the make line in the function above into

    _composing='function_make' \
    command make "$@" || return

The syntax var=value cmd args sets the variable var to the string value just for the duration of running the command line cmd args; it then returns to its previous state (unset, or set to its previous value).

For this particular construction, the name of the variable just needs to be reasonably unique and transparent to a curious human reader; and the value is also just a reasonably unique and reasonably transparent string which the function and the Makefile need to agree on.

Depending on what you end up storing in the environment, this could introduce complications if you need this mechanism for multiple Makefiles. Running it in directory a and then switching to a similar directory b will appear to work, but uses the a things where the poor puny human would expect the b things. (If the variables you set contain paths, relative paths fix this scenario, but complicate others.)

Extending this to a model similar to Ruby's rvm or Python's virtualenv might be worth exploring; they typically add an indicator to the shell prompt to remind you which environment is currently active, and have some (very modest) safeguards in place to warn you when your current directory and the environment disagree.

Another wart: Hardcoding make to always load docker.env is likely to produce unwelcome surprises one day. Perhaps hardcode a different file name which is specific to this hook - say, .compose_post_make_hook? It can then in turn contain something like

. ./docker.env

in this particular directory.

Upvotes: 0

Renaud Pacalet
Renaud Pacalet

Reputation: 29250

EDIT completely reworked the answer after the OP explained in a comment that he wants the environment variables to be defined for any user shell.

If your goal is to have a set of environment variables defined for any user shell (I assume this means interactive shell), you can simply add these definitions to the shell's startup file (.bashrc for bash). From GNU make manual:

Variables in make can come from the environment in which make is run. Every environment variable that make sees when it starts up is transformed into a make variable with the same name and value. However, an explicit assignment in the makefile, or with a command argument, overrides the environment. (If the ‘-e’ flag is specified, then values from the environment override assignments in the makefile. See Summary of Options. But this is not recommended practice.)

Example:

$ cat .bashrc
...
export FOOBAR=foobar
export BARFOO="bar foo"
...
$ cat Makefile
all:
    @printf '$$(FOOBAR)=%s\n' '$(FOOBAR)'
    @printf 'FOOBAR='; printenv FOOBAR
    @printf '$$(BARFOO)=%s\n' '$(BARFOO)'
    @printf 'BARFOO='; printenv BARFOO
$ make
$(FOOBAR)=foobar
FOOBAR=foobar
$(BARFOO)=bar foo
BARFOO=bar foo

If you want to keep these definitions separate, you can just source the file from .bashrc:

$ cat docker.env
export FOOBAR=foobar
export BARFOO="bar foo"
$ cat .bashrc
...
source <some-path>/docker.env
...

And finally, if you don't want to add the export bash command to your file, you can parse the file in your .bashrc:

$ cat docker.env
FOOBAR=foobar
BARFOO="bar foo"
$ cat .bashrc
...
while read -r line; do
    eval "export $$line"
done < <(sed -n '/=/p' <some-path>/docker.env)
...

Of course, there are some constraints for the syntax of your docker.env file (no unquoted special characters, no spaces in variable names, properly quoted values...) If your syntax is not bash-compatible it is time to ask another question about parsing this specific syntax and converting it into bash-compatible syntax.

Upvotes: 1

jesugmz
jesugmz

Reputation: 2670

According with ArchWiki, each process of Bash...

Each process stores their environment in the /proc/$PID/environ file.

so once Make execute a source, export or any other command to set a new environment variable it will be applied only for that process.

As workaround I've written in the bash startup file so the variables will be in the global environment as soon as a new bash shell is loaded:

SHELL := /bin/bash
RC := ~/.bashrc
ENV := $(shell sed -n '/=/p' docker.env)

test:
     @$(foreach e,$(ENV),echo $(e) >> $(RC);) \

Upvotes: 1

Related Questions