Chrysophylaxs
Chrysophylaxs

Reputation: 6583

Makefile to build client server application with shared sources

I am having trouble creating a good Makefile as a beginner user of make. I am creating a client/server application in c, which have some shared functions. In order to structure the files, I was thinking it would look something like this:

obj/
    <all .o files here>
src/
    client/
        <client related .c files here>
    server/
        <server related .c files here>
    shared/
        <.c files used by both here>
include/
    <all .h files here>

Makefile
client
server

Where client and server are the binaries that can be run. Unfortunately, when I try it on a client.c file in src/client/

make: *** No rule to make target 'obj/client.o', needed by 'client'.  Stop.

I suppose I need a good rule that compiles each of the separate .c files to object files, but I don't know how to proceed :(

Any help is greatly appreciated!

The Makefile I have right now is:

CLIENT_TARGET := client
SERVER_TARGET := server

CLIENT_SOURCES := $(wildcard src/client/*.c)
SERVER_SOURCES := $(wildcard src/server/*.c)
SHARED_SOURCES := $(wildcard src/shared/*.c)

CLIENT_OBJECTS := $(patsubst src/client%,obj%, $(patsubst %.c,%.o, $(CLIENT_SOURCES)))
SERVER_OBJECTS := $(patsubst src/server%,obj%, $(patsubst %.c,%.o, $(SERVER_SOURCES)))
SHARED_OBJECTS := $(patsubst src/shared%,obj%, $(patsubst %.c,%.o, $(SHARED_SOURCES)))

INCLUDE := -I./include
LIBPATH :=
LIBS := 

FLAGS := -Wall -Werror
CCFLAGS := $(FLAGS) -std=c99

CC := gcc

all: client server

client: $(CLIENT_OBJECTS)
    $(CC) $(CCFLAGS) $(INCLUDE) $(CLIENT_OBJECTS) $(SHARED_OBJECTS) -o $(CLIENT_TARGET) $(LIBPATH) $(LIBS)

server: $(SERVER_OBJECTS)
    $(CC) $(CCFLAGS) $(INCLUDE) $(SERVER_OBJECTS) $(SHARED_OBJECTS) -o $(SERVER_TARGET) $(LIBPATH) $(LIBS)

%.o: ../src/%.c
    $(CC) $(CCFLAGS) $(INCLUDE) -c $< -o $@

clean:
    rm -rf obj/*
    rm -f $(CLIENT_TARGET) $(SERVER_TARGET)

Upvotes: 1

Views: 308

Answers (2)

MadScientist
MadScientist

Reputation: 100836

I don't really think it's necessary to completely rewrite your makefile. In fact you are almost there.

I will make one observation: it would actually be simpler to preserve the source directory structure inside the obj directory rather than trying to put all the object files from all the source directies in a single obj directory. Not only is it easier in the makefile but it's safer as well in case you happen to have the same foo.c file in multiple source directories.

However, to do it all in one directory you have to write three pattern rules: one for each source directory. First, it's much simpler to use this than multiple patsubst invocations:

CLIENT_OBJECTS := $(patsubst src/client/%.c,obj/%.o, $(CLIENT_SOURCES))
SERVER_OBJECTS := $(patsubst src/server/%.c,obj/%.o, $(SERVER_SOURCES))
SHARED_OBJECTS := $(patsubst src/shared/%.c,obj/%.o, $(SHARED_SOURCES))

When you see a rule %.x : %.y, the values of % must match exactly otherwise the rule doesn't match. You have a rule %.o: ../src/%.c. You seem to be assuming that first % will be replaced with some value, then the prerequisite will be searched for as a relative path from the target location, but that's not how it works.

Pattern rules are a pure textual substitution: they don't even have to represent actual files, they could be URLs or chapter titles or some other random thing. Make will just match the target pattern, then take the part that matched the % and plug it into the prerequisite and see if that target can be built.

If you want to put all sources from different source directories into the same output directory you need a separate rule for each source directory so that the translation will work:

obj/%.o: src/client/%.c
        $(CC) $(CCFLAGS) $(INCLUDE) -c $< -o $@
obj/%.o: src/server/%.c
        $(CC) $(CCFLAGS) $(INCLUDE) -c $< -o $@
obj/%.o: src/shared/%.c
        $(CC) $(CCFLAGS) $(INCLUDE) -c $< -o $@

If you want to preserve the directory structure, so that src/client/foo.c is compiled into obj/client/foo.c, then you can do it entirely in one rule:

OBJECTS := $(patsubst src/%.c,obj/%.o,$(CLIENT_SOURCES) $(SERVER_SOURCES) $(SHARED_SOURCES))

obj/%.o : src/%.c
        @mkdir -p $(@D)
        $(CC) $(CCFLAGS) $(INCLUDE) -c $< -o $@

Upvotes: 1

John Bollinger
John Bollinger

Reputation: 180201

Since you are a beginner with make I suggest starting with conventional layout. Specifically, it is normal (and easier) to build object files into the same directory as their corresponding source files. In cases such as this, with multiple source directories, that also protects you against the possibility of name collisions among your object files.

It would be slightly easier still to put all your sources in the same directory (not necessarily the project root). The main issue with that is determining which objects contribute to which executables, but the best practice there is to list those explicitly in your makefile, as opposed to relying on $(wildard) matching (which is specific to GNU's make implementation, by the way, and not portable to others).

I would even be inclined to put the headers in the source directory, too.

Remember always that a project's source layout bears no particular relationship to its installed layout. Or it shouldn't, anyway. Other than for testing purposes, you should not be planning for your executables to run from the build directory. For general use, they should be installed to the system, or at least to a separate location within your own space.

All that would give you a project layout more like this:

src/
    <all C source files here>
    <all C header files here>
Makefile
client
server

A Makefile for that might look like so:

CLIENT_TARGET = client
SERVER_TARGET = server

CLIENT_SOURCES = src/client_main.c src/client_extra.c
SERVER_SOURCES = src/server_main.c src/server_other.c
SHARED_SOURCES = src/common.c

CLIENT_OBJECTS = $(CLIENT_SOURCES:.c=.o)
SERVER_OBJECTS = $(SERVER_SOURCES:.c=.o)
SHARED_OBJECTS = $(SHARED_SOURCES:.c=.o)

WARNING_FLAGS = -Wall -Werror

# "CFLAGS" is a conventional name, and it is used automatically by many 'make'
# implementations' built-in rules
CFLAGS = $(WARNING_FLAGS) -std=c99

# "CPPFLAGS" is a conventional name in at least the GNU build system, and it is
# used automatically by GNU 'make's built-in rules.
CPPFLAGS = -Isrc

# LDFLAGS is another conventional name, used by many 'make' implementations' built-in rules
LDFLAGS =

# LIBS is common, but it is not used in GNU 'make's built-in rules
LIBS =

# You should not have to define variable 'CC' explicitly to get the system's default
# C compiler.  If you specifically require gcc, but you're not confident that it will
# be the default compiler on all systems you want to support, then you may need a more
# complex build system.
#
# CC = gcc

all: $(CLIENT_TARGET) $(SERVER_TARGET)

$(CLIENT_TARGET): $(CLIENT_OBJECTS) $(SHARED_OBJECTS)
    $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $^ $(LIBS)

$(SERVER_TARGET): $(SERVER_OBJECTS) $(SHARED_OBJECTS)
    $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $^ $(LIBS)

# The built-in rule for building .o files from corresponding .c files should do
# the right thing here.

# clean exactly and only the targets we may have built
clean:
    rm -f $(CLIENT_OBJECTS) $(SERVER_OBJECTS) $(SHARED_OBJECTS) $(CLIENT_TARGET) $(SERVER_TARGET)

Except inasmuch as it relies on details of 'make's built-in rule for compiling C source files to object files, that's portable to most POSIX-conforming 'make' implementations, not just GNU's.

Upvotes: 0

Related Questions