Skip to content

Instantly share code, notes, and snippets.

@Redchards
Created September 2, 2015 00:46
Show Gist options
  • Save Redchards/271f61580c0883524545 to your computer and use it in GitHub Desktop.
Save Redchards/271f61580c0883524545 to your computer and use it in GitHub Desktop.
What an ugly makefile ! But it was damn fun to write :)
# Damn is this makefile ugly ! And buggy tooo !
# But it was sure fun to write :P
# 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)))
.SECONDEXPANSION:
OBJS=$(addprefix obj/$(CONFIG)/, $(OBJ))
ALLOBJ=$(addprefix obj/, $(OBJ))
DEPS:=$(OBJ:.$(OBJEXT)=.$(DEPEXT))
# Define the path where the result will be outputted
OUTPATH=$(if $(filter $(CONFIG), analysis),$(SCANDIR),$(BINDIR)/$(CONFIG))
# .PHONY targets.
.PHONY: clean cleanall debug release analyze
.SECONDEXPANSION:
all: pre-build $(EXEC)
# Target specific variable value.
# These targets are used to do less typing while invoking the make with a special config.
debug: CONFIG:=debug
debug: pre-build $(EXEC)
release: CONFIG:=release
release: pre-build $(EXEC)
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 build.gen
$(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=
-include build.gen
build.gen:
@echo "$(EXEC) :: $(OBJS)" > build.gen
ifeq ($(EXECUTION), jit)
@printf "\t$(SILENT) $(LD) $(EXEC) -o $(BINDIR)/$(CONFIG)/$(EXEC)\n" >> build.gen
else
$(if $(filter $(CONFIG), analysis),\
printf 'echo "No executable code output for analysis"',\
printf '\t@ mkdir -p $(BINDIR)/$(CONFIG)\n\t$(LD) -o $(BINDIR)/$(CONFIG)/$(EXEC) $$^ $(LDFLAGS)\n') >> build.gen
endif
@echo "" >> build.gen
@echo '-include $$(addprefix obj/$(CONFIG)/,$(DEPS))' >> build.gen
@echo "PERCENT=%" >> build.gen
@printf "$(OBJDIR)/$(CONFIG)/"'$$(PERCENT)'".$(OBJEXT): $(SRCDIR)/"'$$(PERCENT)'".$(CEXT)\n\
$$(eval OK=1)\n\
$(SILENT) mkdir -p $(OBJDIR)/$(CONFIG)/"'$$(dir $$*)'"\n\
$(SILENT) $(CXX) -I$(INCLDIR) -x c -o $(OBJDIR)/$(CONFIG)/"'$$*'".$(OBJEXT) -c $$< $(CFLAGS) $(FLAGS) $(MAKEDEPEND)\n" >> build.gen
@printf "$(OBJDIR)/$(CONFIG)/"'$$(PERCENT)'".$(OBJEXT): $(SRCDIR)/"'$$(PERCENT)'".$(CXXEXT)\n\
$$(eval OK=1)\n\
$(SILENT) mkdir -p $(OBJDIR)/$(CONFIG)/"'$$(dir $$*)'"\n\
$(SILENT) $(CXX) -I$(INCLDIR) -x c++ -o $(OBJDIR)/$(CONFIG)/"'$$*'".$(OBJEXT) -c $$< $(CXXFLAGS) $(FLAGS) $(MAKEDEPEND)\n" >> build.gen
# This target is the main target.
# It will link all file generated by its dependencies together
#$(EXEC): override ALLOBJ=$(addprefix obj/$(CONFIG)/, $(OBJ))
$(EXEC) ::
@ $(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",)
clean:
$(SILENT) rm -rf $(OBJDIR)/*
cleanall: clean
$(SILENT) rm -rf $(EXEC)
$(SILENT) rm -f ./build.gen
# 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