##### BASE MAKEFILE
#
# This makefile is intended to be a basis on which new makefiles are written.
# It is divided into five parts:
#  * help text generation
#  * path for subproject locations and thirparty include/library paths
#  * default values for tools (like CXX) and compiler/linker flags
#  * description of known libraries that may be used in the build
#  * functions that define build artifacts
#
# library handling will be described in detail later.
#
# we have three functions that defined build artifacts:
#  * build-executable
#  * build-static-library
#  * build-dynamic-library
#
# they all have the same, basic signature:
#    (name: string, sources: list<string>, usedLibraries: list<string>)
#
# $name determines the name given to the output file (for executables) or the library name given
# to the output library (for libraries).
#
# $sources must be a list of source files (allowed types are .cpp and .c), relative to the directory
# of the Makefile you are writing (see example below).
#
# $usedLibraries is a (possibly empty) list of libraries as defined in the KNOWN LIBRARIES section
# below. they will be automatically added to compiler and linker invocations as needed.
#
# EXAMPLE:
#
# imagine you have a project with a directory structure like this:
#  - /build
#    - Makefile
#  - /source
#    - /lib
#      - ...
#    - /exe
#      - ...
#
# you want your Makefile to build a shared library from /source/lib and an executable from
# /source/exe. the you could write your Makefile like this:
#
# include ,../../build/Makefile
#
# # will generate a libFancy.so in /build, uses libz
# $(call build-static-library,\
#     Fancy,\
#     $(shell find ../source/lib -iname '*.cpp'),\
#     z)
#
# # this registers libFancy as a shared library. see details below (KNOWN LIBRARIES).
# $(call define-dep-lib, Fancy, -I ../source/lib/include, -L . -l Fancy)
#
# # will generate an executable file Run in /build, uses libFancy
# $(call build-executable,\
#    Run,\
#    $(shell find ../source/exe -iname '*.cpp'),\
#    Fancy)
#
# # a dependency must be added between Run and Fancy, to ensure that Fancy is built first.
# Run: | libFancy.so

override V := $(if $V,,@)

all:

define HELP_ARGS_GENERIC
	@echo  '  BEEGFS_DEBUG=1             Enables debug information and symbols'
	@echo  '  BEEGFS_DEBUG_OPT=1         Enables internal debug code, but compiled'
	@echo  '                             with optimizations'
	@echo  '  BEEGFS_DEBUG_IP=1          Enables low-level debug of network sendto'
	@echo  '                             and recvfrom'
	@echo  '  CXX=<compiler>             Specifies a c++ compiler'
	@echo  '  DISTCC=distcc              Enables the usage of distcc'
	@echo  '  V=1                        Print command lines of tool invocations'
	@echo  '  BEEGFS_COMMON_PATH=<path>  Path to the common directory'
	@echo  '  BEEGFS_THIRDPARTY_PATH=<path>'
	@echo  '                             Path to the thirdparty directory'
	@echo  '  BEEGFS_USE_PCH=1           Enables use of precompiled headers'
endef
define HELP_TARGETS_GENERIC
	@echo '  all (default)     build only'
	@echo '  clean             delete compiled files'
	@echo '  help              print this help message'
endef

help:
	@echo 'Optional arguments:'
	$(HELP_ARGS_GENERIC)
	$(HELP_ARGS_SPECIFIC)
	@echo
	@echo 'Targets:'
	$(HELP_TARGETS_GENERIC)
	$(HELP_TARGETS_SPECIFIC)

root_dir := $(realpath $(dir $(lastword $(MAKEFILE_LIST)))/..)
build_dir := $(realpath $(dir $(firstword $(MAKEFILE_LIST))))

BEEGFS_COMMON_PATH             ?= $(root_dir)/common
BEEGFS_COMMON_PCH_H            ?= $(BEEGFS_COMMON_PATH)/source/common/pch.h
BEEGFS_THIRDPARTY_PATH         ?= $(root_dir)/thirdparty
BEEGFS_COMM_DEBUG_PATH         ?= $(root_dir)/comm_debug
BEEGFS_FSCK_PATH               ?= $(root_dir)/fsck
BEEGFS_EVENT_LISTENER_PATH     ?= $(root_dir)/event_listener
BEEGFS_DEVEL_INCLUDE_PATH      ?= $(root_dir)/client_devel/include
BEEGFS_CLIENT_PATH             ?= $(root_dir)/client_module/include

BOOST_INC_PATH    ?= $(BEEGFS_THIRDPARTY_PATH)/source/boost
NU_INC_PATH       ?= $(BEEGFS_THIRDPARTY_PATH)/source/nu/include

GTEST_INC_PATH    ?= $(BEEGFS_THIRDPARTY_PATH)/source/gtest/googletest/include
GTEST_LIB_PATH    ?= $(BEEGFS_THIRDPARTY_PATH)/build

ifneq ($(target_arch),)
   STRIP := $(target_arch)-strip
   AR := $(target_arch)-ar
   CC := $(target_arch)-gcc
   CXX := $(target_arch)-g++
endif

SHELL := /bin/bash
STRIP ?= strip
CXX   ?= g++
AR    ?= ar
CLANG_TIDY ?= clang-tidy

# if -T is supported by ar, use it. thin archives are quicker to create and maintain.
ifeq ($(shell ar -TM 2>&1 <<<""),)
AR += -T
endif

CXXFLAGS = \
   -std=c++17 -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64 \
   -isystem $(BOOST_INC_PATH) -isystem $(NU_INC_PATH) \
   -pthread \
   -fno-strict-aliasing -fmessage-length=0 \
   -Wall -Wextra -Wunused-variable -Woverloaded-virtual -Wno-unused-parameter -Wuninitialized \
      -Wno-missing-field-initializers \
      -Winvalid-pch \
   -ggdb3 \
   $(USER_CXXFLAGS)
CXXFLAGS_RELEASE = -O3 -Wuninitialized
CXXFLAGS_DEBUG = -O1 -D_FORTIFY_SOURCE=2 \
   -DBEEGFS_DEBUG -DDEBUG_READWRITE -DDEBUG_MUTEX_LOCKING -DDEBUG_REFCOUNT \
   -DDEBUG_BACKTRACE  -DLOG_DEBUG_MESSAGES
CXXFLAGS_COVERAGE = --coverage -O1 -fno-inline

LDFLAGS = -std=c++1y \
   -rdynamic \
   -pthread -lrt \
   -Wl,-rpath,'$$ORIGIN/../lib' \
   $(USER_LDFLAGS)
LDFLAGS_COVERAGE = --coverage

ifneq ($(BEEGFS_DEBUG),)
CXXFLAGS += $(CXXFLAGS_DEBUG)
else ifneq ($(BEEGFS_COVERAGE),)
CXXFLAGS += $(CXXFLAGS_COVERAGE)
LDFLAGS += $(LDFLAGS_COVERAGE)
else
CXXFLAGS += $(CXXFLAGS_RELEASE)
endif


CXXFLAGS += -DBEEGFS_VERSION="\"$(BEEGFS_VERSION)\""

ifeq ($(BEEGFS_NVFS),1)
CXXFLAGS += -DBEEGFS_NVFS
endif

ifeq ($(BEEGFS_DEBUG_RDMA),1)
CXXFLAGS += -DBEEGFS_DEBUG_RDMA
endif

ifneq ($(BEEGFS_DEBUG_IP),)
CXXFLAGS += -DBEEGFS_DEBUG_IP
endif

###### KNOWN LIBRARIES
#
# this is our local registry of known libraries, both our own and thirdparty libraries.
#
# currently supports building with our own libraries common and client-devel.
# thirdparty packages supported are:
#  * dl (from system)
#
# to define new libraries, add appriopriate calls to define-dep-lib in this section.
# the signature of define-dep-lib is:
#    (name: string, CXXFLAGS: string, libraryPath: string[, LDFLAGS: string])
#
# $name is used to identify the library within this registry, and is otherwise unused.
#
# $CXXFLAGS will be added to the set of CXXFLAGS used when compiling files from projects that use
# this library (see example at the beginning of this file for reference).
#
# $libraryPath is the path (absolute or relative) to the library. glob patterns are allowed, and in
# fact required if you want to link a dynamic library.
#
# $LDFLAGS will be added to the set of LDFLAGS used when linking shared libraries or executables
# that use this library. if the library you want to link is static, LDFLAGS need not be set - the
# library will be added to the list of archives.
#
#
# to define libraries that are taken from the system, use define-dep-lib-system. it will use its
# arguments to ask pkgconfig for the correct compiler and linker flags. then signature of
# define-dep-lib-system is:
#    (name: string, pkgconfigID: string)
#
# $name is used only to identify the library.
#
# $pkgconfigID is the identifier pkgconfig will be asked about.
define resolve-dep-cflags
$(if $(filter undefined,$(origin _DEP_LIB_CXX[$(strip $1)])),\
   $(error I have no CXXFLAGS for $1!),\
   $(_DEP_LIB_CXX[$(strip $1)]))
endef
define resolve-dep-ldflags
$(if $(filter undefined,$(origin _DEP_LIB_LD[$(strip $1)])),\
   $(error I have no LDFLAGS for $1!),\
   $(_DEP_LIB_LD[$(strip $1)]))
endef
define resolve-dep-deps
$(if $(filter undefined,$(origin _DEP_LIB_DEPS[$(strip $1)])),\
   $(error I have no library dependencies for $1!),\
   $(_DEP_LIB_DEPS[$(strip $1)]))
endef
define define-dep-lib
$(strip
   $(eval _DEP_LIB_CXX[$(strip $1)] = $(strip $2))
   $(eval _DEP_LIB_LD[$(strip $1)] = $(strip $(or $4, $3)))
   $(eval _DEP_LIB_DEPS[$(strip $1)] = $(strip $3))
   $(eval $(strip $3): ))
endef
define define-dep-lib-system
$(strip
   $(if $(shell pkg-config --exists $2 || echo fail),
      $(error Could not find library $2 in system!))
   $(call define-dep-lib,
      $1,
      $(shell pkg-config --cflags $2),
      $(shell pkg-config --libs $2)))
endef

# don't bother to search the system for libraries if we only run `make clean'
# this also has the nice side effect that libraries needn't be present in the system
# when `make clean' is run - if we override dependency resolution to not do anything.
ifeq ($(strip $(MAKECMDGOALS)),clean)
   resolve-dep-cflags :=
   resolve-dep-ldflags :=
   resolve-dep-deps :=
else
   $(call define-dep-lib, common,\
      -I $(BEEGFS_COMMON_PATH)/source \
      -I $(BEEGFS_CLIENT_PATH), \
      -ldl $(BEEGFS_COMMON_PATH)/build/libbeegfs-common.a)

   $(call define-dep-lib, client-devel,\
      -I $(BEEGFS_DEVEL_INCLUDE_PATH),)

   $(call define-dep-lib, dl, , , -ldl)

   $(call define-dep-lib, rdma, , -lrdmacm -libverbs)

   $(call define-dep-lib, gtest,\
      -isystem $(GTEST_INC_PATH)/include,\
      $(GTEST_LIB_PATH)/libgtest.a)

   $(call define-dep-lib, cassandra, -I$(BEEGFS_THIRDPARTY_PATH)/source/datastax)

   $(call define-dep-lib-system, curl, libcurl)  
   $(call define-dep-lib-system, z, zlib)

   $(call define-dep-lib, blkid, , , -lblkid)
   $(call define-dep-lib, uuid, , , -luuid)
   $(call define-dep-lib-system, nl3-route, libnl-route-3.0)
endif


clean:
	@$(RM) -rf $(if $V,,-v) $(CLEANUP_FILES)


NONDEP_MAKEFILES = $(filter-out %.d,$(MAKEFILE_LIST))

%.autogen.h:
	@# Generate a proxy-header for precompilation.
	@true > $@  # truncate file
	@echo '/* Autogenerated precompiled header (PCH)' >> $@
	@echo ' * For each set of compiler options we need to compile ' >> $@
	@echo ' * a PCH separately. To realize that, we create ' >> $@
	@echo ' * proxy PCHs like this file and compile these. */' >> $@
	@echo '#include "$(BEEGFS_COMMON_PCH_H)"' >> $@

%.h.gch: %.h
	@echo "[CXX]   $@"
	$V$(CXX) $(CXXFLAGS) -c $< -E -MMD -MP -MF$<.d -MT$@ -o/dev/null
	$V$(DISTCC) $(CXX) $(CXXFLAGS) -o$@ -c $(realpath $<)

%.cpp.o: %.cpp $(NONDEP_MAKEFILES)
	@echo "[CXX]   $<"
	$V$(DISTCC) $(CXX) $(CXXFLAGS) -MMD -MF$<.d -MT$<.o -MP -o $<.o -c $(realpath $<)

%.c.o: %.c $(NONDEP_MAKEFILES)
	@echo "[CXX]   $<"
	$V$(DISTCC) $(CXX) $(CXXFLAGS) -MMD -MF$<.d -MT$<.o -MP -o$<.o -c $(realpath $<)

# first partial list: checks we want to exclude on a general basis right now
# second list: very minor problems we don't want to be bugged about yet, but want to fix over time
# third list: definite errors, fix as soon as possible
define -clang_tidy_checks
-clang-analyzer-alpha.deadcode.UnreachableCode
-fuchsia-default-arguments
-fuchsia-overloaded-operator
-google-build-using-namespace
-google-readability-braces-around-statements
-google-readability-casting
-google-readability-namespace-comments
-hicpp-braces-around-statements
-llvm-header-guard
-llvm-include-order
-llvm-namespace-comment
-readability-braces-around-statements
-readability-implicit-bool-conversion

-android-cloexec-accept
-android-cloexec-creat
-android-cloexec-dup
-android-cloexec-epoll-create
-android-cloexec-fopen
-android-cloexec-open
-boost-use-to-string
-bugprone-branch-clone
-bugprone-exception-escape
-bugprone-narrowing-conversions
-bugprone-sizeof-expression
-cert-oop54-cpp
-clang-analyzer-core.CallAndMessage
-clang-analyzer-core.uninitialized.Assign
-clang-analyzer-cplusplus.NewDeleteLeaks
-clang-analyzer-deadcode.DeadStores
-clang-analyzer-optin.cplusplus.UninitializedObject
-clang-diagnostic-deprecated-copy
-cppcoreguidelines-avoid-c-arrays
-cppcoreguidelines-avoid-goto
-cppcoreguidelines-avoid-magic-numbers
-cppcoreguidelines-init-variables
-cppcoreguidelines-interfaces-global-init
-cppcoreguidelines-macro-usage
-cppcoreguidelines-narrowing-conversions
-cppcoreguidelines-non-private-member-variables-in-classes
-cppcoreguidelines-pro-bounds-array-to-pointer-decay
-cppcoreguidelines-pro-bounds-constant-array-index
-cppcoreguidelines-pro-bounds-pointer-arithmetic
-cppcoreguidelines-pro-type-const-cast
-cppcoreguidelines-pro-type-cstyle-cast
-cppcoreguidelines-pro-type-member-init
-cppcoreguidelines-pro-type-reinterpret-cast
-cppcoreguidelines-pro-type-static-cast-downcast
-cppcoreguidelines-pro-type-union-access
-cppcoreguidelines-pro-type-vararg
-cppcoreguidelines-special-member-functions
-fuchsia-default-arguments-calls
-fuchsia-default-arguments-declarations
-fuchsia-statically-constructed-objects
-google-default-arguments
-google-explicit-constructor
-google-readability-avoid-underscore-in-googletest-name
-google-readability-redundant-smartptr-get
-google-readability-todo
-google-runtime-int
-google-runtime-references
-hicpp-avoid-c-arrays
-hicpp-avoid-goto
-hicpp-deprecated-headers
-hicpp-explicit-conversions
-hicpp-member-init
-hicpp-multiway-paths-covered
-hicpp-no-array-decay
-hicpp-no-assembler
-hicpp-no-malloc
-hicpp-signed-bitwise
-hicpp-special-member-functions
-hicpp-uppercase-literal-suffix
-hicpp-use-auto
-hicpp-use-emplace
-hicpp-use-equals-default
-hicpp-use-equals-delete
-hicpp-use-noexcept
-hicpp-use-nullptr
-hicpp-vararg
-llvm-qualified-auto
-misc-misplaced-widening-cast
-misc-move-constructor-init
-misc-non-private-member-variables-in-classes
-misc-redundant-expression
-misc-string-compare
-misc-unused-parameters
-modernize-avoid-bind
-modernize-avoid-c-arrays
-modernize-loop-convert
-modernize-make-shared
-modernize-make-unique
-modernize-pass-by-value
-modernize-raw-string-literal
-modernize-replace-random-shuffle
-modernize-return-braced-init-list
-modernize-use-auto
-modernize-use-default
-modernize-use-default-member-init
-modernize-use-nodiscard
-modernize-use-emplace
-modernize-use-equals-default
-modernize-use-equals-delete
-modernize-use-noexcept
-modernize-use-nullptr
-modernize-use-trailing-return-type
-modernize-use-using
-performance-faster-string-find
-performance-inefficient-string-concatenation
-performance-unnecessary-copy-initialization
-performance-unnecessary-value-param
-readability-const-return-type
-readability-convert-member-functions-to-static
-readability-else-after-return
-readability-implicit-bool-cast
-readability-isolate-declaration
-readability-magic-numbers
-readability-make-member-function-const
-readability-named-parameter
-readability-non-const-parameter
-readability-qualified-auto
-readability-redundant-control-flow
-readability-redundant-smartptr-get
-readability-redundant-string-cstr
-readability-string-compare
-readability-uppercase-literal-suffix

-cert-err58-cpp
-cert-err60-cpp
-cert-msc30-c
-cert-msc50-cpp
-clang-analyzer-alpha.cplusplus.VirtualCall
-clang-analyzer-core.DivideZero
-clang-analyzer-optin.cplusplus.VirtualCall
-cppcoreguidelines-no-malloc
-cppcoreguidelines-owning-memory
-readability-misleading-indentation
endef
clang_tidy_checks := *,$(shell echo $(strip $(-clang_tidy_checks)) | tr ' ' ',')

# make-tidy-rule
#
# add a rule to invoke clang-tidy for the given source files
# arguments:
#  #1: identifier for library/executable
#  #2: source files
#  #3: include paths and defines
make-tidy-rule = $(eval $(call -make-tidy-rule,$(strip $1),$2,$3))
define -specific-tidy-rule
$1:
	@echo "[TIDY]  $2"
	$V$(CLANG_TIDY) -checks='$(clang_tidy_checks)' -quiet $2 -- $$(CXXFLAGS) $3
endef
define -make-tidy-rule
.PHONY: tidy
tidy: tidy-$1

.PHONY: tidy-$1 $(foreach file,$2,$(file).tidy-$1)
tidy-$1: $(foreach file,$2,$(file).tidy-$1)

$(foreach file,$2,$(eval $(call -specific-tidy-rule,$(file).tidy-$1,$(file),$3)))
endef

define -build-pch-fragment
ifneq ($(BEEGFS_USE_PCH),)
# .o files depend on precompiled headers.
$(addsuffix .o,$2): CXXFLAGS += -include $(1).autogen.h
$(addsuffix .o,$2): $(1).autogen.h.gch
endif
# not behind conditional -- let's delete these always
CLEANUP_FILES += $(1).autogen.h $(1).autogen.h.gch
CLEANUP_FILES += $(1).autogen.h.d  # "dependency" files that get produced when compiling the header
endef


# build-executable
#
# define a new executable for the build
# arguments:
#  #1: name of the executable
#  #2: sources
#  #3: required libraries
#  #4: include directories
build-executable = $(eval $(call -build-executable-fragment,$(strip $1),$2,$3,$4))
define -build-executable-fragment
all: $1

$(call -build-pch-fragment,$1,$2)

CLEANUP_FILES += $1 $(addsuffix .o,$2) $(addsuffix .d,$2)

$(addsuffix .o .tidy-%,$2): CXXFLAGS += \
   $(foreach lib,$3,$(call resolve-dep-cflags,$(lib))) $(addprefix -I, $4)

$1: LDFLAGS += \
   -Wl,--start-group $(foreach lib,$3,$(call resolve-dep-ldflags,$(lib))) -Wl,--end-group

$1: $(addsuffix .o,$2) $(foreach lib,$3,$(call resolve-dep-deps,$(lib)))
	@echo "[LD]	$$@"
	$$V$$(CXX) -o $$@ $(addsuffix .o,$2) $$(LDFLAGS)

-include $(addsuffix .d,$2)

$(call make-tidy-rule, $1, $2,\
   $(foreach lib,$3,$(call resolve-dep-cflags,$(lib))) $(addprefix -I, $4))
endef

# build-test
#
# build a test runner from specified test files. acts much like build-executable, except that it
# includes gtest in the build as a non-library .a
#  #1: name if the test executable
#  #2: sources
#  #3: required libraries (not including gtest)
#  #4: included directories (not including gtest)
define build-test
$(call build-executable, $1, $2, $3, $4 -isystem $(GTEST_INC_PATH))

$1: LDFLAGS += $(GTEST_LIB_PATH)/libgtest.a
endef

# build-static-library
#
# define a new (static) library for the build
# arguments:
#  #1: name of the library
#  #2: sources
#  #3: required libraries
#  #4: include directories
build-static-library = $(eval $(call -build-static-library-fragment,lib$(strip $1).a,$2,$3,$4))
define -build-static-library-fragment
all: $1

$(call -build-pch-fragment,$1,$2)

CLEANUP_FILES += $1 $(addsuffix .o,$2) $(addsuffix .d,$2)

$(addsuffix .o .tidy-%,$2): CXXFLAGS += \
   $(foreach lib,$3,$(call resolve-dep-cflags,$(lib))) $(addprefix -I, $4)

$(build_dir)/$1: $(addsuffix .o,$2)
	@echo "[AR]	$1"
	@rm -f $$@
	$$V$(AR) -rcs $$@ $$^

$1: $(build_dir)/$1

-include $(addsuffix .d,$2)

$(call make-tidy-rule, $1, $2,\
   $(foreach lib,$3,$(call resolve-dep-cflags,$(lib))) $(addprefix -I, $4))
endef

# build-shared-library
#
# define a new (shared) library for the build
# arguments:
#  #1: name of the library
#  #2: sources
#  #3: required libraries
#  #4: include directories
build-shared-library = $(eval $(call -build-shared-library-fragment,lib$(strip $1).so,$2,$3,$4))
define -build-shared-library-fragment
all: $1

$(call -build-pch-fragment,$1,$2)

CLEANUP_FILES += $1 $(addsuffix .o,$2) $(addsuffix .d,$2)

$(addsuffix .o .tidy-%,$2): CXXFLAGS += \
   -fPIC $(foreach lib,$3,$(call resolve-dep-cflags,$(lib))) $(addprefix -I, $4)

$1: LDFLAGS += \
  -Wl,--start-group $(foreach lib,$3,$(call resolve-dep-ldflags,$(lib))) -Wl,--end-group

$(build_dir)/$1: $(addsuffix .o,$2) $(foreach lib,$3,$(call resolve-dep-deps,$(lib)))
	@echo "[LD]	$1"
	$$V$$(CXX) -shared -o $$@ $(addsuffix .o,$2) \
		-Wl,--whole-archive $$(LDFLAGS) -Wl,--no-whole-archive

$1: $(build_dir)/$1

-include $(addsuffix .d,$2)

$(call make-tidy-rule, $1, $2,\
   $(foreach lib,$3,$(call resolve-dep-cflags,$(lib))) $(addprefix -I, $4))
endef
