.PHONY: all frontend backend backend-epics backend-debug release release-epics test bench race lint clean run # --------------------------------------------------------------------------- # # Sources — adding any file here triggers a frontend or backend rebuild # # --------------------------------------------------------------------------- # FRONTEND_SRCS := $(shell find web/src -name '*.tsx' -o -name '*.ts' -o -name '*.css') \ $(wildcard web/vendor/*.mjs web/vendor/*.js web/vendor/*.css) \ tools/buildfrontend/main.go GO_SRCS := $(shell find cmd internal -name '*.go') web/embed.go go.mod go.sum # --------------------------------------------------------------------------- # # Outputs # # --------------------------------------------------------------------------- # FRONTEND_OUT := web/dist/main.js BINARY := dist/uopi # --------------------------------------------------------------------------- # # Top-level targets # # --------------------------------------------------------------------------- # all: $(BINARY) frontend: $(FRONTEND_OUT) backend: $(BINARY) # --------------------------------------------------------------------------- # # Build rules # # --------------------------------------------------------------------------- # # Bundle TypeScript/TSX → web/dist/ using esbuild (pure Go, no npm) $(FRONTEND_OUT): $(FRONTEND_SRCS) @mkdir -p web/dist go run ./tools/buildfrontend/main.go # Compile the self-contained binary (embeds web/dist at build time) $(BINARY): $(GO_SRCS) $(FRONTEND_OUT) @mkdir -p dist go build -ldflags="-s -w" -o $(BINARY) ./cmd/uopi # Build with EPICS Channel Access support. # # Auto-detection: if EPICS_BASE is set in the environment, CGO flags are # derived automatically. Override any variable on the command line: # # make backend-epics EPICS_BASE=/path/to/epics/base EPICS_ARCH=linux-x86_64 # # If EPICS_BASE is not set, set it to the directory that contains # include/cadef.h and lib//libca.so. EPICS_BASE ?= $(EPICS_BASE) # Detect host arch from EPICS startup script if not provided. EPICS_ARCH ?= $(shell \ if [ -x "$(EPICS_BASE)/startup/EpicsHostArch" ]; then \ "$(EPICS_BASE)/startup/EpicsHostArch"; \ elif [ -x "$(EPICS_BASE)/lib" ]; then \ ls "$(EPICS_BASE)/lib" | grep linux | head -1; \ else \ echo "linux-x86_64"; \ fi) EPICS_CGO_CFLAGS ?= -I$(EPICS_BASE)/include -I$(EPICS_BASE)/include/os/Linux -I$(EPICS_BASE)/include/compiler/gcc EPICS_CGO_LDFLAGS ?= -L$(EPICS_BASE)/lib/$(EPICS_ARCH) -lca -lCom -Wl,-rpath,$(EPICS_BASE)/lib/$(EPICS_ARCH) backend-epics: $(FRONTEND_OUT) @if [ -z "$(EPICS_BASE)" ]; then \ echo "ERROR: EPICS_BASE is not set."; \ echo " export EPICS_BASE=/path/to/epics/base"; \ echo " then re-run: make backend-epics"; \ exit 1; \ fi @echo "Building with EPICS support" @echo " EPICS_BASE = $(EPICS_BASE)" @echo " EPICS_ARCH = $(EPICS_ARCH)" @mkdir -p dist CGO_ENABLED=1 \ CGO_CFLAGS="$(EPICS_CGO_CFLAGS)" \ CGO_LDFLAGS="$(EPICS_CGO_LDFLAGS)" \ go build -tags epics -ldflags="-s -w" -o $(BINARY) ./cmd/uopi @echo "Built: $(BINARY) (EPICS CA enabled)" # Unstripped binary for debugging backend-debug: $(FRONTEND_OUT) @mkdir -p dist go build -o $(BINARY) ./cmd/uopi # --------------------------------------------------------------------------- # # Portable release targets # # --------------------------------------------------------------------------- # # # Two flavours depending on whether EPICS is needed: # # make release — pure-Go, CGO_ENABLED=0, 100 % static binary. # No EPICS; works anywhere without extra libs. # # make release-epics — CGO build with EPICS CA. # Tries to link libca/libCom statically (if .a # archives are present in $(EPICS_LIBDIR)). # Falls back to bundled shared libs + $ORIGIN # rpath when only .so files are available. # The output directory (dist/release/) contains # everything needed to run on the target machine. # # Cross-compilation (no-EPICS only): override RELEASE_OS / RELEASE_ARCH. # make release RELEASE_OS=linux RELEASE_ARCH=arm64 RELEASE_OS ?= linux RELEASE_ARCH ?= amd64 RELEASE_DIR := dist/release EPICS_LIBDIR := $(EPICS_BASE)/lib/$(EPICS_ARCH) # Fully static, no CGO — the most portable build possible. release: $(FRONTEND_OUT) @mkdir -p $(RELEASE_DIR) CGO_ENABLED=0 GOOS=$(RELEASE_OS) GOARCH=$(RELEASE_ARCH) \ go build -ldflags="-s -w" -o $(RELEASE_DIR)/uopi ./cmd/uopi @echo "Built: $(RELEASE_DIR)/uopi (fully static, no EPICS)" # EPICS release — maximum static linking. # Strategy A (preferred): if libca.a + libCom.a exist, embed them directly # → single self-contained binary, no extra files needed. # Strategy B (fallback): copy the shared libs next to the binary and set an # $ORIGIN rpath so the OS finds them regardless of installation prefix. release-epics: $(FRONTEND_OUT) @if [ -z "$(EPICS_BASE)" ]; then \ echo "ERROR: EPICS_BASE is not set."; \ echo " export EPICS_BASE=/path/to/epics/base"; \ exit 1; \ fi @mkdir -p $(RELEASE_DIR) @echo "Building portable EPICS release" @echo " EPICS_BASE = $(EPICS_BASE)" @echo " EPICS_ARCH = $(EPICS_ARCH)" @if [ -f "$(EPICS_LIBDIR)/libca.a" ] && [ -f "$(EPICS_LIBDIR)/libCom.a" ]; then \ echo " Strategy A: static .a archives found — embedding libca + libCom"; \ CGO_ENABLED=1 GOOS=linux GOARCH=$(RELEASE_ARCH) \ CGO_CFLAGS="$(EPICS_CGO_CFLAGS)" \ CGO_LDFLAGS="-Wl,-Bstatic -L$(EPICS_LIBDIR) -lca -lCom -Wl,-Bdynamic -lm -lpthread -lrt -ldl" \ go build -tags epics \ -ldflags="-s -w -linkmode external" \ -o $(RELEASE_DIR)/uopi ./cmd/uopi; \ echo "Built: $(RELEASE_DIR)/uopi (EPICS statically embedded)"; \ else \ echo " Strategy B: no .a archives — bundling shared libs with \$$ORIGIN rpath"; \ CGO_ENABLED=1 GOOS=linux GOARCH=$(RELEASE_ARCH) \ CGO_CFLAGS="$(EPICS_CGO_CFLAGS)" \ CGO_LDFLAGS="-L$(EPICS_LIBDIR) -lca -lCom -Wl,-rpath,\$$ORIGIN" \ go build -tags epics \ -ldflags="-s -w" \ -o $(RELEASE_DIR)/uopi ./cmd/uopi; \ cp $(EPICS_LIBDIR)/libca.so* $(RELEASE_DIR)/; \ cp $(EPICS_LIBDIR)/libCom.so* $(RELEASE_DIR)/; \ echo "Built: $(RELEASE_DIR)/ (deploy the whole directory together)"; \ ls $(RELEASE_DIR)/; \ fi # --------------------------------------------------------------------------- # # Dev / CI # # --------------------------------------------------------------------------- # test: go test ./... # Run tests with the race detector enabled. race: go test -race ./... # Run all benchmarks and print memory allocations. bench: go test -bench=. -benchmem -benchtime=3s ./internal/... lint: go vet ./... run: $(BINARY) $(BINARY) clean: rm -rf dist/ web/dist/