본문 바로가기
iOS/개발

[iOS] 프로젝트 개발 환경 세팅 자동화 with fastlane, Makefile

by 바등쪼 2023. 8. 7.

필요성 및 배경

iOS 프로젝트를 진행하다 보면 github에 올리지 말아야 하는 코드, 파일들이 생기게 됩니다.

API Secret Key, API의 baseURL 등 다양한 데이터들이 해당되며 일반적으로 이 데이터들은 별도의 파일을 만들고 그 파일을 .gitignore에 추가하여 Git 커밋에 포함되지 않도록 합니다.

 

Git의 관리를 받지 않는 파일이 생긴다는 것은 결국 팀원간 파일 공유를 수작업으로 진행해야 한다는 것을 의미합니다.

Git의 도움 없이 슬랙이나 카톡으로 파일들을 주고 받는 경험은 꽤나 귀찮고 번거롭습니다.

파일들을 카톡으로 주고 받는 모습..ㅎㅎ

 

심지어 이러한 비밀 파일(private files)들을 수정하고 팀원들에게 공유하는 것을 까먹는다면 서로 다른 환경 속에서 프로젝트를 빌드, 개발하는 불상사가 발생할 수 있습니다.

 

저도 최근까지 계속 이러한 비밀 파일들을 수작업으로 주고 받았는데 현재 진행중인 프로젝트에서 멋진 팀원 중 한분이 private file을 Github Content API를 사용하여 원격으로 관리하는 기능을 추가해주었습니다.

마치 faslane에서 match를 위한 인증서를 private repo에 올려두고 사용자는 터미널에 명령어를 입력하면 해당 인증서들이 로컬에 설치되는 것처럼 유사하게 동작합니다.

 

이로 인해 private file들을 카톡으로 넘겨줄 필요 없이 수정한 사람이 github private 레포지토리에 push를 하면 다른 사람들은 해당 레포지토리의 파일들을 다운로드 받는 명령어를 입력하면 동기화되는 작지만 매우 유용한 자동화 기능이었습니다.

 

이 기능을 보고 저는 한가지 더 나아가 전체 개발 환경에 필요한 세팅을 도와주는 명령어를 구현해보면 어떨까 생각해보았습니다.

토스의 SLASH 22 영상 에서 신입 사원이 들어왔을 때 명령어 한줄로 스크립트를 실행시키면 필요한 모든 개발 환경 설정이 완료된다는 것을 보고 영감을 받았습니다.

 

나도 토스만큼의 고도화된 세팅은 아니겠지만 개발 환경 세팅을 한줄로 끝내보는 명령어를 만들어보자! 라는 생각으로 작업을 진행해보았습니다.

 

요구사항

프로젝트에 Tuistfastlane이 적용되어 있습니다.

따라서 private 파일들을 설치 받는 것 말고도 추가적인 명령어들을 더 실행시키는 기능을 수행해야 합니다.

  1. private files 다운로드
  2. 개발자의 개인 Apple ID를 입력 받아 저장 (fastlane 사용 위해)
  3. 개발자의 개인 App Password 입력 받아 저장 (fastlane match 사용 위해)
  4. tuist fetch 실행하여 의존성 설치
  5. tuist plugin 설치
  6. tuist generate 실행
  7. match를 통해 인증서 다운로드
  8. 프로젝트 버저닝 세팅

이 작업들이 이루어져야 비로소 온전히 프로젝트를 실행할 수 있는 환경이 구축됩니다. (적다보니 엄청 많았네요 그동안 이 많은 작업을 자동화할 생각을 하지 못했다는 것에 아쉬움이... 지금이라도 해봅시다!)

 

 

구현

1. private file 다운로드 명령어

이 부분은 멋진 팀원분이 작업을 해주셨습니다..!

아래는 요약 부분이며 전체 코드는 링크를 참고해주세요!

Makefile에 구현

# Privates 파일 다운로드

BASE_URL=https://raw.githubusercontent.com/sopt-makers/{Private 파일 레포지토리 주소}

XCCONFIG_PATHS = \
    xcconfigs/Base Common.xcconfig \
    xcconfigs/Base/Projects Project-Development.xcconfig \
    xcconfigs/Base/Projects Project-PROD.xcconfig \
    xcconfigs/Base/Projects Project-QA.xcconfig \
    xcconfigs/Base/Projects Project-Test.xcconfig \
    xcconfigs/Base/Projects Shared.xcconfig \
    # ...생략...
    
define download_file
	mkdir -p $(1)
	curl -H "Authorization: token $(2)" -o $(1)/$(3) $(BASE_URL)/$(1)/$(3)
endef

download-privates:

	# Get GitHub Access Token
	@if [ ! -f .env ]; then \
		read -p "Enter your GitHub access token: " token; \
		echo "GITHUB_ACCESS_TOKEN=$$token" > .env; \
	else \
		/bin/bash -c "source .env; make _download-privates"; \
		exit 0; \
	fi
	
	make _download-privates

_download-privates:
	
	$(eval export $(shell cat .env))
	
	# fastlane .env 설치

	$(call download_file,fastlane,$$GITHUB_ACCESS_TOKEN,.env)
	
	# Config.swift 설치
	
	$(call download_file,Projects/Modules/Network/Sources,$$GITHUB_ACCESS_TOKEN,Config.swift)
	
	# Xcconfigs 설치

	$(eval TOTAL_ITEMS = $(words $(XCCONFIG_PATHS)))
	$(foreach index, $(shell seq 1 2 $(TOTAL_ITEMS)), \
		$(eval DIR = $(word $(index), $(XCCONFIG_PATHS))) \
		$(eval FILE = $(word $(shell expr $(index) + 1), $(XCCONFIG_PATHS))) \
		$(call download_file,$(DIR),$$GITHUB_ACCESS_TOKEN,$(FILE)); \
	)
	
	# GoogleService-info 설치
	
	$(call download_file,Projects/SOPT-iOS/Resources,$$GITHUB_ACCESS_TOKEN,GoogleService-Info.plist)
	$(call download_file,Projects/Demo/Resources,$$GITHUB_ACCESS_TOKEN,GoogleService-Info.plist)
	
	# TestConfig.swift 설치
	
	$(call download_file,Projects/Modules/TestCore/Sources,$$GITHUB_ACCESS_TOKEN,TestConfig.swift)

터미널에서 프로젝트 경로로 이동하고 make download-privates를 입력하면 우리가 gitignore를 했던 파일들이 최신 버전으로 쭉 설치됩니다.

파일들이 차례로 설치되는 과정 캡쳐

curl과 Github Content API를 이용한 방식입니다.

  • curl
    • command line용 data transfer tool
    • 다운로드/업로드 가능
    • Linux, MacOS에는 기본 탑재
    • curl [options...] <url> 형태로 사용
  • Github Content API
    • Rest API를 사용하여 레포지토리에서 Base64로 인코딩된 콘텐츠를 만들고 수정 삭제
    • Github를 원격 저장소처럼 사용 가능
    • https://raw.githubusercontent.com/{owner}/{repo}/{branch}/{file_path} 형태로 사용 

이런식으로 깃허브에 private 레포지토리를 만들고 다운로드 받고자 하는 파일들을 업로드 한 뒤 팀원들을 레포지토리에 초대합니다.

이 레포지토리의 content 주소가 baseURL이 됩니다.

 

  1. download-privates를 실행
  2. 사용자에게 Github Access Token을 입력 받는다.
  3. 이 토큰을 .env에 기록하여 다음번에 실행할 때는 2~3번 과정을 생략하도록 한다.
  4. curl을 통해 github Content API를 호출하여 지정한 경로에 파일들 저장 (필요시 반복문 사용)

흐름을 요약해보았습니다. 물론 코드를 보면 리눅스 명령어가 많기 때문에 더 복잡하게 느껴지지만 반복되는 구조입니다.

 

 

2. 애플 아이디, 앱 암호를 업데이트 하는 명령어 구현

지금까지의 코드들로 인해 private 파일들을 다운로드하는 것 까지 성공했지만 이것만으로는 동작이 불가능합니다.

private 파일중에서 fastlane/.env 파일에는 사용자의 애플 아이디가 들어가야 하는데 설치된 파일에는 디폴트 문자열이 들어가 있기 때문입니다.

 

따라서, 프라이빗 파일들을 다 설치하면 사용자에게 애플 아이디를 입력받아 .env의 적절한 위치에 넣는 로직이 필요했습니다.

+ 앱 암호 또한 사용자마다 다르기 때문에 같이 입력 받도록 했습니다.

 

Makefile에 구현

update_apple_id:
	# Update Apple ID in fastlane/.env
	@if [ -f fastlane/.env ]; then \
		if [ -z "$$email" ]; then \
			read -p "Enter your Apple ID: " email; \
		fi; \
        sed -i '' '1s/.*/APPLE_ID="'"$$email"'"/' fastlane/.env; \
        echo "애플 아이디가 저장되었습니다."; \
		if [ -z "$$app_password" ]; then \
			read -p "Enter your App Password: " app_password; \
		fi; \
        sed -i '' '2s/.*/FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD="'"$$app_password"'"/' fastlane/.env; \
        echo "앱 암호가 저장되었습니다."; \
		cat fastlane/.env; \
    else \
        echo "fastlane/.env 파일이 존재하지 않습니다."; \
    fi

make update_apple_id email={my_id} app_password={your_password} 이렇게 명령어를 입력하면 실행되도록 구현했습니다.

 

구조를 살펴보면 다음과 같습니다.

  1. if문과 -f를 사용하여 우선 fastlane/.env 파일이 로컬에 존재하는지 분기합니다. (쉘 스크립트 if문 옵션)
  2. .env 파일 존재하지 않다면 파일이 존재하지 않는다는 문장을 출력하고 종료합니다.
  3. email 변수를 명령어를 입력할 때 제공하지 않았다면 read 명령어로 받아서 email 변수에 저장
  4. sed 명령어를 이용해 .env 파일의 첫 번째 줄을 사용자가 입력한 이메일로 교체 (sed 명령어)
  5. app_password 변수를 명령어를 입력할 때 제공하지 않았다면 read 명령어로 받아서 app_password 변수에 저장
  6. sed 명령어를 이용해 .env 파일의 두 번째 줄을 사용자가 입력한 앱 암호로 교체
  7. cat으로 변경 사항 출력 후 종료

sed -i 다음에 바로 ''를 붙이고 있는데 다른 sed 예제에서는 없는 문자입니다. 하지만 이걸 추가하지 않으면 macOS에서 제대로 동작하지 않는 문제가 있었어서 추가했습니다.

 

3. regenerate 명령어 구현

Makefile에 구현

regenerate:
	rm -rf **/**/**/*.xcodeproj
	rm -rf **/**/*.xcodeproj
	rm -rf **/*.xcodeproj
	rm -rf *.xcworkspace
	tuist generate

tuist generate를 감싼 명령어입니다.

기존에 생성된 xcodeproj들을 제거하고 새로 생성합니다.

이 명령어는 프로젝트 초기부터 사용중인 명령어입니다.

그냥 tuist generate를 해도 되지만 그렇게 하면 원래 존재하던 프로젝트 파일들과 충돌이 발생하는 경우가 있어서 사용중이었습니다.

 

 

4. 인증서 설치 lane 구현 및 regnerate lane 생성

지금까지는 Makefile에서 구현을 했다면 이제부터 나오는 코드들은 fastlane의 Fastfile에 구현했습니다.

왜냐하면 fastlane의 lane들을 실행시켜야 하며 Makefile은 파일들을 다루는 것에 집중하는 것이 맞다고 판단하였기 때문입니다.

Fastfile에서는 Ruby를 사용해야 합니다.

  ### Match

  desc "Match all code signing"
  lane :match_read_only do
    match(
      type: "appstore",
      app_identifier: ['com.sopt-stamp-iOS.test', 'com.sopt-stamp-iOS.release', 'com.sopt-stamp-iOS.release.WidgetExtension'],
      readonly: true
    )

    match(
      type: "development",
      app_identifier: ['com.sopt-stamp-iOS.test', 'com.sopt-stamp-iOS.release', 'com.sopt-stamp-iOS.release.WidgetExtension'],
      readonly: true
    )
  end

match를 이용해 appstore와 development 타입의 인증서를 다운로드 받는 lane입니다.

 

  desc "Regenerate tuist Projects with the specified version"
  lane :regenerate do |version|

    Dir.chdir("../") do
      sh("make regenerate")
    end

    set_version(version: version[:version])
  end

Makefile의 regenerate를 실행하고 프로젝트의 버전까지 설정해주는 함수 입니다.

 

여기서 호출하고 있는 set_version은 다음과 같습니다.

  desc 'Set Marketing and Build version'
  lane :set_version do |version|
    increment_version_number(
      version_number: version[:version],
      xcodeproj: "./Projects/SOPT-iOS/SOPT-iOS.xcodeproj"
    )
  
    increment_build_number(
      build_number: Time.new.strftime("%Y.%m%d.%H%M"), # 2023.0524.2100
      xcodeproj: "./Projects/SOPT-iOS/SOPT-iOS.xcodeproj"
    )
  end

 

5. 최종 명령어 구현 - start_project

이제 처음 목표했던 개발 환경을 전부 세팅하는 명령어입니다.

이름은 start_project라고 지었습니다.

앞서 만든 명령어들을 조합하여 사용합니다.

 

  desc "Initial Setting For Projects"
  lane :start_project do |version|

    Dir.chdir("../") do
      github_access_token = prompt(text: "Enter your GitHub access token: ")
      sh("make download-privates token=#{github_access_token}")
      apple_id = prompt(text: "Enter your Apple ID: ")
      app_password = prompt(text: "Enter your App Password")
      sh("make update_apple_id email=#{apple_id} app_password=#{app_password}")
      sh("tuist fetch")
      sh("fastlane install_plugins")
    end
    
    regenerate(version: version[:version])

    match_read_only
  end
  1. Dir.chdir("../")를 호출하여 현재 디렉토리 변경 (lane 함수를 실행하면 프로젝트의 경로에서 "원래위치/fastlane"로 이동하기 때문입니다. 다시 프로젝트 경로로 이동시키는 거에요!)
  2. prompt를 활용해 GitHub access token 받기
  3. make download-privates 실행 및 2번에서 받은 토큰 전달
  4. 마찬가지로 Apple 아이디와 App Password를 입력 받기
  5. make update_apple_id 실행 및 4번에서 받은 값들 전달
  6. tuist fetch 실행
  7. fastlane install_plugins 실행
  8. regenerate 실행 및 버전 세팅
  9. 인증서 다운로드

Makefile에 구현한 명령어들 자체적으로도 사용자에게 Github Access Token이나 Email 등을 입력받은 로직이 있지만 추가적으로 넣은 이유는 Fastlane의 lane을 실행하는 환경과 Makefile의 명령어를 실행하는 환경이 달라서 Makefile의 read -p가 여기서는 실행되지 않는 문제가 있었기 때문입니다.

 

 

결과

이제 우리 프로젝트를 clone 받은 새로운 팀원은 터미널에 fastlane start_project를 입력하면 자동으로 모든 과정이 진행되어 개발 환경 구축이 완료되는 것을 확인할 수 있습니다.

 

지금까지는 많은 명령어들을 직접 입력하고 파일들 다운로드 받아 알맞은 위치에 복붙해야 했다면 이제는 그럴 필요가 없어져 실수를 방지하고 추가적으로 발생할 리소스들을 상당히 줄일 수 있습니다.

 

실행 영상

이번 작업을 진행하면서 다양한 것들을 경험해볼 수 있었습니다.

curl과 Github ContentAPI, Makefile, Shell 명령어, Ruby 코드.....

이중에서는 생소한 것들도 많아서 하면서 시행착오를 많이 겪었지만 잘 돌아가는 모습을 보니 정말 뿌듯하네요 ㅎㅎ

 

iOS 개발 뿐만 아니라 이러한 자동화를 위한 코드를 작성하는 것도 큰 즐거움이었던 것 같습니다!!👍

개발 과정에서 생기는 불필요한 시간을 줄이고 생산성을 높이는 데 기여한 것 같아서 새로운 느낌의 즐거움이었네요!

 

[관련 Pull Request]

https://github.com/sopt-makers/SOPT-iOS/pull/261

 

[Feat] #260 - 개발 환경 세팅 자동화 명령어 추가 by lsj8706 · Pull Request #261 · sopt-makers/SOPT-iOS

🌴 PR 요약 🌱 작업한 브랜치 feature/#260 🌱 PR Point 최근에 준호형이 private file들을 쉽게 다운로드 받는 명령어를 추가한 것에 이어서 파편화되어 있는 세팅 명령어들을 한번에 실행하는 lane을 추

github.com

 

댓글