make for compiling — all *.c files in folders & subfolders in project
Ad 1 and 2: The filenames can safely include directories and %
matches /
as necessary. So you can easily have:
$(wildcard subdir/*.c) $(wildcard anotherdir/*.c)
or even
$(wildcard */*.c)
... or as suggested by keltar in comment
$(shell find . -name '*.c')
which is recursive.
Ad 3: You are doing it.
Ad 4: Create a target with $(OBJ)
as dependencies and use the automatic variable just as you do for compilation:
main : $(OBJ)
$(LD) $(LDFLAGS) -o $@ $< $(LIBS)
Create a Makefile to compile all .c files in a folder/subfolder and ship the binaries elsewhere
I am assuming that by "efficient" you mean "more generic/automated". Here is the most "efficient" solution I could think of:
CC := gcc
CFLAGS := -Wall
BIN_DIR := exe
SRC_DIRS := $(wildcard *-*)
.PHONY: all clean $(SRC_DIRS)
all: $(SRC_DIRS)
clean:
del /s /q $(BIN_DIR)\*
define SETUP_TARGETS
$(1): $$(patsubst $(1)/%.c,$(BIN_DIR)/%.exe,$$(wildcard $(1)/*.c))
$(BIN_DIR)/%.exe: $(1)/%.c $$(wildcard *.c)
$(CC) $(CFLAGS) $$(addprefix -I ,$$(patsubst %/,%,$$(dir $$^))) $$^ -o $$@
endef
$(foreach target,$(SRC_DIRS),$(eval $(call SETUP_TARGETS,$(target))))
Observations about the makefile:
- Since you are using the file extension
.exe
, I am assuming that you are running GNU Make on MS-Windows. Hence, I used Command Prompt syntax and.exe
extension. - You have to put the makefile in the root directory of the chapter (
chapter-1/
in this case). - When you copy the code to your makefile, please pay attention to the indented parts (recipe of each rule). Each command line must start with a
TAB
character, not spaces. - I am assuming the directory
exe/
already exists and always will be there. - The makefile will detect every subdirectory that matches the pattern
*-*
(e.g. 1-1, 1-2, ...) and will create a target for each one. - The command line
make target
(target is a subdirectory, e.g. 1-1, 1-2, ...) will create one executable inexe/
for each source file in the target subdirectory. I am assuming that each source file has a functionmain()
. - Each target build will also include every source file
*.c
from the root directory (ch1.c
in this case). I am assuming that these sources do not contain a functionmain()
. - The files involved in the builds do not have to be named in any particular way. All files will be detected.
- The include paths used in the compilations are the same directories that contain the sources involved, which are the target subdirectory and the root directory. Hence, you can
#include
header files from them to your programs. - The command lines
make
andmake all
will build all the targets. - The command line
make clean
will delete every file inexe/
. - This makefile was created to minimize build operations so that only executables that are out of date with respect to one of their dependent sources will be built. If you try to rebuild a target without modifying any source, you are going to get a warning like
make: Nothing to be done for '1-1'
. - You can use this makefile for other chapters as well, as long as they have the same directory structure.
Enjoy :)
Makefile : Automatically compile all c files, keeping .o files in separate folder
To build foo.o
from foo.c
, locally:
foo.o: foo.c
$(CC) -c $< -o $@
To do the same, but with any needed header files in src/
:
SRC := src
foo.o: foo.c
$(CC) -I$(SRC) -c $< -o $@
To do the same, but with the source file in src/
:
SRC := src
foo.o: $(SRC)/foo.c
$(CC) -I$(SRC) -c $< -o $@
To do that, but put the object file in obj/
:
SRC := src
OBJ := obj
$(OBJ)/foo.o: $(SRC)/foo.c
$(CC) -I$(SRC) -c $< -o $@
A pattern rule that will do that for any such object file (obj/foo.o
, obj/bar.o
, ...):
SRC := src
OBJ := obj
$(OBJ)/%.o: $(SRC)/%.c
$(CC) -I$(SRC) -c $< -o $@
To create the list of desired objects:
SOURCES := $(wildcard $(SRC)/*.c)
OBJECTS := $(patsubst $(SRC)/%.c, $(OBJ)/%.o, $(SOURCES))
And a rule to cover them all:
all: $(OBJECTS)
Putting it all together:
SRC := src
OBJ := obj
SOURCES := $(wildcard $(SRC)/*.c)
OBJECTS := $(patsubst $(SRC)/%.c, $(OBJ)/%.o, $(SOURCES))
all: $(OBJECTS)
$(CC) $^ -o $@
$(OBJ)/%.o: $(SRC)/%.c
$(CC) -I$(SRC) -c $< -o $@
Note that this has one big shortcoming: is does not track dependencies on header files. This can be done automatically, but it's a subtle trick; it can wait until you've mastered this much.
how to write a make file with a project : source in multi-level sub dir , keep obj file in separate folder?
First we construct a list of the sources in the tree:
SRCDIR := src
SOURCES := $(shell find $(SRCDIR) -name "*.c")
Then use that to construct a list of the object files we want:
OBJDIR := obj
OBJECTS := $(patsubst %.c,$(OBJDIR)/%.o,$(notdir $(SOURCES)))
If all the source files were in the working directory, we could use a simple static pattern rule:
$(OBJECTS): $(OBJDIR)/%.o: %.c
blah blah building $@ from $<
And to get that to work when the sources are in different directories, all we need is the vpath
directive:
SRCDIRS := $(dir $(SOURCES))
vpath %.c $(SRCDIRS)
Now for the headers. To steer the compiler toward the directories containing the headers, we could construct a string of -I
flags in one line or we could copy all of the headers into header/
as follows.
To keep all of the headers up to date, and not copy them unnecessarily, we must treat them as targets. First we make a list of them, as we did with the objects:
HEADERS := $(shell find $(SRCDIR) -name "*.h")
HEADERDIR := header
HEADERTARGS := $(addprefix $(HEADERDIR)/,$(notdir $(HEADERS)))
Then we write a static pattern rule, just as we did for the objects:
$(HEADERTARGS): $(HEADERDIR)/%.h: %.h
cp $< $@
vpath %.h $(SRCDIRS)
And finally add the headers as prerequisites of the objects:
$(OBJECTS): $(OBJDIR)/%.o: %.c $(HEADERTARGS)
...
This is slightly inefficient, as it will rebuild all objects if even one header has changed, but to correct that shortcoming would require a more complex makefile.
How give subdirectories for C project to include .h file in makefile?
Some approaches to handling this are:
- In the makefile, include a
-I directory
switch in the compilation flags for each directory containing a header file that might be included. The-I
switch tells the compiler to search the following directory, in addition to the directories it usually searches. For example, you would add switches-I first/inc -I second/inc -I third/inc
to the compilation flags. You can list the directories manually in the makefile or use GNU Make features for discovering them during the make. - Add a single
-I directory
switch listing a root directory for the project to the compilation switches and change all the#include
directives in source code to use paths relative to that root, such as#include "first/inc/first.h"
. - Create a makefile target that copies all header files to a single common directory, add one
-I directory
switch for that directory to the compilation switches, and list that target as a dependency for each of the object files, so that make always executes the commands for that target before compiling a source file. That target could be named for the directory used to gather the header files, and it would often be in a temporary or staging area for the build. Also, if you have aclean
target, one of its actions could be to remove that directory.
checking subfolders recursively for additional make files
If you use GNU make under GNU/Linux (or any UNIX-like OS with the find
utility), have no spaces in your directory names and all your makefiles are named Makefile
, the following could be a starting point. Add it to your top makefile and type make all
to build all subdirectories:
SUBDIRS := $(dir $(shell find . -mindepth 2 -type f -name 'Makefile'))
.PHONY: $(SUBDIRS) all
all: $(SUBDIRS)
$(SUBDIRS):
$(MAKE) -C $@
Explanations:
find . -mindepth 2 -type f -name 'Makefile'
is a shell command that finds all files (-type f
) named Makefile
(-name 'Makefile'
) in any subdirectory (-mindepth 2
) of the current directory.
$(shell CMD)
is the make function that passes the shell command CMD
to the shell and returns the result.
$(dir LIST)
returns the directory part of all paths of LIST
.
SUBDIRS := $(dir ...)
assigns all this to the make variable SUBDIRS
(replace SUBDIRS
by any name you want, it's just a name). So, if you have two subdirectories named foo
and bar/baz
, and if they contain a file named Makefile
, find . -mindepth 2 -type f -name 'Makefile'
returns foo/Makefile bar/baz/Makefile
, and SUBDIRS := $(dir $(shell find . ...))
assigns foo bar/baz
to the make variable SUBDIRS
.
.PHONY: $(SUBDIRS) all
declares that any name in the value of make variable SUBDIRS
, plus all
, are phony targets: that is, they are not real file names (even if files or directories with these names actually exist), and make, when asked to, shall rebuild them even if they already exist and are up to date with respect to their prerequisites.
all: $(SUBDIRS)
tells make that the all
target depends on all names in the value of make variable SUBDIRS
; in order to make all
make shall first make all names in the value of make variable SUBDIRS
.
Finally:
$(SUBDIRS):
$(MAKE) -C $@
is a make rule that explains how to make any name in the value of make variable SUBDIRS
. The recipe ($(MAKE) -C $@
) simply consists in invoking make again ($(MAKE)
) but in the $@
directory (-C $@
). The reason why you must use $(MAKE)
instead of just make
can be found in the GNU make documentation. $@
is one of the many make automatic variables. In recipes it expands as the current target. So, if the SUBDIRS
make variable has value foo bar/baz
, this rule is the same as the two separate rules:
foo:
$(MAKE) -C foo
bar/baz:
$(MAKE) -C bar/baz
Compile all .c files in a directory using GCC compiler in CMD
I guess gcc itself doesn't have such a parameter.
But you can try the regular wildcard argument of gcc *.c -o Output
where the *
(wildcard) is to read as "any".
Related Topics
How to Fix Urllib3 Runtimeerror: Requests Dependency 'Urllib3' Must Be Version >= 1.21.1, < 1.22
Bash Script Counting Instances of Itself Wrongly
Producer Consumer Implementation in a Block Device Driver
Is There a Linker Flag to Force It to Load All Shared Libraries at Start Time
Lapack/Blas/Openblas Proper Installation from Source - Replace System Libraries with New Ones
Use "Git Revert" to Back-Out a Change Adding a Line
Can a Gnome Application Be Automated? How
Infinite Loop Receive from Serial Port
Host Doing Unnecessary Dns Lookup for Localhost
How to Join Multiple Txt Files into Based on Column
Why Does Gdb Prompt "Unexpected Size of Section '.Reg-Xstate/Xxxxx' in Core File."
"Segmentation Fault (Core Dumped)" Error in Fortran Gfortran Linux
Difference Between "Cpu/Mem-Loads/Pp" and "Cpu/Mem-Loads/"
How to Use Unicode in Aspell Dictionary
Init Script '/Dev/Tty: No Such Device or Address' Error on Redirect
Finding Processor Id in Which Process Is Running [Through Command/Interface Similar to Top]