For one of my project, I need to add something to an env var from a makefile.
Here's a the sandboxed problem :
First, the makefile
all:
$(shell source <(echo export MYVAR=$(MYVAR):otherstuff))
And what I'm trying to achieve
export MYVAR=stuff
make
echo $MYVAR
So technically, MYVAR should contains stuff:otherstuff. But instead of that I got a bash error
/bin/sh: -c: line 0: syntax error near unexpected token `('
/bin/sh: -c: line 0: `source <(echo export MYVAR=$MYVAR:otherstuff)'
I don't know what I did wrong, the command worked outside of the makefile.
Thanks!
And what I'm trying to achieve
export MYVAR=stuff
make
echo $MYVAR
So technically, MYVAR should contains stuff:otherstuff.
As was mentioned in the comments, you can't do it. A copy of environment is inherited from the parent process (the shell where you perform export MYVAR=stuff) by the child process (the make call), and whatever the child process does with the variable, would be visible to the make itself, or its own child processes. But never the parent.
Generally, when I need to export some information from the Makefile, I provision special targets for the purpose, e.g.:
print-env:
#echo export MYVAR=$(MYVAR):otherstuff ";"
#echo export MYOTHERVAR=helloworld ";"
Now when you do make print-env, it would produce the output like this:
export MYVAR=stuff:otherstuff ;
export MYOTHERVAR=helloworld ;
Using the makefile we have generated commands, which could be easily copy-pasted from the output into command prompt to execute them.
Though it is possible and doable, I would advise against the feeding of make's output to the shell directly:
eval "`make print-env`"
or even more radical:
`make print-env`
since make output is generally unstable, and might accidentally contain output of other rules, which might confuse the shell.
Related
I have a bash script to run my Makefile-based project through Include-What-You-Use (IWYU) which looks as follows:
#!/bin/bash
export MAKEFLAGS="-k CXX=iwyu -Xiwyu --transitive_includes_only -Xiwyu --mapping_file=qt5_4.imp"
build.sh
The build.sh script sets a couple of environment variables, prepares other parts of the build environment and will then eventually run make.
At first sight this seems to work and do its job. However closer inspection showed, that only CXX=iwyu is actually used in the build. The command line options for IWYU get dropped.
I tried various modifications of my call to fix this, however none seemed to solve the problem.
With
export MAKEFLAGS="-k CXX=iwyu\ -Xiwyu\ --transitive_includes_only\ -Xiwyu\ --mapping_file=qt5_4.imp"
the command line options are no longer dropped, but now suddenly -k seems to get dropped somewhere and my build (because of the missing -k) is terminated early with failure.
With
export MAKEFLAGS="-k CXX='iwyu -Xiwyu --transitive_includes_only -Xiwyu --mapping_file=qt5_4.imp'"
I'm flooded with /bin/sh: 1: Syntax error: Unterminated quoted string which looks like the ending ' is somehow dropped.
Are there any other ways I have to escape and/or quote the spaces in my export to fix this?
You can create a script (once?) to run iwyu — be careful, single quotes and double quotes are not interchangeable:
echo 'iwyu -Xiwyu --transitive_includes_only -Xiwyu --mapping_file=qt5_4.imp "$#"' > ./run.iwyu
chmod +x ./run.iwyu
and then run:
make -k CXX="$PWD/run.iwyu"
or:
export MAKEFLAGS="-k CXX=$PWD/run.iwyu"
This sidesteps the whole problem of spaces in the arguments. As shown, you specify the full path for the run.iwyu script just in case your make process changes directories. If you put the run.iwyu script in a directory on your PATH, you don't need to specify the full path to the script. You could prefix the command line in the script with exec if you like.
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
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);) \
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.
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.
I am trying to create something like a global variable that I will use in order to make my project easy to deploy for other developers.
I would like to have an .sh file where there is a variable defining the location of the project.
Later on I want to export this variable and make it accessable in every makefile that I am creating so that I can use this design to keep everything constant and in one place.
This is an example of what I am trying to build:
Creating and exporting the variables in script.sh:
#!/bin/bash
DIRECTORY='some path value here'
Importing the values in multiple Makefiles:
# start script and fetch the value
VAR := $(shell ./script.sh | sed -n '/^result: /s/^.*: //p')
all:
#echo VAR=$(VAR)
I would like to see how other people are dealing with the same problem.
Being a better developer is my goal here. :)
Feedback always welcomed.
Environment variables exported in the shell are visible from make, so in a shell script like this:
#!/bin/sh
VAR=value
export VAR
make $*
The Makefile will start with VAR defined to value. That's one way to get variables from a shell script into make.
If you don't want the shell script to run make, you can have a user source it:
$ source script.sh
$ make
The variables set in the script will be visible to make this way too.
Or course there doesn't seem to be any reason you need a shell script here. Stick your configuration into a fragment of a Makefile (which would look almost exactly like your shell script, but not use quotes for multiple word values) and then include Makefile.inc in your main makefile.
Also note that syntax like this:
#!/bin/sh or another commment
VAR=value
export VAR
It equally valid included in a Makefile or sourced into a shell script. So sometimes it's possible to use the same include file in both places!
How can I correctly set environment variables in a makefile on Windows?
I get an error about CreateProcess.
C:\>make.exe -f build.makefile start
export MALLOC_PERTURB_=110
process_begin: CreateProcess(NULL, export MALLOC_PERTURB_=110, ...) failed.
make (e=2): The system cannot find the file specified.
c:\testmake.makefile:4: recipe for target 'start' failed
make: *** [start] Error 2
START:
export NODE_ENV=110
echo $(NODE_ENV)
Your question isn't entirely clear but there are a number of obvious things wrong there.
First off you are running make under Windows but writing recipes as if they were shell scripts. That's not the case on Windows (by default at least).
So export is being attempted to be run as an external command which is failing (hence the error message about CreateProcess failing).
You also don't show us the target that is actually throwing that error.
Additionally you are expecting the export and assignment on the first line of the recipe to be in effect for the second line in the recipe body (for the start target).
But that isn't the case. Each line in a makefile target's recipe runs in an independent environment. So the export and assignment on the first line of the START target's recipe doesn't affect the second line.
To do that you need to use one long line or use the .ONESHELL feature of recent versions of make.
Additionally, you are using $(NODE_ENV) in your recipe expecting that to be the shell variable you previously set. And even ignoring the previously stated problem that isn't correct.
make is going to expand $(NODE_ENV) as a make variable before it even runs the recipe at all. You meant ${NODE_ENV} or $NODE_ENV for a shell variable. That said for a make recipe you actually need to escape the $ because ${NODE_ENV} is also a valid make variable expansion. So you need $${NODE_ENV} or $$NODE_ENV there.
Put together you end up with this (assuming you have a shell somewhere to use).
SHELL := /path/to/your/shell
START:
export NODE_ENV=110; \
echo "$${NODE_ENV}"
(though you don't actually need export at that point but that's a different issue entirely).
But if you don't have a shell to use then you get to use cmd.exe and I'm not sure what the equivalent of export there is (though as I just said you don't need it for this) and you end up with something like this (entirely untested):
START:
NODE_ENV=110; \
echo %NODE_ENV%
So, to compile my executable, I need to have the library locations set up correctly. The problem is, the setup comes from a bunch of scripts that do the env variable exporting, and what needs to be set up may change (beyond my control) so I need to use those scripts instead of copying their functionality. To compile in regular command line, I need to do something like:
setup library1
setup library2
source some_other_setup_script.bash
g++ blah.c
# setup is a executable on my system that run some scripts
How would I write a makefile that accomplishes that? As far as I tried, the env variable exporting does not carry over (i.e. "export VAR=remember; echo $VAR" won't work)
You can also add environment variables properly with the machinery of GNU make, like so:
export TEST:="Something Good!"
test:
echo $$TEST
This (I think) has different semantics from:
TEST2:="Something not quite so useful?"
test2:
echo ${TEST2}
Which (again, I think) does the substitution within make before passing along to the shell. Note that the export command doesn't work within a target block, just unindented as an immediately executed command.
If variable exporting is not working the way it does on your command line, that suggests that Make is choosing a shell different from the one you're using, with different syntax for handling variables (export VAR=remember; echo $VAR works fine for me). Make uses /bin/sh by default, but you can override this with the SHELL variable, which Make does not import from the environment. I suggest setting SHELL (in the Makefile) to whatever you're using in your environment and trying the export VAR=remember experiment again.
Ultimately you will need to define the variable and execute the compiler in a shell list or even a script, rather than in separate make commands. There are a couple of refinements you could add, however. You could tell make about the script:
maintarget: script.sh blah.c
source script.sh; g++ blah.c
script.sh:
setup include script here
Another thing would be to just execute all that stuff in the same shell
maintarget: blah.c
run this; run that; run the other thing; g++ blah.c
I believe all make versions will run a ; list in the same shell, but you can always force a subshell with (list) or by calling specifically a shell script as a compiler command wrapper.
Don't forget to have the appropriate targets depend on your scripts themselves. BTW, some make versions (pmake aka bsd make) can execute a command when defining a make variable, and all versions of make then exports those. But I don't think gmake can do that.
You could write another shell script that executes all those commands, then prints out variable assignments that make can use. Run the script, pipe its output to a file, then include that file from your Makefile. For example:
Makefile:
all:
echo $(FOO)
test.mk: test.sh
./$< > $#
include test.mk
test.sh
echo "FOO=1"
Running "make" in the directory containing this Makefile produces:
make: Entering directory `/home/luser/build/mktest'
Makefile:7: test.mk: No such file or directory
./test.sh > test.mk
make: Leaving directory `/home/luser/build/mktest'
make: Entering directory `/home/luser/build/mktest'
echo 1
1
make: Leaving directory `/home/luser/build/mktest'
make creates test.mk by running the shell script, then includes it. test.mk contains the output of test.sh, and is parsed as a Makefile. See http://www.gnu.org/software/make/manual/make.html#Include for more details.
We use a variant of this in Mozilla's client.mk to let you define options in a "mozconfig" file:
http://mxr.mozilla.org/mozilla-central/source/client.mk#138
Restatement: How do I get a shell variable into a make file?
Something like:
MYVAR := $(shell echo $(MYVAR)) <any_makefile_additions_here>
So, this defines MYVAR inside a MAKEFILE when an environment variable named MYVAR is also set.
It might be of interest, that, in order to override an option that is already defined in a makefile, make supports (I am referring to GNU Make 3.82, but other version probably too) the option -e.
Example:
Makefile:
CC=gcc
...
Run make:
CC=gcc-4.7
make -e
will use gcc-4.7 instead of gcc.