Android

[Android] Fastlane 적용하여 빌드 및 배포 자동화

이예짜니 2022. 5. 24. 23:34

개요

Fastlane은 앱의 빌드, 테스트, 배포(출시) 등 일련의 작업들을 자동화 해주는 툴로 Android, iOS 환경에서 사용할 수 있습니다.

직접 적용해보며 느낀 특징으로는,

  • versionCode 자동으로 관리
  • gradle 작업을 위임하여 빌드
  • play store 배포 (production, alpha, beta, internal의 track 지정)

Fastlane 설치

저는 Mac OS 기준 HomeBrew를 사용하여 fastlane을 설치했습니다.

다른 환경에서 fastlane 설치 가이드

$ brew install fastlane

Fastlane 초기화

프로젝트 루트 디렉토리에서 실행

$ fastlane init

초기화 시 아래의 작업 입력

  • 패키지 이름 입력
  • Service Account 등록 시 다운로드 받은 비공개 키(Json) 경로 입력
  • 스토어에 배포된 앱의 meta data 다운 받을지 여부 선택

입력을 완료하면 fastlane(Appfile, Fastfile), Gemfile, Gemfile.lock 파일들이 생성된 걸 확인할 수 있다.

  • Appfile : 앱의 구성 정보를 정의하는 파일
  • Fastfile : fastlane의 동작을 구동하는 lane(작업)을 정의하는 파일

Service Account 등록 및 비공개 키(Json) 생성 방법

fastlane을 통해서 스토어에 앱을 배포하기 위해서는 Google Play Android Developer API 사용 설정과 Service Account 등록이 필요하다.

Service Account를 등록하면 계정의 비공개 키(Json)를 생성할 수 있고, 해당 키를 사용하여 스토어에 앱을 배포할 수 있다.

 

1. Google Play Console -> "설정" -> "API 엑세스"로 이동

2. "새 서비스 계정 만들기" 선택

    2-1. Google Cloud Platform으로 이동

    2-2. "서비스 계정 만들기" 선택

        2-2-1. 서비스 계정의 이름, ID, 설명 입력

        2-2-2. "역할 선택"에서 "서비스 계정 사용자" 선택

        2-2-3. "완료" 선택

    2-3. "생성된 서비스 계정으로" 선택

        2-3-1. "키" -> "키 추가" -> "새 키 만들기"

        2-3-2."Json" 유형의 비공개 키 만들기

        2-3-3. 비공개 키는 자동으로 다운로드 됨 (해당 키는 복구할 수 없으므로 안전하게 관리해야 함)

    2-4. 다시 Google Play Console로 복귀

3. "생성된 서비스 계정"의 "권한 부여" 선택

4. 사용하고자 하는 권한을 선택 (공식문서에는 관리자(모든 권한) 선택 권장)

 

2-3. "생성된 서비스 계정으로" 선택
2-3-2."Json" 유형의 비공개 키 만들기
3. "생성된 서비스 계정"의 "권한 부여" 선택

Fastlane 빌드 구성

fastlane을 동작 시키기 위해서 해당 작업들을 fastfile에 정의해야 한다.

fastfile은 ruby 언어로 작성하며 아래의 특징들 위주로 정리해보았다.

  • versionCode 자동으로 관리
  • gradle 작업을 위임하여 빌드
  • play store 배포 (production, alpha, beta, internal의 track 지정)

versionCode 자동으로 관리

새로운 버전의 앱을 스토어에 배포하기 위해서는 반드시 기존의 배포된 앱보다 높은 versionCode를 적용해야 한다.

사용하는 versionCode의 규칙은 yy/MM/dd/nnn (ex. 220607001, 220607002)이고, Fastlane을 통해 스토어에 배포된 앱(internal, production)의 versionCode를 읽어와 비교하여 새로운 versionCode를 생성 및 적용한다.

PACKAGE_NAME = "com.lee.oneweekonebook"
PLAYSTORE_KEY_PATH = nil
TRACK_TYPE_PRODUCTION = "production"
TRACK_TYPE_INTERNAL = "internal"

def today_formatted
  Date.today.strftime('%Y%m%d')
end

# ex) 220607001
def default_version_code
  "#{today_formatted}001"[2..]
end

def version_code_from_play_store(trackType)
  google_play_track_version_codes(
    package_name: PACKAGE_NAME,
    track: trackType, # production, beta, alpha, internal
    json_key: PLAYSTORE_KEY_PATH
  )[0]
end

# play store의 배포된 앱(production, internal)의 최신 버전코드를 가져온다
def latest_version_code_from_play_store
  production_version_code = version_code_from_play_store(TRACK_TYPE_PRODUCTION)
  internal_version_code = version_code_from_play_store(TRACK_TYPE_INTERNAL)
  if production_version_code >= internal_version_code
    return production_version_code
  else
    return internal_version_code
  end
end

def create_version_code
  # 최신 버전코드
  latest_version_code = latest_version_code_from_play_store.to_s

  # 디폴트 버전코드 (오늘 기준으로 생성)
  version_code = default_version_code

  if version_code > latest_version_code
    return version_code
  else
    version_code_index = latest_version_code[6..]
    new_index = version_code_index.to_i + 1
    new_version_code = "#{latest_version_code[0..5]}%03d" % new_index
    return new_version_code
  end
end

Release 버전 앱 빌드

desc "Build release version as aab"
lane :build_release do |values|
  PLAYSTORE_KEY_PATH = values[:playstore_key_path]

  # 유효한 버전코드를 생성
  store_version_code = create_version_code

  # 프로젝트에 버전코드 적용
  android_set_version_code(
    version_code: store_version_code
  )

  # 기존에 빌드된 파일들을 제거하고
  gradle(task: 'clean')

  # bundle(aab), build_type(Release)로 빌드 및 스토어 키로 사이닝
  gradle(
    task: "bundleRelease",
    properties: {
      "android.injected.signing.store.file" => values[:keystore_path],
      "android.injected.signing.store.password" => values[:keystore_password],
      "android.injected.signing.key.alias" => values[:keystore_alias],
      "android.injected.signing.key.password" => values[:keystore_password],
    }
  )
end

play store 배포

desc "Deploy a release version to the Google Play as internal"
lane :deploy_internal do |values|

  # internal(내부 테스트) 버전으로 배포한다
  upload_to_play_store(
    track: TRACK_TYPE_INTERNAL,
    aab: values[:aab_path],
    json_key: values[:playstore_key_path],
    skip_upload_metadata: true,
    skip_upload_changelogs: true,
    skip_upload_screenshots: true,
    skip_upload_images: true,
    skip_upload_apk: true,
    )
end

fastlane plugin 설치

// 버전코드 변경을 위한 plugin
$ fastlane add_plugin versioning_android

fastfile의 작업 실행

release 버전 앱 빌드하여 play store 배포

$ fastlane build_production keystore_path:'/Users/.../keystore' keystore_password:'...' key_password:'...' keystore_alias:'...' playstore_key_path:'/Users/.../play_store_upload_key.json'

$ fastlane deploy_internal aab_path:'/Users/.../owob-release.aab' playstore_key_path:'/Users/.../play_store_upload_key.json'