[iOS] Tuist 모듈 생성/삭제 자동화로 개발 생산성 높이기

반복적인 모듈 생성과 삭제를 Shell Script로 자동화한 과정

모듈화 아키텍처, 좋긴 한데...

팀 라이빗은 Tuist를 사용해서 모듈화 아키텍처를 구성하고 있어요.

기능별로 모듈을 분리하면 빌드 시간 단축, 의존성 관리, 코드 재사용성 측면에서 장점도 많은데요!

그러나 새로운 기능을 개발할 때마다 모듈을 추가해야 해서 해당 과정이 꽤 번거롭다는 단점도 있었습니다. 😅

모듈 하나 추가하려면 파일 4개를 수정해야 한다고?

아래 사진은 현재 라이빗의 프로젝트 구조를 나타낸 다이어그램인데요. 한눈에 봐도 꽤 모듈이 많은 것을 알 수 있습니다.

위와 같은 모듈 구조를 유지하며 새 모듈을 추가하려면 4개의 파일을 수동으로 수정해야 했어요.

1. Module+Constant.swift

모듈의 하위 타겟(Data, Domain, Feature)을 정의하는 enum을 추가하는 단계입니다.

코드로는 아래와 같이 작성해요!

// MARK: - User Module

public enum UserModule: String {
    case userData = "UserData"
    case userDomain = "UserDomain"
    case userFeature = "UserFeature"
}

2. Module+TargetID.swift

TargetID enum에 새 case를 추가하고, name, sourcesPath 등의 computed property에도 case를 추가해야 합니다.

public enum TargetID {
    case app
    case core(CoreModule)
    case login(LoginModule)
    case search(SearchModule)
    case user(UserModule)  // 추가

    public var name: String {
        switch self {
        // ...
        case .user(let module): return module.rawValue  // 추가
        }
    }

    public var sourcesPath: SourceFilesList {
        switch self {
        // ...
        case .user(let module):  // 추가
            return ["\(module.rawValue)/Sources/**"]
        }
    }
}

3. Module+ProjectID.swift

프로젝트 레벨의 case를 추가합니다.

public enum ProjectID: String, CaseIterable {
    case app = "App"
    case core = "Core"
    case dsKit = "DSKit"
    case search = "Search"
    case login = "Login"
    case user = "User"  // 추가

    public var name: String { rawValue }

    public var path: Path {
        .relativeToRoot("Projects/\(rawValue)")
    }
}

4. TargetDependency+Extension.swift

다른 모듈에서 의존성을 추가할 때 사용할 헬퍼 함수를 작성합니다.

extension TargetDependency {
    // ...

    public static func user(_ module: UserModule) -> TargetDependency {
        return .project(target: module.rawValue, path: ProjectID.user.path)
    }
}

모듈 하나 추가하는데 4개 파일, 10군데 이상을 수정해야 한다는 점에서 휴먼 에러가 발생하기도 쉽고, 무엇보다 귀찮은 게 컸습니다.

이런 과정, 자동화할 수 있지 않을까?

위와 같은 과정을 명령어 하나만으로 딸깍! 하고 자동화할 수 있다면 작업 시간은 얼마나 감소할까요?

기존에는 한눈에 봐도 복잡한 수정 과정과 파일을 찾는 과정, 작성하고 휴먼 에러가 났을 경우 해당 에러를 해결하는 시간까지 족히 10분은 걸렸던 것 같습니다. (에러가 날 경우 더 오랜 시간이 걸리는 경우도 있었어요... ㅜㅜ)

그래서 대화형 Shell Script를 만들어서 이 과정을 자동화해보자! 라는 생각을 했고, 실행에 옮겼습니다.

먼저 모듈 생성 스크립트를 생성하고, 대화형 인터페이스를 구성해 다른 팀원들도 편하게 사용할 수 있도록 처리했는데요.

완성한 모듈 생성 스크립트완성한 모듈 생성 스크립트

자동화의 과정은 멀고도 험하다... 🚶‍♀️

#!/bin/bash

# Tuist 모듈 자동 생성 스크립트 (대화형)
# 사용법: ./Scripts/create_module.sh

set -e

# 색상 정의
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
BOLD='\033[1m'
NC='\033[0m'

터미널에서 색상을 사용해 시각적으로 보기 좋게 만들었어요. 사용자 경험도 중요하니까요! 😎

# 모듈 이름 입력
echo -e "${BOLD}${BLUE}📦 모듈 이름을 입력하세요${NC}"
echo -e "${YELLOW}   (예: User, Concert, Ticket)${NC}"
read -p "   모듈 이름: " MODULE_INPUT

# 입력 검증
if [ -z "$MODULE_INPUT" ]; then
    echo -e "${RED}❌ 모듈 이름을 입력해주세요.${NC}"
    exit 1
fi

# 이름 정규화 (첫 글자 대문자)
MODULE_NAME_UPPER="$(echo "${MODULE_INPUT:0:1}" | tr '[:lower:]' '[:upper:]')${MODULE_INPUT:1}"
MODULE_NAME_LOWER="$(echo "${MODULE_INPUT:0:1}" | tr '[:upper:]' '[:lower:]')${MODULE_INPUT:1}"

사용자가 user라고 입력하든 User라고 입력하든 자동으로 대소문자를 정규화하는 코드를 추가해 휴먼 에러를 방지하고 편리성을 높이려고 했어요.

여러 명이 함께 사용하는 스크립트이기 때문에 한명에게 맞춰진 스크립트 구조보다는 모두가 함께 사용할 수 있는 범용적인 스크립트를 만들기 위해 노력했던 것 같습니다.

Swift 파일의 특정 위치에 코드를 삽입하기 위해서는 awk라는 키워드를 사용해야 했는데요.

아래와 같이 작업해 적절한 위치에 마크 주석과 새로운 enum을 모두 삽입할 수 있었습니다.

awk -v upper="$MODULE_NAME_UPPER" -v lower="$MODULE_NAME_LOWER" \
    -v data="$CREATE_DATA" -v domain="$CREATE_DOMAIN" -v feature="$CREATE_FEATURE" '
/\/\/ MARK: - External Dependency/ {
    print ""
    print "// MARK: - " upper " Module"
    print ""
    print "public enum " upper "Module: String {"
    if (data == "true") print "    case " lower "Data = \"" upper "Data\""
    if (domain == "true") print "    case " lower "Domain = \"" upper "Domain\""
    if (feature == "true") print "    case " lower "Feature = \"" upper "Feature\""
    print "}"
    print ""
}
{ print }
' "$CONSTANT_FILE" > "$TEMP_FILE"
mv "$TEMP_FILE" "$CONSTANT_FILE"

에러 방지를 위한 백업 및 롤백 기능 구현

이번 작업에서 가장 중요하게 다뤘던 부분은 모듈이 잘못 삭제되거나 생성될 경우 복구할 수 있는 기능이 필요하다는 점이었습니다.

이를 구현하기 위해 작업 시 백업을 생성하고, 롤백 함수를 구현해 스크립트 실행 시 예전 백업 여부를 확인하고 롤백 유무를 확인하도록 했습니다.

# 백업 디렉토리
BACKUP_DIR="$PROJECT_ROOT/.module_backup"

# 백업 생성
echo -e "   ${YELLOW}[0/5]${NC} 백업 생성 중..."
mkdir -p "$BACKUP_DIR"
cp "$CONSTANT_FILE" "$BACKUP_DIR/"
cp "$TARGET_ID_FILE" "$BACKUP_DIR/"
cp "$PROJECT_ID_FILE" "$BACKUP_DIR/"
cp "$DEPENDENCY_FILE" "$BACKUP_DIR/"
echo "$MODULE_NAME_UPPER" > "$BACKUP_DIR/module_name.txt"

# Projects 디렉토리도 백업
if [ -d "$MODULE_DIR" ]; then
    cp -r "$MODULE_DIR" "$BACKUP_DIR/Projects_${MODULE_NAME_UPPER}"
fi

위와 같이 백업 디렉토리를 생성하고 백업을 생성, Projects 디렉토리를 백업하는 함수도 만들어두었습니다.

rollback() {
    echo -e "${YELLOW}⏪ 롤백 중...${NC}"

    if [ -d "$BACKUP_DIR" ]; then
        cp "$BACKUP_DIR/Module+Constant.swift" "$CONSTANT_FILE"
        cp "$BACKUP_DIR/Module+TargetID.swift" "$TARGET_ID_FILE"
        cp "$BACKUP_DIR/Module+ProjectID.swift" "$PROJECT_ID_FILE"
        cp "$BACKUP_DIR/TargetDependency+Extension.swift" "$DEPENDENCY_FILE"

        # Projects 디렉토리 복원
        if [ -f "$BACKUP_DIR/module_name.txt" ]; then
            MODULE_NAME=$(cat "$BACKUP_DIR/module_name.txt")
            if [ -d "$BACKUP_DIR/Projects_$MODULE_NAME" ]; then
                cp -r "$BACKUP_DIR/Projects_$MODULE_NAME" "$PROJECT_ROOT/Projects/$MODULE_NAME"
            fi
        fi

        rm -rf "$BACKUP_DIR"
        echo -e "${GREEN}✅ 롤백 완료!${NC}"
    else
        echo -e "${RED}❌ 백업 파일을 찾을 수 없습니다.${NC}"
    fi
}

이제 거의 다 왔는데... 어떻게 실행하죠?

열심히 만들었지만 실행을 위해서는 매번 터미널에서 ./Scripts/create_module.sh 와 같이 파일 경로를 모두 작성해야 했는데요!

이런 귀찮음과 복잡함을 해결하기 위해 Makefile을 추가해서 make module이라는 간단한 명령어로 실행할 수 있게 했습니다.

.PHONY: module module-delete module-rollback generate clean

# 모듈 생성
module:
	@./Scripts/create_module.sh

# 모듈 삭제
module-delete:
	@./Scripts/delete_module.sh

# Tuist generate
generate:
	@tuist generate

# Tuist clean
clean:
	@tuist clean

파일 이름을 찾는 시간 없이 빠르게 make module 명령어 하나면 모듈 스크립트를 생성할 수 있었습니다.

직접 모듈 생성 테스트를 진행한 결과 모듈 생성 시간을 기존 10분에서 30초로 줄이는 성과도 얻을 수 있었습니다!

마무리

프로젝트에 스크립트를 통한 자동화를 도입해 본 경험은 처음이었는데요.

반복적인 작업을 자동화하면 실수도 줄이고, 개발에 더 집중할 수 있다는 점을 깨닫게 되었습니다 ㅎㅎ

프로젝트에 스크립트를 통한 자동화를 도입하실 예정이시라면 이 글이 참고가 되었으면 좋겠습니다! 🙌

참고하실 수 있는 제 PR 링크를 함께 전달드리며 글을 마치겠습니다. 감사합니다!

PR #32: 모듈 생성 및 삭제 자동화 로직 구현