From dce6ab2a796787a763ccdb8e33ae86501c3c24c0 Mon Sep 17 00:00:00 2001 From: gentpan Date: Mon, 14 Sep 2026 23:37:43 +0500 Subject: [PATCH] =?UTF-8?q?=E6=AF=8F=E6=AC=A1=E6=9E=84=E5=BB=BA=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E5=8D=B8=E8=BD=BD=E6=97=A7=E7=89=88=E5=B9=B6=E5=AE=89?= =?UTF-8?q?=E8=A3=85=E6=96=B0=E7=89=88=EF=BC=9A=E6=9C=AC=E6=9C=BA=E5=8F=AA?= =?UTF-8?q?=E4=BF=9D=E7=95=99=20/Applications=20=E4=B8=80=E4=B8=AA=20OpenS?= =?UTF-8?q?tats?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ARCHITECTURE.md | 13 +++++++---- Makefile | 29 ++++++++--------------- Scripts/install_local.sh | 50 ++++++++++++++++++++++++++++++++++++++++ Scripts/release.sh | 5 +++- 4 files changed, 72 insertions(+), 25 deletions(-) create mode 100755 Scripts/install_local.sh diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 414c114..53063a6 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -32,17 +32,20 @@ hosting SwiftUI, no third-party dependencies. The Xcode project is generated fro ```bash brew install xcodegen -make build # Debug -make install # Release into /Applications +make build # Release build, then replace /Applications/OpenStats.app and relaunch make test # swift test in the package ``` Versions read `0.2.0 (0003)`: `MARKETING_VERSION` changes only when releasing (`make bump-patch` for small releases, `make bump-minor` for larger ones), and the four-digit `CURRENT_PROJECT_VERSION` goes up by one on every `make build` (`BUMP=0` skips it). Build numbers -never reset, so every package has a larger `CFBundleVersion` than the one before. Build products are -unregistered from Launch Services so only `/Applications/OpenStats.app` shows up in Spotlight and the -widget gallery. +never reset, so every package has a larger `CFBundleVersion` than the one before. Every `make build` runs +`Scripts/install_local.sh`: it ends the running app (the helper restores fans and sleep when the +connection drops), deletes the old `/Applications/OpenStats.app`, *moves* the new bundle there so no +copy stays in the build folder, re-registers it with Launch Services, and relaunches. Only one +OpenStats ever exists on the machine, so Spotlight and the widget gallery never show duplicates. +`INSTALL=0` compiles without installing; `Scripts/release.sh` uses it and installs the notarized +build at the end. `project.yml` defaults to ad-hoc signing so the project opens anywhere. The Makefile passes the first Developer ID Application identity from the keychain (and `--timestamp` for Release) diff --git a/Makefile b/Makefile index fd7b28b..88965a2 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,7 @@ PROJECT := OpenStats.xcodeproj SCHEME := OpenStats -CONFIG ?= Debug +# 每次构建都会安装到 /Applications,默认用 Release +CONFIG ?= Release DERIVED := build/DerivedData APP := $(DERIVED)/Build/Products/$(CONFIG)/OpenStats.app LSREGISTER := /System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister @@ -21,29 +22,19 @@ endif generate: xcodegen generate --quiet -## 编译 App(含辅助工具);每次构建号加一,BUMP=0 时不加 +## 编译 App(含辅助工具)并安装:卸载旧版、装上新版、启动,本机始终只有 /Applications 一份。 +## 每次构建号加一(BUMP=0 不加);INSTALL=0 只编译不安装(发布脚本使用) build: @if [ "$(BUMP)" != "0" ]; then ./Scripts/version.sh build; fi @$(MAKE) --no-print-directory compile + @if [ "$(INSTALL)" != "0" ]; then ./Scripts/install_local.sh $(APP); fi compile: generate xcodebuild -project $(PROJECT) -scheme $(SCHEME) -configuration $(CONFIG) \ -derivedDataPath $(DERIVED) -destination 'platform=macOS' -quiet $(SIGN_FLAGS) build - @# 编译产物不登记到启动服务,系统里只保留 /Applications 那一份(聚焦搜索、打开方式、小组件列表不重复) - @$(LSREGISTER) -u $(APP) >/dev/null 2>&1 || true -## 编译并启动(会先结束正在运行的 OpenStats) -run: build stop - open $(APP) - -## 以 Release 构建安装到 /Applications 并启动(覆盖旧版本,保留偏好设置) -install: - $(MAKE) build CONFIG=Release - -pkill -x OpenStats - rm -rf /Applications/OpenStats.app - ditto $(DERIVED)/Build/Products/Release/OpenStats.app /Applications/OpenStats.app - $(LSREGISTER) -f /Applications/OpenStats.app - open /Applications/OpenStats.app +## 与 build 相同,保留旧名字 +run install: build ## 发布:签名、公证、装订、打包 DMG,生成 Homebrew cask(见 Scripts/release.sh) release: @@ -69,9 +60,9 @@ bump-major: test: cd Packages/OpenStatsKit && swift test -## 用本机实时数据渲染各页面截图到 build/snapshots -snapshot: build - $(APP)/Contents/MacOS/OpenStats --snapshot build/snapshots +## 用本机实时数据渲染各页面截图到 build/snapshots(使用已安装的应用,不重新构建) +snapshot: + /Applications/OpenStats.app/Contents/MacOS/OpenStats --snapshot build/snapshots open: generate open $(PROJECT) diff --git a/Scripts/install_local.sh b/Scripts/install_local.sh new file mode 100755 index 0000000..54d885a --- /dev/null +++ b/Scripts/install_local.sh @@ -0,0 +1,50 @@ +#!/bin/bash +# 本机只保留一个 OpenStats:退出正在运行的旧版 → 删除 /Applications 里的旧版 → 把刚编译的新版移进去 → 启动。 +# 用移动而不是复制,编译目录里不留第二份;其他位置被系统登记的同名应用会取消登记并提示。 +# +# Scripts/install_local.sh build/DerivedData/Build/Products/Release/OpenStats.app +set -euo pipefail +cd "$(dirname "$0")/.." + +SOURCE="${1:?用法:Scripts/install_local.sh }" +TARGET="/Applications/OpenStats.app" +BUNDLE_ID="com.openstats.app" +LSREGISTER=/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister + +[ -d "$SOURCE" ] || { echo "error: 找不到 $SOURCE" >&2; exit 1; } +codesign --verify --deep --strict "$SOURCE" + +# 结束旧版。辅助工具在连接断开时会自动恢复风扇与睡眠设置,所以直接结束是安全的 +# (不用 AppleScript 退出,免得弹出“自动化”权限请求) +if pgrep -x OpenStats >/dev/null; then + pkill -x OpenStats 2>/dev/null || true + for _ in $(seq 1 25); do pgrep -x OpenStats >/dev/null || break; sleep 0.2; done +fi + +if [ -d "$TARGET" ]; then + "$LSREGISTER" -u "$TARGET" >/dev/null 2>&1 || true + rm -rf "$TARGET" +fi +mv "$SOURCE" "$TARGET" +"$LSREGISTER" -f "$TARGET" + +# 编译目录里残留的旧产物(Debug / Release 另一种配置)也清掉 +for stale in build/DerivedData/Build/Products/*/OpenStats.app; do + [ -d "$stale" ] || continue + "$LSREGISTER" -u "$stale" >/dev/null 2>&1 || true + rm -rf "$stale" +done + +# 其他位置的同名应用只取消登记并提示,不擅自删除 +others="$(mdfind "kMDItemCFBundleIdentifier == '$BUNDLE_ID'" 2>/dev/null | grep -vx "$TARGET" | grep -v '^/Volumes/' || true)" +if [ -n "$others" ]; then + echo "⚠️ 其他位置还有 OpenStats(已取消系统登记,如不需要请手动删除):" + while IFS= read -r path; do + "$LSREGISTER" -u "$path" >/dev/null 2>&1 || true + echo " $path" + done <<< "$others" +fi + +open "$TARGET" +version="$(defaults read "$TARGET/Contents/Info" CFBundleShortVersionString) ($(defaults read "$TARGET/Contents/Info" CFBundleVersion))" +echo "✅ 已安装并启动 OpenStats $version:$TARGET" diff --git a/Scripts/release.sh b/Scripts/release.sh index 6ff2a8f..f4e51b2 100755 --- a/Scripts/release.sh +++ b/Scripts/release.sh @@ -37,7 +37,7 @@ echo "版本 ${VERSION} · 签名身份:${SIGN_ID}" # ---- 构建 -------------------------------------------------------------------- -make build CONFIG=Release SIGN_ID="$SIGN_ID" +make build CONFIG=Release INSTALL=0 SIGN_ID="$SIGN_ID" codesign --verify --deep --strict --verbose=2 "$APP" for binary in "$APP" "$APP/Contents/MacOS/OpenStatsHelper" "$APP/Contents/PlugIns/OpenStatsWidget.appex"; do @@ -122,6 +122,9 @@ cask "openstats" do end CASK +# 本机只保留一份:把刚公证的发布版装到 /Applications,编译目录里不留副本 +./Scripts/install_local.sh "$APP" + echo echo "✅ ${DIST}/${DMG_NAME}" echo " SHA-256 ${SHA256}"