-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathMakefile
More file actions
249 lines (201 loc) · 11.4 KB
/
Copy pathMakefile
File metadata and controls
249 lines (201 loc) · 11.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
BINARY_NAME = ensemble
GO ?= go
GOFLAGS = -trimpath
APP_TAGS ?= novulkan
APP_BUILD_TAGS = ensemble_app $(APP_TAGS)
MODULE = github.com/ensemble/ensemble
# Embedded Tor (go-libtor) is cgo and linux-only. On linux we compile it in
# (CGO_ENABLED=1, needs a C compiler); on every other OS go-libtor ships no C
# sources, so we build pure-Go (CGO_ENABLED=0) and rely on an external tor via
# --tor-path.
HOST_OS := $(shell $(GO) env GOOS)
ifeq ($(HOST_OS),linux)
HOST_CGO := CGO_ENABLED=1
HOST_CGO_GUARD := command -v "$${CC:-cc}" >/dev/null 2>&1 || command -v gcc >/dev/null 2>&1 || command -v clang >/dev/null 2>&1 || { echo "no C compiler found — embedded Tor needs cgo on linux (install gcc/clang), or build pure-Go with CGO_ENABLED=0 and run with --tor-path"; exit 1; }
else
HOST_CGO := CGO_ENABLED=0
HOST_CGO_GUARD := true
endif
# Without this, bare `make` runs the first target in the file (ui-proto),
# which needs protoc + npm even when you only want the Go binary.
.DEFAULT_GOAL := build
.PHONY: build build-dev app registry registry-package ui ui-proto run run-headless test test-app test-integration test-race test-cover lint proto proto-ts trust-local-ca clean help dotnet-build dotnet-test dotnet-test-integration
## Build
ui-proto: ## Generate TypeScript protobuf stubs into ui/src/gen
@command -v protoc >/dev/null || { echo "protoc not found — install it (macOS: brew install protobuf) or use 'make build-dev' for a Go-only build without the web UI"; exit 1; }
@command -v npm >/dev/null || { echo "npm not found — install Node.js (macOS: brew install node) or use 'make build-dev' for a Go-only build without the web UI"; exit 1; }
@cd ui && npm ci >/dev/null
@mkdir -p ui/src/gen
protoc -Iapi/proto \
--plugin=protoc-gen-es=ui/node_modules/.bin/protoc-gen-es \
--es_out=ui/src/gen \
--es_opt=target=ts \
api/proto/ensemble.proto
ui: ui-proto ## Build the SPA bundle and stage it under internal/ui-service/dist for go:embed
cd ui && npm ci && npm run build
@# Replace previous bundle contents but preserve the tracked .gitkeep so the
@# directory still exists (and `go:embed all:dist` still has a file to embed)
@# even after a `clean`. Go's go:embed does not follow directory symlinks, so
@# we copy rather than link.
@mkdir -p internal/ui-service/dist
@find internal/ui-service/dist -mindepth 1 ! -name .gitkeep -exec rm -rf {} +
cp -r ui/dist/. internal/ui-service/dist/
build: ui ## Build the binary (embeds the SPA; in-process Tor on linux, external tor elsewhere)
@$(HOST_CGO_GUARD)
$(HOST_CGO) $(GO) build $(GOFLAGS) -o bin/$(BINARY_NAME) .
build-dev: ## Build the binary with -tags noembed (skip SPA embed; useful for Go-only iteration)
@$(HOST_CGO_GUARD)
@mkdir -p internal/ui-service/dist
@touch internal/ui-service/dist/.gitkeep
$(HOST_CGO) $(GO) build $(GOFLAGS) -tags noembed -o bin/$(BINARY_NAME) .
app: ## Build the native Gio desktop app shell
@$(HOST_CGO_GUARD)
$(HOST_CGO) $(GO) build $(GOFLAGS) -tags "$(APP_BUILD_TAGS)" -o bin/$(BINARY_NAME)-app ./cmd/ensemble-app
# The registry binary never runs Tor itself (its hosting daemon owns the
# network), so it builds pure-Go with the embedded-tor cgo dependency tagged
# out — small binary, trivially cross-compilable.
registry: ## Build the registry service binary
CGO_ENABLED=0 $(GO) build $(GOFLAGS) -tags noembedtor -o bin/ensemble-registry ./cmd/ensemble-registry
# Package the registry service as its own installable archive (ADR-0009 §2 —
# the distribution system distributes its own distributor). VERSION required:
# make registry-package VERSION=0.1.0 [REGISTRY_GOOS=linux REGISTRY_GOARCH=amd64]
REGISTRY_GOOS ?= $(shell $(GO) env GOOS)
REGISTRY_GOARCH ?= $(shell $(GO) env GOARCH)
registry-package: ## Build dist/registry-<version>-<os>-<arch>.tar.gz
@test -n "$(VERSION)" || { echo "VERSION is required: make registry-package VERSION=0.1.0"; exit 1; }
@rm -rf dist/registry-pkg && mkdir -p dist/registry-pkg/bin
CGO_ENABLED=0 GOOS=$(REGISTRY_GOOS) GOARCH=$(REGISTRY_GOARCH) $(GO) build $(GOFLAGS) -tags noembedtor \
-o dist/registry-pkg/bin/ensemble-registry ./cmd/ensemble-registry
@printf 'name: registry\nversion: %s\ndescription: ensemble service registry — signed index distribution\nentrypoint:\n command: ["bin/ensemble-registry"]\ncapabilities:\n transport: rpc\n acl: public\n commands: [registry-index, registry-publish, registry-yank]\n max_payload_bytes: 1048576\n' "$(VERSION)" \
> dist/registry-pkg/ensemble-service.yaml
tar -C dist/registry-pkg -czf dist/registry-$(VERSION)-$(REGISTRY_GOOS)-$(REGISTRY_GOARCH).tar.gz .
@rm -rf dist/registry-pkg
@echo "dist/registry-$(VERSION)-$(REGISTRY_GOOS)-$(REGISTRY_GOARCH).tar.gz"
run: build ## Build and run (daemon + TUI)
./bin/$(BINARY_NAME)
run-headless: build ## Build and run in headless mode
./bin/$(BINARY_NAME) --headless
## Test
test: test-app ## Run unit tests
$(GO) test ./internal/... ./testutil/...
test-app: ## Run native Gio app tests with desktop-safe tags
$(HOST_CGO) $(GO) test -tags "$(APP_BUILD_TAGS)" ./cmd/...
test-integration: ## Run integration tests (requires network + Tor)
$(GO) test -tags=integration ./internal/...
test-race: ## Run tests with race detector
$(GO) test -race ./internal/... ./testutil/...
test-cover: ## Generate coverage report
$(GO) test -coverprofile=coverage.out ./internal/... ./testutil/...
$(GO) tool cover -html=coverage.out
## Code quality
lint: ## Run linters
golangci-lint run ./...
fmt: ## Format code
$(GO) fmt ./...
vet: ## Run go vet
$(GO) vet ./internal/... ./testutil/...
$(HOST_CGO) $(GO) vet -tags "$(APP_BUILD_TAGS)" ./cmd/...
## Protobuf
PYTHON ?= python3
PY_PROTO_OUT = clients/python/ensemble/_proto
proto: proto-go proto-py proto-ts ## Regenerate protobuf code (Go + Python + TypeScript)
proto-ts: ## Regenerate TypeScript protobuf code under ui/src/gen
@command -v protoc >/dev/null || { echo "protoc not found — install it (macOS: brew install protobuf)"; exit 1; }
@cd ui && npm ci >/dev/null
@mkdir -p ui/src/gen
protoc -Iapi/proto \
--plugin=protoc-gen-es=ui/node_modules/.bin/protoc-gen-es \
--es_out=ui/src/gen \
--es_opt=target=ts \
api/proto/ensemble.proto
proto-go: ## Regenerate Go protobuf code
protoc --go_out=api/pb --go_opt=paths=source_relative \
--go-grpc_out=api/pb --go-grpc_opt=paths=source_relative \
-Iapi/proto \
api/proto/ensemble.proto
protoc --go_out=internal/protocol/pb --go_opt=paths=source_relative \
-Iinternal/protocol/proto \
internal/protocol/proto/messages.proto
proto-py: ## Regenerate Python gRPC stubs for clients/python
mkdir -p $(PY_PROTO_OUT)
$(PYTHON) -m grpc_tools.protoc \
-Iapi/proto \
--python_out=$(PY_PROTO_OUT) \
--grpc_python_out=$(PY_PROTO_OUT) \
api/proto/ensemble.proto
@# Rewrite the absolute import in *_pb2_grpc.py to a relative import
@# so the generated stubs work as a package (clients/python/ensemble/_proto/).
@sed -i 's/^import ensemble_pb2 as ensemble__pb2$$/from . import ensemble_pb2 as ensemble__pb2/' \
$(PY_PROTO_OUT)/ensemble_pb2_grpc.py
@touch $(PY_PROTO_OUT)/__init__.py
## Local HTTPS (per-machine UI CA)
# Where the daemon stores its local-HTTPS CA. Override with `make trust-local-ca DATA_DIR=/path/to/data`.
DATA_DIR ?= $(HOME)/.ensemble
LOCAL_CA = $(DATA_DIR)/ui/local-ca.crt
trust-local-ca: ## Trust the daemon's per-machine UI CA so https://127.0.0.1:9102 loads warning-free
@test -f "$(LOCAL_CA)" || { \
echo "no CA at $(LOCAL_CA) — start the daemon once with --ui-local-https first"; \
exit 1; }
@uname_out=$$(uname -s); \
case "$$uname_out" in \
Linux) \
echo "Installing CA into /usr/local/share/ca-certificates (requires sudo)"; \
sudo cp "$(LOCAL_CA)" /usr/local/share/ca-certificates/ensemble-local-ca.crt; \
sudo update-ca-certificates; \
echo "Done. Firefox uses its own trust store — visit https://127.0.0.1:9102 and add an exception, or import $(LOCAL_CA) under Preferences → Privacy & Security → Certificates."; \
;; \
Darwin) \
echo "Installing CA into the System keychain (requires sudo + your login password)"; \
sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain "$(LOCAL_CA)"; \
;; \
*) \
echo "Unsupported platform $$uname_out. Manually trust $(LOCAL_CA) in your OS / browser certificate store."; \
exit 1; \
;; \
esac
## .NET client (clients/dotnet)
# The integration suite spawns `bin/ensemble`, so build-dev (skips the SPA
# embed, no npm install required) is the right prerequisite — fast and
# sufficient for gRPC-surface tests.
DOTNET ?= dotnet
DOTNET_SLN = clients/dotnet/Ensemble.Client.sln
dotnet-build: ## Build the .NET client solution (Ensemble.Client + tests)
$(DOTNET) build $(DOTNET_SLN) -c Debug
dotnet-test: ## Run .NET unit tests (excludes integration suite)
$(DOTNET) test $(DOTNET_SLN) -c Debug --filter "Category!=Integration"
dotnet-test-integration: build-dev ## Run .NET integration tests against a real ensemble daemon
ENSEMBLE_BIN=$(CURDIR)/bin/$(BINARY_NAME) \
$(DOTNET) test $(DOTNET_SLN) -c Debug --filter "Category=Integration"
## Cross-compilation
#
# Only linux/amd64 embeds Tor (cgo, host C compiler) — that's the realistic
# embedded desktop target and what the spike proved. Every other cross target
# builds pure-Go (CGO_ENABLED=0) and runs an external tor via --tor-path:
# - macOS / Windows: go-libtor has no C sources there (`!linux` → stub).
# - linux/arm{,64}: cgo-cross-compiling go-libtor via zig hit a wall of
# go-libtor C-portability + glibc-version issues (NDEBUG, strlcpy, UBSan,
# epoll_pwait2/arc4random) — not worth chasing for a non-shipping target, so
# they use `-tags noembedtor` to drop embedded Tor and cross-compile cleanly.
# For an embedded Tor binary on a Pi, build natively on the device (cgo just
# works there) — or revisit if a linux/arm clubhouse build is ever needed.
# `make build-all` runs them all. The server/container image is built separately
# (deployment/Dockerfile, also pure-Go external via -tags noembedtor).
build-linux-amd64: ## Build for Linux amd64 (embedded Tor, cgo, host C compiler)
@$(HOST_CGO_GUARD)
CGO_ENABLED=1 GOOS=linux GOARCH=amd64 $(GO) build $(GOFLAGS) -o bin/$(BINARY_NAME)-linux-amd64 .
build-linux-arm64: ## Build for Linux arm64 (pure-Go, external tor via --tor-path) — Raspberry Pi 3/4/5 64-bit
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 $(GO) build $(GOFLAGS) -tags noembedtor -o bin/$(BINARY_NAME)-linux-arm64 .
build-linux-arm: ## Build for Linux 32-bit ARMv7 (pure-Go, external tor via --tor-path) — Pi 2/3/Zero 2 W on 32-bit OS
CGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=7 $(GO) build $(GOFLAGS) -tags noembedtor -o bin/$(BINARY_NAME)-linux-arm .
build-darwin-amd64: ## Build for macOS amd64 (pure-Go, external tor via --tor-path)
CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 $(GO) build $(GOFLAGS) -o bin/$(BINARY_NAME)-darwin-amd64 .
build-darwin-arm64: ## Build for macOS arm64 / Apple Silicon (pure-Go, external tor via --tor-path)
CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 $(GO) build $(GOFLAGS) -o bin/$(BINARY_NAME)-darwin-arm64 .
build-windows-amd64: ## Build for Windows amd64 (pure-Go, external tor via --tor-path)
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 $(GO) build $(GOFLAGS) -o bin/$(BINARY_NAME)-windows-amd64.exe .
build-all: build-linux-amd64 build-linux-arm64 build-linux-arm build-darwin-amd64 build-darwin-arm64 build-windows-amd64 ## Build for all platforms
## Housekeeping
clean: ## Remove build artifacts
rm -rf bin/ coverage.out
help: ## Show this help
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}'