Make for Compiling - All *.C Files in Folders & Subfolders in Project

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 in exe/ for each source file in the target subdirectory. I am assuming that each source file has a function main().
  • 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 function main().
  • 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 and make all will build all the targets.
  • The command line make clean will delete every file in exe/.
  • 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 a clean 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



Leave a reply



Submit