4
\$\begingroup\$

I have a makefile that I've been using to compile my shared libraries which reads in the files from different folders and then builds the objects and binaries in their own folders.

The folder structure I'm using roughly:

-Folder
 |
 +--API Header Files Dir
 |
 +--Library (.so) Project One
 |  +-- MakeFile Project One
 |  +-- src (*.cpp files)
 |  +-- inc (*.h files)
 |  +-- obj (compiled object files)
 |  +-- bin (compiled libraries)
 |
 +--Library (.so) Project Two
 |  +-- MakeFile Project Two
 |  +-- src (*.cpp files)
 |  +-- inc (*.h files)
 |  +-- obj (compiled object files)
 |  +-- bin (compiled libraries)
 |

I've tried to put together a general makefile that can accomplish this and this post was quite nice and I've edited my original makefile to the best of my understanding to try to make this work.

Here's my makefile:

######################################################################################
# Date Created          :   25-06-2015
# Date Modified         :   03-08-2015
#
# Note                  : 1. make         : to build unoptimized standard build
#                         2. make debug   : to build  with debug symbols
#                         3. make release : to build  with symbols stripped and optimized
#
# Usage                 : change the "NAME" to your program name or else use as it is
######################################################################################
ifndef _ARCH
_ARCH := $(shell uname -m)
export _ARCH
endif

NAME := Test
MAJOR := 1
MINOR := 5
VERSION := v$(MAJOR)_$(MINOR)
TARGET := $(NAME)_$(VERSION).o

SRCEXT   := cpp
SRCDIR   := src
INCPATH  := inc
OBJDIR   := obj
BINDIR   := bin
BIN_DEBUGDIR   := $(BINDIR)/debug
API_DIR := /home/API/Multibody


INCLUDES := -I. \
            -I$(INCPATH) \
            -I$(API_DIR)

# C++ compiler flag
CXXFLAGS := -Wall -march=native $(INCLUDES) -c

# Linker parameter flags

# Linker library flags
LIBDIRS  := -L$(API_DIR)/lib
# LDFLAGS   := -shared
LDFLAGS  := -Wl,-rpath -Wl,$(API_DIR)/lib $(LIBDIRS)
LDLIBS   := -lCouplingClientV2_2_0

SRCS    := $(shell find $(SRCDIR) -name '*.$(SRCEXT)')
SRCDIRS := $(shell find . -name '*.$(SRCEXT)' -exec dirname {} \; | uniq)
OBJS    := $(patsubst %.$(SRCEXT),$(OBJDIR)/%.o,$(SRCS))


###########################################################################
# Rules to compile our files  - Do not change below this line!
###########################################################################

ifeq ($(SRCEXT), cpp)
CC       = $(CXX)
else
CXXFLAGS  += -std=gnu99
endif

.PHONY: all debug release clean distclean 


all: $(BINDIR)/$(TARGET)

# Build debug library
debug: CXXFLAGS += -g
debug: clean $(BIN_DEBUGDIR)/$(TARGET)


# strip all symbols from release verison
release: LDFLAGS += -s 
release: CXXFLAGS += -O2
release: $(BINDIR)/$(TARGET)

$(BINDIR)/$(TARGET): buildrepo $(OBJS)
    @mkdir -p `dirname $@`
#   @echo "Linking $@..."
    $(CC) $(LDFLAGS) -o $@ $(OBJS) $(LDLIBS)
#   @echo "$@ sucessfully built."

$(BIN_DEBUGDIR)/$(TARGET): buildrepo $(OBJS)
    @mkdir -p `dirname $@`
#   @echo "Linking $@..."
    $(CC) $(LDFLAGS) -o $@ $(OBJS) $(LDLIBS)
#   @echo "$@ sucessfully built."

$(OBJDIR)/%.o: %.$(SRCEXT)
#   @echo "Generating dependencies for $<..."
    $(call make-depend,$<,$@,$(subst .o,.d,$@))
#   @echo "Compiling $<..."
    $(CC) $(CXXFLAGS) $< -o $@

clean:
    $(RM) -r $(OBJDIR)

distclean: clean
    $(RM) -r $(BINDIR)/$(TARGET)
    $(RM) -r $(BIN_DEBUGDIR)/$(TARGET)

buildrepo:
    $(call make-repo)

define make-repo
   for dir in $(SRCDIRS); \
   do \
    mkdir -p $(OBJDIR)/$$dir; \
   done
endef


# usage: $(call make-depend,source-file,object-file,depend-file)
define make-depend
  $(CC) -MM       \
        -MMD      \
        -MF $3    \
        -MP       \
        -MT $2    \
        $(CXXFLAGS) \
        $(LDFLAGS) \
        $1
endef

The file is mainly used for compiling shared libraries, but also has to be able to easily switch to creating an executable linking to some libraries like it's currently set up.

What are normal best practices? Is there a neater way to write the directory building rules for the binaries and object files? Should I be including native architecture such as optimization for Intel or AMD chips in names?

\$\endgroup\$
1
\$\begingroup\$

One Up'ed me

Well you have gone one step further than I have.
I have been planning adding the "Make-Depend" step but have not got around to it.

Layout

One thing you don't do is build your executable/objects in a different directory that the source. This is generally best because debug/release binary are not guaranteed to be compatible and thus you should compile them into their own sub directories to make sure they do not get mixed up.

I have found separating my header files from the source a pain. So I keep these with the source and automatically install them as part of installation. Any header files I don't want installed go into sub directory called details.

So my structure looks like this:

-Folder
 |
 +--build
 |  +--tools
 |  |  +--ACoupleOfGenericMakefiles
 |  +--autotools
 |     +--SomeRulesIhaveWritten
 |
 +--Library (.so) Project One
 |  +-- MakeFile Project One
 |  +-- src (*.cpp files)
 |  +-- inc (*.h files)
 |  +-- details
 |  |   +-- inc (*.h that should not be installed)
 |  +-- test
 |  |   +-- unittest:src (*.cpp)
 |  |   +-- unittest:header (*.h)
 |  +-- release
 |  |   +-- obj (compiled object files)
 |  |   +-- lib ((compiled libraries:  *.o *.so  (so => platform specific) )
 |  +-- debug
 |  |   +-- obj (compiled object files)
 |  |   +-- lib (compiled libraries)
 |  +-- coverage                          // compiled debug with code coverage turned on. 
 |      +-- obj (compiled object files)
 |      +-- lib (compiled libraries)
 |      +-- unittest.app
 |
 +--Library (.so) Project Two
 |  +-- MakeFile Project Two
 |  +-- src (*.cpp files)
 |  +-- inc (*.h files)
 |  +-- details
 |  |   +-- inc (*.h that should not be installed)
 |  +-- test
 |  |   +-- unittest:src (*.cpp)
 |  |   +-- unittest:header (*.h)
 |  +-- release
 |  |   +-- obj (compiled object files)
 |  |   +-- lib (compiled libraries)
 |  +-- debug
 |  |   +-- obj (compiled object files)
 |  |   +-- lib (compiled libraries)
 |  +-- coverage                                // compiled debug with code
 |      +-- obj (compiled object files)
 |      +-- lib (compiled libraries)
 |      +-- unittest.app
 |

Makefile

TARGET := $(NAME)_$(VERSION).o

Personally I add a debug suffix (and a C++ version suffix). So that debug libraries have "D" (note not all platforms generate compatible binaries for debug/release so you don't want to mix them up). Even if they were compatible you don't want to accidentally link against a debug library when you are doing performance analysis so if you are explicit you will not accidentally get them mixed up.

At the end my libraries I also have a "03" "11" "14" or "17" suffix to show which version of the language they were built against. Maybe a bit paranoid. But I have been bitten by this once so I automated it so it would not happen again.

 CXXFLAGS := -Wall -march=native $(INCLUDES) -c

Like the -march=native means you are really trying to optimize for performance. But you don't really want to include that in debug mode.

May I also suggest:

-Werror -Wall -Wextra -Wstrict-aliasing -pedantic -Wunreachable-code

You should defiantly be using -Werror. Your code should compile with zero warnings. So make sure your compiler does not let you generate objects until the warnings are gone.

LDFLAGS  := -Wl,-rpath -Wl,$(API_DIR)/lib $(LIBDIRS)

Most people forget about -rpath though I use RPATH directly (I can remember why so now I have to go and try and work it out again and it a comment to my generic makefile so I understand the decision).

Don't need to shell out to find files:

SRCS    := $(shell find $(SRCDIR) -name '*.$(SRCEXT)')

I suppose this finds all the source.
My Makefile only builds the current directory. So I use the simpler form.

SRCS    := $(wildcard *.$(SRCEXT))

Are you sure this works?

$(OBJDIR)/%.o: %.$(SRCEXT)
#   @echo "Generating dependencies for $<..."
    $(call make-depend,$<,$@,$(subst .o,.d,$@))
#   @echo "Compiling $<..."
    $(CC) $(CXXFLAGS) $< -o $@

By the point you execute: make-depend it has already decided what needs to be re-built (if you change the dependencies from last time this will not be picked up until your next run now).

Looks relatively good. Here is a link to my generic makefile:

https://github.com/Loki-Astari/ThorMaker/blob/master/tools/Makefile

One thing I hate about C++ compilation is seeing all the command line arguments. So my Makefile hides all that and you just see the basics info:

> make
Building debug
g++ -c ConectReader.cpp -g                                           OK
g++ -c ConectWriter.cpp -g                                           OK
g++ -c Connection.cpp -g                                             OK
g++ -c PackageBuffer.cpp -g                                          OK
g++ -c PackageStream.cpp -g                                          OK
g++ -c PrepareStatement.cpp -g                                       OK
g++ -c RequPackageHandShakeResp.cpp -g                               OK
g++ -c RespPackageHandShake.cpp -g                                   OK
g++ -c RespPackageResultSet.cpp -g                                   OK
g++ -c Statement.cpp -g                                              OK
g++ -dynamiclib -install_name libThorMySQL14D.dylib -o debug/libThorMySQL.dylib -g OK
 Done Building MySQL debug/libThorMySQL.dylib

If there is an error then it prints out all the arguments:

> make
Building debug
ERROR
g++ -c ConectReader.cpp -o debug/ConectReader.o -fPIC -Wall -Wextra -Wstrict-aliasing -pedantic -Werror -Wunreachable-code -Wno-long-long -I/Users/martin/Repository/ThorsSQL/build/include -isystem /Users/martin/Repository/ThorsSQL/build/include3rd -DBOOST_FILESYSTEM_VERSION=3 -g -Wno-unreachable-code -DTHOR_USE_CPLUSPLUS14 -std=c++14
========================================
ConectReader.cpp:12:40: error: unknown type name 'XX'
    capabilities    = newCapabilities; XX
                                       ^
1 error generated.
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.