include choose_mrbuild.mk
include $(MRBUILD_MK)/Makefile.common.header


# "0" or undefined means "false"
# everything else means  "true"

# libelas stereo matcher. Available in Debian/non-free. I don't want to depend
# on anything in non-free, so I default to not using libelas
USE_LIBELAS ?= 0

# If true, we compile libstb into image.o and libmrcal.so. If false, we link to
# an external libstb.so. We default to building it ourselves on macos and using
# the library otherwise
USE_LOCAL_STB_IMPLEMENTATION ?= $(COND_DARWIN)

# convert all USE_XXX:=0 to an empty string
$(foreach v,$(filter USE_%,$(.VARIABLES)),$(if $(filter 0,${$v}),$(eval $v :=)))
# to print them all: $(foreach v,$(filter USE_%,$(.VARIABLES)),$(warning $v = '${$v}'))


PROJECT_NAME := mrcal
ABI_VERSION  := 5
TAIL_VERSION := 0

VERSION = $(VERSION_FROM_PROJECT)

LIB_SOURCES +=			\
  mrcal.c			\
  opencv.c			\
  uncertainty.c			\
  image.c			\
  stereo.c			\
  poseutils.c			\
  poseutils-opencv.c		\
  poseutils-uses-autodiff.cc	\
  triangulation.cc              \
  cahvore.cc                    \
  traverse-sensor-links.c       \
  heap.cc                       \
  python-cameramodel-converter.c


ifneq (${USE_LIBELAS},) # using libelas
LIB_SOURCES := $(LIB_SOURCES) stereo-matching-libelas.cc
endif

ifneq ($(USE_LOCAL_STB_IMPLEMENTATION),)
image.o: CFLAGS += -DSTB_IMAGE_IMPLEMENTATION=1
endif

BIN_SOURCES +=					\
  test/test-gradients.c				\
  test/test-cahvor.c				\
  test/test-lensmodel-string-manipulation.c     \
  test/test-parser-cameramodel.c                \
  test/test-heap.c

LDLIBS += -ldogleg $(if $(USE_LOCAL_STB_IMPLEMENTATION),,-lstb) -lpng -ljpeg -llapack

ifneq (${USE_LIBELAS},) # using libelas
LDLIBS += -lelas
endif

CFLAGS    += --std=gnu99
CCXXFLAGS += -Wno-missing-field-initializers -Wno-unused-variable -Wno-unused-parameter -Wno-missing-braces

$(patsubst %.c,%.o,$(shell grep -l '#include .*minimath\.h' *.c */*.c)): minimath/minimath_generated.h
minimath/minimath_generated.h: minimath/minimath_generate.pl
	./$< > $@.tmp && mv $@.tmp $@
EXTRA_CLEAN += minimath/minimath_generated.h

DIST_INCLUDE +=			\
	mrcal.h			\
	image.h			\
	internal.h		\
	basic-geometry.h	\
	poseutils.h		\
	triangulation.h		\
	types.h			\
	stereo.h                \
	heap.h                  \
	python-cameramodel-converter.h



DIST_BIN :=					\
	mrcal-calibrate-cameras			\
	mrcal-convert-lensmodel			\
	mrcal-show-distortion-off-pinhole	\
	mrcal-show-splined-model-correction	\
	mrcal-show-projection-uncertainty	\
	mrcal-show-projection-diff		\
	mrcal-show-model-resolution		\
	mrcal-show-stereo-pair-diff		\
	mrcal-reproject-points			\
	mrcal-reproject-image			\
	mrcal-graft-models			\
	mrcal-to-cahvor				\
	mrcal-from-cahvor			\
	mrcal-to-kalibr				\
	mrcal-from-kalibr			\
	mrcal-from-ros				\
	mrcal-show-geometry			\
	mrcal-show-valid-intrinsics-region	\
	mrcal-is-within-valid-intrinsics-region \
	mrcal-triangulate			\
	mrcal-cull-corners                      \
	mrcal-show-residuals-board-observation  \
	mrcal-show-residuals                    \
	mrcal-stereo

# generate manpages from distributed binaries, and ship them. This is a hoaky
# hack because apparenly manpages from python tools is a crazy thing to want to
# do
DIST_MAN := $(addsuffix .1,$(DIST_BIN))

# if using an older mrbuild SO won't be defined, and we need it
SO ?= so

# parser
cameramodel-parser_GENERATED.c: cameramodel-parser.re mrcal.h
	re2c $< > $@.tmp && mv $@.tmp $@
LIB_SOURCES += cameramodel-parser_GENERATED.c
EXTRA_CLEAN += cameramodel-parser_GENERATED.c
cameramodel-parser_GENERATED.o: CCXXFLAGS += -fno-fast-math

ALL_NPSP_EXTENSION_MODULES := $(patsubst %-genpywrap.py,%,$(wildcard *-genpywrap.py))
ifeq (${USE_LIBELAS},) # not using libelas
ALL_NPSP_EXTENSION_MODULES := $(filter-out elas,$(ALL_NPSP_EXTENSION_MODULES))
endif
ALL_PY_EXTENSION_MODULES   := _mrcal $(patsubst %,_%_npsp,$(ALL_NPSP_EXTENSION_MODULES))
%/:
	mkdir -p $@

######### python stuff

# python-cameramodel-converter contains a utility function for external Python
# wrapping. I link this into libmrcal.so, but libmrcal.so does NOT link with
# libpython. 99% of the usage of libmrcal.so will not use this, so it should
# work without libpython. People using this function will be doing so as part of
# PyArg_ParseTupleAndKeywords(), so they will be linking to libpython anyway.
# Thus I weaken all the references to libpython here. c_build_rule is the
# default logic in mrbuild
#
# This is a DEEP rabbithole. With Debian/trixie (released summer 2025) objcopy
# --weaken works to weaken the symbols after compiling. With older objcopy (not
# sure which), the objcopy command completes successfully, but it doesn't
# actually weaken the symbols. I thus run nm to find the non-weakened symbols;
# if any remain, I recompile, explicitly telling the linker about the weak
# linkage on these symbols. I can't do this on the first pass because before
# compiling even once I don't know which Py symbols I'm going to get (some are
# generated by the Python internals).
#
# On macos there're more details: there is no objcopy and nm works slightly
# differently and the linker has to be explicitly be told to not worry about the
# undefined symbols. Here I allow objcopy to fail (the symbols will be weakened
# on the second pass, as with a tool-old objcopy), and I make sure to invoke nm
# in a way that will work on both macos and Linux (the options and output
# formatting varies a bit).
ifneq ($(COND_DARWIN),)
   libmrcal.$(SO): LDFLAGS += -Wl,-undefined,dynamic_lookup
endif
python-cameramodel-converter.o: %.o:%.c
	$(c_build_rule) && mv $@ _$@
	$(OBJCOPY) --wildcard --weaken-symbol='Py*' --weaken-symbol='_Py*' _$@ 2>&1 || true
	$(NM) -u _$@ | awk '$$1 != "w" && $$NF ~ "Py" { print $$NF }' > python-cameramodel-converter-py-symbol-refs
	if [ -s python-cameramodel-converter-py-symbol-refs ]; then			\
	  < python-cameramodel-converter-py-symbol-refs					\
	    awk 'BEGIN {ORS=""; print "#define PY_REFS(_) " } {print "_("$$1") "} '	\
	  > python-cameramodel-converter-py-symbol-refs.h				\
	  &&										\
	  $(c_build_rule) -DWEAKEN_PY_REFS;						\
	else										\
	  mv _$@ $@;									\
	fi
EXTRA_CLEAN += \
  python-cameramodel-converter-py-symbol-refs   \
  python-cameramodel-converter-py-symbol-refs.h \
  _*python-cameramodel-converter.o






%-npsp-pywrap-GENERATED.c: %-genpywrap.py
	python3 $< > $@.tmp && mv $@.tmp $@
mrcal/_%_npsp$(PY_EXT_SUFFIX): %-npsp-pywrap-GENERATED.o libmrcal.$(SO) libmrcal.$(SO).${ABI_VERSION}
	$(PY_MRBUILD_LINKER) $(PY_MRBUILD_LDFLAGS) $(LDFLAGS) $< -lmrcal -o $@

ALL_NPSP_C  := $(patsubst %,%-npsp-pywrap-GENERATED.c,$(ALL_NPSP_EXTENSION_MODULES))
ALL_NPSP_O  := $(patsubst %,%-npsp-pywrap-GENERATED.o,$(ALL_NPSP_EXTENSION_MODULES))
ALL_NPSP_SO := $(patsubst %,mrcal/_%_npsp$(PY_EXT_SUFFIX),$(ALL_NPSP_EXTENSION_MODULES))

EXTRA_CLEAN += $(ALL_NPSP_C)

# https://gcc.gnu.org/bugzilla/show_bug.cgi?id=95635
$(ALL_NPSP_O): CFLAGS += -Wno-array-bounds

mrcal-pywrap.o: $(addsuffix .h,$(wildcard *.docstring))
mrcal/_mrcal$(PY_EXT_SUFFIX): mrcal-pywrap.o libmrcal.$(SO) libmrcal.$(SO).${ABI_VERSION}
	$(PY_MRBUILD_LINKER) $(PY_MRBUILD_LDFLAGS) $(LDFLAGS) $< -lmrcal -lsuitesparseconfig -o $@

PYTHON_OBJECTS := mrcal-pywrap.o python-cameramodel-converter.o $(ALL_NPSP_O)

$(PYTHON_OBJECTS): CFLAGS += $(PY_MRBUILD_CFLAGS)

# The python libraries (compiled ones and ones written in python) all live in
# mrcal/
DIST_PY3_MODULES := mrcal

all: mrcal/_mrcal$(PY_EXT_SUFFIX) $(ALL_NPSP_SO)
EXTRA_CLEAN += mrcal/*$(PY_EXT_SUFFIX)


TESTS_ALL_TARGETS := test-all test-nosampling test-triangulation-uncertainty test-external-data
$(TESTS_ALL_TARGETS): all
	./test.sh $@
.PHONY: $(TESTS_ALL_TARGETS)
test:
	@echo "Which test set should we run? I know about '$(TESTS_ALL_TARGETS)'" > /dev/stderr; false
.PHONY: test

include Makefile.doc

include $(MRBUILD_MK)/Makefile.common.footer

# to work with mrbuild < 1.14
OBJCOPY ?= objcopy
