BINARIES := maglevd maglevc maglevd-frontend maglevt MODULE := git.ipng.ch/ipng/vpp-maglev PROTO_DIR := proto PROTO_FILE := $(PROTO_DIR)/maglev.proto GEN_FILES := internal/grpcapi/maglev.pb.go internal/grpcapi/maglev_grpc.pb.go # Web bundle is built by Vite and embedded by the Go binary via //go:embed. # Any change under cmd/frontend/web/src/ retriggers an npm build; the # generated cmd/frontend/web/dist/index.html is the sentinel. FRONTEND_WEB_SRC := $(shell find cmd/frontend/web/src -type f 2>/dev/null) \ cmd/frontend/web/index.html \ cmd/frontend/web/package.json \ cmd/frontend/web/vite.config.ts \ cmd/frontend/web/tsconfig.json FRONTEND_WEB_DIST := cmd/frontend/web/dist/index.html NATIVE_ARCH := $(shell go env GOARCH) VERSION := 1.0.0 COMMIT_HASH := $(shell git rev-parse --short HEAD 2>/dev/null || echo unknown) DATE := $(shell date -u +%Y-%m-%dT%H:%M:%SZ) LDFLAGS := -X '$(MODULE)/cmd.version=$(VERSION)' \ -X '$(MODULE)/cmd.commit=$(COMMIT_HASH)' \ -X '$(MODULE)/cmd.date=$(DATE)' # CGO_ENABLED=0 produces fully static binaries: no libc dependency, so the # debian package runs on any Linux-amd64/arm64 host regardless of glibc # version (or musl). The only behavioural change is that Go's net package # uses the pure-Go resolver instead of libc's getaddrinfo, which skips # /etc/nsswitch.conf and NSS modules — fine for DNS-only lookups, which is # all maglevd does. govpp has no cgo (its binapi is hand-marshalled Go # structs) so this has no effect on multi-VPP-version compatibility. export CGO_ENABLED := 0 TEST ?= tests/ VPP_API_DIR ?= $(HOME)/src/vpp/build-root/install-vpp_debug-native/vpp/share/vpp/api # GO_VERSION is what install-deps-go downloads from go.dev when the # system Go is missing or older than this. Debian Trixie only ships # golang-go 1.24 (main), and go.mod requires 1.25+, so the `apt install # golang-go` path isn't sufficient — we fall back to the upstream # tarball in /usr/local/go. Override on the command line to pull a # specific patch release: make install-deps GO_VERSION=1.25.5 GO_VERSION ?= 1.25.0 # GOLANGCI_LINT_VERSION is the minimum golangci-lint version that # install-deps-go-tools accepts. Raised to 1.64.0 because earlier # releases don't understand Go 1.25 syntax (1.64 is the last v1 line # and shipped Go 1.25 support; any v2.x release satisfies the floor # trivially via version sort). install-deps-go-tools always `go # install`s @latest, then asserts the resulting binary reports a # version >= this floor as a sanity check. Override on the command # line if you want to force a specific minimum, e.g. # make install-deps GOLANGCI_LINT_VERSION=2.0.0 GOLANGCI_LINT_VERSION ?= 1.64.0 .PHONY: all build build-amd64 build-arm64 test proto vpp-binapi lint fixstyle fixstyle-web pkg-deb robot-test clean maglevd-frontend-web install-deps install-deps-apt install-deps-go install-deps-go-tools all: build build: $(GEN_FILES) $(FRONTEND_WEB_DIST) mkdir -p build/$(NATIVE_ARCH) go build -ldflags "$(LDFLAGS)" -o build/$(NATIVE_ARCH)/maglevd ./cmd/server/ go build -ldflags "$(LDFLAGS)" -o build/$(NATIVE_ARCH)/maglevc ./cmd/client/ go build -ldflags "$(LDFLAGS)" -o build/$(NATIVE_ARCH)/maglevd-frontend ./cmd/frontend/ go build -ldflags "$(LDFLAGS)" -o build/$(NATIVE_ARCH)/maglevt ./cmd/tester/ build-amd64: $(GEN_FILES) $(FRONTEND_WEB_DIST) mkdir -p build/amd64 GOOS=linux GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o build/amd64/maglevd ./cmd/server/ GOOS=linux GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o build/amd64/maglevc ./cmd/client/ GOOS=linux GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o build/amd64/maglevd-frontend ./cmd/frontend/ GOOS=linux GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o build/amd64/maglevt ./cmd/tester/ build-arm64: $(GEN_FILES) $(FRONTEND_WEB_DIST) mkdir -p build/arm64 GOOS=linux GOARCH=arm64 go build -ldflags "$(LDFLAGS)" -o build/arm64/maglevd ./cmd/server/ GOOS=linux GOARCH=arm64 go build -ldflags "$(LDFLAGS)" -o build/arm64/maglevc ./cmd/client/ GOOS=linux GOARCH=arm64 go build -ldflags "$(LDFLAGS)" -o build/arm64/maglevd-frontend ./cmd/frontend/ GOOS=linux GOARCH=arm64 go build -ldflags "$(LDFLAGS)" -o build/arm64/maglevt ./cmd/tester/ # maglevd-frontend-web rebuilds the SolidJS bundle. The Go binary embeds the # resulting cmd/frontend/web/dist/ via //go:embed, so a `go build` after # this target picks up any asset changes automatically. maglevd-frontend-web: $(FRONTEND_WEB_DIST) $(FRONTEND_WEB_DIST): $(FRONTEND_WEB_SRC) cd cmd/frontend/web && npm install && npm run build pkg-deb: build-amd64 build-arm64 debian/build-deb.sh vpp-maglevd amd64 $(VERSION) debian/build-deb.sh vpp-maglevd arm64 $(VERSION) debian/build-deb.sh vpp-maglev amd64 $(VERSION) debian/build-deb.sh vpp-maglev arm64 $(VERSION) test: $(GEN_FILES) go test ./... proto: $(GEN_FILES) $(GEN_FILES): $(PROTO_FILE) protoc \ --go_out=. --go_opt=module=$(MODULE) \ --go-grpc_out=. --go-grpc_opt=module=$(MODULE) \ $(PROTO_FILE) # vpp-binapi regenerates the Go bindings for VPP API files used by maglevd # from a local VPP build. The LB plugin ships with upstream VPP; any newer # messages (e.g. lb_conf_get, lb_as_v2_dump) require a VPP build that has # them. Override VPP_API_DIR on the command line to point at another tree: # make vpp-binapi VPP_API_DIR=/path/to/share/vpp/api vpp-binapi: @command -v binapi-generator >/dev/null 2>&1 || { \ echo "installing binapi-generator..."; \ go install go.fd.io/govpp/cmd/binapi-generator@v0.12.0; \ } rm -rf internal/vpp/binapi mkdir -p internal/vpp/binapi binapi-generator \ --input=$(VPP_API_DIR) \ --output-dir=internal/vpp/binapi \ --import-prefix=$(MODULE)/internal/vpp/binapi \ --no-source-path-info \ --no-version-info \ lb lb_types rm -f internal/vpp/binapi/lb/lb_rpc.ba.go fixstyle: fixstyle-web gofmt -w . fixstyle-web: cd cmd/frontend/web && npx prettier --write . lint: golangci-lint run ./... # install-deps is an opt-in "set up a fresh developer box" target. Tested # on Debian Trixie; the apt half should also work on Bookworm and recent # Ubuntu LTS. Splits into three sub-targets so they can be run individually: # # install-deps-apt — Debian-packaged build-time deps (nodejs, npm, # protoc, git, make, dpkg-dev, curl). # install-deps-go — ensure a Go toolchain >= $(GO_VERSION) is on # the system. Downloads the upstream tarball # into /usr/local/go when the system Go is # missing or older than the go.mod floor. # install-deps-go-tools — `go install` the helpers this repo needs # (protoc-gen-go, protoc-gen-go-grpc, golangci- # lint) and assert golangci-lint is new enough # to understand Go 1.25 syntax. # # Each sub-target is idempotent and safe to re-run. install-deps: install-deps-apt install-deps-go install-deps-go-tools @echo "" @echo "==> All build dependencies installed." @echo " Make sure these are on PATH:" @echo " /usr/local/go/bin (Go toolchain)" @echo " \$$(go env GOPATH)/bin (protoc-gen-go, golangci-lint, ...)" install-deps-apt: @set -eu; \ if [ "$$(id -u)" = 0 ]; then SUDO=""; else SUDO="sudo"; fi; \ echo "==> Installing apt packages (nodejs, npm, protoc, git, make, dpkg-dev)"; \ $$SUDO apt-get update; \ $$SUDO apt-get install -y --no-install-recommends \ nodejs npm protobuf-compiler git make dpkg-dev \ ca-certificates curl tar # install-deps-go short-circuits when go env GOVERSION already reports a # version >= GO_VERSION. Otherwise it downloads the official upstream # tarball (https://go.dev/dl/) and extracts it to /usr/local/go, matching # the layout that go.dev recommends and that most Debian setups use for # "Go newer than apt provides". install-deps-go: @set -eu; \ if [ "$$(id -u)" = 0 ]; then SUDO=""; else SUDO="sudo"; fi; \ echo "==> Checking Go toolchain (required: $(GO_VERSION)+)"; \ if command -v go >/dev/null 2>&1; then \ CURRENT=$$(go env GOVERSION 2>/dev/null | sed 's/^go//'); \ OLDEST=$$(printf '%s\n%s\n' "$(GO_VERSION)" "$$CURRENT" | sort -V | head -n1); \ if [ "$$OLDEST" = "$(GO_VERSION)" ] && [ -n "$$CURRENT" ]; then \ echo " go$$CURRENT already installed (>= $(GO_VERSION)), skipping."; \ exit 0; \ fi; \ echo " go$$CURRENT is older than $(GO_VERSION), upgrading."; \ else \ echo " no Go toolchain on PATH, installing."; \ fi; \ DEB_ARCH=$$(dpkg --print-architecture); \ case "$$DEB_ARCH" in \ amd64) GOARCH=amd64 ;; \ arm64) GOARCH=arm64 ;; \ armhf) GOARCH=armv6l ;; \ *) echo " unsupported architecture: $$DEB_ARCH" >&2; exit 1 ;; \ esac; \ TARBALL="go$(GO_VERSION).linux-$$GOARCH.tar.gz"; \ URL="https://go.dev/dl/$$TARBALL"; \ echo " downloading $$URL"; \ curl -fsSL -o "/tmp/$$TARBALL" "$$URL"; \ echo " installing to /usr/local/go"; \ $$SUDO rm -rf /usr/local/go; \ $$SUDO tar -C /usr/local -xzf "/tmp/$$TARBALL"; \ rm -f "/tmp/$$TARBALL"; \ echo " installed $$(/usr/local/go/bin/go version)" # install-deps-go-tools installs the three Go binaries this repo calls # out to during `make proto` and `make lint`. protoc-gen-go and # protoc-gen-go-grpc pin to specific upstream release branches; golangci- # lint pulls @latest (the v2 install path) and then we assert the # installed version parses as >= GOLANGCI_LINT_VERSION so a stale binary # in $GOPATH/bin from a previous dev session doesn't silently get used # against Go 1.25 code it can't parse. Run `make install-deps # GOLANGCI_LINT_VERSION=2.0.0` if you want to enforce a tighter floor. install-deps-go-tools: @set -eu; \ if ! command -v go >/dev/null 2>&1; then \ export PATH="/usr/local/go/bin:$$PATH"; \ fi; \ echo "==> Installing Go tools via 'go install'"; \ echo " google.golang.org/protobuf/cmd/protoc-gen-go"; \ go install google.golang.org/protobuf/cmd/protoc-gen-go@latest; \ echo " google.golang.org/grpc/cmd/protoc-gen-go-grpc"; \ go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest; \ echo " github.com/golangci/golangci-lint/v2/cmd/golangci-lint"; \ go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest; \ GOBIN="$$(go env GOBIN)"; \ if [ -z "$$GOBIN" ]; then GOBIN="$$(go env GOPATH)/bin"; fi; \ echo "==> Asserting golangci-lint version >= $(GOLANGCI_LINT_VERSION)"; \ if ! "$$GOBIN/golangci-lint" version >/dev/null 2>&1; then \ echo " ERROR: $$GOBIN/golangci-lint is not executable" >&2; \ exit 1; \ fi; \ INSTALLED=$$("$$GOBIN/golangci-lint" version 2>&1 | sed -En 's/.*has version v?([0-9][0-9.]*).*/\1/p' | head -n1); \ if [ -z "$$INSTALLED" ]; then \ echo " ERROR: could not parse golangci-lint version output" >&2; \ "$$GOBIN/golangci-lint" version >&2; \ exit 1; \ fi; \ OLDEST=$$(printf '%s\n%s\n' "$(GOLANGCI_LINT_VERSION)" "$$INSTALLED" | sort -V | head -n1); \ if [ "$$OLDEST" != "$(GOLANGCI_LINT_VERSION)" ]; then \ echo " ERROR: golangci-lint $$INSTALLED is older than the required $(GOLANGCI_LINT_VERSION)" >&2; \ echo " The tool understands Go 1.25 syntax only from v1.64.0 / v2.x onward." >&2; \ exit 1; \ fi; \ echo " golangci-lint $$INSTALLED (>= $(GOLANGCI_LINT_VERSION)) OK" tests/.venv: tests/requirements.txt python3 -m venv tests/.venv tests/.venv/bin/pip install -q -r tests/requirements.txt robot-test: build tests/.venv tests/rf-run.sh docker $(TEST) clean: rm -rf build/ rm -f $(GEN_FILES)