include ../../sample_objdir.mk

TARGET      := vfs_read_overflow_test
TARGET_TEST := vfs_mapped_ptr_test
TARGET_TEST2 := vfs_large_file_test
TARGET_TEST3 := vfs_seek_contract_test
TARGET_TEST4 := vfs_bulk_read_test
TARGET_TEST5 := filestream_rbuf_fault_test
TARGET_TEST6 := vfs_v5_metadata_test
TARGET_TEST7 := vfs_rename_replace_test
TARGET_TEST8 := vfs_prefetch_test
TARGET_TEST9 := vfs_readahead_test

LIBRETRO_COMM_DIR := ../../..

COMMON_SOURCES := \
	$(LIBRETRO_COMM_DIR)/compat/fopen_utf8.c \
	$(LIBRETRO_COMM_DIR)/compat/compat_strl.c \
	$(LIBRETRO_COMM_DIR)/encodings/encoding_utf.c \
	$(LIBRETRO_COMM_DIR)/file/file_path.c \
	$(LIBRETRO_COMM_DIR)/file/file_path_io.c \
	$(LIBRETRO_COMM_DIR)/file/retro_dirent.c \
	$(LIBRETRO_COMM_DIR)/streams/file_stream.c \
	$(LIBRETRO_COMM_DIR)/string/rstrtod.c \
	$(LIBRETRO_COMM_DIR)/time/rtime.c \
	$(LIBRETRO_COMM_DIR)/vfs/vfs_implementation.c

COMMON_OBJS := $(call sample_objs,$(COMMON_SOURCES:.c=.o))
OBJS        := vfs_read_overflow_test.o vfs_mapped_ptr_test.o \
	vfs_large_file_test.o vfs_seek_contract_test.o \
	vfs_bulk_read_test.o filestream_rbuf_fault_test.o \
	vfs_v5_metadata_test.o vfs_rename_replace_test.o \
	vfs_prefetch_test.o vfs_readahead_test.o $(COMMON_OBJS)

# filestream_rbuf_fault_test drives the two arms on which
# filestream_rbuf_fill() gives up and hands the caller back to the
# byte-at-a-time path.  The tell arm needs nothing from the
# implementation - filestream_vfs_init() is seam enough - but a refused
# allocation has no other way in, so that one target links its own
# file_stream.o with FILESTREAM_RBUF_MALLOC pointed at its fault
# allocator.  Scoped to this object and no further: every other target
# here, and every build in the tree, links the unmodified unit with
# plain malloc() and no added symbol or branch.
FAULT_FS_OBJ := file_stream_rbuf_fault.o
FAULT_OBJS   := $(filter-out %/file_stream.o,$(COMMON_OBJS)) \
	$(FAULT_FS_OBJ)

# vfs_readahead_test needs the read-ahead thread, so that one target
# links a vfs_implementation.o built with HAVE_THREADS and the threads.
RA_VFS_OBJ := vfs_implementation_threads.o
RA_OBJS    := $(filter-out %/vfs_implementation.o,$(COMMON_OBJS)) \
	$(RA_VFS_OBJ) rthreads_ra.o retro_eventcount_ra.o
ifeq ($(UNAME_S),Linux)
   RA_LIBS := -lpthread
endif

# vfs_rename_replace_test runs the replace-on-rename branch the consoles
# take, so that one target links its own vfs_implementation.o built
# with -DGEKKO (and, like GameCube and Wii, no mmap), and wraps
# rename/remove/stat to refuse a rename onto an existing name as those
# filesystems do.
RENAME_VFS_OBJ := vfs_implementation_gekko.o
RENAME_OBJS    := $(filter-out %/vfs_implementation.o,$(COMMON_OBJS)) \
	$(RENAME_VFS_OBJ)

# -DHAVE_MMAP is required for vfs_read_overflow_test to exercise the
# patched code path; without it the implementation falls back to
# buffered fread and that test becomes a smoke test that can't
# discriminate the overflow case.
#
# vfs_mapped_ptr_test needs BOTH settings to be meaningful, which is
# why this is a variable rather than a fixed flag.  With mappings on,
# filestream_matches_buf() compares against the map and its windowed
# fallback never runs - so the window-boundary cases would be
# asserting on code that was not executed.  Build with MMAP=0 to put
# that path under test; the test reports which one it took.
# Windows has no <sys/mman.h>.  The Win32 file-mapping backend is
# picked up from _WIN32 inside vfs.h and needs no flag here, so
# default this off there rather than leave a plain `make` in an MSYS2
# shell failing on a missing POSIX header.
UNAME_S := $(shell uname -s 2>/dev/null)
ifneq (,$(filter MINGW% MSYS% CYGWIN%,$(UNAME_S)))
   MMAP ?= 0
else
   MMAP ?= 1
endif
ifeq ($(MMAP),1)
   CFLAGS += -DHAVE_MMAP
endif

# vfs_bulk_read_test asserts which I/O path an open lands on, and the
# interesting configuration - descriptor I/O with no file mapping - is
# the one the consoles have and no desktop does.  Build
# 'make MMAP=0 DESCRIPTOR_IO=1' to reproduce it here; MMAP=0 on its
# own gives the other case, a target that is on neither list and must
# keep its buffered behaviour unchanged.
DESCRIPTOR_IO ?=
ifeq ($(DESCRIPTOR_IO),1)
   CFLAGS += -DVFS_HAVE_DESCRIPTOR_IO=1
endif

# FAKE_CFR=1 stands in a copy_file_range that copies to EOF whatever
# len it was given (what gVisor was observed doing); FAKE_CFR=2 one
# that copies three times len.  Both exercise vfs_v5_metadata_test's
# distrust-and-resume path on a kernel that would never trip it.
ifneq ($(FAKE_CFR),)
   CFLAGS += -DVFS_COPY_TEST_FAKE_CFR=$(FAKE_CFR)
endif
# Every sample build narrates the copy stepper's platform calls on
# stderr (which path, what was asked, what came back); a frontend
# build never defines this.
CFLAGS += -DVFS_COPY_DEBUG
CFLAGS += -Wall -pedantic -std=gnu99 -g1 -I$(LIBRETRO_COMM_DIR)/include

# The samples workflow passes SANITIZER=address,undefined to every
# sample dir; without this block the variable was silently ignored
# here, so these two ran unchecked while the rest of the tree did not.
ifneq ($(SANITIZER),)
   CFLAGS  := -fsanitize=$(SANITIZER) -fno-omit-frame-pointer $(CFLAGS)
   LDFLAGS := -fsanitize=$(SANITIZER) $(LDFLAGS)
endif

# --wrap is a GNU ld option, and on a Windows host the _WIN32 rename
# branch would be compiled ahead of the GEKKO one, so the console
# rename target is built on Linux only.
ifeq ($(UNAME_S),Linux)
   RENAME_TARGET := $(TARGET_TEST7)
endif

all: $(TARGET) $(TARGET_TEST) $(TARGET_TEST2) $(TARGET_TEST3) $(TARGET_TEST4) \
	$(TARGET_TEST5) $(TARGET_TEST6) $(TARGET_TEST8) $(TARGET_TEST9) $(RENAME_TARGET)

%.o: %.c
	$(CC) -c -o $@ $< $(CFLAGS)

$(SAMPLE_OBJDIR)/%.o: $$(call sample_src,$$*).c
	@mkdir -p $(@D)
	$(CC) -c -o $@ $< $(CFLAGS)

$(TARGET): vfs_read_overflow_test.o $(COMMON_OBJS)
	$(CC) -o $@ $^ $(LDFLAGS)

$(TARGET_TEST): vfs_mapped_ptr_test.o $(COMMON_OBJS)
	$(CC) -o $@ $^ $(LDFLAGS)

$(TARGET_TEST8): vfs_prefetch_test.o $(COMMON_OBJS)
	$(CC) -o $@ $^ $(LDFLAGS)

$(RA_VFS_OBJ): $(LIBRETRO_COMM_DIR)/vfs/vfs_implementation.c
	$(CC) -c -o $@ $< $(CFLAGS) -DHAVE_THREADS

rthreads_ra.o: $(LIBRETRO_COMM_DIR)/rthreads/rthreads.c
	$(CC) -c -o $@ $< $(CFLAGS) -DHAVE_THREADS

retro_eventcount_ra.o: $(LIBRETRO_COMM_DIR)/rthreads/retro_eventcount.c
	$(CC) -c -o $@ $< $(CFLAGS) -DHAVE_THREADS

$(TARGET_TEST9): vfs_readahead_test.o $(RA_OBJS)
	$(CC) -o $@ $^ $(LDFLAGS) $(RA_LIBS)

# vfs_large_file_test builds sparse fixtures of 3 GiB and 5 GiB.  They
# cost no space on a filesystem with sparse files and are removed
# afterwards; where the filesystem cannot hold them the test skips.
$(TARGET_TEST2): vfs_large_file_test.o $(COMMON_OBJS)
	$(CC) -o $@ $^ $(LDFLAGS)

# vfs_seek_contract_test runs its contract checks on a 4 KiB fixture
# first, so a machine that cannot hold the 4 GiB one still tests the
# properties instead of skipping the run.
$(TARGET_TEST3): vfs_seek_contract_test.o $(COMMON_OBJS)
	$(CC) -o $@ $^ $(LDFLAGS)

# vfs_bulk_read_test covers filestream_read_file() and the descriptor
# path RETRO_VFS_FILE_ACCESS_HINT_SEQUENTIAL_BULK selects.  It spawns
# threads to check that whole-file reads hold no shared state, so it
# is the one target here that needs -lpthread; that also makes it the
# one worth running under SANITIZER=thread.
$(TARGET_TEST4): vfs_bulk_read_test.o $(COMMON_OBJS)
	$(CC) -o $@ $^ $(LDFLAGS) -lpthread

$(FAULT_FS_OBJ): $(LIBRETRO_COMM_DIR)/streams/file_stream.c \
		rbuf_fault_hooks.h
	$(CC) -c -o $@ $< $(CFLAGS) -I. -include rbuf_fault_hooks.h \
		-DFILESTREAM_RBUF_MALLOC=filestream_rbuf_fault_malloc

# filestream_rbuf_fault_test installs its own VFS, which takes the
# memory mapping out of play, so it behaves identically with MMAP=1 and
# MMAP=0 and there is nothing to gain from running it in both lanes.
$(TARGET_TEST5): filestream_rbuf_fault_test.o $(FAULT_OBJS)
	$(CC) -o $@ $^ $(LDFLAGS)

# vfs_v5_metadata_test covers the VFS API v5 additions (read-only
# state, mtime, begin/step/close copy, dirent_stat) through the path_*,
# filestream_copy* and retro_dirent_stat wrappers.  The copy is a
# caller-stepped state machine: no threads, no extra objects.
$(TARGET_TEST6): vfs_v5_metadata_test.o $(COMMON_OBJS)
	$(CC) -o $@ $^ $(LDFLAGS)

$(RENAME_VFS_OBJ): $(LIBRETRO_COMM_DIR)/vfs/vfs_implementation.c
	$(CC) -c -o $@ $< $(filter-out -DHAVE_MMAP,$(CFLAGS)) -DGEKKO

$(TARGET_TEST7): vfs_rename_replace_test.o $(RENAME_OBJS)
	$(CC) -o $@ $^ $(LDFLAGS) -Wl,--wrap=rename,--wrap=remove,--wrap=stat

clean:
	rm -rf $(SAMPLE_OBJDIR)
	rm -f $(TARGET) $(TARGET_TEST) $(TARGET_TEST2) $(TARGET_TEST3) \
		$(TARGET_TEST4) $(TARGET_TEST5) $(TARGET_TEST6) \
		$(TARGET_TEST7) $(TARGET_TEST8) $(TARGET_TEST9) $(OBJS) $(FAULT_FS_OBJ) $(RENAME_VFS_OBJ) \
		$(RA_VFS_OBJ) rthreads_ra.o retro_eventcount_ra.o
	rm -rf v5_meta_dir
	rm -f large_3gib.bin large_5gib.bin seek_small.bin seek_4gib.bin
	rm -f rbuf_fault.txt vfs_readahead_fixture.bin
	rm -f rename_replace.bin rename_replace.bin.old rename_replace.bin.tmp
	rm -f bulk_select.bin bulk_select_w.bin bulk_roundtrip.bin \
		bulk_short.bin bulk_shrank.bin bulk_error.bin \
		bulk_binary.bin bulk_threads.bin

.PHONY: clean
