# Makefile for use with GNU make

THIS_MAKEFILE_DIR:=$(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))

CONFIGFILE ?= ../config.mk
$(info Using config file ${CONFIGFILE})

CONFIGFILEPATH:=$(shell ls ${CONFIGFILE} >/dev/null 2>/dev/null && realpath ${CONFIGFILE})
ifeq ($(CONFIGFILEPATH),)
  ifeq ($(shell dirname ${CONFIGFILE}),.)
    CONFIGFILEPATH:=$(shell ls ../${CONFIGFILE} >/dev/null 2>/dev/null && realpath ../${CONFIGFILE})
    ifneq ($(CONFIGFILEPATH),)
      $(info Config file ${CONFIGFILE} not found; using ../${CONFIGFILE})
    endif
  endif
endif

ifeq ($(CONFIGFILEPATH),)
  $(error Config file ${CONFIGFILE} not found)
else
  include ${CONFIGFILEPATH}
endif

CC ?= cc

LIB_SUFFIX=
LIBZSV_L=-lzsv${LIB_SUFFIX}
LIBZSV_A=libzsv${LIB_SUFFIX}.a
LIBZSV_BUILD=${BUILD_DIR}/lib/${LIBZSV_A}
LIBZSV_BUILD_DIR=${BUILD_DIR}/lib

# if we are incorporating these commands into another project, we might
# want to use different values for ZSV_TEMPFILE_SUFFIX and ZSV_CACHE_PREFIX
# to do: set these in the configure script
ifneq ($(ZSV_TEMPFILE_SUFFIX),)
  CFLAGS+= -DZSV_TEMPFILE_SUFFIX='${ZSV_TEMPFILE_SUFFIX}'
endif
ifneq ($(ZSV_CACHE_PREFIX),)
  CFLAGS+= -DZSV_CACHE_PREFIX='${ZSV_CACHE_PREFIX}'
endif

CFLAGS+=-fsigned-char
ifneq ($(WIN),)
  CFLAGS+= -Wno-incompatible-pointer-types
  LIBS+=-lShlwapi
endif

ifneq ($(ZSV_IS_PROP_FILE_HANDLER),)
	CFLAGS+=-DZSV_IS_PROP_FILE_HANDLER=${ZSV_IS_PROP_FILE_HANDLER}
endif
ifneq ($(ZSV_IS_PROP_FILE_DEPTH),)
	CFLAGS+=-DZSV_IS_PROP_FILE_DEPTH=${ZSV_IS_PROP_FILE_DEPTH}
endif

ifeq ($(HAVE_NEON),1)
  CFLAGS+= -DZSV_HAVE_NEON
endif

ifeq ($(DEFAULT_PARSER_FAST),1)
  CFLAGS+= -DZSV_DEFAULT_PARSER_FAST
endif

ifeq ($(ZSV_NO_ONLY_CRLF),1)
  CFLAGS+= -DZSV_NO_ONLY_CRLF
endif

# ZSV_NO_TOON: build without any TOON features (no 2toon command, no compare
# --toon, no 2json --from-toon / 2toon --from-json, and no toonwriter/json2toon
# objects). Guards in cli.c/compare.c/2json.c and the link rules key off this.
ifeq ($(ZSV_NO_TOON),1)
  CFLAGS+= -DZSV_NO_TOON
endif

# ZSV_NO_TAB_AUTO_RECOGNIZE: do not default to a tab delimiter for .tab input
# files (.tsv is always recognized). Guard in app/utils/prop.c keys off this.
ifeq ($(ZSV_NO_TAB_AUTO_RECOGNIZE),1)
  CFLAGS+= -DZSV_NO_TAB_AUTO_RECOGNIZE
endif

DEBUG=0
WIN=
ifeq ($(WIN),)
  # MINGW = 1 in config.mk (configure's host check; not the environment, and a CC given
  # on the command line does not undo it), or a w64 compiler
  WIN=$(if $(and $(filter file,$(origin MINGW)),$(filter 1,$(MINGW))),1,$(if $(findstring w64,$(CC)),1,0))
endif

MORE_OBJECTS=
NO_LTO=0

ifeq ($(DEBUG),1)
  NO_LTO=1
  DBG_SUBDIR+=dbg
else
  DBG_SUBDIR+=rel
endif

ifeq ($(NO_LTO),1)
  CFLAGS+= -fno-lto
else
  CFLAGS+=${CFLAGS_LTO}
  LDFLAGS_OPT+= ${LDFLAGS_OPT_LTO}
endif

NO_STDIN=
ifeq ($(NO_STDIN),1)
  CFLAGS+= -DNO_STDIN
endif

CFLAGS+= ${CFLAGS_PIC}

CONFIGURE_HOST?=
ifeq ($(CONFIGURE_HOST),)
ifneq ($(HOST),)
  CONFIGURE_HOST=$(HOST)
endif
endif

EXE=
ifeq ($(WIN),0)
  BUILD_SUBDIR=$(shell uname)/${DBG_SUBDIR}
  ifneq ($(findstring emcc,$(CC)),) # emcc
    NO_THREADING ?= 1
    NO_PLAYGROUND ?= 1
    EXE=.em.js
    LDFLAGS += -s ALLOW_MEMORY_GROWTH=1 -s FORCE_FILESYSTEM=1 -s WASM_BIGINT=1
    ifeq ($(DEBUG),1)
      CFLAGS += -DWASM_SIMD_COMPAT_SLOW
      LDFLAGS += -s ASSERTIONS=1 -s STACK_OVERFLOW_CHECK=1 -s SAFE_HEAP=1
    endif
    ifeq ($(TESTING),1) # make test
      NODE_EXISTS := $(shell command -v node 2>/dev/null)
      ifeq ($(NODE_EXISTS),)
        $(error "NodeJS not found. Install NodeJS to run wasm tests.")
      endif
      LDFLAGS += -s ENVIRONMENT=node
      LDFLAGS += -s INVOKE_RUN=1 -s MAIN_MODULE=2
      LDFLAGS += -s EXIT_RUNTIME=1 -s NODERAWFS=1
      LDFLAGS += -s STACK_SIZE=128KB # for 2tsv test
    else ifeq ($(NO_PLAYGROUND),0) # wasm playground
      LDFLAGS += -s ENVIRONMENT=web
      LDFLAGS += -s EXPORTED_RUNTIME_METHODS="['callMain']" -s INVOKE_RUN=0 -s MAIN_MODULE=2
    else
      CFLAGS += -DNO_PLAYGROUND
      LDFLAGS += -s EXPORTED_FUNCTIONS="['_free','_malloc']"
      LDFLAGS += -s EXPORTED_RUNTIME_METHODS="['setValue','stringToUTF8Array','lengthBytesUTF8','writeArrayToMemory']"
      LDFLAGS += -s RESERVED_FUNCTION_POINTERS=4 -s MAIN_MODULE=2 -lidbfs.js
    endif
    ifneq ($(NO_THREADING),1)
      CFLAGS += -pthread
      LDFLAGS += -s USE_PTHREADS=1 -s PTHREAD_POOL_SIZE=2
    endif
  endif
  ifneq ($(findstring wasm32-wasi,$(CC)),) # wasi-sdk
    # wasm has no signals, mmap, process CPU clocks or getpid. wasi-libc ships
    # emulation for each, gated on a -D<UPPERCASE> and a matching -l<lowercase>.
    # := so the tr subshell runs once, not on every expansion of CFLAGS
    WASI_EMULATED:=signal mman process-clocks getpid
    WASI_EMULATED_UC:=$(shell echo '${WASI_EMULATED}' | tr 'a-z-' 'A-Z_')
    CFLAGS+= $(addprefix -D_WASI_EMULATED_,${WASI_EMULATED_UC})
    # consumed by LDLIBS_LAST below, which the link rules put after every archive
    WASI_LIBS=$(addprefix -lwasi-emulated-,${WASI_EMULATED})
    # wasi-sdk defaults to a 64KB stack, which 2tsv's 64KB output buffer alone
    # overflows; --stack-first then makes any future overflow trap instead of
    # silently overwriting the data segment
    LDFLAGS+= -Wl,-z,stack-size=262144 -Wl,--stack-first
    # wasi-libc's default printf traps on %Lf (writer.c's zsv_writer_cell_Lf)
    WASI_LIBS+= -lc-printscan-long-double
  endif
else
  BUILD_SUBDIR=win/${DBG_SUBDIR}
  EXE=.exe
  CFLAGS+= -D__USE_MINGW_ANSI_STDIO -D_ISOC99_SOURCE
  CFLAGS+= -Wl,--strip-all
  CONFIGURE_HOST=x86_64-w64-mingw32
  CFLAGS+=-Dsetenv\(a,b,c\)=_putenv_s\(a,b\)
endif

JQ_CONFIGURE_HOST=
ifneq ($(CONFIGURE_HOST),)
  JQ_CONFIGURE_HOST=--host=$(CONFIGURE_HOST)
endif

CFLAGS+=-std=gnu11
CFLAGS+=-Wunused
CFLAGS_O ?=
ifeq ($(CFLAGS_O),)
  ifeq ($(DEBUG),0)
    CFLAGS_O=-O3
  else
    CFLAGS_O=-O0
  endif
endif

CCBN=$(shell basename ${CC})

CFLAGS+=${CFLAGS_O}
CFLAGS+=-Wshadow -Wall -Wextra
CFLAGS+=-D_GNU_SOURCE
ifeq ($(DEBUG),0)
  CFLAGS+=-DNDEBUG -Wno-missing-braces -pedantic -DSTDC_HEADERS ${CFLAGS_OPT} #-ftree-vectorize

  ifeq (${CCBN},clang)
    CFLAGS+= -Wno-gnu-statement-expression
  endif

  ifeq ($(PGO),1)
    CFLAGS+= -fprofile-generate -fprofile-dir=/tmp/p4
  else
    ifeq ($(PGO),2)
      CFLAGS+= -fprofile-use=/tmp/p4
    else
      $(echo "No profiling set. To use PGO, compile with PGO=1, then run with data, then compile again with PGO=2")
    endif
  endif
else
  CFLAGS += ${CFLAGS_DEBUG}
endif

ifneq ($(INCLUDEDIR),)
  CFLAGS+= -I${INCLUDEDIR}
endif
THIS_LIB_BASE:=$(shell cd .. && pwd)
INCLUDE_DIR:=${THIS_LIB_BASE}/include
BUILD_DIR:=${THIS_LIB_BASE}/build/${BUILD_SUBDIR}/${CCBN}
UTILS1=writer file err signal mem clock arg dl string dirs prop cache jq os index chunk appname rng

ZSV_EXTRAS ?=

ifneq ($(findstring emcc,$(CC)),) # emcc
  ZSV_EXTRAS=1
  UTILS1+=emcc/fs_api
  ifneq ($(NO_THREADING),1)
    LDFLAGS+=-pthread
  endif
else # not emcc
  CFLAGS+= ${CFLAGS_AVX} ${CFLAGS_SSE}
  LDFLAGS+=-lpthread # Linux explicitly requires
endif

ifeq ($(ZSV_EXTRAS),1)
  UTILS1+= overwrite overwrite_writer
endif

UTILS=$(addprefix ${BUILD_DIR}/objs/utils/,$(addsuffix .o,${UTILS1}))

ifeq ($(NO_THREADING),1)
  CFLAGS+= -DNO_THREADING
endif

ifeq ($(ZSV_NO_PARALLEL),1)
  CFLAGS+=-DZSV_NO_PARALLEL
endif

ifeq ($(ZSV_PARALLEL_TEMPFILE),1)
  CFLAGS+=-DZSV_PARALLEL_TEMPFILE
endif

ifeq ($(ZSV_EXTRAS),1)
  CFLAGS+= -DZSV_EXTRAS
endif

OBJECTS=${UTILS}

ifeq ($(NO_MEMMEM),1)
  OBJECTS+= ${BUILD_DIR}/objs/utils/memmem.o
  CFLAGS+=-DNO_MEMMEM
  UTIL_A_OBJ_WIN=memmem
endif

# every Windows executable links the manifest that makes its code page UTF-8
ifeq ($(WIN),1)
  WIN_MANIFEST_OBJECT=${BUILD_DIR}/objs/win/utf8-manifest.o
  OBJECTS+= ${WIN_MANIFEST_OBJECT}
endif

PROG_NAME ?= zsv
ZSV=$(BINDIR)/$(PROG_NAME)${EXE}

SOURCES=echo paste check count count-pull select select-pull 2tsv 2json serialize flatten pretty stack desc sql 2db compare prop rm mv jq
ifeq ($(ZSV_EXTRAS),1)
  SOURCES+=overwrite
endif
CLI_SOURCES=echo select desc count paste check 2tsv pretty sql flatten 2json serialize stack 2db compare prop rm mv jq
ifeq ($(ZSV_EXTRAS),1)
  CLI_SOURCES+=overwrite
endif
# 2toon exists only when TOON features are enabled
ifneq ($(ZSV_NO_TOON),1)
  SOURCES+=2toon
  CLI_SOURCES+=2toon
endif

ifeq ($(ZSVSHEET_BUILD),1)
  SOURCES+=sheet
  CLI_SOURCES+=sheet
  CFLAGS+=-DZSVSHEET_BUILD
  CFLAGS+=${CFLAGS_NCURSES}
  LDFLAGS+=${LDFLAGS_NCURSES}
endif

CFLAGS+= -DUSE_JQ

STATIC_LIB_FLAGS=
ifeq ($(STATIC_BUILD),1)
  STATIC_LIB_FLAGS=${LDFLAGS_STATIC}
else
  ifneq ($(STATIC_LIBS),)
    STATIC_LIB_FLAGS=${LDFLAGS_STATIC}
  endif
endif

STANDALONE_PFX=${BUILD_DIR}/bin/zsv_

TARGETS=$(addprefix ${STANDALONE_PFX},$(addsuffix ${EXE},${SOURCES})) ${CLI}

BUILDS=$(addprefix build-,${SOURCES})
CLEANS=$(addprefix clean-,${SOURCES})

## cli
VERSION= $(shell (git describe --always --dirty --tags 2>/dev/null || echo "v0.0.0-zsv") | sed 's/^v//')
CLI_OBJ_PFX=${BUILD_DIR}/objs/cli_
CLI_APP_OBJECT=${CLI_OBJ_PFX}cli.o
CLI=${BUILD_DIR}/bin/cli${EXE}
ifneq ($(findstring emcc,$(CC)),) # emcc
  CLI_ADDITIONAL=${BUILD_DIR}/bin/cli.em.wasm
endif
CLI_OBJECTS=$(addprefix ${CLI_OBJ_PFX},$(addsuffix .o,${CLI_SOURCES}))

CFLAGS+=${CFLAGS_AUTO}

ifeq ($(VERBOSE),1)
  CFLAGS+= ${CFLAGS_VECTORIZE_OPTIMIZED} ${CFLAGS_VECTORIZE_MISSED} ${CFLAGS_VECTORIZE_ALL}
endif

INIH_SRC=${THIS_MAKEFILE_DIR}/external/inih
INIH_INCLUDE=${THIS_MAKEFILE_DIR}/external/inih
INIH_OBJECT=${BUILD_DIR}-external/inih/inih.o
CLI_INCLUDE+= -I${INIH_INCLUDE}

YAJL_SRC_DIR=${THIS_MAKEFILE_DIR}/external/yajl
YAJL_OBJ1=yajl yajl_alloc yajl_buf yajl_encode yajl_gen yajl_lex yajl_parser yajl_tree yajl_version
YAJL_OBJ=$(addprefix ${BUILD_DIR}-external/yajl/,$(addsuffix .o,${YAJL_OBJ1}))
YAJL_INCLUDE=-I${YAJL_SRC_DIR}/build/yajl-2.1.1/include

YAJL_HELPER_OBJ=${BUILD_DIR}-external/yajl_helper/yajl_helper.o
YAJL_HELPER_INCLUDE=-I${THIS_MAKEFILE_DIR}/external/yajl_helper
CFLAGS+=${YAJL_HELPER_INCLUDE} ${YAJL_INCLUDE}

# jq
JQ_TARBALL=${THIS_MAKEFILE_DIR}/external/jq-1.6.tar.gz
JQ_PATCHES=${THIS_MAKEFILE_DIR}/external/jq-1.6-wasi.patch
JQ_SRC=${BUILD_DIR}-external/jq-src

JQ_PREFIX ?=
ifeq ($(JQ_PREFIX),)
  JQ_PREFIX=${BUILD_DIR}-external/jq-build
  JQ_INCLUDE_DIR=${JQ_PREFIX}/include
  JQ_BUNDLE_LIB=${JQ_PREFIX}/lib/libjq.a
  JQ_LIB=${JQ_BUNDLE_LIB}
else
  JQ_INCLUDE_DIR=${JQ_PREFIX}/include
  JQ_LIB=
endif

## json writer
JSONWRITER_SRC=${THIS_MAKEFILE_DIR}/external/json_writer-1.01
JSONWRITER_INCLUDE=-I${THIS_MAKEFILE_DIR}/external/json_writer-1.01
JSONWRITER_OBJECT=${BUILD_DIR}-external/json_writer-1.01/jsonwriter.o

## toon writer
TOONWRITER_SRC=${THIS_MAKEFILE_DIR}/external/toon_writer-1.0
TOONWRITER_INCLUDE=-I${THIS_MAKEFILE_DIR}/external/toon_writer-1.0
TOONWRITER_OBJECT=${BUILD_DIR}-external/toon_writer-1.0/toonwriter.o

## json2toon: JSON<->TOON conversion (2json --from-toon / 2toon --from-json), with
## its vendored yatl TOON parser. Shares zsv's yajl/jsonwriter/toonwriter (identical headers).
JSON2TOON_SRC=${THIS_MAKEFILE_DIR}/external/json2toon-1.0
JSON2TOON_INCLUDE=-I${JSON2TOON_SRC} -I${JSON2TOON_SRC}/yatl
JSON2TOON_CORE_OBJ=$(addprefix ${BUILD_DIR}-external/json2toon-1.0/,$(addsuffix .o,json2toon toon2json format convenience))
YATL_OBJ=$(addprefix ${BUILD_DIR}-external/json2toon-1.0/yatl/,$(addsuffix .o,yatl yatl_parser yatl_buf yatl_encode yatl_alloc yatl_version))
JSON2TOON_OBJECT=${JSON2TOON_CORE_OBJ} ${YATL_OBJ}

## memfile
MEMFILE_SRC=${THIS_MAKEFILE_DIR}/external/memfile-1.0/src
MEMFILE_INCLUDE=-I${THIS_MAKEFILE_DIR}/external/memfile-1.0/include
MEMFILE_OBJECT=${BUILD_DIR}-external/memfile-1.0/memfile.o

## utf8proc
UTF8PROC_SRC=${THIS_MAKEFILE_DIR}/external/utf8proc-2.11.3
UTF8PROC_INCLUDE=${THIS_MAKEFILE_DIR}/external/utf8proc-2.11.3
UTF8PROC_OBJECT=${BUILD_DIR}-external/utf8proc-2.11.3/utf8proc.o
CFLAGS+= -I${UTF8PROC_INCLUDE} -DUTF8PROC -DUTF8PROC_STATIC

# sqlite3
# headers too: the amalgamation and the sqlite3_wasi.h shim are #included, not compiled
SQLITE_SRC=$(wildcard ${THIS_MAKEFILE_DIR}/external/sqlite3/sqlite3*.c) \
           $(wildcard ${THIS_MAKEFILE_DIR}/external/sqlite3/sqlite3*.h)
SQLITE_EXT=${BUILD_DIR}-external/sqlite3/sqlite3_and_csv_vtab.o
SQLITE_EXT_INCLUDE=-I${THIS_MAKEFILE_DIR}/external/sqlite3

SQL_INTERNAL_OBJECT=${BUILD_DIR}/objs/sql_internal.o

# everything uses prop, which in turn uses yajl and jq and json and sqlite3
OBJECTS+= ${YAJL_OBJ} ${YAJL_HELPER_OBJ} ${BUILD_DIR}/objs/utils/json.o ${SQLITE_EXT}
MORE_SOURCE+= ${YAJL_INCLUDE} ${YAJL_HELPER_INCLUDE} -I${JQ_INCLUDE_DIR} ${SQLITE_EXT_INCLUDE}
MORE_LIBS+=${JQ_LIB} ${LDFLAGS_JQ}

# Appended after everything else on the link line, so every archive that might
# draw on the wasi-libc emulation shims precedes them (a static archive must
# follow its users). MORE_LIBS is not late enough: target-specific
# `MORE_LIBS+=` appends below (pcre2, PDCurses) land after this file's global
# appends. Empty off wasi-sdk.
LDLIBS_LAST=${WASI_LIBS}

help:
	@echo "To build: ${MAKE} [DEBUG=1] [STATIC_BUILD=1] [clean] [clean-all] [BINDIR=${BINDIR}] [JQ_PREFIX=/usr/local] <install|all|install-util-lib|test>"
	@echo
	@echo "If JQ_PREFIX is not defined, libjq will be built in the build dir"
	@echo "If STATIC_BUILD is set to 1, will build with -static flag, if the compiler supports it"
	@echo
	@echo "To build and/or test individual apps, run:"
	@echo "  ${MAKE} test"
	@echo "which will build and test all apps, or to build/test a single app:"
	@echo "  ${MAKE} test-xx"
	@echo "where xx is any of:"
	@echo "  echo count count-pull paste check select select-pull 2tsv 2json serialize flatten pretty stack desc sql 2db prop rm mv overwrite"
	@echo ""
	@echo "To run benchmarks:"
	@echo "  ${MAKE} benchmark"
	@echo ""
	@echo "To run asan/fuzz tests:"
	@echo "  make -C test test-vuln-asan-repro"
	@echo "  make -C test test-vuln-asan-fuzz [FUZZ_ITERS=1000000]"
	@echo ""
	@echo "To run msan tests: see ../.github/workflows/msan.yml"
	@echo ""

DESTDIR ?=

install: ${ZSV}
ifneq ($(DESTDIR),)
	mkdir -p $(DESTDIR)/bin
	cp -p `dirname $<`/* $(DESTDIR)/bin/
endif

benchmark: ${STANDALONE_PFX}select${EXE} ${STANDALONE_PFX}count${EXE} ${STANDALONE_PFX}2tsv${EXE}
	${MAKE} -C benchmark all TESTING=1

uninstall-all: uninstall uninstall-util-lib

uninstall:
ifneq ($(DESTDIR),)
	rm -rf $(DESTDIR)/bin/zsv
else
	rm -rf ${ZSV}
endif

ZSV_UTIL_A=${LIBDIR}/libzsvutil.a
${ZSV_UTIL_A}:SQLITE_EXT=
${ZSV_UTIL_A}:SQLITE_EXT_INCLUDE=
${ZSV_UTIL_A}: ${BUILD_DIR}/objs/utils/util.a
	@mkdir -p `dirname $@`
	cp -p $< $@

UTIL_A_OBJ:=writer file dirs-no-jq os ${UTIL_A_OBJ_WIN}
UTIL_A_OBJ:=$(addprefix ${BUILD_DIR}/objs/utils/,$(addsuffix .o,${UTIL_A_OBJ}))

${BUILD_DIR}/objs/utils/util.a: ${UTIL_A_OBJ}
	$(AR) rcv $@ $^ # $?
	$(RANLIB) $@
	$(AR) -t $@
	@echo Built $@

uninstall-util-lib:
	@rm -f ${ZSV_UTIL_A}

install-util-lib: ${ZSV_UTIL_A}

build: ${CLI}

all: ${TARGETS}

${LIBZSV_BUILD}:
	${MAKE} -C ../src CONFIGFILE=${CONFIGFILEPATH} build DEBUG=${DEBUG}

${ZSV}: ${CLI}
	mkdir -p `dirname "$@"`
ifneq ($(findstring emcc,$(CC)),) # emcc
	cp -p $< `dirname "$@"`/
	cp -p ${CLI_ADDITIONAL} `dirname "$@"`/
else
	cp -p $< $@
endif

cli: build-cli

build-cli: ${CLI}

clean-cli:
	@rm -f  ${CLI_APP_OBJECT} ${CLI_OBJECTS}

${BUILDS}: build-%: ${STANDALONE_PFX}%${EXE}
	@echo Built ${STANDALONE_PFX}$*${EXE}

${CLEANS}: clean-%:
	rm -f ${STANDALONE_PFX}$*${EXE}
	rm -f ${BUILD_DIR}/*/*$*.o

.PHONY: all install cli build build-% clean clean-% test test-% lib-% check install-util-lib uninstall-util-lib clean-util-lib benchmark

.SECONDARY: ${OBJECTS}

# -I win: the .rc names utf8.manifest relative to its own directory. The WINDRES
# check is here rather than at parse time so that `make clean` works with a
# config.mk written before configure detected windres
${WIN_MANIFEST_OBJECT}: win/utf8-manifest.rc win/utf8.manifest
	$(if ${WINDRES},,$(error WINDRES is not set; re-run ./configure))
	@mkdir -p `dirname "$@"`
	${WINDRES} -I win -O coff $< -o $@

.POSIX:
.SUFFIXES:
.SUFFIXES: .o .c .a

${BUILD_DIR}/objs/sql_internal.o ${BUILD_DIR}/objs/utils/index.o ${BUILD_DIR}/objs/utils/chunk.o: ${BUILD_DIR}/objs/%.o: %.c %.h # ${BUILD_DIR}/objs/%.o: %.c %.h
	@mkdir -p `dirname "$@"`
	${CC} ${CFLAGS} -I${INCLUDE_DIR} -o $@ -c $<

${BUILD_DIR}/objs/utils/%.o : utils/%.c ${INCLUDE_DIR}/zsv/utils/%.h ${JQ_LIB}
	@mkdir -p `dirname "$@"`
	${CC} ${CFLAGS} -I${INCLUDE_DIR} -I${UTF8PROC_INCLUDE} -DINCLUDE_SRC -o $@ -c utils/$*.c ${MORE_SOURCE}

${BUILD_DIR}/objs/utils/dirs-no-jq.o : utils/dirs.c ${INCLUDE_DIR}/zsv/utils/dirs.h
	@mkdir -p `dirname "$@"`
	${CC} ${CFLAGS} -I${INCLUDE_DIR} -I${UTF8PROC_INCLUDE} -DINCLUDE_SRC -o $@ -c utils/dirs-no-jq.c ${MORE_SOURCE}

${BUILD_DIR}/objs/zsv_%.o: %.c
	@mkdir -p `dirname "$@"`
	${CC} ${CFLAGS} -I${INCLUDE_DIR} -I${UTF8PROC_INCLUDE} -c $< -o $@

${UTF8PROC_OBJECT}: ${UTF8PROC_SRC}/utf8proc.c
	@mkdir -p `dirname "$@"`
	${CC} ${CFLAGS} -I${UTF8PROC_INCLUDE} -c ${UTF8PROC_SRC}/utf8proc.c -o $@

${INIH_OBJECT}: ${INIH_SRC}/ini.c
	@mkdir -p `dirname "$@"`
	${CC} ${CFLAGS} -I${INIH_INCLUDE} -DINI_HANDLER_LINENO=1 -DINI_CALL_HANDLER_ON_NEW_SECTION=1 -c $< -o $@

${CLI_APP_OBJECT} : cli_ini.c builtin/*.c ${JQ_LIB}
${CLI_APP_OBJECT} ${CLI_OBJECTS}: ${CLI_OBJ_PFX}%.o: %.c ${UTF8PROC_SRC}/utf8proc.c # ${MORE_OBJECTS}
	@mkdir -p `dirname "$@"`
	${CC} ${PREPEND_INCLUDE} ${CFLAGS} ${DEPFLAGS} -DVERSION=\"${VERSION}\" -DZSV_CLI ${CLI_INCLUDE} ${MEMFILE_INCLUDE} -I${THIS_MAKEFILE_DIR}/external/sglib -I${INCLUDE_DIR} -I${UTF8PROC_INCLUDE} -c $< -o $@ ${MORE_SOURCE}

${CLI}: cli_internal.c.in cli_internal.h cli_internal.h.in ${CLI_APP_OBJECT} ${LIBZSV_BUILD} ${CLI_OBJECTS} ${OBJECTS} ${UTF8PROC_OBJECT} cli_ini.c ${INIH_OBJECT} ${MORE_OBJECTS} ${SQL_INTERNAL_OBJECT}
	@mkdir -p `dirname "$@"`
	${CC} ${CFLAGS} -I${INCLUDE_DIR} -o $@ ${CLI_APP_OBJECT} ${CLI_OBJECTS} ${OBJECTS} ${UTF8PROC_OBJECT} ${INIH_OBJECT} -L${LIBZSV_BUILD_DIR} ${LIBZSV_L} ${LDFLAGS} ${LDFLAGS_OPT} ${MORE_OBJECTS} ${MORE_SOURCE} ${MORE_LIBS} ${STATIC_LIB_FLAGS} ${LDLIBS_LAST}
	@echo Built $@

cli_internal.h.in: ${THIS_MAKEFILE_DIR}/../include/zsv/ext/implementation_private.h
	@( \
		OUTPUT=`cat $< | awk '/ZSV_EXT_EXPORT/,/;/' | sed 's/ZSV_EXT_EXPORT *//g' | sed $$'s/ *;.*/;\\\n/' | sed 's/(/)(/' | sed 's/zsv_ext_\([a-z]*\))/(*\1)/' | grep -v '^$$'`; \
		echo "$$OUTPUT" | cmp -s - $@ || echo "$$OUTPUT" > $@; \
	)

cli_internal.c.in: cli_internal.h.in
	@( \
		OUTPUT=`cat $< | sed 's/\(.*(\*\)\([^)]*\)\(.*\);/zsv_ext_func_assign((\1\3), \2);/'`; \
		echo "$$OUTPUT" | cmp -s - $@ || echo "$$OUTPUT" > $@; \
	)

${SQLITE_EXT}: ${SQLITE_SRC}

# ${JQ_INCLUDE_DIR}: ${JQ_LIB}

# the patches are portability-only and guarded per target, so apply unconditionally.
# $@ must be removed first: without that, re-extracting over an existing tree
# moves the new tree *inside* the old one, leaving a stale unpatched jq in place.
# `cd && patch -p1` rather than `patch -d`, which busybox patch does not support.
# `-i $$p </dev/null` rather than feeding the patch on stdin: on a context
# mismatch patch prompts for a file name, and reading that answer from the patch
# itself leaves a silently half-patched tree; with stdin at EOF it fails instead.
# touch: tar restores the archive's own (2018) mtime, so without it $@ is always
# older than its prerequisites and every make re-extracts jq -- which, since
# ${JQ_LIB} is a prerequisite of every util object, rebuilds the whole app.
${JQ_SRC}: ${JQ_TARBALL} ${JQ_PATCHES}
	@rm -rf $@ $@-tmp
	@mkdir -p $@-tmp
	cd $@-tmp && tar xf ${JQ_TARBALL}
	for p in ${JQ_PATCHES}; do (cd $@-tmp/jq-1.6 && patch -p1 -i "$$p" < /dev/null) || exit 1; done
	mv $@-tmp/jq-1.6 $@
	rm -rf $@-tmp
	@touch $@

lib-jq: ${JQ_LIB}
	@echo "Using jq library ${JQ_LIB}"

${JQ_BUNDLE_LIB}: ${JQ_SRC} # -D_REENTRANT needed for clang to not break
	@if [ -e ${JQ_SRC}-conf ]; then \
		rm -rf ${JQ_SRC}-conf; \
	fi
	# copy the directory because its timestamp gets updated after libjq is built
	@cp -a ${JQ_SRC} ${JQ_SRC}-conf
	cd ${JQ_SRC}-conf \
	&& CC="${CC}" CFLAGS="$$(echo '${CFLAGS}' | sed 's/-fsanitize=[^ ]*//g; s/-fno-omit-frame-pointer//g') -w -D_REENTRANT" ./configure \
	    --prefix="${JQ_PREFIX}" \
	    --disable-maintainer-mode \
	    --without-oniguruma \
	    --no-recursion \
	    --disable-docs \
	    --disable-shared \
	    --enable-static \
	    ${JQ_CONFIGURE_HOST} \
	&& (${MAKE} install -k DESTDIR=)

${JSONWRITER_OBJECT}: ${JSONWRITER_SRC}/jsonwriter.c
	@mkdir -p `dirname "$@"`
	${CC} ${CFLAGS} ${JSONWRITER_INCLUDE} -DINCLUDE_UTILS $< -c -o $@

${TOONWRITER_OBJECT}: ${TOONWRITER_SRC}/toonwriter.c ${TOONWRITER_SRC}/toon_numeric.c ${TOONWRITER_SRC}/toonwriter.h ${TOONWRITER_SRC}/toon_numeric.h
	@mkdir -p `dirname "$@"`
	${CC} ${CFLAGS} ${TOONWRITER_INCLUDE} $< -c -o $@

# json2toon core translation units (share zsv's yajl/jsonwriter/toonwriter). -w: vendored third-party.
${JSON2TOON_CORE_OBJ}: ${BUILD_DIR}-external/json2toon-1.0/%.o: ${JSON2TOON_SRC}/%.c ${JSON2TOON_SRC}/internal.h ${JSON2TOON_SRC}/j2t_config.h ${JSON2TOON_SRC}/json2toon.h
	@mkdir -p `dirname "$@"`
	${CC} ${CFLAGS} ${JSON2TOON_INCLUDE} ${JSONWRITER_INCLUDE} ${TOONWRITER_INCLUDE} ${YAJL_INCLUDE} -w -c $< -o $@

# vendored yatl TOON parser (used by json2toon's reverse direction)
${YATL_OBJ}: ${BUILD_DIR}-external/json2toon-1.0/yatl/%.o: ${JSON2TOON_SRC}/yatl/%.c
	@mkdir -p `dirname "$@"`
	${CC} ${CFLAGS} -I${JSON2TOON_SRC}/yatl -w -c $< -o $@

${MEMFILE_OBJECT}: ${MEMFILE_SRC}/memfile.c
	@mkdir -p `dirname "$@"`
	${CC} ${CFLAGS} ${MEMFILE_INCLUDE} -DINCLUDE_UTILS $< -c -o $@

# sglib
MORE_SOURCE+=-I${THIS_MAKEFILE_DIR}/external/sglib

# 2json, desc, compare, flatten use jsonwriter
${CLI} ${STANDALONE_PFX}2json${EXE} ${STANDALONE_PFX}desc${EXE} ${STANDALONE_PFX}compare${EXE} ${STANDALONE_PFX}flatten${EXE}: ${JSONWRITER_OBJECT}
${CLI} ${STANDALONE_PFX}2json${EXE} ${STANDALONE_PFX}desc${EXE} ${STANDALONE_PFX}compare${EXE} ${STANDALONE_PFX}flatten${EXE}: MORE_OBJECTS+= ${JSONWRITER_OBJECT}
${STANDALONE_PFX}2json${EXE} ${CLI_OBJ_PFX}2json.o ${STANDALONE_PFX}desc${EXE} ${CLI_OBJ_PFX}desc.o ${STANDALONE_PFX}compare${EXE} ${CLI_OBJ_PFX}compare.o ${STANDALONE_PFX}flatten${EXE} ${CLI_OBJ_PFX}flatten.o: MORE_SOURCE+=${JSONWRITER_INCLUDE} ${SQLITE_EXT_INCLUDE}

ifneq ($(ZSV_NO_TOON),1)
# 2toon and compare use toonwriter; 2toon also has its own db->toon converter, so it needs sqlite3 headers
${CLI} ${STANDALONE_PFX}2toon${EXE} ${STANDALONE_PFX}compare${EXE}: ${TOONWRITER_OBJECT}
${CLI} ${STANDALONE_PFX}2toon${EXE} ${STANDALONE_PFX}compare${EXE}: MORE_OBJECTS+= ${TOONWRITER_OBJECT}
${STANDALONE_PFX}2toon${EXE} ${CLI_OBJ_PFX}2toon.o: MORE_SOURCE+=${TOONWRITER_INCLUDE} ${SQLITE_EXT_INCLUDE}
${STANDALONE_PFX}compare${EXE} ${CLI_OBJ_PFX}compare.o: MORE_SOURCE+=${TOONWRITER_INCLUDE}

# 2json --from-toon / 2toon --from-json use json2toon (+ vendored yatl); both
# directions share zsv's yajl/jsonwriter/toonwriter. The CLI already links both
# writers (via 2json/2toon/compare), so it only adds json2toon; each standalone
# adds the one writer it does not already link.
${CLI}: ${JSON2TOON_OBJECT}
${CLI}: MORE_OBJECTS+= ${JSON2TOON_OBJECT}
${STANDALONE_PFX}2json${EXE}: ${JSON2TOON_OBJECT} ${TOONWRITER_OBJECT}
${STANDALONE_PFX}2json${EXE}: MORE_OBJECTS+= ${JSON2TOON_OBJECT} ${TOONWRITER_OBJECT}
${STANDALONE_PFX}2toon${EXE}: ${JSON2TOON_OBJECT} ${JSONWRITER_OBJECT}
${STANDALONE_PFX}2toon${EXE}: MORE_OBJECTS+= ${JSON2TOON_OBJECT} ${JSONWRITER_OBJECT}
# compare uses json2toon for its --redline TOON output (already links jsonwriter/toonwriter)
${STANDALONE_PFX}compare${EXE}: ${JSON2TOON_OBJECT}
${STANDALONE_PFX}compare${EXE}: MORE_OBJECTS+= ${JSON2TOON_OBJECT}
# 2json/2toon/compare include the vendored <json2toon.h>/<jsonwriter.h>/<toonwriter.h>
# with angle brackets, so put the vendored dirs FIRST -- via PREPEND_INCLUDE, emitted
# right after $(CC), ahead of the global -I${INCLUDEDIR} -- so a copy installed under
# the prefix (e.g. an older json2toon) cannot shadow the vendored headers.
${STANDALONE_PFX}2json${EXE} ${CLI_OBJ_PFX}2json.o ${STANDALONE_PFX}2toon${EXE} ${CLI_OBJ_PFX}2toon.o ${STANDALONE_PFX}compare${EXE} ${CLI_OBJ_PFX}compare.o: PREPEND_INCLUDE+=${JSON2TOON_INCLUDE} ${JSONWRITER_INCLUDE} ${TOONWRITER_INCLUDE}
endif

ifeq ($(USE_SIMDUTF),1)
CFLAGS+=-DUSE_SIMDUTF
SIMDUTF_OBJECT=${BUILD_DIR}/check/simdutf_wrapper.o
${SIMDUTF_OBJECT}: check/simdutf_wrapper.cpp
	@mkdir -p `dirname "$@"`
	${CC} ${CFLAGS} -std=c++17 -ffunction-sections -fdata-sections -fno-exceptions -fno-rtti -c check/simdutf_wrapper.cpp -o $@

${CLI} ${STANDALONE_PFX}check${EXE}: ${SIMDUTF_OBJECT}
${CLI} ${STANDALONE_PFX}check${EXE}: MORE_OBJECTS+=${SIMDUTF_OBJECT} -lstdc++
endif

# check if we are on arm64
ifeq ($(ARM64),)
  ifneq ($(shell uname -a | grep Darwin | grep arm64),)
    ARM64=1
  endif
endif

# CLI, select and sheet use PCRE2-8
BULITIN_PCRE2_PREFIX=${BUILD_DIR}-external/pcre2
ifeq ($(HAVE_PCRE2_8),1)
  NO_PCRE2_JIT=
  # TO DO: make PCRE2_STATIC a configure option / detection
  PCRE2_STATIC=1
  CFLAGS+=-DHAVE_PCRE2_8
  # add dependency
  ${CLI} ${STANDALONE_PFX}select${EXE} ${STANDALONE_PFX}sheet${EXE}: ${BUILD_DIR}/objs/utils/pcre2-8/pcre2-8.o

  PCRE2_LIB=-lpcre2-8
  ifeq ($(PCRE2_STATIC),1)
    CFLAGS+= -DPCRE2_STATIC
    ifeq ($(PCRE2_8_PREFIX),builtin)
      PCRE2_8_PREFIX=${BULITIN_PCRE2_PREFIX}
      # add dependency
      ${CLI} ${STANDALONE_PFX}select${EXE} ${STANDALONE_PFX}sheet${EXE}: ${PCRE2_8_PREFIX}/lib/libpcre2-8.a
    endif
    PCRE2_LIB=${PCRE2_8_PREFIX}/lib/libpcre2-8.a
  endif

  ifeq ($(NO_PCRE2_JIT),)
    ifeq ($(ARM64),1)
      # see https://github.com/zherczeg/sljit/issues/99
      NO_PCRE2_JIT=1
    endif
  endif

  ifeq ($(NO_PCRE2_JIT),1)
    CFLAGS+=-DNO_PCRE2_JIT
  endif

  LDFLAGS_PCRE2_8=
  INCLUDE_PCRE2_8=${INCLUDEDIR}
  ifneq ($(PCRE2_8_PREFIX),$(PREFIX))
    ifneq ($(PCRE2_STATIC),1)
      LDFLAGS_PCRE2_8=-L${PCRE2_8_PREFIX}/lib
    endif
    INCLUDE_PCRE2_8=${PCRE2_8_PREFIX}/include
  endif

  ${CLI} ${STANDALONE_PFX}select${EXE} ${STANDALONE_PFX}sheet${EXE}: MORE_LIBS+=${BUILD_DIR}/objs/utils/pcre2-8/pcre2-8.o ${LDFLAGS_PCRE2_8} ${PCRE2_LIB}
endif

# sheet uses curses. On Windows/MinGW with no installed PDCurses, configure sets
# PDCURSES_BUILTIN=1; bootstrap a private static copy into the build dir using the
# same script CI uses, then point sheet's include + link at it.
ifeq ($(PDCURSES_BUILTIN),1)
  PDCURSES_VERSION ?= 3.9
  BUILTIN_PDCURSES_PREFIX=${BUILD_DIR}-external/pdcurses
  PDCURSES_SRC=${BUILTIN_PDCURSES_PREFIX}/PDCurses-${PDCURSES_VERSION}
  PDCURSES_LIB=${PDCURSES_SRC}/wincon/libpdcurses.a
  CFLAGS+= -I${PDCURSES_SRC}
  # Compiling sheet.c needs curses.h and linking needs the static archive; both
  # are produced by the bootstrap recipe below, so make sheet depend on the lib.
  ${CLI_OBJ_PFX}sheet.o ${STANDALONE_PFX}sheet${EXE}: ${PDCURSES_LIB}
  ${CLI} ${STANDALONE_PFX}sheet${EXE}: MORE_LIBS+= ${PDCURSES_LIB}

${PDCURSES_LIB}:
	${MAKE} -C external -f ${CONFIGFILEPATH} -f build.mk $@ BUILD_DIR=${BUILD_DIR} CONFIGURE_HOST=${CONFIGURE_HOST} PDCURSES_VERSION=${PDCURSES_VERSION} DESTDIR=
endif

# pcre2-8.o actually only needs ${PCRE2_8_PREFIX}/include/pcre2.h, but if we are using builtin pcre2, then
# that will only exist after the builtin library has been built
ifeq ($(PCRE2_8_PREFIX),$(BULITIN_PCRE2_PREFIX))
  PCRE2_8_O_DEP=${PCRE2_8_PREFIX}/lib/libpcre2-8.a
else
  PCRE2_8_O_DEP=
endif

${BUILD_DIR}/objs/utils/pcre2-8/pcre2-8.o: ${BUILD_DIR}/objs/utils/%.o : utils/%.c utils/%.h ${PCRE2_8_O_DEP}
	@mkdir -p `dirname "$@"`
	${CC} ${CFLAGS} -I${INCLUDE_PCRE2_8} -o $@ -c utils/$*.c

${BULITIN_PCRE2_PREFIX}/lib/libpcre2-8.a:
	${MAKE} -C external -f ${CONFIGFILEPATH} -f build.mk $@ BUILD_DIR=${BUILD_DIR} CONFIGURE_HOST=${CONFIGURE_HOST} DESTDIR=

# utils/db uses jsonwriter
${BUILD_DIR}/objs/utils/db.o : MORE_SOURCE+= ${JSONWRITER_INCLUDE} ${SQLITE_EXT_INCLUDE}

# flatten uses memfile
${CLI} ${STANDALONE_PFX}flatten${EXE}: ${MEMFILE_OBJECT}
${CLI} ${STANDALONE_PFX}flatten${EXE}: MORE_OBJECTS+= ${MEMFILE_OBJECT} ${JSONWRITER_INCLUDE}
${STANDALONE_PFX}flatten${EXE}: MORE_SOURCE+=${MEMFILE_INCLUDE} ${SQLITE_EXT_INCLUDE}

# 2json uses utils/db
${CLI} ${STANDALONE_PFX}2json${EXE}: ${BUILD_DIR}/objs/utils/db.o
${CLI} ${STANDALONE_PFX}2json${EXE}: MORE_OBJECTS+= ${BUILD_DIR}/objs/utils/db.o

# pretty uses termcap
${CLI} ${STANDALONE_PFX}pretty${EXE}: MORE_LIBS+=${LDFLAGS_TERMCAP}

${CLI} ${STANDALONE_PFX}sheet${EXE} ${STANDALONE_PFX}sql${EXE}: ${SQL_INTERNAL_OBJECT}
${CLI} ${STANDALONE_PFX}sheet${EXE} ${STANDALONE_PFX}sql${EXE}: MORE_OBJECTS+=${SQL_INTERNAL_OBJECT}

# Dependency tracking for the sheet and select unity builds: sheet.c #includes ~20
# sheet/*.c sources and select.c #includes select/*.c, but the compile rules list
# only the top-level .c as a prerequisite, so edits to the included files would
# otherwise not trigger a rebuild (stale binary). -MMD records every #included
# user file (including the .c files), -MP adds phony targets so a later-removed
# include does not break the build, and -MT/-MF name the rule target and the
# depfile. DEPFLAGS is set only on these targets, so it expands to nothing for
# every other command; the depfiles live under ${BUILD_DIR} and are removed by
# `make clean`.
UNITY_TARGETS=${CLI_OBJ_PFX}sheet.o ${STANDALONE_PFX}sheet${EXE} ${CLI_OBJ_PFX}select.o ${STANDALONE_PFX}select${EXE}
${UNITY_TARGETS}: DEPFLAGS=-MMD -MP -MT $@ -MF $@.d
-include $(addsuffix .d,${UNITY_TARGETS})

${STANDALONE_PFX}%${EXE}: %.c ${OBJECTS} ${MORE_OBJECTS} ${LIBZSV_BUILD} ${UTF8PROC_OBJECT}
	@mkdir -p `dirname "$@"`
	${CC} ${PREPEND_INCLUDE} ${CFLAGS} ${DEPFLAGS} -I${INCLUDE_DIR} -o $@ $< ${OBJECTS} ${MORE_OBJECTS} ${MORE_SOURCE} -L${BUILD_DIR}/lib ${LIBZSV_L} ${UTF8PROC_OBJECT} ${LDFLAGS} ${LDFLAGS_OPT} ${MORE_LIBS} ${STATIC_LIB_FLAGS} ${LDLIBS_LAST}

${BUILD_DIR}-external/sqlite3/sqlite3_and_csv_vtab.o: ${BUILD_DIR}-external/%.o : external/%.c
	@mkdir -p `dirname "$@"`
	${CC} ${CFLAGS} -w -I${INCLUDE_DIR} ${YAJL_INCLUDE} ${YAJL_HELPER_INCLUDE} -o $@ -c $<

${YAJL_OBJ}: ${BUILD_DIR}-external/yajl/%.o : external/yajl/src/%.c
	@mkdir -p `dirname "$@"`
	${CC} ${CFLAGS} -w ${YAJL_INCLUDE} -c external/yajl/src/$*.c -o $@

${YAJL_HELPER_OBJ}: external/yajl_helper/yajl_helper.c
	@mkdir -p `dirname "$@"`
	${CC} ${CFLAGS} ${YAJL_INCLUDE} ${YAJL_HELPER_INCLUDE} -c $< -o $@

check test:	test-standalone test-cli test-shared-lib

test-shared-lib:
	@echo "TO DO: create tests for using shared library"

test-standalone:
	@${MAKE} -C test test QUIET=1 LEAKS=${LEAKS} CONFIGFILE=${CONFIGFILEPATH} DEBUG=${DEBUG}

test-cli: ${CLI}
	@${MAKE} -C test $@ QUIET=1 LEAKS=${LEAKS} CONFIGFILE=${CONFIGFILEPATH} DEBUG=${DEBUG} CLI=${CLI} TESTING=${TESTING}

clean-all: clean clean-external clean-obj clean-lib clean-util-lib

clean-external:
	@rm -rf ${JQ_SRC}

clean-util-lib:
	@rm -f ${BUILD_DIR}/objs/utils/util.a

clean-lib:
	@rm -rf ${BUILD_DIR}-external

clean: clean-obj clean-internal
	rm -rf ${BUILD_DIR}
	${MAKE} -C test clean CONFIGFILE=${CONFIGFILEPATH} DEBUG=${DEBUG}

clean-obj:
	rm -rf ${BUILD_DIR}/bin ${INIH_OBJECT} ${JSONWRITER_OBJECT} ${UTF8PROC_OBJECT} ${MEMFILE_OBJECT}
	rm -rf external/utf8proc-2.11.3

clean-internal:
	$(RM) cli_internal.c.in cli_internal.h.in

# tar restores the archive's (old) mtimes, and this is a multi-target rule, so
# touching only $@ leaves the other output older than the tarball -- which made
# the two targets ping-pong re-extraction on every build (each extraction resets
# both files to old mtimes but touches only one). That now surfaces as a spurious
# zsv_sheet relink, since the sheet depfile makes it depend on utf8proc.h. Touch
# BOTH so they are reliably newer than the tarball and the rule runs only once.
${UTF8PROC_SRC}/utf8proc.c ${UTF8PROC_SRC}/utf8proc.h: ${THIS_MAKEFILE_DIR}/external/utf8proc-2.11.3.tar.gz
	@cd external && tar xf utf8proc-2.11.3.tar.gz && chown -R `whoami` utf8proc-2.11.3
	@touch ${UTF8PROC_SRC}/utf8proc.c ${UTF8PROC_SRC}/utf8proc.h
