개발

이대로만 하면 iOS 앱을 fastlane과 CircleCI을 이용하여 자동 빌드 및 배포 완성(CI/CD 파이프라인)

소소ing 2024. 3. 12. 15:48
반응형

 

제대로 정리해 놓은 글이 왜 이렇게 없을까?! 

Github Action으로 구성했다가... 무료 200분의 압박으로 CircleCI로 넘어갔다.

CircleCI의 장점을 간략히 정리하면

- 무료시간 빌드 사용 가능 시간이 더 길다.

- 최신 Xcode 지원 (GitHub Action에서 실행되는 MacOS 안 Xcode 14.2가 최신이라니...)

정도가 되겠다. 

 

※ CI/CD 파이프라인이란? 

지속적 통합 (Continuous Integration, CI) 및 지속적 배포 (Continuous Deployment, CD)를 지원하기 위한 자동화된 프로세스를 의미. 

이 파이프라인은 소프트웨어 개발 및 배포 과정을 효율적으로 관리하고, 소프트웨어 제공 속도를 높이고 품질을 향상시키는 데 도움이 된다.

 

환경

  • LOCAL : Mac Mini M1 16GB

 

1. Github에 업로드된 프로젝트를 로컬에서 내려받은 상태에서 fastlane을 연동해 주어야 한다. 

fastlane는 왜 연동해 주어야 하는걸까?

- fastlane은 iOS App Build시 필요한 대다수 모든 절차를 간편하고 쉽고 연동할 수 있게 해주기 때문이다. 

- 자동 빌드 배포를 구성하려고 한다면 되도록 사용을 권장한다!

// 터미널에서 아래 명령어로 설치 가능
brew install fastlane
 

만약 M1 칩으로 인한 오류가 발생 시 아래 명령어로 대체 해서 해보자!

arch -arm64 brew install fastlane
 

 

2. fastlane이 설치가 된 상태에서 프로젝트 폴더로 이동 후 터미널 실행해서 아래 명령어 입력

fastlane init
 

이런 형태로 출력 되면 4번을 선택하자! 

(이후 무언가 절차가 더 있다면 아무렇게 해도 무관하다... 아래 파일들 직접 만들거나 내용 수정하면 동일하기 때문!)

 

3. fastlane init가 완료 되면 프로젝트 폴더 안 fastlane 폴더가 생성된다. 기본으로 Appfile과 Fastfile이 자동으로 생성된다. 

프로젝트 디렉터리 안에 Gemfile도 생성된다. 

- Appfile은 따로 내용을 적어 줄 필요는 없다. 

- Fastfile 안 내용은 다음과 같이 구성한다. 

# CircleCI에서 설정한 변수를 그대로 가져오지 못하는 케이스가 있어 base64 인코딩/디코딩을 활용한다.
require 'base64'

# iOS 플랫폼을 선언 
default_platform(:ios)

# platform 다시한번 선언  
platform :ios do
  # 해당 작업에 대한 설명 작성 
  desc "Push a new version build to Distribution"
  # lane : 다음에 오는 release 부분이 추후 do 아래 항목을 실행시키는 명령어로 사용된다.
  lane :release do 
    # Base64.decode64를 이용해서 각 환경 변수를 가져오는 부분 환경 변수 설정은 아래 부분에서 참고하자.
    #    ※ 4. (1) 부분에서 설명 ※ 
    # 그리고 strip를 사용하지 않으면 간혹 개행 공백 문자가 포함되어 러닝 오류가 발생되니 넣어주자.
    # 왜 Base64를 사용하는지는 CircleCI 환경변수 관련 부분에서 참고하자. 
    decoded_API_KEY_CONTENT = Base64.decode64(ENV['API_KEY_CONTENT']).strip
    decoded_API_KEY_NAME = Base64.decode64(ENV['API_KEY_NAME']).strip
    decoded_API_KEY_ISSUER = Base64.decode64(ENV['API_KEY_ISSUER']).strip
    decoded_FIREBASE_APP_ID = Base64.decode64(ENV['FIREBASE_APP_ID']).strip
    decoded_FIREBASE_TOKEN = Base64.decode64(ENV['FIREBASE_TOKEN']).strip
    # puts을 통해 제대로 디코딩 되었는지 확인 (이 부분은 제거해도 무관하다.)
    puts "API_KEY_CONTENT: #{decoded_API_KEY_CONTENT}"
    puts "API_KEY_NAME: #{decoded_API_KEY_NAME}"
    puts "API_KEY_ISSUER: #{decoded_API_KEY_ISSUER}"
    puts "FIREBASE_APP_ID: #{decoded_FIREBASE_APP_ID}"
    puts "FIREBASE_TOKEN: #{decoded_FIREBASE_TOKEN}"

    # fastlain에서 제공하는 app_store_connect_api_key 함수를 이용하여 
    # 앱스토어 커넥트에 로그인 할 수 있는 
    # api_key를 구성한다. 필요한 이유와 각 키를 구하는 방법은 아래에서 설명한다. 
    #    ※ 4. (1) 부분에서 설명 ※ 
    api_key = app_store_connect_api_key(
      key_id: decoded_API_KEY_NAME,
      issuer_id: decoded_API_KEY_ISSUER,
      key_content: decoded_API_KEY_CONTENT,
      duration: 1200,
      in_house: false
    )

    # 인증서(.p12) 및 프로비저닝 프로파일(.mobileprovision) 파일의 경로 설정
    # 여기서 인증 서 및 프로비저닝 프로파일은 git 프로젝트와 동일 선상에 푸시되어 있어야 한다!
    cert_path = "<p12 cert 파일 경로 및 이름 지정>.p12"
    cert_password = "<p12 파일 추출 시 비밀번호 입력>"
    profile_path_ios = "<target 1 provision 파일 경로 및 이름 지정>.mobileprovision"
    profile_path_widget = "<target 2 provision 파일 경로 및 이름 지정>.mobileprovision"

    # CI/CD 파이프라인에서 사용되어 빌드 및 배포 프로세스에서 보안 인증서, 프로비저닝 프로필 
    # 및 기타 보안 관련 정보를 관리하는 데 사용하는 용도로 keychain을 생성한다.   
    create_keychain(
      name: "<키체인 이름 지정>",
      password: "<키체인 비밀번호 지정>",
      default_keychain: true,
      unlock: true,
      timeout: 3600,
      lock_when_sleeps: false
    )
    
    # import_certificate 함수를 이용하여 특정 인증서(.p12 파일 형식)를 시스템의 키체인에 
    # 추가하는 데 사용한다.
    import_certificate(
      certificate_path: cert_path,
      certificate_password: cert_password,
      keychain_name: "<create_keychain으로 생성한 이름>",
      keychain_password: "<create_keychain으로 생성한 비밀번호>"
    )

    # 프로비저닝 프로파일 설치하기 target 다중일 경우 profile_path_를 여러개 만들어 
    # 각 프로비저닝 프로파일을 설치한다. (Mac에서 더블 클릭과 같은 효과!?)
    install_provisioning_profile(
      path: profile_path_ios
    )
    install_provisioning_profile(
      path: profile_path_widget
    )

    # CocoaPods 종속성 설치 아래 두 경우 중 하나만 기입하자.
    # - 단순 설치 시 pod install에 해당 
    cocoapods
    # - pod update 를 원할 경우
    podfile(
      update: true
    )

    # 실제 Git에 올라가 있는 코드를 기준으로 앱을 빌드한다. 
    # - export_method : "app-store" 는 배포용 빌드 옵션이다.
    # - export_options 안 provisioningProfiles  : Xcode 빌드 시스템에 앱 빌드 및 
    #   서명을 위해 사용할 프로비저닝 프로파일을 지정하는 데 사용되는 옵션이다.
    build_app(
      scheme: "<App Target 이름>",
      export_method: "app-store", # 필요한 다른 옵션 추가
      export_options: {
        provisioningProfiles: {
          "<Terget Identifier 1>": "<Profile Name 지정1>", 
          "<Terget Identifier 2>": "<Profile Name 지정2>"
        }
      }
    )

    # 내부 테스트 용으로 테스트만 할 경우 Firebase Appdistribution으로 배포 후 슬랙으로 안내하고
    # 배포 용일 경우 testflight로 업로드 하도록 구성한다.
    # - ONLY_TEST 변수는 CircleCI 설정 파일에서 넘겨준다.
    # - skip_waiting_for_build_processing 옵션을 넣으면 TestFlight에 앱이 올라 올때 까지 
    #   대기하지 않는다.
    if ENV['ONLY_TEST'] == 'false'
      # upload_to_testflight만 해줘도 심사 요청이 가능했다.
      upload_to_testflight(
        skip_submission: true,
        api_key: api_key,
        skip_waiting_for_build_processing: true
      )
      # upload_to_app_store(
      #   skip_metadata: true,
      #   skip_screenshots: true,
      #   api_key: api_key,
      #   skip_waiting_for_build_processing: true
      # )
    else 
      # 가장 마지막 Git 커밋 메시지 가져오기
      commit_message = last_git_commit[:message]

      # firebase_app_distribution 함수를 이용하여 Firebase AppDistribution으로 자동 배포
      # - release_notes 부분에 깃에서 가져온 commit_message를 입력한다.
      # - 각 변수 값을 가져오는 방법은 아래에서 설명
      #    ※ 4. (1) 부분에서 설명 ※ 
      firebase_app_distribution(
        app: decoded_FIREBASE_APP_ID,
        groups:"<그룹 이름1>, <그룹 이름2>",
        release_notes: "#{commit_message}",
        firebase_cli_token: decoded_FIREBASE_TOKEN
      )
 
      # 1.0.0 형태로 나오는 버전 정보를 가져온다. 
      version_number = get_version_number(
        xcodeproj: "ios.xcodeproj",
        target: "ios"
      )
      # Xcode - Target - General - Build 값을 가져온다.
      build_number = get_build_number(
        xcodeproj: "ios.xcodeproj"
      )
      # build_number대신 Info.plist 안 BundleVersion으로 사용을 원할 경우 아래 부분으로 사용가능
      bundle_version = get_info_plist_value(
        path: "ios/Info.plist",
        key: "CFBundleVersion"
      )
      # 위에서 가져온 버전 정보 및 내용을 slack_url (slack web hooks)를 이용하여 전송한다. 
      # - payload에 "version" 항목을 넣게 되면 슬랙에서 버전 항목이 추가되어 별도로 표시된다.
      #   아래에서 확인하자.
      #    ※ 4. (2) 부분에서 설명 ※ 
      slack(
        message: "새로운 앱이 Firebase AppDistribution으로 배포 되었습니다.",
        slack_url: "<hooks.slack.com 해당 값 넣기>", 
        payload: {
          "version": "v#{version_number} (#{build_number})"
        }
      )
    end
  end
 

Fastfile에서 추후 확인해야 할 것은 뒤에서 차차 진행하도록 하며 다음 파일내용을 설명한다. 

 

- Gemfile 안 내용은 다음과 같이 구성한다. (만약 없다면 파일을 그냥 만들면 된다.)

source "https://rubygems.org"

gem "fastlane"

# 위 2줄은 기본으로 생성이 되었을 거다 아래 cocoapods gem 을 명시적으로 추가 하자
gem 'cocoapods'
# Firebase app distribution 플러그 인 설치하기 부분이다.
plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile')
eval_gemfile(plugins_path) if File.exist?(plugins_path)
 

Gemfile 안에서 Firebase app distribution 플러그인을 설치 했기 때문에 프로젝트 폴더 안 fastlane 폴더 안에 Pluginfile을 만들어 아래와 같이 넣어주자!

 

- Pluginfile 안 내용은 다음과 같이 구성한다.

# Firebase app distribution 플러그 인 명시
gem 'fastlane-plugin-firebase_app_distribution'
 

 

이렇게 구성하면 프로젝트 폴더 안 fastlane 폴더 안에는 아래와 같이 구성되어 있어야 한다.

ls -al fastlane/
total 56
drwxr-xr-x@  8 hyeonggyulim  staff   256  3  6 11:13 .
drwxr-xr-x@ 20 hyeonggyulim  staff   640  3  6 11:13 ..
-rw-r--r--@  1 hyeonggyulim  staff   242  3  4 16:12 Appfile
-rw-r--r--@  1 hyeonggyulim  staff  4310  3  6 15:55 Fastfile
-rw-r--r--@  1 hyeonggyulim  staff   131  3  5 14:54 Pluginfile
 

 

4. 그럼 이제 Fastfile에서 아래에서 설명한다는 부분을 하나씩 살펴보자.

(1) Fastfile에서 사용하는 각 변수를 구하는 방법을 알아보자! 

※ 각 변수의 실제 할당은 CircleCI 또는 GitHub Action 이용시 각 환경에 맞는 환경변수로 등록해야 하기 때문에 뒤에서 다시 안내한다.

※ 보안을 위해 Base64 디코딩을 하였다. 이 부분은 CircleCI 환경변수 등록 부분에서 설명하며, Base64 디코딩은 하지 않아도 무관하다. 

    # Base64.decode64를 이용해서 각 환경 변수를 가져오는 부분 환경 변수 설정은 아래 부분에서 참고하자.
    # 그리고 strip를 사용하지 않으면 간혹 개행 공백 문자가 포함되어 러닝 오류가 발생되니 넣어주자.
    decoded_API_KEY_CONTENT = Base64.decode64(ENV['API_KEY_CONTENT']).strip
    decoded_API_KEY_NAME = Base64.decode64(ENV['API_KEY_NAME']).strip
    decoded_API_KEY_ISSUER = Base64.decode64(ENV['API_KEY_ISSUER']).strip
    decoded_FIREBASE_APP_ID = Base64.decode64(ENV['FIREBASE_APP_ID']).strip
    decoded_FIREBASE_TOKEN = Base64.decode64(ENV['FIREBASE_TOKEN']).strip
    # puts을 통해 제대로 디코딩 되었는지 확인 (이 부분은 제거해도 무관하다.)
    puts "API_KEY_CONTENT: #{decoded_API_KEY_CONTENT}"
    puts "API_KEY_NAME: #{decoded_API_KEY_NAME}"
    puts "API_KEY_ISSUER: #{decoded_API_KEY_ISSUER}"
    puts "FIREBASE_APP_ID: #{decoded_FIREBASE_APP_ID}"
    puts "FIREBASE_TOKEN: #{decoded_FIREBASE_TOKEN}"
 

> app_store_connect_api_key 를 구하는데 사용되는 변수 API_KEY_NAME / API_KEY_ISSUER / API_KEY_CONTENT 3가지를 알아보자.

app_store_connect_api_key가 먼저 왜 필요할까? 

app_store_connect_api_key가 필요한 이유는 각 단계에서 애플에 로그인이 필요한 단계에서 기존 애플 ID / Password로 로그인을 하게 되면 이중인증이 발생하기 때문에 인중인증 없이 로그인을 하기위한 용도로 생각하면 된다.

fastlane match를 통한 인증서 관리 / Testflight 자동 배포 / AppStore 업로드 시 사용하게 된다. 

※ 여기서는 match를 사용하지 않고 인증서 & 프로비저닝프로파일을 깃에 업로드 하여 사용하는 방식을 이용했다. (match 사용을 원할 경우 fastlane Matchfile을 구성해 주어도 좋다)

※ 이 방법으로 진행을 할 경우 FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD를 이용한 인증 방식은 이용하지 않아도 된다! 

※ 또한 app_store_connect_api_key를 이용하는 방법이 보다 안전한 방법이라고 한다!

 

 

[API_KEY_NAME / API_KEY_ISSUER / API_KEY_CONTENT 발급 절차]

Step1. https://appstoreconnect.apple.com 사이트로 이동하여 애플 계정으로 로그인 한다. 

(⚠️ 앱 소유자 계정으로 해야 한다.)

Step2. 사용자 및 액세스 → 통합 → App Store Connect API 화면으로 이동하면 Issuer ID가 먼저 보인다. 

이 값을 API_KEY_ISSUER 변수에 할당할 예정이니 기록!

Step3. 위 화면 아래 + 버튼을 클릭하여 API 키를 생성한다. 

식별하기 편한 이름을 적고 액세스 권한은 관리자로 지정한다.

생서 되면 API 키 ID가 만들어지는데 이 값을 API_KEY_NAME으로 할당 예정이니 기록!

 

Step4. 만들고 나면 아래 화면 밑에 .p8 파일을 다운로드 할 수 있는 버튼이 있다. 이 파일을 다운받는다.

⚠️ 단, 최초1회 다운 후 더 이상 다운로드가 불가능하니 파일을 잘 관리하자!

Step5. 파일을 열어서 나오는 전체 문자를 API_KEY_CONTENT로 할당할 예정이니 기록!

대략 아래와 같은 행태이기 때문에 -----BEGIN PRIVATE KEY----- 부분에서 -----END PRIVATE KEY----- 부분까지 전체다 사용되어야 한다.

-----BEGIN PRIVATE KEY-----
.... 생략 ....
-----END PRIVATE KEY-----
 

 

> FIREBASE_APP_ID와 FIREBASE_TOKEN을 구하는 방법을 알아보자!

자동 빌드 후 Firebase AppDistribution으로 자동 배포를 하고 싶은 경우라면 이 두가지 값을 구해 주어야 한다. 

만약, Firebase AppDistribution으로 배포하지 않고 TestFlight로 테스트 하는 경우라면 이 부분은 생략하자!

 

[FIREBASE_APP_ID 발급 절차]

Step1. https://console.firebase.google.com 으로 이동하여 내 프로젝트로 들어간 후 왼쪽 메뉴바에서 프로젝트 개요 옆 설정 버튼을 클릭 하자!

Step2. 설정 버튼 클릭 해서 노출되는 하위 메뉴들 중 프로젝트 설정 메뉴를 클릭 한다.

Step3. 프로젝트 설정 화면 일반 탭 제일 하위에 보면 내 앱 부분이 있다. 여기서 자동 빌드를 구성할 타겟을 선택하자.

Step4. 내 앱 부분 타켓을 선택하면 SDK 설정 및 구성 부분 아래에 앱ID 라고 표시된 부분이 있다. 

이 값을 FIREBASE_APP_ID 변수로 할당할 예정이다.

 

[FIREBASE_TOKEN 발급 절차]

Step1. Firebase Token을 얻기 위해서는 Firebase CLI를 설치해야 한다. 터미널 실행 후 아래 명령어를 입력하자.

npm install -g firebase-tools
 

Step2. 설치 후 아래 명령어를 통해 firebase에 로그인 해주어야 한다. 

firebase login
 

Step3. 위 명령어 입력 시 Allow Firebase to collect CLI and Emulator Suite usage and error reporting information? 질문이 뜨며 n 선택 후 Waiting for authentication... 표시와 함께 GUI로 브라우저가 뜨게 된다. 

브라우저에서 Firebase 프로젝트 접근 권한이 있는 계정으로 로그인 하자.

Step4. 로그인이 완료 되면 Success! Logged in as <로그인한 이메일 주소> 로 표시가 뜨게된다. 

이후 터미널에 아래 명령어를 입력하자.

cat ~/.config/configstore/firebase-tools.json
 

Step5. motd, usage, user, tokens 정보가 나오며 tokens 부분 refresh_token 값을 FIREBASE_TOKEN 변수로 할당할 예정이니 저장해 놓자!

 

 

(2) Fastfile에서 사용하는 slack 연동 방법을 알아보자! 

      # 위에서 가져온 버전 정보 및 내용을 slack_url (slack web hooks)를 이용하여 전송한다. 
      # - payload에 "version" 항목을 넣게 되면 슬랙에서 버전 항목이 추가되어 별도로 표시된다.
      #   아래에서 확인하자.
      slack(
        message: "새로운 앱이 Firebase AppDistribution으로 배포 되었습니다.",
        slack_url: "<hooks.slack.com 해당 값 넣기>", 
        payload: {
          "version": "v#{version_number} (#{build_number})"
        }
      )
 

> Fastlane에서 slack 함수를 기본으로 제공하기 때문에 별도 추가 연동 없이 사용이 가능하다. 

> slack 함수에서 필요한 부분은 slack_url로 보통 slack 웹훅 url 이라고 많이 이야기 한다. 

 

[slack_url 발급 절차]

Step1. https://api.slack.com 사이트로 이동 후 로그인 한다. 

Step2. 오른쪽 상단 Your apps에서 앱을 하나 새로 만든다. 

Step3. 앱을 만든 후 해당 앱을 선택하여 들어가면 왼쪽 메뉴가 아래 처럼 표시되며, Features 부분의 Incoming Webhooks 메뉴를 선택하자.

Step4. Incoming Webhooks 로 이동 후 제일 하단 Webhook URL 부분 Add New Webhook to Workspace를 선택한다.

Step5. Add New Webhook to Workspace 클릭 시 Slack API에 만든 앱과 Slack의 어느 채널로 연동할지 창이 뜨며, 어느 채널로 메시지를 보낼지 선택하고 허용을 누르자.

Step6. 허용 후 생성되는 Webook URL을 Fastfile 안 slack 부분 slack_url 에 넣어주면 된다. 

      # 1.0.0 형태로 나오는 버전 정보를 가져온다. 
      version_number = get_version_number(
        xcodeproj: "ios.xcodeproj",
        target: "ios"
      )
      # Xcode - Target - General - Build 값을 가져온다.
      build_number = get_build_number(
        xcodeproj: "ios.xcodeproj"
      )
      # build_number대신 Info.plist 안 BundleVersion으로 사용을 원할 경우 아래 부분으로 사용가능
      bundle_version = get_info_plist_value(
        path: "ios/Info.plist",
        key: "CFBundleVersion"
      )
      slack(
        message: "새로운 앱이 Firebase AppDistribution으로 배포 되었습니다.",
        slack_url: "<hooks.slack.com 해당 값 넣기>", 
        payload: {
          "version": "v#{version_number} (#{build_number})"
        }
      )
 

 

[slack 예시 화면]

iOS_Distribution 이란 이름과 그 앞 아이콘은 앞서 '[slack_url 발급 절차] Step2' 에서 생성한 앱의 이름과 아이콘이 자동으로 표시된다. 

※ 이미 슬렉으로 전송된 메시지 라고 하더라도 생성한 앱 이름과 아이콘을 바꾸면 전송된 메시지에도 일괄적으로 변경된다. 

- payload 배열 안에 version으로 할당해 주면 아래 이미지 처럼 버전 부분이 들어간 것을 볼 수 있다. 

- Git Commit를 보여주기 때문에 push 할때 내용을 잘 작성하면 테스터들에게 별도로 메시지를 주지 않아도 될듯 하다. 

 

 

5. fastlane 관련 설명 부분

(1) fastlane을 이용한 iOS 프로비저닝 관리

> 자동 빌드 배포를 위해서는 Xcode 프로젝트 안 Targets -> Signing & Capabilities 부분의 Automatically manage signing 체크가 해제가 된 상태여야 하며, Git에 업로드 시 해제 된 상태로 올라가 있어야 한다. 

나는 수동으로 인증서와 프로비저닝을 만든 후 Git에 업로드 하여 CI/CD 환경에서 사용하도록 구성했다.

> 왜 Automatically manage signing 체크가 해제가 된 상태여야 할까? 

Automatically manage signing 옵션이 켜져 있으면 Xcode가 코드 사이닝 및 프로비저닝 프로파일을 자동으로 관리하기 때문에 Fastlane은 Fastfile에 정의된 코드 사이닝 및 프로비저닝 관련 명령을 무시하고 Xcode에서 설정한 값들을 사용하게 되어 빌드 오류가 발생되기 때문이다. 

⚠️ 여기서 주의할 부분은 Distribution 배포를 자동화 할 경우 Git에 업로드 되어 있는 .xcodeproj 파일 안 Signing 부분에 Automatically manage signing 해제 후 실제 배포에 사용하는 프로비저닝으로 선택되어 있는 상태로 git에 올려야 한다! (다른 Profile이 선택된 상태로 Git에 올라갈 경우 CI/CD 빌드는 error 65가 발생)

 

[Fastlane을 통한 프로비저닝 관리 방법]

Step1. 먼저 프로젝트 폴더 안 cert 폴더를 하나 만들자! (폴더 이름은 임의로 만들어도 무방하다)

Step2. 프로젝트 폴더 안 cert 폴더 안에 인증서와 프로비저닝 프로파일을 생성하여 넣어둔다. 

.p12 파일과 .mobileprovision 파일로 구성되어 있다.

⚠️ 인증서(.p12) 파일 추출 시 지정한 비밀번호를 잘 기억했다가 앞서 작성하라고 안내된 Fastfile 안 cert_password 의 값에 넣어 주어야 한다.

※ 여기서 잠깐! 인증서와 프로비저닝프로파일 생성 방법을 모른다면 아래('인증서 및 프로비저닝 생성 방법')에서 확인하자!

 

Step3. 프로젝트를 Xcode로 실행하여 빌드 대상 모든 타겟에 대해 Provisioning Profile을 Step2에서 저장한 Provisioning Profile로 수동 설정한다.

사진 삭제

사진 설명을 입력하세요.

Step4. 수정 후 해당 내용을 Git에 배포하면 Fastlane을 통해 인증서 연동이 완료 된다.

 

 

[인증서 및 프로비저닝 생성 방법]

> 먼저 인증서 생성의 경우 Xcode를 통해 손쉽게 생성이 가능하다!

Step1. Xcode 실행 후 상단 Xcode 메뉴를 클릭하여 Settings 메뉴로 이동한다. 

Step2. Settings 화면 Accounts 화면에 나의 애플 계정을 로그인 하고 오른쪽 AppID 영역 아래 Team 부분에서 인증서를 생성할 Team 이름을 더블 클릭하자! 

Step3. Signing certificates for 화면이 나오면 왼쪽 하단 + 버튼을 눌르자!

Step4. + 버튼을 통해 출력되는 메뉴 중 Apple Distribution 항목을 눌러주자!

Step5. 이후 키체인 접근 앱을 실행하여 인증서 부분에 생성한 Apple Distribution을 찾은 후 화살표를 펼친다.

※ 여기서 잠깐! 키체인 접근 인증서 부분에 생성한 인증서가 없다면 https://developer.apple.com/account/resources/certificates/list 로 이동하여 생성한 인증서 파일을 수동으로 다운 받고 더블클릭 해주면 된다!

Step6. 펼쳐진 상태에서 두개 항목을 선택하고 마우스 우클릭 시 노출되는 2개 항목 내보내기를 통해 .p12 인증서 파일을 내려 받을 수 있다! 

이때 비밀번호를 요구하게 된다. 

 

> 그 다음 프로비저닝 프로파일 생성방법을 알아보자!

Step1. Apple developer 사이트 안 프로파일 항목으로  https://developer.apple.com/account/resources/profiles/list 이동한다!

Step2. Profiles 옆 + 버튼으로 각 타겟에 맞는 Profile을 만들고 다운받아주면 된다. 

 

 

6. 사전 준비가 끝났다면 CircleCI 구성을 알아보자

(1) CircleCI 사이트 연동

> CircleCI는 지속적 통합 (Continuous Integration, CI) 및 지속적 배포 (Continuous Deployment, CD)를 지원하는 클라우드 기반의 자동화된 소프트웨어 개발 플랫폼이다. 

> GitHub Action과 기능이 유사하지만 무료로 제공되는 사용시간이 더 길며, 최신 Xcode 버전을 지원하는 장점이 있어서 CircleCI로 구성했다. 

 

[CircleCI 기본 구성 하기]

Step1. https://circleci.com 사이트에서 가입 및 기업을 생성한다.

Step2. 기업 등록 후 앱을 생성할때 소스를 가져올 저장소(GitHub)를 선택하여 레파지토리를 지정한다. 

Step3. 첫 등록 시 https://app.circleci.com/pipelines/circleci/ CircleCI 대시보드에서 자동으로 config 구성을 하게 되지만 무시해도 된다. 

Step4. 여기까지 했으면 기본 구성은 끝!

 

 

[CircleCI 프로젝트 안 사용 가능한 환경 변수 등록하기]

> CircleCI를 통해 Fastlane을 실행하는 구조이기 때문에 CircleCI 환경 변수를 등록하여 Fastlane에서 사용할 수 있도록 해야 한다. 

Step1. CircleCI 대시보드에서 우측 상단 Project Settings로 이동한다.

 

Step2. Project Settings 화면 우측 메뉴에서 Environment Variables를 선택한다. 

Step3. Environment Variables 화면에서 앞서 구한 변수의 base64 인코딩 값을 넣어준다. 

⚠️ 값을 그대로 넣어주어도 되지만 안전을 위해 Base64 인코딩을 권장한다.

 

Step4. base64 인코딩 값을 구하는 방법은 아래 명령어를 이용하자!

// 특정 파일을 인코딩 할 때 아래 명령어로 인코딩된 .txt 파일을 만들어 Step3 각 변수에 입력하자
base64 < '파일경로 및 이름.p8' > '파일경로 및 이름_base64.txt'

// 특정 문자열을 인코딩 할 때는 아래 명령어를 사용하자!
echo '문자열' | base64
 

 

 

(2) CircleCI 설정하기

> CircleCI 동작을 위해서는 <GitHub 레파지토리>/.circleci/config.yml 파일이 해당 위치에 존재 하여야 한다. 

> 여기서는 해당 파일에 대해서 알아보자!

 

[config.yml 구성 하기]

Step1. <GitHub 레파지토리>/.circleci/config.yml 파일을 생성한다. (GitHub Add file 사용)

※ 자동 빌드를 구성하고자 하는 브런치 경로에서 만들어야 한다. 

Step2. config.yml 파일 내용은 아래와 같다.

# CircleCI 버전 2.1 사용
version: 2.1

# ruby orb: 이 orb는 Ruby 프로젝트를 위한 구성을 제공 
# - circleci/ruby@1.1.2 버전의 orb를 사용하고 있으며, Ruby 프로젝트를 위한 환경 및 도구를 설정. 
# - 이 orb를 사용하면 Ruby 프로젝트를 빌드하고 테스트하는 데 필요한 구성을 간편하게 사용할 수 있다.
# macos orb: 이 orb는 macOS 환경에서 작업하는 데 필요한 구성을 제공
# - circleci/macos@2.4.1 버전의 orb를 사용하고 있으며, macOS 환경에서 실행되는 작업을 설정. 
# - 이 orb를 사용하면 macOS 환경에서 빌드 및 테스트를 수행하는 데 필요한 구성을 간편하게 사용할 수 있다.
orbs:
  ruby: circleci/ruby@1.1.2
  macos: circleci/macos@2.4.1

jobs:
  build:
    macos:
      # 사용할 Xcode 버전 지정 
      xcode: "15.0.0"
    environment:
      # FL_OUTPUT_DIR: 이 환경 변수는 출력 디렉토리를 설정. 
      # - output으로 설정되어 있으므로, 프로젝트에서 생성된 출력 파일이나 결과물은 output 디렉토리에 저장.
      # LC_ALL 및 LANG: 이 두 환경 변수는 로케일 설정을 지정. 
      # - en_US.UTF-8으로 설정되어 있으므로, 프로세스의 언어 및 문자 인코딩이 영어(미국)로 설정되고 UTF-8로 인코딩. 
      # - 이는 보통 다국어 프로젝트에서 특정 언어 및 문자 인코딩을 사용할 때 사용.
      FL_OUTPUT_DIR: output
      LC_ALL: en_US.UTF-8
      LANG: en_US.UTF-8
    steps:
      # git code checkout
      - checkout
      # feature/v로 시작하는 모든 브런치에서 Info.plist와 Prefix.swift 변경 여부를 확인하여 
      # - Firebase AppDistribtuion으로 배포할지, TestFligth로 배포 할지 결정하는 로직  
      - run:
          name: Check if Info.plist has changed on feature/v* branches
          command: |
            # 가장 마지막으로 커밋한 정보를 가져온다. 
            PREVIOUS_COMMIT=$(git log -n 2 --pretty=format:"%H" | tail -n 1)
            # 변경된 파일 리스트를 가져온다.
            changed_files=$(git diff $PREVIOUS_COMMIT...$CIRCLE_BRANCH)
            echo "Changed files:"
            echo "$changed_files"
            # feature/v로 시작하는 브랜치인지 확인
            if [[ $CIRCLE_BRANCH == feature/v* ]]; then
              echo "This is a feature/v* branch."
              # Info.plist 파일이 마지막 커밋에서 변경되었는지 확인
              if git diff --name-only $PREVIOUS_COMMIT...$CIRCLE_BRANCH | grep 'ios/Info.plist'; then
                echo "ios/Info.plist has changed."
                # Prefix.swift 파일이 마지막 커밋에서 변경되었는지 확인  
                if git diff --name-only $PREVIOUS_COMMIT...$CIRCLE_BRANCH | grep 'Prefix.swift'; then
                  echo "Prefix.swift has changed."
                  # 두개 파일 모두 변경 시 테스트 모드 false
                  echo 'export ONLY_TEST=false' >> $BASH_ENV
                else
                  # ios/Info.plist 만 변경 시 테스트 모드 true
                  echo "Prefix.swift has not changed."
                  echo 'export ONLY_TEST=true' >> $BASH_ENV
                fi
              else
                echo "ios/Info.plist has not changed. Stopping the job."
                # 둘다 변경되지 않으면 circleci 종료
                circleci step halt
              fi
            else
              echo "This branch does not match the pattern 'feature/v*'. Stopping the job."
              # 브런치가 feature/v 시작하지 않으면 circleci 종료
              circleci step halt
            fi
      # restore_cache: 이 단계는 이전에 캐시된 의존성을 복원
      # - Gemfile.lock 파일의 체크섬을 기반으로 이전에 캐시된 버전을 찾아 사용
      - restore_cache:
          key: v1-gems-{{ checksum "Gemfile.lock" }}
      - run:
          name: Install Bundler
          command: gem install bundler
      - run:
          name: Bundle Install
          command: bundle install --path vendor/bundle
      # 설치된 종속성을 캐시로 저장. vendor/bundle 디렉토리의 내용을 캐시로 저장. 
      # - 이후 빌드에서 같은 종속성을 다시 설치할 필요가 없도록 캐시
      - save_cache:
          paths:
            - vendor/bundle
          key: v1-gems-{{ checksum "Gemfile.lock" }}
      # 앞서 프로젝트에 연동한 fastlane 실행 부분 
      # - fastlane/Fastfile 안 구문 동작 
      - run:
          name: Run Fastlane
          command: bundle exec fastlane release

workflows:
  version: 2
  build_and_test:
    jobs:
      - build:
          # filters 옵션을 사용하여 특정 브런치 동작 수행 가능 
          filters:
            branches:
              only:
                - /^feature\/v.*/
 

※ workflows의 filters를 이용하여 특정 브런치로 제한 할 수 있지만 파일 변경 등에 대한 부분은 - run: 구간으로 작성해 주어야 했다.

 

 

7. CircleCI 동작 확인 

 

 

반응형