0
votes

I have a makefile which is supposed to compile a large number of source files into individual object files, then link them into a shared library.

The list of source files are stored in a variable, SOURCES. During the $(OBJECTS) target, where the object files are compiled, make runs the command $(CC) $(CFLAGS) -c $< -o $@, where $< is $(addprefix $(SRCPATH),$(SOURCES)).

This makes the command use the same source file for every object file, giving me a bunch of object files made from Time.cpp and causing the linker to give me a bunch of errors of functions that are already defined in every other object file. How can I get this makefile to work?

# Variable setup
# BUILD - Either Debug or Release, specify when running make
# ARCH - Either 32 or 64, specify when running make
# CC - The compiler
# INC - The include directories
# CFLAGS - Compiler flags to use
# LDFLAGS - Linker flags to use
# OBJDIR - Directory for .o files
# BINARY - Output file path
# SOURCES - Path to each individual source file
# OBJECTS - Object files

ifeq ($(and $(ARCH),$(BUILD)),)
    $(error You have either not defined an architecture or build or both, please run "make BUILD=(DEBUG/RELEASE) ARCH=(32/64)")
endif

CC  = g++
INC = -I../../include -I../../extlibs/headers -I../../extlibs/headers/libfreetype/linux
LDFLAGS = -lX11 -lGL -lGLEW -lfreetype -ljpeg -lopenal -lsndfile
CFLAGS  = $(INC) -std=c++0x -fPIC -pthread -m$(ARCH)
OBJDIR  = ./obj/$(BUILD)/$(ARCH)-bit
BINPATH = ./bin/$(BUILD)/$(ARCH)-bit
BINARY  = $(BINPATH)/libTyrant$(ARCH).so
SRCPATH = ../../src/
SOURCES = System/Time.cpp System/Mutex.cpp System/Log.cpp System/Clock.cpp System/Sleep.cpp System/Unix/ClockImpl.cpp System/Unix/MutexImpl.cpp System/Unix/SleepImpl.cpp System/Unix/ThreadImpl.cpp System/Unix/ThreadLocalImpl.cpp System/Lock.cpp System/String.cpp System/ThreadLocal.cpp System/Thread.cpp Audio/SoundRecorder.cpp Audio/SoundBuffer.cpp Audio/SoundSource.cpp Audio/AudioDevice.cpp Audio/ALCheck.cpp Audio/Sound.cpp Audio/Music.cpp Audio/SoundFile.cpp Audio/SoundStream.cpp Audio/SoundBufferRecorder.cpp Audio/Listener.cpp Graphics/RectangleShape.cpp Graphics/VertexArray.cpp Graphics/Shader.cpp Graphics/ConvexShape.cpp Graphics/ImageLoader.cpp Graphics/Sprite.cpp Graphics/RenderTexture.cpp Graphics/BlendMode.cpp Graphics/Shape.cpp Graphics/CircleShape.cpp Graphics/TextureSaver.cpp Graphics/Vertex.cpp Graphics/RenderTextureImpl.cpp Graphics/Texture.cpp Graphics/Text.cpp Graphics/GLExtensions.cpp Graphics/Image.cpp Graphics/RenderTextureImplFBO.cpp Graphics/GLCheck.cpp Graphics/RenderTextureImplDefault.cpp Graphics/Color.cpp Graphics/Transformable.cpp Graphics/RenderTarget.cpp Graphics/Transform.cpp Graphics/View.cpp Graphics/RenderStates.cpp Graphics/RenderWindow.cpp Graphics/Font.cpp Window/JoystickManager.cpp Window/Joystick.cpp Window/Window.cpp Window/Keyboard.cpp Window/GlResource.cpp Window/Unix/JoystickImpl.cpp Window/Unix/WindowImplX11.cpp Window/Unix/GlxContext.cpp Window/Unix/Display.cpp Window/Unix/VideoModeImpl.cpp Window/Unix/InputImpl.cpp Window/VideoMode.cpp Window/Mouse.cpp Window/GlContext.cpp Window/Context.cpp Window/WindowImpl.cpp Network/Ftp.cpp Network/TcpListener.cpp Network/Packet.cpp Network/IpAddress.cpp Network/TcpSocket.cpp Network/Socket.cpp Network/Unix/SocketImpl.cpp Network/UdpSocket.cpp Network/SocketSelector.cpp Network/Http.cpp
OBJECTS = $(addprefix $(OBJDIR)/,$(SOURCES:.cpp=.o))

ifeq ($(BUILD),DEBUG)
    CFLAGS := $(CFLAGS) -g -pg -Og
endif

ifeq ($(BUILD),RELEASE)
    CFLAGS := $(CFLAGS) -s -O3
endif

all: clean $(addprefix $(SRCPATH),$(SOURCES)) $(BINARY)

$(BINARY): $(OBJECTS) $(BINPATH)
    $(CC) $(LDFLAGS) $(OBJECTS) -shared -o $@

$(OBJECTS): $(addprefix $(SRCPATH),$(SOURCES)) $(OBJDIR)
    $(CC) $(CFLAGS) -c $< -o $@

$(OBJDIR):
    mkdir ./obj
    mkdir ./obj/$(BUILD)
    mkdir $@
    mkdir $@/Audio
    mkdir $@/Graphics
    mkdir $@/Network
    mkdir $@/Network/Unix
    mkdir $@/System
    mkdir $@/System/Unix
    mkdir $@/Window
    mkdir $@/Window/Unix

$(BINPATH):
    mkdir ./bin
    mkdir ./bin/$(BUILD)
    mkdir $@

clean:
    rm -rf bin
    rm -rf obj
2
I feel it's better practice to change your targets depending on your conditionals - that way each target has an unconditional way to build itself. Also, as Beta says, make isn't so good at doing things cross-directory - the common practice is to have all source and targets of a build step in the same directory, and have a separate install step that pulls the result together into the final destination. So these mkdir commands aren't the customary way of doing it - rather, builds for different architectures would usually be created in different subdirectories. - reinierpost

2 Answers

3
votes

You have several problems here.

Suppose all of the source files were in the working directory, and that's where the object files belonged too. Instead of trying to build all of the objects with one command, you could build each object separately, with a pattern rule to cover them all:

%.o: %.cpp
    $(CC) $(CFLAGS) -c $< -o $@

Then you could make the OBJECTS prerequisites of the library, and Make would handle it all:

$(BINARY): $(OBJECTS)
    $(CC) $(LDFLAGS) $^ -shared -o $@

(Once you had that working, you might remember that Make already had built-in rules for things like building foo.o from foo.cpp, but never mind that for now.)

But in your build scheme you combine this with other problems: 1) you have source files in several different directories, and 2) you want to build the objects elsewhere, namely 3) in a directory tree that mirrors the source tree, 4) which you build on the fly.

Addressing all of those points would make for quite an involved answer. Which of them are you already comfortable with?

0
votes

I've made it work, though it may not be optimal. My solution:

$(BINARY): $(SOURCES) $(BINPATH)
    $(CC) $(LDFLAGS) $(OBJECTS) -shared -o $@

$(SOURCES): $(OBJDIR)
    $(CC) $(CFLAGS) -c $(SRCPATH)$@ -o $(patsubst %.cpp,%.o,$(OBJDIR)/$@)

Basically, I just switched the targets from the Object files to the source files, appended the source path to the target name for the input file, and appended the object directory to the target name while also using patsubst to change the file extension from .cpp to .o. The entire makefile is pretty hacked together, I'm aware of that, but it works and that's good enough for me for my first makefile.