Makefile execute shell command error handling - makefile

I have a makefile with this goal:
setup:
#GHE_TOKEN=$(shell vault read -field=value is/cloud/eng/ghe_token)
#GHE_TOKEN=${GHE_TOKEN} pipenv install --dev
What I'm doing here is getting a secret from HashiCorp vault and passing it on to the next command. This works fine if I'm already logged in to vault properly.
However if I'm not logged in the pipenv install command proceeds even though there was an error in vault read.
So what I'm looking for is the make goal to abort and display the error message if there is an error on the vault read call and also be able to capture the output of a successful shell command to read. I know how to do one or the other, but not both.
Thanks for any help!

It isn't working because what you're doing is not at all what you think you're doing :).
When make processes a recipe it works like this: first all make variables and functions in all commands in the recipe are expanded, then each command line in the recipe is invoked one at a time, in its own shell.
So, in your situation note that ${GHE_TOKEN} is a reference to a make variable named GHE_TOKEN, which I assume is not set in your makefile, so it's the empty string. Also the make $(shell ...) function is expanded before any command is run, and note that make doesn't care about the exit code of this; if it fails it won't cause the command to fail.
If we assume that the vault read -field=value is/cloud/engine/ghe_token returns the value mysecret, then make will substitute in that value and run these commands (basically):
/bin/sh -c 'GHE_TOKEN=mysecret'
/bin/sh -c 'GHE_TOKEN= pipenv install --dev'
This clearly isn't going to do what you want.
You need to do all the work in the shell; it's almost always a sign of something wrong if you are using make's shell function in a recipe; a recipe is already running in the shell, so using $(shell ...) just adds confusion.
You probably want this:
setup:
#export GHE_TOKEN=$$(vault read -field=value is/cloud/eng/ghe_token) \
&& pipenv install --dev
so that pipenv is not run if the vault read command fails.

Related

How can i assign the output of a makefile function to a variable

I have a function in a makefile that returns a bucket name in google cloud storage
define get_composer_bucket
gcloud beta composer environments describe --location=europe-west2 ${COMPOSER_SANDBOX_NAME} \
| grep -hnr "dagGcsPrefix"
endef
I want to use the output of this in a makefile command. How can i assign the output of this to a variable? I have tried the below, which returns an empty variable. Am i doing something wrong?
run:
BUCKET=$(call get_composer_bucket)
echo ${BUCKET}
There are many SO answers that will describe to you the difference between make variables and shell variables; knowing this difference is crucial to writing makefiles.
There are also many SO answers that will explain that each command in a recipe is run in a different shell, and that variables set in one shell can't be passed to another shell, so setting a variable in one recipe command line can't be seen in another recipe command line (you can use \ to combine multiple physical lines into a single logical line, but you probably need to add ; as well to satisfy the shell).
And, there are many answers discussing how a makefile is not a shell script (although it can CONTAIN shell scripts) so you can't just write a shell script anywhere in a makefile and have it evaluated.
The simplest thing to do for you is to set a make variable using the make shell function to run the shell script:
BUCKET := $(shell gcloud beta composer environments describe --location=europe-west2 ${COMPOSER_SANDBOX_NAME} | grep -hnr "dagGcsPrefix")
run:
echo $(BUCKET)
Of course, this is different in various ways than what you tried to do above. So maybe it doesn't meet your requirements, but you haven't actually stated what those are.
Please see below code which will help you to extract the name
define get_composer_bucket
`gcloud beta composer environments describe --location=europe-west2 ${COMPOSER_SANDBOX_NAME} \
| grep -hnr "dagGcsPrefix"`
endef
BUCKET:= $(call get_composer_bucket)
run:
#echo "BUCKET=> $(BUCKET)"

Export environment variables from child bash script in GNU Make

I have a script "set_env.py" that outputs the following uppon execution:
export MY_VAR_1=some_value
export MY_VAR_2=some_other_value
I cannot change this script, it is supplied in my current environment.
Further I have a Makefile that looks like this:
SHELL := /bin/bash
all: set_env
env | grep MY_VAR
set_env:
eval ./set_env.py
With this makefile I expected the grep to list my two variables, however it seems the environment is not set.
I suspect this is because make creates a sub-environment for each line and so the variables set on the first line will not be available in the second.
So the question is, how would I go about exporting the environment from the untouchable script in my makefile?
Actually, the output of the python is valid make.
One option then is to read the output of the python directly into the makefile.
The only fly in the ointment is that $(shell) doesn't cut the mustard.
include Environment.mk
PHONY: test
test:
env | grep MY_VAR
Environment.mk:
./set_env.py >$#-tmp
mv $#-tmp $#
How does this work?
The first thing that make tries to do is to ensure the makefile itself is up-to-date.
Since we have told it to include Environment.mk,
make must ensure that is up-to-date too.
Make finds a rule for Environment.mk
The python is run, creating Environment.mk
Environment.mk is read in, creating two make variables with the export attribute
The makefile is now up-to-date, so make proceeds on to the target (test in this case)
Make runs test's recipe, exporting any variables with the export attribute.
No recursion, but you should ensure the python always spits out make compatible syntax.
EDIT
As #raspy points out, this is not the whole story.
As it stands,
once Environment.mk has been created,
it will never be regenerated.
If set_env.py ever generates different output,
you should tell make what conditions these are by adding dependencies.
If set_env.py takes a trivial time to run,
I advise a simple .PHONY.
That way it will run every time you run make,
and Environment.mk will never be stale.
.PHONY: Environment.mk
Recursive make is your friend I'm afraid.
.PHONY: all
all:
eval $$(./set_env.py) && ${MAKE} test
.PHONY: test
test:
env | grep MY_VAR
There are a few moving parts here.
make all executes the shell command eval $(./set_env.py) && make test
The shell first does the command substitution
$(./set_env.py) is replaced by the export commands
The export commands are passed to the (shell) eval command
The environment variables are defined, but only for this shell invocation
The eval succeeds, so the execution passes to the command after the &&
Make runs recursively, but this second make has an augmented environment

Changing the stack size and LD_LIBRARY_PATH using a Makefile

I would like to be able to have a rule inside my makefile that a changes the stack size and that appends to LD_LIBRARY_FILE the current directory.
In the shell I can just do:
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$(pwd)
ulimit -s SOME_NUMBER
Is there way to make a rule inside a makefile that is equivalent to running this two commands and will update the environment variables after the makefile is run?
You can link the executables with -z stack-size=VALUE or -rpath, which might have a similar effect.
You could push the command characters to the TTY layer and hope that the shell will execute them, but that is a bit nasty. The makefile could also compile a program which does the job using ptrace, but that would be very ugly as well.
No
A child process in bash cannot effect the environment of the calling shell (as #mpez pointed out). This is done on purpose to avoid some security holes. It's possible to export the commands to change the environment variable to a script, and run that script after you run make (which can be automated through another script), but make itself cannot effect the environment of the calling shell.

How to set environment variables in makefile?

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%

How to handle setting up environment in makefile?

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.

Resources