우리는 Build Script Basics에서 keyword style을 이용하여 어떻게 task를 정의하는 지 보았다.
이 스타일에는 당신이 어떠한 특정 상황에 사용할 몇가지 변형이 있다.
예를 들어, keyword style은 expressions에서는 동작하지 않는다.
build.gradle
task(hello) << { println "hello" } task(copy, type: Copy) { from(file('srcDir')) into(buildDir) }당신은 또한 string을 이용하여 task 이름을 사용할 수도 있다.
build.gradle
task('hello') << { println "hello" } task('copy', type: Copy) { from(file('srcDir')) into(buildDir) }
또한 당신이 선호할 다른 종류의 대체 방법도 있다.
build.gradle
tasks.create(name: 'hello') << { println "hello" } tasks.create(name: 'copy', type: Copy) { from(file('srcDir')) into(buildDir) }
여기서 우리는 tasks collection에 task를 넣는 것을 배워보았다.
더 많은 create() method의 변형을 알기 위해, TaskContainer를 찾아보면 된다.
Locating tasks
당신은 종종 build file 안에 정의되어 있는 task를 명시해야할 필요가 있을 것이다.
예를 들어, 그것을 설정하거나 dependency로 사용할 때이다.
이 것을 하기 위해서는 다양한 종류의 방법이 있다.
가장 먼저, 각각의 task는 project의 property로써 task 이름을 property 이름으로 명시함으로써 사용 가능하다.
Accessing tasks as properties
build.gradle
task hello println hello.name println project.hello.name
Task는 tasks collection을 이용해서도 이용 가능하다.
Accessing tasks via tasks collection
build.gradle
task hello println tasks.hello.name println tasks['hello'].name
당신은 tasks.getByPath() method를 이용하여서 task의 path를 사용해 어떠한 project로부터도 task에 접근할 수 있다.
당신은 task의 이름이나, 상대 경로, 절대 경로를 통해 getByPath() method를 호출할 수 있다.
Accessing tasks by path
build.gradle
project(':projectA') { task hello } task hello println tasks.getByPath('hello').path println tasks.getByPath(':hello').path println tasks.getByPath('projectA:hello').path println tasks.getByPath(':projectA:hello').path
gradle -q hello 의 실행 결과
> gradle -q hello :hello :hello :projectA:hello :projectA:hello
location task의 더 많은 option을 알아보기 위해 TaskContainer를 찾아보면 된다.
Configuring tasks 하나의 예로써, Gradle에서 제공하는 Copy task를 보자.
당신의 build에 copy task를 만들기 위해서, 당신은 build script에 아래와 같이 선언할 수 있다.
Creating a copy task
build.gradle
task myCopy(type: Copy)
위 task는 기본적인 행동없이 copy task를 만든다.
이 task는 copy API를 사용하여 설정된다.
아래 예들은 같은 설정을 하기 위한 여러가지 다른 방법들을 보여준다.
task의 이름을 정확히 구분하기 위해서, myCopy라고 하자.
다만, type은 "Copy"이다.
당신은 서로 다른 이름으로 같은 type이지만 여러가지 task를 설정할 수 있다.
Configuring a task - various ways
build.gradle
Copy myCopy = task(myCopy, type: Copy) myCopy.from 'resources' myCopy.into 'target' myCopy.include('**/*.txt', '**/*.xml', '**/*.properties')
이 것은 우리가 java에서 object를 설정할 때와 비슷한 방식이다.
당신은 설정할 때마다 myCopy를 반복해서 사용해야 하고, 이 것은 번거롭고 괜찮아 보이지 않는다.
그래서 task를 설정할 다른 방법이 있다.
이 방법은 보통 가장 선호되는 방식이다.
build.gradle
task myCopy(type: Copy) myCopy { from 'resources' into 'target' include('**/*.txt', '**/*.xml', '**/*.properties') }
이 것은 어떠한 task에도 적용된다.
example 안의 3번째 라인은 tasks.getByName() method의 줄임말이다.
만약 당신이 getByName() closure를 넘긴다면, task를 설정할 때에는 적용되지만, 실행할 때는 적용되지 않는다.
이 것은 매우 중요하다.
Defining a task with closure
build.gradle
task copy(type: Copy) { from 'resources' into 'target' include('**/*.txt', '**/*.xml', '**/*.properties') }
Adding dependencies to a task task의 dependency를 정의할 수 있는 몇가지 방법이 있다.
Task는 같은 프로젝트 내에서 다른 task 혹은 다른 프로젝트의 task 로 이름으로써 참조될 수 있다.
다른 프로젝트의 task를 참조하기 위해서, 당신은 task의 이름 앞에 어떤 프로젝트에 속해있는 지 붙여주어야 한다.
아래 예는 projectA:taskX 에서 projectB의 taskY로 dependency를 추가하는 예제이다.
Adding dependency on task from another project
build.gradle
project('projectA') { task taskX(dependsOn: ':projectB:taskY') << { println 'taskX' } } project('projectB') { task taskY << { println 'taskY' } }
gradle -q taskX 의 실행 결과
> gradle -q taskX taskY taskX
task 이름을 사용하는 대신에, 당신은 Task object를 사용하여 denpendency를 정의할 수 있다.
아래 예제를 보자.
Adding dependency using task object
build.gradle
task taskX << { println 'taskX' } task taskY << { println 'taskY' } taskX.dependsOn taskY
gradle -q taskX 의 실행 결과
> gradle -q taskX taskY taskX
더 진보한 사용을 위해서, 당신은 task dependency를 closure를 사용해서 정의할 수 있다.
evaluating 일 때에, closure는 dependency가 계산되어진 채로 넘겨지게 된다.
closure는 task의 dependency로 다루어지는 single task나 Task object의 collection으로 넘겨져야 한다.
아래 예제는 taskX로부터 lib로 시작하는 모든 project의 task에 dependency를 추가하는 예이다.
Adding dependency using closure
build.gradle
task taskX << { println 'taskX' } taskX.dependsOn { tasks.findAll { task -> task.name.startsWith('lib') } } task lib1 << { println 'lib1' } task lib2 << { println 'lib2' } task notALib << { println 'notALib' }
gradle -q taskX 의 실행 결과
> gradle -q taskX lib1 lib2 taskX
Ordering tasks
몇몇의 case에서 두 가지 이상의 task가 서로 dependency가 명시되어 있지 않을 때, task의 순서를 조정하는 것은 유용하다.
task의 순서와 dependency의 가장 큰 차이점은 순서 조정은 task의 실행 여부가 영향을 미치지 않는다는 것이다.
Task 순서 조정은 다음과 같은 시나리오에서 유용하다.
- 순서에 따른 task 실행을 강제할 때 : ex. 'clean' 전에 'build' 는 실행되지 않게 하기
- build 시작 시에 build validation 실행 : ex. release build를 시작하기 전에 알맞은 credential인지 체크
- 오랜 시간이 걸리는 verification task 이전에 빠른 verification task를 실행하여 빠른 feedback 얻기 : ex. integration test 이전에 unit test 실행
- 특정 type의 모든 task의 결과를 모으는 task : ex. 모든 실행된 test task의 결과를 모은 test report task
순서를 정하는 규칙에는 두 가지가 있다. : "must run after" 와 "should run after"
만약 당신이 "must run after"를 사용하여 taskB가 taskA 다음에 항상 실행되게 한다면, 언제나 taskA와 taskB는 실행될 것이다.
이 것은 taskB.mustRunAfter(taskA)로 표현된다.
"should run after" 규칙은 비슷하지만 두가지 상황에서는 무시되기 때문에 조금 덜 엄격하다.
가장 먼저 이 규칙을 사용하여 ordering cycle을 도입하는 경우이다.
두 번째로는 병렬 실행을 사용하고 모든 task의 dependencyrk "sholud run after" task와 상관이 없을때이다.
이러한 경우 이 task는 "should run after" dependency가 실행되든 아니든 실행된다.
당신은 "should run after" 를 순서 조정이 도움되는 곳에 쓰는 것이 좋지만, 완전히 요구되는 사항은 아니다.
이러한 규칙이 있을 때 taskB가 없이도 taskA의 실행은 가능하고, 그 반대의 경우도 가능하다.
Adding a 'must run after' task ordering
build.gradle
task taskX << { println 'taskX' } task taskY << { println 'taskY' } taskY.mustRunAfter taskX
gradle -q taskY taskX 의 실행 결과
> gradle -q taskY taskX taskX taskY
Adding a 'should run after' task ordering
build.gradle
task taskX << { println 'taskX' } task taskY << { println 'taskY' } taskY.shouldRunAfter taskX
gradle -q taskY taskX 의 실행 결과
> gradle -q taskY taskX taskX taskY
위의 예에서는 taskY를 실행하는 것이 taskX를 실행하는 것을 유발하지는 않는다.
gradle -q taskY 의 실행 결과
> gradle -q taskY taskY
“must run after” 이나 “should run after” 규칙을 2 task 사이에 적용하기 위해서는, 당신은 Task.mustRunAfter() 와 Task.shouldRunAfter() method를 사용할 수 있다.
이러한 method들은 task instance, task 이름이나 Task.dependsOn()에 허용되는 어떠한 입력도 허용한다.
“B.mustRunAfter(A)” 혹은 “B.shouldRunAfter(A)” 는 task 사이에 어떠한 dependency도 암시하지 않는다.
- taskA와 taskB를 독립적으로 실행시키는 것이 가능하다. 순서 규칙은 오직 두 task 사이의 실행을 위한 스케쥴에만 영향을 미친다.
- --continue 와 함께 실행할 때, A가 실패했어도 B를 실행시키는 것이 가능하다. 위에 언급되었듯이, "should run after"는 이 것이 ordering cycle을 도입할 때 무시된다.
A 'should run after' task ordering is ignored if it introduces an ordering cycle
build.gradle
task taskX << { println 'taskX' } task taskY << { println 'taskY' } task taskZ << { println 'taskZ' } taskX.dependsOn taskY taskY.dependsOn taskZ taskZ.shouldRunAfter taskX
gradle -q taskX 의 실행 결과
> gradle -q taskX taskZ taskY taskX
Adding a description to a task 당신은 당신의 task에 description을 추가할 수 있다.
description은 gradle tasks 를 실행할 때 출력된다.
Adding a description to a task
build.gradle
task copy(type: Copy) { description 'Copies the resource directory to the target directory.' from 'resources' into 'target' include('**/*.txt', '**/*.xml', '**/*.properties') }
Replacing tasks
가끔 당신은 task를 교체하고 싶을 수 있다.
예를 들어, 당신은 Java plugin에 custom된 다른 type의 task로 교체하고 싶을 수 있다.
그럴 때는 아래와 같이 할 수 있다.
Overwriting a task
build.gradle
task copy(type: Copy) task copy(overwrite: true) << { println('I am the new one.') }
gradle -q copy 의 실행 결과
> gradle -q copy I am the new one.
이 것은 둘다 같은 이름을 쓰기 때문에,Copy 타입의 task를 당신이 정의한 것으로 교체한다.
당신이 새로운 task를 정의할 때, 당신은 반드시 overwrite 속성을 true로 설정하여야 한다.
그렇지 않으면 Gradle은 exception을 발생시키고, 그 이름의 task가 이미 존재한다고 얘기할 것이다.
Skipping tasks
Gradle은 task를 건너뛸 수 있는 여러가지 방법을 제공한다.
Using a predicate
당신은 task에 predicate를 붙이기 위해 onlyIf() method를 사용할 수 있다.
task의 action은 오직 predicate가 true일 때만 실행된다.
당신은 predicate를 closure로써 실행할 수 있다.
closure는 task를 파라미터로써 넘겨주고,
task가 실행되어야한다면 true, task가 skip되어야 한다면 false를 return 하여야 한다.
predicate는 task가 실행되기로 된 바로 직전에 체크된다.
Skipping a task using a predicate
build.gradle
task hello << { println 'hello world' } hello.onlyIf { !project.hasProperty('skipHello') }
gradle hello -PskipHello의 실행 결과
> gradle hello -PskipHello :hello SKIPPED BUILD SUCCESSFUL Total time: 1 secs
Using StopExecutionException
만약 task를 skip하기 위한 logic이 predicate로 표현할 수 없다면, 당신은 StopExecutionException을 사용할 수 있다.
만약 exception이 action에 의해 실행된다면 같은 task 안의 이어지는 action은 더 이상 실행되지 않는다.
하지만, 다음 task로 build는 이어지게 된다.
Skipping tasks with StopExecutionException
build.gradle
task compile << { println 'We are doing the compile.' } compile.doFirst { // Here you would put arbitrary conditions in real life. // But this is used in an integration test so we want defined behavior. if (true) { throw new StopExecutionException() } } task myTask(dependsOn: 'compile') << { println 'I am not affected' }
gradle -q myTask 의 실행 결과
> gradle -q myTask I am not affected
이 것은 당신이 Gradle로부터 제공받은 task로부터 실행할 때 유용하다.
이 것은 어떠한 task에서 built-in 되어 있는 action을 당신이 조건적으로 실행을 추가할 수 있게 허용해준다.
Enabling and disabling tasks
모든 task는 실행할 수 있게 해주는 flag를 가진다.
기본적으로 true로 설정되어있다.
이 것을 false로 설정하면 해당 task의 실행을 막게된다.
Enabling and disabling tasks
build.gradle
task disableMe << { println 'This should not be printed if the task is disabled.' } disableMe.enabled = false
gradle disableMe 의 실행 결과
> gradle disableMe :disableMe SKIPPED BUILD SUCCESSFUL Total time: 1 secs
gradle disableMe 의 실행 결과
> gradle disableMe :disableMe SKIPPED BUILD SUCCESSFUL Total time: 1 secs
Skipping tasks that are up-to-date
만약 당신이 Java Plugin에서 사용할 수 있는 것처럼 Gradle에 포함된 task를 사용한다면, Gradle이 이미 최신인 task는 실행을 skip 한다는 것을 알아차렸을 것이다.
이런 행동은 built-in task뿐만 아니라, 당신의 task에서도 가능하다.
Declaring a task's inputs and outputs
아래 예제를 보자.
여기는 XML 파일로부터 여러번 output file을 생성한다.
여러 번 실행시켜 보자.
A generator task
build.gradle
task transform { ext.srcFile = file('mountains.xml') ext.destDir = new File(buildDir, 'generated') doLast { println "Transforming source file." destDir.mkdirs() def mountains = new XmlParser().parse(srcFile) mountains.mountain.each { mountain -> def name = mountain.name[0].text() def height = mountain.height[0].text() def destFile = new File(destDir, "${name}.txt") destFile.text = "$name -> ${height}\n" } } }
gradle transform 의 실행 결과
> gradle transform :transform Transforming source file.gradle transform 의 실행 결과
> gradle transform :transform Transforming source file.
Gradle은 이 task를 두 번 실행하였고, 아무것도 변화하지 않아도 task를 skip하지 않는다는 것을 알 수 있다.
위 예제는 action closure로써 task가 정의되어 있다.
Gradle은 closure가 무엇을 하는 지 알 수 없고 자동으로 현재의 task가 최신인지 아닌지 파악할 수 없다.
Gradle의 최신 체킹을 사용하기 위해, 당신은 task의 input 과 output을 선언하여야 한다.
각각의 task는 inputs와 outputs의 속성을 가지고 있고, 이 것을 통해 입력과 출력을 선언할 수 있다.
아래를 보면, XML 파일을 입력 소스로 선언하고, destDir을 출력으로 선언한 것을 볼 수 있다.
두 번 실행해보자.
Declaring the inputs and outputs of a task
build.gradle
task transform { ext.srcFile = file('mountains.xml') ext.destDir = new File(buildDir, 'generated') inputs.file srcFile outputs.dir destDir doLast { println "Transforming source file." destDir.mkdirs() def mountains = new XmlParser().parse(srcFile) mountains.mountain.each { mountain -> def name = mountain.name[0].text() def height = mountain.height[0].text() def destFile = new File(destDir, "${name}.txt") destFile.text = "$name -> ${height}\n" } } }
gradle transform 의 실행 결과
> gradle transform :transform Transforming source file.
gradle transform 의 실행 결과
> gradle transform :transform UP-TO-DATE
이제, Gradle은 어떤 파일이 최신인지 아닌지 체크해야하는지 알게되었다.
task의 inputs는 TaskInputs 속성이다. task의 outputs는 TaskOutputs 속성이다.
outputs를 정의하지 않은 task는 절대 최신인지 아닌지를 고려하지 않을 것이다.
task의 outputs 가 파일이 아닌 시나리오, 더 복잡한 시나리오 등을 위해서, TaskOutputs.upToDateWhen() method 는 당신이 프로그래밍적으로 최신인지 아닌지 Gradle이 체크하게 해준다.
오직 output만 정의된 task는 이전 build에서 outputs가 변화하지 않으면, 현재의 것을 최신 것으로 간주한다.
Task rules
가끔 당신은 매우 큰 숫자의 값을 파라미터로 사용하게 될 task를 원할 수 있다.
이러한 방식을 잘 해낼 방법이 바로 task rules이다.
build.gradle
tasks.addRule("Pattern: ping") { String taskName -> if (taskName.startsWith("ping")) { task(taskName) << { println "Pinging: " + (taskName - 'ping') } } }
gradle -q pingServer1 의 실행 결과
> gradle -q pingServer1 Pinging: Server1
String 파라미터는 rule의 description으로 사용되고, 이것은 gradle tasks를 통해 볼 수 있다.
Rules는 커맨드 라인에서 task를 호출할 때만 쓰이지는 않는다.
당신은 rule 기반의 task에서 dependsOn 관계를 만들 수 있다.
Dependency on rule based tasks
build.gradle
tasks.addRule("Pattern: pinggradle -q groupPing 의 실행 결과") { String taskName -> if (taskName.startsWith("ping")) { task(taskName) << { println "Pinging: " + (taskName - 'ping') } } } task groupPing { dependsOn pingServer1, pingServer2 }
> gradle -q groupPing Pinging: Server1 Pinging: Server2
만약 당신이 gradle -q tasks를 실행한다면 "pingServer1" 혹은 "pingServer2" 라는 이름의 task를 찾을 수 없을 것이다.
하지만 이 script는 task를 실행시키는 request를 기반으로 실행된다.
Finalizer tasks
Filnalizer task는 finalized task가 실행되기로 계획되어 있을 때, 자동적으로 task graph에 추가된다.
Adding a task finalizer
build.gradle
task taskX << { println 'taskX' } task taskY << { println 'taskY' } taskX.finalizedBy taskY
gradle -q taskX 의 실행 결과
> gradle -q taskX taskX taskY
Finallizer task는 finalizer task가 실패하더라도 실행된다.
Task finalizer for a failing task
build.gradle
task taskX << { println 'taskX' throw new RuntimeException() } task taskY << { println 'taskY' } taskX.finalizedBy taskY
gradle -q taskX 의 실행 결과
> gradle -q taskX taskX taskY
원본 출처 : https://docs.gradle.org/current/userguide/more_about_tasks.html
댓글 없음 :
댓글 쓰기