Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Best practice for building a make file

Tags:

makefile

Background:

I am relatively new to Make and I started building a makefile for my Othello game I recently built. My current makefile is relatively simple. Yet I am looking to refine it further and add a build and archive feature.

Question 1: Why doesn't clean work?

Question 2: For Archiving I plan on using

# tar cvzf *.o Othello

to archive, the .o and executable generated when compiled. Is this a good standard practice or is there a better way to do this?

Question 3: For the build portion, I plan on running the executable using ./Othello is this best practice for build in a makefile?

The git repository is posted below thank you for your time

all: Othello

Othello: main.o Myothello.o space.o game.o
        clang++ main.o Myothello.o space.o game.o -o Othello

main.o: main.cc
        clang++ -c main.cc

game.o: game.cc
        clang++ -c game.cc

Myothello.o: Myothello.cc
            clang++ -c Myothello.cc

space.o: space.cc
        clang++ -c space.cc

clean:
rm -f *.o core *.core
rm MyOthello
like image 636
David Wisniewski Avatar asked Feb 14 '18 16:02

David Wisniewski


People also ask

Is makefile obsolete?

Makefiles are not obsolete, in the same way that text files are not obsolete.

What is the command to build a make file?

The make utility requires a file, Makefile (or makefile ), which defines set of tasks to be executed. You may have used make to compile a program from source code. Most open source projects use make to compile a final executable binary, which can then be installed using make install .

What is the purpose of a make file?

Makefile sets a set of rules to determine which parts of a program need to be recompile, and issues command to recompile them. Makefile is a way of automating software building procedure and other complex tasks with dependencies. Makefile contains: dependency rules, macros and suffix(or implicit) rules.


1 Answers

Here is an example makefile for you, tested on your github project.

Features:

  1. Automatic dependency generation.
  2. Automatic rebuild when Makefile is changed.
  3. Debug/release builds in different directories. Debug build is the default, use make BUILD=release for release builds.
  4. Supports gcc and clang. gcc is the default, use make COMPILER=clang for clang.
  5. clean target works by removing the entire build directory.
  6. run target to build and run, e.g. make run_othello.

Limitations:

  1. Assumes that all source files are in the same directory. In a bigger project there are going to be multiple directories each with multiple build targets (executable, static or shared library). An isomorphic build directory structure is required to support this. Which is totally doable but requires more complexities in example Makefile below, so that it omitted for brevity.

When archiving you may like to archive only the source files and the makefile. No need to include any build artefacts (like object files, libraries and executables).


# ==== Begin prologue boilerplate.
all : # The canonical default target.
BUILD := debug
build_dir := ${CURDIR}/${BUILD}
exes := # Executables to build.
# ==== End prologue boilerplate.

# ==== Begin define executable othello.
exes += othello
objects.othello = main.o game.o Myothello.o space.o
-include ${objects.othello:%.o=${build_dir}/%.d} # Include auto-generated dependencies.
# ==== End define executable othello.

# ==== Begin define another executable.
# ... as for othello
# ==== End define another executable.

# ==== Begin rest of boilerplate.
SHELL := /bin/bash
COMPILER=gcc

CXX.gcc := /bin/g++
CC.gcc := /bin/gcc
LD.gcc := /bin/g++
AR.gcc := /bin/ar

CXX.clang := /bin/clang++
CC.clang := /bin/clang
LD.clang := /bin/clang++
AR.clang := /bin/ar

CXX := ${CXX.${COMPILER}}
CC := ${CC.${COMPILER}}
LD := ${LD.${COMPILER}}
AR := ${AR.${COMPILER}}

CXXFLAGS.gcc.debug := -Og -fstack-protector-all
CXXFLAGS.gcc.release := -O3 -march=native -DNDEBUG
CXXFLAGS.gcc := -pthread -std=gnu++14 -march=native -W{all,extra,error} -g -fmessage-length=0 ${CXXFLAGS.gcc.${BUILD}}

CXXFLAGS.clang.debug := -O0 -fstack-protector-all
CXXFLAGS.clang.release := -O3 -march=native -DNDEBUG
CXXFLAGS.clang := -pthread -std=gnu++14 -march=native -W{all,extra,error} -g -fmessage-length=0 ${CXXFLAGS.clang.${BUILD}}

CXXFLAGS := ${CXXFLAGS.${COMPILER}}
CFLAGS := ${CFLAGS.${COMPILER}}

LDFLAGS.debug :=
LDFLAGS.release :=
LDFLAGS := -fuse-ld=gold -pthread -g ${LDFLAGS.${BUILD}}
LDLIBS := -ldl

COMPILE.CXX = ${CXX} -c -o $@ ${CPPFLAGS} -MD -MP ${CXXFLAGS} $(abspath $<)
PREPROCESS.CXX = ${CXX} -E -o $@ ${CPPFLAGS} ${CXXFLAGS} $(abspath $<)
COMPILE.C = ${CC} -c -o $@ ${CPPFLAGS} -MD -MP ${CFLAGS} $(abspath $<)
LINK.EXE = ${LD} -o $@ $(LDFLAGS) $(filter-out Makefile,$^) $(LDLIBS)
LINK.SO = ${LD} -shared -o $@ $(LDFLAGS) $(filter-out Makefile,$^) $(LDLIBS)
LINK.A = ${AR} rsc $@ $(filter-out Makefile,$^)

all : ${exes:%=${build_dir}/%} # Build all exectuables.

.SECONDEXPANSION:
# Build an executable.
${exes:%=${build_dir}/%} : ${build_dir}/% : $$(addprefix ${build_dir}/,$${objects.$$*}) Makefile | ${build_dir}
    $(strip ${LINK.EXE})

# Run an executable. E.g. make run_othello
${exes:%=run_%} : run_% : ${build_dir}/%
    @echo "---- running $< ----"
    /usr/bin/time --verbose $<

# Create the build directory on demand.
${build_dir} :
    mkdir $@

# Compile a C++ source into .o.
# Most importantly, generate header dependencies.
${build_dir}/%.o : %.cc Makefile | ${build_dir}
    $(strip ${COMPILE.CXX})

# Compile a C source into .o.
# Most importantly, generate header dependencies.
${build_dir}/%.o : %.c Makefile | ${build_dir}
    $(strip ${COMPILE.C})

clean :
    rm -rf ${build_dir}

.PHONY : clean all run_%

# ==== End rest of boilerplate.
like image 180
Maxim Egorushkin Avatar answered Nov 23 '22 06:11

Maxim Egorushkin