안드로이드 개발자를 혼란스럽게 만드는 Gradle의 5가지 요소

Sehee Jeong·2022년 9월 11일
4

사전 동의 후 올리는 5 Gradle Things that get Android Developers Confused 번역본입니다.




안드로이드 개발자를 혼란스럽게 만드는 Gradle의 5가지 요소

프로젝트 내부에 있는 Gradle script는 안드로이드 개발자에게 있어서 마법의 주문서처럼 보일 수 있습니다.

우리가 앱 개발을 하면서 Gradle을 이용해 상호작용 하는 것은 의존성을 추가하거나, target SDK, Gradle version 업데이트, build flavor 혹은 build type 을 작업하는 것으로만 국한되었을거예요. 빌드가 실패할 때마다 stack overflow 에서 답을 찾으려고도 했을거고요. 만약 여러분이 프로젝트를 모놀리틱에서 멀티모듈로 분리하고자 한다면, gradle.settings를 다뤄봐야하는 시기가 올텐데,...🤔 내가 gradle에 대해서 잘 알지 못하고, 설상가상으로 gradle docs가 이해되지 않는다면? 이 글이 당신의 첫 시작에 조금이나마 도움이 될 수 있을 것이라고 생각합니다 :)

Gradle이 무엇인가요?

Gradle은 application에 대한 설명이 들어있는 빌드 자동화 툴이며, gradle을 이용해 android project를 구성할 수 있습니다. 보통 gradle script는 Groovy 혹은 Kotlin DSL을 사용하게 됩니다. 이 포스트에서는 많이 사용되고 있는 Groovy 문법을 통해 Gradle을 설명할 예정입니다만, groovy script를 어느정도 알아야 Kotlin DSL도 자유롭게 사용 및 이해할 수 있다는 점을 참고해주세요.


우리가 Gradle을 어려워하는 이유

  • 우리는 모듈, 패키지에 속한 클래스나 사용하는 언어에 대해서는 잘 알고 있습니다. 프로덕트를 만들기 위해 존재하고, 항상 마주치는 곳이기 때문이죠. 하지만 Gradle은 프로젝트 빌드를 도와주는 도구일 뿐이기에 한번 셋팅이 완료되면 자주 만지지 않는 부분입니다. 그러므로 상대적으로 관심이 덜 할수 밖에 없습니다.
  • 여러 가지 방법으로 동일 작업을 수행할 수 있습니다.
  • 생각보다 StackOverflow에 모여있는 Gradle 관련 해답들은 상당수 레거시가 많아, 실제 현업에 적용하기에 어려운 부분도 존재합니다.
  • Groovy Magic 🧙

이제 Gradle에 대해 조금씩 이해해봅시다.


1) Task를 실행할 때 "gradle taskName" 대신에 "./gradlew taskName" 을 사용하는 이유

우리는 local이 아닌 프로젝트 내부에서 gradle wrapper를 사용하여 환경을 구성하고 있습니다. 즉, 프로젝트에 참여하고 있는 개발자들에게 gradle wrapper를 통해 프로젝트 내부에 한해서는 일관성있는 버전을 제공하기 위함입니다. local에서 gradle 셋팅을 하는 경우에는 gradle command를 사용할 수 있지만, 보통 안드로이드 프로젝트에서는 local에 직접 설치하여 사용하지는 않기 때문에 Gradle wrapper를 이용하여 프로젝트 디렉토리 안에서는 동일한 gradle 환경을 맞추고 실행도 가능하도록 만드는 것입니다. 이 말은 반대로, 자신이 작업하고 있는 프로젝트 외부에서는 gradle command를 실행할 수 없다는 것입니다.

💡 Reference : https://kotlinworld.com/314


2) Android Studio의 Modules과 Gradle의 Projects

하나의 android project도 gradle project입니다. gradle은 project와 build.gradle 파일 간 1:1 관계를 가지고 있습니다. 동시에 안드로이드 프로젝트에서 module이라고 불리는 것은 gradle에서는 proejct라고 불립니다.


3) 문자열 작성을 위한 작은따옴표('), 큰따옴표(")

Groovy는 작은따옴표('), 큰따옴표(") 둘 다 문자열로 인식합니다.

Groovy에서 String type은 다음과 같이 나타낼 수 있습니다 :

  • 'Hello World 1' -- String
  • "Hello World 2" -- String
  • "Hello World $count" -- GString

4) 함수의 호출, 프로퍼티, 대괄호 사용 시기

  • 함수가 최소 하나의 프로퍼티를 가지고 있으면, 대괄호는 무시할 수 있습니다.
  • 클래스 필드에는 필드를 명시할 수 있는 getter/setter 를 만들어야 합니다.

./gradlew showMagic (Mac) or gradle showMagic (Windows)를 통해 아래 코드가 실행될 때 Foo.name() 함수가 얼마나 많이 트리거될지 유추해보세요.

class Foo {
    def name = ""

    void name(String newString) {
        name = newString
        println("Foo.name() triggered")
    }
}

tasks.create("showMagic") {
    group "Magic group"
    description "Check setter and getter capabilities"
    
    doFirst {
        def foo = new Foo()

        foo.name = "string 1"
        println("showMagic() ${foo.name}")
        
        foo.name("string 2") //대괄호 추가
        println("showMagic() ${foo.name}")
        
        foo.name "string 3" //대괄호 무시
        println("showMagic() ${foo.name}")
        
        foo.setName("string 4") //setter
        println("showMagic() ${foo.name}")
        
        foo.setName "string 5"
        println("showMagic() ${foo.name}")
        
        String propertyOrFunctionName = "name"
        foo."$propertyOrFunctionName" = "string 6"
        println("showMagic() ${foo.name}")
    }
}

결과:

showMagic() string 1
Foo.name() triggered
showMagic() string 2
Foo.name() triggered
showMagic() string 3
showMagic() string 4
showMagic() string 5
showMagic() string 6

대괄호를 무시할 수도 있고 콤마(,)로 구분하여 파라미터를 전달할 수도 있습니다.


void foo(String url, String parameter) {
    println url
    println parameter
}

tasks.create("callFoo") {
    doFirst {
        foo "google.com", 'consistency'
        println "New string"
   }
}

이 사실을 알고 있으면 우리 프로젝트에 존재하는 gradle 파일들을 더욱 일관성 있는 코드 스타일을 유지할 수 있을겁니다.


5) 주요 Gradle entity와 building block 개념

우리가 사용하고자 하는 gradle의 이름이나 용도를 알지 못한다면, 문제해결이 더 어려워질 수 있습니다. 아래는 Gradle project에 대한 공통적인 실행범위 및 구성 요소입니다. (Android Plugin이 추가되는 것과 혼동하지 마세요.)


Entities:

  • Project: build file로부터 다른 구성요소들에 대해 상호작용을 도와주는 최상위 구성요소입니다. Gradle docs에서는 "Gradle Task 집합체"라고도 불립니다.

  • Task: group, description, dependsOn와 같은 여러 default property를 가지고 있고, doFirst, doLast (etc. gradle docs) 와 같은 method 를 이용해 구성할 수 있습니다. Task에서 가장 중요한 것은 결과나 상태를 가지고 있다는 것이어서, property와 method를 이용해 실행 시점을 결정할 수 있다는 것입니다.

  • Action: Task가 실행되는 동안 실행되는 실제 코드입니다.

  • Plugin: Gradle plugin 기능을 확장하는 구성요소와 Task들의 집합체입니다. apply plugin: <pluginName>, apply from: <file.gradle> 형식으로 추가됩니다.


Build script blocks:

  • buildScript {} : 이 block 에서 모든 것이 시작됩니다. 나중에 기능을 확장하고 plugin을 적용하기 위해 추가적인 의존성을 정의할 수 있습니다. 보통 repositories{}, dependencies{} block 이 포함되어 있습니다.

  • repositories {} : plugin이나 의존성을 어디로부터 받아올 것인지 명시하는 곳입니다.

  • configurations {} : 이 script block을 본 적이 없을 수 있습니다. 이 block은 dependencies{} block 내부에서 사용되는 의존성 범위를 명시하는 블록입니다. 의존성 충돌을 해결하기 위한 전략으로 해당 block이 정의되곤 합니다.
    classPath, implementation, api 그리고 안드로이드 plugin으로 사용되는 androidTestImplementation, debugImplementation 가 configurations 의 예시입니다.

  • dependencies {} : 프로젝트에서 사용되는 종속성이나 라이브러리를 정의하는 곳입니다.

  • plugins {} : plugins block은 반드시 script의 가장 첫번째로 나타나야 하며, id 'plugin.name' 형식의 plugin id가 포함됩니다. 해당 block은 PluginDependenciesSpec 의 한 유형이며, 의존성을 적용할 때 plugin id 나 apply from: 'dependencies.gradle' 대신에 apply plugin: <pluginName> 형태로도 사용할 수 있습니다.

  • allProjects {} / subProjects {} : 모든 프로젝트 혹은 하위 프로젝트에 적용되어야 하는 종속성과 저장소를 정의하는 곳입니다. 보통 안드로이드 프로젝트에서는 project 수준의 build.gradle 에 정의되어 있습니다.



이 포스팅을 읽은 후에는, 독자에게 있어 Gradle script가 더이상 마법🧙의 주문서가 아닌 잘 이해할 수 있는 하나의 스크립트 언어가 되기를 기대합니다.

profile
android developer @bucketplace

0개의 댓글