I am learning how to write Makefile. I found the .VARIABLES variable which holds all the variables valid in Makefile.
I can check the variables' name with command like this:
test:
#echo "${.VARIABLES}" | tr ' ' '\n'
But I don't know how to should the values of them. Can anyone teach me to to do that?
If you are using GNU Make, then you can use a crafty combination of foreach and .VARIABLES like this:
test:
$(foreach var,$(.VARIABLES),$(info $(var) = $($(var))))
Related
In a Makefile I'm trying to do the following:
Here I'm getting a couple things I need:
S3_BUCKET_NAME_STATE=$(shell terraform output -json | jq '.S3_Bucket.value')
DYNAMODB_LOCK=$(shell terraform output -json | jq '.dynamo_db_lock.value')
And with the result of that (i.e.: the content in the variables) I want to do some replacements in a file, like this:
sed 's/{{ BUCKET_NAME }}/$$(S3_BUCKET_NAME_STATE)/g;' variables.tf.template > variables.tf)
But it keeps on not doing it right. I've tried using ${} instead but i can't make it work...
Help is appreciated!
You didn't mention whether these are make variables or shell variables, so I'm going to assume they're make variables.
Here's a working example of how to do this. It has 3 files: test.json, variables.tf.template, and Makefile.
Makefile:
S3_BUCKET_NAME_STATE := $(shell cat test.json | jq -r '.S3_Bucket.value')
all:
sed 's/{{ BUCKET_NAME }}/$(S3_BUCKET_NAME_STATE)/g;' variables.tf.template > variables.tf
test.json:
{"S3_Bucket": {"value": "Foo"}}
variables.tf.template:
some stuff {{ BUCKET_NAME }}
I made the following changes:
Using := instead of =. The equals operator is the lazy substitution operator, which expands the macro at the time that it's used rather than at the time of defintion.
Using -r with jq. This gets the raw string value.
Using $(var) instead of $$(var). Make interprets a double dollar sign as an escaped dollar sign.
I tested this on GNU Make 3.81 and 4.3.
I'm trying to figure out how to get an eval'd variable (using output from the shell) to pass conditional checks like ifndef or ifdef. I need to use the shell because I'm actually using a script that returns some output.
foo::
$(eval var := $(shell echo 'hello'))
ifndef var
#printf 'ifndef is true. var is ${var}'
else
#printf 'ifndef is false. var is ${var}'
endif
Running "make foo" will output the following:
'ifndef is true. var is hello'
As you can see, ifndef doesn't find anything in var, even though printf is showing that var contains the string "hello".
What am I missing here?
You can't do this because ifdef et. al. are parsed when the makefile is read in (note that they do not begin with a TAB character so they are not part of the recipe), and the recipe (which includes $(eval ...)) is not parsed until much later when make wants to build the target foo.
And, you cannot put ifdef et. al. into the recipe because if you prefix them with TAB then they get passed to the shell, not parsed by make. In general if you want a conditional inside a recipe you have to use shell conditionals, NOT make conditionals, because the recipe is a shell script.
You can use the $(if ...) function:
foo::
$(eval var := $(shell echo 'hello'))
#printf 'ifndef is $(if $(var),true,false). var is ${var}'
But to me this looks like a mistake and you might reconsider what you're trying to do at a more fundamental level.
I've been learning make and am struggling to figure something out. I have some rules with this general structure.
FILE = "myfile.txt"
test :
YOUR = $(subst my,your,$(FILE));\
cat $(FILE) $(YOUR)
I would expect the end result to be running the command:
cat myfile.txt yourfile.txt
Instead I get the following...
YOUR = "yourfile.txt";\
cat "myfile.txt"
/bin/sh: YOUR: command not found
make: *** [test] Error 1
If instead of using the subst function, I just do YOUR="yourfile" in the makefile, everything looks fine. Any suggestions or have I missed something pretty fundamental? I should add that I'm using tabs and not spaces to start the lines for the commands within the rule.
FILE = "myfile.txt"
test :
$(eval YOUR = $(subst my,your,$(FILE)))
cp $(FILE) $(YOUR)
You have to use the eval function in the recipe (Define make variable at rule execution time)
You need to distinguish between what make executes and what the shell executes. Your line with YOUR = starts with a tab and is part of the actions of a rule, so it is executed by the shell, which can't find a program YOUR to execute with some arguments.
Place the expansion outside the rule:
YOUR = $(subst my,your,$(FILE))
test:
cat $(FILE) $(YOUR)
Note that shell assignments require no space around the equals sign, and use ${} rather than $() to reference variables: YOUR=${FILE/my/your} in Bash (and if written in a make rule, you'd need $$ in place of $ so that the shell sees a single dollar sign and make does not try the variable expansion that it doesn't understand). The shell uses $() to execute the command contained within, and the result is often captured in a variable: YOUR=$(echo "${FILE}" | sed 's/my/your/').
If you only need the variable in the shell recipe and not in the make context then you don't need to bother playing with eval (which are hoisted) and can just assign to shell variables instead.
For example:
FILE = "myfile.txt"
test :
YOUR='$(subst my,your,$(FILE))';\
cat $(FILE) "$${YOUR}"
I have a variable containing list of files separated with string _NEWLINE_. I need to output that variable into a file so that each file is in a separate line. The trick is that it needs to works on FreeBSD and Solaris.
This is what I am trying now:
echo "lib/alarms-1.2/priv/snmp_conf/agent.conf: lib/alarms/priv/snmp_conf/agent.conf_NEWLINE_lib/alarms-1.2/priv/snmp_conf/agent.conf.src: lib/alarms/priv/snmp_conf/agent.conf.src_NEWLINE_lib/alarms-1.2/priv/snmp_conf/community.conf: lib/alarms/priv/snmp_conf/community.conf" | sed 's|_NEWLINE_|\'$'\n|g'
This works on FreeBSD and in shell on Solaris. But when run in GNUmakefile on Solaris I am getting this (notice $ at the end of each line):
lib/alarms-1.2/priv/snmp_conf/agent.conf: lib/alarms/priv/snmp_conf/agent.conf$
lib/alarms-1.2/priv/snmp_conf/agent.conf.src: lib/alarms/priv/snmp_conf/agent.conf.src$
lib/alarms-1.2/priv/snmp_conf/community.conf: lib/alarms/priv/snmp_conf/community.conf$
If I remove \'$' from sed then it works on Solaris but doesn't on FreeBSD. Maybe there is a way of telling which version to use depending on which system the makefile is executed?
EDIT:
Thanks to the solution proposed by bobbogo I created an exemplary makefile that provides the desired outcome and seems to be working on both FreeBSD and Solaris:
one-line := lib/alarms-1.2/priv/snmp_conf/agent.conf: lib/alarms/priv/snmp_conf/agent.conf_NEWLINE_lib/alarms-1.2/\
priv/snmp_conf/agent.conf.src: lib/alarms/priv/snmp_conf/agent.conf.src_NEWLINE_lib/alarms-1.2/priv/snmp_conf/comm\
unity.conf: lib/alarms/priv/snmp_conf/community.conf
many-lines := { echo '$(subst _NEWLINE_,' && echo ',${one-line})'; }
.PHONY: all
all:
$(shell $(many-lines) > test.txt)
If this is GNU make, then do it all in make.
one-line := lib/alarms-1.2/priv/snmp_conf/agent.conf: lib/alarms/priv/snmp_conf/agent.conf_NEWLINE_lib/alarms-1.2/priv/snmp_conf/agent.conf.src: lib/alarms/priv/snmp_conf/agent.conf.src_NEWLINE_lib/alarms-1.2/priv/snmp_conf/community.conf: lib/alarms/priv/snmp_conf/community.conf
define \n
endef
many-lines := $(subst _NEWLINE_,${\n},${one-line})
Now ${many-lines} has just what you want. Annoyingly, it's quite hard to use in shell lines. If you do this:
tgt:
echo '${many-lines}'
make will invoke a separate shell for each line. The first shell invocation will get an un-paired ' and exit with an error.
.ONESHELL:
tgt:
echo '${many-lines}'
will work in an invasive sort of way. The proper fix is to ensure each line of ${many-lines} has valid sh syntax. Some mouthfull like:
echolines = $(subst ${\n},'${\n}echo ',echo '${many-lines}')
.PHONY: aa
aa:
$(call echolines,${many-lines})
Sheesh.
Tried many different solutions, including defining \n as mentioned in this answer: Add a newline in Makefile 'foreach' loop
The real problem is inconsistent implementation of the echo command across platforms, and the fact that by default make invokes shell commands using sh, which itself is quite inflexible.
I found a better way thanks to this answer: "echo -e" when called directly and when called via a shell-script
The better way is to use printf instead of echo
Construct the string with \n instead of _NEWLINE_ to separate parts that go into separate lines in the output file:
some_string = lib/alarms-1.2/priv/snmp_conf/target_params.conf: lib/alarms/priv/snmp_conf/target_params.conf\nlib/alarms-1.2/priv/snmp_conf/community.conf: lib/alarms/priv/snmp_conf/community.conf\n
and then in the makefile print it simply as this:
#printf "$(some_string)" >> $(some_file)
Works on both, FreeBSD and Solaris.
Disclaimer: I have no experience with Solaris or FreeBSD... here goes anyway.
In make, you can use $(patsubst pattern,replacement,text) to substitute a pattern.
try this...
FILENAMES := "lib/alarms-1.2/priv/snmp_conf/agent.conf: lib/alarms/priv/snmp_conf/agent.conf_NEWLINE_lib/alarms-1.2/priv/snmp_conf/agent.conf.src: lib/alarms/priv/snmp_conf/agent.conf.src_NEWLINE_lib/alarms-1.2/priv/snmp_conf/community.conf: lib/alarms/priv/snmp_conf/community.conf"
.PHONY: all
all:
#echo $(patsubst _NEWLINE_,${\n},$(FILENAMES))
As an alternative, I think your first approach will work, if you just double the $ to "escape" it:
sed 's|_NEWLINE_|\'$$'\n|g'
I have a slew of makefile targets that do the same thing:
${SOME_FILE}:
${FILES} | ${DIST_DIR}
##cat ${FILES} | \
sed 's/#DATE/'"${DATE}"'/' | \
sed 's/#VERSION/'"${CR_VER}"'/' \
> ${OUT_FILE};
where ${FILES} and ${OUT_FILE} are the only things changing. I'm trying to figure out if it's possible to simplify these targets to something like:
${SOME_FILE}:
compile(${FILES},${OUT_FILE})
Thanks for any insight.
GNU make has this:
http://www.gnu.org/software/make/manual/make.html#Call-Function
To define a multi-line function, you would use this syntax:
http://www.gnu.org/software/make/manual/make.html#Canned-Recipes
Links to docs (like in the accepted answer) are good but good example is better :)
define my_func
$(eval $#_PROTOCOL = "https:")
$(eval $#_HOSTNAME = $(1))
$(eval $#_PORT = $(2))
echo "${$#_PROTOCOL}//${$#_HOSTNAME}:${$#_PORT}/"
endef
my-target:
#$(call my_func,"example.com",8000)
Take into consideration the following:
There are no custom "functions" in Makefile language. So "my_func" is actually a variable that simply contains a text.
That "function" doesn't have its own scope. All the content of that "function" is copied into the recipe as is. All variables in the body will be used after that as a part of the recipe.
Don't use spaces near the commas to prettify param list of "call" function.
Args are passed like $(1), $(2), ... that is not very handy, so re-declare them with meaningful names. This is optional but recommended for bigger "function" bodies.
Variables are declared with $#_ prefix that makes them "local" to the rule (actually not local but prefixed by the target name).
So we have imitation of functions with imitation of local variables but generally this works good.
If you don't want to restrict yourself to GNUmake, your best bet is probably to generate makefile fragments yourself and then include them.