# git for MontaukOS
#
# Upstream: https://github.com/git/git (submodule at upstream/, pinned to
# v2.39.5).
#
# git is built as a plain system program (0:/os/git.elf), not an app bundle:
# it is a command-line tool driven from the terminal.
#
# CONFIGURATION
#   The build must not pick up any host defaults. git derives its platform
#   settings from `uname -s` through config.mak.uname, which on this host
#   would answer "Linux" and switch on clock_gettime, getdelim, /dev/tty and
#   friends -- none of which the Montauk libc has. Every uname_* variable is
#   therefore overridden to a name config.mak.uname does not know, leaving
#   the knobs below as the only configuration.
#
#   The NO_* list is not "things git can live without" so much as an
#   inventory of what MontaukOS does not provide yet: no threads, no
#   sockets, no iconv, no regex, no file-backed mmap. git ships portable
#   fallbacks for all of them (compat/), which is what makes this port
#   tractable at all.
#
# COMPAT SHIMS (compat/) supply the headers and functions the Montauk libc
# has no equivalent of -- see compat/README for what each one does and, more
# importantly, what it does not.

UPSTREAM := upstream
COMPAT   := $(CURDIR)/compat

PORT_NAME := git

include ../../mk/port.mk

GIT_BIN     := $(UPSTREAM)/git
GIT_INSTALL := $(MONTAUKOS)/programs/bin/os

# Where git's compiled-in paths point on the target. programs/bin/os is the
# staging directory for 0:/os, so GIT_PREFIX and GIT_STAGE are two names for
# the same place -- one as the OS sees it, one as the build host does.
# RUNTIME_PREFIX is off (it wants /proc/self/exe), so these are baked in:
#
#   $(GIT_PREFIX)/share/git-core/templates   `git init` skeleton
#   $(GIT_PREFIX)/etc/gitconfig              system-wide config
#   $(GIT_PREFIX)/libexec/git-core           external subcommands (none yet;
#                                            every command in this build is a
#                                            builtin, which is what makes git
#                                            usable without fork/exec)
GIT_PREFIX := 0:/os/git
GIT_STAGE  := $(GIT_INSTALL)/git

# `git version` string. Without an override GIT-VERSION-GEN runs `git
# describe` on the checkout and, because the patch series is applied in the
# working tree, reports "2.39.5.dirty" -- true but unhelpful. Name the port
# instead: the tree really is patched, and this says by whom.
GIT_UPSTREAM_TAG := $(shell git -C $(UPSTREAM) describe --tags --abbrev=0 2>/dev/null)
GIT_VERSION_STR  := $(patsubst v%,%,$(GIT_UPSTREAM_TAG))-montauk

# ==== Toolchain flags =================================================
# CFLAGS/LDFLAGS/EXTLIBS must be passed on git's make COMMAND LINE, not
# through the environment: its Makefile assigns `CFLAGS = -g -O2 -Wall`
# outright, and a plain assignment beats an environment value. Only a
# command-line assignment wins. Overriding them is safe -- git keeps its own
# required includes and defines in BASIC_CFLAGS, which is separate and is
# appended to ALL_CFLAGS after ours.

GIT_CFLAGS := \
    -std=gnu99 \
    $(PORT_CFLAGS_COMMON) \
    -Wno-unused-parameter \
    -Wno-sign-compare \
    -I $(COMPAT) \
    -include $(COMPAT)/montauk-paths.h \
    -include $(COMPAT)/montauk-ownership.h \
    -isystem $(shell $(CC) -print-file-name=include)

# crt1.o carries _start; -nostdlib (from PORT_LDFLAGS_COMMON) drops the
# driver's own startup files, so name them here.
GIT_CRT := \
    $(SDK_LIB)/libc/crt1.o \
    $(SDK_LIB)/libc/crti.o \
    $(SDK_LIB)/libc/crtn.o

COMPAT_LIB := $(CURDIR)/compat/libgitcompat.a

GIT_LDFLAGS := $(PORT_LDFLAGS_COMMON) $(GIT_CRT)

# EXTLIBS lands on the link line after the objects, which is where the compat
# archive, zlib and libc have to sit for the static link to resolve. Naming
# libz.a here rather than letting ZLIB_PATH add -lz is deliberate: overriding
# EXTLIBS drops git's own `EXTLIBS += -lz` append. ZLIB_PATH is still set, for
# the -I it contributes to BASIC_CFLAGS.
GIT_EXTLIBS := $(COMPAT_LIB) $(PORT_SYSROOT_LIB)/libz.a $(LIBC_A)

# ==== Build configuration =============================================
#
# DEFAULT_PAGER=cat is load-bearing, not a preference: git_pager() treats the
# literal "cat" as "no pager at all" and skips the fork entirely (pager.c).
# Left at its default it would try to spawn `less` on every `git log` and
# fail, since MontaukOS cannot fork.
#
# OBJECT_CREATION_USES_RENAMES=1 because there are no hard links. Every loose
# object is written to a temp file and then finalized; by default git tries
# link()+unlink() first and only falls back to rename() when link fails, so
# without this every single object write makes a doomed syscall and travels an
# error path. The knob picks rename() up front, which is what happens anyway.
#
# DEFAULT_EDITOR is deliberately NOT set. With an empty environment TERM is
# unset, so git considers the terminal dumb, git_editor() returns NULL, and
# `git commit` with no -m says "terminal is dumb, but EDITOR unset. Please
# supply the message using either -m or -F option." Naming an editor here
# would replace that with a failed spawn.

GIT_ARGS := \
    CC=$(CC) \
    AR=$(AR) \
    CFLAGS="$(GIT_CFLAGS)" \
    LDFLAGS="$(GIT_LDFLAGS)" \
    EXTLIBS="$(GIT_EXTLIBS)" \
    uname_S=MontaukOS \
    uname_M=x86_64 \
    uname_O=MontaukOS \
    uname_R=1 \
    uname_P=x86_64 \
    uname_V=1 \
    prefix=$(GIT_PREFIX) \
    GIT_VERSION=$(GIT_VERSION_STR) \
    ZLIB_PATH=$(PORT_SYSROOT) \
    BLK_SHA1=1 \
    NO_CURL=1 \
    NO_EXPAT=1 \
    NO_OPENSSL=1 \
    NO_ICONV=1 \
    NO_GETTEXT=1 \
    NO_PTHREADS=1 \
    NO_PERL=1 \
    NO_PYTHON=1 \
    NO_TCLTK=1 \
    NO_REGEX=1 \
    NO_MMAP=1 \
    NO_IPV6=1 \
    NO_INET_NTOP=1 \
    NO_INET_PTON=1 \
    NO_HSTRERROR=1 \
    NO_SYS_SELECT_H=1 \
    NO_UNIX_SOCKETS=1 \
    NO_SETITIMER=1 \
    NO_STRUCT_ITIMERVAL=1 \
    NO_LIBGEN_H=1 \
    NO_MEMMEM=1 \
    NO_STRCASESTR=1 \
    NO_STRLCPY=1 \
    NO_STRTOUMAX=1 \
    NO_MKDTEMP=1 \
    NO_INITGROUPS=1 \
    NO_GECOS_IN_PWENT=1 \
    NO_NSEC=1 \
    NO_INSTALL_HARDLINKS=1 \
    NO_TRUSTABLE_FILEMODE=1 \
    NO_SYMLINK_HEAD=1 \
    INTERNAL_QSORT=1 \
    OBJECT_CREATION_USES_RENAMES=1 \
    DEFAULT_PAGER=cat

GIT_MAKE := MAKEFLAGS= $(MAKE)

.PHONY: all clean git-build install uninstall

all: git-build

$(COMPAT_LIB): $(wildcard compat/*.c) $(wildcard compat/*.h)
	$(CC) $(GIT_CFLAGS) -c compat/montauk-compat.c -o compat/montauk-compat.o
	$(AR) rcs $@ compat/montauk-compat.o
	@echo "Built: $@"

# The templates/ sub-make builds the skeleton `git init` copies into a new
# repository (info/exclude, the sample hooks, the description file) into
# templates/blt. Without it every `git init` warns that templates are
# missing. It is a subdirectory build, not a target of the top Makefile, so
# it is invoked directly -- `make templates` up there just matches the
# directory name and does nothing.
git-build: $(COMPAT_LIB)
	@$(call ns_patch,$(UPSTREAM))
	@# git's link rule knows nothing about EXTLIBS or the crt objects, so a
	@# rebuilt compat archive -- or a rebuilt libc, which is the one that
	@# actually catches people out -- would otherwise leave the old binary
	@# in place. Drop it and let the sub-make link again.
	@for lib in $(GIT_EXTLIBS) $(GIT_CRT); do \
	    if [ -f $(GIT_BIN) ] && [ "$$lib" -nt $(GIT_BIN) ]; then \
	        echo "   RELINK: $$(basename $$lib) is newer than $(GIT_BIN)"; \
	        rm -f $(GIT_BIN); \
	    fi; \
	done
	$(GIT_MAKE) -C $(UPSTREAM) $(GIT_ARGS) git
	$(GIT_MAKE) -C $(UPSTREAM)/templates
	@test -f $(GIT_BIN) || { echo "git: expected $(GIT_BIN)" >&2; exit 1; }
	@echo "Built: $(GIT_BIN) ($$(stat -c%s $(GIT_BIN)) bytes)"

# Installed as a system program, not an app bundle: git is a command line
# tool with no window. It carries no manifest and no .port marker, so
# remember to uninstall before cutting a release ISO.
#
# The binary is STRIPPED on the way in -- the debug info is five sixths of
# its size (17 MB -> 3 MB), and it all has to fit in the ramdisk.
install: git-build
	mkdir -p $(GIT_INSTALL)
	$(TOOLCHAIN_PREFIX)strip -o $(GIT_INSTALL)/git.elf $(GIT_BIN)
	mkdir -p $(GIT_STAGE)/share/git-core
	rm -rf $(GIT_STAGE)/share/git-core/templates
	cp -rL $(UPSTREAM)/templates/blt $(GIT_STAGE)/share/git-core/templates
	@echo "installed: 0:/os/git.elf + $(GIT_PREFIX)/share/git-core/templates"

uninstall:
	rm -rf $(GIT_INSTALL)/git.elf $(GIT_STAGE)

clean:
	@$(GIT_MAKE) -C $(UPSTREAM) $(GIT_ARGS) clean >/dev/null 2>&1 || true
	@git -C $(UPSTREAM) checkout -- . 2>/dev/null || true
	rm -f $(COMPAT_LIB) compat/*.o
