.PHONY: \ lint test clean \ version-sync version-check version-bump \ server-run server-build server-lint server-test server-clean \ desktop-build-mac desktop-build-win desktop-build-linux \ desktop-lint desktop-test desktop-clean \ release-assets-check release-assets-web release-assets-linux release-assets-windows release-assets-macos release-assets-checksums \ release-assets-server-linux release-assets-server-windows release-assets-server-macos \ release-assets-desktop-linux release-assets-desktop-windows release-assets-desktop-macos \ _backend-lint _backend-test _backend-clean _backend-build \ _versionctl-lint _versionctl-test \ _frontend-install _frontend-build _frontend-check _frontend-test _frontend-dev _frontend-clean \ _desktop-test _desktop-clean _desktop-prepare-frontend _desktop-prepare-embedfs _desktop-prepare-windows-resource \ _server-run-backend _server-run-frontend \ _check-linux-target-arch _check-windows-target-arch _ensure-appimagetool \ _package-linux-tar _package-linux-appimage _package-linux-deb _package-linux-rpm \ _package-macos-zip _package-macos-dmg # Delay shell lookups until a target needs them, then cache the result for this make run. lazy_shell = $(or $($(1)),$(eval $(1) := $(shell $(2)))$($(1))) VERSION = $(call lazy_shell,_VERSION,go run ./versionctl print) GIT_COMMIT ?= $(call lazy_shell,_GIT_COMMIT,git rev-parse --short HEAD 2>/dev/null || printf 'unknown') BUILD_TIME ?= $(call lazy_shell,_BUILD_TIME,date -u +"%Y-%m-%dT%H:%M:%SZ") TARGET_ARCH ?= $(call lazy_shell,_TARGET_ARCH,go env GOARCH) GO_LDFLAGS = -X nex/backend/pkg/buildinfo.version=$(VERSION) -X nex/backend/pkg/buildinfo.commit=$(GIT_COMMIT) -X nex/backend/pkg/buildinfo.buildTime=$(BUILD_TIME) GO_LDFLAGS_WIN = $(GO_LDFLAGS) -H=windowsgui RELEASE_DIR ?= build/release LINUX_DESKTOP_BINARY = build/nex-linux-$(TARGET_ARCH) WINDOWS_DESKTOP_BINARY = build/nex-win-$(TARGET_ARCH).exe WINDOWS_SERVER_BINARY = build/nex-server-windows-$(TARGET_ARCH).exe WINDRES ?= windres ifeq ($(TARGET_ARCH),arm64) APPIMAGE_ARCH := aarch64 DEB_ARCH := arm64 RPM_ARCH := aarch64 else APPIMAGE_ARCH := x86_64 DEB_ARCH := amd64 RPM_ARCH := x86_64 endif WINDOWS_WINDRES_FORMAT_BFD := pe-x86-64 WINDOWS_WINDRES_FORMAT_LLVM := x86_64-w64-mingw32 WINDOWS_RESOURCE := rsrc_windows_amd64.syso APPIMAGETOOL_PATH := build/tools/appimagetool-$(APPIMAGE_ARCH).AppImage APPIMAGETOOL_URL ?= https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-$(APPIMAGE_ARCH).AppImage APPIMAGETOOL ?= $(APPIMAGETOOL_PATH) # ============================================ # 全局命令 # ============================================ lint: _backend-lint _frontend-check _versionctl-lint @printf 'Lint complete\n' test: _backend-test _frontend-test _desktop-test _versionctl-test @printf 'All tests passed\n' clean: _backend-clean _frontend-clean _desktop-clean @printf 'Clean complete\n' # ============================================ # 版本管理 # ============================================ version-sync: go run ./versionctl sync version-check: go run ./versionctl check version-bump: BUMP ?= patch version-bump: $(eval _BUMP_ARG := $(if $(SET_VERSION),$(SET_VERSION),$(BUMP))) $(eval _NEW_VERSION := $(shell go run ./versionctl bump $(_BUMP_ARG))) git add VERSION frontend/ git commit -m "chore: 版本升迁 v$(_NEW_VERSION)" git tag "v$(_NEW_VERSION)" @printf '版本升迁完成: v%s\n' "$(_NEW_VERSION)" # ============================================ # Server 模式 # ============================================ server-run: @$(MAKE) -j2 _server-run-backend _server-run-frontend server-build: version-check _backend-build _frontend-build @printf 'Server build complete\n' server-lint: _backend-lint _frontend-check @printf 'Server lint complete\n' server-test: _backend-test _frontend-test @printf 'Server tests passed\n' server-clean: _backend-clean _frontend-clean @printf 'Server artifacts cleaned\n' _server-run-backend: @$(MAKE) -C backend run _server-run-frontend: _frontend-install cd frontend && bun run dev # ============================================ # Desktop 模式 # ============================================ desktop-build-mac: version-check _desktop-prepare-frontend _desktop-prepare-embedfs @printf 'Building macOS desktop...\n' cd backend && CGO_ENABLED=1 GOOS=darwin GOARCH=arm64 go build -ldflags "$(GO_LDFLAGS)" -o ../build/nex-mac-arm64 ./cmd/desktop cd backend && CGO_ENABLED=1 GOOS=darwin GOARCH=amd64 go build -ldflags "$(GO_LDFLAGS)" -o ../build/nex-mac-amd64 ./cmd/desktop lipo -create build/nex-mac-arm64 build/nex-mac-amd64 -output build/nex-mac-universal lipo -info build/nex-mac-universal | grep -q 'x86_64 arm64' rm -f build/nex-mac-arm64 build/nex-mac-amd64 @printf 'Packaging macOS app bundle...\n' rm -rf build/Nex.app mkdir -p build/Nex.app/Contents/MacOS build/Nex.app/Contents/Resources cp build/nex-mac-universal build/Nex.app/Contents/MacOS/nex @if [ -f assets/icon.icns ]; then \ cp assets/icon.icns build/Nex.app/Contents/Resources/; \ else \ printf 'Missing assets/icon.icns\n'; \ exit 1; \ fi @MIN_MACOS_VERSION=$$(vtool -show-build build/nex-mac-universal | awk '/minos / {print $$2; exit}'); \ if [ -z "$$MIN_MACOS_VERSION" ]; then \ printf 'Unable to read macOS minimum version\n'; \ exit 1; \ fi; \ go run ./versionctl macos-plist "$$MIN_MACOS_VERSION" > build/Nex.app/Contents/Info.plist chmod +x build/Nex.app/Contents/MacOS/nex @printf 'macOS desktop build complete\n' desktop-build-win: version-check _desktop-prepare-frontend _desktop-prepare-embedfs _desktop-prepare-windows-resource _check-windows-target-arch @printf 'Building Windows desktop $(TARGET_ARCH)...\n' mkdir -p build cd backend && CGO_ENABLED=1 GOOS=windows GOARCH=$(TARGET_ARCH) go build -ldflags "$(GO_LDFLAGS_WIN)" -o ../$(WINDOWS_DESKTOP_BINARY) ./cmd/desktop @printf 'Windows desktop build complete\n' desktop-build-linux: version-check _desktop-prepare-frontend _desktop-prepare-embedfs _check-linux-target-arch @printf 'Building Linux desktop $(TARGET_ARCH)...\n' mkdir -p build cd backend && CGO_ENABLED=1 GOOS=linux GOARCH=$(TARGET_ARCH) go build -ldflags "$(GO_LDFLAGS)" -o ../$(LINUX_DESKTOP_BINARY) ./cmd/desktop @printf 'Linux desktop build complete\n' desktop-lint: _backend-lint _frontend-check @printf 'Desktop lint complete\n' desktop-test: _desktop-test @printf 'Desktop tests passed\n' desktop-clean: _desktop-clean @printf 'Desktop artifacts cleaned\n' _desktop-test: cd backend && go test ./cmd/desktop/... -v _desktop-clean: rm -rf build/ embedfs/assets embedfs/frontend-dist backend/cmd/desktop/rsrc_windows_amd64.syso _desktop-prepare-frontend: _frontend-install @printf 'Preparing frontend for desktop...\n' cd frontend && cp .env.desktop .env.production.local cd frontend && bun run build rm -f frontend/.env.production.local _desktop-prepare-embedfs: @printf 'Preparing embedded filesystem...\n' rm -rf embedfs/assets embedfs/frontend-dist cp -r assets embedfs/assets cp -r frontend/dist embedfs/frontend-dist _desktop-prepare-windows-resource: _check-windows-target-arch @printf 'Preparing Windows $(TARGET_ARCH) executable icon...\n' @WINDRES_CMD="$(WINDRES)"; \ WINDRES_FMT="$(WINDOWS_WINDRES_FORMAT_BFD)"; \ if command -v llvm-windres >/dev/null 2>&1; then \ WINDRES_CMD=llvm-windres; \ WINDRES_FMT="$(WINDOWS_WINDRES_FORMAT_LLVM)"; \ elif "$$WINDRES_CMD" --version 2>&1 | grep -qi LLVM; then \ WINDRES_FMT="$(WINDOWS_WINDRES_FORMAT_LLVM)"; \ fi; \ command -v "$$WINDRES_CMD" >/dev/null 2>&1 || { printf 'Missing windres tool: %s\n' "$$WINDRES_CMD"; exit 1; }; \ cd backend/cmd/desktop && "$$WINDRES_CMD" -O coff -F "$$WINDRES_FMT" -i icon_windows.rc -o $(WINDOWS_RESOURCE) # ============================================ # 发布资产 # ============================================ release-assets-check: go run ./versionctl release-assets-check @printf 'Release assets check passed\n' release-assets-web: version-check release-assets-check _frontend-build rm -rf "$(RELEASE_DIR)" mkdir -p "$(RELEASE_DIR)" asset=$$(go run ./versionctl asset-name web tar.gz); \ tar -C frontend -czf "$(RELEASE_DIR)/$$asset" dist release-assets-linux: version-check release-assets-check _check-linux-target-arch rm -rf "$(RELEASE_DIR)" mkdir -p "$(RELEASE_DIR)" @$(MAKE) release-assets-server-linux TARGET_ARCH=$(TARGET_ARCH) RELEASE_DIR="$(RELEASE_DIR)" @$(MAKE) release-assets-desktop-linux TARGET_ARCH=$(TARGET_ARCH) RELEASE_DIR="$(RELEASE_DIR)" release-assets-windows: version-check release-assets-check _check-windows-target-arch rm -rf "$(RELEASE_DIR)" mkdir -p "$(RELEASE_DIR)" @$(MAKE) release-assets-server-windows TARGET_ARCH=$(TARGET_ARCH) RELEASE_DIR="$(RELEASE_DIR)" @$(MAKE) release-assets-desktop-windows TARGET_ARCH=$(TARGET_ARCH) RELEASE_DIR="$(RELEASE_DIR)" release-assets-macos: version-check release-assets-check rm -rf "$(RELEASE_DIR)" mkdir -p "$(RELEASE_DIR)" @$(MAKE) release-assets-server-macos RELEASE_DIR="$(RELEASE_DIR)" @$(MAKE) release-assets-desktop-macos RELEASE_DIR="$(RELEASE_DIR)" release-assets-server-linux: version-check _check-linux-target-arch mkdir -p build "$(RELEASE_DIR)" cd backend && CGO_ENABLED=1 GOOS=linux GOARCH=$(TARGET_ARCH) go build -ldflags "$(GO_LDFLAGS)" -o ../build/nex-server-linux-$(TARGET_ARCH) ./cmd/server asset=$$(go run ./versionctl asset-name server linux $(TARGET_ARCH) tar.gz); \ tar -C build -czf "$(RELEASE_DIR)/$$asset" nex-server-linux-$(TARGET_ARCH) release-assets-server-windows: version-check _check-windows-target-arch mkdir -p build "$(RELEASE_DIR)" cd backend && CGO_ENABLED=1 GOOS=windows GOARCH=$(TARGET_ARCH) go build -ldflags "$(GO_LDFLAGS)" -o ../$(WINDOWS_SERVER_BINARY) ./cmd/server asset=$$(go run ./versionctl asset-name server windows $(TARGET_ARCH) zip); \ if command -v powershell.exe >/dev/null 2>&1; then POWERSHELL=powershell.exe; else POWERSHELL=powershell; fi; \ "$$POWERSHELL" -NoProfile -Command "Compress-Archive -LiteralPath '$(WINDOWS_SERVER_BINARY)' -DestinationPath '$(RELEASE_DIR)/$$asset' -Force" release-assets-server-macos: version-check mkdir -p build "$(RELEASE_DIR)" cd backend && CGO_ENABLED=1 GOOS=darwin GOARCH=amd64 go build -ldflags "$(GO_LDFLAGS)" -o ../build/nex-server-macos-amd64 ./cmd/server cd backend && CGO_ENABLED=1 GOOS=darwin GOARCH=arm64 go build -ldflags "$(GO_LDFLAGS)" -o ../build/nex-server-macos-arm64 ./cmd/server lipo -create build/nex-server-macos-amd64 build/nex-server-macos-arm64 -output build/nex-server-macos-universal lipo -info build/nex-server-macos-universal | grep -q 'x86_64 arm64' asset=$$(go run ./versionctl asset-name server macos amd64 tar.gz); \ tar -C build -czf "$(RELEASE_DIR)/$$asset" nex-server-macos-amd64 asset=$$(go run ./versionctl asset-name server macos arm64 tar.gz); \ tar -C build -czf "$(RELEASE_DIR)/$$asset" nex-server-macos-arm64 asset=$$(go run ./versionctl asset-name server macos universal tar.gz); \ tar -C build -czf "$(RELEASE_DIR)/$$asset" nex-server-macos-universal rm -f build/nex-server-macos-amd64 build/nex-server-macos-arm64 build/nex-server-macos-universal release-assets-desktop-linux: version-check release-assets-check desktop-build-linux _package-linux-tar _package-linux-appimage _package-linux-deb _package-linux-rpm release-assets-desktop-windows: version-check release-assets-check desktop-build-win mkdir -p "$(RELEASE_DIR)" asset=$$(go run ./versionctl asset-name desktop windows $(TARGET_ARCH) zip); \ if command -v powershell.exe >/dev/null 2>&1; then POWERSHELL=powershell.exe; else POWERSHELL=powershell; fi; \ "$$POWERSHELL" -NoProfile -Command "Compress-Archive -LiteralPath '$(WINDOWS_DESKTOP_BINARY)' -DestinationPath '$(RELEASE_DIR)/$$asset' -Force" release-assets-desktop-macos: version-check release-assets-check desktop-build-mac _package-macos-zip _package-macos-dmg rm -rf build/Nex.app build/dmg release-assets-checksums: @cd "$(RELEASE_DIR)" && \ rm -f SHA256SUMS && \ for asset in *; do \ [ -f "$$asset" ] || continue; \ if command -v sha256sum >/dev/null 2>&1; then \ sha256sum "$$asset"; \ elif command -v shasum >/dev/null 2>&1; then \ shasum -a 256 "$$asset"; \ else \ printf 'Missing sha256sum or shasum\n' >&2; \ exit 1; \ fi; \ done > SHA256SUMS && \ test -s SHA256SUMS _check-linux-target-arch: @if [ "$(TARGET_ARCH)" != "amd64" ] && [ "$(TARGET_ARCH)" != "arm64" ]; then \ printf 'Unsupported Linux TARGET_ARCH: %s\n' "$(TARGET_ARCH)"; \ exit 1; \ fi _check-windows-target-arch: @if [ "$(TARGET_ARCH)" != "amd64" ]; then \ printf 'Unsupported Windows TARGET_ARCH: %s\n' "$(TARGET_ARCH)"; \ exit 1; \ fi _ensure-appimagetool: @mkdir -p build/tools @if [ ! -x "$(APPIMAGETOOL)" ]; then \ printf 'Downloading appimagetool for %s...\n' "$(APPIMAGE_ARCH)"; \ command -v curl >/dev/null 2>&1 || { printf 'Missing curl for appimagetool download\n'; exit 1; }; \ curl -L "$(APPIMAGETOOL_URL)" -o "$(APPIMAGETOOL)"; \ chmod +x "$(APPIMAGETOOL)"; \ fi; \ printf 'Using appimagetool: %s\n' "$(APPIMAGETOOL)" _package-linux-tar: mkdir -p "$(RELEASE_DIR)" asset=$$(go run ./versionctl asset-name desktop linux $(TARGET_ARCH) tar.gz); \ tar -C build -czf "$(RELEASE_DIR)/$$asset" nex-linux-$(TARGET_ARCH) _package-linux-appimage: _ensure-appimagetool mkdir -p "$(RELEASE_DIR)" appdir="build/appimage/nex-$(TARGET_ARCH).AppDir"; \ rm -rf "$$appdir"; \ mkdir -p "$$appdir/usr/bin" "$$appdir/usr/share/applications" "$$appdir/usr/share/icons"; \ install -m 0755 "$(LINUX_DESKTOP_BINARY)" "$$appdir/usr/bin/nex"; \ install -m 0644 packaging/linux/nex.desktop "$$appdir/nex.desktop"; \ install -m 0644 packaging/linux/nex.desktop "$$appdir/usr/share/applications/nex.desktop"; \ install -m 0755 packaging/linux/AppRun "$$appdir/AppRun"; \ cp -R assets/icons/hicolor "$$appdir/usr/share/icons/"; \ cp assets/icon.png "$$appdir/nex.png"; \ asset=$$(go run ./versionctl asset-name desktop linux $(TARGET_ARCH) AppImage); \ ARCH=$(APPIMAGE_ARCH) APPIMAGE_EXTRACT_AND_RUN=1 "$(APPIMAGETOOL)" "$$appdir" "$(RELEASE_DIR)/$$asset"; \ chmod +x "$(RELEASE_DIR)/$$asset"; \ test -s "$(RELEASE_DIR)/$$asset" _package-linux-deb: mkdir -p "$(RELEASE_DIR)" pkgdir="build/pkg/deb/nex-$(TARGET_ARCH)"; \ rm -rf "$$pkgdir"; \ mkdir -p "$$pkgdir/DEBIAN" "$$pkgdir/usr/bin" "$$pkgdir/usr/share/applications" "$$pkgdir/usr/share/icons"; \ install -m 0755 "$(LINUX_DESKTOP_BINARY)" "$$pkgdir/usr/bin/nex"; \ install -m 0644 packaging/linux/nex.desktop "$$pkgdir/usr/share/applications/nex.desktop"; \ cp -R assets/icons/hicolor "$$pkgdir/usr/share/icons/"; \ printf '%s\n' \ 'Package: nex' \ 'Version: $(VERSION)' \ 'Section: utils' \ 'Priority: optional' \ 'Architecture: $(DEB_ARCH)' \ 'Maintainer: Nex Maintainers ' \ 'Depends: libgtk-3-0, libayatana-appindicator3-1, xdg-utils' \ 'Description: AI Gateway desktop application' \ ' Nex is an AI Gateway desktop application.' \ > "$$pkgdir/DEBIAN/control"; \ asset=$$(go run ./versionctl asset-name desktop linux $(TARGET_ARCH) deb); \ dpkg-deb --build --root-owner-group "$$pkgdir" "$(RELEASE_DIR)/$$asset"; \ dpkg-deb -I "$(RELEASE_DIR)/$$asset" >/dev/null _package-linux-rpm: mkdir -p "$(RELEASE_DIR)" topdir="$(abspath build/rpmbuild-$(TARGET_ARCH))"; \ rm -rf "$$topdir"; \ mkdir -p "$$topdir/BUILD" "$$topdir/BUILDROOT" "$$topdir/RPMS" "$$topdir/SOURCES" "$$topdir/SPECS" "$$topdir/SRPMS"; \ rpmbuild -bb --target "$(RPM_ARCH)" \ --define "_topdir $$topdir" \ --define "nex_version $(VERSION)" \ --define "nex_binary $(abspath $(LINUX_DESKTOP_BINARY))" \ --define "nex_desktop_file $(abspath packaging/linux/nex.desktop)" \ --define "nex_icons_dir $(abspath assets/icons/hicolor)" \ packaging/linux/nex.spec; \ rpm_file=$$(find "$$topdir/RPMS" -type f -name '*.rpm' | sort | tail -n 1); \ test -n "$$rpm_file"; \ asset=$$(go run ./versionctl asset-name desktop linux $(TARGET_ARCH) rpm); \ cp "$$rpm_file" "$(RELEASE_DIR)/$$asset"; \ rpm -qip "$(RELEASE_DIR)/$$asset" >/dev/null _package-macos-zip: mkdir -p "$(RELEASE_DIR)" asset=$$(go run ./versionctl asset-name desktop macos universal zip); \ ditto -c -k --keepParent build/Nex.app "$(RELEASE_DIR)/$$asset" _package-macos-dmg: mkdir -p "$(RELEASE_DIR)" dmgdir="build/dmg/Nex"; \ rm -rf "$$dmgdir"; \ mkdir -p "$$dmgdir"; \ cp -R build/Nex.app "$$dmgdir/Nex.app"; \ ln -s /Applications "$$dmgdir/Applications"; \ asset=$$(go run ./versionctl asset-name desktop macos universal dmg); \ hdiutil create -volname Nex -srcfolder "$$dmgdir" -ov -format UDZO "$(RELEASE_DIR)/$$asset"; \ hdiutil verify "$(RELEASE_DIR)/$$asset" >/dev/null && \ rm -rf "$$dmgdir" # ============================================ # 共享 helper targets # ============================================ _backend-build: @$(MAKE) -C backend build _backend-lint: @$(MAKE) -C backend lint _backend-test: @$(MAKE) -C backend test _backend-clean: @$(MAKE) -C backend clean _versionctl-lint: @$(MAKE) -C versionctl lint _versionctl-test: @$(MAKE) -C versionctl test _frontend-install: cd frontend && bun install _frontend-build: _frontend-install cd frontend && bun run build _frontend-check: _frontend-install cd frontend && bun run check _frontend-test: _frontend-install cd frontend && bun run test _frontend-dev: _frontend-install cd frontend && bun run dev _frontend-clean: rm -rf frontend/dist frontend/.next frontend/coverage frontend/playwright-report frontend/test-results frontend/tsconfig.tsbuildinfo