재직 중인 회사에서 apk로 배포하던걸 bundle로 배포하게 됐습니다. 그러면서 기존에 내부에서 테스트를 위해서 사용하던 Fabric Beta(apk파일만 지원)를 사용할 수 없게 됐습니다. 그래서 내부적으로 Fabric Beta와 비슷한 형식의 시스템을 구현하게 됐습니다.
기능을 구현하기 위해서 사용한 기술들로는 플레이스토어 내부 앱 공유, Firebase의 Cloud Store, Functions, Custom Gradle Plugin을 개발해서 진행했습니다.
자동화(?)된 플로우를 설명해드리면,
bundle로 앱 빌드 -> 구글 내부 앱 공유 api를 통해서 업로드 -> 리턴 받은 download Url을 firebase functions로 구현된 api 호출 -> functions에서 Cloud Store에 업로드하는 api 호출 -> Slack 특정 채널에 압축된 파일 업로드입니다. 이 순서로 task들이 자동으로 실행돼 저희가 개발한 beta 앱에서 확인을 할 수 있게 됩니다. 앱의 경우 Firebase Cloud Store에 저장된 데이터를 보여주는 단순한 앱입니다.
bundle 빌드, functions에서 Cloud Store호출을 제외한 모든 부분은 Custom Gradle Plugin을 개발해서 진행됐습니다.
그럼 여기서 Custom Gradle Plugin이란 무엇이고, 어떻게 만들 수 있는지 설명하겠습니다.
Gradle Plugin?
안드로이드를 개발하게 되면 Gradle이란 빌드 툴을 사용하게 됩니다. 플러그인을 프로젝트에 적용하면 플러그인이 프로젝트 기능을 확장할 수 있습니다.
여기서 저희는 이미 아래와 같은 플러그인을 적용해서 사용하고 있었습니다.
apply plugin: 'com.android.application'
아래와 같이 작업을 할 수 있습니다.
android {
compileSdkVersion 29
defaultConfig {
applicationId "com.mozzet.diffu"
minSdkVersion 22
targetSdkVersion 29
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
debug {
applicationIdSuffix ".debug"
}
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
Android 관련된 정보는 아래 두 링크에서 확인 가능합니다.
https://developer.android.com/studio/build
http://google.github.io/android-gradle-dsl/current/
Custom Plugin 만드는 방법들
-
Build script
-
현재 작성된 빌드 스크립트 내에 추가적으로 작성하는 방법입니다. 이 방법은 따로 작업을 해줄 필요가 없어서 편합니다. 하지만 재사용이 불가능합니다.
-
-
buildSrc project
-
플러그인을 rootProjectDir/buildSrc/src/main/groovy (java or kotlin) 디렉터리에 넣고 작성하여서 프로젝트 내 다른 빌드 스크립트에서 사용할 수 있습니다. 첫 번째 방식과 차이점은 프로젝트 내에 모든 빌드 스크립트에서 사용이 가능하다는 점입니다. 하지만 외부에서는 재사용이 불가능합니다.
-
-
Standalone project
-
플러그인을 별도의 프로젝트로 만들어 개발할 수 있습니다. 이 경우 다른 프로젝트에서 사용이 가능하고, 다른 사용자와도 공유할 수 있습니다.
-
이제 각각의 생성 방법에 대해서 알아보겠습니다. 저는 kotlin 및 kotlin DSL을 이용해서 개발했습니다.
1. Build script 이용해서 플러그인 개발
간단하게 아래의 코드를 추가하면 끝납니다.
build.gradle.kts
class TaskInBuildScriptPlugin : Plugin<Project> {
override fun apply(project: Project) {
project.task("taskInBuildScript") {
group = "custom plugin task group"
doLast {
println("Hello from the taskInBuildScript")
}
}
}
}
// Apply the plugin
apply<TaskInBuildScriptPlugin>()
추가적으로 필요한 설정을 extenstion을 이용해서 설정을 할 수 있습니다. 위에 코드에서 익스텐션을 추가한 코드입니다.
open class TaskInBuildScriptPluginExtension {
var name: String? = null
}
class TaskInBuildScriptPlugin : Plugin<Project> {
override fun apply(project: Project) {
val extension = project.extensions.create<TaskInBuildScriptPluginExtension>("buildScript")
project.task("taskInBuildScript") {
group = "custom plugin task group"
doLast {
println("${extension.name}, Hello from the taskInBuildScript")
}
}
}
}
// Apply the plugin
apply<TaskInBuildScriptPlugin>()
// Configure the extension using a DSL block
configure<TaskInBuildScriptPluginExtension> {
name = "sangcomz"
}
-결과 화면-
2. buildSrc project
프로젝트 내부에 아래와 같이 만들어 줍니다.
아래 3개의 파일을 추가해줍니다.
TaskInBuildSrcPlugin.kt
class TaskInBuildSrcPlugin : Plugin<Project> {
override fun apply(project: Project) {
project.task("taskInBuildSrc") {
group = "custom plugin task group"
doLast {
println("Hello from the taskInBuildScript")
}
}
}
}
buildSrc/build.gradle.kts
plugins {
`kotlin-dsl`
}
repositories {
jcenter()
}
resources/META-INF/task-in-buildsrc-plugin.properties
implementation-class=TaskInBuildSrcPlugin
사용한 부분의 build.gradle.kts에 생성한 플러그인을 추가해줍니다.
plugins {
kotlin("jvm") version "1.3.61"
//apply buildSrc plugin
`task-in-buildsrc-plugin`
}
-결과 화면-
동일하게 Extension을 추가해보겠습니다. 플러그인 자체에 extension을 추가하는 코드는 위에 코드를 참고해주세요.
TaskInBuildSrcPluginExtension.kt
open class TaskInBuildSrcPluginExtension(var name: String? = null)
플러그인이 적용된 build.gradle.kts에 아래와 같은 코드 블록을 추가해서 값들을 설정해줍니다.
//buildSrc extension
buildSrc{
name = "sangcomz"
}
-결과 화면-
3.Standalone Project
이번엔 제가 실제로 사용을 위해 만든 plugin을 이용해서 설명을 하겠습니다. (슬쩍 홍보..)
https://github.com/sangcomz/gradle-slack-upload-plugin
먼저 프로젝트를 생성해줍니다.
이 경우에 buildSrc를 이용해서 plugin을 생성하는 것과 비슷합니다.
저는 이런 식으로 생성을 해줬습니다. 최상위 패키지가 buildSrc와 src의 차이만 있습니다. 여기서 중요한 부분은 배포를 해줘야 한다는 점입니다.
일단 저는 로컬에 배포하는 방식으로 설명을 드리겠습니다.
실제 maven 저장소에 올리는 방법은 제 샘플 코드 혹은 추가적으로 포스팅하도록 하겠습니다. 제가 이 글을 수정해야 할 이유는 한 가지로만....
build.gradle.kts
plugins {
kotlin("jvm") version "1.3.61"
`kotlin-dsl`
maven
`maven-publish`
id("com.jfrog.bintray") version "1.8.4"
}
group = "xyz.sangcomz"
version = "0.0.3"
repositories {
mavenCentral()
jcenter()
}
dependencies {
implementation(kotlin("stdlib-jdk8"))
implementation("com.github.kittinunf.fuel:fuel:2.2.1")
}
tasks {
compileKotlin {
kotlinOptions.jvmTarget = "1.8"
}
compileTestKotlin {
kotlinOptions.jvmTarget = "1.8"
}
}
tasks {
"uploadArchives"(Upload::class) {
repositories {
withConvention(MavenRepositoryHandlerConvention::class) {
mavenDeployer {
withGroovyBuilder {
"repository"("url" to uri("repo")) {
}
}
}
}
}
}
}
이제 uploadArchives task를 실행해줍니다. 그렇게 되면 repo라는 폴더가 생성됩니다.
그러면 이제 이 주소에 있는 gradle plugin을 사용해보도록 하겠습니다.
build.gradle.kts
buildscript {
repositories {
maven {
url = uri("/Users/sangcomz/projects/gradle-slack-upload-plugin/repo")
}
}
dependencies {
classpath("xyz.sangcomz:gradle-slack-upload-plugin:0.0.4")
}
}
apply {
plugin("xyz.sangcomz.gradle")
}
적용을 하게 되면 slack file upload라는 그룹으로 생성된 것을 확인할 수 있습니다.
Standalone의 경우 동일하게 extension을 사용할 수 있습니다.
configure<xyz.sangcomz.gradle.SlackUploadPluginExtension>{
token = "your api token"
channels = "wowchannel"
title = "File Title"
initialComment = "Upload Sample.txt"
filePaths = arrayOf("sample.txt")
zipName = "wowUploada"
zipFilePath = "build/zip"
deleteZipFileAfterUpload = false
}
간단하게 세 가지 방법에 대해서 알아봤습니다. 더 자세하고 groovy를 이용한 방법을 확인하고 싶으시다면 아래의 링크에서 확인이 가능합니다. 영어라는 점을 빼면 훨씬 좋은 공식 문서입니다.
https://docs.gradle.org/current/userguide/custom_plugins.html
추가적으로 궁금한 부분이 있으면 댓글로 남겨주세요.
감사합니다.