[번역] Gradle(2)

rin·2020년 4월 14일
1

Document 번역

목록 보기
5/22
post-thumbnail

2020.04.14 추가

A Groovy Build Script Primer

Groovy 빌드 스크립트는 대부분 configuration 같다. 예를 들면 프로젝트의 일부 property 설정, 종속성 구성, task 선언 등이 있다. 이런 Configuration은 Groovy 언어 구성을 기반으로 한다. 이 입문서는 이러한 Configuration이 무엇인지 설명하며 가장 중요한 것은 Gradle의 API 문서와 어떻게 관련되는지에 대한 내용이다.

Project 객체

Groovy는 Java를 기반으로 하는 객체 지향 언어이므로 속성과 메소드가 객체에 적용된다. 어떤 경우에는 객체가 암시적인데 특히 빌드 스크립트의 최상위 레벨에서 그렇다 (e.g. {} 블록 안에 중첩되지 않음)

다음은 규정되지 않은 property 및 블록을 포함하는 빌드 스크립트의 일부이다.

version = '1.0.0.GA'

configurations {
	...
}

versionconfigurations {} 모두 org.gradle.api.Project의 일부이다.

이 예제는 모든 Groovy 빌드 스크립트가 암시적인 프로젝트의 인스턴스에 의해 어떻게 지원받는지에 대한 방식을 보여준다. 규정되지 않은 요소가 어디에 정의됐는지 모르는 경우 항상 프로젝트 API 도큐먼트를 확인하여 올바른 위치인지 확인해야한다.

빌드 스크립트에서 Groovy MetaClass 프로그래밍 기술을 사용하지 마라. Gradle은 동적 런타임 속성을 추가하기위한 자체 API를 제공한다.

Groovy의 특수한 MetaClass 프로그래밍을 사용하면 빌드간에 많은 양의 메모리를 유지하므로 Gradle daemon이 메모리 부족을 초래할 수 있다.


🔎 Gradle deamon guide
Gradle은 JVM에서 실행되며 초기화 시간이 요구되는 여러 라이브러리를 사용한다. 결과적으로 이를 시작하는데 성능이 떨어질 수가 있는데, 이에 대한 해결책이 Gradle daemon이다. Gradle daemon은 다른 방법보다 훨씬 빨리 빌드를 실행하는 오래 지속되는 백그라운드 프로세스이다. 프로젝트에 대한 데이터를 메모리에 보관하여 부트 스트랩 프로세스를 피하고 캐싱을 활용해 이를 수행한다. 데몬으로 Gradle 빌드를 실행하기 위해선 사용 여부를 설정하기만 하면 된다.

Properties

<obj>.<name>                // 속성값 가져오기
<obj>.<name> = <value>      // 새로운 값으로 속성 설정
"$<name>"                   // 문자열에 속성값 포함된 경우
"${<obj>.<name>}"           // 위와 동일

/***
	Example
*/
version = '1.0.1'
myCopyTask.description = 'Copies some files'

file("$buildDir/classes")
println "Destination: ${myCopyTask.destinationDir}"

속성(property)은 객체의 일부 상태를 나타낸다. = 기호를 통해 보고 있는 속성이 무엇인지 명확히 할 수 있다. 그렇지 않다면, 다른 꾸밈이 없이 <obj>로 시작하는 이름도 property이다.

이름이 규정되지 않은 경우는 다음 중 하나 일 수 있다.

  • 해당 이름의 task 인스턴스
  • Project의 속성
  • 추가 속성이 다른 프로젝트에서 정의
  • 블록 내에 암시적으로 존재하는 객체의 속성
  • 이런 빌드 스크립트에서 정의된 지역 변수

플러그인은 프로젝트 객체에 자체 속성을 추가 할 수 있다. API 도큐먼트 리스트의 모든 속성은 코어 플러그인으로부터 추가돼있다. 속성의 출처를 찾는 것이 힘들다면 빌드에서 사용 중인 플러그인의 도큐먼트를 확인하도록 해라.

코어가 아닌 플러그인으로 추가된 빌드 스크립트에서 프로젝트 속성을 참조할 경우, 속성이 프로젝트 객체에 속할 것이 분명하므로 접두사로 project를 고려해라.

🔎API document 내의 property
Groovy DSL reference는 빌드 스크립트 내에서 property로서 사용될 수 있음을 표현하고 있으나, property는 뒷단에서 메소드로서 구현되기 때문에 Javadocs는 오로지 메소드만 표시한다.

  • argument가 없는 get<PropertyName> 메소드를 이용해 property를 읽을 수 있다. (같은 타입의 property를 리턴)
  • 해당 속성값과 같은 타입의 argument 하나를 가지는 set<PropertyName> 메소드를 이용해 속성을 수정할 수 있다. (리턴값 없음-void)

property 이름은 일반적으로 소문자로 시작하지만, 메소드 이름은 대문자로 시작한다. 따라서 getter 메소드 getProjectVersion()는 projectVersion의 property에 해당한다. 이 규칙은 이름이 두 개 이상의 대문자로 시작할 때는 적용되지 않는다. (e.g. getRAM()은 property로써의 RAM에 해당한다.)

/***
	Example
*/
project.getVersion() //아래와 동일
project.version

project.setVersion('1.0.1') //아래와 동일
project.version = '1.0.1'

Methods

<obj>.<name>()              // Method call with no arguments
<obj>.<name>(<arg>, <arg>)  // Method call with multiple arguments
<obj>.<name> <arg>, <arg>   // 위와 동일, 괄호 생략한 경우

/***
	Example
*/
myCopyTask.include '**/*.xml', '**/*.properties' //<obj>.<name> <arg>, <arg>

ext.resourceSpec = copySpec()   // `copySpec()` comes from `Project`   
				// <obj>.<name>()  

file('src/main/java') 	//'file( .. )' comes from 'Project'
			// <obj>.<name>(<arg>)
println 'Hello, World!' // <obj>.<name> <arg> 괄호 생략됨

Gradle은 종종 메서드를 객체의 상태를 구성하는데에 사용하는 반면에, 메소드는 객체의 일부 동작을 나타낸다. 메소드는 argument나 빈 괄호로 식별 가능하다. 메서드에게 argument가 없는 경우와 같이 빈 괄호가 필요가 필요한 경우도 있으므로, 항상 괄호를 사용하는 것이 (해석하기에) 간단할 수 있다.

Gradle에는 메소드가 콜렉션 기반의 프로퍼티와 동일한 이름인 경우, 이 메소드는 그 콜렉션에 값을 추가한다.

Block

블록 또한 메소드이며, 마지막 argument에 대한 특별한 타입일 뿐이다.

<obj>.<name> {
     ...
}

<obj>.<name>(<arg>, <arg>) {
     ...
}

/***
	Example
*/
configurations {
    assets
}

sourceSets { //sourceSets.main.java.srcDirs = ['src']
    main {
        java {
            srcDirs = ['src']
        }
    }
}

project(':util') {
    apply plugin: 'java-library'
}

블록은 빌드 요소의 여러 측면을 한번에 구성하기 위한 메커니즘이다. configuration을 중첩하는 방법을 제공함으로써 구조화된 데이터 형식을 나타내게 해준다.
블록에는 두가지 중요한 측면이 있다.
1. 특정한 signature를 가진 메소드로 구현된다.
2. 규정되지 않은 메소드 및 property의 대상("delegate, 대리인")을 변경할 수 있다.
둘 다 Groovy 언어가 지닌 기능을 기반으로 하며 다음 섹션에서 설명한다.

Block method signatures
signature나 좀 더 구체화된 표현을 통해 블록 뒤에 구현된 메소드를 좀 더 쉽게 식별 할 수 있다. 만약 메소드가 블록에 해당하는 경우 :

  • 적어도 하나의 argument가 필요하다.
  • 마지막 argument는 groovy.lang.Closure type이나 org.gradle.api.Action type이어야 한다.

예를 들어, Project.copy(Action)은 이런 요구 사항과 일치하므로 다음 구문을 사용할 수 있다.

copy {
    into "$buildDir/tmp"
    from 'custom-resources'
}

어떻게 into()와 from() 작업을 하는 걸까? 이것들은 메소드가 분명하지만 API 도큐먼트의 어디서 찾을 수 있을까? 정답은 객체의 위임을 이해함으로써 나온다.

Delegation
규정되지 않은(unqualified) 속성을 찾을 수 있는 속성 목록의 섹션 중 하나의 공통 장소는 프로젝트 오브젝트에 있다. 그러나 블록 내부에 그런 규정되지 않은 속성 및 방법의 대체 소스인 the block’s delegate object가 존재한다.

이 개념을 설명하기 위해 이전 섹션의 마지막 예를 다시 보도록 하자.

copy {
    into "$buildDir/tmp"
    from 'custom-resources'
}

이 예제의 모든 메소드와 특성은 규정되지 않았다. Project API 도큐먼트 내에서 copy()와 buildDir을 찾겠지만 into()와 from()은 어떡할 것인가? 이는 copy {} 블록의 위임(delegate)에 의해 해결된다. deledate의 유형이 무엇인지 알아내기 위해선 해당하는 API 도큐먼트를 참조하도록 한다.

블럭 메소드의 signature에 따라 delegate 유형을 결정하는 두 가지 방법이 있다.

  • Action argument인 경우 타입의 파라메터를 확인한다.
    위 예제에서 메소드의 signature는 copy(Action< ? super CopySpec>)이다. 그리고 '< >'광호 내의 비트로서 delegate type(이 경우엔 CopySpec)을 알려준다.
  • Closure arguments인 경우에는 문서에는 어떤 타입으로 구성됐는지, 어떤 타입의 delegate인지(같은 것에 대한 다른 용어)에 대해 명시적으로 설명되어있다.
    따라서 CopySpec에서 into()와 from()을 모두 찾을 수 있을 것이다. 두 메소드 모두 마지막 인수로 Action을 사용하는 변형이 있을 수 있으며, 이는 블록 구문을 함께 사용할 수 있음을 의미한다.
    모든 새로운 Gradle API는 Closure가 아닌 Action 인수 유형을 선언하므로, delegate type을 쉽게 선택할 수 있다. 이전 API도 기존의 Closure에 추가적인 Action의 변형이 있다.

2020.04.15 추가

Project API

이 인터페이스는 빌드 파일에서 Gradle과 상호 작용하는데 사용하는 기본 API이다. Project로 부터 모든 Gradle 기능에 프로그래밍 방식으로 엑세스 가능하다.

Lifecycle

Project와 build.gradle 파일 사이에는 일대일 관계가 있다. 필드 초기화 중 Gradle은 빌드에 참여할 각 프로젝트에 대한 객체를 다음과 같이 조립한다.

  1. 빌드를 위한 Settings 인스턴스를 작성한다.
  2. settings.gradle 스크립트가 존재한다면, 이에 따라서 Setting 오브젝트를 구성한다.
  3. 구성된 Setting 오브젝트를 사용해 Project 인스턴스 계층을 만든다.
  4. 마지막으로, build.gradle 파일에 의해 실행될 Project가 존재한다면 Project를 구성한다. Project는 하위 프로젝트보다 먼저 측정될 수 있도록 범위가 넓은 순서로 확인한다. 이 순서는 Project.evaluationDependsOnChildren()이나 Project.evaluationDependsOn(java.lang.String)를 사용해 명시적 평가 종속성(evaluation dependency)을 추가함으로써 무시 할 수 있다.

✔️Project.evaluationDependsOnChildren() : 각 하위 프로젝트에 대한 평가 종속성이 있음을 선언한다.
✔️Project.evaluationDependsOn(java.lang.String) : 주어진 경로를 가진 프로젝트에 대한 평가 종속성이 있음을 선언한다.

Tasks

프로젝트는 기본적으로 task 객체의 모음이다. 각 task는 클래스 컴파일, 단위 테스트 실행, WAR 파일 압축과 같은 기본 작업을 수행하다. TaskContainer의 create() 메소드 중 하나를 사용해 프로젝트에 task를 추가할 수 있다.(e.g. TaskContainer.create(java.lang.String)). TaskContainer의 lookup 메소드 중 하나를 사용하여 기존 task를 찾을 수 있다.(e.g. TaskCollection.getByName(java.lang.String))

Dependencies

프로젝트는 일반적으로 task를 수행하는데 필요한 많은 의존성이 있다. 또한 프로젝트는 일반적으로 다른 프로젝트에서 사용할 수 있는 많은 아티책트를 생성한다. 이러한 의존성은 configuration으로 그룹화되며 리포지토리에서 검색, 업로드 할 수 있다.

  • Project.getConfigurations() 메소드의 반환값인 ConfigurationContainer를 사용해 configuration을 관리할 수 있다.
  • Project.getDependencies() 메소드의 반환값인 DependencyHandler는 dependency를 관리한다.
  • Project.getArtifacts() 메소드의 반환값인 ArtifactHandler는 아티팩트를 관리한다.
  • Project.getRepositories() 메소드의 반환값인 RepositoryHandler는 레포지토리를 관리한다.
메소드output관리하는 대상
Project.getConfigurations()ConfigurationContainerconfiguration
Project.getDependencies()DependencyHandlerdependency
Project.getArtifacts()ArtifactHandler아티팩트
Project.getRepositories()RepositoryHandler레포지토리

Multi-project Builds

프로젝트는 프로젝트의 계층구조를 따라 정렬된다. 프로젝트에는 이름과 계층 구조에서 프로젝트를 고유하게 식별하는 정규화된 경로가 존재한다.

Plugins

프로젝트 구성을 모듈화하고 재사용하는데 사용할 수 있다. 플러그인은 PluginAware.apply(java.util.Map) 메소드를 사용하거나 PluginDependenciesSpec 플러그인 스크립트 블록을 사용해 적용할 수 있다.

Dynamic Project Properties

Gradle은 프로젝트를 구성하기 위해 프로젝트 인스턴스에 대한 프로젝트 빌드 파일을 실행한다. 스크립트가 사용하는 모든 property 또는 메소드는 관련 프로젝트 객체에 위임된다. 즉, 스크립트에서 프로젝트 인터페이스의 메소드와 property를 직접 사용할 수 있다.

For example:

defaultTasks('some-task')  // Delegates to Project.defaultTasks()
reportsDir = file('reports') // Delegates to Project.file() and the Java Plugin

또한 프로젝트 property를 사용해 프로젝트 인스턴스에 엑세스 할 수 있다. 이는 경우에 따라 스크립트를 더욱 명확하게 만든다. 예를 들어 프로젝트 이름을 엑세스 하는 경우 name대신 project.name을 사용할 수 있다.

프로젝트에는 5개의 속성 scope가 있으며 property를 검색한다. 빌드 파일에서 이름을 사용하거나, 프로젝트의 Project.property(java.lang.String) 메소드를 호출함으로써 그러한 property를 엑세스 할 수 있다.

스코프scope는 다음과 같다.

  • 프로젝트 객체 자체
    • 이 스코트는 프로젝트 구현 클래스에서 선언한 모든 property getter, setter가 포한된다.
    • 예를 들어, Project.getRootProject()를 이용해 rootProject의 property를 엑세스할 수 있다.
    • 이 범위의 property는 해당 getter 또는 setter 메서드의 존재 여부에 따라 읽거나 쓸 수 있다.
  • 프로젝트의 추가 property
    • 각 프로젝트는 임의의 이름 -> 값 쌍을 포함 할 수 있는 map 타입의 추가적인 property를 유지보수한다.
    • 일단 정의되면 이 범위의 프로퍼티를 읽고 쓸 수 있다.
    • 자세한 내용은 추가속성을 참조한다.
  • 플러그인에 의해 프로젝트에 추가된 확장
    • 각 확장(extension)은 확장과 동일한 이름을 가진 읽기 전용 property로 사용할 수 있다.
  • 플러그인에 의해 프로젝트에 추가된 convention property
    • 플러그인은 프로젝트의 convention 객체를 통해 프로젝트에 property와 메소드를 추가할 수 있다.
    • 이 범위의 프로퍼티는 컨벤션 객체에 따라 읽거나 쓸 수 있다.
  • 프로젝트의 task
    • task 이름을 프로퍼티 이름으로 사용해 task를 엑세스 할 수 있다.
    • 이 범위의 프로퍼티는 읽기 전용이다.
    • 예를 들어, compile이라는 task는 compile 프로퍼티로 엑세스 할 수 있다.
  • 추가 프로퍼티와 컨벤션 프로퍼티는 프로젝트의 부모에서 재귀적으로 루트 프로젝트까지 상속된다.
    • 이 범위의 프로퍼티는 읽기 전용이다.

📌프로퍼티를 읽을 때 프로젝트는 위의 스코프를 순서대로 검색하고, 해당 프로퍼티를 처음 찾은 스코프에서 값을 반환한다. 자세한 내용은 Project.property(java.lang.String)를 참조하라.


Object property(String propertyName)

요청한 프로퍼티의 값을 반환한다. 이 메소드는 다음과 같이 프로퍼티를 찾는다.
1. 이 프로젝트 객체에 해당하는 이름이 있으면, 값을 반환한다.
2. 이 프로젝트에 해당하는 이름의 확장자(extension)이 있으면 확장자를 반환한다.
3. 이 프로젝트의 컨벤션 객체에 해당 이름으로 지정된 프로퍼티가 있으면 값을 반환한다.
4. 이 프로젝트에 해당 이름의 추가 프로퍼티가 있으면 값을 반환한다.
5. 이 프로젝트에 해당 이름의 task가 있으면 task를 반환한다.
6. 이 프로젝트의 조상 프로젝트에서 해당 이름의 컨벤션 프로퍼티나 추가 프로퍼티를 검색한다.
7. 찾을 수 없으면 MissingPropertyException이 발생한다.


📌프로퍼티를 작성할 때 프로젝트는 위의 스코프를 순서대로 검색하고 프로퍼티를 처음 찾은 스코프에서 값을 설정한다. 해당 프로퍼티를 찾지 못하면 예외가 발생한다. 자세한 내용은 Project.setProperty(java.lang.String, java.lang.Object)를 참조하라.


void setProperty(String name, Object value)

해당 프로젝트의 속성을 설정한다.
이 메소드는 다음 위치에서 지정된 이름의 프로퍼티를 검색하고, 프로퍼티를 발견하는 첫번째 스코프에서 값을 설정한다.
1. 해당 프로젝트 오브젝트 자신. 예를 들어 rootDir 프로젝트의 프로퍼티
2. 프로젝트의 컨벤션. 예를 들어 srcRootName 플러그인의 프로퍼티
3. 프로젝트의 추가 프로퍼티.
속성을 찾을 수 없으면 MissingPropertyException이 발생한다.


Extra Properties

모든 추가 프로퍼티는 ext 네임 스페이스를 통해 정의해야한다. (🤔커스텀 프로퍼티라고 이해하면 될듯) 추가 속성이 정의되면 소유 객체(아래 예시에서 각 프로젝트, task, 하위 프로젝트)에서 직접 사용할 수 있으며 읽거나 업데이트 또한 가능하다. 네임 스페이스를 통해 초기 선언만 수행하면 된다.

project.ext.prop1 = "foo"
task doStuff {
    ext.prop2 = "bar"
}
subprojects { ext.${prop3} = false }

ext 또는 소유한 객체를 통해 추가 프로퍼티를 읽는다.

ext.isSnapshot = version.endsWith("-SNAPSHOT")
if (isSnapshot) {
    // do snapshot stuff
}

Dynamic Methods

프로젝트에는 5개의 메소드 scope가 잇으며 메소드를 검색한다.

  • 프로젝트 객체 자체
  • 빌드 파일
    • 프로젝트는 빌드 파일에 선언된 동일한 메소드를 찾는다.
  • 플러그인에 의해 프로젝트에 추가된 extension
    • 각 extension은 클로저를 사용하는 메소드나 동작의 파라미터로 사용 가능하다.
  • 플러그인에 의해 프로젝트에 추가된 컨벤션 메소드
    • 플러그인은 프로젝트의 Convention 객체를 통해, 프로퍼티나 메소드를 추가 할 수 있다.
  • 프로젝트의 task
    • task 이름을 메소드 이름으로 사용하고 단일 클로저나 Action 파라미터를 씀으로써 각 task에 대한 메서드가 추가된다.
    • Task.configure(groovy.lang.Closure) 메소드는 제공된 클로저와 연관된 task에 대한 메소드를 호출한다.
    • 예를 들어 compile이라는 task를 가진 프로젝트에서 메소드는 다음의 이름으로 추가된다. : void compile(Closure configureClosure).
  • 루트 프로젝트까지 재귀적으로 상위 프로젝트의 메소드를 탐색한다,
  • 값인 클로저인 프로젝트의 프로퍼티
    • 클로저는 메소드로 취급되며 제공된 파라미터로 호출된다. 이 프로퍼티는 위에 설명된대로 위치한다.

Properties, Method, Script Block

링크에서 모든 내용 확인 가능

profile
🌱 😈💻 🌱

0개의 댓글