I can not understand the output of patsubst in a makefile - function

This is the guilty makefile :
$ cat -n example.mak
1 define this
2 $(patsubst $(1)/%.o,%.o,why_this_does/that.o)
3 $(patsubst butnot/%.o,%.o, butnot/but_not_that.o)
4 endef
5
6 why:
7 $(info $(call this, why_this_does) ?)
And this is my question :
$ make -f example.mak
why_this_does/that.o
but_not_that.o ?
make: 'why' is up to date.

The root cause is not in patsubst but in call. The manual has a note:
A final caution: be careful when adding whitespace to the arguments to call. As with other functions, any whitespace contained in the second and subsequent arguments is kept; this can cause strange effects. It’s generally safest to remove all extraneous whitespace when providing parameters to call.
and indeed, if you replace
$(info $(call this, why_this_does) ?)
by
$(info $(call this,why_this_does) ?)
you get what you want.

Related

Export a function in Makefile

I'd like to use a function in a recursive call of make.
I have the following in the principal Makefile:
define my_func
Hello $(1) from $(2)
endef
export my_func
And further, in another one called later, I have :
$(error my_func is $(call my_func,StackOverFlow,Me))
It gives me this output Makefile_rec.mk:1: *** my_func is Hello from . Stop.
But what I'd like is Makefile_rec.mk:1: *** my_func is Hello StackOverFlow from Me. Stop.
Is there a way using make to export such a variable/function and get it to work when using call function ?
Thanks !
-- EDIT --
As pointed by #Renaud Pacalet here, I would like also to use this kind of macro either in the current Makefile and in sub-makes.
If possible, not by including a file each time I need the macro
If you want this to work you must double the $ signs in your definition:
define my_func
Hello $$(1) from $$(2)
endef
export my_func
From the manual:
To pass down, or export, a variable, make adds the variable and its
value to the environment for running each line of the recipe. The
sub-make, in turn, uses the environment to initialize its table of
variable values.
You must protect the $ from one extra expansion.
Of course, you cannot use the same macro in the top Makefile. If it is a problem, you must define a macro for the top Makefile and another one for the sub-make Makefile:
host> cat Makefile
define my_func
Hello $(1) from $(2)
endef
my_func_1 := $(call my_func,$$(1),$$(2))
export my_func_1
all:
$(MAKE) -f Makefile_rec.mk
$(info TOP: $(call my_func,StackOverFlow,Me))
host> cat Makefile_rec.mk
all:
$(info BOT: $(call my_func_1,StackOverFlow,Me))
host> make --no-print-directory
TOP: Hello StackOverFlow from Me
make -f Makefile_rec.mk
BOT: Hello StackOverFlow from Me
make[1]: 'all' is up to date.

gmake function / ifneq/else/endif

I am trying to create a function that will determine if a directory exist, and if it does, add a target to the all list. but something is wrong. Here is the Makefile code snippet:
define buildMQ
$(info **** Checking to see if the MQ series directory exist *****)
ifneq "$(wildcard $(MQ_DIR) )" ""
$(info /opt/mqm was found)
MQ_APPS=MQSAPP
else
$(error $n$n**** ERROR - The MQ Series direcory: "$(MQ_DIR)" does not exist ******$n$n)
endif
endef
ifeq ('$(FN)' , 'TEST')
TEST_APPS=
endif
ifeq ('$(FN)' , 'ONSITE_TEST')
ONSITE_TEST_APPS= # insert ONSITE_TEST only apps here
$(call buildMQ)
endif
ifeq ('$(FN)' , 'ACCOUNT')
ACCOUNT_APPS=
$(call buildMQ)
endif
all:$(COMMON_APPS) $(TEST_APPS) $(ONSITE_TEST_APPS) $(ACCOUNT_APPS) $(MQ_APPS) makexit
and when I run it with FN = ONSITE_TEST:
**** Checking to see if the MQ series directory exist *****
/opt/mqm was found
Makefile:128: ***
**** ERROR - The MQ Series direcory: "/opt/mqm" does not exist ******
How can both print statements get printed? What am I missing?
The directory does exist
There's a lot of misunderstanding here about how call works. The call function takes a variable (name), plus zero or more arguments. It assigns the arguments to $1, $2, etc. and then it expands the variable.
Note that by "expands" here we don't mean "interprets the variable value as if it were a makefile". We mean very simply, go through the value of the variable and locate all make variables and functions and replace them with their appropriate values.
So, you invoke $(call buildMQ). This will not assign any values to $1, etc. since you didn't provide any arguments: in effect this is exactly the same as just using $(buildMQ); the call function has no impact here.
So make expands the value of the buildMQ variable... basically it takes the value as one long string:
$(info **** Checking to see if the MQ series directory exist *****) ifneq "$(wildcard $(MQ_DIR) )" "" $(info /opt/mqm was found) MQ_APPS=MQSAPP else $(error $n$n**** ERROR - The MQ Series direcory: "$(MQ_DIR)" does not exist ******$n$n) endif
and expands it. So first it expands the $(info ... Checking ... function and prints that. Then it expands the $(wildcard ..) and replaces that. Then it expands the $(info /opt/mqm ...) and prints that. Then it expands the $(error ...) and shows the message and exits.
If it hadn't exited, then you'd have a syntax error because a function like call cannot expand to a multi-line statement; as above it's not evaluated like a set of makefile lines. It has to expand to a single value makefile line.
You need to use the eval function if you want make to parse the contents of a variable as if it were a makefile; eval doesn't take a variable name it takes a string to parse, so it would be:
$(eval $(buildMQ))
However, this won't do what you want for the same reason: it expands the buildMQ variable and that causes all the functions to be expanded first, before eval even sees them.
One option would be to escape all the variables and functions reference in buildMQ. But in your situation a simpler solution is to use the value function to prevent expansion before eval sees the value:
$(eval $(value buildMQ))

Encountered an issue setting the value of a variable inside a Makefile function

Directory structure (to run this example) looks like this:
example
|-- Makefile
|-- foo
| |-- foo.h (empty file)
|-- bar
| |-- bar.h (empty file)
Makefile looks like this:
TARGETS=foo bar
define GEN_HEADER_RULE
$(1)_INCS = $(wildcard $(1)/*.h)
$(info $$($(1)_INCS) is [$($(1)_INCS)])
$(info $$(wildcard $$(1)/*) is: [$(wildcard $(1)/*)])
endef
$(foreach t,$(TARGETS),$(eval $(call GEN_HEADER_RULE,$t)))
all:
#echo "DONE"
Actual output is:
$(foo_INCS) is []
$(wildcard $(1)/*) is: [foo/foo.h]
$(bar_INCS) is []
$(wildcard $(1)/*) is: [bar/bar.h]
DONE
Expected output is:
$(foo_INCS) is [foo/foo.h]
$(bar_INCS) is [bar/bar.h]
I'm failing to understand why the value of the variable $(1)_INCS is not being set to be the output of the wildcard function? The wildcard function is clearly working without issue - as shown by the $info command dumping the output directly from it.
I have constructed this simple Makefile to demonstrate the same issue I have encountered in a much larger build system.
You forgot to protect variables and functions from double expansion. Double all $ except $(1) and you should see almost what you want (except for the second $(info...) that will produce more output than what you expect).
host> cat Makefile
TARGETS=foo bar
define GEN_HEADER_RULE
$(1)_INCS = $$(wildcard $(1)/*.h)
$$(info $$$$($(1)_INCS) is [$$($(1)_INCS)])
$$(info $$$$(wildcard $(1)/*) is: [$$(wildcard $(1)/*)])
endef
$(foreach t,$(TARGETS),$(eval $(call GEN_HEADER_RULE,$t)))
all:
#echo "DONE"
host> make
$(foo_INCS) is [foo/foo.h]
$(wildcard foo/*) is: [foo/foo.h]
$(bar_INCS) is [bar/bar.h]
$(wildcard bar/*) is: [bar/bar.h]
DONE
When using foreach-eval-call remember that there is a first expansion by eval-call and a second expansion by make when it parses the result as make syntax. You frequently need to escape the first expansion ($$) and even sometimes the second one ($$$$). See the GNU make manual for a detailed explanation.

GNU make call function with multiple arguments and multiple commands

I am trying to write a GNU make call function (example below) which has multiple shell commands to execute, such that it can be called with different arguments.
shell_commands = $(shell echo $(1); ls -ltr $(2))
try:
$(call shell_commands,$(FILE1),$(FILE2))
1) Is above the correct way to write a call function with multiple commands? By using a semi-colon to separate them? To make it readable, I write my targets as shown below. Is there a similar way to write a call function?
shell_commands:
echo $(1)
ls -ltr $(2)
2) I get this error from make when I execute make -B try. It looks like it is trying to execute /home/user/file1. But why?
make: execvp: /home/user/file1: Permission denied
make: *** [try] Error 127
3) Is it possible to pass variable number of parameters to a call function? Like pass in just the second parameter and not the first one.
$(call shell_commands,,$(FILE2))
I tried google-ing, searching on SO, and looking on gnu.org but I did not get any solutions. Will appreciate any answers or pointers to any resources which document call function with multiple optional arguments and commands.
Question 1: No, this is not right. The shell make function should NEVER be used inside a recipe: the recipe is already running in the shell, so why would you run another shell? It's just confusing. Second, it's generally recommended to use && between multiple commands in a recipe, so that if the first command fails the entire command will immediately fail, rather than continuing on and perhaps succeeding. Of course, that is not always correct either, it depends on what you're trying to do.
Question 2: This happens because the shell make function is like backticks in the shell: it expands to the output printed by the shell command it runs. Your shell command that make runs is:
echo $(1); ls -ltr $(2)
(where, one assumes, $1 expands to /home/user/file1) which prints the string /home/user/file1. After the expansion, that string is substituted into the recipe and make tries to run that recipe, giving the error you see above.
You want this, most likely:
shell_commands = echo $(1) && ls -ltr $(2)
try:
$(call shell_commands,$(FILE1),$(FILE2))
Now the call expands to the actual text, not an invocation of make's shell function, and then that text is run as the recipe.
Question 3: Sure, just using empty parameters means that the variable $1 (in this case) expands to the empty string.

Can aspell output line number and not offset in pipe mode?

Can aspell output line number and not offset in pipe mode for html and xml files? I can't read the file line by line because in this case aspell can't identify closed tag (if tag situated on the next line).
This will output all occurrences of misspelt words with line numbers:
# Get aspell output...
<my_document.txt aspell pipe list -d en_GB --personal=./aspell.ignore.txt |
# Proccess the aspell output...
grep '[a-zA-Z]\+ [0-9]\+ [0-9]\+' -oh | \
grep '[a-zA-Z]\+' -o | \
while read word; do grep -on "\<$word\>" my_document.txt; done
Where:
my_document.txt is your original document
en_GB is your primary dictionary choice (e.g. try en_US)
aspell.ignore.txt is an aspell personal dictionary (example below)
aspell_output.txt is the output of aspell in pipe mode (ispell style)
result.txt is a final results file
aspell.ignore.txt example:
personal_ws-1.1 en 500
foo
bar
example results.txt output (for an en_GB dictionary):
238:color
302:writeable
355:backends
433:dataonly
You can also print the whole line by changing the last grep -on into grep -n.
This is just an idea, I haven't really tried it yet (I'm on a windows machine :(). But maybe you could pipe the html file through head (with byte limit) and count newlines using grep to find your line number. It's neither efficient nor pretty, but it might just work.
cat icantspell.html | head -c <offset from aspell> | egrep -Uc "$"
I use the following script to perform spell-checking and to work-around the awkward output of aspell -a / ispell. At the same time, the script also works around the problem that ordinals like 2nd aren't recognized by aspell by simply ignoring everything that aspell reports which is not a word of its own.
#!/bin/bash
set +o pipefail
if [ -t 1 ] ; then
color="--color=always"
fi
! for file in "$#" ; do
<"$file" aspell pipe list -p ./dict --mode=html |
grep '[[:alpha:]]\+ [0-9]\+ [0-9]\+' -oh |
grep '[[:alpha:]]\+' -o |
while read word ; do
grep $color -n "\<$word\>" "$file"
done
done | grep .
You even get colored output if the stdout of the script is a terminal, and you get an exit status of 1 in case the script found spelling mistakes, otherwise the exit status of the script is 0.
Also, the script protects itself from pipefail, which is a somewhat popular option to be set i.e. in a Makefile but doesn't work for this script. Last but not least, this script explicitly uses [[:alpha:]] instead of [a-zA-Z] which is less confusing when it's also matching non-ASCII characters like German äöüÄÖÜß and others. [a-zA-Z] also does, but that to some level comes at a surprise.
aspell pipe / aspell -a / ispell output one empty line for each input line (after reporting the errors of the line).
Demonstration printing the line number with awk:
$ aspell pipe < testFile.txt |
awk '/^$/ { countedLine=countedLine+1; print "#L=" countedLine; next; } //'
produces this output:
#(#) International Ispell Version 3.1.20 (but really Aspell 0.60.7-20110707)
& iinternational 7 0: international, Internationale, internationally, internationals, intentional, international's, Internationale's
#L=1
*
*
*
& reelly 22 11: Reilly, really, reel, rely, rally, relay, resell, retell, Riley, rel, regally, Riel, freely, real, rill, roll, reels, reply, Greeley, cruelly, reel's, Reilly's
#L=2
*
#L=3
*
*
& sometypo 18 8: some typo, some-typo, setup, sometime, someday, smote, meetup, smarty, stupor, Smetana, somatic, symmetry, mistype, smutty, smite, Sumter, smut, steppe
#L=4
with testFile.txt
iinternational
I say this reelly.
hello
here is sometypo.
(Still not as nice as hunspell -u (https://stackoverflow.com/a/10778071/4124767). But hunspell misses some command line options I like.)
For others using aspell with one of the filter modes (tex, html, etc), here's a way to only print line numbers for misspelled words in the filtered text. So for example, it won't print misspellings in the comments.
ASPELL_ARGS="--mode=html --personal=./.aspell.en.pws"
for file in "$#"; do
for word in $(aspell $ASPELL_ARGS list < "$file" | sort -u); do
grep -no "\<$word\>" <(aspell $ASPELL_ARGS filter < "$file")
done | sort -n
done
This works because aspell filter does not delete empty lines. I realize this isn't using aspell pipe as requested by OP, but it's in the same spirit of making aspell print line numbers.