프로그래밍 공부/Gradle

Gradle의 태스크(task) 10. Gradle의 태스크 그래프

Wonuk 2021. 9. 3. 21:00
반응형
Gradle의 모든 포스팅은 유튜브 프리렉을 기반으로 작성되었습니다.



링크 : https://www.youtube.com/watch?v=s-XZ5B15ZJ0&list=PL7mmuO705dG2pdxCYCCJeAgOeuQN1seZz

 

강의의 버전이 옛버전 이기 때문에 이론적인 부분만 학습하는것이 좋습니다.

 


9. 그레이들의 태스크(task)

태스크의 기본

그레이들에서 태스크는 빌드를 수행하는 가장 기본적이고 중심이되는 빌드수행의 시작점이라고 할 수 있다.

task hello<<{
	println "Hello Gradle!"
}

task hello<< 에서 <<는 leftShift 연산자이다.

이 연산자를 사용해서 task를 지정하여 내용을 출력할 때 사용되는 연산자이다.

<<는 5.0에서 폐지되었다. 대신 doLast()메소드를 사용해야 한다.

 

 

상단의 hello를풀어서 하단의 내용으로 작성가능하다.

// 출력할 문자열을 def 형으로 선언 및 클로저 정의
def strMsg = { println 'Hello Gradle!' }

// Hello 태스크
task hello{}

// leftShift()를 사용한 문자열 출력
hello.leftShift(strMsg)

 

 

<<연산자가 있을경우, 없을경우 차이점

task gradleTask1<< {
	println 'This is Gradle Task One'
}

task gradleTask2{
	println 'This is Gradle Task Two
}

그레이들의 task에서 연산자 << 부분은 그레이들의 실행단계에서 task로 인식할건지 아닌지 여부를 나타낸다.

 

2가 더 빨리 실행된다.

gradleTask2는 실행단계 이전인 설정단계에서 실행되고

실행단계에서 gradleTask1이 실행되기 때문.

 

 

태스크의 기본

task goodTask <<{
	println description + 'This is Good!'
}
goodTask.description = 'Task Execution->'

그레이들에서 기본적으로 제공해주는 task 객체에 있는 속성을 이용하는 코드이다.

 

 

만약 Task를 정의하기 전에 속성값을 이용해서 사용하려고 한다면

badTask.description = 'Task Execution->'
task badTask<<{
	println description + 'This is Bad!'
}

해당 Task를 알수없기 때문에 오류가 발생한다.

속성을 먼저 정의해서 사용하는 방법은 뒷부분에서 알아볼 예정이다.

 

 

task를 선언하고, 문자열 지정, prevTask를 정의해서 문자열 내용과 함께 출력되도록 스크립트 작성

위의 badTask와 같은 오류를 범하지 않을 수 있다.

task prevTask

prevTask.description = '<Task Execution>'

prevTask<<{
	println description + 'This is Good!'
}

 

 

Gradle은 Groovy를 기반으로 하고있기 때문에 Groovy의 다양한 기능들을 활용할 수 있다.

문자열 함수를 사용할 수 있고, 반복문 사용 가능

// toUpperCase(), toLowerCase() 을 사용하여 문자열 출력
task exeTask1<<{
	String strOutput = 'Have a Good Day'
	println '1. String change :' + strOutput.toUpperCase()
    println '1. String change :' + strOutput.toLowerCase()
}

// 0~10이 되기 전까지 10번 반복하여 it을 출력 일종의 루프문 
task exeTask2<<{
	10.times { println "$it" }
}

실행결과

gradle exeTask1

Task :exeTask1

1. String change : HAVE A GOOD DAY

2. STtring change : have a good day

 

gradle exeTask2

Task :exeTask2

0

1

2

3

4

5

6

7

8

9

 

task를 활용한 반복문 실습

3번의 반복이 수행되는 동안 3개의 task가 정의되어 실행되어 질 수 있도록 작성

3.times{counter->
	task"exeTask$counter"<<{
    	println"task counter:$counter"
    }
}
exeTask1.dependsOn exeTask0,exeTask2

dependsOn 이라는 키워드를 사용하여 exeTask1과 0, 2에 대해 의존관계를 맺음으로써

exeTask1만 실행하더라도 0, 2 모두가 수행된다

 

실행결과

gradle exeTask1 입력시

> Task :exeTask0

task counter : 0

> Task :exeTask2

task counter : 2

> Task :exeTask1

task counter : 1

 

 

Task의 속성인 description의 값을 지정할 수 있다.

task exeTask(description:"This is gradle description")<<{
	println description
}

task (exeTask, description:"This is gradle description")<<{
	println description
}

task의 인자가 많이 지정될수록 복잡해보이기 때문에 상단의 방법으로 정의한다. 가독성 측면에서 유리

 

 

doFirst : 지정된 task가 실행되기전에 먼저 실행되도록 하는 부분

doLast : 지정된 task가 수행이 완료된 후에 실행되도록 하는 부분

task exeTask<<{
	println'	exeTask task'
}

exeTask.doFirst{
	println'>>>exeTask doFirst'
}

exeTask.doLast{
	println">>>exeTask doLast - END : $exeTask.name"
}

gradle exeTask 실행결과

>>> exeTask doFirst

       exeTask task

>>> exeTask doLast - END : exeTask

 

 

 

속성 블럭에서 지정한 값을 task에서 사용하는 방법

task userInfo{
	ext.userName="John"
	ext.userAge="20"
	ext.userGen="Man"
}

task exeTask <<{
	println "Name : "+userInfo.userName
	println "Age : "+userInfo.userAge
	println "Gen : "+userInfo.userGen
}

ext는 기본객체, 나중에 다시배움

userInfo 블록에서 각각의 값을 지정해주고 exeTask에서 출력

 

gradle exeTask 실행결과

Name : John

Age : 20

Gen : Man

 

 

 

defaultTasks를 활용해보기

defaultTasks'exeTask1','exeTask2','exeTask3'

 

defaultTasks는 빌드 수행 시 기본적으로 수행해야 할 task를 지정해서 수행하게 만드는 것

defaultTasks'exeTask1','exeTask2','exeTask3'

task exeTask1<<{
	println 'This is exeTask1 Project'
}

task exeTask2<<{
	println 'This is exeTask2 Project'
}

task exeTask3<<{
	println 'This is exeTask3 Project'
}

 

gradle 실행결과 (task를 지정하지 않음)

>Task : exeTask1

this is exeTask1 Project

>Task : exeTask2

this is exeTask2 Project

>Task : exeTask3

this is exeTask3 Project

지정하지 않아도 기본적으로 다 실행됨

 

 

 

 

def를 이용해서 Map형식으로 값을 지정하고

each 블록을 이용해서 해당 task에 대해서 블록안에서 반복작업을 수행하면서 task들이 정의되고 출력되는 예제

def conMap ={'imgConf':'img.freelec.co.kr', 'smsConf':'sms.freelec.co.kr'}

confMap.each { sbDomain.domainAdder->
	task "exeTask$(svDomain)"<<{
    	println domainAddr
    }
}

times와 다르게 해당 객체에 지정되어 있는 값의 개수만큼 수행된다.

반복을 수행할 때 내부적으로 key와 value값을 지정해서 그 값들을 각각 사용하여 출력할 수 있도록한다.

 

gradle exeTaskimgConf 실행결과

>Task :exeTaskimgConf

img.freelec.co.kr

 

gradle exeTasksmsConf 실행결과

> Task :exeTasksmsConf

sms.freelec.co.kr

 

 

 


조건에 따른 빌드

그레이들 에서는 빌드를 수행할 때 특정 조건에 합당할 경우 빌드를 수행하도록 만들 수 있다.

task exeTask<<{
	println 'Gradle Build Success !!!'
}

// buildType == 'partial-build' 일때 빌드가 수행되도록
exeTask.onlyIf{
	buildType == 'partial-build'
}

gradle exeTask 실행결과

Build Failed 가 나온다. onlyIf 조건 때문이다.

 

gradle -PbuildType=partial-build exeTask 실행결과 [특정 조건에 맞을 때 빌드]

>Task :exeTask

Gradle Build Success !!!

 

 

 

예외처리

 

빌드 수행 시 조건에 맞지 않거나 문제가 생겼을 때 상황에 대해서 예외처리를 하는법

task exeTask <<{
	println'exeTask Build SUCCESS'
}

exeTask<<{
	if(process=='error'){
    	throw new StopExecutionException()
    }
}

exeTask<<{
	println '-- Build END --'
}

if문이나 throw 부분을 사용할 수 있는 이유는 gradle이 groovy를 기반으로 했기 때문이다.

 

 

gradle -Pprocess=ok exeTask 실행결과

> Task :exeTask

exeTask Build SUCCESS

-- Build END --

 

if문으로 예외처리한 부분이 수행되지 않는다.

 

gradle -Pprocess=error exeTask 실행결과

> Task:exeTask

exeTask Build SUCCESS

 

-- Build END --가 출력되지 않는다 if 문의 안의 Exception 처리 때문

 


실행 순서 제어

Gradle은 task들 간에 실행 순서를 제어할 수 있다.

 

mustRunAfter 사용

task exeTaskBefore<<{
	println'exeTaskBefore ---- 1'
}

task exeTaskAfter<<{
	println'exeTaskAfter ---- 2'
}

exeTaskAfter.mustRunAfter exeTaskBefore

mustRunAfter shouldRunAfter를 사용한다. 이 둘은 task간의 실행순서를 제어한다

이 둘에는 강제성에 차이가 있다. must가 should가 더 강하다.

 

 

exeTaskAfter.mustRunAfter exeTaskBefore을 풀어서 설명해보자면

exeTaskAfter는 exeTaskBefore가 수행된 후에 수행하라는 구문이다.

 

즉, exeTaskBefore가 무조건 먼저 수행되도록 제어되어 있는것

 

gradle exeTaskBefore exeTaskAfter 실행결과(before가 먼저 수행되도록)

> Task :exeTaskBefore

exeTaskBefore ---- 1

> Task : exeTaskAfter

exeTaskAfter ---- 2

 

gradle exeTaskAfter exeTaskBefore 실행결과(after가 먼저 수행되도록)

> Task :exeTaskBefore

exeTaskBefore ---- 1

> Task : exeTaskAfter

exeTaskAfter ---- 2

 

실행결과가 동일하다는 것을 확인할 수 있다. (순서를 제어했기 때문 mustRunAfter)

 

 

 

 

shouldRunAfter 사용

task exeTaskBefore<<{
	println'exeTaskBefore ---- 1'
}

task exeTaskAfter<<{
	println'exeTaskAfter ---- 2'
}

exeTaskAfter.shouldRunAfter exeTaskBefore

 

gradle exeTaskBefore exeTaskAfter 실행결과(before가 먼저 수행되도록)

> Task :exeTaskBefore

exeTaskBefore ---- 1

> Task : exeTaskAfter

exeTaskAfter ---- 2

 

gradle exeTaskAfter exeTaskBefore 실행결과(after가 먼저 수행되도록)

> Task :exeTaskBefore

exeTaskBefore ---- 1

> Task : exeTaskAfter

exeTaskAfter ---- 2

 

sholdRunAfter도 MustRunAfter와 동일하게 task들간에 수행되는 실행순서를 강제적으로 지정할 수 있다.

sholdRunAfter는 순환 참조구조가 무시되고 sholdRunAfter가 지정한 순서대로 실행된다.

 

 

 

의존관계와 순서 지정 방법 간의 차이

의존 관계가 아닌 태스크 간에 mustRunAfter 이나 shouldRunAfter 지정이 없다면

해당 태스크들의 연관 관계는 없으며,

mustRunAfter 이나 shouldRunAfter로

지정되어 있다면 해당 테스크는 모두 실행 대상에 지정되어야 지정된 실행 순서에 따라 수행된다.

 

 


10, Gradle의 태스크 그래프

  • 방향성 비순환 그래프(Directed Acyclic Graph)
  • 태스크 간의 의존 관계를 시각적으로 표현

즉, 방향성은 있지만 순환하지 않는 특성이 있다.

만약 Gradle의 task가 순환구조를 갖게 된다면 무한루프에 빠지게되는 큰 오류를 범할 수 있다.

 

task FirstExeTask <<{
	println 'FirstExeTask gradle task'
}

task SecondExeTask(DependsOn:'FirstExeTask')<<{ // 의존관계 설정
	println'SecondExeTask gradle task'
}

task ThirdExeTask(DependsOn:'SecondExeTask')<<{ // 의존관계 설정
	println'ThirdExeTask gradle task'
}

ThirdExeTask가 실행되려면 SecondExeTask를 바라보고 SecondExeTask가 실행되려면 FirstExeTask를 바라본다.

그래서 First, Second, Third 순으로 빌드 수행결과를 확인할 수 있다.

 

gradle ThirdExeTask 실행결과

> Task :FirstExeTask

FirstExeTask gradle task

> Task :SecondExeTask

SecondTask gradle task

> Task :ThirdExeTask

ThirdExeTask gradle task

 

의존관계 때문에 이러한 실행결과가 나온다.

 

 

 

 

종료 테스크를 지정

task FirstExeTask <<{
	println 'FirstExeTask gradle task'
}

FirstExeTask<<{ // 인위적으로 예외발생
	throw new Exception('Exception')
}

task SecondExeTask(DependsOn:'FirstExeTask')<<{ // 의존관계 설정
	println'SecondExeTask gradle task'
}

task ThirdExeTask(DependsOn:'SecondExeTask')<<{ // 의존관계 설정
	println'ThirdExeTask gradle task'
}

task finishTask<<{ // memory clear 문자열 출력
	println 'memory clear'
}

FirstExeTask.finalizedBy finishTask // 특정 테스크 후에 종료 테스크 지정

 

 

gradle ThirdExeTask 실행결과

> Task :FirstExeTask

FirstExeTask gradle task

> Task :finishTask

memory clear

 

FAILURE: Build failed with an exception.

 

FirstExeTask에서 인위적으로 예외를 발생시켰기 때문에 빌드 실패

예외가 발생했음에도 불구하고 finishTask가 수행되었다.

즉 종료 테스크가 지정되면 예외가 발생해도 종료테스크로 지정된 부분은 무조건 수행된다.

 

테스크 그래프

만약 Second exeTask로 Finalizer exeTask를 지정했다면

First exeTask에서는 인위적으로 예외를 발생시켰기 때문에 예외가 발생되면서 종료가되고

Second exeTask는 실행조차 되지 않기때문에 Finalizer exeTask도 실행되지 않고 종료된다.

 

즉 종료 task를 지정할 때에도 각 task간의 예외, 예외상황 들을 확인 후 종료 task를 지정해야 한다.

 

 

반응형