GNU Make: "dir not expected at this moment" - makefile

I have a makefile including the following lines:
buildrepo:
#$(call make-repo)
define make-repo
for dir in $(C_SRCS_DIR); \
do \
mkdir -p $(OBJDIR)/$$dir; \
done
endef
On the line with the commands for dir in $(C_SRCS_DIR); \ I get the following error message:
"dir not expected at this moment"
make: *** [buildrepo] Error 255
I am using GNU make.
Can anybody tell me what is going wrong?

Actually this for ... in ... ; do ... done statement is a Unix command not a GNU make command, therefore I guess you are using a Windows machine (or any other one). You have to find the equivalent for your system.
But GNU make has a foreach function which works like this :
$(foreach dir,$(C_SRCS_DIR),mkdir -p $(OBJDIR)/$(dir);)
Also note that in your very specific case (not related to GNU make but to Windows) you can create all the dirs without a for/foreach loop, just like this :
mkdir -p $(addprefix $(OBJDIR)/,$(C_SRCS_DIR))

Related

execute shell commands inside a target in makefile

I'm new to makefile. I'm trying to perform some shell operation inside a makefile under a target. I made a new_target without modifying the working code. The code looks like this:
all: new_target existing_target
new_target:
TEST_FILES:=$(wildcard $(HOME)/Test/*.cpp)
for f in $(TEST_FILES); do \
$(shell ls) $$f; \
done
Error:
TEST_FILES:=/docker_home/myhome/Test/b.cpp /docker_home/myhome/Test/file.cpp /docker_home/myhome/Test/a.cpp
/bin/sh: 1: TEST_FILES:=/docker_home/myhome/Test/b.cpp: not found
Makefile:6: recipe for target 'new_target' failed
make: *** [new_target] Error 127
The idea is to perform a shell operation(similar to ls) on all the .cpp files in a particular directory
This ...
TEST_FILES:=$(wildcard $(HOME)/Test/*.cpp)
... is (GNU) make syntax that assigns a value to a make variable. Your recipe instructs the shell to execute it as if it were a shell command. Obviously, that doesn't work.
Additionally, $(shell ls) doesn't do what you intend. It will run the ls command without arguments in make's working directory, at the time the makefile is parsed, and insert the results into the command to be run. If you want to run a shell command in your recipe then just put the command in the recipe.
The easiest solution would probably be to move that line outside the recipe (and dedent it):
TEST_FILES:=$(wildcard $(HOME)/Test/*.cpp)
new_target:
for f in $(TEST_FILES); do \
ls $$f; \
done
Note that the $(wildcard) function will be evaluated and the results assigned to TEST_FILES at the time that the makefile is parsed, not when the new_target target is built, but that appears unlikely to be an issue in this case.
Of course, unless you need TEST_FILES for something else, too, a much cleaner way would be to merge it together and get rid of wildcard:
new_target:
for f in $(HOME)/Test/*.cpp; do \
ls $$f; \
done
Or, best of all for this particular case:
new_target:
ls $(HOME)/Test/*.cpp
You need to run it in below way as TEST_FILES is a make variable and you should not mix make and shell:
TEST_FILES:=$(wildcard $(HOME)/Test/*.cpp)
new_target:
for f in $(TEST_FILES); do \
ls $$f; \
done
Note :
When it is time to execute recipes to update a target by make , they are executed by invoking a new sub-shell for each line of the recipe, unless the .ONESHELL special target is in effect. So you dont require a $(shell) explicitly.

Makefile - argument list too long, but only in some configurations

I'm trying to reproduce a situation which happens only in some machines. To reproduce it, I create a directory with 2000 files:
mkdir /tmp/test
cd /tmp/test
for f in $(seq 1 2000); do touch $f.txt; done
Then I use the following Makefile (simplified from the real use case):
FILES:=$(shell find . -name '*.txt')
%.done: %.txt
#echo "done $#"
toolong:
#$(foreach file,$(sort $(FILES)), \
if $(MAKE) $(file); \
then echo "did $(file)" >> $#; \
else echo "failed $(file)" >> $#; fi; )
Running make produces, unsurprisingly, an error:
make: execvp: /bin/sh: Argument list too long
This question presents a solution which does work. However, I need to understand exactly why this error does not happen on my colleagues' computers. I tried the following things:
Increasing stack limit (ulimit -s gives the same result on both machines, 8192, and increasing it does not change anything);
Checking getconf ARG_MAX (2097152 in both machines);
Checking MAX_ARG_STRLEN (131072 in both machines);
Using a different shell (zsh is being used in both machines; I also tried bash, dash and sh, via export SHELL=<shell> make, and also by replacing the symlink /bin/sh -> /bin/bash with a link to dash).
Finally, I tried recompiling Make from source, and realized that, even when I compile the same version of Make (4.1) in my Ubuntu test machine, I get the same behavior as I had in my Fedora, that is, the error "argument list too long".
make --version only shows a single difference between them:
Version from the apt package:
GNU Make 4.1
Built for x86_64-pc-linux-gnu
Version compiled from source (./configure && make):
GNU Make 4.1
Built for x86_64-unknown-linux-gnu
I even tried compiling make-dfsg, which results in an identical make --version, but the result is the same as with my other manually-compiled make.
By increasing the number of files on Ubuntu, I managed to identify that the actual limits in the size of the generated command line are:
Fedora or Arch Linux (both with Make 4.2.1), or Ubuntu with manually-compiled Make 4.1: 128 KB (~1200 files);
Debian Sid or Ubuntu, both with Make 4.1 installed from apt package: 2 MB (~19300 files).
I'd really like to understand (1) why this difference exists, and (2) how could I compile Make to obtain the higher limit, so that I can have the exact same behavior on both machines.
Your recipe expands as a one-line shell compound command (because of the line ending backslashes). And this line is probably way too long. Did you check what it looks like (the shell compound command) on all machines? I suggest that you wrap it in $(info...):
toolong:
#$(info $(foreach file,$(sort $(FILES)), \
if $(MAKE) $(file); \
then echo "did $(file)" >> $#; \
else echo "failed $(file)" >> $#; fi; ))
and see if the output is the same on your machine and on the others. If, for any reason, yours is longer, it could be the explanation. Else it must be a OS difference...
Note: you could have one rule per target, instead of one single rule with a huge recipe for all.
Note: your recipe (independently from its fantastic length) does nothing useful. As the files already exist and do not have pre-requisites, all your sub-make calls will just tell you that the files are up-to-date.

How to loop against directories

I am trying to build a generic task that will execute other task. What I need it to do is to loop against directories and use each dir name executing other task for it.
This is what I have:
# GENERIC TASKS
all-%:
for BIN in `ls cmd`; do
#$(MAKE) --no-print-directory BIN=$(BIN) $*
done
But I get this error, could anyone explain to me how can I make it work
bash
➜ make all-build
for BIN in `ls cmd`; do
/bin/sh: -c: line 1: syntax error: unexpected end of file
make: *** [all-build] Error 2
UPDATE
this is how the complete flow of my makefile looks like:
all-%:
for BIN in `ls cmd`; do \
#$(MAKE) --no-print-directory BIN=$BIN $*; \
done
build-%:
#$(MAKE) --no-print-directory BIN=$* build
build:
docker build --no-cache --build-arg BIN=$(BIN) -t $(BIN) .
Each line of a make-recipe is executed in a distinct invocation of the shell.
Your recipe fails with a shell-syntax error because this line:
for BIN in `ls cmd`; do
is not a valid shell command. Nor is the third line:
done
To have all three lines executed in a single shell you must join them
into a single shell command with make's line-continuation character \:
# GENERIC TASKS
all-%:
for BIN in `ls cmd`; do \
#$(MAKE) --no-print-directory BIN=$$BIN $*; \
done
Note also BIN=$$BIN, not $(BIN). BIN is a shell variable here, not a make variable: $$ escapes $-expansion by make, to preserve the shell-expansion $BIN.
Using ls to drive the loop in Make is an antipattern even in shell script (you want for f in cmd/* if I'm guessing correctly) but doubly so in a Makefile. A proper design would be to let make know what the dependencies are, and take it from there.
all-%: %: $(patsubst cmd/%,%,$(wildcard cmd/*))
$(MAKE) --no-print-directory -$(MAKEFLAGS) BIN=$< $*

`2>/dev/null` does not work inside a Makefile

I tried to suppress an error from rm command by writing
Makefile:
...
clean: $(wildcard *.mod)
-rm $^ 2>/dev/null
...
I ran:
$ make clean
rm 2>/dev/null
make: [clean] Error 64 (ignored)
I still had gotten an error.
Anyway, when I tried
$ rm [some non-existent files] 2>/dev/null
on the bash shell, it just works fine.
How can I use 2>/dev/null inside a makefile?
2>dev/null will redirect the error output so you don't see it, it will not prevent the shell to raise the error level. And the - sign in front of your shell command will tell GNU make to continue even if the error level is raised but it will not either prevent the shell to raise it.
What you want is the shell not to raise the error level and this can be done like this :
Unix (credits to this answer)
-rm $^ 2>/dev/null ; true
Windows
-rm $^ 2>NUL || true
or if you don't have rm on Windows
-del /F /Q $^ 2>NUL || true
The message make: [clean] Error 64 (ignored) is being printed by make after it sees that your shell command has failed.
It will therefore not be affected by any redirection that you use in the recipe.
Two fixes:
Use the -f rm flag. rm -f never returns an error.
(Well, hardly ever anyway, and if it does you probably want to know about it!)
Stop the shell command returning an error: simply append || : to the command.
Say what? Well if the rm succeeds your job is done and make is happy. OTOH if rm fails, the shell runs the second command in the or.
: is a shell built-in that always succeeds, and is much preferable to true IMHO.
The first of these is best in this case,
though the second is a general, if somewhat less efficient, pattern.
.PHONY: clean
clean: ; rm -rf *.mod

GNU Make (running program on multiple files)

I'm stuck trying to figure out how to run a program, on a set of files, using GNU Make:
I have a variable that loads some filenames alike this:
FILES=$(shell ls *.pdf)
Now I'm wanting to run a program 'p' on each of the files in 'FILES', however I can't seem to figure how to do exactly that.
An example of the 'FILES' variable would be:
"a.pdf k.pdf omg.pdf"
I've tried the $(foreach,,) without any luck, and #!bin/bash like loops seem to fail.
You can do a shell loop within the command:
all:
for x in $(FILES) ; do \
p $$x ; \
done
(Note that only the first line of the command must start with a tab, the others can have any old whitespace.)
Here's a more Make-style approach:
TARGETS = $(FILES:=_target)
all: $(TARGETS)
#echo done
.PHONY: $(TARGETS)
$(TARGETS): %_target : %
p $*

Resources