[안드로이드] kakao sample gradle 분석 2.루트의 build.gradle [2]

프로그래밍/Android 관련2017. 12. 6. 21:47

안녕하세요. 개발자 드리머즈입니다.


이전 포스트에 이어서.. kakao sdk sample의 루트에 있는 build.gradle 분석 계속합니다.

subprojects {
repositories {
mavenCentral()
google()
maven { url 'http://devrepo.kakao.com:8088/nexus/content/groups/public/' }
}
}

maven { url 'http://devrepo.kakao.com:8088/nexus/content/groups/public/' }

은 이 샘플의 핵심이 되는, kakao SDK가 있는 경로입니다(remote maven repository).

다른 부분은 이전에 본 내용이기에, 처음보는 subprojects {} 블럭이 무엇인지 찾아봐야겠습니다.


스택 오버플로우에 답변이 있습니다.

https://stackoverflow.com/questions/12077083/what-is-the-difference-between-allprojects-and-subprojects

In a multi-project gradle build, you have a rootProject and the subprojects. The combination of both is allprojects. The rootProject is where the build is starting from. A common pattern is a rootProject has no code and the subprojects are java projects. In which case, you apply the java plugin to only the subprojects:

subprojects {
    apply plugin: 'java'
} 

This would be equivalent to a maven aggregate pom project that just builds the sub-modules.

Concerning the two syntaxes, they do the exact same thing. The first one just looks better. 

번역하면

멀티-프로젝트 빌드에는 루트프로젝트rootProject와 서브프로젝트subproject가 있습니다. 이 두개를 합친 것을 allproject라고 합니다. 루트 프로젝트는 빌드가 시작되는 곳입니다. 루트 프로젝트는 코드가 없고 서브 프로젝트들이 자바 프로젝트인 것이 흔한 패턴입니다. 아래의 경우 자바 플러그인을 서브 프로젝트에만 적용합니다.

subprojects {
    apply plugin: 'java'
} 

이것은 서브 모듈만 빌드하는 maven aggregate pom project와 동등할 것입니다.

두 문법을 봤을 때, 두 문법은 정확하게 같은 것을 합니다. 처음 것이 더 표기 좋습니다. 

와 같습니다.

kakao sdk sample이 딱 여기에 해당하는 것 같습니다. 루트 프로젝트는 코드가 없고.. 여러 많은 서브 프로젝트가 있기 때문이죠. 그래서 allprojects{}가 아닌 subprojects{} 블럭을 사용한 것으로 보입니다.


다음은 task{} 블럭입니다.

// for publishing
task cleanTask {
doLast {
println 'clean dist/ dir for copying and zipping full source...'
delete DIST_DIR
}
}

http://kwonnam.pe.kr/wiki/gradle 따르면,

  • 기본적으로 gradle을 통해 실행되는 단위를 “Task 태스크”라고 한다.(Ant의 target, Maven의 phase와 유사한 개념)
  • 태스크는 의존 관계에 따라 단 한 번만 실행된다 

라고 합니다. 그런데 task에 대해 전혀 모르는 상태에서는.. 저 설명만으로는 정확하게 감이 오지 않습니다.


https://docs.gradle.org/current/userguide/tutorial_using_tasks.html 

https://docs.gradle.org/current/userguide/more_about_tasks.html


위 사이트에서 튜토리얼을 보면 조금 나아지긴 합니다.

제가 일단 대충 이해한 바로는.. cleanTask라는 이름의 task를 정의하는 코드입니다. 이 task라는 것은 함수처럼 나중에 호출할 수 있습니다. Android Studio Terminal에서 명시적으로 특정 task들을 호출할 수 있으며, 만약 task 이름이 명시되지 않으면 default task가 호출 된다고 합니다.


import org.apache.tools.ant.filters.ConcatFilter

task changePhase {
shouldRunAfter(cleanTask)
doLast {
println ">>> changing DEFAULT_PHASE to ${project.defaultDeployPhase}"
ant.propertyfile(file: "${rootProject.projectDir}/gradle.properties") {
entry(key: 'DEFAULT_PHASE', value: project.defaultDeployPhase)
}
}
}


import 구문이 나옵니다. java에서 import를 해야 특정 함수를 사용할 수 있듯이, gradle script 내에서 특정 함수를 사용하기 위해 import하는 것 같습니다.

그 다음에는 changePhase라는 이름의 task를 정의합니다. closure(중괄호) 내부를 보면

shouldRunAfter(cleanTask)가 있습니다. 임의로 cleanTask를 수행하면 자동으로 changePhase가 실행되도록 하는 문장입니다.

doLast {}블럭이 나옵니다. 반대되는 개념으로 doFirst{} 블럭이 있습니다. task는 일반적인 언어의 함수와 다르게 한번 정의 후, 내용을 추가할 수 있습니다. 이 때 추가되는 코드의 순서를 조절하기 위해 필요합니다.

아래의 공식 사이트의 예제를 참고합시다.


https://docs.gradle.org/current/userguide/tutorial_using_tasks.html

Example 16.10. Accessing a task via API - adding behaviour

build.gradle

task hello {
    doLast {
        println 'Hello Earth'
    }
}
hello.doFirst {
    println 'Hello Venus'
}
hello.doLast {
    println 'Hello Mars'
}
hello {
    doLast {
        println 'Hello Jupiter'
    }
}

Output of gradle -q hello

> gradle -q hello
Hello Venus
Hello Earth
Hello Mars
Hello Jupiter

The calls doFirst and doLast can be executed multiple times. They add an action to the beginning or the end of the task’s actions list. When the task executes, the actions in the action list are executed in order. 


처음 task hello{} 블럭에서 hello라는 이름의 task를 정의한 후에.. 나머지 코드에서 추가작업을 하고 있습니다. 이에따른 실행 결과를 보면 어떤 역할을 하는지 감이 옵니다.


doLast{} 블럭의 내부에는 아래의 코드가 있습니다.

        println ">>> changing DEFAULT_PHASE to ${project.defaultDeployPhase}"
        ant.propertyfile(file: "${rootProject.projectDir}/gradle.properties") {
            entry(key: 'DEFAULT_PHASE', value: project.defaultDeployPhase)
        } 

println은 이름에서 예측가능하듯이.. 출력함수 입니다. 디버깅용도로 ${project.defaultDeployPhase}라는 값을 출력합니다.

그 다음 코드에서 ant가 등장하는데 이를 위해 바로 위의 import문이 필요했던 것 같습니다. 대충 이름만 봐도 어떤 일을 하는지 추측할 수 있습니다. 루트에 위치한 gradle.properties에 접근해서 DEFAULT_PHASE의 값을 project.defaultDeployPhase로 변경하는 코드입니다. 현재 gradle.properties를 보면 아래처럼 되어있습니다.

#Thu, 19 Oct 2017 15:07:19 +0900
DIST_DIR=dist
DIST_FULL_SOURCE_DIR=dist/full_sources

KAKAO_SDK_GROUP=com.kakao.sdk
VERSION_CODE=60
KAKAO_SDK_VERSION=1.6.2
DEFAULT_PHASE=release

ANDROID_BUILD_MIN_SDK_VERSION=14
ANDROID_BUILD_TARGET_SDK_VERSION=26
ANDROID_BUILD_SDK_VERSION=android-26
ANDROID_BUILD_TOOL_VERSION=26.0.2
ANDROID_SUPPORT_LIB_VERSION=26.1.0
GOOGLE_PLAY_SERVICES_VERSION=11.4.2
JUNIT_VERSION=4.12
ROBOLECTRIC_VERSION=3.3.2
MOCKITO_VERSION=2.9.0
HAMCREST_VERSION=1.3

org.gradle.daemon=true
org.gradle.parallel=true
org.gradle.configureondemand=true
org.gradle.jvmargs=-Xmx2048M
NEXUS_USERNAME=username
NEXUS_PASSWORD=1234
NEXUS_RELEASE_REPOSITORY_URL=url1
NEXUS_SNAPSHOT_REPOSITORY_URL=url2
추측하건데, 현재는 DEFAULT_PHASE가 release라고 되어있지만, 예를 들어 카카오 측에서.. 내부 개발할 때 debug용으로 바꾸기 위해서 task changePhase가 이용될 것 같습니다.

그 다음 코드는
task revertPhase {
doLast {
println '>>> reverting DEFAULT_PHASE to release'
ant.propertyfile(file: "${rootProject.projectDir}/gradle.properties") {
entry(key: 'DEFAULT_PHASE', value: 'release')
}
}
}
위와 같습니다. changePhase와.. 같은 내용이라 넘어가겠습니다.



task bumpVersionInProperties {
doLast {
if (!git) {
println "This is not a git repository. Using KAKAO_SDK_VERSION in gradle.properties."
return;
}

if (!project.hasProperty('KAKAO_SDK_VERSION')) {
println "There is no KAKAO_SDK_VERSION in gradle.properties.."
return;
}

if (rootProject.version.startsWith(project.property("KAKAO_SDK_VERSION")) && project.property("VERSION_CODE").toInteger() == rootProject.versionCode) {
println "KAKAO_SDK_VERSION is already updated to the latest version."
return;
}
ant.propertyfile(file: "${rootProject.projectDir}/gradle.properties") {
if (rootProject.hasProperty('KAKAO_SDK_VERSION')) {
println "bumping KAKAO_SDK_VERSION to ${rootProject.version}"
entry(key: 'VERSION_CODE', value: rootProject.versionCode)
entry(key: 'KAKAO_SDK_VERSION', value: rootProject.version)
}
}
}
}

bumpVersionInProperties라는 이름의 task를 정의하는 코드입니다. gradle.properties에 VERSION_CODE와 KAKAO_SDK_VERSION을 갱신할 때 사용하는 task 같습니다.


그 다음 코드는

task copyFullSource(dependsOn: [cleanTask, changePhase, bumpVersionInProperties]) {
doLast {
copy {
from '.'
into project.DIST_FULL_SOURCE_DIR
exclude project.DIST_DIR
exclude relativePath(project.buildDir)
exclude '.gradle'
exclude '.idea'
exclude '**/*.iml'
exclude '**/*.sh'
exclude '**/README.md'
exclude '**/.build'
exclude '**/build'
exclude '**/.DS_Store'
exclude '**/doctemplate'
exclude '_gradle.properties'
exclude 'kakao-open-android-sdk-sample/build.gradle'
exclude 'kakao-open-android-ageauth-sample'
exclude 'auth/src/test'
exclude 'friends/src/test'
exclude 'kakaolink/src/test'
exclude 'kakaostory/src/test'
exclude 'kakaotalk/src/test'
exclude 'network/src/test'
exclude 'push/src/test'
exclude 'storage/src/test'
exclude 'usermgmt/src/test'
exclude 'util/src/test'
exclude 's2/src/test'
exclude 'kakao-open-android-link-sample/src/test'
exclude 'kakao-open-android-link-sample/src/androidTest'
exclude 'kakao-open-android-sdk-sample/src/test'
exclude 'kakao-open-android-sdk-sample/src/androidTest'
}

copy {
from 'gradle.properties'
into project.DIST_FULL_SOURCE_DIR

filter(ConcatFilter, append: file('_gradle.properties'))
}

String content = ""
File proFile = new File( "${project.DIST_FULL_SOURCE_DIR}/settings.gradle" )
proFile.readLines().each { String line ->
if (!line.contains(':kakao-open-android-ageauth-sample')) {
content = content.concat(line + '\n')
}
}
proFile.write(content)
}

finalizedBy('revertPhase')
}

위와 같습니다. 긴.. task네요. task의 이름이 copyFullSource인 걸로 보아.. 소스를 특정 폴더?에 복사할 때 사용되는 task인 것 같습니다. 어떻게 동작되는지 살펴봅시다.

dependsOn 이라는 부분이 있습니다. 아래의 예제를 보면 알 수 있습니다.


https://docs.gradle.org/current/userguide/tutorial_using_tasks.html

Example 16.6. Declaration of task that depends on other task

build.gradle

task hello {
    doLast {
        println 'Hello world!'
    }
}
task intro(dependsOn: hello) {
    doLast {
        println "I'm Gradle"
    }
}

Output of gradle -q intro

> gradle -q intro
Hello world!
I'm Gradle 


copuFullSource task를 실행하면.. 그 이전에 cleanTask, changePhase, bumpVersionInProperties task를 먼저 실행하도록 하는 코드로 보입니다.


그 다음에는 처음보는 copy {} 블럭이 등장합니다. 찾아보니..

https://docs.gradle.org/current/dsl/org.gradle.api.Project.html#org.gradle.api.Project:copy(groovy.lang.Closure)

WorkResult copy(Closure closure)

Copies the specified files. The given closure is used to configure a CopySpec, which is then used to copy the files. Example:

copy {
   from configurations.runtime
   into 'build/deploy/lib'
}

말 그대로.. 복사할 때 사용되는 블럭?입니다. 복사를 위한 source와 destination.. 그리고 제외되어야하는 파일을 설정할 수 있습니다.


그 다음 copy {}블럭에는 
filter(ConcatFilter, append: file('_gradle.properties'))
가 등장합니다. ConcatFilter라는 것으로 봐선 gradle.properties의 내용을 _gradle.properties라는 파일의 끝에.. 특정 내용을 쓰라는 것 같습니다.

그리고 마지막에

finalizedBy('revertPhase')
라는 코드가 있습니다.

위의 사이트에 자세한 설명이 있지만, 여기서는 간단하게 revertPhase task를 실행시키는 코드라고 이해하면 될 것 같습니다.

그 다음 코드는
task zipFullSource(type: Zip, dependsOn : copyFullSource) {
from project.DIST_FULL_SOURCE_DIR
destinationDir=file(project.DIST_DIR)
baseName="kakao-android-sdk-project"
version=project.version
classifier='full'
delete project.DIST_FULL_SOURCE_DIR
}

zipFullSoucre라는 task입니다. 이름만 들어봐도 소스를 압축할 때 쓰이는 task 같습니다. kakao측에서 개발한 소스를 압축해서 공식사이트에 배포하는데 이 때 사용되겠죠?
소스를 압축하기 전에.. 압축할 소스를 준비해야 하므로 dependsOn에 copyFullSource가 있는 것은 이해가 됩니다.
그러면 type: Zip은 무슨 역할을 할까요?

task의 type에는 여러가지가 있습니다.

위의 task처럼 파일 압축과 관련된 동작을 하기 위해서는 type: Zip을 사용해야 합니다.
Android Studio Terminal에서 gradlew zipFullSource 명령어로 위의 task를 실행하니.. 실제로 압축이 됩니다.
type: Zip 부분을 제거하면, 에러가 발생합니다.

제가 이해하기로는.. 이 type관련된 코드가.. java와 같은 언어에서 클래스 상속을 받는 것과 유사한 느낌이 듭니다. 부모 클래스에서 특정한 변수와 함수가 있어 이를 자식 클래스에서 사용할 수 있듯이, Zip이라는 task?가 미리 정의되어 있어서 task zipFullSource(type: Zip)라고 하면.. zipFullSource에서 Zip task?의 특정 변수와 함수를 사용할 수 있는 것 같습니다.
아마.. 내부적으로는 상속과 비슷한 개념처럼 동작하지 않을까요?

이제.. 루트의 build.gradle 마지막 코드입니다.
static def bumpVersion(Project project, String updateType) {
def phase = project.hasProperty("deploy_phase") ? project.property("deploy_phase") : project.DEFAULT_PHASE
String rawVersion = project.ext.version
String[] version = rawVersion.split("\\.");
String updatedVersion;
String delimiter = ".";
int newVersion;
if (updateType == 'revision') {
newVersion = Integer.valueOf(version[2]) + 1;
updatedVersion = version[0] + delimiter + version[1] + delimiter + newVersion;

} else if (updateType == 'major') {
newVersion = Integer.valueOf(version[0]) + 1;
updatedVersion = newVersion + delimiter + 0 + delimiter + 0;

} else if (updateType == 'minor') {
newVersion = Integer.valueOf(version[1]) + 1;
updatedVersion = version[0] + delimiter + newVersion + delimiter + 0;
} else {
return rawVersion;
}
if (phase != 'release') {
updatedVersion += "-SNAPSHOT";
}

project.versionCode = project.ext.versionCode + 1;
project.version = updatedVersion;
return updatedVersion;
}

코드가 길긴 하지만.. 내부 동작은 버전을 설정하는 간단한 코드이기에 실제로 봐야할 코드는 적습니다.

static def bumpVersion(Project project, String updateType) {} 는 무엇일까요?


일단 bumpVersion이라는 이름의 method입니다. gradle에서 method도 정의할 수 있군요.


https://www.tutorialspoint.com/groovy/groovy_methods.htm

A method is in Groovy is defined with a return type or with the def keyword. Methods can receive any number of arguments. It’s not necessary that the types are explicitly defined when defining the arguments. Modifiers such as public, private and protected can be added. By default, if no visibility modifier is provided, the method is public. 


위 사이트의 설명을 보면.. Groovy에서 함수는 리턴 타입이나 def 키워드와 함께 정의된다고 합니다. 그래서 static def bumpVersion()에서.. 중간에 def가 쓰인 것 같은데 별 의미는 없는 것으로 추측합니다.


동일 사이트의 아래 설명을 보면 왜 static의 역할을 알 수 있습니다.

class Example { 
   int x; 
	
   public int getX() { 
      return x; 
   } 
	
   public void setX(int pX) { 
      x = pX; 
   } 
	
   static void main(String[] args) { 
      Example ex = new Example(); 
      ex.setX(100); 
      println(ex.getX()); 
   } 
}

In our above example, note that this time we are specifying no static attribute for our class methods. In our main function we are actually creating an instance of the Example class and then invoking the method of the ‘ex’ object.

The output of the above method would be the value 100.

 

static의 역할은 java언어랑 똑같은 것 같습니다. 그런데 왜 static을 썼는지는 잘 모르겠습니다.

서브 프로젝트의 gradle에서.. 루트 gradle의 함수에 접근하려면 static이어야 하지 않을까 추측합니다.







작성자

Posted by 드리머즈

관련 글

댓글 영역