๋” ์ด์ƒ ์ˆ˜๋™ ๋ฐฐํฌ๋Š” ๊ทธ๋งŒ! iOS ๋ฐฐํฌ ์ž๋™ํ™” ๊ตฌ์ถ•๊ธฐ

doyeon
  • #iOS
  • #CICD
  • #Fastlane
  • #GitHub Actions

๋“ค์–ด๊ฐ€๋ฉฐ

์•ˆ๋…•ํ•˜์„ธ์š”. ํ”„๋กฌ iOS ๊ฐœ๋ฐœ์ž ์ด๋„์—ฐ์ž…๋‹ˆ๋‹ค.

ํ”„๋กฌ iOS ์•ฑ์„ ๊ฐœ๋ฐœํ•˜๋ฉด์„œ ๊ฐ€์žฅ ๋ฒˆ๊ฑฐ๋กœ์› ๋˜ ์ž‘์—… ์ค‘ ํ•˜๋‚˜๊ฐ€ ๋ฐ”๋กœ ๋ฐฐํฌ์˜€์Šต๋‹ˆ๋‹ค. ๋งค๋ฒˆ ๋ฐฐํฌํ•  ๋•Œ๋งˆ๋‹ค ์ ์ ˆํ•œ ๋ธŒ๋žœ์น˜๋กœ ์ฒดํฌ์•„์›ƒํ•˜๊ณ , Xcode๋ฅผ ์ผœ์„œ xcconfig์—์„œ ๋ฒ„์ „์„ ๋ณ€๊ฒฝํ•˜๊ณ , ์•„์นด์ด๋ธŒ๋ฅผ ๊ธฐ๋‹ค๋ฆฌ๊ณ , ipa ํŒŒ์ผ์„ ์ถ”์ถœํ•ด์„œ Firebase Distribution์— ์—…๋กœ๋“œํ•˜๊ณ , TestFlight๊นŒ์ง€ ๋‹ค์‹œ ํ•œ๋ฒˆ ์—…๋กœ๋“œํ•˜๋Š” ๊ณผ์ •์„ ๋ฐ˜๋ณตํ•ด์•ผ ํ–ˆ์ฃ .

๊ฐ ๋‹จ๊ณ„๋งˆ๋‹ค ์ผ์ผ์ด ๋Œ€๊ธฐํ•œ ๋’ค ๋‹ค์Œ ์ž‘์—…์„ ์ด์–ด๊ฐ€์•ผ ํ•˜๋Š” ๋ฒˆ๊ฑฐ๋กœ์›€์€ ๋ฌผ๋ก , ๋ธŒ๋žœ์น˜ ์ฒดํฌ์•„์›ƒ์ด๋‚˜ ๋ฒ„์ „ ๊ด€๋ฆฌ์—์„œ ์‹ค์ˆ˜๊ฐ€ ๋ฐœ์ƒํ•˜๊ธฐ๋„ ํ–ˆ์Šต๋‹ˆ๋‹ค.

์ €ํฌ ํŒ€์€ Firebase Distribution๊ณผ TestFlight ๋‘ ํ”Œ๋žซํผ์„ ๋ชจ๋‘ ์‚ฌ์šฉํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๋ฐฐํฌ ๊ณผ์ •์ด ๋”์šฑ ๋ณต์žกํ–ˆ์Šต๋‹ˆ๋‹ค.

์ด์— ๋”ฐ๋ผ ๋ฐ˜๋ณต์ ์ธ ์ž‘์—…์„ ์ž๋™ํ™”ํ•ด ํŒ€ ๋ฆฌ์†Œ์Šค๋ฅผ ์ตœ์ ํ™”ํ•˜๊ณ , ์‹ค์ˆ˜๊ฐ€ ๋ฐœ์ƒํ•˜์ง€ ์•Š๋Š” ์•ˆ์ •์ ์ธ ๋ฐฐํฌ ํ™˜๊ฒฝ์„ ๋งˆ๋ จํ•˜๊ธฐ ์œ„ํ•ด ๋ฐฐํฌ ์ž๋™ํ™” ํ”„๋กœ์ ํŠธ๋ฅผ ์‹œ์ž‘ํ•˜๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

Fastlane ์ธํ„ฐํŽ˜์ด์Šค ์„ค๊ณ„

์šฐ์„  ๋ฐฐํฌ ์ž๋™ํ™”์˜ ํ•ต์‹ฌ์ด ๋  Fastlane์˜ ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์„ค๊ณ„ํ–ˆ์Šต๋‹ˆ๋‹ค. ํ”„๋กฌ ์„œ๋น„์Šค๋Š” ํŒฌ์•ฑ๊ณผ ์•„ํ‹ฐ์•ฑ ๋‘ ๊ฐ€์ง€ ์•ฑ์„ ์šด์˜ํ•˜๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ์ด๋ฅผ ๋ชจ๋‘ ์ง€์›ํ•ด์•ผ ํ–ˆ๊ณ , ๋˜ํ•œ ๊ฐœ๋ฐœ/QA/์Šคํ…Œ์ด์ง€/ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ์„ ๊ตฌ๋ถ„ํ•ด ๋ฐฐํฌํ•  ์ˆ˜ ์žˆ๋„๋ก ๊ตฌ์„ฑํ–ˆ์Šต๋‹ˆ๋‹ค.

# Fastlane CICD Interface

# Firebase Distribution ์—…๋กœ๋“œ
lane :upload_firebase do |options|
  # Parameters:
  # - app_type: fan/arti
  # - environment: dev/qa/stg/prd
  # - version: ์•ฑ ๋ฒ„์ „
  # - build_number: ๋นŒ๋“œ ๋ฒˆํ˜ธ
  # - release_notes: ๋ฆด๋ฆฌ์ฆˆ ๋…ธํŠธ
end

# TestFlight ์—…๋กœ๋“œ
lane :upload_testflight do |options|
  # Parameters:
  # - app_type: fan/arti
  # - version: ์•ฑ ๋ฒ„์ „
  # - build_number: ๋นŒ๋“œ ๋ฒˆํ˜ธ
  # TestFlight๋Š” ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ๋งŒ ์—…๋กœ๋“œํ•˜๋ฉฐ,
  # ๋ฆด๋ฆฌ์ฆˆ ๋…ธํŠธ๋Š” ๋ณ„๋„๋กœ ๊ด€๋ฆฌํ•˜๊ธฐ ๋•Œ๋ฌธ์— ํŒŒ๋ผ๋ฏธํ„ฐ์—์„œ ์ œ์™ธ
end

Firebase Distribution ์—…๋กœ๋“œ์™€ TestFlight ์—…๋กœ๋“œ, ์ด๋ ‡๊ฒŒ 2๊ฐœ์˜ public lane์œผ๋กœ ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ตฌ์„ฑํ–ˆ์Šต๋‹ˆ๋‹ค. TestFlight์˜ ๊ฒฝ์šฐ ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ๋งŒ ์—…๋กœ๋“œํ•˜๊ณ  ๋ฆด๋ฆฌ์ฆˆ ๋…ธํŠธ๋„ App Store Connect์—์„œ ๋ณ„๋„๋กœ ๊ด€๋ฆฌํ•˜๊ธฐ ๋•Œ๋ฌธ์— ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ์ตœ์†Œํ™”ํ–ˆ์Šต๋‹ˆ๋‹ค.

Fastlane ๊ตฌํ˜„

Fastfile์—์„œ๋Š” ํ™˜๊ฒฝ๋ณ„๋กœ ์ ์ ˆํ•œ configuration๊ณผ ์Šคํ‚ค๋งˆ๋ฅผ ๋งคํ•‘ํ•˜๊ณ , Firebase Distribution์˜ ๊ฒฝ์šฐ ํ…Œ์Šคํ„ฐ ๊ทธ๋ฃน๋„ ํ™˜๊ฒฝ๋ณ„๋กœ ๊ตฌ๋ถ„ํ•ด์„œ ๊ด€๋ฆฌํ•˜๋„๋ก ํ–ˆ์Šต๋‹ˆ๋‹ค.

def environment_to_configuration(environment)
  case environment
  when "dev"
    CONFIGURATION_DEV    # SandboxRelease
  when "qa"
    CONFIGURATION_QA     # QARelease
  when "stg"
    CONFIGURATION_STG    # StageRelease
  when "prd"
    CONFIGURATION_PRD    # StoreRelease
  else
    UI.user_error!("Invalid environment: #{environment}")
  end
end

# Firebase ํ…Œ์Šคํ„ฐ ๊ทธ๋ฃน ํ™˜๊ฒฝ๋ณ„ ๋งคํ•‘
def tester_group_by_configuration(configuration)
    case configuration
    when CONFIGURATION_DEV
      tester_groups = FIREBASE_TESTER_GROUPS_DEV      # "fromm-๊ฐœ๋ฐœํŒ€, fromm-๊ธฐํšํŒ€, ..."
    when CONFIGURATION_QA
      tester_groups = FIREBASE_TESTER_GROUPS_QA
    when CONFIGURATION_STG
      tester_groups = FIREBASE_TESTER_GROUPS_STG
    when CONFIGURATION_PRD
      tester_groups = FIREBASE_TESTER_GROUPS_PRD
    end
end

Firebase Distribution๊ณผ TestFlight์—์„œ ๊ณตํ†ต์œผ๋กœ ์‚ฌ์šฉํ•˜๋Š” update_version, build_fromm_app ๋“ฑ์˜ ํ•จ์ˆ˜๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ ์•„๋ž˜์™€ ๊ฐ™์ด lane์„ ์™„์„ฑํ•˜์˜€์Šต๋‹ˆ๋‹ค!

lane :upload_firebase do |options|
    puts options
    
    app_type = options[:app_type]
    app_type_string = app_type == "fan" ? "ํŒฌ์•ฑ" : "์•„ํ‹ฐ์•ฑ"
    environment = options[:environment]
    version = options[:version]
    build_number = options[:build_number]
    release_notes = options[:release_notes] || "#{app_type_string} #{version}(#{build_number}) #{environment} ํ™˜๊ฒฝ ๋ฐฐํฌ์ž…๋‹ˆ๋‹ค."
    is_fan = app_type == "fan"
    configuration = environment_to_configuration(environment)
    tester_group = tester_group_by_configuration(configuration)
    
    update_version(
        isFan: is_fan,
        version_number: version,
        build_number: build_number,
        bump_build_number: false
    )
    
    build_fromm_app(
        configuration: configuration,
        is_fan: is_fan,
        distribution_service: DISTRIBUTON_SERVICE_FIREBASE
    )
    
    firebase_app_distribution(
        app: app_id,
        service_credentials_file: "./keystore/fastlane-firebase.json",
        ipa_path: ipa_absolute_path,
        release_notes: release_notes,
        groups: tester_groups,
        debug: true
    )
end

GitHub Actions์™€ ๋‚œ๊ด€๋“ค

Fastlane์„ ๋กœ์ปฌ์—์„œ ์‹คํ–‰ํ•  ์ˆ˜๋„ ์žˆ์ง€๋งŒ, ๋” ์ผ๊ด€๋˜๊ณ  ์•ˆ์ •์ ์ธ ํ™˜๊ฒฝ์„ ๊ตฌ์„ฑํ•˜๊ธฐ ์œ„ํ•ด GitHub Actions๋ฅผ ํ™œ์šฉํ•˜๊ธฐ๋กœ ํ–ˆ์Šต๋‹ˆ๋‹ค. ์ฒ˜์Œ์—๋Š” ๋‹น์—ฐํžˆ GitHub์—์„œ ์ œ๊ณตํ•˜๋Š” ์ตœ์‹  macOS Runner๋ฅผ ์‚ฌ์šฉํ•˜๋ ค๊ณ  ํ–ˆ์ฃ .

jobs:
  distribute-firebase:
    name: Distribute to Firebase
    runs-on: macos-latest

์—ฌ๊ธฐ์„œ ์ฒซ ๋ฒˆ์งธ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค. ์ €ํฌ ํ”„๋กœ์ ํŠธ๋Š” Tuist๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ๋•Œ๋ฌธ์—, GitHub์—์„œ ํด๋ก ํ•œ ๋’ค xcworkspace๋ฅผ ์ง์ ‘ generateํ•ด์•ผ ํ–ˆ์Šต๋‹ˆ๋‹ค. ์ด๋ฅผ ์œ„ํ•ด mise์™€ tuist๊ฐ€ ์ •์ƒ ๋™์ž‘ํ•˜๋„๋ก ๋ฒ„์ „์„ ๋งž์ถ”๊ณ  workflow๋ฅผ ์„ค์ •ํ•˜๋Š” ๊ณผ์ •์—์„œ ๊ฝค ๋งŽ์€ ์–ด๋ ค์›€์„ ๊ฒช์—ˆ์Šต๋‹ˆ๋‹ค.

๊ทธ๋Ÿฐ๋ฐ ๋” ํฐ ๋‚œ๊ด€์ด ๊ธฐ๋‹ค๋ฆฌ๊ณ  ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. ๋ฐ”๋กœ ์ฝ”๋“œ ์‚ฌ์ด๋‹๊ณผ ๋นŒ๋“œ ์‹œ๊ฐ„ ๋ฌธ์ œ์˜€์Šต๋‹ˆ๋‹ค.

GitHub-hosted Runner์˜ ํ•œ๊ณ„

์•„์นด์ด๋ธŒ๋ฅผ ํ•ด์„œ Firebase๋“  TestFlight๋“  ๋ฐฐํฌ ์‚ฌ์ดํŠธ์— ์˜ฌ๋ฆฌ๋ ค๋ฉด ์ธ์ฆ์„œ์™€ ํ”„๋กœ๋น„์ €๋‹ ํŒŒ์ผ์ด ํ•„์š”ํ•œ๋ฐ, ๋…๋ฆฝ๋œ ํ™˜๊ฒฝ์—์„œ ์ด ํ”„๋กœ๋น„์ €๋‹ ํŒŒ์ผ์„ ์„ธํŒ…ํ•˜๋Š” ๊ฒƒ์ด ์–ด๋ ค์› ์Šต๋‹ˆ๋‹ค.

๊ธฐ์กด์— Auto Signing์„ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์—ˆ๊ธฐ ๋•Œ๋ฌธ์— App Store Connect์— ์ดˆ๋Œ€๋œ Apple ๊ณ„์ •์ด ํ•„์š”ํ•œ ์ƒํ™ฉ์ด์—ˆ์ฃ .

ํŒ€ ๊ณ„์ •์„ ์‚ฌ์šฉํ•ด workflow๋‚ด์—์„œ ๋กœ๊ทธ์ธ์„ ํ•˜๊ฑฐ๋‚˜ fastlane match๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ์‹์„ ๊ณ ๋ คํ•ด๋ณด์•˜๋Š”๋ฐ์š”, ๋‹ค์Œ์˜ ์ด์œ ๋กœ ์•„์˜ˆ ๋‹ค๋ฅธ ๋ฐฉ์‹์œผ๋กœ ๋ชจ๋“  ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

๋ฐ”๋กœ ๋นŒ๋“œ์— ์†Œ์š”๋˜๋Š” ์‹œ๊ฐ„์ด์—ˆ์Šต๋‹ˆ๋‹ค. GitHub Runner์˜ ์„ฑ๋Šฅ์ด ์ข‹์ง€ ์•Š์•˜๋Š”์ง€, ์•„์นด์ด๋ธŒ๊ฐ€ ๋„ˆ๋ฌด๋„ˆ๋ฌด ์˜ค๋ž˜ ๊ฑธ๋ ธ์Šต๋‹ˆ๋‹ค.

์ด์— ๋”ฐ๋ผ ์ด workflow ์‹คํ–‰์‹œ๊ฐ„์€ ๊ฑฐ์˜ 40๋ถ„ ์ด์ƒ์ด ์†Œ์š”๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ์ˆ˜๋™์œผ๋กœ ๋ฐฐํฌํ•  ๋•Œ๋„ 10~20๋ถ„ ์ •๋„๊ฐ€ ๊ฑธ๋ฆฌ๋Š”๋ฐ, ์ž๋™ํ™”๊ฐ€ ๋œ๋‹ค ํ•˜๋”๋ผ๋„ ์ˆ˜๋™ ๋ฐฐํฌ๋ณด๋‹ค ํ›จ์”ฌ ์˜ค๋ž˜ ๊ฑธ๋ฆฐ๋‹ค๋ฉด ๋ฐฐํฌ ํ”„๋กœ์„ธ์Šค์— ์ ์šฉํ•˜๊ธฐ๊ฐ€ ๊ณค๋ž€ํ–ˆ์Šต๋‹ˆ๋‹ค.

img.png

Self-hosted Runner๋กœ์˜ ์ „ํ™˜

์ด๋Ÿฐ ๋ฌธ์ œ๋“ค ๋•Œ๋ฌธ์— ๋กœ์ปฌ ๋จธ์‹  ํ™œ์šฉํ•œ self-hosted runner๋ฅผ ์ด์šฉํ•˜๊ธฐ๋กœ ๊ฒฐ์ •ํ–ˆ์Šต๋‹ˆ๋‹ค. ๋นŒ๋“œํ•˜๊ธฐ ์œ„ํ•œ ๋งฅ๋ถ 2๊ฐœ๋ฅผ ํ• ๋‹น๋ฐ›์•˜๊ณ , ์ด๋ฅผ GitHub Actions runner์— ๋“ฑ๋กํ–ˆ์Šต๋‹ˆ๋‹ค.

ํ•ด๋‹น ๋งฅ๋ถ์—๋Š” ๋ฏธ๋ฆฌ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ํ™˜๊ฒฝ์„ ์„ธํŒ…ํ•ด๋‘์—ˆ์Šต๋‹ˆ๋‹ค:

  • Tuist ์„ค์น˜: ํ”„๋กœ์ ํŠธ generate๋ฅผ ์œ„ํ•ด
  • ์˜ฌ๋ฐ”๋ฅธ ๋ฒ„์ „์˜ Xcode ์„ค์น˜: ์ผ๊ด€๋œ ๋นŒ๋“œ ํ™˜๊ฒฝ์„ ์œ„ํ•ด
  • Xcode์— ํŒ€ ๊ณต์šฉ ๊ณ„์ • ๋“ฑ๋ก: ํŒ€ ๊ณ„์ •์œผ๋กœ ์‚ฌ์ด๋‹ ์„ค์ • (Auto Signing ์‚ฌ์šฉ ๊ฐ€๋Šฅ)

๊ทธ๋ฆฌ๊ณ  workflow์—์„œ๋Š” ํŠน์ • Xcode ๋ฒ„์ „์„ ์‚ฌ์šฉํ•˜๋„๋ก ๋ช…์‹œ์ ์œผ๋กœ ์„ ํƒํ•˜๋Š” ๊ณผ์ •๋„ ํฌํ•จํ–ˆ์Šต๋‹ˆ๋‹ค:

XCODE_PATH = "/Applications/Xcode-16.4.0.app"

# Fastlane์—์„œ Xcode ๋ฒ„์ „ ์ง€์ •
xcode_select(XCODE_PATH)
jobs:
  distribute-firebase:
    name: Distribute to Firebase
    runs-on: self-hosted  # ์ด์ œ ์šฐ๋ฆฌ ๋งฅ๋ถ์—์„œ ์‹คํ–‰!

workflow ์‹คํ–‰ ์‹œ๊ฐ„๋„ ๋Œ€ํญ ๊ฐ์†Œ๋˜์–ด 10๋ถ„ ๋‚ด์™ธ๊ฐ€ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

img_4.png

self-hosted runner๋ฅผ ํ†ตํ•ด ๋‹ค์–‘ํ•œ ํ™˜๊ฒฝ ์„ค์ • ๋ฌธ์ œ์™€ ๋นŒ๋“œ ์‹œ๊ฐ„ ๋ฌธ์ œ๋ฅผ ํ•œ๋ฒˆ์— ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค!

GitHub Secret์œผ๋กœ ์ธ์ฆ ๊ด€๋ฆฌ

Firebase์™€ TestFlight์— ํ•„์š”ํ•œ ์—ฌ๋Ÿฌ ์ธ์ฆ์„œ๋“ค์„ GitHub Secret์— ์ €์žฅํ•ด๋‘์—ˆ์Šต๋‹ˆ๋‹ค. ํŠนํžˆ JSON ํ‚ค ํŒŒ์ผ๊ฐ™์€ ๊ฒฝ์šฐ๋Š” ๊ฐœํ–‰์ด ํฌํ•จ๋˜๋ฉด GitHub workflow์—์„œ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— base64๋กœ ์ธ์ฝ”๋”ฉํ•ด์„œ ์‚ฌ์šฉํ–ˆ์Šต๋‹ˆ๋‹ค.

- name: Generate key file
  run: |
    mkdir -p keystore
    echo '${{ secrets.FIREBASE_SERVICE_ACCOUNT_KEY }}' | base64 -d > ./keystore/fastlane-firebase.json

Firebase Distribution์„ ์œ„ํ•œ ํ™˜๊ฒฝ๋ณ€์ˆ˜๋“ค๋„ workflow์—์„œ ๋™์ ์œผ๋กœ ์ƒ์„ฑํ•˜๋„๋ก ๊ตฌ์„ฑํ–ˆ์Šต๋‹ˆ๋‹ค:

- name: Make Fastlane Env
  run: |
    echo FIREBASE_FAN_APP_ID=${{ secrets.FIREBASE_FAN_APP_ID }} >> ./Fastlane/.env
    echo FIREBASE_STAR_APP_ID=${{ secrets.FIREBASE_STAR_APP_ID }} >> ./Fastlane/.env

์‹ค์ œ ๋ฐฐํฌ ๊ณผ์ •์€ Tuist๋กœ ํ”„๋กœ์ ํŠธ๋ฅผ generateํ•˜๊ณ  Fastlane์„ ์‹คํ–‰ํ•˜๋Š” ๋‹จ๊ณ„๋กœ ๊ตฌ์„ฑ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค:

- name: Make .xcodeproj file
  run: |
      tuist install
      TUIST_ROOT_DIR=${PWD} tuist generate --no-binary-cache --verbose

      # Verify project files were generated
      echo "Checking generated project files..."
      ls -la *.xcworkspace *.xcodeproj 2>/dev/null || echo "No workspace/project files found at root"
      find . -name "*.xcworkspace" -o -name "*.xcodeproj" | head -5

- name: Run Fastlane
  run: fastlane upload_firebase app_type:${{ inputs.type }} environment:${{ inputs.environment }} version:${{ inputs.version_number }} build_number:${{ inputs.build_number }} release_notes:"${{ inputs.releaseNote }}"

๋นŒ๋“œ ๋จธ์‹  ๊ฐ€์šฉ์„ฑ ํ™•๋ณด

์–ธ์ œ๋“  ๊ฐ€์šฉ์„ฑ ์žˆ๊ฒŒ ๋นŒ๋“œ ๋จธ์‹ ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก Amphetamine ์•ฑ์„ ์„ค์น˜ํ•ด ๋งฅ๋ถ์ด ์Šฌ๋ฆฝ ๋ชจ๋“œ๋กœ ๋“ค์–ด๊ฐ€์ง€ ์•Š๊ณ  ์˜จ๋ผ์ธ ์ƒํƒœ๊ฐ€ ์œ ์ง€๋˜๋„๋ก ํ•˜์˜€์Šต๋‹ˆ๋‹ค.

๊ทธ๋ฆฌ๊ณ  Chrome ์›๊ฒฉ ๋ฐ์Šคํฌํƒ‘ ํ™•์žฅ ํ”„๋กœ๊ทธ๋žจ์„ ์ด์šฉํ•ด ์–ด๋””์„œ๋“  ๋ฌธ์ œ ํ•ด๊ฒฐ์„ ํ•  ์ˆ˜ ์žˆ๋„๋ก ์„ธํŒ…ํ–ˆ์Šต๋‹ˆ๋‹ค.

๋‹ค๋งŒ ๊ธฐ๊ธฐ๋ฅผ ์™„์ „ํžˆ ๋‹ซ์œผ๋ฉด ์Šฌ๋ฆฝ ๋ชจ๋“œ๊ฐ€ ๋˜๊ณ  ๋„คํŠธ์›Œํฌ๊ฐ€ ์œ ์‹ค๋˜๋ฏ€๋กœ ์ด ๋ถ€๋ถ„์€ ์ฃผ์˜ํ•˜๋„๋ก ์•ˆ๋‚ดํ–ˆ์Šต๋‹ˆ๋‹ค.

์ž๋™ ๋ฒ„์ „ ๊ด€๋ฆฌ

์ถ”๊ฐ€์ ์œผ๋กœ Actions์—์„œ ๋ฐฐํฌ๊ฐ€ ์„ฑ๊ณตํ•˜๋ฉด ๋ฒ„์ „์—… ์ปค๋ฐ‹์„ ์ž๋™์œผ๋กœ ์˜ฌ๋ฆฌ๋„๋ก ๊ตฌ์„ฑํ–ˆ์Šต๋‹ˆ๋‹ค.

- name: Auto commit version changes
  if: success()
  run: |
    git config --global user.name "GitHub Actions"
    git config --global user.email "actions@github.com"

    git add XCConfig/Artist/ArtistBase.xcconfig XCConfig/Fan/FanBase.xcconfig
    git commit -m "[version-up] ${{ inputs.version_number }}(${{ inputs.build_number }})"
    git push

์ด์ œ ๋ฐฐํฌ๊ฐ€ ์„ฑ๊ณตํ•˜๋ฉด xcconfig ํŒŒ์ผ์˜ ๋ฒ„์ „ ์ •๋ณด๊ฐ€ ์ž๋™์œผ๋กœ ์—…๋ฐ์ดํŠธ๋˜๊ณ , ์ปค๋ฐ‹๊นŒ์ง€ ์˜ฌ๋ผ๊ฐ€์„œ ๋ฒ„์ „ ํžˆ์Šคํ† ๋ฆฌ๋ฅผ ๊น”๋”ํ•˜๊ฒŒ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

workflow_dispatch๋กœ ํŽธ๋ฆฌํ•œ GUI ๋ฐฐํฌ

workflow_dispatch๋ฅผ ์ด์šฉํ•ด์„œ GUI ํ™˜๊ฒฝ์—์„œ ํŽธํ•˜๊ฒŒ ๋ฐฐํฌํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. GitHub์˜ Actions ํƒญ์—์„œ ๋ฒ„ํŠผ ํด๋ฆญ๋งŒ์œผ๋กœ ๋ฐฐํฌํ•  ์ˆ˜ ์žˆ๊ณ , ๋ชจ๋ฐ”์ผ์—์„œ๋„ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•ด์„œ ์ •๋ง ํŽธ๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

on:
  workflow_dispatch:
    inputs:
      type:
        description: App Type (arti, fan)
        required: true
      environment:
        description: Server Environment (dev, qa, stg, prd)
        required: true
      version_number:
        description: Version Number (e.g. 1.2.3)
        required: true
      build_number:
        description: Build Number (e.g. 100)
        required: true
      releaseNote:
        description: Release Note
        required: true

img_1.png

GitHub ์•ฑ์„ ์„ค์น˜ํ•˜๋ฉด ๋ชจ๋ฐ”์ผ์—์„œ๋„ Actions๋ฅผ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ์–ด์„œ, ์–ธ์ œ ์–ด๋””์„œ๋“  ๋ฐฐํฌํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

img_2.png

Slack ์•Œ๋ฆผ์œผ๋กœ ์™„๋ฒฝ ๋งˆ๋ฌด๋ฆฌ

๋ฐฐํฌ ์„ฑ๊ณต ์‹œ Slack์œผ๋กœ ์•Œ๋ฆผ์„ ๋ณด๋‚ด๋„๋ก ๊ตฌ์„ฑํ•ด์„œ ํŒ€์›๋“ค์ด ์‹ค์‹œ๊ฐ„์œผ๋กœ ๋ฐฐํฌ ํ˜„ํ™ฉ์„ ํŒŒ์•…ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ–ˆ์Šต๋‹ˆ๋‹ค. Steve Jobs์™€ Tim Cook ์•„์ด์ฝ˜์œผ๋กœ Firebase Distribution๊ณผ TestFlight๋ฅผ ๊ตฌ๋ถ„ํ•ด์„œ ์•Œ๋ฆผ์„ ๋ณด๋‚ด๋Š” ๊ฒƒ๋„ ์†Œ์†Œํ•œ ์žฌ๋ฏธ ์š”์†Œ์ž…๋‹ˆ๋‹ค.

# in Fastfile
SLACK_USERNAME = distribution_service == DISTRIBUTON_SERVICE_TESTFLIGHT ? SLACK_USERNAME_TIM : SLACK_USERNAME_STEVE
ICON_URL = distribution_service == DISTRIBUTON_SERVICE_TESTFLIGHT ? ICON_URL_TIM : ICON_URL_STEVE

slack(
    message: message,
    slack_url: DEPLOY_NOTY_SLACK_URL,
    username: SLACK_USERNAME,
    icon_url: ICON_URL,
    default_payloads: [],
    attachment_properties: {
        fields: fields
    },
    fail_on_error: false,
    link_names: true
)

img_3.png

๋งˆ์น˜๋ฉฐ

์ด๋ ‡๊ฒŒ ํ•ด์„œ ์ €ํฌ๋Š” iOS ๋ฐฐํฌ ์ž๋™ํ™”์— ์„ฑ๊ณตํ–ˆ์Šต๋‹ˆ๋‹ค! ์ด์ œ ๋” ์ด์ƒ ๋ฒˆ๊ฑฐ๋กœ์šด ์ˆ˜๋™ ๋ฐฐํฌ ๊ณผ์ •์„ ๋ฐ˜๋ณตํ•  ํ•„์š”๊ฐ€ ์—†์–ด์กŒ๊ณ , ์‹ค์ˆ˜ํ•  ๊ฐ€๋Šฅ์„ฑ๋„ ํฌ๊ฒŒ ์ค„์–ด๋“ค์—ˆ์Šต๋‹ˆ๋‹ค.

ํŠนํžˆ GitHub Actions์˜ workflow_dispatch๋ฅผ ํ™œ์šฉํ•œ GUI ๊ธฐ๋ฐ˜ ๋ฐฐํฌ๋Š” ์ •๋ง ๋งŒ์กฑ์Šค๋Ÿฌ์šด ๊ฒฐ๊ณผ์˜€์Šต๋‹ˆ๋‹ค. ๋ช‡ ๋ฒˆ์˜ ํด๋ฆญ๋งŒ์œผ๋กœ ์›ํ•˜๋Š” ํ™˜๊ฒฝ์— ์›ํ•˜๋Š” ๋ฒ„์ „์„ ๋ฐฐํฌํ•  ์ˆ˜ ์žˆ๊ณ , ๋ชจ๋ฐ”์ผ์—์„œ๋„ ๊ฐ€๋Šฅํ•˜๋‹ค๋Š” ์ ์ด ํฐ ์žฅ์ ์ด์—ˆ์Šต๋‹ˆ๋‹ค.

๋ฌผ๋ก  self-hosted runner๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด์„œ ๋นŒ๋“œ ๋จธ์‹  ๊ด€๋ฆฌ๋ผ๋Š” ์ƒˆ๋กœ์šด ๊ณผ์ œ๊ฐ€ ์ƒ๊ฒผ์ง€๋งŒ, ๊ทธ๋ณด๋‹ค๋Š” ์–ป์€ ๊ฒƒ์ด ํ›จ์”ฌ ๋งŽ๋‹ค๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค. ๋นŒ๋“œ ์†๋„๋„ ๋นจ๋ผ์กŒ๊ณ , ๋งŽ์€ ๋ฌธ์ œ๋“ค์ด ๊น”๋”ํ•˜๊ฒŒ ํ•ด๊ฒฐ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

์•ž์œผ๋กœ๋Š” ์ด ์ž๋™ํ™”๋œ ๋ฐฐํฌ ์‹œ์Šคํ…œ์„ ํ†ตํ•ด ๋” ๋น ๋ฅด๊ณ  ์•ˆ์ •์ ์œผ๋กœ ํ”„๋กฌ ์„œ๋น„์Šค๋ฅผ ๊ฐœ์„ ํ•ด ๋‚˜๊ฐ€๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. ๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค!

โ† ๋ชฉ๋ก์œผ๋กœ ๋Œ์•„๊ฐ€๊ธฐ

Art Changes Life

๋…ธ๋จธ์Šค์™€ ํ•จ๊ป˜ ์—”ํ„ฐํ…Œํฌ ์‚ฐ์—…์„ ํ˜์‹ ํ•ด๋‚˜๊ฐˆ ๋ฉค๋ฒ„๋ฅผ ์ฐพ์Šต๋‹ˆ๋‹ค.

์ฑ„์šฉ ์ค‘์ธ ๊ณต๊ณ  ๋ณด๊ธฐ