* plugin,gui: a stalled stream can't wedge the host, stale reads can't draw, and the browser suite runs in CI internal/plugin: the host has one reader of the child's stdout, and it handed each message to its call by sending on that call's channel. A caller that stopped draining a streamed body (a slow HTTP client on a plugin-backed chat stream) filled the channel; the reader then blocked, and every other call through the host -- all plugin providers and accounts, moved built-ins included -- stopped being dispatched until the stalled stream was drained. A second call's answer arrived only after the first was drained. Each call now queues its head and chunks in a bounded backlog (4 MiB of chunks, 4096 of them) drained by its own reader, so the host's reader never waits on one consumer; a call whose consumer stops reading is given up on alone, with the limit as why. Aborts go through a goroutine too (abortLoop), so a child that stopped reading its stdin can't make an abort wait either. host_test.go covers the stalled consumer, the bounds and the abort path. internal/gui: loadUsage() and refreshUsage() both read the Overview's usage?period=, and neither checked after the await that its answer was still the newest: a 24 h answer landing after a 7 d one drew the old period's figures under the new period's name, and leaving the tab could still write the Overview's cost into the shared header. Both now claim one shared read generation before the request and draw only when it is still the newest and the page, tab and period are unchanged. providersWhileFetching() also stopped for good after one failed poll or reload; it now asks again. usage-stale-answer.test.cjs and providers-fetching-retry.test.cjs lift the real functions out of app.js and drive them with a faked api/page/timers, so they need no browser; usage-refresh-flight.test.cjs gains the browser case for the same race. Makefile/.github: `make test-ui` runs every internal/gui/tests/*.test.cjs through Node's test runner -- auto-discovered, UI_TEST_CONCURRENCY files at a time (default 2) -- and a new `ui` job in Test runs it on ubuntu-latest with Playwright's Chromium and WebKit. A missing browser fails the job; it never skips to a pass. internal/gui/tests/README.md points at `make test-ui` instead of a hand-kept command line. Verified: node --test on the two new files 21/21; go test -count=1 ./internal/plugin/ ok; go test -race -count=1 ./internal/plugin/ ok; go vet ./internal/plugin/ and go build ./... clean; node --check on the changed JS; actionlint on the workflow. Not verified: the full browser suite is not green yet. `make test-ui` is 1641 passed / 70 failed of 1711 across 225 files, and 12 of the 19 failing files fail the same way on a pristine main (a known baseline: a fixture boots the page in web mode while asserting the desktop window's narrow behaviour). The `ui` job will be red until those baselines are dealt with -- nothing is skipped or masked -- and the job has not yet run on a GitHub-hosted runner. * plugin: distinguish stalled readers from large responses * plugin: clarify best-effort abort delivery
130 lines
6.0 KiB
Makefile
130 lines
6.0 KiB
Makefile
VERSION ?= $(shell git describe --tags --always --dirty 2>/dev/null || echo dev)
|
|
LDFLAGS = -s -w -X main.version=$(VERSION)
|
|
TAGS = production
|
|
TARGETS = darwin/arm64 darwin/amd64 linux/amd64 linux/arm64 windows/amd64 windows/arm64 android/arm64
|
|
|
|
# The GUI links the platform webview through cgo, so it is built natively.
|
|
# `nogui` builds the terminal-only magpie, which cross-compiles anywhere.
|
|
# macOS 12 is the oldest Go (1.25 and later) runs on; Info.plist says so too.
|
|
ifeq ($(shell uname -s),Darwin)
|
|
export CGO_CFLAGS = -mmacosx-version-min=12.0
|
|
export CGO_LDFLAGS = -mmacosx-version-min=12.0
|
|
endif
|
|
# On Linux, GTK 3 and WebKitGTK 4.1: older distributions have them, where
|
|
# Wails' default GTK 4 and WebKitGTK 6 would leave them out.
|
|
ifeq ($(shell uname -s),Linux)
|
|
TAGS += gtk3
|
|
endif
|
|
# Termux uses Android's libc and has no desktop webview. Keep the web UI
|
|
# and terminal commands, without linking GTK or Wails.
|
|
ifeq ($(shell go env GOOS),android)
|
|
TAGS = nogui
|
|
endif
|
|
|
|
.PHONY: build cli install test test-ui app icons release release-cli release-windows release-linux clean dev dev-once
|
|
|
|
build:
|
|
go build -tags "$(TAGS)" -trimpath -ldflags="$(LDFLAGS)" -o magpie .
|
|
|
|
cli:
|
|
CGO_ENABLED=0 go build -tags nogui -trimpath -ldflags="$(LDFLAGS)" -o magpie .
|
|
|
|
install:
|
|
go install -tags "$(TAGS)" -trimpath -ldflags="$(LDFLAGS)" .
|
|
|
|
test:
|
|
go vet -tags "$(TAGS)" ./... && go test -tags "$(TAGS)" ./...
|
|
|
|
# The browser regressions under internal/gui/tests: the real pages in
|
|
# Playwright's Chromium and WebKit; see internal/gui/tests/README.md.
|
|
UI_TESTS = $(wildcard internal/gui/tests/*.test.cjs)
|
|
# Files are independent and run in parallel; 1 diagnoses a flaky one.
|
|
UI_TEST_CONCURRENCY ?= 2
|
|
|
|
test-ui:
|
|
@test -n "$(UI_TESTS)" || { echo "no GUI tests found under internal/gui/tests" >&2; exit 1; }
|
|
node --test --test-concurrency=$(UI_TEST_CONCURRENCY) $(UI_TESTS)
|
|
|
|
# macOS bundle: menu bar app with no Dock icon (LSUIElement).
|
|
app: build
|
|
@rm -rf magpie.app
|
|
@mkdir -p magpie.app/Contents/MacOS magpie.app/Contents/Resources
|
|
@cp magpie magpie.app/Contents/MacOS/magpie
|
|
@cp build/darwin/magpie.icns magpie.app/Contents/Resources/magpie.icns
|
|
@cp build/darwin/Assets.car magpie.app/Contents/Resources/Assets.car
|
|
@sed 's/@VERSION@/$(VERSION)/' build/darwin/Info.plist > magpie.app/Contents/Info.plist
|
|
@echo " magpie.app"
|
|
|
|
icons:
|
|
@go run build/icon/gen.go tray internal/gui/tray.png
|
|
@go run build/icon/gen.go tray-flap internal/gui/trayflap
|
|
@go run build/icon/gen.go app 64 internal/gui/icon.png
|
|
@go run build/icon/gen.go app 1024 internal/gui/icon-1024.png
|
|
@rm -rf build/darwin/magpie.iconset && mkdir -p build/darwin/magpie.iconset
|
|
@for s in 16 32 128 256 512; do \
|
|
go run build/icon/gen.go app $$s build/darwin/magpie.iconset/icon_$${s}x$${s}.png; \
|
|
go run build/icon/gen.go app $$((s*2)) build/darwin/magpie.iconset/icon_$${s}x$${s}@2x.png; \
|
|
done
|
|
@iconutil -c icns build/darwin/magpie.iconset -o build/darwin/magpie.icns && rm -rf build/darwin/magpie.iconset
|
|
@# macOS 26 draws the Dock icon from AppIcon.icon, light and dark (Xcode 26's actool);
|
|
@# the compiled Assets.car is kept in the tree so a release needn't have it
|
|
@go run build/icon/gen.go glyph 1024 build/darwin/AppIcon.icon/Assets/magpie.png
|
|
@rm -rf build/darwin/actool && mkdir -p build/darwin/actool
|
|
@xcrun actool build/darwin/AppIcon.icon --compile build/darwin/actool --platform macosx \
|
|
--minimum-deployment-target 11.0 --app-icon AppIcon --output-partial-info-plist build/darwin/actool/partial.plist >/dev/null
|
|
@mv build/darwin/actool/Assets.car build/darwin/Assets.car && rm -rf build/darwin/actool
|
|
|
|
release: clean build
|
|
@mkdir -p dist
|
|
@cp magpie dist/magpie-$(shell go env GOOS)-$(shell go env GOARCH)
|
|
@$(MAKE) --no-print-directory release-cli
|
|
|
|
release-cli:
|
|
@mkdir -p dist
|
|
@for t in $(TARGETS); do \
|
|
os=$${t%/*}; arch=$${t#*/}; ext=""; [ $$os = windows ] && ext=.exe; \
|
|
echo " $$os/$$arch (cli)"; \
|
|
CGO_ENABLED=0 GOOS=$$os GOARCH=$$arch go build -tags nogui -trimpath -ldflags="$(LDFLAGS)" -o dist/magpie-cli-$$os-$$arch$$ext . || exit 1; \
|
|
done
|
|
|
|
# The desktop app for Windows: the system WebView2 needs no cgo, so both
|
|
# arches cross-compile. Linked as a GUI program (no console window), with
|
|
# the icon, version and a DPI-aware manifest from build/windows.
|
|
WINVER = $(shell echo $(VERSION) | sed -E 's/^v//; s/[^0-9.].*//')
|
|
release-windows:
|
|
@mkdir -p dist
|
|
@go run github.com/tc-hib/go-winres@v0.3.3 make --in build/windows/winres.json --arch amd64,arm64 --out rsrc \
|
|
--file-version "$(or $(WINVER),0.0.0)" --product-version "$(or $(WINVER),0.0.0)"
|
|
@for arch in amd64 arm64; do \
|
|
echo " windows/$$arch (app)"; \
|
|
CGO_ENABLED=0 GOOS=windows GOARCH=$$arch go build -tags production -trimpath -ldflags="$(LDFLAGS) -H windowsgui" -o dist/magpie-windows-$$arch.exe . || exit 1; \
|
|
done; rm -f rsrc_windows_*.syso
|
|
|
|
# The desktop app for Linux links GTK 3 and WebKitGTK 4.1 through cgo, so it
|
|
# is built natively, one arch per machine.
|
|
release-linux:
|
|
@mkdir -p dist
|
|
@echo " linux/$(shell go env GOARCH) (app)"
|
|
@go build -tags "$(TAGS)" -trimpath -ldflags="$(LDFLAGS)" -o dist/magpie-linux-$(shell go env GOARCH) .
|
|
|
|
clean:
|
|
rm -rf magpie magpie.exe magpie.app dist rsrc_windows_*.syso
|
|
|
|
# Development: the UI is served from internal/gui/assets and the window
|
|
# reloads itself when a file there is saved. With fswatch installed, the
|
|
# windows stay up and a Go change rebuilds and restarts only the backend
|
|
# behind them (build/dev.sh); without it, dev-once runs the app as one
|
|
# process. Uses its own ports so a running magpie keeps serving the agents.
|
|
DEV_ADDR ?= 127.0.0.1:3426
|
|
DEV_UI ?= 127.0.0.1:3427
|
|
DEV_BACKEND ?= 127.0.0.1:3428
|
|
DEV_CONTROL ?= 127.0.0.1:3429
|
|
dev:
|
|
@if command -v fswatch >/dev/null; then \
|
|
MAGPIE_ADDR=$(DEV_ADDR) MAGPIE_DEV_UI=$(DEV_UI) MAGPIE_DEV_BACKEND=$(DEV_BACKEND) MAGPIE_DEV_CONTROL=$(DEV_CONTROL) build/dev.sh; \
|
|
else $(MAKE) --no-print-directory dev-once; fi
|
|
|
|
dev-once:
|
|
@go build -tags dev -o magpie-dev . && echo " magpie-dev · UI from internal/gui/assets, reload on save · gateway $(DEV_ADDR)"
|
|
@MAGPIE_ADDR=$(DEV_ADDR) MAGPIE_DEV_UI=$(DEV_UI) ./magpie-dev app
|