From c890aef64d3c0b6906b7ff7dced0298778bae37a Mon Sep 17 00:00:00 2001 From: opficdev <162981733+opficdev@users.noreply.github.com> Date: Sat, 5 Sep 2026 23:41:12 +0900 Subject: [PATCH 1/4] =?UTF-8?q?docs:=20README=20=EC=9E=AC=EA=B5=AC?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 167 ++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 137 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index fde807a..07d8dc1 100644 --- a/README.md +++ b/README.md @@ -1,26 +1,109 @@ -# Cradle +

Cradle

-Cradle은 Swift Macro를 사용해 의존성 graph의 factory를 생성하는 라이브러리입니다. +

+ Factory를 선언하면 Cradle이 의존성을 연결하고 graph별 수명을 관리해요. +

-## 요구 사항 +

+ ExampleApp + · + 사용 안내 + · + 테스트 + · + MIT License +

-- Swift tools 6.3 -- iOS 17 이상 또는 macOS 10.15 이상 -- `CradlePlugin` 사용 시 arm64 또는 x86_64 macOS 빌드 호스트 +
-## 설치 +Cradle은 Swift Macro를 사용해 `@Provide` Factory를 반환 타입과 매개변수 타입으로 연결해요. 어떤 값을 누가 만들고 언제까지 쓸지는 graph마다 정해요. + +Swift tools 6.3과 iOS 17 이상 또는 macOS 10.15 이상이 필요해요. -### Swift Package Manager +## 첫 graph -`Package.swift`의 dependencies에 Cradle을 추가합니다. 아래 `1.0.0`은 배포가 완료된 버전 예시이며, 최초 tag가 게시되기 전에는 실제 설치에 사용할 수 없습니다. +이 예제에서는 `UserRepository`와 `LoadUserUseCase`를 graph가 만들고 보관해요. 화면마다 달라지는 `UserID`는 `userProfileViewModel`을 호출할 때 넘겨요. + +```swift +import Cradle + +struct UserID { + let rawValue: String +} + +struct User { + let id: UserID +} + +protocol UserRepository { + func load(id: UserID) -> User +} + +struct LiveUserRepository: UserRepository { + func load(id: UserID) -> User { + User(id: id) + } +} + +struct LoadUserUseCase { + let repository: any UserRepository + + func execute(id: UserID) -> User { + repository.load(id: id) + } +} + +struct UserProfileViewModel { + let user: User +} + +@DependencyGraph +final class AppGraph { + @Provide + private func makeUserRepository() -> any UserRepository { + LiveUserRepository() + } + + @Provide + private func makeLoadUserUseCase( + repository: any UserRepository + ) -> LoadUserUseCase { + LoadUserUseCase(repository: repository) + } + + @Provide(.transient) + private func makeUserProfileViewModel( + useCase: LoadUserUseCase, + @External userID: UserID + ) -> UserProfileViewModel { + UserProfileViewModel(user: useCase.execute(id: userID)) + } +} + +let graph = AppGraph() +let viewModel = graph.userProfileViewModel( + userID: UserID(rawValue: "user-1") +) +``` + +`@Provide`는 반환 타입을 기준으로 Factory를 연결해요. 기본값은 graph마다 한 번 만들고 계속 쓰며, `.transient`는 접근할 때마다 새로 만들어요. `@External`은 graph가 만들 수 없는 호출 시점 값에 붙여요. + +Cradle은 graph를 만들 때 누락한 등록, 중복된 등록, 순환 의존성처럼 연결할 수 없는 구성을 컴파일 단계에서 알려줘요. Factory 본문에서 하는 임의 호출이나 실행 중 상태까지 검사하지는 않아요. + +## 설치 + +아직 release tag가 없어요. 지금은 `develop` branch를 가리켜 설치해요. ```swift dependencies: [ - .package(url: "https://github.com/opficdev/Cradle.git", from: "1.0.0") + .package( + url: "https://github.com/opficdev/Cradle.git", + branch: "develop" + ) ] ``` -앱 또는 라이브러리 target에는 `Cradle` product를 연결합니다. +Cradle을 쓸 target에는 `Cradle` product를 추가해요. ```swift .target( @@ -31,7 +114,23 @@ dependencies: [ ) ``` -`CradleTesting`은 테스트 target에서만 선택적으로 연결합니다. graph 선언과 `.mock` 편의 API를 함께 사용하려면 `Cradle`도 같은 target에 추가합니다. +
+첫 release tag 뒤 버전을 고정하기 + +첫 release tag가 올라오면 의존성 버전을 고정할 수 있어요. + +```swift +dependencies: [ + .package(url: "https://github.com/opficdev/Cradle.git", from: "1.0.0") +] +``` + +
+ +
+테스트와 Mermaid 산출물 추가하기 + +테스트에서 Factory를 바꾸려면 `CradleTesting`을 test target에만 연결해요. graph 선언과 `.mock` 편의 API를 함께 쓰려면 `Cradle`도 같은 target에 추가해요. ```swift .testTarget( @@ -43,7 +142,7 @@ dependencies: [ ) ``` -`CradlePlugin`은 import하는 라이브러리가 아니라 macOS 빌드 호스트에서 실행하는 Build Tool Plugin입니다. Mermaid 개발 산출물이 필요한 target에만 연결합니다. +`CradlePlugin`은 Swift에서 import하지 않아요. macOS build host에서 실행되는 Build Tool Plugin이므로 Mermaid 개발 산출물이 필요할 때만 target에 연결해요. ```swift .target( @@ -57,31 +156,39 @@ dependencies: [ ) ``` -`@DependencyGraph`, `@Provide`, `@External`의 사용 조건과 생성 멤버 계약은 [DependencyGraph 안내](Sources/Cradle/Cradle.docc/DependencyGraph.md)에서 확인할 수 있습니다. 테스트 대역 교체는 [CradleTesting 안내](Sources/CradleTesting/CradleTesting.docc/CradleTesting.md)를 참고합니다. +`CradlePlugin`은 arm64 또는 x86_64 macOS build host에서만 쓸 수 있어요. -## 배포 +
-관리자는 Actions의 `Deploy SPM`을 수동으로 실행하고, 접두사 없는 `version`과 선택 `release_notes`를 입력합니다. 배포 흐름은 다음 순서로 진행합니다. +
+Xcode에서 Mermaid 파일 열기 -1. `version` 형식과 기존 tag를 확인합니다. -2. artifact, 원격 revision 소비자, 전체 test를 검증합니다. -3. 검증한 commit에 annotated tag를 만들고 원격 tag revision을 확인합니다. -4. exact version 소비자를 검증한 뒤 GitHub Release를 생성하고 게시 상태를 확인합니다. +`CradlePlugin`은 build 때 Mermaid 원본을 만들어요. Xcode에서 파일을 바로 열고 싶다면 target의 마지막 Run Script 단계에서 [CopyCradleMermaid.sh](Examples/ExampleApp/Scripts/CopyCradleMermaid.sh)를 실행하고 Based on dependency analysis를 선택 해제해요. -tag push 뒤 exact version 소비자 검증이나 Release 생성에 실패하면 tag는 그대로 남고 GitHub Release는 생성되지 않습니다. 배포는 tag를 삭제하거나 이동하지 않으므로, 원인을 확인한 뒤 기존 tag를 기준으로 별도 Release 절차를 진행해야 합니다. +```sh +/bin/sh "${SRCROOT}/Scripts/CopyCradleMermaid.sh" +``` -## Mermaid 개발 산출물 +`Cmd+B` 뒤 `Examples/ExampleApp/.cradle/DependencyGraph.mmd`가 생겨요. `.cradle/`은 Git에 올리지 않고 앱과 라이브러리 binary에도 넣지 않아요. Xcode의 build 도구 작업 경로는 공개된 고정 경로가 아니에요. 경고가 나타나면 `CopyCradleMermaid.sh`의 검색 경로를 확인해요. -`CradlePlugin`은 build 중 Mermaid 원본을 생성합니다. Xcode에서 바로 열 파일이 필요하면 target의 마지막 Run Script 단계가 `.cradle/DependencyGraph.mmd`로 복사하게 설정할 수 있습니다. +
-### ExampleApp 설정 +
+관리자용 SwiftPM 배포 -`Examples/ExampleApp`의 `Cradle Mermaid copy` 단계는 [CopyCradleMermaid.sh](Examples/ExampleApp/Scripts/CopyCradleMermaid.sh)를 실행합니다. 같은 설정을 추가할 때는 이 단계를 target의 마지막 Build Phase에 두고 Based on dependency analysis를 선택 해제합니다. +관리자는 Actions에서 `Deploy SPM` workflow를 직접 실행해요. 접두사 없는 `version`과 선택 `release_notes`를 입력하면 아래 순서로 배포돼요. -```sh -/bin/sh "${SRCROOT}/Scripts/CopyCradleMermaid.sh" -``` +1. `version` 형식과 같은 tag가 이미 있는지 확인해요. +2. artifact, 원격 revision 소비자, 전체 test를 검증해요. +3. 검증한 commit에 annotated tag를 만들고 원격 tag revision을 확인해요. +4. exact version 소비자를 검증한 뒤 GitHub Release를 생성하고 게시 상태를 확인해요. + +tag를 원격에 push한 뒤 exact version 소비자 검증이 실패하면 tag는 남지만 GitHub Release는 아직 생성되지 않아요. `createRelease` 요청 뒤 응답 확인이 실패한 경우에는 GitHub에서 Release 생성 여부를 직접 확인해요. 배포 과정에서 tag를 삭제하거나 이동하지 않으므로 원인을 고친 뒤 기존 tag를 기준으로 별도 Release 절차를 진행해요. + +
-`Cmd+B`를 마치면 `Examples/ExampleApp/.cradle/DependencyGraph.mmd`가 생성됩니다. `.cradle/`은 Git에서 제외되며 앱이나 라이브러리 제품에 포함되지 않습니다. +## 더 살펴보기 -Xcode의 build 도구 작업 경로는 공개된 고정 경로가 아닙니다. 이 설정은 현재 `CradlePlugin` 산출물을 쉽게 열기 위한 방법이므로, Xcode 구조가 바뀐 뒤 경고가 나오면 [CopyCradleMermaid.sh](Examples/ExampleApp/Scripts/CopyCradleMermaid.sh)의 검색 경로를 확인해야 합니다. +- [ExampleApp](Examples/ExampleApp) — 단일 app target에서 `sources`, `@External`, SwiftUI `@Observable`과 `@State`를 쓰는 상품 상세 예제 +- [DependencyGraph 안내](Sources/Cradle/Cradle.docc/DependencyGraph.md) — `sources`, 수명, actor graph, `CradlePlugin`, 선언 조건과 compiler diagnostic +- [CradleTesting 안내](Sources/CradleTesting/CradleTesting.docc/CradleTesting.md) — `overrides: true`, `.replace`, `.mock`, graph별 테스트 대역 구성 From 124286305970cdd755a6806b11677a5868858918 Mon Sep 17 00:00:00 2001 From: opficdev <162981733+opficdev@users.noreply.github.com> Date: Sun, 6 Sep 2026 00:04:16 +0900 Subject: [PATCH 2/4] =?UTF-8?q?docs:=20=EB=B0=B0=ED=8F=AC=20=EB=AC=B8?= =?UTF-8?q?=EC=84=9C=EC=99=80=20=EC=95=84=ED=82=A4=ED=85=8D=EC=B2=98=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 24 +++++---------- docs/Architecture.md | 70 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+), 17 deletions(-) create mode 100644 docs/Architecture.md diff --git a/README.md b/README.md index 07d8dc1..de6d360 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,8 @@ · 사용 안내 · + 아키텍처 + · 테스트 · MIT License @@ -86,19 +88,19 @@ let viewModel = graph.userProfileViewModel( ) ``` -`@Provide`는 반환 타입을 기준으로 Factory를 연결해요. 기본값은 graph마다 한 번 만들고 계속 쓰며, `.transient`는 접근할 때마다 새로 만들어요. `@External`은 graph가 만들 수 없는 호출 시점 값에 붙여요. +`@Provide`는 반환 타입을 기준으로 Factory를 연결해요. 기본값은 graph마다 한 번 만들고 계속 쓰며, `.transient`는 접근할 때마다 Factory를 다시 호출해요. `@External`은 graph가 만들 수 없는 호출 시점 값에 붙여요. Cradle은 graph를 만들 때 누락한 등록, 중복된 등록, 순환 의존성처럼 연결할 수 없는 구성을 컴파일 단계에서 알려줘요. Factory 본문에서 하는 임의 호출이나 실행 중 상태까지 검사하지는 않아요. ## 설치 -아직 release tag가 없어요. 지금은 `develop` branch를 가리켜 설치해요. +Cradle은 Swift Package Manager에서 `1.0.0` 이상 버전으로 설치해요. ```swift dependencies: [ .package( url: "https://github.com/opficdev/Cradle.git", - branch: "develop" + from: "1.0.0" ) ] ``` @@ -114,23 +116,10 @@ Cradle을 쓸 target에는 `Cradle` product를 추가해요. ) ``` -
-첫 release tag 뒤 버전을 고정하기 - -첫 release tag가 올라오면 의존성 버전을 고정할 수 있어요. - -```swift -dependencies: [ - .package(url: "https://github.com/opficdev/Cradle.git", from: "1.0.0") -] -``` - -
-
테스트와 Mermaid 산출물 추가하기 -테스트에서 Factory를 바꾸려면 `CradleTesting`을 test target에만 연결해요. graph 선언과 `.mock` 편의 API를 함께 쓰려면 `Cradle`도 같은 target에 추가해요. +`CradleTesting`은 `DependencyOverride.mock` 편의 API를 제공해요. 테스트에서 `.mock`을 쓰려면 `CradleTesting`을, graph 선언도 한다면 `Cradle`을 같은 test target에 추가해요. `.replace`를 직접 쓰는 경우에는 `Cradle`만 필요해요. ```swift .testTarget( @@ -190,5 +179,6 @@ tag를 원격에 push한 뒤 exact version 소비자 검증이 실패하면 tag ## 더 살펴보기 - [ExampleApp](Examples/ExampleApp) — 단일 app target에서 `sources`, `@External`, SwiftUI `@Observable`과 `@State`를 쓰는 상품 상세 예제 +- [아키텍처](docs/Architecture.md) — Macro 확장, `CradlePlugin`, Mermaid artifact 제작 경로 - [DependencyGraph 안내](Sources/Cradle/Cradle.docc/DependencyGraph.md) — `sources`, 수명, actor graph, `CradlePlugin`, 선언 조건과 compiler diagnostic - [CradleTesting 안내](Sources/CradleTesting/CradleTesting.docc/CradleTesting.md) — `overrides: true`, `.replace`, `.mock`, graph별 테스트 대역 구성 diff --git a/docs/Architecture.md b/docs/Architecture.md new file mode 100644 index 0000000..247b43b --- /dev/null +++ b/docs/Architecture.md @@ -0,0 +1,70 @@ +# Cradle 아키텍처 + +Cradle은 compiler가 graph 선언을 확장하는 경로와 build 중 Mermaid를 만드는 경로를 나눠요. 둘 다 소비자 Swift source를 보지만, 서로의 결과를 사용하지 않아요. + +## 소비자 build 흐름 + +아래 그림의 실선은 package target 의존성이에요. 점선은 compiler 확장, plugin 실행, 개발용 산출물 생성처럼 build 중에만 일어나는 흐름이에요. + +```mermaid +flowchart LR + subgraph Consumer["소비자 target"] + App["앱 또는 library target
Swift source"] + Test["test target"] + end + + subgraph Package["Cradle package"] + Cradle["Cradle
공개 Macro 선언과 runtime type"] + Macros["CradleMacros
Macro 확장과 compiler diagnostic"] + Testing["CradleTesting
선택 test support"] + Plugin["CradlePlugin
선택 Build Tool Plugin"] + Maker["CradleDiagramMaker
prebuilt executable artifact"] + Syntax["swift-syntax products"] + end + + App -->|import| Cradle + Test -->|import| Cradle + Test -->|선택 import| Testing + Testing --> Cradle + Cradle --> Macros + Macros --> Syntax + App -.->|Macro 확장| Macros + Macros -.->|생성 멤버와 compiler diagnostic| App + App -.->|선택 plugin 연결| Plugin + Plugin --> Maker + Plugin -.->|현재 target Swift source 전달| Maker + Maker -.->|개발용 .mmd 생성| Output["plugin work directory
CradleDiagrams/module/DependencyGraph.mmd
binary 미포함"] +``` + +소비자 target은 `Cradle`만 import해요. `CradleMacros`는 `Cradle`이 참조하는 Macro 구현 target이므로 소비자가 직접 의존하거나 import하지 않아요. Macro는 `@DependencyGraph`와 `@Provide`를 확장하고, graph 연결이 성립하지 않는 경우 compiler diagnostic을 만들어요. + +`CradleTesting`은 test target에서만 선택해요. `CradlePlugin`도 별도 경로예요. plugin은 현재 target의 Swift source를 `CradleDiagramMaker`에 전달해 `.mmd`를 만들 뿐, app이나 library binary에 연결하지 않아요. + +## artifact 제작 경로 + +소비자 build는 prebuilt `CradleDiagramMaker`를 사용해요. 아래 target들은 그 artifact를 제작하고 검증할 때만 쓰며, 소비자 target의 의존성이 아니에요. + +```mermaid +flowchart LR + Script["Scripts/build-diagram-artifact.sh"] + Artifact["Artifacts/CradleDiagramMaker.artifactbundle
CradleDiagramMaker binary"] + Syntax["swift-syntax products"] + Parser["SwiftParser"] + + subgraph ArtifactBuild["artifact 제작 전용 target"] + MakerSource["CradleDiagramMakerSource
executable target"] + MakerSupport["CradleDiagramMakerSupport"] + Analysis["CradleGraphAnalysis"] + end + + MakerSource --> MakerSupport + MakerSupport --> Analysis + MakerSupport --> Parser + Analysis --> Syntax + Script -.->|arm64와 x86_64 build| MakerSource + MakerSource -.->|artifact 제작| Artifact +``` + +`CradleDiagramMakerSource`는 `CradleDiagramMakerSupport`와 `CradleGraphAnalysis`로 source의 선언을 읽어요. `Scripts/build-diagram-artifact.sh`는 arm64와 x86_64 실행 파일을 만들어 artifact에 넣어요. 소비자 build에서는 이 artifact만 `CradlePlugin`이 실행해요. + +Mermaid 분석은 Macro 확장과 별개예요. 따라서 이 문서는 runtime override 선택, graph 수명, `@External` 호출 경로를 설명하지 않아요. 그 계약은 [DependencyGraph 안내](../Sources/Cradle/Cradle.docc/DependencyGraph.md)에서 확인할 수 있어요. From 98f7d14cb3cbf3f9b46f2735b648fb08d7794dd2 Mon Sep 17 00:00:00 2001 From: opficdev <162981733+opficdev@users.noreply.github.com> Date: Sun, 6 Sep 2026 00:06:18 +0900 Subject: [PATCH 3/4] =?UTF-8?q?docs:=20=EC=84=B9=EC=84=A0=20=EC=88=9C?= =?UTF-8?q?=EC=84=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 48 ++++++++++++++++++++++++------------------------ 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index de6d360..7237cb8 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,30 @@ Cradle은 Swift Macro를 사용해 `@Provide` Factory를 반환 타입과 매개 Swift tools 6.3과 iOS 17 이상 또는 macOS 10.15 이상이 필요해요. +## 설치 + +Cradle은 Swift Package Manager에서 `1.0.0` 이상 버전으로 설치해요. + +```swift +dependencies: [ + .package( + url: "https://github.com/opficdev/Cradle.git", + from: "1.0.0" + ) +] +``` + +Cradle을 쓸 target에는 `Cradle` product를 추가해요. + +```swift +.target( + name: "AppComposition", + dependencies: [ + .product(name: "Cradle", package: "Cradle") + ] +) +``` + ## 첫 graph 이 예제에서는 `UserRepository`와 `LoadUserUseCase`를 graph가 만들고 보관해요. 화면마다 달라지는 `UserID`는 `userProfileViewModel`을 호출할 때 넘겨요. @@ -92,30 +116,6 @@ let viewModel = graph.userProfileViewModel( Cradle은 graph를 만들 때 누락한 등록, 중복된 등록, 순환 의존성처럼 연결할 수 없는 구성을 컴파일 단계에서 알려줘요. Factory 본문에서 하는 임의 호출이나 실행 중 상태까지 검사하지는 않아요. -## 설치 - -Cradle은 Swift Package Manager에서 `1.0.0` 이상 버전으로 설치해요. - -```swift -dependencies: [ - .package( - url: "https://github.com/opficdev/Cradle.git", - from: "1.0.0" - ) -] -``` - -Cradle을 쓸 target에는 `Cradle` product를 추가해요. - -```swift -.target( - name: "AppComposition", - dependencies: [ - .product(name: "Cradle", package: "Cradle") - ] -) -``` -
테스트와 Mermaid 산출물 추가하기 From 9cd09e35f77c4c5653e767c57d76770d94642d78 Mon Sep 17 00:00:00 2001 From: opficdev <162981733+opficdev@users.noreply.github.com> Date: Sun, 6 Sep 2026 00:43:22 +0900 Subject: [PATCH 4/4] =?UTF-8?q?docs:=20=EC=86=8C=EB=B9=84=EC=9E=90=20Merma?= =?UTF-8?q?id=20=EC=95=88=EB=82=B4=20=EB=B3=B4=EC=99=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7237cb8..37c9348 100644 --- a/README.md +++ b/README.md @@ -152,13 +152,13 @@ Cradle은 graph를 만들 때 누락한 등록, 중복된 등록, 순환 의존
Xcode에서 Mermaid 파일 열기 -`CradlePlugin`은 build 때 Mermaid 원본을 만들어요. Xcode에서 파일을 바로 열고 싶다면 target의 마지막 Run Script 단계에서 [CopyCradleMermaid.sh](Examples/ExampleApp/Scripts/CopyCradleMermaid.sh)를 실행하고 Based on dependency analysis를 선택 해제해요. +`CradlePlugin`은 build 때 Mermaid 원본을 만들어요. 외부 Xcode 프로젝트에서 파일을 바로 열려면 먼저 [CopyCradleMermaid.sh](Examples/ExampleApp/Scripts/CopyCradleMermaid.sh)를 소비자 프로젝트의 `Scripts/CopyCradleMermaid.sh`로 복사해요. Mermaid가 필요한 같은 target에 `CradlePlugin`을 연결한 뒤, target의 마지막 Run Script 단계에서 다음 명령을 실행하고 Based on dependency analysis를 선택 해제해요. ```sh /bin/sh "${SRCROOT}/Scripts/CopyCradleMermaid.sh" ``` -`Cmd+B` 뒤 `Examples/ExampleApp/.cradle/DependencyGraph.mmd`가 생겨요. `.cradle/`은 Git에 올리지 않고 앱과 라이브러리 binary에도 넣지 않아요. Xcode의 build 도구 작업 경로는 공개된 고정 경로가 아니에요. 경고가 나타나면 `CopyCradleMermaid.sh`의 검색 경로를 확인해요. +`Cmd+B` 뒤 소비자 프로젝트의 `.cradle/DependencyGraph.mmd`가 생겨요. `.cradle/`은 Git에 올리지 않고 앱과 라이브러리 binary에도 넣지 않아요. Xcode의 build 도구 작업 경로는 공개된 고정 경로가 아니에요. 경고가 나타나면 `CopyCradleMermaid.sh`의 검색 경로를 확인해요.