cmakefile

Makefile to create .o and program file in separate directories depending on architecture of host PC


Trying to create a Makefile that compiles C source into a .o file in a subfolder as well as creating the executable in a different subfolder depending on the architecture of the host PC. When I run "make" from a terminal window, make correctly compiles the first .c file into a .o and places the .o in the correct directory. However, when make tries to use the .o to create the executable, make complains that it cannot find the .o file. However, if I run make again, it finds the .o file and creates the executable in the correct folder. I am using vpath to indicate where to find the .o file and the executable. How can I fix this so make finds the .o file on the first run? Here is the Makefile:

# Makefile for helloword1 & helloworld2 programs
#

CFLAGS=-Wall

CC=gcc

ARCH=$(shell arch)

# set path to .o files
vpath %.o ./obj/$(ARCH)

# set path to executable files
vpath % ./bin/$(ARCH)

# The default target
all: helloworld1 helloworld2

# generic rule for compiling .c to .o
%.o : %.c
    $(CC) $< -c $(CFLAGS) -o ./obj/$(ARCH)/$@

# generic rule for creating executable
%: %.o
    $(CC) $< -o ./bin/$(ARCH)/$@

helloworld1: helloworld1.o
helloworld2: helloworld2.o

clean:
    -rm -f ./obj/$(ARCH)/*.o ./bin/$(ARCH)/*

Here is make console output:

les@DV5590-LM22:~/C-Programs/makefile_play$ make
gcc helloworld1.c -c -Wall -o ./obj/x86_64/helloworld1.o
gcc helloworld1.o -o ./bin/x86_64/helloworld1
/usr/bin/ld: cannot find helloworld1.o: No such file or directory
collect2: error: ld returned 1 exit status
make: *** [Makefile:25: helloworld1] Error 1

les@DV5590-LM22:~/C-Programs/makefile_play$ make
gcc ./obj/x86_64/helloworld1.o -o ./bin/x86_64/helloworld1
gcc helloworld2.c -c -Wall -o ./obj/x86_64/helloworld2.o
gcc helloworld2.o -o ./bin/x86_64/helloworld2
/usr/bin/ld: cannot find helloworld2.o: No such file or directory
collect2: error: ld returned 1 exit status
make: *** [Makefile:25: helloworld2] Error 1

les@DV5590-LM22:~/C-Programs/makefile_play$ make
gcc ./obj/x86_64/helloworld2.o -o ./bin/x86_64/helloworld2

les@DV5590-LM22:~/C-Programs/makefile_play$ 

Solution

  • You can use vpath or VPATH to indicate where source files can be found when they are searched for to build something, but not to indicate where the product files should be created. This is what vpath or VPATH are made for: locate input files, not output files.

    In your case you don't really need vpath. If your make is GNU make, you can try the following (note the added .PHONY rule, the order-only prerequisites and the rule to create the destination directories):

    CFLAGS := -Wall
    CC     := gcc
    ARCH   := $(shell arch)
    OBJDIR := obj/$(ARCH)
    SRC    := $(wildcard *.c)
    OBJ    := $(patsubst %.c,$(OBJDIR)/%.o,$(SRC))
    BINDIR := bin/$(ARCH)
    EXE    := $(addprefix $(BINDIR)/,helloworld1 helloworld2)
    
    # all and clean are not files
    .PHONY: all clean
    
    # The default target
    all: $(EXE)
    
    # generic rule for compiling .c to .o
    $(OBJDIR)/%.o : %.c | $(OBJDIR)
        $(CC) $< -c $(CFLAGS) -o $@
    
    # executable - object files dependencies
    $(BINDIR)/helloworld1: $(OBJDIR)/helloworld1.o
    $(BINDIR)/helloworld2: $(OBJDIR)/helloworld2.o
    
    # generic rule for creating executable
    $(EXE): | $(BINDIR)
        $(CC) $^ -o $@
    
    # create destination directories
    $(OBJDIR) $(BINDIR):
        mkdir -p $@
    
    clean:
        -rm -f $(OBJ) $(EXE)
    

    If your executables depend on more object files you can add them to the dependency rules, e.g., if helloworld1 also depends on foo.o:

    $(BINDIR)/helloworld1: $(OBJDIR)/helloworld1.o $(OBJDIR)/foo.o
    

    Or:

    $(BINDIR)/helloworld1: $(addprefix $(OBJDIR)/,helloworld1.o foo.o)
    

    If your executables depend only on an object file with the same name you could simplify a bit with a static pattern rule:

    CFLAGS := -Wall
    CC     := gcc
    ARCH   := $(shell arch)
    OBJDIR := obj/$(ARCH)
    SRC    := $(wildcard *.c)
    OBJ    := $(patsubst %.c,$(OBJDIR)/%.o,$(SRC))
    BINDIR := bin/$(ARCH)
    EXE    := $(addprefix $(BINDIR)/,helloworld1 helloworld2)
    
    .PHONY: all clean
    
    all: $(EXE)
    
    $(OBJDIR)/%.o : %.c | $(OBJDIR)
        $(CC) $< -c $(CFLAGS) -o $@
    
    $(EXE): $(BINDIR)/%: $(OBJDIR)/%.o | $(BINDIR)
        $(CC) $^ -o $@
    
    $(OBJDIR) $(BINDIR):
        mkdir -p $@
    
    clean:
        -rm -f $(OBJ) $(EXE)