Created
September 1, 2015 02:05
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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