From 7acbdd9af395134c332312a6194b475e7acd35de Mon Sep 17 00:00:00 2001 From: ensan-hcl Date: Sun, 14 Jun 2026 19:32:27 +0900 Subject: [PATCH] feat: add Sparkle auto update release flow --- .gitignore | 5 + README.md | 31 +- azooKeyMac.xcodeproj/project.pbxproj | 24 ++ .../xcshareddata/swiftpm/Package.resolved | 104 +++++++ azooKeyMac/AppDelegate.swift | 41 +++ azooKeyMac/Info.plist | 6 + azooKeyMac/azooKeyMac.entitlements | 2 + create_release.sh | 279 ++++++++++++++++++ pkg-scripts/postinstall | 25 +- pkg-scripts/preinstall | 13 + pkgbuild.sh | 19 +- sparkle-appcast.example.xml | 22 ++ 12 files changed, 564 insertions(+), 7 deletions(-) create mode 100644 azooKeyMac.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved create mode 100755 create_release.sh create mode 100644 pkg-scripts/preinstall create mode 100644 sparkle-appcast.example.xml diff --git a/.gitignore b/.gitignore index 279e0dd6..bd50bf56 100644 --- a/.gitignore +++ b/.gitignore @@ -21,5 +21,10 @@ DerivedData/ *.xcodeproj/* !*.xcodeproj/project.pbxproj !*.xcodeproj/xcshareddata/ +!azooKeyMac.xcodeproj/project.xcworkspace/ +azooKeyMac.xcodeproj/project.xcworkspace/* +!azooKeyMac.xcodeproj/project.xcworkspace/xcshareddata/ +!azooKeyMac.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/ +!azooKeyMac.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved **/xcshareddata/WorkspaceSettings.xcsettings !*.xcworkspace/contents.xcworkspacedata diff --git a/README.md b/README.md index e710c7a7..eb6186fc 100644 --- a/README.md +++ b/README.md @@ -124,7 +124,36 @@ ls -lh azooKeyMac/Resources/zenz-v3.1-small-gguf/ggml-model-Q5_K_M.gguf * Git LFSが導入されていない環境では、重みファイルがローカル環境に落とせていない場合があります。`azooKeyMac/Resources/zenz-v3.1-small-gguf/ggml-model-Q5_K_M.gguf` が数十MB以上あるかを確認し、ポインタのままであれば `git -C azooKeyMac/Resources/zenz-v3.1-small-gguf lfs pull` を実行してください ### pkgファイルの作成 -`pkgbuild.sh`によって配布用のdmgファイルを作成できます。`build/azooKeyMac.app` としてDeveloper IDで署名済みの.appを配置してください。 +`pkgbuild.sh`によって配布用のpkgファイルを作成できます。作成された `azooKey-release-signed.pkg` は初回インストールと上書きインストールの両方で利用します。 + +自動アップデートはSparkle経由のpkg更新として配布します。リリースビルドではSparkleの `generate_keys` で作成した公開鍵を `SPARKLE_PUBLIC_ED_KEY` に設定してください。 + +初回のみ、SparkleのEdDSA鍵を生成します。`generate_keys` は秘密鍵を実行したMacのlogin Keychainに保存し、アプリへ埋め込む公開鍵を出力します。秘密鍵はGitHubやリポジトリに置かないでください。 + +```sh +xcodebuild -resolvePackageDependencies \ + -project azooKeyMac.xcodeproj \ + -scheme azooKeyMac \ + -clonedSourcePackagesDirPath ./build/source-packages + +./build/source-packages/artifacts/sparkle/Sparkle/bin/generate_keys +``` + +出力された `SUPublicEDKey` の文字列を、以後のrelease時に `SPARKLE_PUBLIC_ED_KEY` として渡します。 + +GitHub Releaseの作成は `create_release.sh` で行います。このスクリプトは実行中のcommitにtagを打ち、`pkgbuild.sh` を実行し、`azooKey-release-signed.pkg` と `appcast.xml` をGitHub Releaseへアップロードします。release前に `CURRENT_PROJECT_VERSION` を増やしてください。 + +```sh +SPARKLE_PUBLIC_ED_KEY="..." ./create_release.sh --stable-release +SPARKLE_PUBLIC_ED_KEY="..." ./create_release.sh --pre-release +SPARKLE_PUBLIC_ED_KEY="..." ./create_release.sh --test-release +``` + +stable releaseのtagは標準で `vMARKETING_VERSION`、pre-releaseのtagは `vMARKETING_VERSION-pre.CURRENT_PROJECT_VERSION` になります。必要な場合は `--tag`、`--repo`、`--remote` で上書きできます。 + +`--test-release` は自動アップデートの動作検証用です。標準では固定tag `sparkle-update-test` のGitHub Releaseを作成または更新し、アプリに埋め込むfeed URLも `https://github.com/azooKey/azooKey-Desktop/releases/download/sparkle-update-test/appcast.xml` に切り替えます。このpkgを手動インストールした環境では、以後Sparkleが検証用feedを見に行きます。tag名を変える場合は `TEST_RELEASE_TAG` または `--tag`、feed URLを別の固定URLにしたい場合は `SPARKLE_FEED_URL` を指定してください。 + +pkg更新後、postinstallはConverterServerのLaunchAgentを再登録してConverterServerだけを再起動します。既に起動しているazooKeyMacクライアントは終了せず、クライアント側の更新は次回ログイン後に反映されます。 ### v1.0リリースに向けて [meta: v1.0のリリースに向けたロードマップ(#181)](https://github.com/azooKey/azooKey-Desktop/issues/181)をご覧ください. diff --git a/azooKeyMac.xcodeproj/project.pbxproj b/azooKeyMac.xcodeproj/project.pbxproj index 74e3c37a..0f8c3a78 100644 --- a/azooKeyMac.xcodeproj/project.pbxproj +++ b/azooKeyMac.xcodeproj/project.pbxproj @@ -16,6 +16,7 @@ 551434E82D576B6600A9B5CA /* lm_c_abc.marisa in Resources */ = {isa = PBXBuildFile; fileRef = 551434E12D576B6600A9B5CA /* lm_c_abc.marisa */; }; 551434EA2D57A8AC00A9B5CA /* ggml-model-Q5_K_M.gguf in Resources */ = {isa = PBXBuildFile; fileRef = 551434E92D57A8AC00A9B5CA /* ggml-model-Q5_K_M.gguf */; }; 554A26602DB37657003C5CFB /* Core in Frameworks */ = {isa = PBXBuildFile; productRef = 554A265F2DB37657003C5CFB /* Core */; }; + 556B9A102EFD000000000001 /* Sparkle in Frameworks */ = {isa = PBXBuildFile; productRef = 556B9A0F2EFD000000000001 /* Sparkle */; }; 556C52FB2BAAAF7E00EB343F /* en.tiff in Resources */ = {isa = PBXBuildFile; fileRef = 556C52F92BAAAF7D00EB343F /* en.tiff */; }; 556C52FC2BAAAF7E00EB343F /* en@2x.tiff in Resources */ = {isa = PBXBuildFile; fileRef = 556C52FA2BAAAF7D00EB343F /* en@2x.tiff */; }; 55A27D022BAAADDB00512DCD /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 55A27D002BAAADDB00512DCD /* InfoPlist.strings */; }; @@ -75,6 +76,7 @@ buildActionMask = 2147483647; files = ( 554A26602DB37657003C5CFB /* Core in Frameworks */, + 556B9A102EFD000000000001 /* Sparkle in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -175,6 +177,7 @@ name = azooKeyMac; packageProductDependencies = ( 554A265F2DB37657003C5CFB /* Core */, + 556B9A0F2EFD000000000001 /* Sparkle */, ); productName = azooKeyMac; productReference = 1A41E61426E745D9009B65D7 /* azooKeyMac.app */; @@ -256,6 +259,7 @@ mainGroup = 1A41E60B26E745D9009B65D7; packageReferences = ( 554A265E2DB37657003C5CFB /* XCLocalSwiftPackageReference "Core" */, + 556B9A0E2EFD000000000001 /* XCRemoteSwiftPackageReference "Sparkle" */, ); productRefGroup = 1A41E61526E745D9009B65D7 /* Products */; projectDirPath = ""; @@ -530,6 +534,8 @@ MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = dev.ensan.inputmethod.azooKeyMac; PRODUCT_NAME = "$(TARGET_NAME)"; + SPARKLE_FEED_URL = "https://github.com/azooKey/azooKey-Desktop/releases/latest/download/appcast.xml"; + SPARKLE_PUBLIC_ED_KEY = ""; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_OBJC_INTEROP_MODE = objcxx; SWIFT_VERSION = 5.0; @@ -566,6 +572,8 @@ MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = dev.ensan.inputmethod.azooKeyMac; PRODUCT_NAME = "$(TARGET_NAME)"; + SPARKLE_FEED_URL = "https://github.com/azooKey/azooKey-Desktop/releases/latest/download/appcast.xml"; + SPARKLE_PUBLIC_ED_KEY = ""; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_OBJC_INTEROP_MODE = objcxx; SWIFT_VERSION = 5.0; @@ -712,6 +720,17 @@ }; /* End XCLocalSwiftPackageReference section */ +/* Begin XCRemoteSwiftPackageReference section */ + 556B9A0E2EFD000000000001 /* XCRemoteSwiftPackageReference "Sparkle" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/sparkle-project/Sparkle"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 2.9.3; + }; + }; +/* End XCRemoteSwiftPackageReference section */ + /* Begin XCSwiftPackageProductDependency section */ 554A265F2DB37657003C5CFB /* Core */ = { isa = XCSwiftPackageProductDependency; @@ -722,6 +741,11 @@ package = 554A265E2DB37657003C5CFB /* XCLocalSwiftPackageReference "Core" */; productName = Core; }; + 556B9A0F2EFD000000000001 /* Sparkle */ = { + isa = XCSwiftPackageProductDependency; + package = 556B9A0E2EFD000000000001 /* XCRemoteSwiftPackageReference "Sparkle" */; + productName = Sparkle; + }; /* End XCSwiftPackageProductDependency section */ }; rootObject = 1A41E60C26E745D9009B65D7 /* Project object */; diff --git a/azooKeyMac.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/azooKeyMac.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved new file mode 100644 index 00000000..8027bf29 --- /dev/null +++ b/azooKeyMac.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -0,0 +1,104 @@ +{ + "originHash" : "55effa169647ca9c7c8b5246b11dc5675832149b5597141776ed501cf66fa2ff", + "pins" : [ + { + "identity" : "azookeykanakanjiconverter", + "kind" : "remoteSourceControl", + "location" : "https://github.com/azooKey/AzooKeyKanaKanjiConverter", + "state" : { + "revision" : "bbef9d2d99a2e9e69ac3f7e2e07b08474de59a81" + } + }, + { + "identity" : "jinja", + "kind" : "remoteSourceControl", + "location" : "https://github.com/johnmai-dev/Jinja", + "state" : { + "revision" : "31c4dd39bcdc07eaa42a384bdc88ea599022b800", + "version" : "1.1.2" + } + }, + { + "identity" : "sparkle", + "kind" : "remoteSourceControl", + "location" : "https://github.com/sparkle-project/Sparkle", + "state" : { + "revision" : "d46d456107feacc80711b21847b82b07bd9fb46e", + "version" : "2.9.3" + } + }, + { + "identity" : "swift-algorithms", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-algorithms", + "state" : { + "revision" : "87e50f483c54e6efd60e885f7f5aa946cee68023", + "version" : "1.2.1" + } + }, + { + "identity" : "swift-asn1", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-asn1.git", + "state" : { + "revision" : "eb50cbd14606a9161cbc5d452f18797c90ef0bab", + "version" : "1.7.0" + } + }, + { + "identity" : "swift-collections", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-collections", + "state" : { + "revision" : "7b847a3b7008b2dc2f47ca3110d8c782fb2e5c7e", + "version" : "1.3.0" + } + }, + { + "identity" : "swift-crypto", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-crypto.git", + "state" : { + "revision" : "95ba0316a9b733e92bb6b071255ff46263bbe7dc", + "version" : "3.15.1" + } + }, + { + "identity" : "swift-numerics", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-numerics.git", + "state" : { + "revision" : "0c0290ff6b24942dadb83a929ffaaa1481df04a2", + "version" : "1.1.1" + } + }, + { + "identity" : "swift-tokenizers", + "kind" : "remoteSourceControl", + "location" : "https://github.com/ensan-hcl/swift-tokenizers", + "state" : { + "revision" : "4a606f66e0cc4d7d9f0197649e812f7fc86a4c34", + "version" : "0.0.1" + } + }, + { + "identity" : "swiftymarisa", + "kind" : "remoteSourceControl", + "location" : "https://github.com/ensan-hcl/SwiftyMarisa", + "state" : { + "revision" : "91acc3cb0e747e0bd4b0e0813d42fb273d10e62f", + "version" : "0.0.1" + } + }, + { + "identity" : "zipfoundation", + "kind" : "remoteSourceControl", + "location" : "https://github.com/weichsel/ZIPFoundation.git", + "state" : { + "revision" : "22787ffb59de99e5dc1fbfe80b19c97a904ad48d", + "version" : "0.9.20" + } + } + ], + "version" : 3 +} diff --git a/azooKeyMac/AppDelegate.swift b/azooKeyMac/AppDelegate.swift index 5ff7f873..3c0f8885 100644 --- a/azooKeyMac/AppDelegate.swift +++ b/azooKeyMac/AppDelegate.swift @@ -9,6 +9,7 @@ import Cocoa import Core import InputMethodKit import KanaKanjiConverterModuleWithDefaultDictionary +import Sparkle import SwiftUI // Necessary to launch this app @@ -34,6 +35,7 @@ class AppDelegate: NSObject, NSApplicationDelegate { var configWindowController: NSWindowController? var userDictionaryEditorWindowController: NSWindowController? var kanaKanjiConverter = KanaKanjiConverter.withDefaultDictionary() + private var updaterController: SPUStandardUpdaterController? private var userDictionaryMemoryDirectoryURL: URL { AppGroup.memoryDirectoryURL() @@ -122,10 +124,47 @@ class AppDelegate: NSObject, NSApplicationDelegate { } } + @objc private func checkForUpdates(_ sender: Any?) { + guard let updaterController else { + let alert = NSAlert() + alert.messageText = "自動アップデートは未設定です" + alert.informativeText = "SPARKLE_FEED_URL と SPARKLE_PUBLIC_ED_KEY を設定したビルドで利用できます。" + alert.runModal() + return + } + updaterController.checkForUpdates(sender) + } + + private func configureUpdaterIfAvailable() { + guard + let feedURL = Bundle.main.object(forInfoDictionaryKey: "SUFeedURL") as? String, + !feedURL.isEmpty, + let publicEDKey = Bundle.main.object(forInfoDictionaryKey: "SUPublicEDKey") as? String, + !publicEDKey.isEmpty + else { + NSLog("Sparkle updater is disabled because SUFeedURL or SUPublicEDKey is not configured.") + return + } + self.updaterController = SPUStandardUpdaterController(startingUpdater: true, updaterDelegate: nil, userDriverDelegate: nil) + } + + private func addApplicationMenuIfNeeded() { + let appMenuItem = NSMenuItem(title: "azooKey", action: nil, keyEquivalent: "") + NSApp.mainMenu?.addItem(appMenuItem) + + let appSubmenu = NSMenu(title: "azooKey") + appMenuItem.submenu = appSubmenu + + let checkForUpdatesItem = NSMenuItem(title: "更新を確認...", action: #selector(checkForUpdates(_:)), keyEquivalent: "") + checkForUpdatesItem.target = self + appSubmenu.addItem(checkForUpdatesItem) + } + func applicationDidFinishLaunching(_ notification: Notification) { // Insert code here to initialize your application self.server = IMKServer(name: Bundle.main.infoDictionary?["InputMethodConnectionName"] as? String, bundleIdentifier: Bundle.main.bundleIdentifier) NSLog("tried connection") + self.configureUpdaterIfAvailable() // Keychainから設定値を非同期で読み込み Task { @@ -138,6 +177,8 @@ class AppDelegate: NSObject, NSApplicationDelegate { NSApp.mainMenu = NSMenu() } + self.addApplicationMenuIfNeeded() + // Add an Edit menu let editMenu = NSMenuItem(title: "Edit", action: nil, keyEquivalent: "") NSApp.mainMenu?.addItem(editMenu) diff --git a/azooKeyMac/Info.plist b/azooKeyMac/Info.plist index 27b5e671..8727dff2 100644 --- a/azooKeyMac/Info.plist +++ b/azooKeyMac/Info.plist @@ -4,6 +4,12 @@ LSUIElement + SUEnableInstallerLauncherService + + SUFeedURL + $(SPARKLE_FEED_URL) + SUPublicEDKey + $(SPARKLE_PUBLIC_ED_KEY) ComponentInputModeDict tsVisibleInputModeOrderedArrayKey diff --git a/azooKeyMac/azooKeyMac.entitlements b/azooKeyMac/azooKeyMac.entitlements index b54a5c38..4df6530d 100644 --- a/azooKeyMac/azooKeyMac.entitlements +++ b/azooKeyMac/azooKeyMac.entitlements @@ -15,6 +15,8 @@ com.apple.security.temporary-exception.mach-lookup.global-name dev.ensan.inputmethod.azooKeyMac.ConverterServer + $(PRODUCT_BUNDLE_IDENTIFIER)-spks + $(PRODUCT_BUNDLE_IDENTIFIER)-spki diff --git a/create_release.sh b/create_release.sh new file mode 100755 index 00000000..2921ee99 --- /dev/null +++ b/create_release.sh @@ -0,0 +1,279 @@ +#!/bin/bash +set -euo pipefail + +PROJECT_NAME="azooKeyMac" +SCHEME="azooKeyMac" +CONFIGURATION="Release" +PKG_PATH="./azooKey-release-signed.pkg" +APPCAST_PATH="./build/release/appcast.xml" +GITHUB_REPOSITORY="${GITHUB_REPOSITORY:-azooKey/azooKey-Desktop}" +GIT_REMOTE="${GIT_REMOTE:-origin}" +SPARKLE_FEED_URL="${SPARKLE_FEED_URL:-}" +TEST_RELEASE_TAG="${TEST_RELEASE_TAG:-sparkle-update-test}" + +release_kind="" +tag_name="" + +usage() { + cat <<'EOF' +Usage: + ./create_release.sh --stable-release + ./create_release.sh --pre-release + ./create_release.sh --test-release + +Options: + --stable-release Create a stable GitHub Release and mark it as latest. + --pre-release Create a GitHub pre-release. It is not marked as latest. + --test-release Create or update the fixed test release for update verification. + --tag TAG Override the generated tag name. + --repo OWNER/REPO GitHub repository to upload to. Default: azooKey/azooKey-Desktop. + --remote REMOTE Git remote used for pushing the tag. Default: origin. + -h, --help Show this help. + +Required environment: + SPARKLE_PUBLIC_ED_KEY Sparkle EdDSA public key embedded into the app. + +Optional environment: + SPARKLE_SIGN_UPDATE Path to Sparkle's sign_update tool. + SPARKLE_FEED_URL Appcast URL embedded into the app. + TEST_RELEASE_TAG Fixed tag used by --test-release. Default: sparkle-update-test. + GITHUB_REPOSITORY Default value for --repo. + GIT_REMOTE Default value for --remote. +EOF +} + +fail() { + echo "error: $*" >&2 + exit 1 +} + +require_command() { + command -v "$1" >/dev/null 2>&1 || fail "missing required command: $1" +} + +read_build_setting() { + local key="$1" + awk -v key="$key" ' + $1 == key { + value = $3 + sub(/;$/, "", value) + gsub(/^"|"$/, "", value) + print value + exit + } + ' "${PROJECT_NAME}.xcodeproj/project.pbxproj" +} + +xml_escape() { + local value="$1" + value="${value//&/&}" + value="${value///>}" + value="${value//\"/"}" + value="${value//\'/'}" + printf '%s' "$value" +} + +find_sign_update() { + if [ -n "${SPARKLE_SIGN_UPDATE:-}" ]; then + [ -x "${SPARKLE_SIGN_UPDATE}" ] || fail "SPARKLE_SIGN_UPDATE is not executable: ${SPARKLE_SIGN_UPDATE}" + printf '%s\n' "${SPARKLE_SIGN_UPDATE}" + return + fi + + local candidate + for candidate in \ + "./build/source-packages/artifacts/sparkle/Sparkle/bin/sign_update" \ + "./.build/artifacts/sparkle/Sparkle/bin/sign_update" \ + "${HOME}/Library/Developer/Xcode/DerivedData"/*/SourcePackages/artifacts/sparkle/Sparkle/bin/sign_update + do + if [ -x "${candidate}" ]; then + printf '%s\n' "${candidate}" + return + fi + done + + fail "Sparkle sign_update was not found. Set SPARKLE_SIGN_UPDATE to its path." +} + +while [ "$#" -gt 0 ]; do + case "$1" in + --stable-release) + [ -z "${release_kind}" ] || fail "choose only one of --stable-release, --pre-release, or --test-release" + release_kind="stable" + ;; + --pre-release) + [ -z "${release_kind}" ] || fail "choose only one of --stable-release, --pre-release, or --test-release" + release_kind="pre" + ;; + --test-release) + [ -z "${release_kind}" ] || fail "choose only one of --stable-release, --pre-release, or --test-release" + release_kind="test" + ;; + --tag) + shift + [ "$#" -gt 0 ] || fail "--tag requires a value" + tag_name="$1" + ;; + --repo) + shift + [ "$#" -gt 0 ] || fail "--repo requires a value" + GITHUB_REPOSITORY="$1" + ;; + --remote) + shift + [ "$#" -gt 0 ] || fail "--remote requires a value" + GIT_REMOTE="$1" + ;; + -h|--help) + usage + exit 0 + ;; + *) + fail "unknown option: $1" + ;; + esac + shift +done + +[ -n "${release_kind}" ] || fail "choose --stable-release, --pre-release, or --test-release" +[ -n "${SPARKLE_PUBLIC_ED_KEY:-}" ] || fail "SPARKLE_PUBLIC_ED_KEY is required" + +require_command git +require_command gh +require_command awk +require_command date +require_command sed +require_command tr + +if ! git diff --quiet || ! git diff --cached --quiet; then + fail "tracked files have uncommitted changes. Commit before creating a release." +fi + +marketing_version="$(read_build_setting MARKETING_VERSION)" +build_version="$(read_build_setting CURRENT_PROJECT_VERSION)" +[ -n "${marketing_version}" ] || fail "MARKETING_VERSION was not found" +[ -n "${build_version}" ] || fail "CURRENT_PROJECT_VERSION was not found" + +if [ -z "${tag_name}" ]; then + if [ "${release_kind}" = "stable" ]; then + tag_name="v${marketing_version}" + elif [ "${release_kind}" = "pre" ]; then + tag_name="v${marketing_version}-pre.${build_version}" + else + tag_name="${TEST_RELEASE_TAG}" + fi +fi + +if [ -z "${SPARKLE_FEED_URL}" ]; then + if [ "${release_kind}" = "test" ]; then + SPARKLE_FEED_URL="https://github.com/${GITHUB_REPOSITORY}/releases/download/${tag_name}/appcast.xml" + else + SPARKLE_FEED_URL="https://github.com/${GITHUB_REPOSITORY}/releases/latest/download/appcast.xml" + fi +fi + +if [ "${release_kind}" = "test" ] && [[ "${tag_name}" =~ ^v[0-9] ]]; then + fail "--test-release refuses to force-update a version-like tag: ${tag_name}" +fi + +if [ "${release_kind}" != "test" ]; then + if git rev-parse -q --verify "refs/tags/${tag_name}" >/dev/null; then + fail "local tag already exists: ${tag_name}" + fi + if git ls-remote --exit-code --tags "${GIT_REMOTE}" "refs/tags/${tag_name}" >/dev/null 2>&1; then + fail "remote tag already exists: ${tag_name}" + fi +fi + +rm -f "${PKG_PATH}" + +DERIVED_DATA_PATH="./build/derived-data" \ +CLONED_SOURCE_PACKAGES_DIR_PATH="./build/source-packages" \ +SPARKLE_FEED_URL="${SPARKLE_FEED_URL}" \ +SPARKLE_PUBLIC_ED_KEY="${SPARKLE_PUBLIC_ED_KEY}" \ + ./pkgbuild.sh + +[ -f "${PKG_PATH}" ] || fail "pkgbuild did not create ${PKG_PATH}" + +sign_update="$(find_sign_update)" +signature_attributes="$("${sign_update}" "${PKG_PATH}" | tr '\n' ' ' | sed 's/[[:space:]]*$//')" +[ -n "${signature_attributes}" ] || fail "sign_update did not output Sparkle signature attributes" + +pkg_asset_name="$(basename "${PKG_PATH}")" +pkg_url="https://github.com/${GITHUB_REPOSITORY}/releases/download/${tag_name}/${pkg_asset_name}" +pub_date="$(LC_ALL=C date -u '+%a, %d %b %Y %H:%M:%S +0000')" +mkdir -p "$(dirname "${APPCAST_PATH}")" + +if [ "${release_kind}" = "stable" ]; then + release_title="azooKey ${marketing_version}" + sparkle_short_version="${marketing_version}" + gh_release_flags=(--latest) +elif [ "${release_kind}" = "pre" ]; then + release_title="azooKey ${marketing_version} pre-release ${build_version}" + sparkle_short_version="${marketing_version}-pre.${build_version}" + gh_release_flags=(--prerelease --latest=false) +else + release_title="azooKey ${marketing_version} update test ${build_version}" + sparkle_short_version="${marketing_version}-test.${build_version}" + gh_release_flags=(--prerelease --latest=false) +fi + +cat > "${APPCAST_PATH}" < + + + azooKey for macOS + https://github.com/$(xml_escape "${GITHUB_REPOSITORY}") + azooKey for macOS updates + ja + + $(xml_escape "${release_title}") + $(xml_escape "${build_version}") + $(xml_escape "${sparkle_short_version}") + 13.0 + ${pub_date} + + + + +EOF + +if [ "${release_kind}" = "test" ]; then + git tag -fa "${tag_name}" -m "${release_title}" + git push --force "${GIT_REMOTE}" "${tag_name}" + if gh release view "${tag_name}" --repo "${GITHUB_REPOSITORY}" >/dev/null 2>&1; then + gh release edit "${tag_name}" \ + --repo "${GITHUB_REPOSITORY}" \ + --title "${release_title}" \ + --prerelease + gh release upload "${tag_name}" "${PKG_PATH}" "${APPCAST_PATH}#appcast.xml" --repo "${GITHUB_REPOSITORY}" --clobber + else + gh release create "${tag_name}" \ + "${PKG_PATH}" \ + "${APPCAST_PATH}#appcast.xml" \ + --repo "${GITHUB_REPOSITORY}" \ + --verify-tag \ + --title "${release_title}" \ + --notes "自動アップデート検証用の固定feedです。" \ + "${gh_release_flags[@]}" + fi +else + git tag -a "${tag_name}" -m "${release_title}" + git push "${GIT_REMOTE}" "${tag_name}" + + gh release create "${tag_name}" \ + "${PKG_PATH}" \ + "${APPCAST_PATH}#appcast.xml" \ + --repo "${GITHUB_REPOSITORY}" \ + --verify-tag \ + --title "${release_title}" \ + --generate-notes \ + "${gh_release_flags[@]}" +fi + +echo "Created ${release_kind} release ${tag_name}" diff --git a/pkg-scripts/postinstall b/pkg-scripts/postinstall index 56d3b6e8..fc17db25 100755 --- a/pkg-scripts/postinstall +++ b/pkg-scripts/postinstall @@ -6,8 +6,25 @@ app_path="/Library/Input Methods/azooKeyMac.app" server_path="${app_path}/Contents/MacOS/ConverterServer" agent_dir="/Library/LaunchAgents" agent_path="${agent_dir}/${service_name}.plist" +state_dir="/private/tmp/dev.ensan.inputmethod.azooKeyMac.pkg" +install_state_path="${state_dir}/install-state" script_dir="$(cd "$(dirname "$0")" && pwd)" +install_state="initial_install" +if [ -f "${install_state_path}" ]; then + install_state="$(cat "${install_state_path}")" +fi +rm -f "${install_state_path}" +rmdir "${state_dir}" >/dev/null 2>&1 || true + +case "${install_state}" in + initial_install|update) + ;; + *) + install_state="initial_install" + ;; +esac + if [ ! -x "${server_path}" ]; then echo "ConverterServer not found: ${server_path}" >&2 exit 1 @@ -19,7 +36,7 @@ chown root:wheel "${agent_path}" console_user="$(stat -f %Su /dev/console)" if [ -z "${console_user}" ] || [ "${console_user}" = "root" ] || [ "${console_user}" = "_mbsetupuser" ]; then - echo "No active console user; ${service_name} will start on next login." + echo "No active console user; ${service_name} will start on next login. install_state=${install_state}" exit 0 fi @@ -36,4 +53,8 @@ launchctl bootstrap "${gui_domain}" "${agent_path}" launchctl kickstart -k "${gui_domain}/${service_name}" launchctl print "${gui_domain}/${service_name}" >/dev/null -echo "Installed and started ${service_name}" +if [ "${install_state}" = "update" ]; then + echo "Updated azooKeyMac and restarted ${service_name}; azooKeyMac client will keep running until next login." +else + echo "Installed azooKeyMac and started ${service_name}" +fi diff --git a/pkg-scripts/preinstall b/pkg-scripts/preinstall new file mode 100644 index 00000000..11280922 --- /dev/null +++ b/pkg-scripts/preinstall @@ -0,0 +1,13 @@ +#!/bin/sh +set -eu + +app_path="/Library/Input Methods/azooKeyMac.app" +state_dir="/private/tmp/dev.ensan.inputmethod.azooKeyMac.pkg" +install_state_path="${state_dir}/install-state" + +mkdir -p "${state_dir}" +if [ -d "${app_path}" ]; then + echo "update" > "${install_state_path}" +else + echo "initial_install" > "${install_state_path}" +fi diff --git a/pkgbuild.sh b/pkgbuild.sh index f7572e61..b88a3218 100755 --- a/pkgbuild.sh +++ b/pkgbuild.sh @@ -8,6 +8,14 @@ EXPORT_PATH="./build/export" EXPORT_OPTIONS_PLIST="./exportOptions.plist" PKG_SCRIPTS_SOURCE_PATH="./pkg-scripts" PKG_SCRIPTS_PATH="./build/pkg-scripts" +SPARKLE_FEED_URL="${SPARKLE_FEED_URL:-https://github.com/azooKey/azooKey-Desktop/releases/latest/download/appcast.xml}" +SPARKLE_PUBLIC_ED_KEY="${SPARKLE_PUBLIC_ED_KEY:-}" +DERIVED_DATA_PATH="${DERIVED_DATA_PATH:-$(mktemp -d)}" +CLONED_SOURCE_PACKAGES_DIR_PATH="${CLONED_SOURCE_PACKAGES_DIR_PATH:-$(mktemp -d)}" + +if [ -z "${SPARKLE_PUBLIC_ED_KEY}" ]; then + echo "warning: SPARKLE_PUBLIC_ED_KEY is not set; Sparkle updater will be disabled in this build." >&2 +fi # 1. Clean Build rm -rf ./build @@ -23,10 +31,12 @@ xcodebuild \ clean archive \ -configuration "${CONFIGURATION}" \ -archivePath "${ARCHIVE_PATH}" \ - -derivedDataPath "$(mktemp -d)" \ - -clonedSourcePackagesDirPath $(mktemp -d) \ + -derivedDataPath "${DERIVED_DATA_PATH}" \ + -clonedSourcePackagesDirPath "${CLONED_SOURCE_PACKAGES_DIR_PATH}" \ -allowProvisioningUpdates \ - -destination "generic/platform=macOS" + -destination "generic/platform=macOS" \ + SPARKLE_FEED_URL="${SPARKLE_FEED_URL}" \ + SPARKLE_PUBLIC_ED_KEY="${SPARKLE_PUBLIC_ED_KEY}" # 3. Export xcodebuild -exportArchive \ @@ -55,9 +65,10 @@ rm ${EXPORT_PATH}/DistributionSummary.plist rm ${EXPORT_PATH}/ExportOptions.plist mkdir -p "${PKG_SCRIPTS_PATH}" +cp "${PKG_SCRIPTS_SOURCE_PATH}/preinstall" "${PKG_SCRIPTS_PATH}/preinstall" cp "${PKG_SCRIPTS_SOURCE_PATH}/postinstall" "${PKG_SCRIPTS_PATH}/postinstall" cp "./Tools/write_converter_server_launch_agent.sh" "${PKG_SCRIPTS_PATH}/write_converter_server_launch_agent.sh" -chmod +x "${PKG_SCRIPTS_PATH}/postinstall" "${PKG_SCRIPTS_PATH}/write_converter_server_launch_agent.sh" +chmod +x "${PKG_SCRIPTS_PATH}/preinstall" "${PKG_SCRIPTS_PATH}/postinstall" "${PKG_SCRIPTS_PATH}/write_converter_server_launch_agent.sh" # Suppose we have build/azooKeyMac.app # Use this script to create a plist package for distribution diff --git a/sparkle-appcast.example.xml b/sparkle-appcast.example.xml new file mode 100644 index 00000000..c5fc1952 --- /dev/null +++ b/sparkle-appcast.example.xml @@ -0,0 +1,22 @@ + + + + azooKey for macOS + https://github.com/azooKey/azooKey-Desktop + azooKey for macOS updates + ja + + azooKey 1.0 + 1 + 1.0 + 13.0 + Sun, 14 Jun 2026 00:00:00 +0000 + + + +