GNU make: What does '*** missing separator. Stop' mean for a source code file? - makefile

I'm fairly new to Makefiles, and I am trying to create one for a C project I am building using gcc.
I am familiar with the error Makefile:<col>: *** missing separator. Stop. It has popped up before when I used spaces instead of tabs to precede rules in the Makefile. I just tried writing a Makefile for this particular project (being sure to use TAB character instead of spaces) and when I run the make command, I get a very nondescript error I do not understand how to fix: src/main.c:7: *** missing separator. Stop
My directory structure looks like this:
- projectfolder/
- Makefile
- bin/
- build/
- inc/
- src/
- main.c
- otherfolder/
- inc/
- common.h
- io.h
- src/
- io.c
main.c, which includes the main function, has the following imports:
#include <stdio.h>
#include <stdlib.h>
#include "common.h"
#include "io.h"
My goal is to make a bunch of .o and .d files from the files in both src/ and inc/ directories and put those into projectfolder/build/, so that they can then be used to make the final executable in projectfolder/bin/
Finally, here is the Makefile that is causing the issue:
# Compiler and linker
CC := gcc
# Target binary
TARGET := the-program
# File extensions
SRCEXT := c
# Directories
TARGETDIR := bin
BUILDDIR := build
SRCDIRS := src /home/myusername/otherfolder/src
INCDIRS := inc /home/myusername/otherfolder/inc
# gcc options
CCFLAGS := -Wall -Wextra -O3
#---------------------------------------------------------------------------------
#DO NOT EDIT BELOW THIS LINE
#---------------------------------------------------------------------------------
vpath %.$(SRCEXT) $(SRCDIRS)
vpath %.h $(INCDIRS)
CCFLAGS += $(addprefix -I,$(INCDIRS)) -MMD -MP
SRC := $(shell find $(SRCDIRS) -name *.$(SRCEXT))
OBJ := $(SRC:$(SRCDIRS)/%.$(SRCEXT)=$(BUILDDIR)/%.o)
DEP := $(OBJ:.o=.d)
TARGET := $(TARGETDIR)/$(TARGET)
# RULE: Default make
all: makedirs $(TARGET) clean
# RULE: Remake
remake: fullclean all
# RULE: Clean
clean:
rm -rf $(BUILDDIR)
# RULE: Full clean (objects and binaries)
fullclean: clean
rm -rf $(TARGETDIR)
# RULE: Make dirs
makedirs:
mkdir -p $(BUILDDIR)
mkdir -p $(TARGETDIR)
# RULE: Link
$(TARGET): $(OBJ)
$(CC) $(OBJ) -o $#
# RULE: Compile
$(OBJ): $(SRC)
$(CC) $(CCFLAGS) -c $< -o $#
# RULE: Non-file targets
.PHONY: all remake clean fullclean makedirs
# include dependencies
-include $(DEP)
What about this file would cause the described error? I am assuming that it is related to the Makefile itself rather than the C code actually contained in src/main.c since this does not appear to be a compilation error, but if I am wrong, let me know and I can change the post.

This error:
src/main.c:7: *** missing separator. Stop
is clearly being printed by make. Since we know the format of these messages is <makefile>:<linenumber>: <error>, we can see that make is trying to parse the file src/main.c as a makefile and obviously this cannot work.
How could make be doing such a thing? The obvious culprit is this line:
-include $(DEP)
If the variable DEP contains the file src/main.c, then make would try to include that as a makefile and parse it. So how is DEP set?
DEP := $(OBJ:.o=.d)
This changes all words in OBJ that end with .o to end with .d. Crucially, it should be noted that this makes no changes to words that do not end in .o. So, if OBJS contained src/main.c, this would pass it through unmodified to DEPS.
So how is OBJ set? Here are the relevant variables:
SRCDIRS := src /home/myusername/otherfolder/src
SRC := $(shell find $(SRCDIRS) -name *.$(SRCEXT))
OBJ := $(SRC:$(SRCDIRS)/%.$(SRCEXT)=$(BUILDDIR)/%.o)
Let's expand this: the SRC variable runs:
find src /home/myusername/otherfolder/src -name *.c
(you really should escape the *, either with backslash or quotes: it's very dangerous how you have this).
Now we'll say that SRC gets the value:
SRC := src/main.c /home/myusername/otherfolder/src/other.c
Now what does OBJ contain?
OBJ := $(SRC:src /home/myusername/otherfolder/src/%.$(SRCEXT)=build/%.o)
This clearly cannot work: you can't put multiple directories into SRCDIRS, but then treat it as if it contained only one word.

The problem is in the line
OBJ := $(SRC:$(SRCDIRS)/%.$(SRCEXT)=$(BUILDDIR)/%.o)
The pattern substitution works only where the pattern matches and leaves all other strings alone.
As a simple demonstration see this makefile (no rules, just variable assignments):
x := foo bar baz
y := $(x:ba%=gu%)
$(info $(x))
$(info $(y))
Output:
foo bar baz
foo gur guz
As you can see, foo still is there, unchanged although it didn't match the pattern ba%.
In your case you are passing an impossible to substitute pattern, the content of $(SRCDIRS), which is the two words: src /home/myusername/otherfolder/src to the substitution call, therefore main.c - which is either src/main.c or /home/myusername/otherfolder/src/main.c remains unchanged in your list, gets inadvertently flushed further into $(DEP) and is finally included as text with your last line -include $(DEP).
As a recommendation I want to point you to VPATH (see manual and here: https://www.cmcrossroads.com/article/basics-vpath-and-vpath), which is the better alternative to indicate the location of your sources to make.

Related

Makefile do not regenerate main target after delete it

I learning to write makefile. I meet a problem today. My makefile makes the main target successfully. Then I delete the main target and run the "make" command again. But the makefile did not make the main target, just show "make: `main.o' is up to date". I don't know why. Here is my makefile and folder structure:
CXX := g++
FLAGS := -std=c++11
INCLUDE_DIR := $(shell find . -type d)
SOURCE_DIR := $(INCLUDE_DIR)
SOURCE_FILE := $(foreach dir, $(SOURCE_DIR), $(wildcard $(dir)/*.cpp))
OBJECT_FILE := $(SOURCE_FILE:%.cpp=%.o)
-include $(OBJECT_FILE:%.o=%.d)
TARGET := app
all: $(TARGET)
$(TARGET): $(OBJECT_FILE)
$(CXX) $(FLAGS) -o $# $(OBJECT_FILE)
$(OBJECT_FILE): %.o: %.cpp | $(SOURCE_DIR)
$(CXX) $(FLAGS) -c $< -o $# -MD -MF $(#:.o=.d)
./-----main.cpp
|---test
|---func.h
|---func.cpp
Unless you choose a specific target to be built on the command line (via make <target>), make will always choose the first target it sees in the makefile as the default target to be built
Consider these lines in your makefile:
-include $(OBJECT_FILE:%.o=%.d)
TARGET := app
all: $(TARGET)
When nothing is built, there are no .d files so the -include doesn't include anything. Then make finds the target all and uses that as the default.
The second time through the makefile, there are .d files so the -include command includes them. Those files define some targets, the first of which is main.o, and so now that is the first target and built by default.
You should move your -include down to the end of the makefile and ensure that the all target is always the first one built.
Just as an aside, I don't know if it's an artifact of your cut and paste into SO, but many of your lines have extra whitespace at the end. You should be aware that extra whitespace at the end of lines IS SIGNIFICANT in makefiles, in many places. I highly recommend using an editor that will allow you to visualize whitespace and/or automatically strip extra whitespace at the end of lines.

Makefile wildcard and variable expansion

I have this Makefile (abbreviated):
COMPILE = armcc
LINK = armlink
SRCDIR := ./src1 \
./src2
INCLUDES := -I ./inc
CSRC := $(wildcard $(SRCDIR)/*.c)
# CSRC := ./src1/*.c ./src2/*.c
OBJS := $(CSRC:.c=.o)
.PHONY: clean
clean:
#echo "Clean Finished"
%.o: %.c
#echo Compiling $<
#$(COMPILE) $(INCLUDES) $< -o $#
mktest: $(OBJS) Makefile
#echo $(CSRC)
#echo $(OBJS)
#echo building mktest
#$(LINK) -o mktest.axf
When I run it the wildcard only expanded for the last entry in the SRCDIR variable, which is ./src2. The output shown below.
c:> make mktest
./src1 ./src2/file2.c
./src1 ./src2/file2.o
building mktest
If I replace the line where CSRC defined, with the line below it. It works fine, and the output shown below.
c:> make mktest
./src1/*.c ./src2/*.c
./src1/*.o ./src2/*.o
building mktest
This is OK if I only have a few sub-directories I want to include. But if I want to include more, the Makefile will become ugly. Am I not using the wildcard function properly here?
What you would need your CSRC definition to be is:
CSRC:= $(foreach dir,$(SRCDIR),$(wildcard $(dir)/*))
If you look at the documentation:
$(wildcard pattern…)
This string, used anywhere in a makefile, is replaced by a space-separated list of names of existing files that match one of the given file name patterns…
This means your original line actually reads as:
CSRC := $(wildcard src1/ src2/*.c)
That is files whose names are matching against src1/ or src2/*.c.

Why is makefile exhibiting non-deterministic behaviour?

I have a makefile that is trying to do the following: identify all files under the current directory (all sub-directories included) with .c and .s extensions, for each one compile a non-linked object file and put it into a directory. All C files end up in objects/c, all assembly files end up in objects/ass.
The makefile always works as expected on the first execution (all commands are called in the right order) and no errors are produced.
However if I call make again, half of the time i get "nothing to be done for 'all'.". Which is what you would expect, since no files have been modified. But the other half of the time, make is selecting a random assembly file and compiling that file. That is to say,if I keep doing "make" I sometimes compile file1.s sometimes file2.s. and it keeps randomly swapping between the assembly files add infinitum (it never reaches a "nothing to be done") state.
How is make exhibitting non deterministic behaviour?
This is the smallest makefile I could make that reproduces the error:
SRC_C = $(wildcard *.c) $(wildcard **/*.c)
SRC_ASS = $(wildcard *.s) $(wildcard **/*.s)
OBJECTS_C = $(addprefix $(OBJECT_DIR)c/, $(notdir $(SRC_C:.c=.o)))
OBJECTS_ASS = $(addprefix $(OBJECT_DIR)ass/, $(notdir $(SRC_ASS:.s=.o)))
OBJECTS = $(OBJECTS_C) $(OBJECTS_ASS)
OBJECT_DIR = objects/
all: $(OBJECTS)
%/:
mkdir $#
$(OBJECTS_C): $(OBJECT_DIR) $(OBJECT_DIR)c/
arm-none-eabi-gcc -O0 -march=armv8-a $(wildcard */$(#F:.o=.c)) -nostartfiles -c -o $#
$(OBJECTS_ASS): $(OBJECT_DIR) $(OBJECT_DIR)ass/
arm-none-eabi-as -march=armv8-a $(wildcard */$(#F:.o=.s)) -c -o $#
.PHONY: clean
clean:
rm -rf $(OBJECT_DIR)
You have many errors here.
The biggest is a conceptual one: By flattening all your object files into one directory, there's no way to express proper dependencies using pattern rules, so your object files do not really depend on their respective source files. I'd say: just don't do that! Having object directories is fine, but they should mirror the directory structure of the source tree.
Further errors:
directly depending on directories. This will not work as expected, directories should always be order-only dependencies, as already stated in the comments
Make doesn't support recursive wildcards -- if you really need that, you could write your own function or, assuming you're always building on *nix, just call find instead
Pattern rules for creating directories are not the best idea either -- I'd suggest to collect all needed directories in a variable and loop over that.
Stylistic improvements:
Assign variables that don't need deferred evaluation with :=
Assign variables influencing the build process with ?=, so the user can override them at the command line
Use "standard" variables like CC, AS, CROSS_COMPILE
declare all phony targets in .PHONY.
Your Makefile with these changes applied would look like this:
OBJECT_DIR ?= objects
C_OBJECT_DIR ?= $(OBJECT_DIR)/c
AS_OBJECT_DIR ?= $(OBJECT_DIR)/ass
SRC_C:= $(shell find -name \*.c)
SRC_ASS:= $(shell find -name \*.s)
OBJECTS_C:= $(addprefix $(C_OBJECT_DIR)/, $(SRC_C:.c=.o))
OBJECTS_ASS:= $(addprefix $(AS_OBJECT_DIR)/, $(SRC_ASS:.s=.o))
OBJECTS:= $(OBJECTS_C) $(OBJECTS_ASS)
OUTDIRS:= $(sort $(dir $(OBJECTS)))
CROSS_COMPILE ?= arm-none-eabi-
CC ?= gcc
AS ?= as
CFLAGS ?= -O0 -march=armv8-a -nostartfiles
ASFLAGS ?= -march=armv8-a
all: $(OBJECTS)
$(OUTDIRS):
$(foreach _dir,$#,mkdir -p $(_dir);)
$(C_OBJECT_DIR)/%.o: %.c | $(OUTDIRS)
$(CROSS_COMPILE)$(CC) -c -o $# $(CFLAGS) $<
$(AS_OBJECT_DIR)/%.o: %.s | $(OUTDIRS)
$(CROSS_COMPILE)$(AS) -c -o $# $(ASFLAGS) $<
clean:
rm -rf $(OBJECT_DIR)
.PHONY: all clean
Note there is one important thing missing: automatic dependencies. With this Makefile, each object file depends on its respective source file, but completely misses any headers included. For anything other than a simple toy, you should add that, google for "gnu make gcc automatic dependencies" or something similar (not the scope of this question).

Fortran Makefile Error and Questions

I'm trying to make my own Makefile for a Fortran code. It looks very alike to another Makefile, which can perfectly work. But unfortunately I can't find out what's wrong by myself. The configuration is described below:
I have a main folder containing sub-folders name src, lib, results, mod, obj. Makefile itself is also in the main folder. I have several fortran sources files in the src folder, say main.f90, subroutine_1.f90, subroutine_2.f90, module_1.f90 and module_2.f90. Subroutine_1 and subroutine_2 are included in module_1, module_2 in also used in module_1. In this case I only need main.f90 and module_*.f90 to be included in my Makefile(if that's not wrong). I also have a static library to be used.
# For WINDOWS
RM := del
PS := $(strip \)
#--------------------- DICTIONARY ---------------------
BUILD_DIR := .
SRC_DIR := $(BUILD_DIR)$(PS)src
LIB_DIR := -L$(BUILD_DIR)$(PS)lib
TAR_DIR := $(BUILD_DIR)$(PS)results
MOD_DIR := $(BUILD_DIR)$(PS)mod
OBJ_DIR := $(BUILD_DIR)$(PS)obj
#------------------ COMPILER AND FLAG ------------------
FC := gfortran
FCFLAGS := -g -c -fbacktrace -fno-align-commons -fbounds-check\
-J"$(MOD_DIR)"
FLFLAGS :=
#------------------- TARGET & SOURCE--------------------
TARGET := $(TAR_DIR)$(PS)main.exe
SRC := $(notdir $(wildcard $(SRC_DIR)$(PS)module_*.f90))\
main.f90
LIB := -lfftw3-3.lib
LDFLAGS := $(LIB_DIR)
OBJ := $(patsubst %.f90, $(OBJ_DIR)$(PS)%.o, $(notdir $(SRC)))
all: $(TARGET)
# Final linking
$(TARGET): $(OBJ)
$(FC) $(FLFLAGS) -o $# $(OBJ) $(LDFLAGS) $(LIB) #line a
$(OBJ_DIR)$(PS)%.o: $(SRC_DIR)$(PS)%.f90
$(FC) $(FCFLAGS) -o $# $< #line b
clean:
-$(RM) -f $(OBJ_DIR)$(PS)*.o
-$(RM) -f $(MOD_DIR)$(PS)*.mod
I got many errors from the make process but I guess they all come from the same place.
$ make
Makefile:line a: warning: overriding recipe for target '.'
Makefile:line b: warning: ignoring old recipe for target '.'
make: Circular . <- . dependency dropped.
make: *** No rule to make target 'src\%.f90', needed by '.'. Stop.
Except from the error, I also have several questions:
How to choose appropriate flags for compiling?
Since module_2 is used in module_1, do I need to compile them in order? In reality I have many modules so I don't think I'm able to specify that one by one...
When do I need to create a dependency .d file? I don't quite know how it works.
Do I need to compile .mod out of .o? For example add a line
%.mod: %.o
Thanks a lot.

Makefile - compile multiple C file at once

This question is different from the one at makefiles - compile all c files at once in the sense that I have one extra requirement: I want to redirect all the object files in a separate directory.
Here is the setup:
I have multiple sources in a directory say src/mylib.
I want the objects files to end up in build/mylib.
Please note also that under mylib there are subdirectories.
The first attempt was as follows:
sources = $(shell find src/ -name ".c")
objects_dirs = $(subst src/, build/, $(dir $(sources)) # This variable is used by the build rule to create directories for objects files prior to compilation
objects = $(subst src/, build/, $(patsubst %.c, %.o, $(sources))) # This variable has the paths to the objects files that will be generated in the build directory
# This is where things aren't working as expected
$(objects): build $(sources)
$(cc) $(cflags) -o $# $(word 2, $^))
build:
$(foreach dir, $(objects_dirs), $(shell mkdir -p $(dir)))
For the makefile above, only one object file was being generated. I guessed this might have something to do with GCC only being able to generate one object file at a time. Regardless of that, checking the values of $# and $(word 2, $^) in the $(objects) target shows that only one file is being considered even though I have multiple files.
So I changed my makefile to the following:
sources = $(shell find src/ -name ".c")
objects = $(subst src/, build/, $(patsubst %.c, %.o, $(sources))) # This variable has the paths to the objects files that will be generated in the build directory
# This works as expected but it appears to me like make is generating all the objects files even though source files did not change. This can be seen by checking the timestamps on new object files after running make again.
$(objects): build $(sources)
$(foreach source, $(sources), $(shell $(cc) $(cflags) -o $(subst src/,build/, $(patsubst %.o,%.c,$(source))) $(source)))
build:
$(foreach dir, $(objects_dirs), $(shell mkdir -p $(dir)))
The second makefile works as expected but objects files are being rebuilt again which defeats another purpose of using make: only recompile those source files that changed from the last compilation.
Hence my question: how does one generate all object files in a separate directory at once (by this I mean perform the compilation of all sources files in one rule) while making sure that if a source file didn't change the associated object file should not be regenerated.
I am not after speeding up compilation. What I seek is one rule that will generate all objects files such that only updated source files should be recompiled.
The last makefile does the job but there is a recompiling of all source files which defeats another purpose of using make: only changed source files should be recompiled.
EDIT
After reading comments, it appears I have not phrased my question properly. As the details of what I have are already present, I leave the question as it is with additional details below.
The second makefile in the source code above does work. But it does only half the job. The build directory effectively mirrors the src directory.
So if I have say a file as src/mylib/point/point.c, I get build/mylib/point/point.o generated. This is the first part.
The second part is that if point.c does not changes, point.o in the build/mylib/point/ directory must not be regenerated. But after checking timestamps on the object file, I can tell that a new object file replaced the old one after running make again. This is not good because for large projects, compilation time remains O(n) with n being the number of source files to compile.
So this question is about how to preserve the second makefile without make regenerating object files.
From what I can gather from comments, I am asking too much from make. But if anyone knows how to make this happen, I leave the question open.
Makefile:
all:
clean:
src_root := src
src_subdirs := foo foo/bar foo/bar/buz
build_root := build
o_suffix := .o
# Build list of sources. Iterate every subfolder from $(src_subdirs) list
# and fetch all existing files with suffixes matching the list.
source_suffixes := .c .cpp .cxx
sources := $(foreach d,$(addprefix $(src_root)/,$(src_subdirs)),$(wildcard $(addprefix $d/*,$(source_suffixes))))
# If src_subdirs make variable is unset, use 'find' command to build list of sources.
# Note that we use the same list of suffixes but tweak them for use with 'find'
ifeq ($(src_subdirs),)
sources := $(shell find $(src_root) -type f $(foreach s,$(source_suffixes),$(if $(findstring $s,$(firstword $(source_suffixes))),,-o) -name '*$s'))
endif
$(info sources=$(sources))
# Build source -> object file mapping.
# We want map $(src_root) -> $(build_root) and copy directory structure
# of source tree but populated with object files.
objects := $(addsuffix $(o_suffix),$(basename $(patsubst $(src_root)%,$(build_root)%,$(sources))))
$(info objects=$(objects))
# Generate rules for every .o file to depend exactly on corresponding source file.
$(foreach s,$(sources),$(foreach o,$(filter %$(basename $(notdir $s)).o,$(objects)),$(info New rule: $o: $s)$(eval $o: $s)))
# This is how we compile sources:
# First check if directory for the target file exists.
# If it doesn't run 'mkdir' command.
$(objects): ; $(if $(wildcard $(#D)),,mkdir -p $(#D) &&) g++ -c $< -o $#
# Compile all sources.
all: $(objects)
clean: ; rm -rf $(build_root)
.PHONY: clean all
Environment:
$ find
.
./src
./src/foo
./src/foo/bar
./src/foo/bar/bar.cxx
./src/foo/bar/buz
./src/foo/bar/buz/buz.c
./src/foo/bar/foo.c
./src/foo/foo.cpp
Run makefile:
$ make -f /cygdrive/c/stackoverflow/Makefile.sample -j
sources=src/foo/bar/bar.cxx src/foo/bar/buz/buz.c src/foo/bar/foo.c src/foo/foo.cpp
objects=build/foo/bar/bar.o build/foo/bar/buz/buz.o build/foo/bar/foo.o build/foo/foo.o
New rule: build/foo/bar/bar.o: src/foo/bar/bar.cxx
New rule: build/foo/bar/buz/buz.o: src/foo/bar/buz/buz.c
New rule: build/foo/bar/foo.o: src/foo/bar/foo.c
New rule: build/foo/foo.o: src/foo/bar/foo.c
New rule: build/foo/bar/foo.o: src/foo/foo.cpp
New rule: build/foo/foo.o: src/foo/foo.cpp
mkdir -p build/foo/bar && g++ -c src/foo/bar/bar.cxx -o build/foo/bar/bar.o
mkdir -p build/foo/bar/buz && g++ -c src/foo/bar/buz/buz.c -o build/foo/bar/buz/buz.o
mkdir -p build/foo/bar && g++ -c src/foo/bar/foo.c -o build/foo/bar/foo.o
mkdir -p build/foo && g++ -c src/foo/bar/foo.c -o build/foo/foo.o
Environment again:
$ find
.
./build
./build/foo
./build/foo/bar
./build/foo/bar/bar.o
./build/foo/bar/buz
./build/foo/bar/buz/buz.o
./build/foo/bar/foo.o
./build/foo/foo.o
./src
./src/foo
./src/foo/bar
./src/foo/bar/bar.cxx
./src/foo/bar/buz
./src/foo/bar/buz/buz.c
./src/foo/bar/foo.c
./src/foo/foo.cpp
Try running this Makefile with 'src_subdirs=' to exercise another approach to locate sources. Output should be the same.
I finally had some time to experiment with this, so here is what I came up with:
BUILD_DIR = build
SRC_DIR = src
SOURCES = $(shell find $(SRC_DIR)/ -name "*.c")
TARGET = program
OBJECTS = $(SOURCES:$(SRC_DIR)/%.c=$(BUILD_DIR)/%.o)
default: $(TARGET)
.SECONDEXPANSION:
$(OBJECTS) : $$(patsubst $(BUILD_DIR)/%.o,$(SRC_DIR)/%.c,$$#)
mkdir -p $(#D)
$(CC) -c -o $# $(CFLAGS) $<
$(TARGET): $(OBJECTS)
$(CC) -o $# $(CFLAGS) $^
.PHONY: default
Points of interest:
I had to change the sources find pattern from ".c" to "*.c", I'm not sure if it depends on the exact shell used, but if you want to stay portable, be sure to use a widely accepted pattern.
The .SECONDEXPANSION: is needed to enable the $$ rules for GNU Make. It is needed to allow target based substitution rules in the prerequisites for the $(OBJECTS).
The prerequisite $$(patsubst $(BUILD_DIR)/%.o,$(SRC_DIR)/%.c,$$#) is saying, that the current target depends on a specific source file with the same folder structure and name.
The command mkdir -p $(#D) is ensuring, that the path of the current target is created if it's missing.
If all you want is a single rule to handle all object files, without necessarily needing to "compile all at once" then you could have something like this:
BUILD_DIR = build
SOURCES = ...
TARGET = ...
OBJECTS = $(SOURCES:%.c=$(BUILD_DIR)/%.o)
default: target
target: $(TARGET)
$(TARGET): $(OBJECTS)
$(LD) -o $# $(LDFLAGS) $^ $(LIBS)
$(BUILD_DIR)/%.o: %.c
$(CC) -c -o $# $< $(CFLAGS)
$(BUILD_DIR):
-mkdir $#
[Note: This is written from memory and without testing.]
After reading the GNU make manual again, here is a solution that solves the second problem.
The first attempt was the correct path. And the second attempt has the $(sources) in the prerequisites but does not use it in the commands and this is silly.
So the working makefile follows. It puts object files in a separate directory and it only compiles files that have changed.
sources = $(shell find src/ -name ".c")
$objects_dirs = $(subst src/, build/, $(dir $(sources)) # This variable is used by the build rule to create directories for objects files prior to compilation
objects = $(subst src/, build/, $(patsubst %.c, %.o, $(sources))) # This variable has the paths to the objects files that will be generated in the build directory
# This should now work as expected: object files go into their designated directories under "build/" and only updated files will be recompiled.
$(objects): build $(sources)
# After running say "make clean", make will figure out the need to run the first prerequisite.
# If we are doing a clean build, the number of prerequisites will equal the number of new prerequisites.
ifeq ($(words $?), $(words $^))
# Note the use of "$?" instead of "$^". $? is used since it holds prerequisites that are newer than the target while $^ will holds all prerequisites whether they are new or not.
$(foreach source, $(wordlist 2, $(words $?), $?), $(shell $(cc) $(cflags) -o $(subst src/,build, $(patsubst %.c,%.o, $(source))) $(source)))
else
# If we have a few new targets, no need to exclude "build" from prerequisites because the first prerequisite will be a file that changed.
$(foreach source, $?, $(shell $(cc) $(cflags) -o $(subst src/,build, $(patsubst %.c,%.o, $(source))) $(source)))
endif
.PHONY: build
build:
$(foreach dir, $(objects_dirs), $(shell mkdir -p $(dir)))
.PHONY: clean
clean:
#rm -rf build/
The makefile is heavily commented with changes that made it work. The most important changes were:
Use of $(foreach) to compile each file individually as required by GCC
Use of $? to work only with prerequisites that are newer than the target
Use of conditional to detected whether the first prerequisite has changed depending on circumstances. If we have a clean build (running make for the first time or after running make clean), the number of updated prerequisites will be the same as the number of newer prerequisites compared to the target. In other words $(words $?) == $(words $^) will be true. So we use this fact to exclude the firs prerequisite listed (build in our case) from the list of files to pass to GCC.
Also, when building the executable from the objects files, make sure to use $^ and not $? when selecting prerequisites else you will end up with only newer files in the executable and it will not run.
target = bin/mylib.a
.PHONY: all
all: $(target)
$(target): $(objects)
ar -cvq $# $^ # Notice that we're not using $? else only updated object files will end up in the archive.

Resources