#=========================================================================
# Toplevel Makefile for the Modular C++ Build System
#=========================================================================
# Please read the documenation in 'mcppbs-doc.txt' for more details on
# how the Modular C++ Build System works. For most projects, a developer
# will not need to make any changes to this makefile. The key targets
# are as follows:
#
#  - default   : build all libraries and programs
#  - check     : build and run all unit tests
#  - install   : install headers, project library, and some programs
#  - clean     : remove all generated content (except autoconf files)
#  - dist      : make a source tarball
#  - distcheck : make a source tarball, untar it, check it, clean it
#  - distclean : remove everything
#

#-------------------------------------------------------------------------
# Basic setup
#-------------------------------------------------------------------------

# Remove all default implicit rules since they can cause subtle bugs
# and they just make things run slower
.SUFFIXES:
% : %,v
% : RCS/%,v
% : RCS/%
% : s.%
% : SCCS/s.%

# Default is to build the prereqs of the all target (defined at bottom)
default : all
.PHONY : default

project_name := @PACKAGE_TARNAME@
src_dir      := @srcdir@
scripts_dir  := $(src_dir)/scripts

HAVE_INT128 := @HAVE_INT128@
HAVE_DLOPEN := @HAVE_DLOPEN@
HAVE_CLANG_PCH := @HAVE_CLANG_PCH@

# If the version information is not in the configure script, then we
# assume that we are in a working directory. We use the vcs-version.sh
# script in the scripts directory to generate an appropriate version
# string. Currently the way things are setup we have to run this script
# everytime we run make so the script needs to be as fast as possible.

ifeq (@PACKAGE_VERSION@,?)
  project_ver:=$(shell $(scripts_dir)/vcs-version.sh $(src_dir))
else
  project_ver:=@PACKAGE_VERSION@
endif

# Installation directories

prefix       ?= @prefix@

INSTALLDIR ?= $(DESTDIR)$(prefix)

install_hdrs_dir := $(INSTALLDIR)/include
install_libs_dir := $(INSTALLDIR)/lib
install_exes_dir := $(INSTALLDIR)/bin

#-------------------------------------------------------------------------
# List of subprojects
#-------------------------------------------------------------------------

sprojs         := @subprojects@
sprojs_enabled := @subprojects_enabled@

sprojs_include := -I. -I$(src_dir) $(addprefix -I$(src_dir)/, $(sprojs_enabled))
VPATH := $(addprefix $(src_dir)/, $(sprojs_enabled))

#-------------------------------------------------------------------------
# Programs and flags 
#-------------------------------------------------------------------------

# C++ compiler
#  - CPPFLAGS : flags for the preprocessor (eg. -I,-D)
#  - CXXFLAGS : flags for C++ compiler (eg. -Wall,-g,-O3)
#
# To allow a user to specify CFLAGS or similar as part of the Make
# command, we also have mcpps-CFLAGS etc. with stuff that shouldn't be
# lost in such a case.
#
# The order of precedence (highest to lowest) is then:
#
#    - Specified as part of Make command line
#    - Specified as part of running configure
#    - Specified here (default-CFLAGS)
#
# These all appear on the command line, from lowest precedence to
# highest.

default-CFLAGS   := -DPREFIX=\"$(prefix)\" -Wall -Wno-unused -Wno-nonportable-include-path -g -O2 -fPIC
default-CXXFLAGS := $(default-CFLAGS) -std=c++17

mcppbs-CPPFLAGS := @CPPFLAGS@
mcppbs-CFLAGS   := $(default-CFLAGS) @CFLAGS@
mcppbs-CXXFLAGS := $(default-CXXFLAGS) @CXXFLAGS@

CC            := @CC@
CXX           := @CXX@

# These are the flags actually used for a C++ compile or a C compile.
# The language-specific flags come after the preprocessor flags, but
# user-supplied flags always take precedence.
all-cxx-flags := \
  $(mcppbs-CPPFLAGS) $(mcppbs-CXXFLAGS) $(CPPFLAGS) $(CXXFLAGS)
all-c-flags := \
  $(mcppbs-CPPFLAGS) $(mcppbs-CFLAGS) $(CPPFLAGS) $(CFLAGS)

COMPILE       := $(CXX) -MMD -MP $(all-cxx-flags) $(sprojs_include) @BOOST_CPPFLAGS@
COMPILE_C     := $(CC) -MMD -MP $(all-c-flags) $(sprojs_include)

# Linker
#  - LDFLAGS : Flags for the linker (eg. -L)
#  - LIBS    : Library flags (eg. -l)

mcppbs-LDFLAGS := @LDFLAGS@ @BOOST_LDFLAGS@
all-link-flags := $(mcppbs-LDFLAGS) $(LDFLAGS)

comma := ,
LD            := $(CXX)
LIBS          := @LIBS@ @BOOST_ASIO_LIB@ @BOOST_REGEX_LIB@
LINK          := $(LD) -L. $(all-link-flags) -Wl,-rpath,$(install_libs_dir) $(patsubst -L%,-Wl$(comma)-rpath$(comma)%,$(filter -L%,$(LDFLAGS)))

# Library creation

AR            := @AR@
RANLIB        := @RANLIB@

# Host simulator

RUN           := @RUN@
RUNFLAGS      := @RUNFLAGS@

# Installation

MKINSTALLDIRS := $(scripts_dir)/mk-install-dirs.sh
INSTALL       := @INSTALL@
INSTALL_HDR   := $(INSTALL) -m 644
INSTALL_LIB   := $(INSTALL) -m 644
INSTALL_EXE   := $(INSTALL) -m 755
STOW          := @stow@

# Tests
bintests = $(src_dir)/tests/ebreak.py

#-------------------------------------------------------------------------
# Include subproject makefile fragments
#-------------------------------------------------------------------------

sprojs_mk = $(addsuffix .mk, $(sprojs_enabled))

-include $(sprojs_mk)

dist_junk += $(sprojs_mk)

#-------------------------------------------------------------------------
# Reverse list helper function
#-------------------------------------------------------------------------
# This function is used by the subproject template to reverse the list
# of dependencies. It uses recursion to perform the reversal.
# 
# Arguments:
#  $(1)   : space separated input list
#  retval : input list in reverse order
#

reverse_list = $(call reverse_list_h,$(1),)
define reverse_list_h
  $(if $(strip $(1)),                        \
    $(call reverse_list_h,                   \
           $(wordlist 2,$(words $(1)),$(1)), \
           $(firstword $(1)) $(2)),          \
    $(2))
endef

#-------------------------------------------------------------------------
# Template for per subproject rules
#-------------------------------------------------------------------------
# The template is instantiated for each of the subprojects. It relies on
# subprojects defining a certain set of make variables which are all
# prefixed with the subproject name. Since subproject names can have
# dashes in them (and the make variables are assumed to only use
# underscores) the template takes two arguments - one with the regular 
# subproject name and one with dashes replaced with underscores.
#
# Arguments:
#  $(1) : real subproject name (ie with dashes)
#  $(2) : normalized subproject name (ie dashes replaced with underscores)
#

define subproject_template

# In some (rare) cases, a subproject might not have any actual object
# files. It might only include header files or program sources. To keep
# things consistent we still want a library for this subproject, so in
# this spectial case we create a dummy source file and thus the build
# system will create a library for this subproject with just the
# corresponding dummy object file.

ifeq ($$(strip $$($(2)_srcs) $$($(2)_c_srcs)),)
$(2)_srcs += _$(1).cc
$(2)_junk += _$(1).cc
endif

_$(1).cc :
	echo "int _$(2)( int arg ) { return arg; }" > $$@

# Build the object files for this subproject

$(2)_pch := $$(patsubst %.h, %.h.gch, $$($(2)_precompiled_hdrs))
$(2)_objs := $$(patsubst %.cc, %.o, $$($(2)_srcs))
$(2)_c_objs := $$(patsubst %.c, %.o, $$($(2)_c_srcs))
$(2)_deps := $$(patsubst %.o, %.d, $$($(2)_objs))
$(2)_deps += $$(patsubst %.o, %.d, $$($(2)_c_objs))
$(2)_deps += $$(patsubst %.h, %.h.d, $$($(2)_precompiled_hdrs))
$$($(2)_pch) : %.h.gch : %.h
	$(COMPILE) -x c++-header $$< -o $$@
$$($(2)_objs) : %.o : %.cc $$($(2)_gen_hdrs) $$($(2)_pch)
	$(COMPILE) $(if $(HAVE_CLANG_PCH), $$(if $$($(2)_pch), -include-pch $$($(2)_pch))) $$($(2)_CFLAGS) -c $$<
$$($(2)_c_objs) : %.o : %.c $$($(2)_gen_hdrs)
	$(COMPILE_C) $$($(2)_CFLAGS) -c $$<

$(2)_junk += $$($(2)_pch) $$($(2)_objs) $$($(2)_c_objs) $$($(2)_deps) \
  $$($(2)_gen_hdrs)

# Reverse the dependency list so that a given subproject only depends on
# subprojects listed to its right. This is the correct order for linking
# the list of subproject libraries.

$(2)_reverse_deps   := $$(call reverse_list,$$($(2)_subproject_deps))

# Build a library for this subproject

$(2)_lib_libs       := $$($(2)_reverse_deps)
$(2)_lib_libnames   := $$(patsubst %, lib%.a, $$($(2)_lib_libs))
$(2)_lib_libarg     := $$(patsubst %, -l%, $$($(2)_lib_libs))
$(2)_lib_libnames_shared	:= $$(if $$($(2)_install_shared_lib),lib$(1).so,)

lib$(1).a : $$($(2)_objs) $$($(2)_c_objs) $$($(2)_lib_libnames)
	$(AR) rcs $$@ $$^
lib$(1).so : $$($(2)_objs) $$($(2)_c_objs) $$($(2)_lib_libnames_shared) $$($(2)_lib_libnames)
	$(LINK) -shared -o $$@ $(if $(filter Darwin,$(shell uname -s)),-install_name $(install_libs_dir)/$$@) $$^ $$($(2)_lib_libnames) $(LIBS)

$(2)_junk += lib$(1).a
$(2)_junk += $$(if $$($(2)_install_shared_lib),lib$(1).so,)

# Build unit tests

$(2)_test_objs      := $$(patsubst %.cc, %.o, $$($(2)_test_srcs))
$(2)_test_deps      := $$(patsubst %.o, %.d, $$($(2)_test_objs))
$(2)_test_exes      := $$(patsubst %.t.cc, %-utst, $$($(2)_test_srcs))
$(2)_test_outs      := $$(patsubst %, %.out, $$($(2)_test_exes))
$(2)_test_libs      := $(1) $$($(2)_reverse_deps) utst
$(2)_test_libnames  := $$(patsubst %, lib%.a, $$($(2)_test_libs))
$(2)_test_libarg    := $$(patsubst %, -l%, $$($(2)_test_libs))

$$($(2)_test_objs) : %.o : %.cc
	$(COMPILE) -c $$<

$$($(2)_test_exes) : %-utst : %.t.o $$($(2)_test_libnames)
	$(LINK) -o $$@ $$< $$($(2)_test_libnames) $(LIBS)

$(2)_deps += $$($(2)_test_deps)
$(2)_junk += \
	$$($(2)_test_objs) $$($(2)_test_deps) \
  $$($(2)_test_exes) *.junk-dat

# Run unit tests

$$($(2)_test_outs) : %.out : %
	$(RUN) $(RUNFLAGS) ./$$< default | tee $$@

$(2)_junk += $$($(2)_test_outs)

# Build programs

$(2)_prog_objs      := $$(patsubst %.cc, %.o, $$($(2)_prog_srcs))
$(2)_prog_deps      := $$(patsubst %.o, %.d, $$($(2)_prog_objs))
$(2)_prog_exes      := $$(patsubst %.cc, %, $$($(2)_prog_srcs))
$(2)_prog_libs      := $(1) $$($(2)_reverse_deps)
$(2)_prog_libnames  := $$(patsubst %, lib%.a, $$($(2)_prog_libs))
$(2)_prog_libarg    := $$(patsubst %, -l%, $$($(2)_prog_libs))

$$($(2)_prog_objs) : %.o : %.cc
	$(COMPILE) -c $$<

$$($(2)_prog_exes) : % : %.o $$($(2)_prog_libnames)
	$(LINK) -o $$@ $$< $$($(2)_prog_libnames) $(LIBS)

$(2)_deps += $$($(2)_prog_deps)
$(2)_junk += $$($(2)_prog_objs) $$($(2)_prog_deps) $$($(2)_prog_exes)

# Build programs which will be installed

$(2)_install_prog_objs := $$(patsubst %.cc, %.o, $$($(2)_install_prog_srcs))
$(2)_install_prog_deps := $$(patsubst %.o, %.d, $$($(2)_install_prog_objs))
$(2)_install_prog_exes := $$(patsubst %.cc, %, $$($(2)_install_prog_srcs))

$$($(2)_install_prog_objs) : %.o : %.cc $$($(2)_gen_hdrs)
	$(COMPILE) -c $$<

$$($(2)_install_prog_exes) : % : %.o $$($(2)_prog_libnames)
	$(LINK) -o $$@ $$< $$($(2)_prog_libnames) $(LIBS)

$(2)_deps += $$($(2)_install_prog_deps)
$(2)_junk += \
  $$($(2)_install_prog_objs) $$($(2)_install_prog_deps) \
  $$($(2)_install_prog_exes)

# Subproject specific targets

all-$(1) : lib$(1).a $$($(2)_install_prog_exes)

check-$(1) : $$($(2)_test_outs)
	echo; grep -h -e'Unit Tests' -e'FAILED' -e'Segementation' $$^; echo

clean-$(1) :
	rm -rf $$($(2)_junk)

.PHONY : all-$(1) check-$(1) clean-$(1)

# Update running variables

libs += lib$(1).a
objs += $$($(2)_objs)
srcs += $$(addprefix $(src_dir)/$(1)/, $$($(2)_srcs)) 
hdrs += $$(addprefix $(src_dir)/$(1)/, $$($(2)_hdrs)) $$($(2)_gen_hdrs)
junk += $$($(2)_junk)
deps += $$($(2)_deps)

test_outs += $$($(2)_test_outs)

install_config_hdrs += $$(if $$($(2)_install_config_hdr),$(1),)
install_hdrs += $$(addprefix $(src_dir)/$(1)/, $$($(2)_install_hdrs))
install_libs += $$(if $$($(2)_install_lib),lib$(1).a,)
install_libs += $$(if $$($(2)_install_shared_lib),lib$(1).so,)
install_exes += $$($(2)_install_prog_exes)
install_pcs  += $$(if $$($(2)_install_lib),riscv-$(1).pc,)

endef

# Iterate over the subprojects and call the template for each one

$(foreach sproj,$(sprojs_enabled), \
  $(eval $(call subproject_template,$(sproj),$(subst -,_,$(sproj)))))

#-------------------------------------------------------------------------
# Autodependency files
#-------------------------------------------------------------------------

-include $(deps)

deps : $(deps)
.PHONY : deps

#-------------------------------------------------------------------------
# Check
#-------------------------------------------------------------------------

bintest_outs = $(bintests:=.out)
junk += $(bintest_outs)
%.out: % all
	./$* < /dev/null 2>&1 | tee $@

check-cpp : $(test_outs)
	@echo
	! grep -h -e'Unit Tests' -e'FAILED' -e'Segmentation' $^ < /dev/null
	@echo

check-bin : $(bintest_outs)
	! tail -n 1 $^ < /dev/null 2>&1 | grep FAILED

check : check-cpp check-bin

.PHONY : check

#-------------------------------------------------------------------------
# Installation
#-------------------------------------------------------------------------

install-config-hdrs : config.h
	$(MKINSTALLDIRS) $(install_hdrs_dir)
	for dir in $(install_config_hdrs); \
  do \
		$(MKINSTALLDIRS) $(install_hdrs_dir)/$$dir; \
    $(INSTALL_HDR) $< $(install_hdrs_dir)/$$dir; \
  done

install-hdrs : $(install_hdrs)
	$(MKINSTALLDIRS) $(install_hdrs_dir)
	for file in $(subst $(src_dir)/,,$^); \
  do \
		$(MKINSTALLDIRS) $(install_hdrs_dir)/`dirname $$file`; \
    $(INSTALL_HDR) $(src_dir)/$$file $(install_hdrs_dir)/`dirname $$file`; \
  done

install-libs : $(install_libs)
	$(MKINSTALLDIRS) $(install_libs_dir)
	for file in $^; \
  do \
    $(INSTALL_LIB) $$file $(install_libs_dir); \
  done

install-exes : $(install_exes)
	$(MKINSTALLDIRS) $(install_exes_dir)
	for file in $^; \
  do \
    $(INSTALL_EXE) $$file $(install_exes_dir); \
  done

install-pc : $(install_pcs)
	$(MKINSTALLDIRS) $(install_libs_dir)/pkgconfig/
	for file in $^; \
  do \
    $(INSTALL_HDR) $$file $(install_libs_dir)/pkgconfig/; \
  done

install : install-hdrs install-config-hdrs install-libs install-exes install-pc

.PHONY : install install-hdrs install-config-hdrs install-libs install-exes

#-------------------------------------------------------------------------
# Regenerate configure information
#-------------------------------------------------------------------------

config.status : $(src_dir)/configure
	./config.status --recheck

sprojs_mk_in = \
  $(join $(addprefix $(src_dir)/, $(sprojs_enabled)), \
         $(patsubst  %, /%.mk.in, $(sprojs_enabled)))

Makefile : $(src_dir)/Makefile.in $(sprojs_mk_in) config.status
	./config.status

dist_junk += config.status config.h Makefile config.log

#-------------------------------------------------------------------------
# Distribution
#-------------------------------------------------------------------------
# The distribution tarball is named project-ver.tar.gz and it includes
# both enabled and disabled subprojects. 

dist_files = \
  $(sprojs) \
  README \
  style-guide.txt \
  mcppbs-uguide.txt \
  scripts \
  configure.ac \
  aclocal.m4 \
  configure \
  config.h.in \
  Makefile.in \

dist_dir := $(project_name)-$(project_ver)
dist_tgz := $(project_name)-$(project_ver).tar.gz

# Notice that when we make the distribution we rewrite the configure.ac
# script with the current version and we rerun autoconf in the new
# source directory so that the distribution will have the proper version
# information. We also rewrite the "Version : " line in the README.

dist :
	rm -rf $(dist_dir)
	mkdir $(dist_dir)
	tar -C $(src_dir) -cf - $(dist_files) | tar -C $(dist_dir) -xpf -
	sed -i.bak 's/^\(# Version :\).*/\1 $(project_ver)/' $(dist_dir)/README
	sed -i.bak 's/\( proj_version,\).*/\1 [$(project_ver)])/' $(dist_dir)/configure.ac
	cd $(dist_dir) && \
    autoconf && autoheader && \
    rm -rf autom4te.cache configure.ac.bak README.bak
	tar -czvf $(dist_tgz) $(dist_dir)
	rm -rf $(dist_dir)

# You can use the distcheck target to try untarring the distribution and
# then running configure, make, make check, and make distclean. A
# "directory is not empty" error means distclean is not removing
# everything.

distcheck : dist
	rm -rf $(dist_dir)
	tar -xzvf $(dist_tgz)
	mkdir -p $(dist_dir)/build
	cd $(dist_dir)/build; ../configure; make; make check; make distclean
	rm -rf $(dist_dir)

junk += $(project_name)-*.tar.gz

.PHONY : dist distcheck

#-------------------------------------------------------------------------
# Default
#-------------------------------------------------------------------------

all : $(install_hdrs) $(install_libs) $(install_exes)
.PHONY : all

#-------------------------------------------------------------------------
# Makefile debugging
#-------------------------------------------------------------------------
# This handy rule will display the contents of any make variable by
# using the target debug-<varname>. So for example, make debug-junk will
# display the contents of the junk variable.

debug-% :
	@echo $* = $($*)

#-------------------------------------------------------------------------
# Clean up junk
#-------------------------------------------------------------------------

clean :
	rm -rf *~ \#* $(junk)

distclean :
	rm -rf *~ \#* $(junk) $(dist_junk)

.PHONY : clean distclean
