CLI·빌드 도구
952a7ff 기준입니다.xtool은 SwiftPM 패키지를 iOS 앱으로 빌드하고, 서명해서 실제 기기에 설치하는 크로스플랫폼 명령줄 도구입니다. 핵심은 다른 운영체제에서 Swift 문법을 쓰는 데 있지 않습니다. 이미 존재하는 Swift 컴파일러와 Apple SDK를 연결하고, 설치 가능한 앱을 완성하는 나머지 절차를 자동화하는 데 있습니다.
컴파일된 실행 파일과 아이폰에 설치할 수 있는 앱 사이의 간격을 메우는 것이 xtool의 핵심입니다. 그 간격을 이해하면 빌드 도구, 패키지 관리자, 코드 서명이 왜 서로 다른 부품인지 보이기 시작합니다.
요리가 완성되어도 도시락 용기, 메뉴표, 출입 허가, 배송 경로가 없으면 손님에게 가지 못합니다. 실행 파일도 마찬가지입니다. .app이라는 묶음에 실행 파일과 자원을 담고, Info.plist에 신원을 적고, 개발 계정의 허가와 서명을 붙여 기기에 전달해야 합니다.
xtool이 컴파일러 자체를 새로 만드는 것은 아닙니다. 여러 도구가 각자 이해하는 입력과 출력을 이어 붙여, 사람이 반복하던 출고 절차를 코드로 만듭니다.
Package.swift를 읽어 소스와 의존성을 빌드하는 패키지 관리자입니다. SDK는 특정 운영체제용 API·라이브러리·개발 정보를 모은 도구 상자입니다. 앱 번들은 실행 파일과 설정·자원을 정해진 구조로 담은 디렉토리이며, iOS에서는 보통 이름.app 형태입니다.아래는 공식 설치와 SDK 구성을 마친 뒤의 사용 흐름입니다. 첫 명령은 새 앱 프로젝트를 만들고, 두 번째는 앱을 조립합니다. 기기 설치와 인증은 뒤 단계에서 별도로 다루면 실패 원인을 좁히기 쉽습니다.
# 개발 환경 설정 후, 새로운 앱 프로젝트에서 사용하는 예
xtool new --skip-setup Hello
cd Hello
xtool dev build
# 인증과 기기 연결까지 준비된 경우의 설치 경로
xtool dev run
# 명시적인 기기 실행은 별도 명령이다
xtool launch 실제로_설치된_bundleID
Sources/XToolSupport/NewCommand.swift의 NewCommand.run()은 Package.swift, xtool.yml, SwiftUI 화면 코드를 생성합니다. --skip-setup을 생략하면 먼저 SetupOperation을 호출하므로, 프로젝트 생성만 하려는 독자는 이 차이를 알아두어야 합니다.
Sources/XToolSupport/DevCommand.swift의 DevRunCommand.run()은 앱 빌드 후 IntegratedInstaller.install()로 연결됩니다. 이 함수 본문에는 LaunchCommand 호출이 없습니다. 명령 이름의 “run”만 보고 자동 실행까지 보장된다고 읽지 말고, 실제 설치와 실행 단계를 구분해야 합니다.
README는 빌드, 서명·설치, Apple Developer Services 접근을 지원한다고 설명합니다. 소스에서도 이 세 경로를 확인할 수 있습니다. 반면 Xcode의 화면 편집기, SwiftUI 미리보기, Instruments 전체를 구현한 IDE라고 이해하면 범위를 크게 벗어납니다. macOS의 작은 SwiftUI 창도 코드 편집기가 아니라 CLI 등록 방법을 알려주는 안내 화면입니다.
읽고 나면 “소스가 왜 라이브러리 제품으로 선언되는가”, “서명 전에 왜 계정과 기기를 등록하는가”, “Linux용 도구로 어떻게 iOS용 파일을 만드는가”를 파일·함수와 연결해서 설명하는 것이 목표입니다.
2026-10-23 분석 기준 선정 순위는 GitHub Trending Swift Weekly #1입니다. 스타 13,421개와 포크 715개는 관심의 크기를 보여주지만, 특정 기능 때문에 순위가 올랐음을 증명하지는 않습니다. 아래 세 가지는 README·문서·구현으로 볼 때 관심을 끌 만한 이유에 대한 해석입니다.
프로젝트 소개의 “Windows”는 공식 설치 문서 Documentation/xtool.docc/Installation-Linux.md에서 WSL을 통한 Linux 환경으로 구체화됩니다. 기존 Linux 개발 환경에서 SwiftPM 프로젝트를 빌드하고 아이폰으로 전달할 수 있다는 점이 눈에 띕니다. Windows 네이티브 실행 파일이나 Linux용 iOS 시뮬레이터가 제공된다는 뜻은 아닙니다.
개발용 컴퓨터의 운영체제와 결과물이 실행될 운영체제를 분리해 생각하게 만든다는 점이 이 프로젝트의 매력입니다. Linux에서 실행되는 컴파일러가 iOS용 코드를 만들고, 실제 실행은 아이폰이 맡는 구조입니다.
Package.swift는 타깃과 의존성을, xtool.yml은 앱 식별자·권한·자원을 표현합니다. GUI에서 클릭한 상태를 기억하는 대신, 설정 변경을 텍스트 차이로 검토할 수 있습니다. 명령줄 중심의 흐름은 반복 작업을 자동화하기에도 알맞지만, 이것만으로 어떤 앱이든 곧바로 App Store에 배포할 수 있다는 보장은 아닙니다.
| 도구 | 주요 역할 | xtool과 비교할 지점 |
|---|---|---|
| Xcode | 편집·미리보기·빌드·디버깅·분석을 통합한 Apple 개발 환경 | xtool은 CLI와 빌드·서명·기기 설치에 집중합니다. Xcode의 모든 기능을 포함하지 않습니다. |
| SwiftPM 단독 | 패키지의 제품·타깃·의존성을 해석하고 빌드 | xtool은 그 위에 앱 번들 구성, 프로비저닝, 설치를 연결합니다. |
| fastlane | 모바일 앱의 빌드·릴리스 작업을 자동화 | 범위가 겹치지만 역할이 완전히 같지는 않습니다. xtool 자체의 macOS 배포도 fastlane을 사용합니다. |
| xtool | SwiftPM 중심으로 iOS 앱을 조립·서명·설치하는 CLI와 XKit 라이브러리 | Linux·WSL·macOS를 연결하는 구현을 학습할 수 있습니다. Apple SDK와 계정 조건은 남습니다. |
비교 기준은 Apple의 Xcode 소개, fastlane 공식 저장소, xtool의 Package.swift와 macOS/fastlane/Fastfile입니다. 서로를 대체하는 경우도 있지만, 한 도구가 다른 도구를 내부에서 활용하는 관계도 있습니다.
단순한 명령 래퍼였다면 공부 범위도 작았을 것입니다. 이 저장소에는 인증 방식 전환, 운영체제별 HTTP 구현, 기기 연결 복구, 권한과 서버 기능의 매핑, AppImage와 macOS 앱 배포가 함께 있습니다. 초보자에게는 “기능이 동작한다” 이후에 필요한 제품화 작업을 한 번에 살펴볼 수 있는 사례입니다.
완제품 전자레인지의 버튼만 배우는 대신, 덮개 안의 전원·제어판·안전장치가 어떻게 이어지는지 보는 경험에 가깝습니다. 모든 회로를 한 번에 이해할 필요는 없습니다. 먼저 버튼 하나를 눌렀을 때 지나가는 경로부터 따라가면 됩니다.
일반적인 웹 서비스의 프론트엔드·백엔드 구분을 그대로 적용하면 혼란스럽습니다. 이 저장소는 로컬 CLI와 라이브러리가 중심입니다. 사용자 접점은 명령줄, 핵심 처리는 PackLib와 XKit, 외부 서비스는 Apple 개발자 API와 인증 보조 서비스입니다. 자체 웹 서버나 데이터베이스를 필수 구성요소로 두지 않습니다.
루트 Package.swift 첫 줄은 // swift-tools-version:6.2이며, 플랫폼 선언은 .iOS(.v17)과 .macOS(.v14)입니다. 공개 제품은 XKit, XToolSupport 라이브러리와 xtool 실행 파일입니다. Linux 지원은 여기에 .linux 최소 버전을 적는 방식이 아니라 조건부 의존성과 플랫폼별 구현으로 드러납니다.
from: "1.5.0"은 1.5.0 이상, 다음 메이저 버전인 2.0.0 미만을 허용합니다. Package.resolved는 의존성 해석에서 선택한 정확한 버전입니다. .upToNextMinor(from: "1.5.1")은 1.5.1 이상, 1.6.0 미만으로 범위를 좁힙니다. 아래 표의 ‘이상’도 from 조건의 하한을 뜻하며, 다음 메이저 버전은 포함하지 않습니다.| 구성 | 실제 선언·버전 | 코드에서 맡는 일 |
|---|---|---|
| Swift Argument Parser | 조건 1.5.0 이상 · 잠금 1.8.2 | Sources/XToolSupport/XTool.swift의 명령 그룹, @Option·@Flag·@Argument 해석 |
| SwiftUI | 운영체제 SDK의 프레임워크 · 별도 SwiftPM 잠금 없음 | macOS/XToolMac/XToolMacUI.swift의 설치 안내와 명령 복사 버튼 |
| SourceKit-LSP / BSP | Sources/XToolSupport/LSPConfig.swift의 bspVersion: "2.2.0" | 편집기에 iOS용 빌드 정보를 전달하도록 .bsp/xtool.json 구성 |
| Yams | 조건 6.0.0 이상 · 잠금 6.2.2 | PackSchema.init(url:)에서 xtool.yml을 Swift 값으로 변환 |
Sources/xtool/XTool.swift의 XToolMain.main()은 18줄짜리 진입 파일에 있습니다. 실제 명령 등록과 취소 처리는 Sources/XToolSupport/XTool.swift로 옮겨져 있습니다. 얇은 실행 파일이 재사용 가능한 라이브러리를 호출하는 구조입니다.
| 의존성 | Package.swift 조건 → 잠금 버전 | 역할 |
|---|---|---|
| xtool-core | 1.5.1부터 다음 마이너 전 → 1.5.1 | OpenSSL, 서명 보조 타입, 직렬화 유틸리티 |
| SwiftyMobileDevice | 1.7.0부터 다음 마이너 전 → 1.7.0 | 기기 탐색, Lockdown 연결, 설치·디버그 서비스 접근 |
| zsign / Zupersign | 1.9.0부터 다음 마이너 전 → 1.9.0 | XKit에서 사용하는 서명 구현의 기반 |
| swift-dependencies | 1.6.2 이상 → 1.17.1 | HTTP·저장소·압축기를 실행 환경에 맞게 주입 |
| swift-concurrency-extras | 1.3.0 이상 → 1.4.3 | LockIsolated로 동기 콜백의 공유 상태 보호 |
| swift-crypto / certificates | 5.0.0 / 1.0.0 이상 → 5.0.3 / 1.21.0 | 암호 연산, 키와 X.509 인증서 처리 |
| BigInt | 6.0.0 이상 → 6.0.1 | 인증 관련 큰 정수 연산 |
| swift-http-types | 1.3.1 이상 → 1.8.0 | 플랫폼에 덜 묶인 HTTP 요청·응답 표현 |
| OpenAPI 생성기 / 런타임 | 1.6.0 / 1.0.0 이상 → 1.14.2 / 1.13.1 | Apple 개발자 API의 타입과 클라이언트 생성·실행 |
| OpenAPI URLSession 전송 | 1.0.0 이상 → 1.4.0 | Apple 플랫폼의 HTTP 전송 어댑터 |
| AsyncHTTPClient / NIO | 1.23.0 / 2.77.0 이상 → 1.36.2 / 2.104.0 | Linux의 비동기 HTTP와 네트워크 기반 |
| OpenAPI AHC / WebSocketKit | 1.0.0 / 2.15.0 이상 → 1.5.0 / 2.16.2 | Linux의 OpenAPI 전송과 WebSocket 통신 |
이 버전은 루트 Package.swift와 Package.resolved 기준입니다. 의존 패키지 내부의 구현 전체가 이 저장소에 포함된다는 뜻은 아닙니다. 예를 들어 zsign의 저수준 서명 코드는 별도 저장소의 책임이며, 여기서는 이를 호출하는 연결부를 읽을 수 있습니다.
| 도구·설정 | 확인한 값 | 읽어야 할 이유 |
|---|---|---|
| swift-subprocess / system | 잠금 1.0.2 / 1.8.1 | SwiftPM 호출, 파일 경로·파일 디스크립터 처리 |
| Xcode 프로젝트 처리 | xcode-project-format 0.1.0 · XcodeProj 9.17.6 | XcodeProjects 계층과 프로젝트 생성 경로 |
| SDK 보조 도구 | unxip 3.3.0 · darwin-tools 1.1.0 · OpenAppleMacros 1.4.1 | XIP 해제, Linux용 도구 모음과 매크로 실행 지원 |
| Docker 빌드 기반 | SWIFT_VERSION=6.4.1 · swift:${SWIFT_VERSION}-jammy | Ubuntu 22.04 기반에서 Linux 배포물을 만드는 다단계 빌드 |
| C 라이브러리 | libplist 2.6.0 · glue 1.3.1 · libusbmuxd 2.1.0 · libtatsu 1.0.4 | Dockerfile에서 기기 통신 기반을 빌드 |
| 배포·문서·검사 | fastlane 2.240.1 · SwiftLint 0.65.1 · DocC 플러그인 1.5.0 | macOS 서명·공증, 스타일 검사, 문서 사이트 생성 |
Sources/XToolSupport/SDKBuilder.swift는 SDK 호환성을 구분하는 값 sdkEpoch = 4도 관리합니다. darwin-tools와 OpenAppleMacros 버전은 이 파일의 상수에, unxip 버전은 Package.resolved에 있습니다. fastlane 버전은 macOS/Gemfile.lock, SwiftLint 버전은 .swiftlint.yml에 있습니다.
도구 버전 하나만 확인해서는 환경을 재현할 수 없고, 패키지·컴파일러·SDK·배포 도구의 버전을 나누어 읽어야 합니다. 특히 Dockerfile의 libimobiledevice는 고정 태그 대신 master를 사용합니다. 잠금 파일이 있다고 전체 공급망이 완전히 고정되는 것은 아닙니다.
먼저 xtool dev run의 경로를 보겠습니다. 굵직한 경계는 빌드 계획 → 앱 조립 → 개발 계정 허가 → 서명 → 기기 설치입니다. 아래 그림의 Apple 서비스와 Anisette 서비스는 로컬 모듈 밖에 있으며, 빌드 결과를 만드는 과정과 인증 네트워크 통신은 별개입니다.
사용자 / 편집기
|
+-- xtool 명령 -------------------------------------+
| |
v v
XToolSupport LSP / BSP 연결
XToolCommand → DevRunCommand 빌드 정보 전달
|
v
PackOperation.run()
|
+-- Package.swift / Package.resolved / xtool.yml
| |
| v
| Planner.createPlan() ----> Plan
| |
| +--------+-------------------+
| v v
| Packer.pack() XcodePacker
| | 프로젝트 생성
| SwiftPM / SwiftBuild
| + Apple SDK + 도구 모음
| |
| v
+------------------ 이름.app
|
v
XKit: IntegratedInstaller.install()
|
+-- AutoSigner.sign()
| |
| +-- DeveloperServicesProvisioningOperation
| | +-- 기기 등록
| | +-- 개발 인증서 확보
| | +-- 앱 ID / 기능 / 프로파일 준비
| | |
| | +----> Apple 개발자 서비스
| +-- 번들 ID 갱신 / 프로파일 삽입 / 코드 서명
|
+-- Payload 압축 ----> .ipa
+-- AppInstaller ----> 업로드 ----> iPhone / iPad 설치
비밀번호 인증 경로: GrandSlam + ADIDataProvider
|
+----> Apple 인증 서비스
+----> Omnisette 인증 보조 서비스
Sources/XToolSupport/DevCommand.swift는 조립 결과 URL을 받은 뒤 AuthToken, ConnectionOptions, IntegratedInstaller를 연결합니다. dev build는 서명 옵션 없이 조립까지만 할 수 있고, --sign을 주면 별도로 signInPlace()가 호출됩니다.
Sources/PackLib/Planner.swift의 createPlan()은 패키지 그래프에서 메인 앱·확장·자원·권한 경로를 찾아 Plan에 담습니다. Packer.pack()은 그 계획을 실제 파일로 바꿉니다. 이렇게 두 단계를 나누면 “무엇을 만들까”를 계산하는 코드와 “어떻게 만들까”를 수행하는 코드를 따로 읽을 수 있습니다.
// Sources/XToolSupport/DevCommand.swift의 핵심 호출 순서 발췌
let planner = Planner(
buildSettings: buildSettings,
schema: schema
)
let plan = try await planner.createPlan()
if xcode {
return try await XcodePacker(plan: plan).createProject()
}
let packer = Packer(
buildSettings: buildSettings,
plan: plan
)
let bundle = try await packer.pack()
같은 Plan을 XcodePacker.createProject()에도 넘깁니다. 이 중간 표현이 있기 때문에 SwiftPM 기반 조립과 Xcode 프로젝트 생성이 같은 앱 정의를 공유할 수 있습니다. 실행하기 전에 계획을 데이터로 만들면, 하나의 앱 정의를 여러 출력 방식에 재사용할 수 있습니다.
Plan은 건물 설계도입니다. 설계도에는 방과 문이 적혀 있지만 시멘트를 붓지는 않습니다. 한 시공사는 SwiftPM으로 앱을 조립하고, 다른 시공사는 Xcode 프로젝트를 만드는 식으로 같은 설계도를 활용합니다.
Planner.buildGraph()는 SwiftPM의 show-dependencies와 describe JSON을 읽습니다. product(...)는 타깃을 순회하며 자원 번들·동적 라이브러리를 수집하고, 호스트에서 동작하는 macro 타깃은 건너뜁니다. 매크로 구현을 아이폰에 넣을 코드와 혼동하지 않도록 하는 구체적인 경계입니다.
Sources/XKit/HTTPClientProtocol/HTTPClientProtocol.swift는 HTTP 전송과 WebSocket에 필요한 공통 계약을 정의합니다. Linux에서는 Sources/XKit/HTTPClientProtocol/AsyncHTTPClient+HTTP.swift, Apple 플랫폼에서는 Sources/XKit/HTTPClientProtocol/URLSession+HTTP.swift가 실제 구현을 제공합니다. 사용하는 쪽은 운영체제별 클래스를 직접 만들 필요가 줄어듭니다.
// Sources/XKit/DeveloperServices/DeveloperServicesClient.swift 발췌
@Dependency(\.anisetteDataProvider) var anisetteDataProvider
@Dependency(\.httpClient) var httpClient
// macOS/XToolMac/XToolMac.swift의 저장소 선택 발췌
try await withDependencies {
$0.keyValueStorage = KeychainStorage(
service: "sh.xtool.keychain.credentials"
)
} operation: {
try await XTool.run()
}
이 패턴은 네트워크뿐 아니라 저장소에도 적용됩니다. 기본 구현은 DirectoryStorage이고, 정식 macOS 앱의 CLI 경로는 위처럼 KeychainStorage를 주입합니다. 테스트에서는 MemoryKeyValueStorage로 바꿀 수 있습니다. “필요한 능력”과 “지금 사용할 구현”을 분리한 셈입니다.
Sources/PackLib/ToolRegistry.swift의 Cache는 actor입니다. 캐시에는 최종 URL 대신 Task<URL, Error>를 저장합니다. 같은 실행 파일 경로를 여러 곳에서 동시에 요청해도, 이미 시작한 탐색 작업을 같이 기다릴 수 있습니다.
// ToolRegistry.Cache.locate(tool:)의 분기 발췌
let task: Task<URL, Error>
if let cached = cache[tool] {
task = cached
} else {
task = Task { try await _locate(tool: tool) }
cache[tool] = task
}
return try await task.value
중요한 순서는 작업을 저장한 다음 기다린다는 것입니다. await 중에는 다른 요청이 actor에 들어올 수 있지만 이미 캐시가 채워져 있습니다. actor가 있다고 비동기 메서드 전체가 하나의 트랜잭션이 되는 것은 아닙니다. 이 구현은 실패한 Task도 남으므로 같은 프로세스 안에서 재요청할 때 재탐색하지 않는다는 점도 읽을 수 있습니다.
IntegratedInstaller와 AppInstaller 역시 actor입니다. 다만 진행률 콜백을 정리하는 queueUpdateTask()는 LockIsolated로 이전 Task를 잡고 순서를 연결합니다. actor라는 이름 하나로 모든 순서 문제가 해결되는 것이 아니라, 콜백이 들어오는 경계도 따로 관리합니다.
앱의 권한 파일에 키를 적는 것과 Apple 개발자 계정에서 기능을 켜는 것은 서로 다른 일입니다. AppleSignInEntitlement는 com.apple.developer.applesignin을 [String]으로 해석합니다. 이어 Sources/XKit/DeveloperServices/App IDs/Entitlements/DeveloperServicesCapability.swift가 이를 서버 기능 .appleIdAuth와 주 앱 동의 설정으로 변환합니다.
이 연결은 최근 커밋 952a7ff의 “Sign in with Apple 지원”을 이해하는 실마리입니다. 변경의 의미는 로그인 화면 전체를 구현했다는 것이 아니라, 서명·프로비저닝 과정에서 해당 entitlement와 capability를 다루도록 연결했다는 데 있습니다.
| 단계 | 파일·함수 | 하는 일 |
|---|---|---|
| 설정 읽기 | PackSchema.init(url:) | YAML의 entitlementsPath를 보관 |
| 권한 해석 | Sources/XKit/Model/Entitlements/EntitlementTypes.swift | AppleSignInEntitlement를 지원 타입 목록에 등록 |
| 서버 기능 변환 | Sources/XKit/DeveloperServices/App IDs/Entitlements/DeveloperServicesCapability.swift | .appleIdAuth, .primaryAppConsent, isFree: false 지정 |
| 계정에 반영 | DeveloperServicesAddAppOperation.upsertApp(...) | 앱 ID와 기존 capability를 비교해 생성·수정 |
| 서명 반영 | AutoSigner.sign(...) | 프로파일 삽입 후 인증서·개인키·권한 매핑으로 서명 |
권한 해석과 개발자 서비스 연동 코드는 모두 Sources/XKit/ 아래에 있습니다. 무료 팀에서는 addApp(...)이 유료 capability를 요구하는 인식된 권한을 필터링합니다. 따라서 “코드가 키를 이해한다”와 “내 계정으로 그 기능을 쓸 수 있다”는 별개의 조건입니다.
| 실패 지점 | 먼저 읽을 코드 | 확인할 경계 |
|---|---|---|
| 제품 선택 실패 | Planner.selectLibrary(...) | 자동 라이브러리 제품이 여러 개면 product 설정이 있는가 |
| SDK·컴파일 실패 | BuildSettings.swiftPMInvocation(...) | 대상 triple, SDK 경로, Swift 버전이 맞는가 |
| 권한·프로파일 실패 | DeveloperServicesProvisioningOperation.perform() | 기기·인증서·앱 ID 단계 중 어디서 멈췄는가 |
| 페어링 대기 | IntegratedInstaller.performWithRecovery(...) | 기기 잠금 해제와 신뢰 확인을 기다리는가 |
| 기기 설치 실패 | AppInstaller.install(progress:) | 연결·업로드·설치 검증 중 어느 단계인가 |
performWithRecovery()는 신뢰 확인 대기와 기기 암호 잠금 오류를 잡아 메시지를 바꾸고, 기본 0.1초 간격으로 다시 시도합니다. 나머지 오류는 호출부로 전달합니다. 복구 가능한 오류만 구분하는 방식도 배울 만합니다.
xtool/
+-- Package.swift 패키지·제품·타깃·의존성 선언
+-- Package.resolved 의존성의 정확한 선택 버전
+-- Sources/
| +-- xtool/ CLI 진입점
| +-- XToolSupport/ 명령, 인증 설정, SDK 생성, 편집기 연결
| +-- PackLib/ 설정 해석, 계획, 빌드, 앱 조립
| +-- XKit/
| | +-- DeveloperServices/ 기기·앱 ID·인증서·프로파일 관리
| | +-- GrandSlam/ 비밀번호 인증과 Anisette 보조 처리
| | +-- HTTPClientProtocol/ 플랫폼별 네트워크 어댑터
| | +-- Model/ 인증서·키·권한 데이터
| | +-- Signer/ 프로비저닝과 코드 서명 연결
| | +-- Installation/ 기기 연결·전송·설치·디버그 서비스
| | +-- Integration/ 설치 전 과정의 조정자
| | +-- Utilities/ 저장소·압축·식별자 등 공통 기능
| +-- XcodeProjects/ Xcode 프로젝트 형식 변환·입출력
| +-- DeveloperAPI/Generated/ OpenAPI로 생성한 Swift 코드
| +-- CXKit/ OpenSSL 등을 잇는 C 코드와 헤더
| +-- XUtils/ 프로세스·파일·임시 디렉토리 유틸리티
+-- Tests/XToolTests/ Swift Testing 기반 테스트와 보조 코드
+-- Examples/ 단순 앱·자원·동적 라이브러리·확장 사례
+-- integration-tests/ Docker + Bats 통합 테스트
+-- Documentation/xtool.docc/ 설치·첫 앱·앱 구조 문서
+-- Linux/ AppImage 빌드·패키징
+-- macOS/ 안내 앱·XcodeGen 설정·fastlane 배포
+-- .github/workflows/ 빌드·검사·통합 테스트·릴리스
+-- Dockerfile Linux 빌드 단계와 실행 이미지
+-- Makefile 개발·패키징·문서·API 생성 작업
커밋 952a7ff의 파일을 확장자별로 세면 아래와 같습니다. 빈 줄과 주석을 포함한 물리적 줄 수이며, 외부 의존성 다운로드와 .git 내부는 제외합니다. 이 숫자는 구현 난이도나 기능 수를 직접 나타내지는 않습니다.
| 언어·영역 | 파일 수 / 줄 수 | 해석 |
|---|---|---|
| Swift 전체 | 156개 / 42,362줄 | 매니페스트·예제·테스트·문서 속 Swift 파일 포함 |
| 생성된 Swift | 2개 / 28,873줄 | Sources/DeveloperAPI/Generated/의 타입과 클라이언트 |
| 나머지 Swift | 154개 / 13,489줄 | 생성된 두 파일을 제외한 학습 대상의 대략적 크기 |
| C / 헤더 | 2개 77줄 / 3개 40줄 | CXKit의 연결 코드 |
| 셸 | 20개 / 519줄 | 빌드·문서·테스트·튜토리얼용 .sh 파일 |
| JavaScript | 1개 / 109줄 | OpenAPI 입력을 다듬는 Sources/DeveloperAPI/patch.js |
| YAML | 14개 / 359줄 | .yml·.yaml 합계, 언어 소스와 별도 집계 |
Swift 전체의 약 68%가 생성 코드입니다. 처음부터 가장 큰 파일을 펼치면 복잡함을 과대평가하기 쉽습니다. Makefile의 api 타깃과 Sources/DeveloperAPI/openapi-generator-config.yaml을 보면 BundleIds·BundleIdCapabilities·Certificates·Devices·Profiles 태그의 API만 골라 타입과 클라이언트를 생성한다는 사실을 알 수 있습니다.
Sources/xtool/XTool.swift → Sources/XToolSupport/XTool.swift: 진입점과 명령 등록을 읽습니다.Sources/XToolSupport/DevCommand.swift: 사용자가 내린 명령을 어떤 서비스에 연결하는지 봅니다.Sources/PackLib/PackSchema.swift → Sources/PackLib/Planner.swift → Sources/PackLib/Packer.swift: 입력·계획·결과물을 순서대로 봅니다.Sources/XKit/Integration/IntegratedInstaller.swift → Sources/XKit/Signer/AutoSigner.swift: 앱이 설치 허가를 얻는 과정을 읽습니다.Sources/XKit/Model/Entitlements/에서 Sources/XKit/DeveloperServices/까지 따라갑니다.XKit은 7,057줄, XToolSupport는 3,066줄, PackLib은 1,576줄입니다. 먼저 함수 사이를 잇는 지도를 만들고, 각 경계에서 주고받는 타입을 확인하세요. 생성 API나 암호 프로토콜의 상세는 필요해질 때 들어가도 됩니다.
타깃은 함께 컴파일할 소스의 단위이고, 제품은 패키지 밖에 공개할 결과물입니다. Examples/DemoSimple/Package.swift는 앱 소스를 .library 제품으로 노출합니다. 그런데 최종 결과는 실행 가능한 앱입니다. 이 차이를 연결하는 곳이 Packer.build()입니다.
이 함수는 xtool/.xtool-tmp/Package.swift를 만들어 원래 패키지를 RootPackage라는 로컬 의존성으로 참조합니다. 그리고 이름-App 실행 타깃, 빈 stub.c, 앱 제품에 대한 의존성을 생성합니다. 사용자가 선언한 라이브러리를 앱 실행 파일로 묶기 위한 임시 빌드 패키지입니다.
// Packer.build()가 생성하는 형태를 Hello라는 이름으로 풀어 쓴 예
dependencies: [
.package(name: "RootPackage", path: "../.."),
],
targets: [
.executableTarget(
name: "Hello-App",
dependencies: [
.product(name: "Hello", package: "RootPackage"),
],
linkerSettings: [
.unsafeFlags([
"-Xlinker", "-rpath", "-Xlinker", "@executable_path/Frameworks",
]),
]
),
]
작은 실습: Sources/XToolSupport/NewCommand.swift의 템플릿에서 제품 이름과 타깃 이름을 찾고, Plan.Product.targetName이 어떤 이름을 추가하는지 종이에 적어 보세요. 제품 이름을 바꾸면 앱 이름·실행 파일 이름 중 무엇이 연결되는지 예상하는 연습입니다.
arm64-apple-ios는 CPU·공급자·운영체제를 표현합니다. 이름이 triple이어도 arm64-apple-ios-simulator처럼 환경 정보가 더 붙을 수 있습니다.BuildSettings는 구형 경로에서 --swift-sdk, SwiftBuild 경로에서 --triple을 사용합니다. SwiftVersion.supportsSwiftBuild는 버전이 6.4 이상인지 검사하고, BuildSystem.default()가 그에 따라 기본값을 선택합니다. 새로운 빌드 엔진은 기존 SwiftPM 명령을 통해 선택됩니다.
macOS에서는 xcrun으로 실제 Swift 실행 파일을 찾고, 잘못 상속된 SDKROOT를 제거합니다. Linux의 SwiftBuild 경로에서는 Darwin SDK의 toolset-swb.json과 플랫폼 폴더 환경 변수를 추가합니다. 같은 명령처럼 보여도 운영체제별 준비물이 다른 이유가 여기 있습니다.
작은 실습: 기본 기기 triple과 시뮬레이터 triple을 Packer.cfBundleSupportedPlatforms(for:)에 대입해 결과를 예측하세요. 정답은 Tests/XToolTests/PackerTests.swift에서 비교할 수 있습니다. 시뮬레이터용 바이너리를 만든다는 것과 시뮬레이터를 해당 운영체제에서 실행한다는 것은 다른 문제입니다.
Planner.buildGraph()는 여러 패키지 설명을 withThrowingTaskGroup으로 읽고, Packer.pack()은 자원·실행 파일·설정 파일 복사를 작업 그룹으로 묶습니다. 독립 작업은 겹쳐 진행할 수 있지만, 그룹 범위를 떠나기 전에 자식 작업의 종료를 기다립니다.
Packer.pack()은 취소 오류와 일반 오류를 구분하고, 일반 오류가 나면 group.cancelAll()을 호출합니다. 실제 파일 복사 뒤에는 Task.checkCancellation()도 있습니다. 비동기 코드를 읽을 때는 무엇이 동시에 진행되는지와 함께, 실패했을 때 누가 끝까지 정리하는지도 확인해야 합니다.
작은 실습: Packer.pack()에서 동시에 실행 가능한 작업과 반드시 앞서 끝나야 하는 작업을 나누어 보세요. 예를 들어 출력 디렉토리는 자원 복사보다 먼저 존재해야 합니다. TemporaryDirectory의 소유권과 persist(at:)까지 연결하면 성공·실패 시 결과물의 수명도 설명할 수 있습니다.
| 재료 | 쉬운 의미 | 소스에서의 위치 |
|---|---|---|
| 인증서·개인키 | 누구의 서명인지 확인할 신원 정보와 서명에 쓰는 비밀 | Sources/XKit/Model/Certificate.swift, Sources/XKit/Model/Keypair.swift |
| 프로비저닝 프로파일 | 개발용 앱·기기·인증서·허용 권한을 연결하는 허가 정보 | Sources/XKit/Model/Mobileprovision.swift, embedded.mobileprovision |
| entitlement | 앱이 요구하는 특수 기능의 권한 키 | Sources/XKit/Model/Entitlements/ |
| capability | 개발자 서비스에서 앱 ID에 설정하는 기능 | Sources/XKit/DeveloperServices/App IDs/Entitlements/ |
| 코드 서명 | 앱과 권한에 서명을 붙여 이후 검증할 수 있게 하는 절차 | Sources/XKit/Signer/AutoSigner.swift, Sources/XKit/Signer/SignerImpl.swift |
위 파일은 모두 Sources/XKit/ 아래에 있습니다. AutoSigner.sign(...)은 서버에서 준비한 새 bundle ID를 Info.plist에 반영하고, 프로파일을 삽입한 뒤 서명 구현을 호출합니다. 먼저 컴파일만 하고 나중에 이 단계를 붙일 수 있도록 역할이 분리되어 있습니다.
작은 실습: Signer.Identity의 .adhoc과 .real을 비교해 보세요. PackOperation에서 권한을 담기 위해 사용하는 ad-hoc 서명과 개발 인증서 기반 서명을 구분하면, “서명 비슷한 단계가 있었는데 왜 기기 설치가 안 되는가”를 설명할 수 있습니다.
PackSchemaBase는 YAML의 구조를 표현하고, PackSchema.init(validating:)는 앱 식별자 지정 여부와 아이콘 확장자 등을 검사합니다. bundleID와 orgID가 모두 있으면 전자가 우선합니다. 아이콘 경로는 확장자가 정확히 png인지 확인하지만, 이 검사만으로 실제 이미지 크기까지 검증하는 것은 아닙니다.
Entitlements.updateEntitlements(...)는 인식된 권한을 수정하면서 미해석 키를 보존하도록 설계되어 있습니다. 다만 무료 팀용 재구성 경로는 인식된 권한 목록으로 다시 만들므로 같은 보존 규칙이 적용되지 않습니다. 추상 타입의 일반 동작과 특정 호출부의 정책을 함께 읽어야 하는 사례입니다.
작은 실습: bundleID만 있는 설정, orgID만 있는 설정, 둘 다 없는 설정을 보고 어느 입력이 어떤 결과가 될지 예측하세요. 마지막 입력은 Must specify either orgID or bundleID 오류로 이어집니다.
// Tests/XToolTests/PackerTests.swift의 검증 예 발췌
@Test func cfBundleSupportedPlatformsFollowsBuildTriple() {
#expect(Packer.cfBundleSupportedPlatforms(
for: "arm64-apple-ios"
) == ["iPhoneOS"])
#expect(Packer.cfBundleSupportedPlatforms(
for: "arm64-apple-ios-simulator"
) == ["iPhoneSimulator"])
}
Tests/XToolTests/SwiftVersionTests.swift는 다양한 버전 문자열과 잘못된 입력을 @Test(arguments:)로 검증합니다. Tests/XToolTests/RequiresXcodeTrait.swift는 Xcode 경로를 요구하는 테스트의 환경을 별도로 준비합니다. integration-tests/suite/smoke.bats는 새 앱의 빌드 결과에 iOS용 Mach-O 정보가 있는지도 검사합니다. 단위 테스트와 통합 테스트가 서로 다른 실패를 잡는 구체적인 예입니다.
소스와 설정을 읽는 학습에는 Apple 계정이나 아이폰이 필요하지 않습니다. 앱을 빌드하려면 Swift와 Apple SDK가, 실제 기기 설치에는 인증과 연결이 추가로 필요합니다. 저장소는 공식 최소 RAM·CPU 코어 수·디스크 용량을 제시하지 않습니다.
| 항목 | 소스·문서에서 확인한 조건 | 초보자가 확인할 점 |
|---|---|---|
| Linux 호스트 | 공식 설치 문서는 Swift 6.4와 usbmuxd를 안내 | AppImage 아키텍처가 호스트와 맞는지 확인 |
| Windows 호스트 | WSL 사용 및 USB 전달 설정 | Windows 네이티브 지원과 구분, WSL 안에서 기기 접근 확인 |
| Linux CPU | SDKBuilder와 릴리스 행렬에 x86_64·aarch64 | 아이폰의 arm64와 개발 컴퓨터의 CPU는 다를 수 있음 |
| macOS 호스트 | 패키지·안내 앱은 macOS 14 이상 선언, Xcode 설치 필요 | 운영체제 하한과 사용하는 Xcode의 호환 조건을 함께 확인 |
| Apple SDK | Linux 설치 문서는 Xcode 27.1 XIP 안내, CI는 27.1.0 선택 | 공식 다운로드의 계정 인증·이용 조건을 확인하고 SDK 구성 |
| iOS 대상 | 도구 패키지·새 프로젝트 템플릿은 iOS 17 선언 | 앱의 실제 하한은 해당 프로젝트 플랫폼 설정도 확인 |
| 실제 기기 | iPhone 또는 iPad, 페어링·신뢰 확인, 필요 시 개발자 모드 | USB 연결부터 확인하고 잠금 해제 안내에 응답 |
| 인증 | 유료 팀의 App Store Connect API 키 또는 비밀번호·2FA 경로 | 계정 유형에 따라 사용할 capability가 달라짐 |
| 네트워크 | 패키지·SDK 보조 도구 다운로드 및 인증·프로비저닝 통신 | 첫 설정과 기기 서명까지 완전 오프라인인 도구는 아님 |
Package.swift의 도구 버전 6.2는 xtool 자체를 소스에서 빌드할 때 적용되는 매니페스트 조건입니다. 새 앱 템플릿의 도구 버전은 6.0입니다. Linux 설치 문서는 Swift 6.4를 요구하고, Docker는 6.4.1을 선택합니다. Sources/PackLib/BuildSettings.swift에도 Linux 플랫폼 도구 모음 수정과 빌드 서버 옵션 때문에 6.4가 필요하다는 설명이 있습니다.
패키지가 선언한 버전, 호스트 도구에 필요한 버전, 앱이 실행될 운영체제 버전은 서로 다른 조건입니다. macOS 설치 문서의 Swift 6.1 출력은 설치 확인용 예시입니다. xtool 자체의 소스 빌드 조건과, 배포된 xtool로 앱을 빌드할 때의 조건은 나누어 읽어야 합니다.
Planner는 앱 패키지의 iOS 버전을 사용하고, 없으면 내부 기본값 13.0을 사용합니다. 이 대체값은 모든 앱이 iOS 13에서 동작한다는 보증이 아닙니다. 새 프로젝트는 iOS 17을 선언하며, 앱이 쓰는 API와 의존성도 하한에 영향을 줍니다.
실습 준비용 예산으로는 RAM 16GB와 여유 디스크 수십 GB를 잡으면 출발하기 편합니다. 이는 공식 최소 사양이나 성능 측정값이 아닌 학습용 권장 여유입니다. SDK 압축 해제, Swift 의존성, 빌드 캐시가 차지하는 용량은 사용하는 Xcode와 프로젝트 크기에 따라 달라집니다. GPU는 이 CLI의 필수 요구사항으로 선언되어 있지 않습니다.
SDKBuilder.Mode의 buildSlim은 필요한 파일 중심의 SDK를 만들고, 일반 모드는 Xcode 개발 디렉토리의 복사본을 유지해 갱신 경로를 지원합니다. DarwinSDK.Flavor.slim은 제자리 갱신을 지원하지 않습니다. 공간 절약과 업데이트 방식 사이의 선택입니다.
docker-compose.yml은 로컬 소스와 빌드 캐시를 마운트하고, Dockerfile의 dev 단계는 호스트의 usbmuxd 연결을 사용하는 구성을 제시합니다. 컨테이너에서도 USB 연결은 별도로 준비해야 합니다. 개발·빌드 단계의 기반은 Ubuntu 22.04(jammy)이고 Linux 빌드 CI의 실행 호스트는 Ubuntu 24.04입니다. 최종 배포 단계는 swift:6.4.1 이미지를 사용하며 배포판 접미사를 명시하지 않습니다.
DevRunCommand의 --simulator와 SimInstallOperation은 #if os(macOS) 안에 있습니다. 구현은 xcrun simctl install을 사용합니다. Linux에서 시뮬레이터용 대상을 빌드하는 능력과 Apple 시뮬레이터를 Linux에서 실행하는 능력을 같은 것으로 해석하지 마세요.
처음 두 과제는 소스 열람만으로 완수할 수 있습니다. 이후 과제는 공식 개발 환경을 준비한 독자가 별도 연습용 프로젝트에서 수행하는 제안입니다. 아래 명령은 실습용 예시입니다. SDK와 기기·계정 조건은 각 과제에 맞게 준비하세요.
입력: Sources/xtool/XTool.swift, Sources/XToolSupport/XTool.swift, Sources/XToolSupport/DevCommand.swift. 세 파일은 모두 Sources/ 아래에 있습니다.
할 일: xtool dev build가 어떤 명령 타입으로 파싱되고, PackOperation.run()을 거쳐 어느 함수에서 파일이 만들어지는지 화살표 6~8개로 표시하세요. 기본 triple과 결과 디렉토리도 적습니다.
완료 조건: --sign이 없을 때 건너뛰는 인증서 기반 서명 단계, --ipa를 붙였을 때 추가되는 압축 단계를 지도에서 구분할 수 있어야 합니다.
배우는 것: CLI 파싱, 함수 탐색, 제어 흐름. 코드를 실행할 준비가 없어도 시작할 수 있는 과제입니다.
입력: PackSchema.init(validating:), IDSpecifier.formBundleID(product:), Planner.selectLibrary(...).
할 일: 아래 네 설정의 성공 여부와 예상 bundle ID를 적습니다. 제품 이름은 Hello라고 가정하세요. 이어 제품이 두 개이고 product를 생략한 경우도 판단합니다.
# 서로 독립적인 네 설정 사례
# A: version: 1, bundleID: com.study.Exact
# B: version: 1, orgID: com.study
# C: version: 1, bundleID와 orgID 모두 없음
# D: version: 1, orgID: com.study, iconPath: Icon.jpg
완료 조건: A는 com.study.Exact, B는 com.study.Hello, C는 식별자 누락, D는 확장자 검증 실패라고 근거 함수와 함께 설명합니다. 자동 라이브러리 제품이 여러 개면 명시적 선택도 필요합니다.
배우는 것: 설정 스키마, 기본값과 검증의 차이, 오류 메시지를 이용한 코드 이해.
준비: xtool과 SDK를 설정한 개발 환경. 계정 기반 서명 단계 전까지인 dev build를 중심으로 진행합니다.
할 일: 새 Hello 프로젝트에서 Sources/Hello/message.txt를 SwiftPM의 resources: [.process("message.txt")]로 등록합니다. 별도의 Resources/top.txt는 YAML의 최상위 자원으로 등록하세요.
# 연습용 프로젝트의 xtool.yml
version: 1
bundleID: com.study.Hello
resources:
- Resources/top.txt
# 환경을 갖춘 뒤 실행할 명령
xtool dev build
find xtool/Hello.app -maxdepth 3 -type f
관찰: SwiftPM 자원은 별도 .bundle로, YAML의 자원은 앱 루트의 top.txt로 모이는지 비교합니다. 파일명에 접두 경로가 그대로 남는지, 마지막 파일명만 쓰는지도 확인하세요.
완료 조건: 앱 디렉토리 트리와 Planner.product(...) → Plan.Resource → Packer.pack(...)의 분기를 나란히 제시합니다. 참고 구현은 Examples/DemoResources입니다.
준비: 테스트를 작성할 별도 연습용 사본과 Swift 개발 환경. Apple 서비스에 요청하지 않는 단위 검증으로 범위를 잡습니다.
할 일: Tests/XToolTests/WiFiAwareEntitlementTests.swift를 읽고, 같은 구조로 AppleSignInEntitlement의 plist 왕복 변환을 검사하는 테스트를 설계하세요. 테스트 타깃이 @testable import XKit으로 내부 capability에 접근하는 이유도 설명합니다.
# 테스트 입력 plist의 dict 안에 들어갈 조각
<key>com.apple.developer.applesignin</key>
<array>
<string>Default</string>
</array>
완료 조건: 배열 값 보존, .appleIdAuth 매핑, isFree == false, 주 앱 동의 설정을 각각 검증 항목으로 적습니다. 성공한 단위 변환과 실제 서비스 사용 자격은 다른 검증임을 명시하세요.
배우는 것: Codable, 타입 등록, 권한 매핑, 네트워크 없는 회귀 테스트.
입력: Plan과 Planner.createPlan(), 현재 SwiftPM 호출 경계를 함께 읽습니다. 실제 빌드 없이 계획을 보고 싶다는 요구를 가정합니다.
할 일: 출력 항목을 제품 이름·bundle ID·배포 대상·자원·확장으로 정하고, JSON 직렬화용 별도 타입을 설계하세요. 패키지 그래프 입력도 주입 가능하게 분리하면 어떤 호출을 테스트용 데이터로 대체할 수 있을지 적습니다.
주의할 경계: 현재 Planner도 SwiftPM 서브프로세스를 호출합니다. PackOperation.run()에는 SDK 준비와 편집기 설정 갱신이 있습니다. 마지막 빌드 호출만 생략한다고 파일·네트워크 효과가 전부 사라지는 것은 아닙니다.
완료 조건: 정상 앱, 여러 제품의 선택 오류, 확장 포함 앱의 세 입력에 대한 예상 JSON과 오류를 제시하고, 외부 효과를 어디서 분리할지 함수 경계로 설명합니다.
배우는 것: 중간 표현, 테스트 가능한 경계, 기능을 추가하기 전에 부수 효과를 찾는 방법. 현재 제공 기능이 아닌 확장 설계 과제입니다.
실습의 성공 기준은 명령이 끝났다는 사실보다, 결과 파일과 그 파일을 만든 코드의 분기를 연결해 설명하는 데 있습니다. 기기 설치를 추가로 시도할 때도 앱 조립, 서명, 연결, 설치를 따로 기록하면 문제를 좁히기 쉽습니다.
매주 새로운 도구를 설치하기보다, 같은 앱이 지나가는 경계를 하나씩 넓혀 보세요. Swift 문법을 처음 접했다면 1주차를 두 주로 늘려도 됩니다. 기기 설치를 하지 않는 독자는 마지막까지 입력과 예상 결과를 표로 만드는 방식으로 따라갈 수 있습니다.
| 주차 | 배울 개념과 소스 | 남길 결과물 |
|---|---|---|
| 1주차 | Swift 기본 타입·옵셔널·오류·접근 제어. PackSchemaBase, IDSpecifier, SwiftVersion를 읽습니다. | 설정 4개의 성공·실패 표와 guard가 필요한 이유 설명 |
| 2주차 | SwiftPM 제품·타깃·그래프. 예제 매니페스트와 Planner의 의존성 순회를 연결합니다. | 하나의 제품에서 자원·동적 라이브러리까지 이어지는 그래프 |
| 3주차 | 크로스컴파일·링커·번들. BuildSettings, Packer, SDKBuilder의 입력·출력을 구분합니다. | 호스트·타깃·SDK 표, 예상 앱 디렉토리와 Info.plist |
| 4주차 | async/await·actor·취소·자원 수명. ToolRegistry.Cache, 작업 그룹, TemporaryDirectory를 읽습니다. | 동시 요청의 시간 순서 그림과 실패 시 정리 책임 표 |
| 5주차 | 인증·프로비저닝·코드 서명. AuthToken, AutoSigner, Apple 로그인 권한 매핑을 따라갑니다. | 인증서·프로파일·권한의 차이 설명과 권한 왕복 테스트 설계 |
| 6주차 | 테스트·CI·배포 재현성. Swift Testing, Bats, Docker 단계, 릴리스 워크플로를 연결합니다. | 플랫폼별 검증 행렬과 고정되지 않은 외부 입력 목록 |
var, 동시성 경계와 연결됩니다..app, .ipa 중 어느 단계인지 정합니다.마지막에는 Plan을 다른 출력으로 바꿔 보는 설계가 좋습니다. 사람이 읽는 미리보기 표, JSON 보고서, 다른 빌드 경로 중 하나를 고르고 어떤 데이터를 더 담아야 할지 적어 보세요. 현재 구조가 무엇을 쉽게 만들고, 어떤 부분은 아직 직접 호출에 묶여 있는지 드러납니다.
학습의 마지막 결과물은 기능 이름의 목록보다, 새로운 요구를 어느 경계에 넣을지 설명할 수 있는 설계도여야 합니다. 그 설명에 함수 이름과 실패 조건까지 붙으면 다른 개발 도구의 소스를 읽을 때도 같은 방법을 사용할 수 있습니다.
| 용어 | 쉬운 설명 |
|---|---|
| CLI | 창의 버튼 대신 터미널 명령으로 기능을 사용하는 방식입니다. xtool의 주 사용자 접점입니다. |
| SwiftPM | Swift 패키지 관리자입니다. 의존성을 해석하고 타깃과 제품을 빌드합니다. |
| Package.swift | 패키지의 제품·타깃·플랫폼·의존성을 선언하는 Swift 매니페스트입니다. |
| Package.resolved | 의존성 해석에서 선택한 정확한 버전과 리비전을 기록하는 잠금 파일입니다. |
| 타깃 / 제품 | 타깃은 소스를 컴파일하는 단위, 제품은 외부에 공개할 결과물입니다. 같은 이름을 써도 역할은 다릅니다. |
| 의존성 그래프 | 어떤 패키지나 타깃이 무엇을 필요로 하는지 화살표로 연결한 지도입니다. |
| 선언형 설정 | 절차를 하나씩 지시하는 대신 원하는 결과의 속성을 적는 방식입니다. xtool.yml의 앱 ID·자원이 예입니다. |
| 스키마 | 입력 데이터가 어떤 필드와 타입을 가져야 하는지 정하는 규칙입니다. 단순 형식과 의미 검증을 구분할 수 있습니다. |
| Plan / 중간 표현 | 입력 설정을 해석한 뒤 실행 전에 저장하는 작업 설명입니다. 다른 출력 방식에서도 재사용할 수 있습니다. |
| 호스트 / 타깃 | 호스트는 도구가 도는 컴퓨터, 타깃은 결과물이 실행될 환경입니다. |
| 크로스컴파일 | 지금 개발하는 환경과 다른 환경에서 실행될 프로그램을 만드는 일입니다. |
| target triple | arm64-apple-ios처럼 CPU·공급자·운영체제와 추가 환경을 지정하는 문자열입니다. |
| SDK / toolset | SDK는 대상 플랫폼의 API·라이브러리 묶음, toolset은 컴파일러·링커 같은 도구와 옵션의 구성입니다. |
| SwiftBuild | SwiftPM에서 선택할 수 있는 빌드 엔진 경로입니다. 이 커밋은 Swift 6.4 이상이면 기본 선택합니다. |
| 링커 / rpath | 링커는 컴파일 결과와 라이브러리를 연결합니다. rpath는 실행 중 동적 라이브러리를 찾을 경로를 지정합니다. |
| 앱 번들 / .app | 실행 파일·설정·자원을 정해진 구조로 담은 디렉토리입니다. |
| .ipa | iOS 앱 전달에 쓰는 압축 패키지입니다. xtool은 Payload 아래의 앱을 압축합니다. |
| Info.plist / bundle ID | 앱의 이름·실행 파일·플랫폼 등 메타데이터와, 앱을 구분하는 식별자입니다. |
| Mach-O | Apple 플랫폼의 실행 파일·라이브러리 형식입니다. 통합 테스트가 대상 플랫폼 정보를 검사합니다. |
| 프로비저닝 | 앱·개발자·기기·권한을 연결해 설치에 필요한 허가 정보를 준비하는 과정입니다. |
| 인증서 / 개인키 | 인증서는 공개키의 신원을 연결하고, 개인키는 실제 서명에 쓰는 비밀 데이터입니다. |
| entitlement | 앱이 요구하는 특수 기능의 권한 키입니다. 파일에 쓰는 것만으로 사용 자격이 생기지는 않습니다. |
| capability | 개발자 서비스가 앱 ID에 설정하는 기능입니다. 일부 entitlement와 대응합니다. |
| ad-hoc 서명 | 이 코드의 .adhoc는 인증서 없이 서명 데이터를 구성하는 방식입니다. 배포 방식인 Ad Hoc 프로비저닝과 구별해야 합니다. |
| usbmuxd / Lockdown | 컴퓨터와 iOS 기기 사이의 통신 연결과 페어링·기기 서비스 접근을 다루는 기반입니다. |
| GrandSlam / Anisette | Apple ID 인증 경로와 인증에 필요한 보조 데이터를 다루는 구현 영역입니다. 일반 앱용 OAuth 로그인 기능과는 다릅니다. |
| OpenAPI | HTTP API의 경로·입력·응답을 기술하는 형식입니다. 여기서는 그 정의로 Swift 타입과 클라이언트를 생성합니다. |
| 의존성 주입 | HTTP·저장소 같은 기능의 실제 구현을 실행 환경이나 테스트에서 교체하는 방식입니다. |
| actor / Sendable | actor는 공유 상태 접근을 격리하고, Sendable은 동시성 경계를 넘길 수 있는 타입의 약속입니다. |
| 작업 그룹 / 취소 | 관련 비동기 작업을 한 범위에 묶고 종료를 기다리는 구조와, 작업이 협조적으로 중단 요청에 응답하는 방식입니다. |
| ~Copyable / consuming | 값의 복사를 막고 소유권을 소모하는 Swift 표기입니다. 임시 디렉토리의 중복 정리·재사용을 제한하는 데 쓰입니다. |
| LSP / BSP | 편집기와 언어 분석기, 편집기와 빌드 시스템 사이에 정보를 교환하는 프로토콜입니다. |
| AppImage / 공증 | AppImage는 Linux 앱 묶음 형식입니다. macOS 공증은 Apple의 별도 배포 검사 절차이며 같은 기술은 아닙니다. |
| 단위 / 통합 테스트 | 작은 규칙을 격리해 검사하는 테스트와 여러 도구·환경을 연결해 검사하는 테스트입니다. |
로그인은 “민원 창구에 들어갈 사람 확인”, 프로비저닝은 “이 앱·기기에 필요한 허가 발급”, 서명은 “이 묶음을 누가 내보냈는지 검증할 봉인”에 가깝습니다. 어느 하나가 성공했다고 나머지도 자동으로 끝나는 것은 아닙니다.
| 항목 | 확인한 상태 |
|---|---|
| 저장소 / 선정 | xtool-org/xtool · GitHub Trending Swift Weekly #1 |
| 스타 / 포크 | 13,421 / 715 · 2026-10-23 GitHub 기준 |
| 주 언어 / 생성일 | Swift / 2024-11-17 |
| 최신 릴리스 | 1.23.0 · 2026-10-22 |
| 최근 push | 2026-10-23 |
| 분석 커밋 | 952a7ff · 2026-10-23 (한국 시간) · entitlements: support Sign in with Apple (#377) |
| 코드 규모 | Swift 42,362줄, 생성 코드 제외 13,489줄 · C·헤더 117줄 · 셸 519줄 · JavaScript 109줄 |
| 라이선스 | MIT · 루트 LICENSE.md 기준 |
| 소스의 도구 설정 | Swift 도구 버전 6.2 · Linux 문서 6.4 · Docker 6.4.1 |
| 빌드 CI의 범위 | Linux x86_64·aarch64 빌드·테스트, macOS 빌드·테스트, iOS용 XKit 빌드가 정의됨 |
| 통합 검증의 범위 | Bats의 새 앱 빌드·Mach-O 검사와 slim SDK 갱신 오류 검사가 정의됨 |
| 설치와 실행 | dev run은 빌드·서명·설치까지 수행하며, 설치한 앱의 명시적 실행은 launch 명령으로 처리 |
릴리스 1.23.0과 분석 커밋은 같은 대상을 뜻하지 않습니다. 특히 10월 23일의 Apple 로그인 권한 지원이 10월 22일 릴리스에 포함됐다고 이 표만으로 판단할 수 없습니다. 워크플로가 정의되어 있다는 사실도 모든 실행이 통과했다는 의미는 아닙니다.
완전한 Xcode GUI·Apple SDK 대체품은 아닙니다. Windows는 공식 문서상 WSL 경로이고, 시뮬레이터 설치 명령은 macOS 전용입니다. 도구의 MIT 라이선스가 Apple SDK나 모든 의존성에 같은 조건을 적용한다는 뜻도 아닙니다.
문서의 예시와 현재 구현이 다를 수 있습니다. macOS 설치 문서의 Swift 6.1 출력 예시와 xtool 자체의 소스 빌드에 필요한 도구 버전 6.2는 구분해야 합니다. Documentation/xtool.docc/Control.md는 기본 CFBundleDisplayName이 없다고 설명하지만 Planner.product(...)는 제품 이름으로 이 키를 생성합니다. 또한 dev run의 기기·시뮬레이터 경로는 설치까지 수행하므로 앱 실행은 별도 단계로 보아야 합니다.
인증 정보의 전송과 모든 네트워크 요청은 다른 문제입니다. 설치 문서는 자격 증명을 Apple에만 보낸다고 설명하지만, 비밀번호 인증의 기본 RawADIProvider는 OmnisetteADIProvider이고 외부 Anisette 서비스와 보조 데이터를 교환합니다. 이것이 비밀번호를 그 서비스로 보낸다는 뜻은 아닙니다. 다만 모든 통신이 Apple로만 간다거나 완전히 로컬이라는 해석은 소스와 맞지 않습니다.
저장 방식과 계정 자격도 확인해야 합니다. 기본 DirectoryStorage는 파일로 데이터를 저장하고, AuthToken에는 토큰이나 API 키의 PEM이 포함될 수 있습니다. 정식 macOS 앱 CLI는 Keychain 구현을 주입합니다. 일반 CLI까지 같은 보호 방식을 쓴다고 가정하면 안 됩니다. 일부 capability는 유료 팀을 요구하며, 권한 키를 추가하는 것만으로 사용 자격이 생기지 않습니다.
외부 구현과 실행 환경도 영향을 줍니다. 저수준 서명·기기 통신의 일부는 외부 의존성에 있으며, 운영체제와 기기 조합에 따라 필요한 설정이 달라질 수 있습니다. RAM·디스크 여유는 학습용 제안이며 공식 최소 사양이나 벤치마크 수치가 아닙니다.