본문 바로가기

iOS+

Xcode Cloud와 Sparkle framework

개요

이 글은 Xcode Cloud를 통해 간편하게 CI/CD를 수행하고, Sparkle을 통해 앱에서 간편하게 업데이트 구현하는 법에 대해 정리한다

 

Sparkle을 이용한다면 앱에 조금의 코드 추가로 앱에서 설정한 URL을 통해 appcast.xml을 얻어 업데이트를 수행할 수 있다

 

앞으로 우선 Xcode Cloud에 대해 잠깐 정리하고, Sparkle을 통한 업데이트 구현을 위해 알아야 할 내용들을 간단하게 이해하고, 이후 앱 부분에서 좀 더 자세한 내용들에 대해 알아본다

 

 

  • Xcode Cloud
  • Sparkle Framework
  • 앱 설정과 구현

 

Xcode Cloud

Xcode Cloud는 클라우드에서 앱 빌드, 테스트 등의 서비스를 제공한다

 

Xcode Cloud 설정에서 깃헙 레포지터리를 등록하면, 해당 브랜치에 새로 커밋이 추가될 때마다 빌드를 수행할 수 있다(github action과 유사하다)

Custom build script

Xcode Cloud는 위와 같이 각 단계를 기반한 이름으로 아래와 같은 폴더로 프로젝트에 스크립트를 작성하고 추가하면, 해당 단계에서 스크립트를 수행한다

 

  • ci_scripts/
    • ci_post_clone.sh
    • ci_pre_xcodebuild.sh
    • ci_post_xcodebuild.sh

Environment variable reference

아무래도 스크립트를 작성하다보면, 프로젝트와 관련된 정보를 이용해야 하는 경우도 존재할 텐데, 이미 다양한 CI_ 환경변수들을 제공해 준다

 

특히 유용했던 것은 다음과 같다

 

  • CI_PRIMARY_REPOSITORY_PATH - 프로젝트 경로, 이후에 /PROJECT_NAME이 온다
  • CI_BUILD_NUMBER - 빌드 버전

예) 빌드 버전 자동 업데이트

앱의 버전이나 빌드 버전의 경우는 Info.plist에 기록되는데, CFBundleShortVersionStringCFBundleVersion은 빌드 시 스크립트를 통해 자동으로 업데이트할 수 있다

 

/usr/libexec/PlistBuddy는 plist 파일에 키나 값을 지정하는데 사용된다

만약 현재 빌드버전을 바탕으로 빌드 시, CFBundleVersion로 지정하려면 다음과 같이 스크립트를 작성할 수 있다

 

#!/bin/sh

INFO_PLIST_PATH="${CI_PRIMARY_REPOSITORY_PATH}/TodoMate/Info.plist"
BUILD_VERSION="${CI_BUILD_NUMBER}"

if [ -f "$INFO_PLIST_PATH" ]; then
  # Info.plist에 키가 없으면 생성 후 추가
  # /usr/libexec/PlistBuddy -c "Add :CFBundleVersion string $VERSION" "$INFO_PLIST_PATH"
  /usr/libexec/PlistBuddy -c "Set :CFBundleVersion $VERSION" "$INFO_PLIST_PATH"
  echo "Updated CFBundleVersion to $VERSION in Info.plist"
else
  echo "Info.plist not found at $INFO_PLIST_PATH"
fi

 

Sparkle Framework

이후 앱 부분에서 Sparkle 업데이트를 이용하기 위해 설정해야 할 것과 구현에 대해 자세히 알게 될 것이다

 

그에 앞서 실제로 앱에서 업데이트를 수행하기 위해, 업데이트 가능한 앱에 대한 정보를 제공해주는 appcast.xml에 대해 정리해보면 다음과 같다

appcast.xml

macOS 앱을 배포할 때, 개발자 서명이나 공증 과정을 이미 거치지만, Sparkle을 통해 업데이트된 앱으로 교체되도록 하려면 추가적인 작업들이 필요하다

 

앱에서 Sparkle은 배포자의 appcast.xml 파일을 읽어서 업데이트 여부를 확인하고 업데이트를 수행하는데, 이때 파일은 https를 통해 제공되어야 한다

appcast.xml는 Sparkle에서 제공해주는 툴로 만들 수 있지만, 필자는 그냥 백엔드에서 파일 서명과 appcast.xml 생성을 수행하기로 했다

 

아래는 appcast.xml의 예시이다

 

이때 해당 정보와 업데이트 파일의 무결성을 보장하기 위해 다음과 같은 내용이 사용된다

shortVersionString, version

앱에서 새로운 버전 업데이트를 확인하는데 사용된다

shortVersionString은 우리가 흔히 볼 수 있는 버전(예시-1.0.0)이고, version은 빌드 넘버다

sha256

SHA-256(Secure Hash Algorithm 256-bit)은 데이터를 입력으로 받아 256비트(32바이트)의 해시 값을 생성하는 해시 함수로 다운로드 한 파일의 무결성을 확인하는데 사용된다

edSignature

앱에서 공개키를 통해 검증하여 신뢰하는 개발자인지 확인하는데 이때 EdDSA를 사용한다

ED25519는 공용 키 암호화와 서명 생성에 사용되는 비대칭 암호화 알고리즘으로 타원 곡선 암호화(EC) 알고리즘 중 하나로 높은 성능과 보안성을 제공한다

 

서명

 

검증

 

배포 시, 비밀키로 서명을 수행하고 앱에서 공개키로 검증을 수행한다

이때 공개키는 Info.plist에 등록되어한다(앱 부분에서 설명)

 

App과 AppDelegate

import SwiftUI

@main
struct TodoMateApp: App {
	  @NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
    
	  var body: some Scene {
	      WindowGroup {
	          ContentView()
	      }
	      .commands {
	          CommandGroup(replacing: .appInfo) {
	              Button("Check for Updates") {
	                  appDelegate.checkForUpdates()
	              }
	          }
	      }
	  }
}

 

import SwiftUI
import Cocoa
import Sparkle

class AppDelegate: NSObject, SPUUpdaterDelegate {
    var updater: SPUUpdater?
    
    func applicationDidFinishLaunching(_ notification: Notification) {
        /// Sparkle 업데이트 관리
        let updaterController = SPUStandardUpdaterController(startingUpdater: true, updaterDelegate: self, userDriverDelegate: nil)
        updater = updaterController.updater
    }
}

extension AppDelegate {
    func checkForUpdates() {
        updater?.checkForUpdates()
    }
}

 

업데이트 기능을 이용하기 위해 작성하는 코드 부분은 간단하다

프로젝트에 Sparkle 패키지를 추가하고, AppDelegate에서 SPUUdaterDelegate를 채택하여 updater를 지정하고 이를 CommandGroup으로 등록하면 끝이다

 

이때 업데이트 시, 앱은 배포자가 제공하는 appcast.xml을 바탕으로 업데이트를 진행한다

Info

  • SUPublicEDKey - EdDSA(ed25519) 공개키 추가
  • SUFeedURL - appcast.xml 제공 주소
  • SUEnableInstallerLauncherService

위의 정보를 Info.plist에 추가한다

 

<key>SUEnableInstallerLauncherService</key>
<true/>
<key>SUFeedURL</key>
<string><https://$>(UPDATE_SERVER_URL)</string>
<key>SUPublicEDKey</key>
<string>$(PUBLIC_EDKEY)</string>

 

이때 서명을 위한 키 세팅은 Sparkle에서 제공하는 툴을 이용하거나 직접 만들면 된다

 

필자는 appcast.xml을 제공하는 서버를 만들면서 파이썬으로 코드로 작성하다 보니 그냥 해당 작업을 하는 파이썬 코드를 이용해서 키를 만들었다

 

import nacl.signing
import base64

# 키 쌍 생성
private_key = nacl.signing.SigningKey.generate()
public_key = private_key.verify_key

# 키를 Base64로 인코딩
private_key_base64 = base64.b64encode(bytes(private_key)).decode('utf-8')
public_key_base64 = base64.b64encode(bytes(public_key)).decode('utf-8')

# 키 저장
with open('ed25519_private.key', 'w') as f:
    f.write(private_key_base64)
with open('ed25519_public.key', 'w') as f:
    f.write(public_key_base64)

Entitlements

  • com.apple.security.temporary-exception.mach-lookup.global-name

Entitlements 파일에 다음과 같은 키를 등록해야 한다

 

<key>com.apple.security.temporary-exception.mach-lookup.global-name</key>
<array>
    <string>$(PRODUCT_BUNDLE_IDENTIFIER)-spks</string>
    <string>$(PRODUCT_BUNDLE_IDENTIFIER)-spki</string>
</array>

 

마무리

Xcode Cloud를 이용하면 정말 간단하게 스크립트를 통해 CI/CD 파이프라인을 작성할 수 있다

 

Sparkle을 통해 앱에서 새로운 버전을 손쉽게 업데이트할 수 있다

Sparkle은 앱 설정에서 등록한 주소를 통해 appcast.xml을 얻어 업데이트 관련 정보를 통해 업데이트를 수행한다

 

앱 코드 부분에서는 우선 해당 패키지를 추가하고, AppDelegate에서 Sparkle의 SPUUpdater를 통해 업데이트 시 이용한다

이때 WindowGroup에서 CommandGroup을 이용해서 업데이트 관련 설정창을 등록한다

 

앱 설정 부분에서는 Info와 Entitlements에 Sparkle 관련 설정을 하는데, 공개키는 appcast.xml에서 비밀키로 서명한 edSignature를 검증하기 위해 사용된다

 

 

참고로 현재 프로젝트의 CI/CD 파이프라인은 다음과 같다

 

'iOS+' 카테고리의 다른 글

Objective-C  (1) 2024.09.22
WidgetKit  (0) 2024.09.15
macOS 앱 배포 정리  (1) 2024.08.31
Cannot preview in this file  (0) 2024.08.18
UserNotifications  (0) 2024.08.11