# =============================================================================
# Makefile – statically-linked CPython-embedded binaries
#
# Targets:
#   make linux        – x86_64 Linux static binary   (out/runner-linux-x86_64)
#   make android      – arm64-v8a Android static bin  (out/runner-android-arm64)
#   make android-v7   – armeabi-v7a Android static bin (out/runner-android-armv7)
#   make all          – all three
#   make clean        – remove build artefacts
#
# Prerequisites (env vars that must be set before invoking make):
#   CPYTHON_PATH      – root of a CPython source checkout
#   NDK_BUILD_PATH    – root of the Android NDK (r21 recommended)
#
# CPython must be configured + built separately for each target before
# running this Makefile.  See README-BUILD.md for step-by-step instructions.
# The Makefile expects the following layout inside CPYTHON_PATH:
#
#   $CPYTHON_PATH/
#     cpython-build-linux/          ← native (x86_64) build directory
#       libpython3.X.a
#       pyconfig.h                  (generated header)
#     cpython-build-android-arm64/  ← cross build directory for arm64-v8a
#       libpython3.X.a
#       pyconfig.h
#     cpython-build-android-armv7/  ← cross build directory for armeabi-v7a
#       libpython3.X.a
#       pyconfig.h
#     Include/                      ← CPython public headers
#
# =============================================================================

# ---------------------------------------------------------------------------
# Sanity checks
# ---------------------------------------------------------------------------
ifndef CPYTHON_PATH
  $(error CPYTHON_PATH is not set. Point it at your CPython source checkout.)
endif
ifndef NDK_BUILD_PATH
  $(error NDK_BUILD_PATH is not set. Point it at your Android NDK root.)
endif

# ---------------------------------------------------------------------------
# Paths
# ---------------------------------------------------------------------------
OUT        := out
SCRIPT_SRC := hello.py
RUNNER_SRC := runner.c

CPYTHON_INCLUDE := $(CPYTHON_PATH)/Include

# Build directories (where you ran ./configure + make for each target)
BUILD_LINUX        := $(CPYTHON_PATH)/cpython-build-linux
BUILD_ANDROID_A64  := $(CPYTHON_PATH)/cpython-build-android-arm64
BUILD_ANDROID_V7   := $(CPYTHON_PATH)/cpython-build-android-armv7

# NDK toolchain (NDK r21 layout)
# Adjust API level here if needed (21 = Android 5.0, widely compatible)
NDK_API      := 21
NDK_HOST     := $(shell uname -s | tr '[:upper:]' '[:lower:]')-x86_64
NDK_TC       := $(NDK_BUILD_PATH)/toolchains/llvm/prebuilt/$(NDK_HOST)/bin

# Compilers
CC_LINUX         := gcc
CC_ANDROID_A64   := $(NDK_TC)/aarch64-linux-android$(NDK_API)-clang
CC_ANDROID_V7    := $(NDK_TC)/armv7a-linux-androideabi$(NDK_API)-clang

# objcopy variants
OBJCOPY_LINUX       := objcopy
# NDK ships llvm-objcopy; fall back to any cross-objcopy in PATH
OBJCOPY_ANDROID_A64 := $(NDK_TC)/llvm-objcopy
OBJCOPY_ANDROID_V7  := $(NDK_TC)/llvm-objcopy

# ---------------------------------------------------------------------------
# Common CFLAGS
# ---------------------------------------------------------------------------
CFLAGS_COMMON := \
    -O2 \
    -Wall \
    -Wextra \
    -ffunction-sections \
    -fdata-sections

# Linker flags: pull in everything needed by a static CPython build.
# We explicitly exclude ssl, readline, and curses to keep deps minimal.
# -lm -ldl -lpthread -lutil are usually the only system libs required.
LDFLAGS_COMMON := \
    -static \
    -Wl,--gc-sections \
    -lm -ldl -lpthread

# Android static executables additionally need these (Bionic provides them
# but some are in separate .a files in older NDK builds).
LDFLAGS_ANDROID_EXTRA := \
    -lm -ldl

# ---------------------------------------------------------------------------
# Derived artefact names
# ---------------------------------------------------------------------------
# objcopy embeds the raw file; the symbol prefix is derived from the filename
# with non-alphanumeric chars replaced by '_'.
# hello.py  →  _binary_hello_py_start / _binary_hello_py_end
SCRIPT_OBJ_LINUX       := $(OUT)/hello_py-linux.o
SCRIPT_OBJ_ANDROID_A64 := $(OUT)/hello_py-android-arm64.o
SCRIPT_OBJ_ANDROID_V7  := $(OUT)/hello_py-android-armv7.o

RUNNER_OBJ_LINUX        := $(OUT)/runner-linux.o
RUNNER_OBJ_ANDROID_A64  := $(OUT)/runner-android-arm64.o
RUNNER_OBJ_ANDROID_V7   := $(OUT)/runner-android-armv7.o

BIN_LINUX        := $(OUT)/runner-linux-x86_64
BIN_ANDROID_A64  := $(OUT)/runner-android-arm64
BIN_ANDROID_V7   := $(OUT)/runner-android-armv7

# ---------------------------------------------------------------------------
# Phony targets
# ---------------------------------------------------------------------------
.PHONY: all linux android android-v7 android-all clean help

all: linux android-all

linux:      $(BIN_LINUX)
android:    $(BIN_ANDROID_A64)
android-v7: $(BIN_ANDROID_V7)
android-all: $(BIN_ANDROID_A64) $(BIN_ANDROID_V7)

$(OUT):
	mkdir -p $(OUT)

# ---------------------------------------------------------------------------
# Embed hello.py as a binary object for each target
#
# objcopy flags:
#   -I binary          treat input as raw binary
#   -O <bfdname>       output BFD target (elf64-x86-64 / elf32-littlearm / etc.)
#   -B <arch>          set architecture
#   --rename-section   put the blob in .rodata (read-only, avoids W^X issues)
#
# llvm-objcopy (NDK) uses the same flags as GNU objcopy for this use-case.
# ---------------------------------------------------------------------------
$(SCRIPT_OBJ_LINUX): $(SCRIPT_SRC) | $(OUT)
	$(OBJCOPY_LINUX) \
	    -I binary \
	    -O elf64-x86-64 \
	    -B i386:x86-64 \
	    --rename-section .data=.rodata,alloc,load,readonly,data,contents \
	    $< $@

$(SCRIPT_OBJ_ANDROID_A64): $(SCRIPT_SRC) | $(OUT)
	$(OBJCOPY_ANDROID_A64) \
	    -I binary \
	    -O elf64-littleaarch64 \
	    -B aarch64 \
	    --rename-section .data=.rodata,alloc,load,readonly,data,contents \
	    $< $@

$(SCRIPT_OBJ_ANDROID_V7): $(SCRIPT_SRC) | $(OUT)
	$(OBJCOPY_ANDROID_V7) \
	    -I binary \
	    -O elf32-littlearm \
	    -B arm \
	    --rename-section .data=.rodata,alloc,load,readonly,data,contents \
	    $< $@

# ---------------------------------------------------------------------------
# Compile runner.c for each target
# ---------------------------------------------------------------------------
$(RUNNER_OBJ_LINUX): $(RUNNER_SRC) | $(OUT)
	$(CC_LINUX) $(CFLAGS_COMMON) \
	    -I$(CPYTHON_INCLUDE) \
	    -I$(BUILD_LINUX) \
	    -c $< -o $@

$(RUNNER_OBJ_ANDROID_A64): $(RUNNER_SRC) | $(OUT)
	$(CC_ANDROID_A64) $(CFLAGS_COMMON) \
	    -I$(CPYTHON_INCLUDE) \
	    -I$(BUILD_ANDROID_A64) \
	    -c $< -o $@

$(RUNNER_OBJ_ANDROID_V7): $(RUNNER_SRC) | $(OUT)
	$(CC_ANDROID_V7) $(CFLAGS_COMMON) \
	    -I$(CPYTHON_INCLUDE) \
	    -I$(BUILD_ANDROID_V7) \
	    -c $< -o $@

# ---------------------------------------------------------------------------
# Link final binaries
#
# The static CPython archive must come BEFORE -lm/-ldl/-lpthread so the
# linker resolves Python's references into them.
# We also need to wrap the whole CPython archive in --whole-archive for
# builds where the archive contains initialiser functions that are not
# directly referenced from runner.c (e.g. extension module init hooks).
# ---------------------------------------------------------------------------
$(BIN_LINUX): $(RUNNER_OBJ_LINUX) $(SCRIPT_OBJ_LINUX)
	$(CC_LINUX) \
	    $(RUNNER_OBJ_LINUX) \
	    $(SCRIPT_OBJ_LINUX) \
	    -L$(BUILD_LINUX) \
	    -Wl,--whole-archive $(BUILD_LINUX)/libpython*.a -Wl,--no-whole-archive \
	    $(LDFLAGS_COMMON) \
	    -lutil \
	    -o $@
	@echo "✓  Linux binary: $@  ($(shell du -sh $@ | cut -f1))"

$(BIN_ANDROID_A64): $(RUNNER_OBJ_ANDROID_A64) $(SCRIPT_OBJ_ANDROID_A64)
	$(CC_ANDROID_A64) \
	    $(RUNNER_OBJ_ANDROID_A64) \
	    $(SCRIPT_OBJ_ANDROID_A64) \
	    -L$(BUILD_ANDROID_A64) \
	    -Wl,--whole-archive $(BUILD_ANDROID_A64)/libpython*.a -Wl,--no-whole-archive \
	    $(LDFLAGS_COMMON) \
	    $(LDFLAGS_ANDROID_EXTRA) \
	    -o $@
	@echo "✓  Android arm64 binary: $@  ($(shell du -sh $@ | cut -f1))"

$(BIN_ANDROID_V7): $(RUNNER_OBJ_ANDROID_V7) $(SCRIPT_OBJ_ANDROID_V7)
	$(CC_ANDROID_V7) \
	    $(RUNNER_OBJ_ANDROID_V7) \
	    $(SCRIPT_OBJ_ANDROID_V7) \
	    -L$(BUILD_ANDROID_V7) \
	    -Wl,--whole-archive $(BUILD_ANDROID_V7)/libpython*.a -Wl,--no-whole-archive \
	    $(LDFLAGS_COMMON) \
	    $(LDFLAGS_ANDROID_EXTRA) \
	    -o $@
	@echo "✓  Android armv7 binary: $@  ($(shell du -sh $@ | cut -f1))"

# ---------------------------------------------------------------------------
clean:
	rm -rf $(OUT)

help:
	@echo "Targets:  linux | android | android-v7 | android-all | all | clean"
	@echo "Env vars: CPYTHON_PATH  NDK_BUILD_PATH"
