# Copyright (C) 2026 Shanghai Symthosm(Xian Dao Zhi Jue) Intelligent Technology Co., Ltd.
#                   上海先道智觉科技有限责任公司
# Author: Lu Haoran <luhaoran1981@icloud.com>
#         芦浩然
# SPDX-License-Identifier: GPL-2.0-or-later

VERSION       = 0.2.2
SOVER_MAJOR   = 0

CC            = gcc
AR            = ar

# Compiler/linker flags. Distro build systems (Fedora %set_build_flags, Debian
# dpkg-buildflags) inject hardening flags (-fPIE/-pie, fortify, relro) via the
# environment; use ?= so those take precedence. Our own mandatory flags live in
# CPPFLAGS (appended), so they survive any external CFLAGS override.
CFLAGS   ?= -Wall -Wextra -std=c11 -O2 -g
LDFLAGS  ?=
CPPFLAGS ?=

CPPFLAGS += -DTASKHEALTH_VERSION=\"$(VERSION)\" \
            -I $(SRCDIR) -I $(DAEMONDIR) -I $(INCDIR)
PTHREAD   = -pthread
# Library objects must be position-independent for the shared library.
PIC_FLAGS = -fPIC

COMPILE = $(CC) $(CPPFLAGS) $(CFLAGS) $(PTHREAD)
LINK    = $(CC) $(CFLAGS) $(PTHREAD)

prefix         = /usr/local
bindir         = $(prefix)/bin
sbindir        = $(prefix)/sbin
libdir         = $(prefix)/lib
includedir     = $(prefix)/include
pkgconfigdir   = $(libdir)/pkgconfig
mandir         = $(prefix)/share/man
systemdunitdir = $(prefix)/lib/systemd/system

SRCDIR        = src
INCDIR        = include
TESTDIR       = test
DEMODIR       = demo
DAEMONDIR     = daemon

LIBST         = libtaskhealth.a
LIBSO         = libtaskhealth.so
LIBSONAME     = $(LIBSO).$(SOVER_MAJOR)
LIBSOREAL     = $(LIBSO).$(VERSION)

LIB_OBJS      = $(SRCDIR)/taskhealth.o $(SRCDIR)/taskhealth_mutex.o
PUBLIC_HEADERS = $(SRCDIR)/taskhealth.h $(SRCDIR)/taskhealth_mutex.h
PROTOCOL_HEADER = $(INCDIR)/taskhealth/protocol.h
HEADERS       = $(PUBLIC_HEADERS) $(PROTOCOL_HEADER)
DAEMON_SRCS   = $(DAEMONDIR)/main.c $(DAEMONDIR)/server.c $(DAEMONDIR)/registry.c \
                $(DAEMONDIR)/watchdog.c $(DAEMONDIR)/probe.c $(DAEMONDIR)/alert.c
DAEMON_OBJS   = $(DAEMON_SRCS:.c=.o)
TARNAME       = taskhealth-$(VERSION).tar.gz

# default: static + shared library + daemon
all: $(LIBST) $(LIBSONAME) taskhealthd

$(LIBST): $(LIB_OBJS)
	$(AR) rcs $@ $^

$(LIBSONAME): $(LIB_OBJS)
	$(CC) -shared -Wl,-soname,$(LIBSONAME) -o $(LIBSOREAL) $^ $(LDFLAGS) $(PTHREAD)
	ln -sf $(LIBSOREAL) $(LIBSONAME)
	ln -sf $(LIBSONAME) $(LIBSO)

taskhealthd: $(DAEMON_OBJS)
	$(LINK) -o $@ $^ $(LDFLAGS)

$(SRCDIR)/taskhealth.o: $(SRCDIR)/taskhealth.c $(SRCDIR)/taskhealth.h $(PROTOCOL_HEADER)
	$(COMPILE) $(PIC_FLAGS) -c -o $@ $<

$(SRCDIR)/taskhealth_mutex.o: $(SRCDIR)/taskhealth_mutex.c $(SRCDIR)/taskhealth_mutex.h $(SRCDIR)/taskhealth_internal.h
	$(COMPILE) $(PIC_FLAGS) -c -o $@ $<

$(DAEMONDIR)/%.o: $(DAEMONDIR)/%.c
	$(COMPILE) -c -o $@ $<

# demo program
demo: $(DEMODIR)/demo.c $(LIBST) $(HEADERS)
	$(LINK) -o $(DEMODIR)/$@ $< -L. -ltaskhealth $(LDFLAGS)

# unit test (client library API only; needs a running taskhealthd
# on the default socket for the register round-trip tests)
test: $(TESTDIR)/test.c $(LIBST) $(HEADERS)
	$(LINK) -o $(TESTDIR)/$@ $< -L. -ltaskhealth $(LDFLAGS)

check: test
	LD_LIBRARY_PATH=.:$$LD_LIBRARY_PATH $(TESTDIR)/test

# functional daemon tests: connect to a running taskhealthd over the raw
# protocol (see test/run_daemon_tests.sh for the start/assert/stop harness)
test-daemon: $(TESTDIR)/test_daemon.c $(PROTOCOL_HEADER)
	$(COMPILE) -o $(TESTDIR)/test_daemon $< $(LDFLAGS)

check-daemon: all-full test-daemon
	./$(TESTDIR)/run_daemon_tests.sh

# staged install of the public headers + shared lib (and daemon/demo) under
# test/stage/.  The black-box integration test compiles, links and runs against
# these installed artifacts — not the in-tree sources — so it exercises exactly
# what a downstream application consumes (the public interface).
STAGE_DIR = $(TESTDIR)/stage
STAGE_INC = $(STAGE_DIR)/usr/include
STAGE_LIB = $(STAGE_DIR)/usr/lib

stage: all-full
	rm -rf $(STAGE_DIR)
	$(MAKE) install DESTDIR=$(abspath $(STAGE_DIR)) prefix=/usr

# integration tests: drive the real client library against a running taskhealthd,
# consuming ONLY the installed public interface (see run_integration_tests.sh)
test-integration: stage $(TESTDIR)/test_integration.c
	$(LINK) -o $(TESTDIR)/test_integration $(TESTDIR)/test_integration.c \
		-I $(STAGE_INC) -L $(STAGE_LIB) -ltaskhealth $(LDFLAGS)

check-integration: test-integration
	./$(TESTDIR)/run_integration_tests.sh

# standalone unit tests: no daemon required — socket syscalls are stubbed out
# via linker --wrap (see test/socket_stub.c)
test-unit: $(TESTDIR)/test_unit.c $(TESTDIR)/socket_stub.c $(LIBST) $(HEADERS)
	$(LINK) -o $(TESTDIR)/test_unit $(TESTDIR)/test_unit.c $(TESTDIR)/socket_stub.c \
	$(LIBST) \
		-Wl,--wrap=socket -Wl,--wrap=connect -Wl,--wrap=setsockopt \
		-Wl,--wrap=sendmsg -Wl,--wrap=recv -Wl,--wrap=__recv_chk \
		-Wl,--wrap=close \
		$(LDFLAGS)

check-unit: test-unit
	./$(TESTDIR)/test_unit

# code coverage
COV_DIR   = coverage
COV_CFLAGS = -Wall -Wextra -std=c11 -pthread -O0 -g -fPIC \
             -DTASKHEALTH_VERSION=\"$(VERSION)\" -I $(SRCDIR) --coverage

cov: clean
	$(CC) $(COV_CFLAGS) -c -o $(SRCDIR)/taskhealth.o $(SRCDIR)/taskhealth.c
	$(CC) $(COV_CFLAGS) -c -o $(SRCDIR)/taskhealth_mutex.o $(SRCDIR)/taskhealth_mutex.c
	$(AR) rcs $(LIBST) $(LIB_OBJS)
	$(CC) $(COV_CFLAGS) --coverage -o $(TESTDIR)/test $(TESTDIR)/test.c \
		-L. -ltaskhealth $(LDFLAGS)
	LD_LIBRARY_PATH=.:$$LD_LIBRARY_PATH $(TESTDIR)/test 2>/dev/null
	gcov -r $(SRCDIR)/taskhealth.c $(SRCDIR)/taskhealth_mutex.c
	@echo "  => taskhealth.c.gcov  taskhealth_mutex.c.gcov"

all-full: $(LIBST) $(LIBSONAME) taskhealthd demo taskhealth.pc

clean:
	rm -f $(LIB_OBJS) $(DAEMON_OBJS) $(LIBST) $(LIBSOREAL) $(LIBSONAME) $(LIBSO) \
		taskhealthd taskhealth.pc \
		$(DEMODIR)/demo $(TESTDIR)/test $(TESTDIR)/test_daemon \
		$(TESTDIR)/test_integration $(TESTDIR)/test_unit \
		*.gcno *.gcda *.gcov $(SRCDIR)/*.gcno $(SRCDIR)/*.gcda \
		$(TESTDIR)/*.gcno $(TESTDIR)/*.gcda \
		$(DAEMONDIR)/*.gcno $(DAEMONDIR)/*.gcda
	rm -rf $(STAGE_DIR)

install: $(LIBST) $(LIBSONAME) taskhealthd demo
	install -d $(DESTDIR)$(libdir) $(DESTDIR)$(includedir) \
		$(DESTDIR)$(includedir)/taskhealth $(DESTDIR)$(bindir) \
		$(DESTDIR)$(pkgconfigdir) $(DESTDIR)$(sbindir) \
		$(DESTDIR)$(mandir)/man1 $(DESTDIR)$(mandir)/man8 \
		$(DESTDIR)$(mandir)/man7 $(DESTDIR)$(systemdunitdir)
	install -m 644 $(LIBST) $(DESTDIR)$(libdir)
	install -m 755 $(LIBSOREAL) $(DESTDIR)$(libdir)
	ln -sf $(LIBSOREAL) $(DESTDIR)$(libdir)/$(LIBSONAME)
	ln -sf $(LIBSONAME) $(DESTDIR)$(libdir)/$(LIBSO)
	install -m 644 $(PUBLIC_HEADERS) $(DESTDIR)$(includedir)
	install -m 644 $(PROTOCOL_HEADER) $(DESTDIR)$(includedir)/taskhealth
	install -m 644 taskhealth.pc $(DESTDIR)$(pkgconfigdir)
	install -m 755 taskhealthd $(DESTDIR)$(sbindir)
	install -m 755 $(DEMODIR)/demo $(DESTDIR)$(bindir)/taskhealth-demo
	install -m 644 man/taskhealthd.8 $(DESTDIR)$(mandir)/man8/taskhealthd.8
	install -m 644 man/taskhealth-demo.1 $(DESTDIR)$(mandir)/man1/taskhealth-demo.1
	install -m 644 man/taskhealth.7 $(DESTDIR)$(mandir)/man7/taskhealth.7
	install -m 644 contrib/systemd/taskhealthd.service \
		$(DESTDIR)$(systemdunitdir)/taskhealthd.service

# pkg-config file (generated at build time)
taskhealth.pc: taskhealth.pc.in
	sed -e 's|@prefix@|$(prefix)|g' \
	    -e 's|@libdir@|$(libdir)|g' \
	    -e 's|@includedir@|$(includedir)|g' \
	    -e 's|@VERSION@|$(VERSION)|g' \
	    $< > $@

# source tarball (for rpm build)
dist: clean
	mkdir -p taskhealth-$(VERSION)
	cp -r $(SRCDIR) $(INCDIR) $(DEMODIR) $(DAEMONDIR) $(TESTDIR) debian contrib man Makefile \
		taskhealth.pc.in LICENSE LICENSE.MIT README.md \
		taskhealth-$(VERSION)/
	tar czf $(TARNAME) taskhealth-$(VERSION)
	rm -rf taskhealth-$(VERSION)

# Debian package
deb:
	debuild -b -uc -us

# RPM package
rpm: dist
	rpmbuild -ta $(TARNAME)

.PHONY: all all-full clean check check-daemon check-integration check-unit cov install stage demo test test-daemon test-integration test-unit dist deb rpm
