CC ?= cc
AR ?= ar
FUZZ_CC ?= clang
CFLAGS ?= -std=c11 -Wall -Wextra -Wpedantic -Werror -O2
DEPFLAGS := -MMD -MP
CPPFLAGS ?= -Iinclude
VERSION := $(shell cat VERSION)
CPPFLAGS += -DHHY_VERSION=\"$(VERSION)\"
PCRE2_PREFIX ?= $(shell brew --prefix pcre2 2>/dev/null)
GC_PREFIX ?= $(shell brew --prefix bdw-gc 2>/dev/null)
JANSSON_PREFIX ?= $(shell brew --prefix jansson 2>/dev/null)
OPENSSL_PREFIX ?= $(shell brew --prefix openssl@3 2>/dev/null)
ZLIB_PREFIX ?= $(shell brew --prefix zlib 2>/dev/null)
ifneq ($(PCRE2_PREFIX),)
CPPFLAGS += -I$(PCRE2_PREFIX)/include
LDFLAGS += -L$(PCRE2_PREFIX)/lib
endif
ifneq ($(GC_PREFIX),)
CPPFLAGS += -I$(GC_PREFIX)/include
LDFLAGS += -L$(GC_PREFIX)/lib
endif
ifneq ($(JANSSON_PREFIX),)
CPPFLAGS += -I$(JANSSON_PREFIX)/include
LDFLAGS += -L$(JANSSON_PREFIX)/lib
endif
ifneq ($(OPENSSL_PREFIX),)
CPPFLAGS += -I$(OPENSSL_PREFIX)/include
LDFLAGS += -L$(OPENSSL_PREFIX)/lib
endif
ifneq ($(ZLIB_PREFIX),)
CPPFLAGS += -I$(ZLIB_PREFIX)/include
LDFLAGS += -L$(ZLIB_PREFIX)/lib
endif
LDLIBS ?= -lcurl -lpcre2-8 -lgc -ljansson -lcrypto -lz -lm

SOURCES := $(wildcard src/*.c)
SOURCE_NAMES := $(notdir $(SOURCES:.c=.o))
OBJECTS := $(addprefix build/release/,$(SOURCE_NAMES))
DEBUG_OBJECTS := $(addprefix build/debug/,$(SOURCE_NAMES))
TARGET := build/hhy
STATIC_LIBRARY := build/libhhy.a
LIB_OBJECTS := $(filter-out build/release/main.o,$(OBJECTS))
DEBUG_TARGET := build/hhy-debug
FUZZ_TARGET := build/hhy-fuzz
BYTECODE_TEST_TARGET := build/hhy-bytecode-test
EMBED_TEST_TARGET := build/hhy-embed-test
LIBFUZZ_TARGET := build/hhy-libfuzzer
FUZZ_SOURCES := $(filter-out src/main.c,$(SOURCES)) tests/fuzz_runtime.c
SYSTEM := $(shell uname -s | tr '[:upper:]' '[:lower:]')
MACHINE := $(shell uname -m)
ARCH := $(if $(filter arm64 aarch64,$(MACHINE)),arm64,$(MACHINE))
PACKAGE := hhy-$(VERSION)-$(SYSTEM)-$(ARCH)
PREFIX ?= /usr/local
BINDIR ?= $(PREFIX)/bin

.PHONY: all clean extensions test test-bytecode workload-test test-debug debug benchmark benchmark-bytecode benchmark-profiler benchmark-bytecode-cache quality install dist registry-package bytecode-test embed-test fuzz fuzz-smoke fuzz-libfuzzer fuzz-ci

all: $(TARGET) $(STATIC_LIBRARY)

$(TARGET): $(OBJECTS)
	@mkdir -p build
	$(CC) $(CFLAGS) $(LDFLAGS) $(OBJECTS) $(LDLIBS) -o $@

$(STATIC_LIBRARY): $(LIB_OBJECTS)
	@mkdir -p build
	$(AR) rcs $@ $(LIB_OBJECTS)

build/release/%.o: src/%.c
	@mkdir -p build/release
	$(CC) $(CPPFLAGS) $(CFLAGS) $(DEPFLAGS) -c $< -o $@

$(OBJECTS): VERSION

SANITIZERS ?= address,undefined
DEBUG_CFLAGS := -std=c11 -Wall -Wextra -Wpedantic -Werror -O0 -g3 -fsanitize=$(SANITIZERS)

$(DEBUG_TARGET): $(DEBUG_OBJECTS)
	@mkdir -p build
	$(CC) $(DEBUG_CFLAGS) $(LDFLAGS) $(DEBUG_OBJECTS) $(LDLIBS) -o $@

build/debug/%.o: src/%.c
	@mkdir -p build/debug
	$(CC) $(CPPFLAGS) $(DEBUG_CFLAGS) $(DEPFLAGS) -c $< -o $@

$(DEBUG_OBJECTS): VERSION

debug: $(DEBUG_TARGET)

extensions:
	$(MAKE) -C extensions/sample
	$(MAKE) -C extensions/database
	$(MAKE) -C extensions/html

$(BYTECODE_TEST_TARGET): tests/bytecode_alpha.c src/bytecode.c src/typed_plan.c src/compiler.c src/resolver.c src/common.c include/hhy/bytecode.h include/hhy/compiler.h
	@mkdir -p build
	$(CC) $(CPPFLAGS) $(CFLAGS) tests/bytecode_alpha.c src/bytecode.c src/typed_plan.c src/compiler.c src/resolver.c src/common.c -o $@

build/hhy-exception-test: tests/exception_regions.c src/bytecode.c src/typed_plan.c src/compiler.c src/resolver.c src/ast.c src/lexer.c src/parser.c src/common.c include/hhy/bytecode.h include/hhy/compiler.h
	@mkdir -p build
	$(CC) $(CPPFLAGS) $(CFLAGS) tests/exception_regions.c src/bytecode.c src/typed_plan.c src/compiler.c src/resolver.c src/ast.c src/lexer.c src/parser.c src/common.c -o $@

bytecode-test: $(BYTECODE_TEST_TARGET) build/hhy-exception-test
	$(BYTECODE_TEST_TARGET)
	build/hhy-exception-test

$(EMBED_TEST_TARGET): tests/embed_runtime.c $(filter-out build/release/main.o,$(OBJECTS))
	@mkdir -p build
	$(CC) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) tests/embed_runtime.c $(filter-out build/release/main.o,$(OBJECTS)) $(LDLIBS) -o $@

build/hhy-dispatch-test: tests/dispatch_profile.c src/profiler.c src/runtime_lookup.c src/bytecode.c src/typed_plan.c src/compiler.c src/resolver.c src/common.c include/hhy/profiler.h include/hhy/bytecode.h src/runtime_lookup.h include/hhy/compiler.h
	@mkdir -p build
	$(CC) $(CPPFLAGS) $(CFLAGS) tests/dispatch_profile.c src/profiler.c src/runtime_lookup.c src/bytecode.c src/typed_plan.c src/compiler.c src/resolver.c src/common.c -o $@

build/hhy-unwind-test: tests/runtime_unwind.c src/runtime_unwind.c src/runtime_unwind.h
	@mkdir -p build
	$(CC) $(CPPFLAGS) $(CFLAGS) -Isrc tests/runtime_unwind.c src/runtime_unwind.c -o $@

build/hhy-embed-unwind-test: tests/embed_unwind.c $(LIB_OBJECTS)
	$(CC) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) tests/embed_unwind.c $(LIB_OBJECTS) $(LDLIBS) -o $@

build/hhy-embed-unwind-debug: tests/embed_unwind.c $(filter-out build/debug/main.o,$(DEBUG_OBJECTS))
	$(CC) $(CPPFLAGS) $(DEBUG_CFLAGS) $(LDFLAGS) tests/embed_unwind.c $(filter-out build/debug/main.o,$(DEBUG_OBJECTS)) $(LDLIBS) -o $@

build/hhy-file-unwind-test: tests/runtime_file_unwind.c $(LIB_OBJECTS)
	$(CC) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) tests/runtime_file_unwind.c $(filter-out build/release/runtime.o,$(LIB_OBJECTS)) $(LDLIBS) -o $@

build/hhy-file-unwind-debug: tests/runtime_file_unwind.c $(filter-out build/debug/main.o,$(DEBUG_OBJECTS))
	$(CC) $(CPPFLAGS) $(DEBUG_CFLAGS) $(LDFLAGS) tests/runtime_file_unwind.c $(filter-out build/debug/main.o build/debug/runtime.o,$(DEBUG_OBJECTS)) $(LDLIBS) -o $@

build/hhy-call-unwind-test: tests/runtime_call_unwind.c $(LIB_OBJECTS)
	$(CC) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) tests/runtime_call_unwind.c $(filter-out build/release/runtime.o,$(LIB_OBJECTS)) $(LDLIBS) -o $@

build/hhy-call-unwind-debug: tests/runtime_call_unwind.c $(filter-out build/debug/main.o,$(DEBUG_OBJECTS))
	$(CC) $(CPPFLAGS) $(DEBUG_CFLAGS) $(LDFLAGS) tests/runtime_call_unwind.c $(filter-out build/debug/main.o build/debug/runtime.o,$(DEBUG_OBJECTS)) $(LDLIBS) -o $@

embed-test: $(EMBED_TEST_TARGET)
	$(EMBED_TEST_TARGET)

test: build/hhy-concurrency-test build/hhy-typed-test build/hhy-compiler-test $(TARGET) extensions bytecode-test embed-test build/hhy-dispatch-test build/hhy-unwind-test build/hhy-embed-unwind-test build/hhy-file-unwind-test build/hhy-call-unwind-test
	build/hhy-concurrency-test ast
	build/hhy-concurrency-test bytecode
	python3 tests/check-concurrency.py --binary $(TARGET)
	build/hhy-typed-test
	python3 tests/check-typed-specialization.py $(TARGET)
	build/hhy-compiler-test
	python3 tests/check-compiler-program.py $(TARGET)
	python3 tests/check-compiler-observability.py $(TARGET)
	build/hhy-unwind-test
	HHY_CALL_FRAME_UNWIND=1 build/hhy-call-unwind-test ast
	HHY_CALL_FRAME_UNWIND=1 build/hhy-call-unwind-test bytecode
	python3 tests/check-file-unwind.py
	python3 tests/check-embed-unwind.py
	python3 tests/check-dispatch-boundaries.py build/hhy-dispatch-test
	sh tests/run.sh $(TARGET)

test-bytecode: $(TARGET) extensions bytecode-test
	HHY_ENGINE=bytecode sh tests/run.sh $(TARGET)

workload-test: $(TARGET) extensions
	python3 scripts/run-bytecode-workloads.py --binary $(TARGET)

test-debug: build/hhy-concurrency-debug build/hhy-typed-test-debug build/hhy-compiler-test-debug $(DEBUG_TARGET) extensions bytecode-test build/hhy-embed-unwind-debug build/hhy-file-unwind-debug build/hhy-call-unwind-debug
	build/hhy-concurrency-debug ast
	build/hhy-concurrency-debug bytecode
	python3 tests/check-concurrency.py --binary $(DEBUG_TARGET) --output build/concurrency-debug.json
	build/hhy-typed-test-debug
	python3 tests/check-typed-specialization.py $(DEBUG_TARGET)
	build/hhy-compiler-test-debug
	python3 tests/check-compiler-program.py $(DEBUG_TARGET)
	python3 tests/check-compiler-observability.py $(DEBUG_TARGET)
	HHY_CALL_FRAME_UNWIND=1 build/hhy-call-unwind-debug ast
	HHY_CALL_FRAME_UNWIND=1 build/hhy-call-unwind-debug bytecode
	python3 tests/check-file-unwind.py --binary build/hhy-file-unwind-debug --output build/file-unwind-debug.json
	python3 tests/check-embed-unwind.py --binary build/hhy-embed-unwind-debug --output build/embed-unwind-debug.json
	HHY_SKIP_GC_STRESS=1 sh tests/run.sh $(DEBUG_TARGET)

benchmark: $(TARGET)
	python3 scripts/run-benchmarks.py --binary $(TARGET)
	python3 scripts/check-performance.py

benchmark-bytecode: $(TARGET)
	python3 scripts/run-benchmarks.py --binary $(TARGET) --iterations 15 --output build/benchmarks/engine-comparison.json
	python3 scripts/check-performance.py --report build/benchmarks/engine-comparison.json
	python3 scripts/check-bytecode-rc.py --require-ready
	$(MAKE) benchmark-profiler
	$(MAKE) benchmark-bytecode-cache

benchmark-profiler: $(TARGET)
	python3 scripts/check-profiler-overhead.py --binary $(TARGET)

benchmark-bytecode-cache: $(TARGET)
	python3 scripts/evaluate-bytecode-cache.py --binary $(TARGET)
	python3 scripts/check-bytecode-cache-decision.py

quality: $(TARGET)
	python3 tests/check_contracts.py
	python3 tests/check_runtime_governance.py
	python3 tests/check-bytecode-cache-governance.py $(TARGET)
	sh tests/check-promotion-assets.sh
	sh tests/check-docs.sh $(TARGET) README.md
	sh tests/check-docs.sh $(TARGET) README.zh-CN.md
	python3 scripts/check-repository.py
	$(MAKE) benchmark

fuzz:
	@mkdir -p build
	$(CC) $(CPPFLAGS) -DHHY_STANDALONE_FUZZ -std=c11 -Wall -Wextra -Wpedantic -O1 -g \
		-fsanitize=address,undefined $(FUZZ_SOURCES) $(LDFLAGS) $(LDLIBS) -o $(FUZZ_TARGET)

fuzz-smoke: fuzz
	$(FUZZ_TARGET) 1000

fuzz-libfuzzer:
	@mkdir -p build
	$(FUZZ_CC) $(CPPFLAGS) -std=c11 -Wall -Wextra -Wpedantic -O1 -g \
		-fsanitize=fuzzer,address,undefined $(FUZZ_SOURCES) $(LDFLAGS) $(LDLIBS) -o $(LIBFUZZ_TARGET)

fuzz-ci: fuzz-libfuzzer
	rm -rf build/fuzz-corpus
	cp -R tests/fuzz-corpus build/fuzz-corpus
	$(LIBFUZZ_TARGET) -max_total_time=15 -timeout=5 -rss_limit_mb=1024 build/fuzz-corpus

install: $(TARGET) $(STATIC_LIBRARY)
	install -d $(DESTDIR)$(BINDIR)
	install -m 755 $(TARGET) $(DESTDIR)$(BINDIR)/hhy
	install -d $(DESTDIR)$(PREFIX)/lib $(DESTDIR)$(PREFIX)/include/hhy
	install -m 644 $(STATIC_LIBRARY) $(DESTDIR)$(PREFIX)/lib/libhhy.a
	install -m 644 include/hhy/*.h $(DESTDIR)$(PREFIX)/include/hhy/

dist:
	$(MAKE) clean
	$(MAKE) all extensions
	rm -rf build/$(PACKAGE)
	mkdir -p build/$(PACKAGE)/bin build/$(PACKAGE)/lib build/$(PACKAGE)/sdk/lib build/$(PACKAGE)/sdk/include/hhy build/$(PACKAGE)/examples \
		build/$(PACKAGE)/extensions/sample/bin build/$(PACKAGE)/extensions/database/bin \
		build/$(PACKAGE)/extensions/html/bin dist
	cp $(TARGET) build/$(PACKAGE)/bin/hhy
	cp $(STATIC_LIBRARY) build/$(PACKAGE)/sdk/lib/libhhy.a
	cp include/hhy/*.h build/$(PACKAGE)/sdk/include/hhy/
	cp README.md README.zh-CN.md INSTALL.md LICENSE NOTICE build/$(PACKAGE)/
	CC="$(CC)" sh scripts/build-info.sh $(TARGET) > build/$(PACKAGE)/BUILD_INFO.txt
	cp examples/*.hhy examples/README.md build/$(PACKAGE)/examples/
	cp extensions/README.md build/$(PACKAGE)/extensions/
	cp extensions/sample/hhy.toml extensions/sample/bin/hhy-sample build/$(PACKAGE)/extensions/sample/
	mv build/$(PACKAGE)/extensions/sample/hhy-sample build/$(PACKAGE)/extensions/sample/bin/
	cp extensions/database/hhy.toml extensions/database/README.md extensions/database/CHANGELOG.md extensions/database/ACCEPTANCE.md extensions/database/bin/hhy-database build/$(PACKAGE)/extensions/database/
	cp LICENSE NOTICE build/$(PACKAGE)/extensions/database/
	cp build/$(PACKAGE)/BUILD_INFO.txt build/$(PACKAGE)/extensions/database/
	mv build/$(PACKAGE)/extensions/database/hhy-database build/$(PACKAGE)/extensions/database/bin/
	cp extensions/html/hhy.toml extensions/html/bin/hhy-html build/$(PACKAGE)/extensions/html/
	mv build/$(PACKAGE)/extensions/html/hhy-html build/$(PACKAGE)/extensions/html/bin/
	sh scripts/bundle-runtime.sh build/$(PACKAGE)
	COPYFILE_DISABLE=1 tar -C build -czf dist/$(PACKAGE).tar.gz $(PACKAGE)
	@if command -v sha256sum >/dev/null 2>&1; then \
		(cd dist && sha256sum $(PACKAGE).tar.gz > $(PACKAGE).tar.gz.sha256); \
	else \
		(cd dist && shasum -a 256 $(PACKAGE).tar.gz > $(PACKAGE).tar.gz.sha256); \
	fi

registry-package: extensions
	python3 scripts/build-registry-package.py

clean:
	rm -f src/*.o
	rm -rf build
	$(MAKE) -C extensions/sample clean
	$(MAKE) -C extensions/database clean
	$(MAKE) -C extensions/html clean

-include $(OBJECTS:.o=.d) $(DEBUG_OBJECTS:.o=.d)

.PHONY: test-vm-call-policy
test-vm-call-policy: $(TARGET)
	python3 scripts/check-vm-call-runtime-policy.py $(TARGET)

# Isolated process owns the Boehm event hook; production binaries are unchanged.
build/hhy-resource-probe: tests/runtime_resource_probe.c $(LIB_OBJECTS)
	$(CC) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) tests/runtime_resource_probe.c $(filter-out build/release/runtime.o,$(LIB_OBJECTS)) $(LDLIBS) -o $@

build/hhy-resource-probe-debug: tests/runtime_resource_probe.c $(filter-out build/debug/main.o,$(DEBUG_OBJECTS))
	$(CC) $(CPPFLAGS) $(DEBUG_CFLAGS) $(LDFLAGS) tests/runtime_resource_probe.c $(filter-out build/debug/main.o build/debug/runtime.o,$(DEBUG_OBJECTS)) $(LDLIBS) -o $@

.PHONY: test-runtime-resources
test-runtime-resources: $(TARGET) build/hhy-resource-probe
	python3 tests/check-runtime-resources.py

# Research IR is deliberately excluded from production SOURCES and Runtime.
IR_PROBE_SOURCES := compiler/ir_probe.c compiler/ir.c src/ast.c src/lexer.c src/parser.c src/common.c
build/hhy-ir-probe: $(IR_PROBE_SOURCES) compiler/ir.h
	@mkdir -p $(@D)
	$(CC) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) $(IR_PROBE_SOURCES) $(LDLIBS) -o $@

build/hhy-ir-probe-debug: $(IR_PROBE_SOURCES) compiler/ir.h
	@mkdir -p $(@D)
	$(CC) $(CPPFLAGS) $(DEBUG_CFLAGS) $(LDFLAGS) $(IR_PROBE_SOURCES) $(LDLIBS) -o $@

build/hhy-ir-test: tests/compiler_ir.c compiler/ir.c compiler/ir.h
	@mkdir -p $(@D)
	$(CC) $(CPPFLAGS) $(CFLAGS) tests/compiler_ir.c compiler/ir.c -o $@

build/hhy-ir-test-debug: tests/compiler_ir.c compiler/ir.c compiler/ir.h
	@mkdir -p $(@D)
	$(CC) $(CPPFLAGS) $(DEBUG_CFLAGS) tests/compiler_ir.c compiler/ir.c -o $@

.PHONY: test-compiler-ir
test-compiler-ir: $(TARGET) build/hhy-ir-probe build/hhy-ir-test
	build/hhy-ir-test
	python3 tests/check-compiler-ir.py

COMPILER_TEST_SOURCES := src/compiler.c src/bytecode.c src/typed_plan.c src/ast.c src/resolver.c src/lexer.c src/parser.c src/common.c
build/hhy-compiler-probe: compiler/program_probe.c $(COMPILER_TEST_SOURCES) include/hhy/compiler.h
	@mkdir -p $(@D)
	$(CC) $(CPPFLAGS) $(CFLAGS) compiler/program_probe.c $(COMPILER_TEST_SOURCES) -o $@

build/hhy-compiler-test: tests/compiler_program.c $(COMPILER_TEST_SOURCES) include/hhy/compiler.h
	@mkdir -p $(@D)
	$(CC) $(CPPFLAGS) $(CFLAGS) tests/compiler_program.c $(COMPILER_TEST_SOURCES) -o $@

build/hhy-compiler-test-debug: tests/compiler_program.c $(COMPILER_TEST_SOURCES) include/hhy/compiler.h
	@mkdir -p $(@D)
	$(CC) $(CPPFLAGS) $(DEBUG_CFLAGS) tests/compiler_program.c $(COMPILER_TEST_SOURCES) -o $@

.PHONY: test-compiler
test-compiler: build/hhy-typed-test $(TARGET) build/hhy-compiler-probe build/hhy-compiler-test
	build/hhy-typed-test
	python3 tests/check-typed-specialization.py $(TARGET)
	build/hhy-compiler-test
	python3 tests/check-compiler-program.py $(TARGET)
	python3 tests/check-compiler-observability.py $(TARGET)

build/hhy-typed-test: tests/typed_plan.c $(COMPILER_TEST_SOURCES) include/hhy/typed_plan.h
	@mkdir -p $(@D)
	$(CC) $(CPPFLAGS) $(CFLAGS) tests/typed_plan.c $(COMPILER_TEST_SOURCES) -o $@

build/hhy-typed-test-debug: tests/typed_plan.c $(COMPILER_TEST_SOURCES) include/hhy/typed_plan.h
	@mkdir -p $(@D)
	$(CC) $(CPPFLAGS) $(DEBUG_CFLAGS) tests/typed_plan.c $(COMPILER_TEST_SOURCES) -o $@

# Local concurrency experiments use the existing private Runtime harness boundary.
build/hhy-concurrency-test: tests/runtime_concurrency.c $(LIB_OBJECTS)
	$(CC) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) tests/runtime_concurrency.c $(filter-out build/release/runtime.o,$(LIB_OBJECTS)) $(LDLIBS) -o $@

build/hhy-concurrency-debug: tests/runtime_concurrency.c $(filter-out build/debug/main.o,$(DEBUG_OBJECTS))
	$(CC) $(CPPFLAGS) $(DEBUG_CFLAGS) $(LDFLAGS) tests/runtime_concurrency.c $(filter-out build/debug/main.o build/debug/runtime.o,$(DEBUG_OBJECTS)) $(LDLIBS) -o $@

.PHONY: test-concurrency test-concurrency-debug
test-concurrency: $(TARGET) build/hhy-concurrency-test
	build/hhy-concurrency-test ast
	build/hhy-concurrency-test bytecode
	python3 tests/check-concurrency.py --binary $(TARGET)

test-concurrency-debug: $(DEBUG_TARGET) build/hhy-concurrency-debug
	build/hhy-concurrency-debug ast
	build/hhy-concurrency-debug bytecode
	python3 tests/check-concurrency.py --binary $(DEBUG_TARGET) --output build/concurrency-debug.json
