GNU make wildcard alternative? - makefile

I would like to select all files in directory but using FreeBSD's make.
In GNU make this approach works:
FILES=$(wildcard *.c)
I am using FreeBSD's make, not GNU make so I am looking for command that will work in FreeBSD's make.
As it s stated in bottom link, FreeBSD has it's own functions but I cannot find them.
Generic Makefile not working on FreeBSD

You can use != to execute a command in FreeBSD's make. E.g:
FILES!= ls *.c
or if you want to find files in subdirectories as well;
FILES!= find . -type f -name '*.c'

Related

How to search sub-directories using GCC

I have a top level folder abc which contains multiple folders. I don't want to provide every single path to headers in my different folders using -I gcc option. Is there a way through which gcc can search for header file in all subfolders of abc ?
Eg.
abc
|----abcd
|----header1.h
|----abce
|----header2.h
|----abcf
|----header3.h
I want to include header1.h header2.h header3.h but don't want to use following approach
-I abc/abcd/
-I abc/adbe/
-I abc/abcf/
Is there a way through which I can just do -<someflag> abc and it searches whole subdirectories ?
Afaik, gcc does not have anything like that. And for good reasons imho. What if there are two files with the same name but in different directories?
If using clang is an option, you can use -Iabc/**
Depending on what shell you're using, you could create a short shell script. Don't know if this fits your needs exactly, but it should probably be easy enough to modify:
dirs=""
for d in $(find -maxdepth 1 -mindepth 1 -type d -print0)
do
dirs=$dirs"-I $d "
done
echo $dirs
You could use that in combination with assigning a variable in a Makefile. Let's call the above script dirs.sh and then use this in the Makefile:
INCLUDE_DIRS := $(shell ./dirs.sh)

How to use for, find and wildcard in a Makefile?

I am trying to do the following without any success so far:
remove:
for file in $(shell find src/ -name migrations -type d); do rm $(wildcard "$(file)/0*.py"); done
If I understood well, you want to remove all files named:
src/**/migrations/0*.py
where ** can be anything, including nothing at all. find can do this alone:
find src -path '*/migrations/0*.py' -delete
And as make recipes are just bare shell, you do not need $(shell... or anything like this:
remove:
find src -path '*/migrations/0*.py' -delete
But before running this potentially dangerous rule, you should maybe use this one until you are satisfied:
remove:
find src -path '*/migrations/0*.py' -ok rm {} \;
The only difference is that it will ask you confirmation before deleting files. We can also make all this a bit easier:
remove-safe: DELCMD := -ok rm {} \\;
remove-unsafe: DELCMD := -delete
remove-dry-run: DELCMD := -print
remove-safe remove-unsafe remove-dry-run:
find src -path '*/migrations/0*.py' $(DELCMD)
Use one or the other goal depending of what you want:
remove-dry-run to print the list of files that would be deleted without deleting them,
remove-safe to delete the files with confirmation,
remove-unsafe to delete the files without confirmation.
EDIT 1: if this an exercise about loops and make, just remember that make recipes are shell scripts (one per line) that are first expanded by make and next passed to the shell. So:
If you want to use shell variables, use them in one single line (but you can break lines with a trailing \). Else they will be from different shell invocations and it will not work as expected.
If you use the $ sign in your recipe for shell expansion, double them to escape the first expansion by make.
In the following I assume that you do not have directory or file names with spaces or special characters. If you do, it is time to ask a question about your shell.
So, with the bash shell, for instance, we can first build an array of target directories and then loop over them to delete the files:
SHELL := bash
remove:
dirs=( $$(find src -type d -name migrations) ); \
for d in "$${dirs[#]}"; do \
rm -f "$$d"/0*.py; \
done
EDIT 2: you could also use the builtin make loop system with one (phony) clean target per directory.
DIRS := $(shell find src -type d -name migrations)
clean-targets := $(addprefix clean-,$(DIRS))
.PHONY: remove $(clean-targets)
remove: $(clean-targets)
$(clean-targets): clean-%:
rm -f "$*"/0*.py
The tricky part is the:
$(clean-targets): clean-%:
rm -f "$*"/0*.py
rule. It is equivalent to one rule per migrations directory that would be:
clean-DIR: clean-%:
rm -f "$*"/0*.py
where DIR is the directory path. And these rules are static pattern rules. In the recipe the $* make automatic variable is expanded by make as the stem of the clean-% pattern. So, if DIR is src/foo/bar/migrations, the rule for it is equivalent to:
clean-src/foo/bar/migrations:
rm -f src/foo/bar/migrations/0*.py
The main advantage of this style over the shell loop is that make can run all recipes in parallel. If you have hundreds of migrations directories and if you have 8 or 16 cores on your computer, it can really make a difference. Just try:
make -j1 remove
make -j16 remove
and you will see the difference.
The short answer is you can't do that.
It's critical when writing makefile recipes to be clear in your own understanding of how they work: first, make will expand all make variables and functions. Then, make will pass the resulting string to the shell to be run. Finally, make waits until the shell completes and examines the exit code to find out whether it succeeded or not.
Here you're trying to use a shell for loop where the body of the loop invokes a make wildcard function. This cannot work.
Consider what you are wanting make (and the shell) to do: the shell would run its for loop, then every time it executed the body of the loop it would communicate back to make the value of some shell variable and make would expand the make wildcard function using that variable, then return the results to the shell for more processing. This is simply not possible.
Best is to write the recipe using shell constructs. A good rule of thumb is that it's never a good idea to use make's shell function inside a recipe: the recipe is already being run in a shell, so you don't need the shell function at all. It's simply confusing. Similarly, you rarely need make's wildcard function in a recipe because the shell does file globbing for you automatically.
I would write this as:
remove:
find src/ -name migrations -type d | while read -r dir; do rm "$$file"/0*.py; done
Note we have escaped $$file with two dollar signs to keep make from expanding it as a make variable.

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

Shell script to iterate through sub-directories of a directory and execute a command

I want to compile each and every file in a directory as well as its sudirectories and directories of that subdirectories. So the basic idea is to compile all the files in that project. For that i have to write a shell script.
My code
cd pathtomaindirectory
gcc *.c
In this i have to run the that command on all the subdirectory files with .c extensions. So how do i use a for loop to it. I'm new to shell script
Thank You
With bash, enable recursive glob wildcards:
shopt -s globstar nullglob
gcc **.c
You could do something like
cd path/to/main/directory
find . -name '*.c' -exec gcc -c {} \;
gcc -o app *.o

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)

Resources