관리 메뉴

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

[fastlane] 5. fastlane deliver로 Apple Store에 배포, TestFlight 배포 본문

iOS 앱 배포와 출시

[fastlane] 5. fastlane deliver로 Apple Store에 배포, TestFlight 배포

jake-kim 2021. 1. 16. 00:53

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 사용 방법


* fastlane을 쓰지 않고 배포하는 방법에서 먼저 Appstore connect에 앱 등록 할 것:ios-development.tistory.com/299

* fastlane으로 스크린샷(snapshot) 찍는 방법 먼저 참고: ios-development.tistory.com/304

 

[iOS - swift] fastlnae으로 스크린샷 (snapshot) 찍기

fastalne으로 스크린 샷 찍기 Target에 UI Test 추가 fastlane snap shot 초기화 * fastlane 설치 참고: ios-development.tistory.com/255 $ fastlnae snapshot init SnapshotHelper.swift 파일을 UITests디렉..

ios-development.tistory.com

 

Deliverfile 생성

  • 먼저 fastlane/Appfile에서 bundle id가 다수가 입력된 경우, appstore용만 남기고 삭제

Deliverfile생성하려고 할 때 App store connect에 로그인되는데, 어느 bundle id를 쓸것인지 이곳에서 판단

단, 아래 Deliverfile을 초기화한 후 다시 bundleID 세가지를 입력해 놓아야함 (안할 경우 alpha버전 빌드시 오류 발생)

  • Deliverfile 초기화
fastlane deliver init

추가된 파일 확인

  • metadata는 AppStroe Connect에서 입력한 정보를 바탕으로 추가됨

  • metadata안에 연령 등급을 입력하는 json파일 추가: app_store_rating_config.json
    - 경로: ./fastlane/metadata/app_store_rating_config.json
// deprecated
{
  "CARTOON_FANTASY_VIOLENCE": 0,
  "REALISTIC_VIOLENCE": 0,
  "PROLONGED_GRAPHIC_SADISTIC_REALISTIC_VIOLENCE": 0,
  "PROFANITY_CRUDE_HUMOR": 0,
  "MATURE_SUGGESTIVE": 0,
  "HORROR": 0,
  "MEDICAL_TREATMENT_INFO": 0,
  "ALCOHOL_TOBACCO_DRUGS": 0,
  "GAMBLING": 0,
  "SEXUAL_CONTENT_NUDITY": 0,
  "GRAPHIC_SEXUAL_CONTENT_NUDITY": 0,
  "UNRESTRICTED_WEB_ACCESS": 0,
  "GAMBLING_CONTESTS": 0
}

// new
{
"violenceCartoonOrFantasy": "NONE",
"violenceRealistic": "NONE",
"violenceRealisticProlongedGraphicOrSadistic": "NONE",
"profanityOrCrudeHumor": "NONE",
"matureOrSuggestiveThemes": "NONE",
"horrorOrFearThemes": "NONE",
"medicalOrTreatmentInformation": "NONE",
"alcoholTobaccoOrDrugUseOrReferences": "NONE",
"gamblingSimulated": "NONE",
"sexualContentOrNudity": "NONE",
"sexualContentGraphicAndNudity": "NONE",
"unrestrictedWebAccess": false,
"gambling": false,
"contests": "NONE"
}
  • deliverfile을 열어서 아래와 같이 작성
app_identifier "com.jake.MemoryCam" # appfile에 3가지의 bundle id가 존재하므로 release용 bundle id를 따로 입력해야 가능

price_tier(0)

# skip_metadata: false # 메타 데이터를 업로드 하지 않는가에 대한 옵션 (스크린샷은 메타데이터와 별도로 업로드 됨)

# metadata_path: "./fastlane/metadata"

# 심사 시 IDFA(애플 광고 식별자)에 대한 정보
submission_information({
    export_compliance_encryption_updated: false,
    export_compliance_uses_encryption: false,
    content_rights_contains_third_party_content: false, # 3party콘텐츠가 포함 여부
    add_id_info_uses_idfa: false # 내 앱에서 IDFA를 사용 여부
})

app_rating_config_path("./fastlane/metadata/app_store_rating_config.json")

ipa("./fastlane/distribute/MemoryCam.ipa")

submit_for_review(true)  # 앱스토어 커넥트에 리뷰요청을 할 것인지 (단순히 파일 업로드면 false로 지정할 것)

automatic_release(true)  # 승인되면 자동으로 앱스토어에 제출 여부

force(true) # 터미널에서 물어보는 것 생략: Does the Preview on path './fastlane/Preview.html' look okay for you? (y/n)

* price_tier는 가격 정보를 의미

 

  • 빌드, 스크린샷, app store에 업로드 lane을 fastfile에 추가
  lane :upload_appstore do 
    release()
    capture_screenshots
    deliver
  end
  • 실행
    - 단, ERROR ITMS-90685: "CFBundleIdentifier Collision. 오류 대비하여 framework embed 주의
    - host 'H'에서 framework 'F1', 'F2'를 embed하고, 'F1'에서도 'F2'를 embed할 경우 에러가 발생
    - 해결: 'F1'에 가서 'F2'를 do not embed 설정 할 것 (Target -> General -> "Frameworkds, Libraries and Embedded Content에 존재")
bundle exec fastlane upload_appstore

앱스토어에 업로드 성공
심사 대기 중 확인

 


testflight 업로드

  • App Store Connect에 alpha버전(beta버전이 더욱 적합)을 등록 및 테스트 그룹 추가먼저 할 것

  • testflight lane 추가
    - scheme은 Alpha, export_type은 "app-store"로 하여 빌드 후 업로드하는 방식
  lane :alpha_testflight do |options|
    sync_codesign(type:"adhoc")
    sync_codesign(type:"appstore")
    common_build(configuration: "AlphaRelease")
    upload_to_testflight(
      ipa: "./fastlane/distribute/MemoryCam.ipa",
      skip_waiting_for_build_processing: true # false면 테스터에게 빌드된 것이 배포되지 않음
    )
  end

- common_build에 AlphaRelease 추가

# 공통 빌드 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 "AlphaRelease"
      scheme = "Alpha"
      export_type = "app-store"
    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
  • info.plist에 아래 항목 추가

  • testflight 배포 명령어 입력
$ bundle exec fastlane alpha_testflight

* fastfile 전체 코드

# This file contains the fastlane.tools configuration
# You can find the documentation at https://docs.fastlane.tools
#
# For a list of all available actions, check out
#
#     https://docs.fastlane.tools/actions
#
# For a list of all available plugins, check out
#
#     https://docs.fastlane.tools/plugins/available-plugins
#

# Uncomment the line if you want fastlane to automatically update itself
# update_fastlane

default_platform(:ios)

ENV["FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD"] = "lqgm-basd-vvdl-bdmi"

platform :ios do

  # match
  desc "Setup certificate & provisioning profiles"
  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_release do
    sync_codesign(type:"appstore")
  end

  # renew cert & profile
  desc "Fix and renew certificate & provisioning profiles"
  private_lane :renew_codesign do|options|
    match(type:options[:type], force_for_new_devices: true)
  end

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

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

  lane :renew_release_codesign do
    renew_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 "AlphaRelease"
      scheme = "Alpha"
      export_type = "app-store"
    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

  # 빌드 호출
  desc "build debug"
  lane :debug_test do # debug로 할 경우 오류
    sync_codesign(type:"development")
    build_number_bump()
    common_build(configuration:"Debug")
  end

  desc "build alpha"
  lane :alpha do
    sync_codesign(type:"adhoc")
    build_number_bump()
    common_build(configuration:"Alpha")
  end

  desc "build release"
  lane :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

  # firebase 업로드

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

  lane :alpha_firebase do
    alpha()
    upload_to_firebase(appid: "1:303865817001:ios:9c1dsdfsdfs798271f7986b8", tester_group: "inHouse")
  end

  lane :alpha_testflight do |options|
    sync_codesign(type:"adhoc")
    sync_codesign(type:"appstore")
    common_build(configuration: "AlphaRelease")
    upload_to_testflight(
      ipa: "./fastlane/distribute/MemoryCam.ipa",
      skip_waiting_for_build_processing: true # false면 테스터에게 빌드된 것이 배포되지 않음
    )
  end

  lane :upload_appstore do 
    release()
    # capture_screenshots
    deliver
  end
end
  • build 넘버는 fastlane으로 하지만, marketing version은 따로 xcode에서 먼저 올려야 하는 것 주의
    _ marketing버전: major.minor.fatch (앱스토어에서 앱을 다운 받을 때 )
    - major는 api가 바뀐 경우
    - minor는 기능이 바뀐 경우 (이 전버전과는 호환 가능)
    - fatch는 버그 수정 (이 전버전과는 호환 가능)

 

* 참고: docs.fastlane.tools/actions/deliver/

Comments