Skip to content

Instantly share code, notes, and snippets.

@apples
Last active August 29, 2015 14:16
Show Gist options
  • Save apples/ecbe7de3d68def43e82a to your computer and use it in GitHub Desktop.
Save apples/ecbe7de3d68def43e82a to your computer and use it in GitHub Desktop.
Moral of the story: Use Scons.
# User Configuration
# Compiler flags
CPPFLAGS +=
CXXFLAGS += -std=c++1y -pedantic -Wall
LDFLAGS +=
LDLIBS +=
# Libraries and Executables to be built
LIBS += core
EXES += client server
# Build Modes; First mode is the default
MODES := release debug
# Mode-specific flags
MODES[release].cppflags += -DNDEBUG
MODES[release].cxxflags += -O3
MODES[debug].cxxflags += -g -Og
# Target-specific flags
EXES[client].libdeps += core
EXES[server].libdeps += core
# Destination directories
SRCDIR ?= src
BINDIR ?= bin
LIBDIR ?= lib
INCDIR ?= include
# Metadata directories
DEPDIR ?= .deps
OBJDIR ?= .objs
###############################
### WARNING WARNING WARNING ###
### DO NOT PROCEED ###
###############################
# All Target
.PHONY: all
all:
# Compiler configuration
LibLinkFlag=-l$1
DirLinkFlag=-L$1
DirIncFlag=-I$1
# MakeDirectory(DIR)
MakeDirectory=mkdir -p $1
# MakeHeader(SOURCE,HEADER)
MakeHeader=cp $1 $2
# MakeDependency(CPPFLAGS,CXXFLAGS,SOURCE,DEP)
MakeDependency=$(CXX) $1 $2 -M -MT "`echo $4 | sed 's|$(DEPDIR)/\(.*\).d|$(OBJDIR)/\1.o|'` $4" -MF $4 $3
# MakeObject(CPPFLAGS,CXXFLAGS,SOURCE,OBJ)
MakeObject=$(CXX) $1 $2 -c $3 -o $4
# MakeExecutable(LDFLAGS,LDLIBS,OBJS,EXE)
MakeExecutable=$(CXX) $1 $3 $2 -o $4
# MakeArchive(OBJS,LIB)
MakeArchive=$(AR) rcs $2 $1
CppFlags=$($(CURRENT_TARGET).cppflags) $($(CURRENT_MODE).cppflags) $(CPPFLAGS)
CxxFlags=$(CXXFLAGS) $($(CURRENT_MODE).cxxflags) $($(CURRENT_TARGET).cxxflags)
LdFlags=$(LDFLAGS) $($(CURRENT_MODE).ldflags) $($(CURRENT_TARGET).ldflags)
LdLibs=$($(CURRENT_TARGET).ldlibs) $($(CURRENT_MODE).ldlibs) $(LDLIBS)
# Dumb Stuff
EMPTY :=
SPACE :=$(EMPTY) $(EMPTY)
TAB = $(SPACE)$(SPACE)
# Quiet marker
QMARKER:=$(if $(VERBOSE)$(V),,@)
# Types
define DefClass =
$2.SUPER := $1
$2.PROTOTYPE := $3
endef
define New =
$2.TYPE := $1
$2.REALTYPES :=
endef
IsA=$(findstring $1,$($2.TYPE) $($($2.TYPE).SUPER))
# function InfoVar(VAR)
# Equivalent to $(info VAR=$(VAR)).
define InfoVar =
$$(info $2$1=$$($1))
endef
# function DumpInfo(OBJ)
# Dumps all known information about the object $(OBJ) with type $($(OBJ).TYPE).
define DumpInfo =
ifeq ($$(DumpInfo.indent),)
$$(eval $$(call InfoVar,$1))
endif
DumpInfo.indent := x $$(DumpInfo.indent)
DumpInfo.whiteindent = $$(DumpInfo.indent:%=$$$$(SPACE))$$(SPACE)
$$(eval $$(call InfoVar,$1.TYPE,$$(DumpInfo.whiteindent)))
$$(foreach MEMBER,$$($$($1.TYPE).PROTOTYPE),$$(eval $$(call InfoVar,$1.$$(MEMBER),$$(DumpInfo.whiteindent))))
ifneq ($$(words $$($$($1.TYPE).SUPER)),0)
$1.REALTYPES := $$($1.TYPE) $$($1.REALTYPES)
$1.TYPE := $$($$($1.TYPE).SUPER)
$$(eval $$(call DumpInfo,$1))
$1.TYPE := $$(firstword $$($1.REALTYPES))
$1.REALTYPES := $$(wordlist 2,$$(words $$($1.REALTYPES)),$$($1.REALTYPES))
endif
DumpInfo.indent := $$(wordlist 2,$$(words $$(DumpInfo.indent)),$$(DumpInfo.indent))
endef
define TypeError =
$$(error Cannot call $1 with {$2.TYPE=$$($2.TYPE)})
endef
# function AssertType(TYPE,VAR,CALLER)
define AssertType =
ifeq ($$(call IsA,$1,$2),)
$$(eval $$(call TypeError,$3,$2))
endif
endef
$(eval $(call DefClass,,MODE, \
name \
directory \
cppflags \
cxxflags \
ldflags \
ldlibs \
))
$(eval $(call DefClass,,BIN, \
name \
directory \
cppflags \
cxxflags \
sources \
headers \
deps \
objs \
out \
libdeps \
ignore_subdirs \
))
$(eval $(call DefClass,BIN,LIB, \
headers_out \
))
$(eval $(call DefClass,BIN,EXE, \
ldflags \
ldlibs \
))
# Generated Configuration
ALL_DIRS :=
ALL_DEPS :=
ALL_OBJS :=
ALL_LIBS :=
ALL_EXES :=
ALL_HEADERS :=
ifeq ($(OS),Windows_NT)
EXESUFF := .exe
LIBSUFF := .a
else
EXESUFF :=
LIBSUFF := .a
endif
define FindSources =
$$(eval $$(call AssertType,BIN,$1,$0))
$1.sources := $$(if $$(wildcard $$($1.directory)/$$(SRCDIR)),$$(shell find $$($1.directory)/$$(SRCDIR) -name '*.cpp' $$(foreach DIR,$$($1.ignore_subdirs:%=$$($1.directory)/%),-not -path '$$(DIR)*')))
$1.headers := $$(if $$(wildcard $$($1.directory)/$$(INCDIR)),$$(shell find $$($1.directory)/$$(INCDIR) -name '*.hpp' $$(foreach DIR,$$($1.ignore_subdirs:%=$$($1.directory)/%),-not -path '$$(DIR)*')))
endef
Modeify=$(foreach MOD,$(MODES:%=MODES[%]),$($1:%=$($(MOD).directory)/%))
define FindOutput =
$$(eval $$(call AssertType,BIN,$1,$0))
$1.deps := $$($1.sources:%.cpp=$$(DEPDIR)/%.d)
$1.objs := $$($1.sources:%.cpp=$$(OBJDIR)/%.o)
ifneq ($$(call IsA,LIB,$1),)
$1.out := $$(LIBDIR)/lib$$($1.name)$$(LIBSUFF)
$1.headers_out := $$($1.headers:$$($1.directory)/%=%)
else ifneq ($$(call IsA,EXE,$1),)
$1.out := $$(BINDIR)/$$($1.name)$$(EXESUFF)
endif
endef
GetAllLibdeps=$($1.libdeps) $(foreach lib,$($1.libdeps),$(call GetAllLibdeps,LIBS[$(lib)]))
define NormalizeLibdeps =
$$(eval $$(call AssertType,EXE,$1,$0))
$1.libdeps := $(call GetAllLibdeps,$1)
endef
# Madness Ensues
define CreateModeData =
$$(eval $$(call New,MODE,MODES[$1]))
MODES[$1].name := $1
MODES[$1].directory ?= $1
MODES[$1].cppflags += $$(call DirIncFlag,$$(MODES[$1].directory)/$$(INCDIR))
MODES[$1].ldflags += $$(call DirLinkFlag,$$(MODES[$1].directory)/$$(LIBDIR))
MODES[$1].objs :=
MODES[$1].deps :=
MODES[$1].headers_out :=
MODES[$1].outs :=
.PHONY: MODES[$1]
MODES[$1]:
all: MODES[$1]
endef
# function CreateTargetData(TYPE,NAME)
# Constructs a target object named $(TYPE).$(NAME) and packs it with data.
define CreateTargetData =
$$(eval $$(call New,$(1:%S=%),$1[$2]))
$1[$2].name := $2
$1[$2].directory ?= $2
$1[$2].cppflags += $$(call DirIncFlag,$2/include)
$$(eval $$(call FindSources,$1[$2]))
$$(eval $$(call FindOutput,$1[$2]))
ifneq ($$(call IsA,EXE,$1[$2]),)
$$(eval $$(call NormalizeLibdeps,$1[$2]))
$1[$2].ldlibs += $$(foreach LIB,$$($1[$2].libdeps),$$(call LibLinkFlag,$$(LIB)))
endif
endef
ImbueMode=$(foreach F,$1,$($2.directory)/$(F))
define MakeSourceRule =
MY_DEP := $$(call ImbueMode,$$(DEPDIR)/$(1:%.cpp=%.d),$2)
MY_OBJ := $$(call ImbueMode,$$(OBJDIR)/$(1:%.cpp=%.o),$2)
$$(MY_DEP) $$(MY_OBJ): private CURRENT_SOURCE := $1
$$(MY_OBJ): $$(MY_DEP)
$$(MY_DEP): | $$(dir $$(MY_DEP))
$$(MY_OBJ): | $$(dir $$(MY_OBJ))
endef
define MakeHeaderRule =
MY_HEADER := $$(call ImbueMode,$1,$2)
$$(MY_HEADER): private CURRENT_SOURCE := $$($3.directory)/$1
$$(MY_HEADER): | $$(dir $$(MY_HEADER))
endef
# function SetupTarget(TARGET,MODE)
define SetupTarget =
$$(eval $$(call AssertType,BIN,$1,$0))
$$(eval $$(call AssertType,MODE,$2,$0))
MY_OUT := $$(call ImbueMode,$$($1.out),$2)
MY_OBJS := $$(call ImbueMode,$$($1.objs),$2)
MY_DEPS := $$(call ImbueMode,$$($1.deps),$2)
MY_HEADERS :=
ifneq ($$(call IsA,LIB,$1),)
MY_HEADERS := $$(call ImbueMode,$$($1.headers_out),$2)
endif
MY_LIBDEPS_HEADERS := $$(foreach LIBDEP,$$($1.libdeps),$$(call ImbueMode,$$($$(LIBDEP:%=LIBS[%]).headers_out),$2))
MY_LIBDEPS_OUTS := $$(foreach LIBDEP,$$($1.libdeps),$$(call ImbueMode,$$($$(LIBDEP:%=LIBS[%]).out),$2))
$$(MY_OUT) $$(MY_OBJS) $$(MY_DEPS) $$(MY_HEADERS): private CURRENT_TARGET := $1
$$(MY_OUT) $$(MY_OBJS) $$(MY_DEPS) $$(MY_HEADERS): private CURRENT_MODE := $2
$$(MY_DEPS): | $$(MY_LIBDEPS_HEADERS)
ifneq ($$(MAKECMDGOALS),clean)
-include $$(MY_DEPS)
endif
$$(MY_OUT): private CURRENT_OBJS := $$(MY_OBJS)
$$(MY_OUT): $$(MY_OBJS) $$(MY_LIBDEPS_OUTS) | $$(dir $$(MY_OUT)) $$(MY_HEADERS)
$$(foreach SRC,$$($1.sources),$$(eval $$(call MakeSourceRule,$$(SRC),$2)))
$$(foreach HEADER,$$($1.headers_out),$$(eval $$(call MakeHeaderRule,$$(HEADER),$2,$1)))
$2: $$(MY_OUT)
ALL_DEPS += $$(MY_DEPS)
ALL_OBJS += $$(MY_OBJS)
ifneq ($$(call IsA,LIB,$1),)
ALL_HEADERS += $$(MY_HEADERS)
ALL_LIBS += $$(MY_OUT)
else ifneq ($$(call IsA,EXE,$1),)
ALL_EXES += $$(MY_OUT)
endif
endef
$(foreach MOD,$(MODES), \
$(eval $(call CreateModeData,$(MOD))) \
)
$(foreach LIB,$(LIBS), \
$(eval $(call CreateTargetData,LIBS,$(LIB))) \
)
$(foreach EXE,$(EXES), \
$(eval $(call CreateTargetData,EXES,$(EXE))) \
)
$(foreach BIN,$(LIBS:%=LIBS[%]) $(EXES:%=EXES[%]), \
$(foreach MOD,$(MODES:%=MODES[%]), \
$(eval $(call SetupTarget,$(BIN),$(MOD))) \
) \
)
ALL_DIRS := $(sort $(dir $(ALL_DEPS) $(ALL_OBJS) $(ALL_HEADERS) $(ALL_LIBS) $(ALL_EXES)))
$(ALL_DIRS):
$(info [DIR] $@)
$(QMARKER)$(call MakeDirectory,$@)
$(ALL_DEPS):
$(info [DEP] $@)
$(QMARKER)$(call MakeDependency,$(call CppFlags),$(call CxxFlags),$(CURRENT_SOURCE),$@)
$(ALL_OBJS):
$(info [OBJ] $@)
$(QMARKER)$(call MakeObject,$(call CppFlags),$(call CxxFlags),$(CURRENT_SOURCE),$@)
$(ALL_HEADERS):
$(info [HPP] $@)
$(QMARKER)$(call MakeHeader,$(CURRENT_SOURCE),$@)
$(ALL_LIBS):
$(info [LIB] $@)
$(QMARKER)$(call MakeArchive,$(CURRENT_OBJS),$@)
$(ALL_EXES):
$(info [EXE] $@)
$(QMARKER)$(call MakeExecutable,$(call LdFlags),$(call LdLibs),$(CURRENT_OBJS),$@)
.PHONY: clean
clean:
rm -f $(ALL_DEPS) $(ALL_OBJS) $(ALL_LIBS) $(ALL_EXES) $(ALL_HEADERS)
-find $(foreach MOD,$(MODES:%=MODES[%]),$($(MOD).directory)) -depth -type d -exec rmdir {} \;
.PHONY: dumpvars
dumpvars:
$(eval $(call InfoVar,ALL_DIRS))
$(eval $(call InfoVar,ALL_DEPS))
$(eval $(call InfoVar,ALL_OBJS))
$(eval $(call InfoVar,ALL_LIBS))
$(eval $(call InfoVar,ALL_EXES))
$(eval $(call InfoVar,ALL_HEADERS))
$(foreach LIB,$(LIBS),$(eval $(call DumpInfo,LIBS[$(LIB)])))
$(foreach EXE,$(EXES),$(eval $(call DumpInfo,EXES[$(EXE)])))
$(foreach MOD,$(MODES:%=MODES[%]),$(eval $(call DumpInfo,$(MOD))))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment