샘성의 iOS 개발 일지

[Tuist] 모듈 생성 자동화 하기 (tuist scaffold, .stencil) 본문

iOS/Swift

[Tuist] 모듈 생성 자동화 하기 (tuist scaffold, .stencil)

SamusesApple 2024. 6. 18. 15:50
728x90

목적

   원하는 모듈을 생성할 때마다 필요한 파일을 하나하나 세팅해주는 번거로움 해결하기.

  → 모듈에 필요한 코드를 템플릿 파일에 미리 작성해두고 tuist scaffold 명령어를 실행하면 자동으로 해당되는 모듈에 대한 파일 및 코드가 작성 됨.

 

준비물

  1. Tuist 폴더 안에 Templates 폴더 생성
  2. Templates 폴더의 하위 폴더로 '원하는 모듈 이름' 폴더 생성하기
  3. '원하는 모듈 이름' 폴더 안에 '원하는모듈이름.swift' 파일 직접 생성하기

 

파일 구조

...
  └── Tuist
      └── Templates
          └── Domain
              └── Domain.swift       # Domain 모듈에 대한 템플릿 전체 설정
              └── Package.stencil    # Domain 모듈에 대한 패키지 템플릿 파일
              └── Project.stencil    # Domain 모듈에 대한 기본 프로젝트 템플릿 파일
              └── InfoPlist.stencil  # Domain 모듈에 대한 InfoPlist 템플릿 파일
              └── Test.stencil       # Domain 모듈의 테스트 파일에 대한 템플릿 파일
  •  


 

Domain.swift 파일

import ProjectDescription

// scaffold 명령어 시 받을 인자 정의
let name: Template.Attribute = .required("name") // 하위모듈명 (파일명)
let author: Template.Attribute = .required("author")  // 생성자
let currentDate: Template.Attribute = .required("currentDate") // 생성 날짜

// 템플릿 선언
let domainTemplate = Template(
    description: "A template for a new Domain modules",
    attributes: [
        name,
        author,
        currentDate
    ],
    items: DomainTemplate.allCases.map { $0.item }
)

enum DomainTemplate: CaseIterable {
    case package
    case project
    case infoPlist
    case test

    // 템플릿 내부에 추가할 파일
    var item: Template.Item {
        switch self {
        case .package:
            return .file(path: .basePath + "/Package.swift",
                        templatePath: "Package.stencil")
        case .project:
            return .file(path: .basePath + "/Sources/Project.swift",
                         templatePath: "Project.stencil")
        case .infoPlist:
            return .file(path: .basePath + "/\(name)-Info.plist", 
                        templatePath: "InfoPlist.stencil")
        case .test:
            return .file(path: .basePath + "/Tests/" + .testName + "/" + .testName + ".swift",
                         templatePath: "Test.stencil")
        }
    }
}

// 명령어 실행시 Domain 모듈 파일이 생성될 기본 경로.
extension String {
    static var basePath: Self {
        return "Modules/Domain/\(name)"
    }

    static var testName: Self {
        return "\(name)Tests"
    }
}

 

 

Package.stencil 파일

// swift-tools-version: 5.10

import PackageDescription

let package = Package(
    name: "{{ name }}",
    platforms: [
        .iOS(.v17) // 타겟 버전
    ],
    products: [
        .library(
            name: "{{ name }}",
            targets: ["{{ name }}"])
    ],
    dependencies: [
        // 필요한 의존성 추가
    ],
    targets: [
        .target(
            name: "{{ name }}",
            dependencies: []
        )
    ]
)

 

 

 

Project.stencil 파일

// 기본 빈 프로젝트 파일로 세팅

import Foundation

class {{ name }} { }

 

 

 

InfoPlist.stencil 파일

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>CFBundleDevelopmentRegion</key>
    <string>en</string> // region 설정
    <key>CFBundleExecutable</key>
    <string>{{ name }}</string>
    <key>CFBundleIdentifier</key>
    <string>앱번들.{{ name }}</string> // 번들 식별자 정의
    <key>CFBundleInfoDictionaryVersion</key>
    <string>6.0</string>
    <key>CFBundleName</key>
    <string>{{ name }}</string>
    <key>CFBundlePackageType</key>
    <string>FMWK</string>  // 패키지 타입 프레임워크로 세팅
    <key>CFBundleShortVersionString</key>
    <string>1.0</string>
    <key>CFBundleVersion</key>
    <string>1</string>
    <key>CFBundleSupportedPlatforms</key>
    <array>
        <string>iPhoneOS</string> // 타겟 플랫폼 설정
    </array>
    <key>MinimumOSVersion</key>
    <string>17.0</string>  // 최소 타겟 버전
</dict>
</plist>

 

 

Test.stencil 파일

// Test 코드를 위한 파일 템플릿

import XCTest
@testable import {{ name }}

class {{ name }}Tests: XCTestCase {
    func testExample() {
        // Example test case
    }
}

 

 


템플릿으로 Domain 모듈 만들기

tuist scaffold Domain --name DomainSample --author Sam --current-date 2024/06/18

 

 

 

 

 

  • 참고로 Domain.swift 파일에서 'name', 'author', 'currentDate'를 필수 인자로 설정해두었기 때문에 셋 중 하나라도 빠지면 아래와 비슷한 오류가 발생하며 모듈 생성이 불가능하다.

 

 


App의 Project.swift 파일에 모듈 추가하기

1. 앱의 모듈 구성 및 의존성을 관리하는 Project.swift 파일에 새로 생성한 모듈 패키지 및 의존성을 추가한다.

// 앱의 Project.swift 파일

let project = Project(
    name: "MyApp",
    packages: [
        .local(path: .relativeToRoot("Modules/Domain/DomainSample")) // 생성한 모듈 패키지 추가
    ],
    targets: [
        .target(
            name: "MyApp",
            destinations: .iOS,
            product: .app,
            bundleId: "io.tuist.myApp",
            deploymentTargets: .iOS("17.0"),
            infoPlist: .extendingDefault(with: infoPlist),
            sources: ["App/Sources/**"],
            resources: ["App/Resources/**"],
            dependencies: [
            	.package(product: "DomainSample") // 앱에 모듈 의존성 추가
            ],
        ),
        .target(
            name: "MyAppTests",
            destinations: .iOS,
            product: .unitTests,
            bundleId: "io.tuist.myAppTests",
            infoPlist: .default,
            sources: ["App/Tests/**"],
            resources: [],
            dependencies: [.target(name: "MyApp")]
        ),
    ]
)

 

 

2. tuist generate 명령어를 통해 프로젝트를 재구성한다.

tuist generate

 

 

생성된 DomainSample 패키지 추가됨
앱에 Embeded 됨

 

728x90