1

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.

Community
  • 1
  • 1
nt.bas
  • 736
  • 1
  • 6
  • 13
  • 4
    Why not run a parallel make using the `-j` option? – Some programmer dude Jan 10 '17 at 12:09
  • @Someprogrammerdude I am not familiar with the `-j` flag but last I used it my impression was that it sped up compilation which is not what my question is about. – nt.bas Jan 10 '17 at 12:19
  • 4
    Why should they be created all at once? Normally make calls the compiler for each object and creates it one after the other. What is the purpose of your request if it is not speeding up things? – Gerhardh Jan 10 '17 at 12:29
  • @Gerhardh You make a good point. But if I have to write a rule for each object file, I might as well do a manual compilation. So taking your feedback into consideration, what I mean is that I want to have a one catch all rule: perform the compilation of all source files into object files to the destination directory while not recompiling source files that did not change. – nt.bas Jan 10 '17 at 12:35
  • You are aware of that the `gcc` program is only a frontend program that calls other programs to do the actual compilation and linking? And that it doesn't do parallel processing of the input files. When you pass multiple source files to `gcc` it doesn't them "at once" but instead do them in serial, much like calling `gcc` once for each source file. – Some programmer dude Jan 10 '17 at 12:35
  • So the problem is not actually parallel or quicker builds, but only having a single rule for the object files? Then that can be accomplished in other ways. Ways which actually allows parallel and quicker builds – Some programmer dude Jan 10 '17 at 12:36
  • 3
    Without looking into the details of your files I'm almost sure that `-j` is what you want. The makefile should simply list all dependencies properly (not too many, not too few: Exactly the existing ones). Then the `-j ` option will start to build as many targets in parallel as are available, to the maximum given as . (Edit: I see that you may want to build them *in a single gcc run.* That is of course a different requirement not addressed by `-j`.) – Peter - Reinstate Monica Jan 10 '17 at 12:37
  • @Someprogrammerdude Yes, yes. That is what I am after. Thanks :-) And yes, I am aware of the entire compilation process. If you know how a single rule can perform said compilation, please write it as an answer. – nt.bas Jan 10 '17 at 12:38
  • Talk about XY problems... so you want a generic rule for *.o files in a subdirectory structure that is resembling your source structure? Doing it at once or separately doesn't matter as long as the makefile code stays small, readable and is automatically gathering sources with `find` or similar? – grek40 Jan 10 '17 at 12:40
  • @PeterA.Schneider I do not believe knowing the details about the files will help. Especially if someone else in the future has requirements similar to mine with minor differences. **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.** – nt.bas Jan 10 '17 at 12:41
  • There's a thing you should be careful with: when you restrict recompilation, you might run into errors when changing header files without touching all related source files. In such a case, 'naive' rules will leave the objects from untouched source files in an outdated state. Then it's either time for a full rebuild or for more sophisticated build rules – grek40 Jan 10 '17 at 12:45
  • @grek40 You got it :-) But I really don't care how it's done. I used the `find` command because it worked for any directory depth while `widlcard` required me to specify the depth. All I want is that the build directory mirrors the source directory **but** the only changed source files should be recompiled. – nt.bas Jan 10 '17 at 12:46
  • The question you link to is about using `gcc` in an _unusual_ mode where it compiles _multiple_ input files and does whole-program optimisation; that necessarily requires re-examining all the source files if any change. From other comments, it appears that you want to keep the makefile compact (good): you can probably achieve that with a `%.o: %.c` rule which recompiles any source files which have changed (in multiple invocations of `gcc`), and then a separate link rule. – Norman Gray Jan 10 '17 at 12:46
  • @NormanGray You are correct about what I seek. The rule you mention,namely `%.o: %.c` will unfortunately generate objects in the same directory as their corresponding source files and I do not want that. – nt.bas Jan 10 '17 at 12:49
  • @NormanGray But again, this should be restricted to `make`. I want it to call GCC for each source and put the object file in the specified directory. As you can see in the question details, I have achieved this. The problem is that running make again calls GCC for each source file even if it did not change. For large projects, that is a problem! – nt.bas Jan 10 '17 at 12:51
  • @grek40 About naive rules: you are correct again but I am not concerned about updated header files because I have a separate rule that deals with changed header files. – nt.bas Jan 10 '17 at 12:53
  • "What I seek is one rule that will generate all objects files such that only updated source files should be recompiled." - that's the basic idea behind `make`. It still is not clear what your problem is. Maybe you should read the documentation of make and/or do some tutorial. Your question either looks like an XY problem (which often means the asker does not understand the actual problem well enough to point at it) or asking for tutoring. – too honest for this site Jan 10 '17 at 12:55
  • 1
    Core problem: You want to build source files at various depths in a source tree; OK. That's rather difficult to do, goes against the grain of `make`, and `make` will not make this easy. You might want to read [Recursive make considered harmful](http://aegis.sourceforge.net/auug97.pdf) (and other places), though I think Miller somewhat overstates the problems with that approach, and recursive make is how _I_ would approach this. _Short answer_: there isn't a short answer to this. (ie, you may need to re-think your requirements) – Norman Gray Jan 10 '17 at 12:59
  • @Olaf If my question is not clear, I shall take a few minutes to rephrase it. I believe I understand how GCC and make work. I have also achieved half of my goal: the objects files are being generated as expected. But even if source files did not change, make is invoking GCC to rebuild those source files which is a problem as you know. – nt.bas Jan 10 '17 at 13:06
  • @NormanGray You understand what I am after and if you believe this is not easy to do with make, then I shall stick with the my current makefile. It compiles as per my requirements but since it compiles all source files even if they did not change, I shall live with that for now. – nt.bas Jan 10 '17 at 13:08
  • As I already wrote, `make` does **not** call the builder if nothing has changed. If you experience something different, your files **have** changed. As make works with timestamps, I would check those. Maybe your IDE/editing tools did something unexpected. – too honest for this site Jan 10 '17 at 13:11
  • @Olaf Expect I can tell that **even if source files did not change**, associated object files are being rebuilt. I can tell this by looking at the creation time of object files. And I am not using any complicated IDE that does things behind my back. I am using Sublime Text 3 and I am sure it does **not** update my files without my knowledge. I though I am experiencing this because something is wrong with my makefile. Either way, I think I will stick with the current setup as it solves half the problem. – nt.bas Jan 10 '17 at 13:16
  • Can't test right now, but maybe you can try the rule as `$(objects) : $(@:build/%.o=src/%.c)` instead of `$(objects) : $(sources)`. In my theoretical world this should produce the prerequisite from the current target instead of all targets/sources. As said, can't test if this is actually working syntax. – grek40 Jan 10 '17 at 13:20
  • @grek40 I shall try that, thanks. – nt.bas Jan 10 '17 at 13:23
  • @Olaf It appears my second makefile was erroneous and my knowledge of GNU make has indeed been incomplete. After reading the gnu make manual again, I got it to work – nt.bas Jan 11 '17 at 17:17
  • @NormanGray I made it work without going with recursive make. And thanks for the paper as well. – nt.bas Jan 11 '17 at 17:18

4 Answers4

3

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.

Alexey Semenyuk
  • 674
  • 4
  • 9
1

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.

bobbogo
  • 14,989
  • 3
  • 48
  • 57
grek40
  • 13,113
  • 1
  • 24
  • 50
  • It sure took me some time to understand what's going on here. I think this might work as well. Will test it and get back to you. Also, in the `$(OBJECTS)` target recipe, you want to use `$?` so as to work only with updated prerequisites only (`$<` will select only one file in the list of files that changed). Also please consider removing `-o` from C flags since GCC at least does not allow to use `-c` and `-o` at the same time. Apart from that initial feedback, I will get back to you after testing. – nt.bas Jan 11 '17 at 18:39
  • @nt.bas You should definitely test this as I think you didn't get every part of it yet. The `$(OBJECTS)` rule works in a way, where each object has only one prerequisite and this prerequisite is meant to stay (it's the exact *.c file that is used to create the object, so the object will only be re-created if the c file is newer). Using `$^`, `$?` or `$<` in the commands doesn't change anything, since there is only one prerequisite and it __will__ be newer when commands are executed - use whatever you like but I'd advise against `$?` if you ever want to add other prerequisites. – grek40 Jan 11 '17 at 18:57
  • @nt.bas Regarding `gcc -c -o `: this is perfectly valid for `gcc`. Generating object files instead of linking is done with `-c` and specifying the path of the object is done with `-o `. How else would you specify your output name in gcc? – grek40 Jan 11 '17 at 18:59
  • I'll be dammed! It works. Just finished testing it. Learned 3 new things along the way. I'm marking this answer as correct. Thanks! – nt.bas Jan 11 '17 at 19:01
  • Also, you advise against `$?` if I add prerequisites. How would using `$?` come back to bite me? A link to documentation, article or a short comment will be sufficient. – nt.bas Jan 11 '17 at 19:03
  • Also, after testing, I admit i still don't understand how come the `$(OBJECTS)` rule is working exactly with one file at a time. I would expect `$(OBJECTS)` to be a list but here it appears to be lazily fetching one object file at a time. Would you elaborate on how this rule works in the answer? I perfectly understand the prerequisite part. I just don't how `$(OBJECTS)` is working one file at a time instead of considering it as a whole list. – nt.bas Jan 11 '17 at 19:12
  • @nt.bas Regarding the added prerequisites: you __need__ the c file to create your object. Now consider the c file to be old and a different prerequisite to be newer. Using `$?` will mean that the compiler command will be executed without the c file - error instead of updated object. – grek40 Jan 11 '17 at 23:04
  • 1
    @nt.bas Regarding `$(OBJECTS)`, you should think of it as a rule recipe, where a concrete rule is created for each individual target. So when the individual target rule is created, the automatic variables are expanded for this specific target - not for the whole list of targets. I don't really know how to explain, you have to learn it for yourself, [possibly read some documentation](https://www.gnu.org/software/make/manual/html_node/Pattern-Rules.html#Pattern-Rules) and accept that it took me some intense months to get this far ;) – grek40 Jan 11 '17 at 23:23
  • Actually your explanation was excellent. I now understand why your makefile works. Long road ahead, that's for sure. Thanks again for your answer. – nt.bas Jan 12 '17 at 06:47
0

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.]

Some programmer dude
  • 400,186
  • 35
  • 402
  • 621
0

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.
nt.bas
  • 736
  • 1
  • 6
  • 13