Skip to main content

Makefile

# nx_tflite_mob — cross-compile tflite_nif.c for desktop & mobile targets.
#
# Two output flavors per target:
#   priv/<target>/libtflite_nif.so   — dynamic .so for standalone iex / bench
#                                       (android only; iOS forbids dlopen)
#   priv/<target>/libtflite_nif.a    — static archive for Mob's static-NIF
#                                       table (linked into launcher binary)
#
# Targets:
#   android         — android_arm64 .so + .a (NNAPI + XNNPACK)
#   ios_device      — ios_device .a (CoreML + Metal + XNNPACK)
#   ios_sim         — ios_sim .a (XNNPACK only — simulator has no ANE)
#   all_mobile      — android + ios_device + ios_sim
#   mac             — mac_arm64 .so for HOST-side tests (XNNPACK only,
#                     against a Bazel/CMake-built libtensorflowlite_c.dylib —
#                     TFLite has no Mac arm64 prebuilt distribution). Build
#                     the dylib via docs/build_mac_tflite.md first.

ERLANG_PATH := $(shell erl -noshell -eval 'io:format("~s/erts-16.4/include", [code:root_dir()])' -s init stop)

# ── Android ────────────────────────────────────────────────────────────────
ANDROID_NDK    ?= /Users/kevin/Library/Android/sdk/ndk/27.2.12479018
NDK_HOST       := darwin-x86_64
NDK_CC         := $(ANDROID_NDK)/toolchains/llvm/prebuilt/$(NDK_HOST)/bin/aarch64-linux-android29-clang
NDK_AR         := $(ANDROID_NDK)/toolchains/llvm/prebuilt/$(NDK_HOST)/bin/llvm-ar
NDK_RANLIB     := $(ANDROID_NDK)/toolchains/llvm/prebuilt/$(NDK_HOST)/bin/llvm-ranlib
TFLITE_AAR     := /tmp/tflite_android/aar
TFLITE_HDRS    := $(TFLITE_AAR)/headers
TFLITE_SO      := $(TFLITE_AAR)/jni/arm64-v8a/libtensorflowlite_jni.so

# ── iOS ────────────────────────────────────────────────────────────────────
IOS_MIN_VER    := 15.0
TFLITE_IOS_DIR := /tmp/tflite_ios/extracted/TensorFlowLiteC-2.17.0/Frameworks
# xcframework slices — one path per slice (device / simulator-arm64).
IOS_DEVICE_FW  := $(TFLITE_IOS_DIR)/TensorFlowLiteC.xcframework/ios-arm64
IOS_SIM_FW     := $(TFLITE_IOS_DIR)/TensorFlowLiteC.xcframework/ios-arm64_x86_64-simulator
IOS_DEVICE_CML := $(TFLITE_IOS_DIR)/TensorFlowLiteCCoreML.xcframework/ios-arm64
IOS_SIM_CML    := $(TFLITE_IOS_DIR)/TensorFlowLiteCCoreML.xcframework/ios-arm64_x86_64-simulator

IOS_DEV_SDK    := $(shell xcrun --sdk iphoneos --show-sdk-path)
IOS_SIM_SDK    := $(shell xcrun --sdk iphonesimulator --show-sdk-path)
IOS_CC         := $(shell xcrun --find clang)
IOS_AR         := $(shell xcrun --find ar)
IOS_RANLIB     := $(shell xcrun --find ranlib)

# ── Mac arm64 (host-side tests) ────────────────────────────────────────────
# Set MAC_TFLITE_DIR to the cache where you installed the dylib + headers.
# Default matches docs/build_mac_tflite.md's recipe.
MAC_TFLITE_DIR ?= $(HOME)/.mob/cache/tflite-2.16.1-mac_arm64
MAC_TFLITE_HDRS := $(MAC_TFLITE_DIR)/include
MAC_TFLITE_LIB  := $(MAC_TFLITE_DIR)/lib/libtensorflowlite_c.dylib
MAC_CC          := $(shell xcrun --find clang)

# ── Common ─────────────────────────────────────────────────────────────────
SRC            := c_src/tflite_nif.c

# Symbol name for the static NIF table. Mob's driver_tab references
# `tflite_nif_init` (see MobDev.StaticNifs default entry).
STATIC_NIF_DEF := -DSTATIC_ERLANG_NIF_LIBNAME=tflite_nif

# ============================================================================
# Targets
# ============================================================================

.PHONY: android ios_device ios_sim mac all_mobile clean

all_mobile: android ios_device ios_sim

# ── Android ────────────────────────────────────────────────────────────────

android: priv/android_arm64/libtflite_nif.so priv/android_arm64/libtflite_nif.a

priv/android_arm64/libtflite_nif.so: $(SRC)
	@mkdir -p $(@D)
	$(NDK_CC) -O2 -Wall -fPIC -shared \
	    -I$(ERLANG_PATH) \
	    -I$(TFLITE_HDRS) \
	    -Wl,--undefined-version \
	    -Wl,-rpath,'$$ORIGIN' \
	    -Wl,--enable-new-dtags \
	    $< $(TFLITE_SO) \
	    -ldl -llog -lm \
	    -o $@
	@echo "built $@"

priv/android_arm64/libtflite_nif.a: $(SRC)
	@mkdir -p $(@D)
	$(NDK_CC) -O2 -Wall -fPIC -c \
	    $(STATIC_NIF_DEF) \
	    -I$(ERLANG_PATH) \
	    -I$(TFLITE_HDRS) \
	    $< -o $(@D)/tflite_nif.o
	$(NDK_AR) rcs $@ $(@D)/tflite_nif.o
	$(NDK_RANLIB) $@
	@echo "built $@ ($$($(NDK_AR) -t $@ | wc -l) members)"
	@$(ANDROID_NDK)/toolchains/llvm/prebuilt/$(NDK_HOST)/bin/llvm-nm $@ | grep tflite_nif_nif_init || (echo "ERROR: tflite_nif_nif_init symbol missing!" && exit 1)

# Backwards-compat: old layout had priv/android/libtflite_nif.so
priv/android/libtflite_nif.so: priv/android_arm64/libtflite_nif.so
	@mkdir -p $(@D) && cp $< $@

# ── iOS device (arm64) ─────────────────────────────────────────────────────

ios_device: priv/ios_device/libtflite_nif.a

priv/ios_device/libtflite_nif.a: $(SRC)
	@mkdir -p $(@D)
	$(IOS_CC) -O2 -Wall -fPIC -c \
	    -arch arm64 \
	    -target arm64-apple-ios$(IOS_MIN_VER) \
	    -isysroot $(IOS_DEV_SDK) \
	    -fembed-bitcode-marker \
	    $(STATIC_NIF_DEF) \
	    -I$(ERLANG_PATH) \
	    -F$(IOS_DEVICE_FW) -F$(IOS_DEVICE_CML) \
	    $< -o $(@D)/tflite_nif.o
	$(IOS_AR) rcs $@ $(@D)/tflite_nif.o
	$(IOS_RANLIB) $@
	@echo "built $@"
	@nm $@ | grep tflite_nif_nif_init || (echo "ERROR: tflite_nif_nif_init symbol missing!" && exit 1)

# ── iOS simulator (arm64 — apple silicon only; no x86_64) ──────────────────

ios_sim: priv/ios_sim/libtflite_nif.a

priv/ios_sim/libtflite_nif.a: $(SRC)
	@mkdir -p $(@D)
	$(IOS_CC) -O2 -Wall -fPIC -c \
	    -arch arm64 \
	    -target arm64-apple-ios$(IOS_MIN_VER)-simulator \
	    -isysroot $(IOS_SIM_SDK) \
	    $(STATIC_NIF_DEF) \
	    -I$(ERLANG_PATH) \
	    -F$(IOS_SIM_FW) -F$(IOS_SIM_CML) \
	    $< -o $(@D)/tflite_nif.o
	$(IOS_AR) rcs $@ $(@D)/tflite_nif.o
	$(IOS_RANLIB) $@
	@echo "built $@"
	@nm $@ | grep tflite_nif_nif_init || (echo "ERROR: tflite_nif_nif_init symbol missing!" && exit 1)

# ── Mac arm64 (host) ───────────────────────────────────────────────────────
# Dynamic .so for `iex -S mix` + `mix test`. Links against the CMake-built
# libtensorflowlite_c.dylib at MAC_TFLITE_DIR (default ~/.mob/cache/...).
# Embedded rpath of $ORIGIN means the .so finds its TFLite dylib if you
# copy it alongside; we also use -Wl,-rpath,<absolute-cache-path> so it
# resolves out of the cache without copying.

mac: priv/mac/libtflite_nif.so

MAC_SDK := $(shell xcrun --sdk macosx --show-sdk-path)

priv/mac/libtflite_nif.so: $(SRC)
	@if [ ! -f "$(MAC_TFLITE_LIB)" ]; then \
		echo "ERROR: $(MAC_TFLITE_LIB) not found."; \
		echo "Build it first — see docs/build_mac_tflite.md"; \
		exit 1; \
	fi
	@mkdir -p $(@D)
	$(MAC_CC) -O2 -Wall -fPIC -dynamiclib \
	    -isysroot $(MAC_SDK) \
	    -I$(ERLANG_PATH) \
	    -I$(MAC_TFLITE_HDRS) \
	    -Wl,-rpath,$(MAC_TFLITE_DIR)/lib \
	    -undefined dynamic_lookup \
	    $< $(MAC_TFLITE_LIB) \
	    -o $@
	@echo "built $@"

# ── Clean ──────────────────────────────────────────────────────────────────

clean:
	rm -rf priv/android priv/android_arm64 priv/ios_device priv/ios_sim priv/mac priv/native