Current section

Files

Jump to
duckdbex Makefile
Raw

Makefile

SRC_DIR = c_src
DUCKDB_DIR = $(SRC_DIR)/duckdb
DUCKDB_ARCHIVE = $(SRC_DIR)/duckdb.tar.xz
DUCKDB_MANIFEST = $(DUCKDB_DIR)/.sources
DUCKDB_ARCHIVE_CHECKSUM = $(DUCKDB_DIR)/.archive.checksum
DUCKDB_ARCHIVE_SHA256 = $(SRC_DIR)/duckdb.tar.xz.sha256

# The archive is committed to git but not shipped in the Hex package (it does
# not fit Hex's size limit), so a build from the Hex package downloads it from
# the GitHub release of this version. Override DUCKDB_ARCHIVE_URL to use a
# mirror, or a file:// URL to build offline.
DUCKDBEX_VERSION = $(shell sed -n 's/^  @version "\(.*\)"/\1/p' mix.exs)
DUCKDB_ARCHIVE_URL ?= https://github.com/AlexR2D2/duckdbex/releases/download/v$(DUCKDBEX_VERSION)/duckdb.tar.xz

CXXFLAGS = -O3 -std=c++17

# Silence GCC's informational C++17 ABI notes (GCC 10.1 pair-passing change):
# DuckDB and the NIF are built by one compiler, so the ABI never mixes.
CXXFLAGS += -Wno-psabi

# Statically link the DuckDB build-baseline extensions (see
# extension/extension_config.cmake upstream): core_functions, parquet, json and icu.
CXXFLAGS += -DDUCKDB_EXTENSION_CORE_FUNCTIONS_LINKED=1
CXXFLAGS += -DDUCKDB_EXTENSION_PARQUET_LINKED=1
CXXFLAGS += -DDUCKDB_EXTENSION_JSON_LINKED=1
CXXFLAGS += -DDUCKDB_EXTENSION_ICU_LINKED=1

# Include roots emitted by DuckDB's scripts/package_build.py and recorded
# in c_src/duckdb/.include_dirs at generation time (see bin/regen_duckdb.sh).
DUCKDB_INCLUDE_DIRS = $(shell test -f $(DUCKDB_DIR)/.include_dirs && cat $(DUCKDB_DIR)/.include_dirs)
CXXFLAGS += $(foreach dir, $(DUCKDB_INCLUDE_DIRS), -I"$(DUCKDB_DIR)/$(dir)")
CXXFLAGS += -I"$(ERTS_INCLUDE_DIR)"
CXXFLAGS += -DNDEBUG=1

KERNEL_NAME := $(shell uname -s)

PRIV_DIR = $(MIX_APP_PATH)/priv
LIB_NAME = $(PRIV_DIR)/duckdb_nif.so

ifneq ($(CROSSCOMPILE),)
	CXXFLAGS += -fPIC -fvisibility=hidden
	LDFLAGS += -fPIC -shared
else
	ifeq ($(KERNEL_NAME), Linux)
		CXXFLAGS += -fPIC -fvisibility=hidden
		LDFLAGS += -fPIC -shared
	endif
	ifeq ($(KERNEL_NAME), Darwin)
		CXXFLAGS += -fPIC
		LDFLAGS += -dynamiclib -undefined dynamic_lookup
	endif
	ifeq ($(KERNEL_NAME), $(filter $(KERNEL_NAME),OpenBSD FreeBSD NetBSD))
		CXXFLAGS += -fPIC
		LDFLAGS += -fPIC -shared
	endif
endif

# Compile the file list emitted by DuckDB's scripts/package_build.py
# (unity builds + directly referenced sources), plus the NIF files.
# See c_src/duckdb/.sources for the generated list.
GENERATED_SRC = $(shell test -f $(DUCKDB_MANIFEST) && cat $(DUCKDB_MANIFEST))
# Since v2.0.0 package_build.py always lists the bundled jemalloc C sources.
# DuckDB only uses them with -DDUCKDB_ENABLE_JEMALLOC (64-bit Linux), which
# this build does not set, so skip them and keep the default allocator.
GENERATED_SRC := $(filter-out third_party/jemalloc/%, $(GENERATED_SRC))
NIF_SRC = $(SRC_DIR)/nif.cpp $(SRC_DIR)/config.cpp $(SRC_DIR)/term.cpp $(SRC_DIR)/term_to_value.cpp $(SRC_DIR)/value_to_term.cpp
SRC = $(addprefix $(DUCKDB_DIR)/, $(GENERATED_SRC)) $(NIF_SRC)

OBJ = $(patsubst %.cpp, %.o, $(patsubst %.cc, %.o, $(subst $(SRC_DIR), $(PRIV_DIR), $(SRC))))

.PRECIOUS: $(PRIV_DIR)/. $(PRIV_DIR)%/.

$(PRIV_DIR):
	mkdir -p $@

$(PRIV_DIR)/.:
	mkdir -p $@

$(PRIV_DIR)%/.:
	mkdir -p $@

.SECONDEXPANSION:

$(PRIV_DIR)/%.o: $(SRC_DIR)/%.cpp | $$(@D)/.
	$(CXX) $(CXXFLAGS) -c $< -o $@

$(PRIV_DIR)/%.o: $(SRC_DIR)/%.cc | $$(@D)/.
	$(CXX) $(CXXFLAGS) -c $< -o $@

$(LIB_NAME): $(OBJ)
	$(CXX) $(LDFLAGS) $^ -o $@

all: prepare-duckdb
	$(MAKE) build

build: $(PRIV_DIR) $(SRC) $(LIB_NAME)

$(DUCKDB_ARCHIVE):
	@set -eu; \
	echo "Downloading vendored DuckDB sources from $(DUCKDB_ARCHIVE_URL)"; \
	tmp="$$(mktemp "$(SRC_DIR)/duckdb.download.XXXXXX")"; \
	trap 'rm -f "$$tmp"' EXIT; \
	curl -fsSL --retry 3 -o "$$tmp" "$(DUCKDB_ARCHIVE_URL)" || { \
		echo "ERROR: could not download $(DUCKDB_ARCHIVE_URL); set DUCKDB_ARCHIVE_URL to a mirror or a file:// path"; exit 1; }; \
	if command -v sha256sum >/dev/null 2>&1; then actual="$$(sha256sum < "$$tmp")"; else actual="$$(shasum -a 256 < "$$tmp")"; fi; \
	actual="$${actual%% *}"; \
	expected="$$(cat "$(DUCKDB_ARCHIVE_SHA256)")"; \
	if [ "$$actual" != "$$expected" ]; then \
		echo "ERROR: sha256 mismatch for $(DUCKDB_ARCHIVE_URL): expected $$expected, got $$actual"; exit 1; \
	fi; \
	mv "$$tmp" "$(DUCKDB_ARCHIVE)"

prepare-duckdb: $(DUCKDB_ARCHIVE)
	@set -eu; \
	archive_checksum="$$(cksum < "$(DUCKDB_ARCHIVE)")"; \
	installed_checksum="$$(cat "$(DUCKDB_ARCHIVE_CHECKSUM)" 2>/dev/null || true)"; \
	if [ ! -f "$(DUCKDB_MANIFEST)" ] || [ "$$archive_checksum" != "$$installed_checksum" ]; then \
		echo "Extracting vendored DuckDB sources"; \
		extract_dir="$$(mktemp -d "$(SRC_DIR)/duckdb.extract.XXXXXX")"; \
		trap 'rm -rf "$$extract_dir"' EXIT; \
		tar -xJf "$(DUCKDB_ARCHIVE)" -C "$$extract_dir"; \
		test -f "$$extract_dir/duckdb/.sources" || { echo "ERROR: invalid $(DUCKDB_ARCHIVE)"; exit 1; }; \
		rm -rf "$(DUCKDB_DIR)"; \
		mv "$$extract_dir/duckdb" "$(DUCKDB_DIR)"; \
		printf '%s\n' "$$archive_checksum" > "$(DUCKDB_ARCHIVE_CHECKSUM)"; \
		find "$(DUCKDB_DIR)" -type f -exec touch {} +; \
	fi

# Guard against a broken/partial vendor tree: every file in .sources must
# exist on disk (run this in CI to catch rsync/LFS accidents).
verify-sources: prepare-duckdb
	@missing=""; while IFS= read -r f; do \
	  [ -f "$(DUCKDB_DIR)/$$f" ] || missing="$$missing $$f"; \
	done < "$(DUCKDB_DIR)/.sources"; \
	if [ -n "$$missing" ]; then \
	  echo "ERROR: missing vendored sources:$$missing"; exit 1; \
	fi; \
	echo "OK: all vendored sources present"

clean:
	$(RM) -rf run $(OBJ)
	$(RM) -f $(LIB_NAME)

.PHONY: all build prepare-duckdb clean verify-sources