BINARIES := data-beans data-beans-sim
UNAME_S := $(shell uname -s)
HAS_NVCC := $(shell command -v nvcc 2>/dev/null)
ifeq ($(UNAME_S),Darwin)
DEFAULT_BACKEND := metal
else ifneq ($(HAS_NVCC),)
DEFAULT_BACKEND := cuda
else
DEFAULT_BACKEND := cpu
endif
BACKEND ?= $(DEFAULT_BACKEND)
H5CC_PATH := $(shell command -v h5cc 2>/dev/null)
H5CC_PREFIX := $(if $(H5CC_PATH),$(shell dirname $$(dirname $(H5CC_PATH))))
HDF5_CANDIDATES := $(HDF5_DIR) $(CONDA_PREFIX) $(H5CC_PREFIX) /opt/homebrew /usr/local /usr
HDF5_DETECTED_DIR := $(firstword $(foreach d,$(HDF5_CANDIDATES), \
$(if $(strip $(d)), \
$(if $(wildcard $(d)/include/hdf5.h), \
$(if $(or $(wildcard $(d)/lib/libhdf5.so), \
$(wildcard $(d)/lib/libhdf5.a), \
$(wildcard $(d)/lib/libhdf5.dylib), \
$(wildcard $(d)/lib64/libhdf5.so), \
$(wildcard $(d)/lib64/libhdf5.a)), \
$(d))))))
ifeq ($(HDF5_DETECTED_DIR),)
HDF5_PKGCFG_OK := $(shell if pkg-config --exists hdf5 2>/dev/null || pkg-config --exists hdf5-serial 2>/dev/null; then echo yes; fi)
else
HDF5_PKGCFG_OK := yes
endif
ifeq ($(HDF5_PKGCFG_OK),yes)
DEFAULT_HDF5 := on
else
DEFAULT_HDF5 := off
endif
HDF5 ?= $(DEFAULT_HDF5)
ifeq ($(HDF5),on)
ifneq ($(HDF5_DETECTED_DIR),)
export HDF5_DIR := $(HDF5_DETECTED_DIR)
endif
endif
ifeq (,$(filter $(BACKEND),cpu cuda metal))
$(error Unknown BACKEND='$(BACKEND)'; valid values are: cpu, cuda, metal)
endif
ifeq (,$(filter $(HDF5),on off))
$(error Unknown HDF5='$(HDF5)'; valid values are: on, off)
endif
CUDA_CAP_CACHE := $(or $(XDG_CACHE_HOME),$(HOME)/.cache)/data-beans/cuda-compute-cap
cuda_cap_from_model = $(strip \
$(if $(or $(findstring RTX 50,$(1)),$(findstring Blackwell,$(1))),120, \
$(if $(or $(findstring B200,$(1)),$(findstring B100,$(1)),$(findstring GB200,$(1))),100, \
$(if $(or $(findstring H100,$(1)),$(findstring H200,$(1)),$(findstring H800,$(1)),$(findstring H20,$(1)),$(findstring GH200,$(1))),90, \
$(if $(or $(findstring RTX 40,$(1)),$(findstring L40,$(1)),$(findstring L4,$(1)),$(findstring Ada,$(1))),89, \
$(if $(or $(findstring A100,$(1)),$(findstring A800,$(1)),$(findstring A30,$(1))),80, \
$(if $(or $(findstring RTX 30,$(1)),$(findstring RTX A,$(1)),$(findstring A10,$(1)),$(findstring A40,$(1)),$(findstring A16,$(1)),$(findstring A2,$(1))),86, \
$(if $(or $(findstring RTX 20,$(1)),$(findstring GTX 16,$(1)),$(findstring Quadro RTX,$(1)),$(findstring TITAN RTX,$(1)),$(findstring T4,$(1))),75, \
$(if $(or $(findstring V100,$(1)),$(findstring TITAN V,$(1))),70, \
$(if $(or $(findstring GTX 10,$(1)),$(findstring P100,$(1)),$(findstring P40,$(1)),$(findstring TITAN X,$(1))),60, \
))))))))))
ifeq ($(BACKEND),cuda)
ifneq ($(strip $(CUDA_COMPUTE_CAP)),)
CUDA_CAP_SOURCE := set by caller
else
CUDA_CAP_SMI := $(shell timeout 15 nvidia-smi --query-gpu=compute_cap --format=csv,noheader 2>/dev/null \
| head -n1 | tr -d ' ' | grep -E -x '[0-9]+\.[0-9]+' | tr -d .)
ifneq ($(CUDA_CAP_SMI),)
CUDA_COMPUTE_CAP := $(CUDA_CAP_SMI)
CUDA_CAP_SOURCE := nvidia-smi
$(shell mkdir -p $(dir $(CUDA_CAP_CACHE)) && echo $(CUDA_CAP_SMI) > $(CUDA_CAP_CACHE))
else ifneq ($(wildcard $(CUDA_CAP_CACHE)),)
CUDA_COMPUTE_CAP := $(shell cat $(CUDA_CAP_CACHE))
CUDA_CAP_SOURCE := cached from an earlier nvidia-smi run ($(CUDA_CAP_CACHE))
else
CUDA_GPU_MODEL := $(shell sed -n 's/^Model:[[:space:]]*//p' /proc/driver/nvidia/gpus/*/information 2>/dev/null | head -n1)
CUDA_COMPUTE_CAP := $(call cuda_cap_from_model,$(CUDA_GPU_MODEL))
CUDA_CAP_SOURCE := $(if $(CUDA_COMPUTE_CAP),from the GPU model '$(CUDA_GPU_MODEL)'; nvidia-smi is not working,not detected$(if $(CUDA_GPU_MODEL),; nvidia-smi is not working and '$(CUDA_GPU_MODEL)' is not in the family table))
endif
endif
ifneq ($(strip $(CUDA_COMPUTE_CAP)),)
export CUDA_COMPUTE_CAP
endif
endif
CARGO_FEATURE_LIST :=
ifneq ($(BACKEND),cpu)
CARGO_FEATURE_LIST += $(BACKEND)
endif
ifeq ($(HDF5),on)
CARGO_FEATURE_LIST += hdf5
endif
empty :=
space := $(empty) $(empty)
comma := ,
ifeq ($(strip $(CARGO_FEATURE_LIST)),)
CARGO_FEATURES :=
else
CARGO_FEATURES := --features $(subst $(space),$(comma),$(strip $(CARGO_FEATURE_LIST)))
endif
ifeq ($(HDF5),on)
CARGO_FEATURES_CPU_FALLBACK := --features hdf5
else
CARGO_FEATURES_CPU_FALLBACK :=
endif
ifeq ($(BACKEND)$(strip $(CUDA_COMPUTE_CAP)),cuda)
CUDA_CAP_HINT := echo " (no CUDA compute capability could be detected; if the GPU is fine, rerun with CUDA_COMPUTE_CAP=<cap>, e.g. 86)";
else
CUDA_CAP_HINT :=
endif
CARGO_FEATURES := $(if $(CARGO_FEATURES),$(CARGO_FEATURES)$(comma)sim,--features sim)
CARGO_FEATURES_CPU_FALLBACK := $(if $(CARGO_FEATURES_CPU_FALLBACK),$(CARGO_FEATURES_CPU_FALLBACK)$(comma)sim,--features sim)
.PHONY: all install install-cpu install-cuda install-metal \
uninstall build build-cuda build-metal test clean help
help:
@echo "data-beans Makefile"
@echo ""
@echo "Auto-detected backend on this host: $(DEFAULT_BACKEND)"
@echo "Auto-detected HDF5 support: $(DEFAULT_HDF5)$(if $(HDF5_DETECTED_DIR), (HDF5_DIR=$(HDF5_DETECTED_DIR)))"
ifeq ($(BACKEND),cuda)
@echo "CUDA compute capability: $(or $(CUDA_COMPUTE_CAP),none) ($(CUDA_CAP_SOURCE))"
endif
@echo ""
@echo "Install targets:"
@echo " install - Install $(BINARIES) with auto-detected backend"
@echo " (falls back to CPU if a GPU build fails)"
@echo " install-cpu - Force CPU-only install"
@echo " install-cuda - Force CUDA + cuDNN install (Linux)"
@echo " install-metal - Force Metal + Accelerate install (macOS)"
@echo ""
@echo "Other targets:"
@echo " uninstall - Uninstall both binaries"
@echo " build - Release build with auto-detected backend"
@echo " (falls back to CPU if a GPU build fails)"
@echo " build-cuda - Build with CUDA"
@echo " build-metal - Build with Metal"
@echo " test - Run all tests"
@echo " clean - Clean build artifacts"
@echo ""
@echo "Overrides:"
@echo " make <target> BACKEND={cpu|cuda|metal}"
@echo " make <target> HDF5={on|off} @echo " HDF5_DIR=<prefix> make ... @echo " make <target> CUDA_COMPUTE_CAP=86
all: install
install:
ifeq ($(BACKEND),cuda)
@echo "CUDA compute capability: $(or $(CUDA_COMPUTE_CAP),none) ($(CUDA_CAP_SOURCE))"
endif
ifeq ($(BACKEND),cpu)
@echo "Installing $(BINARIES) (backend: cpu, HDF5: $(HDF5))..."
cargo install --locked --force --path . $(CARGO_FEATURES)
else
@echo "Installing $(BINARIES) (backend: $(BACKEND), HDF5: $(HDF5))..."
@if cargo install --locked --force --path . $(CARGO_FEATURES); then \
echo "Installed with backend: $(BACKEND)"; \
else \
echo ""; \
echo " $(BACKEND) build failed; retrying with CPU"; \
$(CUDA_CAP_HINT) \
echo ""; \
cargo install --locked --force --path . $(CARGO_FEATURES_CPU_FALLBACK) && \
echo "Installed with backend: cpu (fell back from $(BACKEND))"; \
fi
endif
install-cpu:
@$(MAKE) install BACKEND=cpu
install-cuda:
@$(MAKE) install BACKEND=cuda
install-metal:
@$(MAKE) install BACKEND=metal
uninstall:
cargo uninstall data-beans
build:
ifeq ($(BACKEND),cuda)
@echo "CUDA compute capability: $(or $(CUDA_COMPUTE_CAP),none) ($(CUDA_CAP_SOURCE))"
endif
ifeq ($(BACKEND),cpu)
cargo build --release $(CARGO_FEATURES)
else
@if ! cargo build --release $(CARGO_FEATURES); then \
echo ""; \
echo " $(BACKEND) build failed; retrying with CPU"; \
$(CUDA_CAP_HINT) \
echo ""; \
cargo build --release $(CARGO_FEATURES_CPU_FALLBACK); \
fi
endif
build-cuda:
@$(MAKE) build BACKEND=cuda
build-metal:
@$(MAKE) build BACKEND=metal
test:
cargo test --features sim
clean:
cargo clean