make - Get path to file without filename - makefile

I have in my Makefile a rule to get all src files:
CFILES := $(shell find ./ -type f -name '*.c')
How can I get only the Path to the file (without the filename) of the CFILES.
For example:
./drivers/fb/fb.c becomes ./drivers/fb
./stivalle/stivalle.c becomes ./stivalle

See the GNU make section on File Name Functions.
So you can write:
DIRS := $(dir $(CFILES))
This gives you paths with a trailing slash. If you want to remove the trailing slash, you can use:
DIRS := $(patsubst %/,%,$(dir $(CFILES)))
This will also give you duplicates if you have multiple source files in the same directory. If you want only one instance of each directory you can use:
DIRS := $(sort $(patsubst %/,%,$(dir $(CFILES))))

dirname is your friend. (dont't forget man dirname)
eg
$ dirname /opt/foo/bar.json
/opt/foo

Related

Makefile dir function doesn't expand as expected in for loop

I want to iterate through a list of files, execute cd in their respective directories before executing a command.
So I would like to use $(dir $(FILES)) function to get the directory, but it seems not to work properly in the for loop.
FILES=../dir1/file1 ../dir2/file2
.PHONY: all
all:
#for f in $(FILES); do \
echo $$f in $(dir $${f}) ; \
done
outputs
../dir1/file1 in ./
../dir2/file2 in ./
The $(dir $${f}) gets expanded to ./ instead of ../dirN.
Note :
Writing only echo $(dir $(FILES)) outside of the for loop outputs ../dir1/ ../dir2/ as expected.
using $(abspath ...) doesn't work either.
What am I missing ?
You are mixing make constructs (dir) and shell constructs. Unfortunately they are not evaluated at the same time. Make evaluates its constructs before passing the recipe to the shell, once for all, not during the execution of the recipe by the shell. When expanding your recipe (before passing it to the shell) it transforms it into:
for f in ../dir1/file1 ../dir2/file2; do echo $f in ./; done
because when make expands $(dir $${f}) it first expands the parameter:
$(dir ${f})
and then, as there is no / in the string ${f}, the expansion of the dir function replaces it with ./. From the GNU make manual:
$(dir names…)
Extracts the directory-part of each file name in names. The directory-part of the file name is everything up through (and
including) the last slash in it. If the file name contains no slash,
the directory part is the string ./. For example,
$(dir src/foo.c hacks)
produces the result src/ ./.
Anyway, mixing make and shell constructs is usually not what you want to do (there are exceptions). You could use only shell constructs:
all:
#for f in $(FILES); do \
echo $$f in $$(dirname $$f) ; \
done
Or you could use a static pattern rule to get completely rid of the shell loop and let make iterate over a list of similar targets:
FILES = ../dir1/file1 ../dir2/file2
FOOS = $(addsuffix .foo,$(FILES))
.PHONY: all $(FOOS)
all: $(FOOS)
$(FOOS): %.foo:
#echo $* in $(dir $*)

How to escape backslash in makefile shell function

I need to recursively find all the header files in a list of directories. I can't figure out how to escape the command properly. I have searched around and found various information on escaping in makefiles but I have not been able to solve this issue.
In bash the following does what I want:
find path1 path2 path3 -type f \( -name *.hpp -o -name *.h -o *.hxx \)
In my make file I have tried a few combinations of foreach, etc. Currently I have this:
INCLUDE_PATHS ?= path1 path2 path3
MY_HEADERS := $(shell find $(INCLUDE_PATHS) -type f \( -name *.h -o -name *.hpp -o -name *.hxx \))
This produces:
find: paths must precede expression
Usage: find [-H] [-L] [-P] [path...] [expression]
If I just look for one extension such as "*.hpp" it works fine (I assume because the \(...\) is not needed).
I have tried various combinations of $, ', ". \ to escape the '\' characters in the shell command without success.
Any help would be greatly appreciated.
Your problem doesn't have anything to do with make or the value of INCLUDE_PATHS or how make interprets backslash characters. The problem is that you're not escaping your globbing, and it's matching some local files. Rewrite your function to escape your glob statements, like this:
MY_HEADERS := $(shell find $(INCLUDE_PATHS) -type f \( -name \*.h -o -name \*.hpp -o -name \*.hxx \))
I would be very surprised if the original command works in bash without quoting those characters, if you run it from the same directory containing the same contents as make.
The variable MY_HEADERS becomes correct, by calling $($INCLUDE_PATHS) -- not $(INCLUDE_PATHS).
So your Makefile would be:
INCLUDE_PATHS ?= path1 path2 path3
MY_HEADERS := $(shell find $($INCLUDE_PATHS) -type f \( -name *.h -o -name *.hpp -o -name *.hxx \))
You can continue to check the variable's value:
all: printme
printme:
#echo $(MY_HEADERS)
Running this Makefile by make will show your desired answer.
Although MadScientist already answered the question perfectly, you could use the following to avoid the shell altogether:
INCLUDE_PATHS ?= path1 path2 path3
EXTENSIONS := .h .hpp .hxx
MY_HEADERS := $(shell find $(INCLUDE_PATHS) -type f \( -name \*.h -o -name \*.hpp -o -name \*.hxx \))
$(info $(MY_HEADERS))
MY_HEADERS := $(foreach p,$(INCLUDE_PATHS),$(foreach e,$(EXTENSIONS),$(wildcard $(p)/*$(e))))
$(info $(MY_HEADERS))

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 $^

How do I get the files in a directory and all of its subdirectories in bash?

I'm working on a C kernel and I want to make it easier to compile all of the sources by using a bash script file. I need to know how to do a foreach loop and only get the files with a .c extension, and then get the filename of each file I find so I can make gcc compile each one.
Use find to walk through your tree
and then read the list it generates using while read:
find . -name \*.c | while read file
do
echo process $file
done
If the action that you want to do with file is not so complex
and can be expressed using ore or two commands, you can avoid while
and make all things with the find itself. For that you will use -exec:
find . -name \*.c -exec command {} \;
Here you write your command instead of command.
You can also use -execdir:
find . -name \*.c -execdir command {} \;
In this case command will be executed in the directory of found file (for each file that was found).
If you're using GNU make, you can do this using only make's built-in functions, which has the advantage of making it independent of the shell (but the disadvantage of being slower than find for large trees):
# Usage: $(call find-recursive,DIRECTORY,PATTERN)
find-recursive = \
$(foreach f,$(wildcard $(1)/*),\
$(if $(wildcard $(f)/.),\
$(call find-recursive,$(f),$(2)),\
$(filter $(2),$(f))))
all:
#echo $(call find-recursive,.,%.c)

sed in makefile, usage

I am in the process of learning makefile creation.
Current target is to scan the $(SOURCEDIR) for .c files and create (according to that lise of .c file paths in the format of /path/file.c) a nice $(VPATH) so I do not need to use recursive makefiles (aka pain in the a..).
Atm I am stuck with this, where $(SOURCETREE) will be empty on $(ECHO)
SOURCES := $(shell find $(SOURCEDIR) -name '*.c')
SOURCETREE := $(dir $(SOURCES))
SOURCETREE := $(shell $(ECHO) $(SOURCETREE) | $(SED) -e "s/[[:space:]]/\n/g" | uniq | $(SED) -e "s/\n/[[:space:]]/g");
Maybe I just do not get the point (got late again :/ )
Thanks for any help.
Note: In a bash shell it works perfectly on my linux workbox (I replace thevariables accordingly)
Note: I am no sed pro, so please explain if you do an voodoo with sed, thank you
Comments:
Remove the backticks. They are unnecessary; the $(shell) function already captures the command's output.
The echo/sed/uniq/sed chain can be replaced by ls/uniq. ls will print one file name per line so no need for the replace-and-undo song and dance.
It looks like you're losing the result from $(dir) because you use $(SOURCES) rather than $(SOURCETREE) in the third assignment.
Result:
SOURCES := $(shell find $(SOURCEDIR) -name '*.c')
SOURCETREE := $(dir $(SOURCES))
SOURCETREE := $(shell ls $(SOURCETREE) | uniq);
Actually, even this shortened shell invocation is unnecessary. The $(sort) function will sort a list of names and remove duplicates. From make's documentation: "Incidentally, since sort removes duplicate words, you can use it for this purpose even if you don't care about the sort order."
SOURCES := $(shell find $(SOURCEDIR) -name '*.c')
SOURCETREE := $(sort $(dir $(SOURCES)))

Resources