Skip to content

Instantly share code, notes, and snippets.

@Redchards
Created September 1, 2015 02:05
Show Gist options
  • Save Redchards/2699407d7914fbe2b6a8 to your computer and use it in GitHub Desktop.
Save Redchards/2699407d7914fbe2b6a8 to your computer and use it in GitHub Desktop.
Simple makefile aimed to rapid prototyping and testing of C or/and C++ code
# TODO: Add options for library generation.
# Even though this makefile is only intended as testing purpose, I'm not entirely happy with it.
# Indeed, I got multiple evals, and a more or less serious problem :
# the makefile has no "memory", so it does not remember the previous build config.
# The problem is, if we build in a debug mod, and then in a release mod, nothing will be rebuilt.
# To build with another mod, we need to clean the whole obj files, and then build with the desired config.
# One possible fix would be, instead of using target dependent variables for "debug", "release" and "analysis"
# rules, using the $@ automatic variable (name of the target) along with secondary expansion (.SECONDARYEXPANSION:)
# to build the dependency list. This way, to object files would be separated. We would have obj/debug for debug objs,
# and obj/release for release objs.
# This makefile is aimed toward rapid prototyping, and small project building.
# It supports C and C++, and variety of other options.
# The shell that the make will use to execute shell commands.
# It can be set to any decent shell without any problem.
SHELL:=/bin/bash
# We only define the C++ compiler, and it will take care of compiling both C and C++ files.
# We also set the linker (LD) to the same value, as most of the compilers do it through the same command.
CXX= clang++
LD= $(CXX)
# The final executable, or library, name.
EXEC:= Arena
# Current configuration. Debug by default.
CONFIG:=debug
# Various directory path.
# As nothing is hardcoded, the values can be set to the liking of the user, without breaking something.
LIBDIR:= lib
OBJDIR:= obj
SRCDIR:= src
INCLDIR:= include
BINDIR:= bin
SCANDIR:= scan
# Basic C and C++ flags
CFLAGS= -W -Wall -Wextra
CXXFLAGS= $(CFLAGS) -std=c++1y
CFLAGS+= -std=c11
# Flags used only for debug mod
DEBUGFLAGS:= -g -O0
# Flags used only for release mod
RELEASEFLAGS:= -O3
# Flags used only for analyzis mod
ANALYZERFLAGS:= --analyze -Xanalyzer -analyzer-output=html -o $(SCANDIR)
# Flags used only for bitcode compilation (by LLVM/clang)
JITFLAGS:= -emit-llvm -S -fno-use-cxa-atexit
# Character used to make the makefile silent.
SILENT:=@
# Flags used to create dependencies
MAKEDEPEND:= -MMD
# Flags used by the linker
LDFLAGS:=
# This variable will determine whether we will use native or jit execution.
# The values are either "native" or "jit".
# The value "jit" is only valid for the clang compiler.
EXECUTION:=native
# Turns on/off the verbose output of the makefile.
# The values are either "yes" or "no"
VERBOSE:=no
# Extensions of the different types of file
OBJEXT:=o
DEPEXT:=d
CEXT:=c
CXXEXT:=cxx
ifeq ($(EXECUTION), jit)
ifeq ($(CONFIG), analysis)
$(warning Warning : analysis mod is independant of execution mod. No bitcode will be generated)
endif
ifeq (, $(filter $(CXX), clang clang++))
$(error Error : attempt to emit llvm IR, but not using clang)
endif
CXXFLAGS+= $(JITFLAGS)
CFLAGS+= $(JITFLAGS)
LD= llvm-link
EXEC:= $(addprefix $(EXEC).,bc)
OBJEXT= bco
endif
ifeq ($(CONFIG), analysis)
ifeq (, $(filter $(CXX), clang clang++))
$(error Error : attempt to enable analysis, but not using clang)
endif
CXXFLAGS+= $(ANALYZERFLAGS)
endif
ifeq ($(VERBOSE), yes)
SILENT=
endif
SRC:=$(shell find $(SRCDIR) -name '*.$(CEXT)' -o -name '*.$(CXXEXT)')
OBJ=$(subst src/,,$(SRC:.$(CEXT)=.$(OBJEXT)))
OBJ:=$(subst src/,,$(OBJ:.$(CXXEXT)=.$(OBJEXT)))
ALLOBJ:=$(addprefix $(OBJDIR)/,$(OBJ))
DEPS:=$(ALLOBJ:.$(OBJEXT)=.$(DEPEXT))
# Define the path where the result will be outputted
OUTPATH=$(if $(filter $(CONFIG), analysis),$(SCANDIR),$(BINDIR)/$(CONFIG))
all: pre-build $(EXEC)
# .PHONY targets.
.PHONY: clean cleanall debug release analyze
# Target specific variable value.
# These targets are used to do less typing while invoking the make with a special config.
debug: CONFIG=debug
debug: all
release: CONFIG=release
release: all
analysis: CONFIG=analysis
analysis: all
# The first dependency of the "all" target.
# It takes care of selecting the right flags depending on the config.
# And yhea, I know eval is evil, but this will be executed only once in the makefile, sooo ... no big deal :)
pre-build: build-info
$(eval FLAGS:=$(shell \
export CONFIG="$(CONFIG)";\
export RELEASEFLAGS="$(RELEASEFLAGS)";\
export DEBUGFLAGS="$(DEBUGFLAGS)";\
export ANALYZERFLAGS="$(ANALYZERFLAGS)";\
\
if [ $$CONFIG == "debug" ]; then\
echo $$DEBUGFLAGS;\
elif [ $$CONFIG == "release" ]; then\
echo $$RELEASEFLAGS;\
elif [ $$CONFIG == "analysis" ]; then\
echo $$ANALYZERFLAGS;\
fi))
$(if $(FLAGS),,$(error Error : configuration "$(CONFIG)" is invalid !))
OK=
# This target is the main target.
# It will link all file generated by its dependencies together
$(EXEC): $(ALLOBJ)
ifeq ($(EXECUTION), jit)
$(SILENT) $(LD) $(EXEC) -o $(BINDIR)/$(CONFIG)/$(EXEC)
else
@ $(if $(filter $(CONFIG), analysis),\
echo "No executable code output for analysis",\
mkdir -p $(BINDIR)/$(CONFIG);\
$(LD) -o $(BINDIR)/$(CONFIG)/$(EXEC) $^ $(LDFLAGS))
endif
@ $(if $(OK)$(filter $(CONFIG), analysis),\
echo -e "\e[1m\e[32mGeneration successful !\e[0m",\
echo -e "\e[1m\e[32mNothing to do, everything up to date !\e[0m")
ifeq ($(EXECUTION), native)
$(SILENT) $(if $(filter $(CONFIG), release),\
echo "Stripping binary ...";\
strip $(EXEC);\
echo "Done !",)
else
@ $(if $(OK),\
echo -e "\e[1m\e[92mBitcode generated\e[0m",)
endif
@ $(if $(OK)$(filter $(CONFIG), analysis),\
echo -e "Resulting file : \e[1m\e[92m$(EXEC)\e[0m";\
echo -e "See the result in the following directory : \e[1m\e[96m$(OUTPATH)\e[0m",)
# The include directive is usually used to include other makefiles.
# In this case, it is used to include dependencies, which are really just text files, which list dependencies
# of an object file, telling if we should recompile it, or still use the old one.
# If you open a dependency file in your favorite text editor, you will see that it's made of a rule and some
# dependencies. The rule is a *.o, and thus, this rule will execute only and only if one or more of its dependency
# is newer than the old *.o. If yes, one of the below rule will execute, depending on if the file depends on a c
# or a c++ file.
# In fact, it really works like includes in C or C++, it's juste merely a copy/paste of the passed file(s).
# Side note : the "-" tells make not to generate error if one or more dependencie is missing. It can be the case
# if dependency generation was disabled, and then enabled witout cleaning (which is just ok).
-include $(DEPS)
# I know, other evals ...
# Rule to generate object file for C files
$(OBJDIR)/%.$(OBJEXT) : $(SRCDIR)/%.$(CEXT)
@echo =
$(eval OK=1)
$(SILENT) mkdir -p $(@D)
$(SILENT) $(CXX) -I$(INCLDIR) -x c -o $@ -c $< $(CFLAGS) $(FLAGS) $(MAKEDEPEND)
# Rule to generate object file for C++ files
$(OBJDIR)/%.$(OBJEXT) : $(SRCDIR)/%.$(CXXEXT)
$(eval OK=1)
$(SILENT) mkdir -p $(@D)
$(SILENT) $(CXX) -I$(INCLDIR) -x c++ -o $@ -c $< $(CXXFLAGS) $(FLAGS) $(MAKEDEPEND)
clean:
$(SILENT) rm -rf $(OBJDIR)/*
cleanall: clean
$(SILENT) rm -rf $(EXEC)
# The summary of the upcoming compilation configuration printed at the begining.
# It could be extended, but it is sufficient like this.
HEADER_MSG:="COMPILATION OF PROJECT : $(EXEC)"
build-info:
@echo
$(shell printf "#%.0s" $$(seq 1 $$(tput cols)))
@echo -e "\e[1m\e[91m$(shell export HEADER_MSG=$(HEADER_MSG); printf " %.0s" $$(seq 1 $$(($$(tput cols)/2 - $${#HEADER_MSG}/2)))) $(HEADER_MSG)\e[0m"
@echo
@echo -e "Generation configuration is \e[1m\e[32m$(CONFIG)\e[0m"
@echo
@echo -e "Sources directory search path is : \e[1m\e[96m$(SRCDIR)\e[0m (SRCDIR)"
@echo -e "Includes directory search path is : \e[1m\e[96m$(INCLDIR)\e[0m (INCLDIR)"
@echo -e "Libraries directory search path is : \e[1m\e[96m$(LIBDIR)\e[0m (LIBDIR)"
@echo -e "Objects directory is : \e[1m\e[96m$(OBJDIR)\e[0m (OBJDIR)"
@echo -e "The binary output path is : \e[1m\e[96m$(OUTPATH)\e[0m"
@echo
$(shell printf "#%.0s" $$(seq 1 $$(tput cols)))
@echo
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment