Makefile trailing error "Command not found" when calling custom shell function - bash

I have a simple Makefile:
define git_all
ls --recursive --directory --color=never */.git \
| sed 's/\/.git//' \
| xargs --no-run-if-empty --max-procs=10 --replace={} git -C '{}' $1 || true
endef
gitpull:
#$(call git_all,pull -v)
The 'gitpull' rule is supposed to detect any and all repositories in the subfolders that are under the folder that the Makefile is in. For example:
Makefile
\- a/.git
\- b/.git
...
When I run 'make gitpull' the scripts works fine with one minor glitch. If a repository is already up to date I get this weird trailing error:
> make gitpull
make: Already: Command not found
make: *** [Makefile:xxx: gitpull] Error 127
I guess the shell is trying to interpret the text 'Already up to date' as a command and its failing. But why does it try to do that in the first place? I'm obviously missing something but what?

You seem to be confused as to what $(shell ...) does. If you want this to happen when the target is run, not when the Makefile is parsed, you want to take out the $(shell ...).
Also, don't use ls in scripts.
If I'm able to guess what you are trying to do, probably something like
define git-all
find . -type d -name '.git' -execdir sh -c 'cd ..; git -C $1' _ {} \;
endef
here:
$(call git-all,pull -v)

Related

gmake emits a "permission denied" error on a target. Why?

I'm running a golang build under a Makefile, and getting a truly strange permission error on a folder that has user permissions. The makefile looks like this:
PID = /tmp/nodemon-golang-project.pid
TMPL_FILES = $(shell find . -type f -name '*.tmpl')
GO_FILES = $(shell find . -type f -name '*.go')
CMD_FILES = $(shell find ./cmd -type f -name '*.go')
SASS_FILES = $(shell find ./scss -type f -name '*.scss')
APP = ./app
CSS = ./static/css/style.css
serve: restart
#fswatch -o . | xargs -n1 -I{} make restart || make kill
kill:
#kill `cat $(PID)` || true
before:
#echo "\nRESTARTED ..."
$(APP): $(GO_FILES) $(TMPL_FILES)
echo $(GO_FILES)
#go build -o $# $(CMD_FILES)
$(CSS): $(SASS_FILES)
#echo Generating CSS
#rm static/css/style.css
#sh -c sass scss/styles.scss static/css/style.css
restart: kill before $(APP) $(CSS)
#$(APP) & echo $$! > $(PID)
#echo Try css
#$(CSS)
.PHONY: serve restart kill before scss
I get the error when this runs:
$ gmake serve
RESTARTED ...
Try css
gmake: ./static/css/style.css: Permission denied
gmake: *** [Makefile:32: restart] Error 127
I'm trying to figure out why I'd get a permission error. All of the directories are under the normal user, and gmake is running under that same user. All of the directories involved as well, and all the directories are writable and executable.
Why would gmake have a problem with a normal file with normal permissions? Am I doing something wrong with the Makefile that would cause this kind of error?
First rule of debugging makefiles is, never add # to your makefile until it's fully working. You're just hiding all the critical information from yourself. Even after it's fully working I recommend not adding #.
Second, I don't know why you're adding sh -c to this rule; you're already in a shell, why start another one?
#sh -c sass scss/styles.scss static/css/style.css
However none of the above are your problem. Your problem is this:
CSS = ./static/css/style.css
....
restart: kill before $(APP) $(CSS)
#$(APP) & echo $$! > $(PID)
#echo Try css
#$(CSS)
Note this last line; CSS expands to ./static/css/style.css so that file is invoked as a program, but it doesn't have executable bits set, so the invocation fails.

Nested for loop not working in makefile

I am trying to use a nested for loop for searching and copying some files inside the recipe of one of the targets inside a makefile:
DIR = $(DIR_A) $(DIR_B)
install:
for dirs in $(DIR); do \
for file in $(shell find $(dirs) -type f -and -not -path "*/.svn*" | sed -e "s|$(dirs)||"); do \
folder=$${file%/*}; \
$(INSTALL) -d $(DEST_DIR)$$folder/log; \
$(INSTALL) $(dirs)/$$file $(DEST_DIR)$$folder/log; \
done \
done
However $(dirs) variable always evaluates to empty inside the second for loop and the current working directory gets passed to "find" instead of first directory path from $(DIR).
Can someone please suggest if I am missing something ?
Thanks.
The $(dirs) in the find command is being expanded by make to the make variable dirs which is unset and thus the empty string. To reference the shell variable, you need to escape the $:
for file in $$(find $${dirs} -type f -and \
-not -path "*/.svn*" | sed -e "s|$${dirs}||"); do
But don't do this. It is much cleaner to explicitly list the files you intend to install. If there are many, it is fine to write a script to generate the Makefile. What you are doing is a fragile mess.
You have made several errors, and you will find it almost impossible to solve them as l;ong as you insist on trying to solve them all at once.
Let's try this in stages. Suppose your DIR_A and DIR_B are north and south. On the command line, if you try this:
for dirs in north south; do echo $dirs; done
you will get the correct output:
north
south
If you try it as a makefile rule:
install:
for dirs in $(DIR); do echo $$dirs; done
again, it works correctly.
If you try your makefile recipe:
install:
for dirs in $(DIR); do \ for file in $(shell ls $$dirs); do \ echo "file is $$file"; \ done \ done
it fails, because Make expands the $(shell ...) command before passing the entire for command to the shell, when dirs has not yet been assigned a value. One way to construct a sensible shell command is to use backticks:
for dirs in north south; do for file in `ls $dirs`; do echo "file is $file"; done done
This works on the command line. A makefile rule built around it:
install:
for dirs in $(DIR); do for file in `ls $$dirs`; do echo "file is $$file"; done done
also works.
That should be enough to allow you to rewrite your makefile.

Ignoring file with similar names from compiling in Makefile

Suppose we have a directories two foo and bar. Where foo contains files example.S, run.c, exec.S and bar contains example.S
when I pass foo and bar as dependencies.
I want to compile only example.S in the directory bar by ignoring example.S in directory foo
DEP := foo bar
DIR := $(foreach $dirs, $(DEP), $(shell $(HOME) -find -type d -name $(dirs))),
would provide me the absolute path of the foo and bar directories.
FILES := $(foreach file, $(DIR), $(wildcard $(file)/*)), would provide me the list of files foo and bar as
foo/example.S foo/run.c foo/exec.S bar/example.S
I would like to check for multiple occurance of file with same name. and only compile the latest, i.e, instead of foo/example.S I like to compile bar/example.S...
I have no idea how to do it, As I am very new to creation of Makefile.
This is actually more of a shell question than a makefile one. What you want to do is create a shell command that generates two column list for each file -- the first column being the full filename, and the second being just the filename. Then sort it, and remove the ones with duplicate filenames, and then output only the first column.
Basically you would use:
DIRS = ./A ./B
files=`find $(DIRS) -type f -exec sh -c 'echo {} $$(basename {})' \; | sort -u --stable -k2,2 | awk '{print $$1}'`
You would have to replace the ./B ./A with a sorted list of directories you wanted to search in. Notice the $$'s -- Make resolves these to $ before running the shell command. The $(DIRS) only has a single $, so it is expanded before the command is executed.
Explanation:
find ./B ./A -type f -exec sh -c 'echo {} $(basename {})' \; : This searches the directories B and then A (in that order)
type -f specifies files only -- it won't return directories.
-exec sh -c 'echo {} $(basename {})' \; : for each file found, it will run the echo command where {} resolves to the file it found. It will therefore print the full file path, followed by just the filename.
sort -u --stable -k2,2: Sort the list of files based on column 2 (the filename). The -u means unique, so if there are matching files, it only prints the first. The --stable means that if there are two matching lines, it will always output the first one.
awk '{print $1}': print the first column of the output (the full path name).
You then have a list of unique filenames, including their directories in $(files)
There's a (relatively) simple way to get what you want, starting with reversing a list (with thanks to #simona). After you have calculated DIR your way,
reverse = $(if $(1),$(call reverse,$(wordlist 2,$(words $(1)),$(1)))) $(firstword $(1))
DIR := $(call reverse,$(DIR))
Then use VPATH:
VPATH = $(DIR)
Now you can tell Make to search for a file (such as example.S), and it will find the last instance (in this case .../bar/example.S):
example: example.S
#echo building $# from $^

bash, "make clean" in all subdirectories

How can I find every Makefile file in the current path and subdirs and run a make clean command in every occurance.
What I have till now (does not work) is something like:
find . -type f -name 'Makefile' 2>/dev/null | sed 's#/Makefile##' | xargs -I% cd % && make clean && cd -
Another option would be to use find with -execdir but this gives me the issue with $PATH : The current directory is included in the PATH environment variable, which is insecure in combination with the -execdir action of find ....
But I do not want to change the $PATH variable.
An answer using the tools I used would be helpful so that I can understand what I do wrong,
but any working answer is acceptable.
Of course find is an option.. My approach with that would be more like:
find . -name Makefile -exec bash -c 'make -C "${1%/*}" clean' -- {} \;
But since you're using bash anyway, if you're in bash 4, you might also use globstar.
shopt -s globstar
for f in **/Makefile; do make -C "${f%/*}" clean; done
If you want to use the execution feature of find you can still do this:
find "${PWD}" -name Makefile -exec sh -c 'cd "${0%Makefile}" && make clean' {} \;
I would use the following approach:
find "$(pwd)" -name Makefile | while read -r line; do cd "$(dirname "$line")" && make clean; done
Please note the find $(pwd) which gives the full path as output of find.

Linux bash-script to run make in all subdirectories

I'm trying to write a bash-script in Linux which traverses the current directory and, in every subdirectory, it launches the existing makefile. It should work for each subdirectory, regardless of depth.
Some restrictions:
I cannot use Python;
I don't know in advance how many subdirectories and their names;
I don't know in advance the name of current directory;
the make command for each directory should only be launched if there is makefile in such folder.
Any ideas on how to do it?
Using -exec and GNU make
find -type f \( -name 'GNUmakefile' -o -name 'makefile' -o -name 'Makefile' \) \
-exec bash -c 'cd "$(dirname "{}")" && make' \;
Given that this is make-related. I'd try to use a makefile at the top-level instead of a script. Something like this:
MAKEFILES:=$(shell find . -mindepth 2 -name Makefile -type f)
DIRS:=$(foreach m,$(MAKEFILES),$(realpath $(dir $(m))))
.PHONY: all
all: $(DIRS)
.PHONY: $(DIRS)
$(DIRS):
$(MAKE) -C $#
I'd accept what #MLSC says about using for with find, and that kind of applies here too .. the problem with that is when you have a space in the directory name. However, in many cases that's not going to happen, and IMHO there are benefits in using a makefile instead of a script. (There might be a solution using make that can cope with spaces in the directory name, but I can't think of it off the top of my head.)
You can use this script https://gist.github.com/legeyda/8b2cf2c213476c6fe6e25619fe22efd0.
Example usage is:
foreach */ 'test -f Makefile && make'
This should work if dont care about the execution order or if parent directory also has a Makefile.
#!/bin/bash
for f in $(find . -name Makefile); do
pushd $(dirname $f)
make
popd
done

Resources