관리 메뉴

김종권의 iOS 앱 개발 알아가기

[fastlane] 4. build_app (빌드, 배포, firebase에 배포, firebase_app_distribution, 빌드 번호 증가) 본문

iOS 앱 배포와 출시

[fastlane] 4. build_app (빌드, 배포, firebase에 배포, firebase_app_distribution, 빌드 번호 증가)

jake-kim 2020. 12. 12. 13:11

1. fastlane이란?

2. Bundler란? cocoapod 동기화 방법?

3. fastlane match (certificate, provisioning profile 정보를 git에 저장)

4. fastlane build_app (빌드, firebase에 배포)

5. fastlane 앱 스토어에 배포 (App Store Connect)

6. fastlane register devices, 디바이스 정보(UDID, Name) Apple Developer 등록 방법 (register_devices)

*7. fastlane 총 정리 및 phase별 configuration 설정, 환경변수 설정

*8. fastlane Bitrise 이용한 자동 배포 구축 방법

 

cf) fastlane 환경 변수 (.env.default) 사용하여 가장 단순한 match 사용 방법


빌드 후 배포 과정

  • fastfile에 각각 alpha, release라는 lane을 정의하여 사용
  • 각 lane은 우선 sync_codesign을 불러서 인증서를 획득하고 빌드를 수행 -> .ipa파일과 dSYM파일 획득 -> 이 파일들을 배포
  • .ipa파일이란? iOS 디바이스에서만 설치할 수 있는 ARM아키텍쳐 용 바이너리 파일이 압축 된 파일
  • dSYM(debug symbol file)파일이란? 컴파일러가 소스코드를 기계어로 변환할 때 생성되고 기계어를 다시 매핑하는 정보를 지닌 파일
    xcode에서 archive를 하면 생성되는데, 앱스토어에서 크래시 리포트를 줄 때 이 파일을 이용하여 해석할 수 있음

fastlane build_app을 이용하여 빌드

  • 공통 빌드 lane 정의
    * 먼저 여기를 참고하여 match관련 lane정의

  • 각 빌드 lane 정의

  • 아래와 같은 오류 발생 시 여기 참고하여 pod 설정

  • 전체 코드 (주의사항: build_bump가 동작하려면 xcode에서 또다른 설정 필요 - 아래 내용에서 계속)
default_platform(:ios)

ENV["FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD"] = "xtjt-hxui-nyix-bsnm"

platform :ios do
  
  # match 갱신 - git에 push (certificate, profile visioning)

  private_lane :renew_codesign do|options|
    match(type:options[:type], force_for_new_devices: true)
  end

  lane :renew_codesign_debug do
    renew_codesign(type:"development")
  end

  lane :renew_codesign_alpha do
    renew_codesign(type:"adhoc")
  end

  lane :renew_codesign_beta do
    renew_codesign(type:"adhoc")
  end

  lane :renew_codesign_release do
    renew_codesign(type:"appstore")
  end

  # match 로드 (certificate, profile visioning)

  private_lane :sync_codesign do|options|
    match(type:options[:type], readonly: true)
  end

  lane :sync_codesign_debug do 
    sync_codesign(type:"development")
  end

  lane :sync_codesign_alpha do
    sync_codesign(type:"adhoc")
  end

  lane :sync_codesign_beta do
    sync_codesign(type:"adhoc")
  end

  lane :sync_codesign_release do
    sync_codesign(type:"appstore")
  end

  # 공통 빌드 lane

  desc "common build"
  lane :common_build do |options|
    cocoapods(
      clean_install: true,
      try_repo_update_on_error: true
    )

    case options[:configuration]
    when "Debug"
      scheme = "Debug"
      export_type = "development"
    when "Alpha"
      scheme = "Alpha"
      export_type = "ad-hoc"
    when "Beta"
      scheme = "Beta"
      export_type = "ad-hoc"
    else 
      scheme = "Release"
      export_type = "app-store"
    end

    build_app(
      scheme: "MemoryCam", # app name
      configuration: scheme,
      export_method: export_type,
      output_directory: "./fastlane/distribute",
      clean: true
    )
  end

  # 빌드 호출

  lane :build_debug do # debug로 할 경우 오류
    sync_codesign(type:"development")
    build_number_bump()
    common_build(configuration:"Debug")
  end

  lane :build_alpha do
    sync_codesign(type:"adhoc")
    build_number_bump()
    common_build(configuration:"Alpha")
  end

  lane :build_beta do |options|
    sync_codesign(type:"adhoc")
    build_number_bump()
    common_build(configuration: "Beta")
  end

  lane :build_release do
    sync_codesign(type:"appstore")
    build_number_bump()
    common_build(configuration:"Appstore")
  end

  # 빌드 번호 업 & commit

  lane :build_number_bump do
    ensure_git_status_clean
    increment_build_number(xcodeproj: "./MemoryCam/MemoryCam.xcodeproj")
    build_number = get_build_number(xcodeproj: "./MemoryCam/MemoryCam.xcodeproj")
    commit_version_bump(message: "Bump build number to #{build_number} by fastlane", xcodeproj: "./MemoryCam/MemoryCam.xcodeproj")
    push_to_git_remote
  end


end
  • 빌드 실행 시 아래 에러 주의 (원인: xcode에 provisioning profile이 세팅 안된 것, Signing & Capabilities가서 해주면 해결)

오류메세지
성공한 결과, 메세지
ipa파일 생성된 것 확인

fastlane을 통하여 Firebase에 배포를 하는 이유

* fastlane없이 firebase에 배포 방법: ios-development.tistory.com/251

  • 주의: firebase프레임워크는 share_pod으로 쓰면 안되고 반드시 한 프로젝트에만 의존성을 주입해주어야 오류가 나지 않음
  • fastlane으로 Firebase에 배포하는 이유는? fastlane을 사용하면, fastfile에서 tester group을 미리 정해놓고 ipa파일도 자동으로 올려질 수 있게 하는 편리함
  • 단, Apple Developer 사이트에서 테스터들의 UDID등록하는 것은 수동으로 해주어야 함
  • UDID등록 후 fastlane에서 재배포 하면 테스터들에게 배포 성공

Firebase에서 Configuration별 GoogleService-info.plist파일 다운

  • multiple project 세팅 (debug, alpha, beta, release Configuration 마다 각각의 GoogleService-info.plist를 갖음)
  • multiple project 세팅을 하는 이유: firebase SDK 종속적이기 때문
    (google analytics를 쓴다고 하면 alpha버전, testing버전이 분석 대상이 되면 안되기 때문)
  • Firebase 사이트에 있는 한 프로젝트에서 각 Configuration별로 다운

GoogleService-info.plist생성

  • 프로젝트에 GoogleService-info.plist추가

  • Configurations에 Debug / Alpha / Beta / Release이름 기억

  • 프로젝트 target -> Build Phases -> '+'버튼을 눌러서 "New Run Script Phase"선택

  • Run Script에 GoogleService-Info파일들을 Configuration 별로 관리하기 위하여 아래처럼 작성
    - 디렉토리 주소 확인: root/{product_name}/service/

if [ ${CONFIGURATION} == "Release" ]; then
    echo "Release"
    cp -r "${PROJECT_DIR}/${PRODUCT_NAME}/Service/GoogleService-Info-release.plist" "${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app/GoogleService-Info.plist"
elif [ ${CONFIGURATION} == "Beta" ]; then  
    echo "Beta"
    cp -r "${PROJECT_DIR}/${PRODUCT_NAME}/Service/GoogleService-Info-beta.plist" "${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app/GoogleService-Info.plist"
else 
    echo "Debug / Alpha"
    cp -r "${PROJECT_DIR}/${PRODUCT_NAME}/Service/GoogleService-Info-alpha.plist" "${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app/GoogleService-Info.plist"
fi

Firebase SDK연동 

  • Firebase cocoapod 설치
pod 'Firebase'
  • AppDelegate에 application(_, didFinishLaunchingWithOptions) -> Bool 함수에서 초기화: FirebaseApp.configure()

 

Firebase에 배포

  • 프로젝트의 루트에서 아래 명령어 실행: 옵션을 선택하라고 나오면, "Option 3: RubyGems.org" 선택
fastlane add_plugin firebase_app_distribution
  • gemfile에 "plugins_path"와 파일 "Pluginfile"이 생긴 것 확인

gemfile
Pluginfile

  • firebase 업로드 할 lane에 bundle id를 적용시킬 수 있도록 Appfile에 정의
# 디폴트 값 정의 (밑의 for_lane과 일치하지 않는 fastfile의 함수 lane들은 해당 값 적용)

# default 번들 ID
app_identifier("com.jake.Kimp.debug")

# Your Apple email address
apple_id("palatable7@naver.com")

# Developer Portal Team ID
team_id("SS7U83UJNK") 

# fastfile에 가기전에 app_identifier가 설정
for_platform :ios do

    # debug: 위에서 정한 default 번들 ID를 사용
  
    # alpha

    ...

    for_lane :upload_firebase_alpha do
      app_identifier 'com.jake.Kimp.alpha'
    end

    # beta

    ...

    for_lane :upload_firebase_beta do
      app_identifier 'com.jake.Kimp.beta'
    end
    
  end
  
  • firebase에 업로드 하는 lane을 fastfile에 정의

  • ipa_path 수정: `MemoryCam`부분 -> 앱 이름
  • firebase_cli_token 정보 수정

1. firebase CLI설치

$ curl -sL https://firebase.tools | bash

2. firebase로그인 

$ firebase login:ci

// 로그아웃 하고싶을 경우, firebase logout

3. terminal에 출력되는 토큰 정보 복사하여 위 lane에 firebase_cli_token에 붙여넣기

  • appid

  • tester_group

*  group생성방법은 여기 참고: ios-development.tistory.com/251

주의: debug가 디폴트로 되어 있으므로 alpha버전 bundle id를 누른 후 "시작하기"버튼을 누르지 않으면 firebase에 배포할 때 오류

Build_bump: 빌드 넘버 추가

  • 프로젝트 세팅 1) agvtool 활성화

  • build bump lane 관련 fastfile 내용 (각 빌드에서 호출)

  • 배포 전 체크 리스트
    - 주의0: framework(Domain, CommonExtension), pod 프로젝트 파일 모두 Configuration을 main처럼 alpha, beta, release을 생성해야 모두 빌드 실패 방지

- 주의1: remote git에 프로젝트가 존재

- 주의2: simulator가 선택되어 있으면 x


- 주의3: commit할 내용이 working directory에 존재 x
- 주의4: bundle exec pod update, bundle update 시도 후 ./install_pods 할 것
- 주의5: Firebase홈페이지에서 AppDistribution -> 해당 앱 번들 선택 -> 시작하기 버튼 눌러야 가능

$ bundle exec fastlane upload_firebase_alpha
  • 배포 성공
    주의: 모바일 테스트 기기에서는 첫 번째 배포에서 debug오류 뜨므로, 2번째 배포부터 정상적으로 다운 가능

* 참고: AppDevice UUID테스터 추가 방법

* firebase업로드 할 때 terminal에서 아래 오류 대처 방법 - git의 remote이름을 origin으로 하지 않으면 오류 뜨므로, origin으로 수정하여 다시 시도 할 것

Exit status of command 'git push origin master:master --tags' was 128 instead of 0.
fatal: 'origin' does not appear to be a git repository
fatal: Could not read from remote repository.

Please make sure you have the correct access rights
and the repository exists.


전체코드 - Appfile

# 디폴트 값 정의 (밑의 for_lane과 일치하지 않는 fastfile의 함수 lane들은 해당 값 적용)

# default 번들 ID
app_identifier("com.jake.Kimp.debug")

# Your Apple email address
apple_id("palatable7@naver.com")

# Developer Portal Team ID
team_id("SS7U83UJNK") 

# fastfile에 가기전에 app_identifier가 설정
for_platform :ios do

    # debug: 위에서 정한 default 번들 ID를 사용
  
    # alpha

    for_lane :build_alpha do
      app_identifier 'com.jake.Kimp.alpha'
    end
  
    for_lane :renew_codesign_alpha do
      app_identifier 'com.jake.Kimp.alpha'
    end
  
    for_lane :sync_codesign_alpha do
      app_identifier 'com.jake.Kimp.alpha'
    end

    for_lane :upload_firebase_alpha do
      app_identifier 'com.jake.Kimp.alpha'
    end

    # beta

    for_lane :build_beta do
      app_identifier 'com.jake.Kimp.beta'
    end
  
    for_lane :renew_codesign_beta do
      app_identifier 'com.jake.Kimp.beta'
    end
  
    for_lane :sync_codesign_beta do
      app_identifier 'com.jake.Kimp.beta'
    end

    for_lane :upload_firebase_beta do
      app_identifier 'com.jake.Kimp.beta'
    end
  
    # release

    for_lane :renew_codesign_release do
        app_identifier 'com.jake.Kimp'
    end

    for_lane :sync_codesign_release do
      app_identifier 'com.jake.Kimp'
    end

    for_lane :build_release do
        app_identifier 'com.jake.Kimp'
    end

    for_lane :upload_testflight do
        app_identifier 'com.jake.Kimp'
    end

    for_lane :upload_appstore do
      app_identifier 'com.jake.Kimp'
    end
  
  end
  


전체코드 - fastfile

default_platform(:ios)

ENV["FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD"] = "xtjt-haui-nyix-bsnm"

platform :ios do
  
  # match 갱신 - git에 push (certificate, profile visioning)

  private_lane :renew_codesign do|options|
    match(type:options[:type], force_for_new_devices: true)
  end

  lane :renew_codesign_debug do
    renew_codesign(type:"development")
  end

  lane :renew_codesign_alpha do
    renew_codesign(type:"adhoc")
  end

  lane :renew_codesign_beta do
    renew_codesign(type:"adhoc")
  end

  lane :renew_codesign_release do
    renew_codesign(type:"appstore")
  end

  # match 로드 (certificate, profile visioning)

  private_lane :sync_codesign do|options|
    match(type:options[:type], readonly: true)
  end

  lane :sync_codesign_debug do 
    sync_codesign(type:"development")
  end

  lane :sync_codesign_alpha do
    sync_codesign(type:"adhoc")
  end

  lane :sync_codesign_beta do
    sync_codesign(type:"adhoc")
  end

  lane :sync_codesign_release do
    sync_codesign(type:"appstore")
  end

  # 공통 빌드

  desc "common build"
  lane :common_build do |options|
    cocoapods(
      clean_install: true,
      try_repo_update_on_error: true
    )

    case options[:configuration]
    when "Debug"
      scheme = "Debug"
      export_type = "development"
    when "Alpha"
      scheme = "Alpha"
      export_type = "ad-hoc"
    when "Beta"
      scheme = "Beta"
      export_type = "ad-hoc"
    else 
      scheme = "Release"
      export_type = "app-store"
    end

    build_app(
      scheme: "Kimp", # app name
      configuration: scheme,
      export_method: export_type,
      output_directory: "./fastlane/distribute",
      clean: true
    )
  end

  # 빌드 호출

  lane :build_debug do # debug로 할 경우 오류
    sync_codesign(type:"development")
    build_number_bump()
    common_build(configuration:"Debug")
  end

  lane :build_alpha do
    sync_codesign(type:"adhoc")
    build_number_bump()
    common_build(configuration:"Alpha")
  end

  lane :build_beta do
    sync_codesign(type:"adhoc")
    build_number_bump()
    common_build(configuration: "Beta")
  end

  lane :build_release do
    sync_codesign(type:"appstore")
    build_number_bump()
    common_build(configuration:"Appstore")
  end

  # 빌드 번호 업 & commit

  lane :build_number_bump do
    ensure_git_status_clean
    increment_build_number(xcodeproj: "./Kimp/Kimp.xcodeproj")
    build_number = get_build_number(xcodeproj: "./Kimp/Kimp.xcodeproj")
    commit_version_bump(message: "Bump build number to #{build_number} by fastlane", xcodeproj: "./Kimp/Kimp.xcodeproj")
    push_to_git_remote
  end

  # firebase 업로드

  lane :upload_to_firebase do |options|
    firebase_app_distribution(
      app: options[:appid],
      groups: options[:tester_group],
      firebase_cli_token: "1//0e-h7nTIJyTgeCgYIARAAGA4SNwF-L9IraFQcr4OVscUk376dZjnWCwHsiDmQBYSNL_pnZECOiScrtUCFn21ibJWKP_PM31gy10o",
      ipa_path: "./fastlane/distribute/Kimp.ipa"
    )
  end

  lane :upload_firebase_alpha do
    build_alpha()
    upload_to_firebase(appid: "1:1018046236453:ios:e162fe01a3c6sde5ca857ccc", tester_group: "inHouse")
  end

  lane :upload_firebase_beta do
    build_beta()
    upload_to_firebase(appid: "1:1018046236453:ios:72ee3bbe45c6sd57ab857ccc", tester_group: "inHouse")
  end

  # TestFlight

  lane :upload_testflight do
    build_release()
    upload_to_testflight(
      ipa: "./fastlane/distribute/Kimp.ipa",
      skip_waiting_for_build_processing: true # false면 테스터에게 빌드된 것이 배포되지 않음
    )
  end

  # Appstore
  
  lane :upload_appstore do 
    release()
    # capture_screenshots
    deliver
  end

end

Comments