본문 바로가기
C.W.K.
Lesson 01 of 06 · published

Package.swift 읽고 쓰기

~15 min · packages-projects, swiftpm, manifest, modules

Level 0번들 열어본 사람
0 XP0/84 lessons0/17 achievements
0/100 XP to next level100 XP to go0% complete
"매니페스트는 Swift로 쓰지만, 계약서처럼 읽어. 뭐가 있고, 어느 플랫폼용이고, 뭘로 만드는지."

가족이 Mac 앱을 매니페스트로 빌드하는 이유

이 가족의 Mac 앱은 전부 Swift 패키지야. 데스크톱 쪽엔 .xcodeproj가 하나도 없어. Package.swift는 짧고, diff가 깔끔하고, 코드처럼 리뷰되고, 터미널이든 스크립트든 코딩 에이전트든 똑같이 빌드돼. 명령도 swift build -c release --product Spark 한 줄이야. 대신 번들, 서명, 설치는 네 스크립트 몫이 되는데, 그건 6트랙에서 다뤄. iOS랑 워치 앱에는 XcodeGen을 써. 앱 타깃엔 패키지로 표현할 수 없는 게 필요하거든. 이건 네 번째 레슨에서 볼 거야.

매니페스트의 구성

  • 첫 줄이 중요해. // swift-tools-version: 6.2가 어떤 매니페스트 API를 쓸 수 있는지 정해. 도구가 읽어 들이는 주석이라, 틀리게 적으면 새 설정들이 아예 없는 걸로 취급돼.
  • platforms는 배포 대상이야. 패키지가 돌아간다고 약속하는 가장 오래된 macOS, iOS, watchOS 버전이지.
  • products는 바깥에서 의존할 수 있는 것들이야. 다른 패키지가 import하는 코드는 .library, 실행할 도구나 앱 바이너리는 .executable.
  • targets가 모듈이야. Swift나 C 코드는 .target, 프로그램은 .executableTarget, 테스트는 .testTarget. 경로를 따로 안 주면 각각 Sources/<Name>이나 Tests/<Name>에 있어.
  • dependencies로는 다른 패키지를 URL과 버전으로, 아니면 로컬 path로 가져와.
  • 타깃별 swiftSettings / linkerSettings에는 언어 모드 예외, 예정 기능, 링크할 시스템 라이브러리와 프레임워크가 들어가.
  • 끝에 오는 swiftLanguageModes: [.v6]가 패키지 기본값이야. 6.3 툴체인의 swift package init은 이미 이렇게 써줘.

나중에 이득 보는 모듈 나누기

가족 Mac 앱은 크게 두 모듈로 나뉘어. AppKit을 import하지 않는 순수 코어 모듈, 그리고 AppKit과 코어를 조립하는 앱 모듈이야. 가끔은 공유 킷 코드 모듈이 하나 더 붙기도 해. 코어는 Command Line Tools만 깔린 Mac까지 포함해 어느 Mac에서든 빨리 빌드되고 테스트돼. 앱 모듈은 얇아. 나중에 같은 앱에 아이폰 클라이언트가 생기면, 폰이 링크할 게 이미 코어로 준비돼 있어.

발목 잡는 함정 두 가지

path 의존성의 식별자는 디렉터리 이름이야. 가족이 공유 패키지를 swift/라는 폴더에서 CwkKit/으로 옮기기 전에는, package: "CwkKit"을 찾던 사용처는 전부 unknown package 'CwkKit'; valid packages are: 'swift'로 실패했어. 그런데 이름을 바꾸고 나니 이번엔 패키지 자신의 .build 캐시가 옛 절대 경로를 기억하고 있었어. 그래서 달랑 error: fatalError 한 줄만 찍고 실패했지. rm -rf .build로 풀렸어. fatalError는 SwiftPM이 겉에 씌운 말일 뿐, 그 자체로는 아무 정보가 없어. 그러니 그 밑에 찍힌 내용을 읽어.

Code

SparkKit: 모든 플랫폼이 링크하는 코어, 그리고 플랫폼별 조각·swift
// swift-tools-version: 6.2
import PackageDescription

let package = Package(
    name: "SparkKit",
    platforms: [.macOS(.v14), .iOS(.v17), .watchOS(.v10)],
    products: [
        .library(name: "SparkCore", targets: ["SparkCore"]),     // every platform, no UI imports
        .library(name: "SparkMac", targets: ["SparkMac"]),       // macOS pieces behind #if os(macOS)
        .library(name: "SparkPhone", targets: ["SparkPhone"]),   // UIKit pieces behind canImport
        .executable(name: "spark-selftest", targets: ["spark-selftest"]),
    ],
    targets: [
        .target(name: "SparkCore"),
        .target(name: "CSparkProc"),                                 // C shim, headers in include/
        .target(name: "SparkMac", dependencies: ["SparkCore", "CSparkProc"]),
        .target(name: "SparkPhone", dependencies: ["SparkCore"]),
        .executableTarget(name: "spark-selftest", dependencies: ["SparkCore"]),
        .testTarget(name: "SparkCoreTests", dependencies: ["SparkCore"]),
    ],
    swiftLanguageModes: [.v6]
)
실제로 돌리게 될 명령들·bash
swift package init --type executable --name Spark   # tools-version 6.3, swiftLanguageModes [.v6]
swift build                                          # debug, host platform
swift build -c release --product Spark               # the binary your bundle script copies
swift build -c release --show-bin-path               # where it landed
swift test                                           # host platform only (see lesson six)
swift package describe                               # what SwiftPM thinks the package is

External links

Exercise

위 매니페스트대로 Spark 패키지를 SparkKit으로 바꿔. 구성은 Capture 타입이 든 SparkCore, 비어 있는 SparkMac과 SparkPhone, spark-selftest 실행 파일, SparkCoreTests 타깃이야. swift build, swift run spark-selftest, swift test를 돌려봐. 그러고 나서 패키지 디렉터리 이름을 바꾸고 다시 빌드해. 나오는 에러를 그대로 적고, 그걸 푸는 명령도 적어봐.
Hint
Sources/SparkCore, Sources/SparkMac, Sources/SparkPhone, Sources/spark-selftest, Tests/SparkCoreTests 폴더를 만들고 각각 Swift 파일을 하나씩 넣어. Sources/CSparkProc도 만들어서 빈 CSparkProc.c와 include/ 폴더를 둬. 내용은 C 레슨에서 채워. 이 폴더가 없으면 매니페스트 단계에서 빌드가 멈춰. 이름을 바꾼 뒤엔 첫 error: 줄 아래쪽에서 모듈 캐시 경로 얘기를 찾아봐.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인 — 댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.