본문 바로가기

💻 개발IT/Flutter

Flutter 앱 배포 자동화하기 ① (with. Fastlane)

Fastlane ?

다양한 작업을 자동화해주는 라이브러리로 iOS / Android 앱 개발에서 배포 및 빌드 자동화 가능

'Fastfile'이라는 설정 파일을 통해 워크플로우를 정의할 수 있음

 

Fastlane 설치

$ brew install fastlane

 

 

Android Fastlane 적용

 

Google Play Android API 허용

https://console.cloud.google.com/apis/api/androidpublisher.googleapis.com/metrics?project=giftcard-259b5&hl=en

 

Google Cloud Platform

Sign in to continue to Google Cloud Platform

accounts.google.com

 

 

[ENABLE] 을 눌러 사용 설정을 해준다.

 

 

서비스 계정 생성

구글 API를 사용해서 배포를 하기 때문에 계정을 새로 생성해줘야 한다.

 

 

https://console.cloud.google.com/iam-admin/serviceaccounts

 

Google 클라우드 플랫폼

로그인 Google 클라우드 플랫폼으로 이동

accounts.google.com

 

위 링크에 접속하고

좌측 메뉴에서 [서비스 계정] 을 클릭하고 상단의 [CREATE SERVICE ACCOUNT] 를 클릭한다.

 

 

 

Service account ID는 대충 입력해준다.

 

 

권한은 [Service Account User]로 부여해주고, 계정을 생성해준다.

 

생성하고 나면

다시 이전의 서비스 계정 목록으로 돌아오게 되는데

 

생성한 계정 이메일을 클릭한다.

 

 

[KEYS] 탭애서 [ADD KEY] 를 클릭해서

JSON 키를 다운로드 받는다.

 

다운로드 받은 파일을

flutter 루트 폴더/android 에 넣어준다

(key 파일이니 gitignore에 추가해야함!!)

 

출시 권한 부여

https://play.google.com/console/u/0/developers

 

비즈니스를 위한 Google Play | 앱 출시 및 수익 창출 | Google Play Console

Google Play에서 비즈니스를 성장시킬 수 있도록 도와드립니다. 앱과 게임의 출시, 수익 창출, 성장에 필요한 도구와 가이드를 찾아보세요.

play.google.com

 

구글 플레이 콘솔에 접속한 뒤,

좌측 메뉴에서 [사용자 및 권한] 을 클릭하고 상단의 [신규 사용자 초대]를 클릭한다.

 

 

 

 

이메일 주소는 위의 서비스 계정 이메일 주소를 입력하고 권한 만료는 체크하지 않는다.

 

 

자동화할 앱을 선택한 뒤,

 

출시에 모든 항목을 클릭하고 [적용] 후 [사용자 초대]를 마무리 한다.

 

 

Fastlane 초기화

$ cd android
$ fastlane init

 

초기화 명령어를 실행하고

패키지 이름, 위 json 파일 링크와 n 입력한다.

 

완료되면

'fastlane' 이라는 폴더와 Gemfile이 생성 된다.

 

 

Fastlane 작성을 위한 셋팅

배포할 때 릴리즈 노트를 작성해야하는데 (물론, 배포 옵션에 릴리즈 노트를 skip할 수도 있음)

위와 같이 폴더 구조를 생성하면

fastlane이 알아서 인식해서 배포할 때 가지고 가게 된다.

 

위와 같이 언어에 따라 설정할 수도 있고

changelogs 폴더 밑에 버전 코드 명으로 txt 파일을 생성하거나 버전 코드명이 없으면 default.txt 파일을 읽게 된다.

 

 

추가로

 

배포 여부에 따라 Slack 알림이 가는데 이 Slack 웹훅 URL을 가지고 있을 .env 파일을 fastlane 폴더 안에 생성한다.

(.env파일도 gitignore에 추가 필요)

 

 

이런 식으로 정의하면 된다.

 

Fastlane 작성

fastlane/Fastfile에 아래와 같이 작성한다.

default_platform(:android)

# build.gradle 파일에서 현재 versionName 가져오기
def get_version_name
  build_gradle_path = "../app/build.gradle"
  # 해당 파일에 versionName 변수가 많아 앞 뒤로 띄어써서 찾기
  version_name_line = File.readlines(build_gradle_path).find { |line| line.include? " versionName " }
  version_name = version_name_line.match(/versionName\s+["'](.+?)["']/)[1]
  version_name
end

# build.gradle 파일에서 versionCode 가져오기
def get_version_code
  build_gradle_path = "../app/build.gradle"
  version_code_line = File.readlines(build_gradle_path).find { |line| line.include? " versionCode " }
  
  # versionCode 값 추출
  version_code = version_code_line.match(/\d+/)[0].to_i
  version_code
end

# 사용자가 선택한 버전 타입에 따라 버전 증가
def increment_version(version_name, version_type)
  return version_name if version_type == 4  # 4인 경우 현재 버전을 그대로 반환
  
  version_numbers = version_name.split('.').map(&:to_i)

  case version_type
  when 1
    version_numbers[0] += 1
    version_numbers[1] = 0
    version_numbers[2] = 0
  when 2
    version_numbers[1] += 1
    version_numbers[2] = 0
  when 3
    version_numbers[2] += 1
  else
    UI.user_error!("올바르지 않은 버전 유형입니다.")
  end

  version_numbers.join('.')
end

# 사용자로부터 버전 증가 유형을 숫자로 입력받기
def get_version_type_from_user
  version_type = UI.input("증가할 버전 유형을 입력하세요: 1은 Major, 2는 Minor, 3은 Patch, 4는 버전 유지").to_i

  # 유효성 검사
  unless [1, 2, 3, 4].include?(version_type)
    UI.user_error!("잘못된 입력입니다. 1, 2, 3 또는 4를 입력하세요.")
  end

  version_type
end

# versionCode를 1씩 증가시키기
def increment_version_code
  get_version_code + 1
end

# build.gradle 파일에서 특정 키 값 업데이트
def update_gradle_property(key:, value:)
  build_gradle_path = "../app/build.gradle"
  gradle_content = File.read(build_gradle_path)

  # 해당 키 값만 찾아서 업데이트
  updated_content = gradle_content.gsub(/#{key}\s+["']?.+?["']?$/, "#{key} #{value.is_a?(String) ? "\"#{value}\"" : value}")

  # 업데이트된 내용을 다시 build.gradle 파일에 작성
  File.open(build_gradle_path, "w") { |file| file.puts updated_content }

  UI.message("Updated #{key} to #{value} in build.gradle")
end

# Android 앱을 빌드하고 Google Play에 배포하기
def build_and_deploy(new_version_name, new_version_code)
  # versionName 및 versionCode 업데이트
  update_gradle_property(key: "versionName", value: new_version_name)
  update_gradle_property(key: "versionCode", value: new_version_code)

  # 빌드 및 Google Play 배포
  sh("flutter build appbundle")

  upload_to_play_store(
    track: 'production', # internal 일 경우, 내부 테스터 출시
    aab: '../build/app/outputs/bundle/release/app-release.aab',
  )
end

# Slack 메시지를 전송하는 함수
def send_slack_message(success:, app_name:, version_name:, version_code:, error_message: nil)
  # 성공 또는 실패에 따른 메시지 구성
  message = if success
              "✅ *#{app_name}* 배포 완료!\n*버전*: #{version_name} (#{version_code})\n\n"
            else
              "❌ *#{app_name}* 배포 실패\n*버전*: #{version_name} (#{version_code})\n*에러 메시지*: #{error_message}"
            end

  UI.message(ENV['SLACK_URL'])
  # Slack 알림 전송
  slack(
    message: message,
    success: success,
    slack_url: ENV['SLACK_URL']
  )
end

# Main lane 정의
platform :android do
  desc "배포"
  lane :deploy do
    begin
      app_name = "앱 이름"

      # 현재 버전 가져오기
      current_version_name = get_version_name
      current_version_code = get_version_code
      version_type = get_version_type_from_user

      # 버전 증가
      new_version_name = increment_version(current_version_name, version_type)
      new_version_code = increment_version_code

      # 빌드 및 배포
      build_and_deploy(new_version_name, new_version_code)

      # 성공 시 Slack 메시지 전송
      send_slack_message(
        success: true,
        app_name: app_name,
        version_name: new_version_name,
        version_code: new_version_code
      )
    rescue => e
      # 실패 시 버전 되돌리기
      update_gradle_property(key: "versionName", value: current_version_name)
      update_gradle_property(key: "versionCode", value: current_version_code)

      # 실패 시 Slack 메시지 전송
      send_slack_message(
        success: false,
        app_name: app_name,
        version_name: new_version_name,
        version_code: new_version_code,
        error_message: e.message
      )
      UI.user_error!("배포 중 오류가 발생했습니다.")
    end
  end
end

 

과정은

  1. android/app/build.gradle에서 versionName, versionCode 가져오기
  2. 사용자에게 major, minor, patch 업데이트 여부를 프롬프트로 입력 받아 android/app/build.gradle에 업데이트하기
  3. 빌드하기
  4. google play console 에 배포하기 (upload_to_play_store의 track에서 'production'이면 바로 출시 심사, 'internal' 이면 내부 테스트)
  5. 성공하거나 실패할 경우 slack 메시지 알람

 

 

실행

안드로이드 폴더에서 아래 명령어를 실행하면 위 과정이 자동으로 실행된다.

$ fastlane deploy

 

중간에 위와 같이 버전 업데이트 어떤 걸 할 건지 물어보는 프롬프트가 뜨는데

원하는 숫자를 입력하면 그 밑에 업데이트될 버전을 출력해준다.

 

 

조금 기다리면 성공했다는 메시지가 뜨고

google play console의 게시 개요에 들어가면 검토 중인 걸 확인할 수 있다!

 

 

 

굿굿

 

반응형