1

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$ 
5
  • copy pasted your question to chat jepardy. make can't determine file path during dependancy creation. btw, wellcome to SO. Commented Oct 4 at 22:34
  • vpath never makes life easier, and even if you insist on using it, it should be used solely for sources, not objects (see make.mad-scientist.net/papers/rules-of-makefiles for this and additional rules). Just write the dirs in the rule directly, and possibly some $(patsubst). Also, you probably want $(CC) -dumpmachine rather than just arch. Commented Oct 5 at 2:20
  • @o11c patsubst is not portable even across Linuxes. makefile has variables, why not use them? like ARCH = shell(cmd); PATH = my_path; $CC -c $ARCH/$PATH. or something. Commented Oct 5 at 4:01
  • 1
    @o11c: vpath makes life easier when you need it. Claiming that it is useless is just wrong. Commented Oct 5 at 7:24
  • 1
    @jorgeisnotai: portability is a different issue. If the OP uses GNU make they can use GNU make features, of course. And ARCH = shell(cmd) is syntactically incorrect, plus the shell function is a GNU make feature, not POSIX. Commented Oct 5 at 7:27

1 Answer 1

3

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)
Sign up to request clarification or add additional context in comments.

1 Comment

This seems to work and does exactly what I wanted. Thanks for the detailed response!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.