[Swift 5.9][Xcode 15] Swift Package를 사용하지 않고 Swift의 Macros를 사용할 수 있을까? (1) - Why
Swift 5.9에서 새롭게 도입된 매크로는 현재 Swift 패키지를 통해서만 사용 가능합니다. 이 사실은 WWDC 2023에서 열린 What’s new in Swift, Expand on Swift macros, Write Swift macros 세션들을 통해, 매크로가 Swift 패키지를 통해 사용되는 모습을 보여주며 확인할 수 있습니다. 현재로서는 Xcode 프로젝트 타겟에 직접 추가하는 방법은 제공되지 않습니다.
애플이 제안한 방식을 따를 경우, apple/swift-syntax 라이브러리에 의존해야 합니다. 이 라이브러리가 바이너리를 제공하지 않기 때문에, 매회 빌드 시 전체적인 빌드 시간이 증가하는 문제가 발생합니다.
Xcode 15를 사용하면 기본 Swift Macro 템플릿을 활용해 Swift Package를 쉽게 생성할 수 있습니다.
해당 템플릿으로 생성된 Swift Package로 클라이언트를 빌드할 경우, M1 에어를 기준으로 초기 빌드 시간은 약 23.2초가 소요됩니다.
전체 빌드 시간에 Macro 사용으로 인해 23초가 추가되는 것은, 특히 CI나 UI Preview 확인 시 등 빠른 빌드가 필요한 환경에서 큰 제약으로 작용합니다. 이 문제를 해결하기 위해, swift-syntax 바이너리를 생성해 Macro와 연결한다면 빌드 시간을 단축할 수 있을 것입니다.
-load-plugin-executable
Macro 소스를 살펴보겠습니다.
@freestanding(expression)
public macro stringify<T>(_ value: T) -> (T, String) = #externalMacro(module: "MyMacroMacros",
type: "StringifyMacro")
stringify
매크로는 MyMacroMacros
모듈 내의 StringifyMacro
타입을 활용합니다.
MyMacroMacros
모듈의 의존성을 확인하기 위해, 해당 Swift Package의 Package.swift
를 살펴보겠습니다.
let package = Package(
name: "MyMacro",
...
targets: [
.macro(
name: "MyMacroMacros",
dependencies: [
.product(name: "SwiftSyntaxMacros", package: "swift-syntax"),
.product(name: "SwiftCompilerPlugin", package: "swift-syntax")
]
),
...
]
)
여기서 swift-syntax
패키지를 사용하고 있음을 확인할 수 있습니다.
MyMacro
모듈로 돌아가 보겠습니다.
이 모듈은 Package.swift
에서 새로운 타겟 유형인 macro
를 어떻게 연결하는지가 중요합니다.
빌드 로그를 살펴봅시다.
빌드 로그를 통해 MyMacroMacros
모듈을 어떻게 포함하는지 확인할 수 있습니다. -load-plugin-executable
옵션을 사용해 MyMacroMacros
모듈 경로를 포함하는 것을 볼 수 있습니다.
-Xfrontend -load-plugin-executable -Xfrontend /Users/minsone/Library/Developer/Xcode/DerivedData/MyMacro-gbgoxackjnklrdefccvlgwrfzacs/Build/Products/Debug/MyMacroMacros\#MyMacroMacros
MyMacroMacros
의 세부사항을 파악하기 위해, 먼저 file
명령을 사용해 파일 유형을 확인합니다.
$ file MyMacroMacros
MyMacroMacros: Mach-O 64-bit executable arm64
이 파일이 실행 파일임을 알 수 있습니다.
다음으로, otool
을 이용해 어떤 동적 라이브러리를 의존하는지 확인해봅시다.
$ otool -L MyMacroMacros
MyMacroMacros:
/System/Library/Frameworks/Foundation.framework/Versions/C/Foundation (compatibility version 300.0.0, current version 2202.0.0)
/usr/lib/libobjc.A.dylib (compatibility version 1.0.0, current version 228.0.0)
/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1336.61.1)
/usr/lib/libc++.1.dylib (compatibility version 1.0.0, current version 1600.157.0)
/usr/lib/swift/libswiftCore.dylib (compatibility version 1.0.0, current version 5.9.2)
/usr/lib/swift/libswiftCoreFoundation.dylib (compatibility version 1.0.0, current version 120.100.0, weak)
/usr/lib/swift/libswiftDarwin.dylib (compatibility version 1.0.0, current version 0.0.0)
/usr/lib/swift/libswiftDispatch.dylib (compatibility version 1.0.0, current version 34.0.2, weak)
/usr/lib/swift/libswiftIOKit.dylib (compatibility version 1.0.0, current version 1.0.0, weak)
/usr/lib/swift/libswiftOSLog.dylib (compatibility version 1.0.0, current version 4.0.0, weak)
/usr/lib/swift/libswiftObjectiveC.dylib (compatibility version 1.0.0, current version 8.0.0, weak)
/usr/lib/swift/libswiftXPC.dylib (compatibility version 1.0.0, current version 29.0.2, weak)
/usr/lib/swift/libswiftos.dylib (compatibility version 1.0.0, current version 1040.0.0)
/usr/lib/swift/libswiftFoundation.dylib (compatibility version 1.0.0, current version 1.0.0)
다음으로, nm
명령을 사용해 swift-syntax
라이브러리가 포함되어 있는지 확인합니다.
$ nm MyMacroMacros
...
00000001009399c4 T _$s11SwiftParser06TriviaB0V05parseC0_8positionSay0A6Syntax03RawC5PieceOGAF0F4TextV_AF0C8PositionOtFZ
000000010093b344 t _$s11SwiftParser06TriviaB0V05parseC0_8positionSay0A6Syntax03RawC5PieceOGAF0F4TextV_AF0C8PositionOtFZSbs7UnicodeO6ScalarVXEfU0_
000000010093b370 t _$s11SwiftParser06TriviaB0V05parseC0_8positionSay0A6Syntax03RawC5PieceOGAF0F4TextV_AF0C8PositionOtFZSbs7UnicodeO6ScalarVXEfU1_
...
00000001004c4c68 T _$s11SwiftSyntax010BorrowExprB0V016unexpectedBeforeC7KeywordAA015UnexpectedNodesB0VSgvM
00000001004c4cb0 t _$s11SwiftSyntax010BorrowExprB0V016unexpectedBeforeC7KeywordAA015UnexpectedNodesB0VSgvM.resume.0
00000001004c4810 T _$s11SwiftSyntax010BorrowExprB0V016unexpectedBeforeC7KeywordAA015UnexpectedNodesB0VSgvg
...
0000000100827444 T _$s16SwiftDiagnostics07GroupedB0V10SourceFileV11diagnosticsSayAA10DiagnosticVGvM
0000000100827454 t _$s16SwiftDiagnostics07GroupedB0V10SourceFileV11diagnosticsSayAA10DiagnosticVGvM.resume.0
00000001008273e4 T _$s16SwiftDiagnostics07GroupedB0V10SourceFileV11diagnosticsSayAA10DiagnosticVGvg
...
swift-syntax에서 사용하는 라이브러리가 포함되어 있는 것을 확인할 수 있습니다.
결론적으로, 우리는 MyMacroMacros
와 같은 파일을 직접 만들거나 swift-syntax
관련 라이브러리를 XCFramework
로 제공함으로써 빌드 시간을 줄일 수 있는 가능성이 있습니다. 다음 글에서는 이에 대한 구체적인 방법을 논의하겠습니다.