GNU MAKE: functions in dependencies - makefile

I would like to generate a number of files using GNU Make using the following recipe.
ina_as%.dat: ina_driver.m ina_as$(word 1,$(subst _epsi, , %)).m
echo "modelType = '$(word 1,$(subst _epsi, , $*))'; ofile = '$#'; epsi = '$(word 2,$(subst _epsi, , $*))';" | cat - $< | nohup matlab -nodesktop -nosplash
The targets are in a format -- ina_as%d_epsi%.2f.dat (e.g. ina_as1_epsi0.50.dat) and the second prerequisite is ina_as%d.m (e.g. ina_as1.m) (notice, the second part _epsi%.2f missing in the prerequisite file name).
I have tried several combination for the implicit rule ($, $$, $(eval $*) etc.), but it still does not work. I think it could be because Make could not understand the functions ( '$(word 1,$(subst _epsi, , %))' ) in the dependency definition.
There is any way to overcome this problem?
Thanks.

Questions like this come up from time to time. The short answer is that Make simply can't do this in a clean way; the text manipulation statements expand before executing any rule (i.e. before % has any value), and Make doesn't handle wildcards (or regular expressions) very well.
The longer answer is that it can be done, but only by resorting to one kludge or another. If your version of Make supports SECONDEXPANSION, you can do it this way:
.SECONDEXPANSION:
ina_as%.dat: ina_as$$(word 1,$$(subst _, ,%)).m
#echo "modelType = '$(word 1,$(subst _epsi, , $*))'; ofile = '$#'; epsi\
= '$(word 2,$(subst _epsi, , $*))';" | cat - $< | nohup matlab -nodesktop\
-nosplash
If not, you can resort to recursive Make (useful sometimes, no matter what they say):
ina_as%.dat :
#$(MAKE) dummy MODELTYPE=`echo $* | sed "s/_.*//"` EPSI=`echo $* | sed \
"s/.*_epsi//"`
dummy: ina_as$(MODELTYPE).m
#echo "modelType = $(MODELTYPE); ofile = ina_as$(MODELTYPE)_epsi$(EPSI)\
; epsi = $(EPSI);" | cat - ina_as$(MODELTYPE).m | nohup matlab -nodesktop\
-nosplash

Related

How to expand variables after entering custom define that wraps foreach?

I have many very similar $(foreach..) loops in a makefile that work like in the target_old target below:
define NL
endef
extras=some/path/
vars=a b c
all: target_old target_new
target_old:
# foreach and some multiple evals inside and some multiple commands inside
$(foreach var, ${vars}, \
$(eval extra := ${extras}${var}) \
#echo var is ${var} and extras is ${extra}$(NL) \
)
# my try to wrap it in a function
define foreach_vars
$(foreach var, ${vars},
$(eval extra := ${extras}${var}) \
$1$(NL) \
)
endef
target_new:
#echo this is wrong:
$(call foreach_vars, \
#echo var is ${var} and extras is ${extra} \
)
I have many multiple such foreach loops with all the same evals inside the foreach. So I wanted to wrap the foreach loop with the evals inside my own function in foreach_vars. So I don't have to $(eval extra := ${extras}${var}) inside each foreach call. I created target_new target to test it. I would want the output from both targets to be the same, make target_old prints:
$ make target_old
var is a and extras is some/path/a
var is b and extras is some/path/b
var is c and extras is some/path/c
However target_new doesn't pick the ${var} from inside the loop, and ${var} just expands to nothing:
$ make target_new
this is wrong:
var is and extras is
var is and extras is
var is and extras is
I guess this is because the expansion happens before entering the $(call...). Is there any method I can use to "defer" the expansion of arguments inside the $(call...) call until inside foreach inside my function? Is is possible to write a custom foreach-like macro in make? Is there just other method used to implement such functionality?
Yes, your problem comes from the expansion(s) that do not happen when you would like and in the order you would like.
Your use of make is quite unusual because you are using make constructs (foreach, eval, call...) in recipes that are normally plain shell. I guess you have a very good reason but wouldn't it be much easier if you were separating the make world and the shell world? Like in the following, for instance:
extras := some/path/
vars := a b c
target_old:
#for var in $(vars); do \
extra=$(extras)$${var}; \
echo var is $${var} and extra is $${extra}; \
done
It uses make variables (vars, extras) and shell variables (extra, var). The recipe is plain shell. Note the $$ used to escape the first expansion by make such that the shell expansion ${xxx} is done by the shell. Note also the line continuations (\) that form a single line recipe, despite the look. As each line of a make recipe is executed by a separate shell, this is needed to pass shell variables between commands of the shell script.
If you wish, you can also wrap the shell for loop in a make recursively expanded variable:
for = for var in $(vars); do $(1); done
target_new:
#$(call for,extra=$(extras)$${var}; echo var is $${var} and extra is $${extra})
${var} gets immediately expanded, so it needs to be escaped as $${var}. This itself does not fix the issue, since now $1 contains a literal ${var}, which does NOT get expanded within foreach. I would make a simple subst though to get it fixed, e.g.:
$ cat Makefile
define NL
endef
extras=some/path/
vars=a b c
define foreach_vars
$(foreach var, ${vars},
$(eval extra := ${extras}${var}) \
$(subst $$(var),$(var), \
$(subst $$(extra),$(extra), \
$(1))) \
$(NL) \
)
endef
target_new:
$(call foreach_vars, \
#echo var is $$(var) and extras is $$(extra) \
)
Output:
$ make target_new
var is a and extras is some/path/a
var is b and extras is some/path/b
var is c and extras is some/path/c
When make comes to build target_new (like when you type make target_new for instance):
It expands the whole recipeImportant: The recipe is expanded before firing up the shell
For each line of the resulting expansion, it passes one at a time to a fresh invocation of the shell
It's worth showing the expansion make does in painful detail. We have as the recipe:
#echo this is wrong:
$(call foreach_vars, \
#echo var is ${var} and extras is ${extra} \
)
First off, ${var} becomes empty, as is ${extra}
make is left with $(call foreach_vars, #echo var is and extras is ). Now for the call:
1 is set to #echo var is and extras is
make expands $(foreach var, ${vars}, $(eval extra := ${extras}${var}) $1$(NL) )
${vars} is a b c
First iteration:
var is set to a
Make evals extra := some/path/a
The expansion of the eval is empty however, and we are left with $1$(NL) (modulo some whitespace), leaving #echo var is and extras is
Second iteration: ${extra} becomes some/path/b, and we are again left with #echo var is and extras is
Last iteration: ${extra} becomes some/path/c, and we are again left with #echo var is and extras is
The final recipe then:
#echo this is wrong:
#echo var is and extras is
#echo var is and extras is
#echo var is and extras is
which produces the output you described.
So how do we avoid the early expansion of parameters?
Once nice solution is to stick the command-line you want into a variable,
and pass the name of that variable instead.
define foreach_vars # 1: variable containing command-line
$(foreach var,${vars},
$(eval extra := ${extras}${var}) \
${$1}$(NL) \
)
endef
cmds<target_new> = #echo var is ${var} and extras is ${extra}
target_new:
#echo this is right:
$(call foreach_vars,cmds<$#>)
Why mangle the variable name with the name of the target? Lookup tables are nice, and you may find many targets ending up with the same recipe.
cmds<target_new> = #echo var is ${var} and extras is ${extra}
cmds<target_beta> = ${MAKE} ${var}-${extra}
cmds<target_release> = script ${var} | eat ${extra}
target_new target_beta target_release:
$(call foreach_vars,cmds<$#>)
etc.

Makefile does not find target

I have the following Makefile, but it does not work. When I call
make html
I get a
make: *** No rule to make target `docs/index.html', needed by `html'. Stop.
error, even though I think I have defined it.
SRCDIR = source
OUTDIR = docs
RMD = $(wildcard $(SRCDIR)/*.Rmd)
TMP = $(RMD:.Rmd=.html)
HTML = ${subst $(SRCDIR),$(OUTDIR),$(TMP)}
test:
echo $(RMD)
echo $(TMP)
echo $(HTML)
all: clean update html
html: $(HTML)
%.html: %.Rmd
echo $(HTML)
#Rscript -e "rmarkdown::render('$<', output_format = 'prettydoc::html_pretty', output_dir = './$(OUTDIR)/')"
update:
#Rscript -e "devtools::load_all(here::here()); microcosmScheme:::updateFromGoogleSheet(token = './source/googlesheets_token.rds')"
## from https://stackoverflow.com/a/26339924/632423
list:
#$(MAKE) -pRrq -f $(lastword $(MAKEFILE_LIST)) : 2>/dev/null | awk -v RS= -F: '/^# File/,/^# Finished Make data base/ {if ($$1 !~ "^[#.]") {print $$1}}' | sort | egrep -v -e '^[^[:alnum:]]' -e '^$#$$' | xargs
.PHONY: update clean cleanhtml all list
The variables seem to be correct:
15:21 $ make test
echo source/index.Rmd
source/index.Rmd
echo source/index.html
source/index.html
echo docs/index.html
docs/index.html
If I change it as follow it works, but the target points to the SRCDIR, but I want it to point to the OUTDIR:
RMD = $(wildcard $(SRCDIR)/*.Rmd)
HTML = $(RMD:.Rmd=.html)
# HTML = ${subst $(SRCDIR),$(OUTDIR),$(TMP)}
I am sure it is one small thing...
This rule:
%.html : %.Rmd
....
tells make how to build a file foo.html from a file foo.Rmd, or a file source/foo.html from a file source/foo.Rmd, or a file docs/foo.html from a file docs/foo.Rmd.
It doesn't tell make how to build a file docs/foo.html from a file source/foo.Rmd, because the stem that matches the pattern % is not the same.
If you want to write a pattern for docs/foo.html to be built from source/foo.Rmd, you have to write it like this:
$(OUTDIR)/%.html : $(SRCDIR)/%.Rmd
....
so that the part that matches the pattern % is identical.
ETA Some other notes: you should be using := with the wildcard function as it's much better performing. Also you shouldn't use subst here because it replaces all occurrences of the string which could break things if any of your .Rmd files contain the string source for example (e.g., source/my_source_file.Rmd. This is much better written with patsubst, as in:
RMD := $(wildcard $(SRCDIR)/*.Rmd)
HTML := $(patsubst $(SRCDIR)/%.Rmd,$(OBJDIR)/%.html,$(RMD))
Finally, you don't show what the clean target does but it's unusual to have the clean target depended on by all. Usually it's a separate target that is invoked only when you want it, like make clean.

How to concatenate strings in a Makefile?

I have a Makefile that runs pandoc. I want to turn a list of extensions:
PANDOC_EXTENSIONS = \
multiline_tables \
some_other_extension
into a string that looks like:
PANDOC_EXTENSION_LIST = +multiline_tables+some_other_extension
which will then be passed as a command line option to pandoc like this:
pandoc --from$(PANDOC_EXTENSION_LIST) ...
It's trivial in almost any programming language, but I can't figure out how to do this with the patsubst or subst functions, since make doesn't really have lists. Any ideas?
Here:
Makefile
PANDOC_EXTENSIONS = \
multiline_tables \
some_other_extension
$(foreach word,$(PANDOC_EXTENSIONS),$(eval PANDOC_EXTENSION_LIST := $(PANDOC_EXTENSION_LIST)+$(word)))
.PHONY: all
all:
echo $(PANDOC_EXTENSION_LIST)
Which runs like:
$ make
echo +multiline_tables+some_other_extension
+multiline_tables+some_other_extension
As this illustrates, GNU make really does have lists. A sequence of whitespace-separated words is a list.
Based on example in documentation:
empty:=
space:=$(empty) $(empty)
PANDOC_EXTENSIONS = \
multiline_tables \
some_other_extension
all:
#echo +$(subst ${space},+,${PANDOC_EXTENSIONS})
The result:
$ gmake
+multiline_tables+some_other_extension

Converting Windows path to Unix path in a makefile

This question is related to Convert Cygwin path to Windows path in a makefile but it is not the same.
I need to convert a Windows path like:
C:\src\bin
into a Unix path like:
/c/src/bin
Inside a makefile, I can use the following code to convert such paths:
slashedpath = $(subst \\,\/,$(windowspath))
unixpath = $(shell cygpath -u $(slashedpath))
How can I perform the same conversion in a makefile that is being processed by GNU Make, when the cygpath function is not available?
p.s.
What if $(windowspath) contains multiple paths? How to convert them all ?
The makefile:
windowspath=C:\src\bin
unixpath=$(subst \,/,$(subst C:\,/c/,$(windowspath)))
all:
#echo "$(windowspath)"
#echo "$(unixpath)"
gives the output:
C:\src\bin
/c/src/bin
This will also work if $(windowspath) contains multiple paths. Tested on GNU Make 4.2.1 for i686-pc-cygwin, and also on GNU Make 3.81 built for i686-redhat-linux-gnu.
I was surprised that this worked.
Update: This second version will handle various drives such as C:, D:, etc. Some of these ideas are from Eric Melski's answer to In GNU Make, how do I convert a variable to lower case?. If the Makefile is:
DRIVE = $(subst \
A:,/a,$(subst B:,/b,$(subst C:,/c,$(subst D:,/d,$(subst \
E:,/e,$(subst F:,/f,$(subst G:,/g,$(subst H:,/h,$(subst \
I:,/i,$(subst J:,/j,$(subst K:,/k,$(subst L:,/l,$(subst \
M:,/m,$(subst N:,/n,$(subst O:,/o,$(subst P:,/p,$(subst \
Q:,/q,$(subst R:,/r,$(subst S:,/s,$(subst T:,/t,$(subst \
U:,/u,$(subst V:,/v,$(subst W:,/w,$(subst X:,/x,$(subst \
Y:,/y,$(subst Z:,/z,$1))))))))))))))))))))))))))
drive = $(subst \
a:,/a,$(subst b:,/b,$(subst c:,/c,$(subst d:,/d,$(subst \
e:,/e,$(subst f:,/f,$(subst g:,/g,$(subst h:,/h,$(subst \
i:,/i,$(subst j:,/j,$(subst k:,/k,$(subst l:,/l,$(subst \
m:,/m,$(subst n:,/n,$(subst o:,/o,$(subst p:,/p,$(subst \
q:,/q,$(subst r:,/r,$(subst s:,/s,$(subst t:,/t,$(subst \
u:,/u,$(subst v:,/v,$(subst w:,/w,$(subst x:,/x,$(subst \
y:,/y,$(subst z:,/z,$1))))))))))))))))))))))))))
windowspath = c:\src\bin D:\FOO\BAR
unixpath = $(subst \,/,$(call DRIVE,$(call drive,$(windowspath))))
all:
#echo Original: "$(windowspath)"
#echo Modified: "$(unixpath)"
then the output to make is:
Original: c:\src\bin D:\FOO\BAR
Modified: /c/src/bin /d/FOO/BAR
Update 2: The most straight-forward approach, and the most flexible, is to use a standard regular-expression handler such as perl or sed, if these are available. For example, with GNU sed, this Makefile will work as required:
windowspath = c:\src\bin D:\FOO\BAR
unixpath = $(shell echo '$(windowspath)' | \
sed -E 's_\<(.):_/\l\1_g; s_\\_/_g')
all:
#echo Original: "$(windowspath)"
#echo Modified: "$(unixpath)"
Explanation of sed:
s_\<(.):_/\l\1_g For every word starting with something like A: or a:, replace the start with /a.
s_\\_/_g Replace all backslashes with forward slashes.

gnu make: list the values of all variables (or "macros") in a particular run

How can I list the current value of all variables (also called macros) in a Makefile when running make?
E.g. if this is in the Makefile:
CUR-DIR := $(shell /bin/pwd)
LOG-DIR := $(CUR-DIR)/make-logs
Then I would like it to tell me:
CUR-DIR = /home/johv/src/test
LOG-DIR = /home/johv/src/test/make-logs
GNU make provides .VARIABLES
which holds all global variables' names.
However, this includes built-in variables(like MAKEFLAGS).
If you have to exclude built-in variables, some filtering like the following
might be needed.
The following makefile prints user-defined variables(CUR-DIR, LOG-DIR)
using info:
# Place this line at the top of your Makefile
VARS_OLD := $(.VARIABLES)
# Define your variables
CUR-DIR := $(shell pwd)
LOG-DIR := $(CUR-DIR)/make-logs
# Put this at the point where you want to see the variable values
$(foreach v, \
$(filter-out $(VARS_OLD) VARS_OLD,$(.VARIABLES)), \
$(info $(v) = $($(v))))
Thanks to #Ise Wisteria, condensed down, this shows all variables, useful for large projects with multiple makefiles (Buildroot).
$(foreach v, $(.VARIABLES), $(info $(v) = $($(v))))
output: BR2_GCC_TARGET_TUNE = "cortex-a8" ...
If you get an error like: insufficient number of arguments (1) to function 'addprefix' this project had some broken variables... I trimmed the list of variables to show, only with a prefix BR2_
$(foreach v, $(filter BR2_%,$(.VARIABLES)), $(info $(v) = $($(v))))
I ended up doing it like this:
gmake -pn | grep -A1 "^# makefile"| grep -v "^#\|^--" | sort | uniq > makevars.txt
which gives:
CUR-DIR := /home/johv/src/test
LOG-DIR := /home/johv/src/test/make-logs
MAKEFILE_LIST := Makefile
MAKEFLAGS = pn
SHELL = /bin/sh
VARS_OLD := [...]
gmake -pn is really verbose and looks kinda like this:
# environment
GNOME2_PATH = /usr/local:/opt/gnome:/usr:/usr/local:/opt/gnome:/usr
# automatic
#F = $(notdir $#)
# makefile
SHELL = /bin/sh
# default
RM = rm -f
It's also doable without saving all the .VARIABLES and filtering them out.
Moreover, if one of the original .VARIABLES was modified in your makefile, the two most voted answers won't catch it.
Check out $(origin) function. This target filters out and prints all the variables that were defined in a makefile:
print_file_vars:
$(foreach v, $(.VARIABLES), $(if $(filter file,$(origin $(v))), $(info $(v)=$($(v)))))
I get only a few excess variables this way: CURDIR SHELL MAKEFILE_LIST .DEFAULT_GOAL MAKEFLAGS.
One can replace file with environment or command line to print the respective kinds of variables.
There are a lot of good answers here, but you're going to have problems using $($(v)) if some of your variables are of the recursive flavor. This is why you should use $(value $(v)).
This variation cleans this up a little bit, sorts variables by name and makes the output a bit more readable.
dump:
$(foreach v, \
$(shell echo "$(filter-out .VARIABLES,$(.VARIABLES))" | tr ' ' '\n' | sort), \
$(info $(shell printf "%-20s" "$(v)")= $(value $(v))) \
)
Thanks to #kevinf for the great idea. I would suggest a minor change to prevent .VARIABLE itself from printing out in the variable list:
$(foreach v, $(filter-out .VARIABLES,$(.VARIABLES)), $(info $(v) = $($(v))))
Thanks to #kevinf for the foreach solution -- if one wants to export this list as a somewhat machine-readable file, one will have a hard time with uneven quotes or newlines when using echo or printf, since Make isn't able to quote the data correctly -- one needs to use the $(file ...) function to write the data to avoid sh/bash complaining about invalid syntax. For example, use this in your rule -- it prints variable name, definition and expanded value:
$(file > $(MAKEFILE_ENV_FILE),)
$(foreach v, $(.VARIABLES), \
$(file >> $(MAKEFILE_ENV_FILE),$(v)) \
$(file >> $(MAKEFILE_ENV_FILE), := $(value $(v))) \
$(file >> $(MAKEFILE_ENV_FILE), == $($(v))) \
$(file >> $(MAKEFILE_ENV_FILE),) \
)
(This will still not allow to always distinguish malicious variables with double newlines from two variables, for this one now add a sufficiently unique separator infront of each Makefile-generated newline just after each comma inside $(file >> NAME,TEXT))
Set MAKEFILE_ENV_FILE to some filename, e.g.:
MAKEFILE_ENV_FILE := $(abspath $(lastword $(MAKEFILE_LIST))).env

Resources