본문 바로가기
Gradle basic

apply plugin: 'java' 란?

by 이석준석이 2020. 11. 25.

 

 

The Java Plugin

If a dependent project has changed in an ABI-compatible way (only its private API has changed), then Java compilation tasks will be up-to-date. This means that if project A depends on project B and a class in B is changed in an ABI-compatible way (typicall

docs.gradle.org

  • 해당 페이지를 요약하고 정리하고, 찾은 것들도 정리합니다.
  • 아직 1년도 안된 개발자라.. 틀린 내용이 있을 수 있습니다.
  • 틀린 내용이나 지적할 부분이 있다면 robin00q@naver.com 혹은 댓글을 달아주세요.^_^

배경

  • 인텔리제이를 사용해서 Springboot Initizlizer을 사용하여 스프링부트 어플리케이션을 생성하면 아래와 같은 플러그인들이 build.gradle에 적용되어 있는 것을 볼 수 있습니다.
plugins {
    id 'org.springframework.boot' version '2.3.4.RELEASE'
    id 'io.spring.dependency-management' version '1.0.10.RELEASE'
    id 'java'
}
  • 해당 플러그인들 중에서 'java' 플러그인에 대해 위의 Gradle 공식 가이드를 요약해봤습니다.

1. Usage

  • 사용법은 간단합니다.
  • 두 가지 방법 모두 같은 방법입니다.
plugins {
    id 'java'
}
apply plugin: 'java'

2. Java Plugin?

  • 자바 플러그인을 플러그인으로 등록하면 아래와 같은 규칙들이 적용됩니다.

2.1 규칙

  • Java 플러그인의 전제 조건은 

    • 프로덕션 코드는 src/main/java 아래에 둔다.
    • 프로덕션 코드의 리소스들은 src/main/resources 아래에 둔다.
    • 테스트 코드는 src/test/java 아래에 둔다.
    • 테스트 코드 리소스들은 src/test/resources 아래에 둔다.
  • 위의 조건들을 custom 할 수 있으나, 거의 모든 프로젝트를 위와같이 사용하므로 일단 생략합니다.
  • 위의 규칙들을 기반하여 아래의 Gradle Task 들이 수행됩니다.

2.2 SourceSet

  • Java 플러그인을 적용함으로써 두 가지의 SourceSet들이 정의됩니다.

    1. main
    2. test
  • 추가로 소스세트를 정의하는 방법도 있으나 주로 사용하지 않으므로 그건 생략을 ...
  • Springboot Initializer로 스프링부트 프로젝트를 생성한 뒤 (간단하게 Spring Web 정도만 의존성을 추가하면 될 것 같습니다.)  build.gradle 의 제일 아래에 아래와 같이 추가해봅니다.
plugins {
...
}

...

제일 아래에 아래의 코드를 추가합니다.

task printSourceSetInformation() {
    doLast {
        sourceSets.each {srcSet ->
            println "["+srcSet.name+"]"
            print "-->Source directories: "+srcSet.allJava.srcDirs+"\n"
            print "-->Output directories: "+srcSet.output.classesDirs.files+"\n"
            print "-->Compile classpath:\n"
            srcSet.compileClasspath.files.each {
                print "  "+it.path+"\n"
            }
            println ""
        }
    }
}
  • 이후, build.gradle 과 gradlew 가 있는 root에서
    • ./gradlew printSourceSetInformation 을 수행하면 아래와 같이 보입니다.
> Task :printSourceSetInformation
[main]
-->Source directories: [/Users/we/blog-workspace/gradle/javaplugin/src/main/java]
-->Output directories: [/Users/we/blog-workspace/gradle/javaplugin/build/classes/java/main]
  /Users/we/.gradle/caches/modules-2/files-2.1/org.projectlombok/lombok/1.18.16/6dc192c7f93ec1853f70d59d8a6dcf94eb42866/lombok-1.18.16.jar
  /Users/we/.gradle/caches/modules-2/files-2.1/org.springframework.boot/spring-boot-starter-web/2.4.0/4bdd422c370d1d66ffc12ecafdecc70cad406367/spring-boot-starter-web-2.4.0.jar
...

[test]
-->Source directories: [/Users/we/blog-workspace/gradle/javaplugin/src/test/java]
-->Output directories: [/Users/we/blog-workspace/gradle/javaplugin/build/classes/java/test]
-->Compile classpath: 
  /Users/we/blog-workspace/gradle/javaplugin/build/classes/java/main
  /Users/we/blog-workspace/gradle/javaplugin/build/resources/main
  /Users/we/.gradle/caches/modules-2/files-2.1/org.springframework.boot/spring-boot-starter-web/2.4.0/4bdd422c370d1d66ffc12ecafdecc70cad406367/spring-boot-starter-web-2.4.0.jar
  /Users/we/.gradle/caches/modules-2/files-2.1/org.springframework.boot/spring-boot-starter-test/2.4.0/3578b990edab0595cd0e2bc300ba14d72e80f752/spring-boot-starter-test-2.4.0.jar
...

 

  • 위에서 두 가지의 SourceSet들이 Java Plugin을 적용함에 따라 적용 됐다고 한 것처럼, 2개의 SourceSet들이 출력됐습니다.
    • main
      • src/main/java 아래에 있는 파일을 Source로 하여
      • build/classes/java/main 아래에 컴파일 된 코드를 생성합니다.
    • test
      • src/test/java 아래에 있는 파일을 Source로 하여
      • build/classes/java/test 아래에 컴파일 된 코드를 생성합니다.
  • 우리가 지금까지 Springboot를 사용했을 때 컴파일 이후와 같은 규칙을 볼 수 있습니다.

2.3 Task

  • Java 플러그인을 추가하면 아래의 Task들이 추가됩니다.

    • 해당 플러그인들은 인텔리제이를 사용하면 우측의 Gradle을 클릭하면 존재하는 Task들입니다.
    • 혹은 build.gradle과 gradlew 가 있는 root에서
      • ./gradlew task --all 을 통해 볼 수 있습니다. (아래의 그림에 있는 task와 비슷하게 콘솔에 출력됩니다.)

  1. compileJava
    • compile classpath (위의 코드 출력물(2.2)에 있는 .jar 파일들) 에 의존하여 src/main/java [위에서 명시한 프로덕션 코드 위치] 아래의 코드들을 컴파일합니다.
  2. processResources
    • src/main/resources 아래에 있는 파일들 build/resources/main 아래에 옮깁니다.
  3. classes
    • compileJava 와 processResources 를 같이 수행합니다.
    • [depends on compileJava, processResources]
  4. compileTestJava
    • classes를 수행한 이후 [depends on classes]
    • test compile classpath (위의 코드 출력물(2.2)에 있는 .jar 파일들) 에 의존하여 src/test/java [위에서 명시한 테스트 코드 위치] 아래의 코드들을 컴파일합니다.
  5. processTestResources
    • src/test/resources 아래에 있는 파일들build/resources/test 아래에 옮깁니다.
  6. testClasses
    • compileTestJava 와 processTestResources 를 같이 수행합니다.
    • [depends on compileTestJava, processTestResources]
  7. jar
    • classes를 수행한 이후 [depends on classes]
    • 프로덕션 용 jar 파일을 생성합니다.
  8. javadoc
    • classes를 수행한 이후 [depends on classes]
    • Javadoc 을 이용하여 API 문서를 생성합니다.
  9. test
    • testClasses 를 수행한 이후
    • 테스트를 수행합니다.
      • 따라서 우리는 test만 누르면 지금까지
        • 소스코드가 빌드되고
        • resources 하위에 파일들이 옮겨진 이후
        • 테스트파일이 빌드된 후, 결과에 대해 테스트할 수 있었던 겁니다.uploadArchives
  10. clean
    • build 디렉토리를 지웁니다.

아래의 그림은 태스크의 의존관계입니다.

  • 제일 우측에 있는 build가 거의 모든 task들에 의존하고 있었네요.
    • 따라서 build를 실행하면, 코드를 컴파일하고, 테스트도 수행해주고, jar 파일도 생성해주고 ...

Java plugin 의존관계


3. Source Set Properties

 

위에서 설명한대로, Java Plugin을 apply 했다면 기본적으로 두 가지의 소스세트가 제공됩니다.

  • main
  • test

이를 기반으로 build.gradle 에서 여러가지 설정 및, 작업을 할 수 있습니다.

  • SourceSets 내부 블록에서 필요한 경우, 설정을 할 수 있습니다.
  • 아래의 코드를 build.gradle 제일 아래에 넣은 후
    • $ ./gradlew sourceSetProperties 를 수행해봅니다.
task sourceSetProperties {
    doLast {
        sourceSets {
            main {
                println "java.srcDirs = ${java.srcDirs}"
                println "resources.srcDirs = ${resources.srcDirs}"
                println "java.files = ${java.files.name}"
                println "allJava.files = ${allJava.files.name}"
                println "resources.files = ${resources.files.name}"
                println "allSource.files = ${allSource.files.name}"
                println "output.classesDir = ${output.classesDirs}"
                println "output.resourcesDir = ${output.resourcesDir}"
                println "output.files = ${output.files}"
                println "$buildDir"
            }
        }
    }
}

 

결과 : 

java.srcDirs = [/Users/we/blog-workspace/gradle/javaplugin/src/main/java]
resources.srcDirs = [/Users/we/blog-workspace/gradle/javaplugin/src/main/resources]
java.files = [JavapluginApplication.java]
allJava.files = [JavapluginApplication.java]
resources.files = [application.yml]
allSource.files = [application.yml, JavapluginApplication.java]
output.classesDir = main classesDirs
output.resourcesDir = /Users/we/blog-workspace/gradle/javaplugin/build/resources/main
output.files = [/Users/we/blog-workspace/gradle/javaplugin/build/classes/java/main, /Users/we/blog-workspace/gradle/javaplugin/build/resources/main]
/Users/we/blog-workspace/gradle/javaplugin/build

4. Dependency management

 

자바 플러그인은 여러가지 dependency configuration 을 제공합니다.


 

Configuration name 설명
compile (Deprecated) Deprecated 됐습니다.

implementation 을 사용해주세요.
implementation 프로덕션 코드를 컴파일(위에서 설명한 compileJava) 하는데 필요한 라이브러리 의존성을 추가합니다.

- implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
- 해당 의미는 우리 코드를 컴파일하는데 jpa 의존성이 같이 컴파일 되어야 한다는 의미입니다.

- 컴파일 과정에도 필요하며, 런타임 시점에도 필요한 경우 사용합니다. (바로 아래 compileOnly 를 보면 더 이해가 잘 될 것 같아요.)
compileOnly 프로덕션 코드를 컴파일 할 때만 필요하다는 의미입니다.

- 가장 주로 사용되는 경우는 아래와 같이 롬복 의존성을 추가할 때입니다.
- compileOnly 'org.projectlombok:lombok'

- 롬복의존성을 추가한 뒤, 컴파일이 실행되면 빌드 된 파일 내부에는 @Getter, @NoArgsConstructor 등 은 사라져있고, 실제 Getter, 실제 생성자 등이 생성되어있습니다.

- 따라서 컴파일 과정에만 필요 (빌드 출력물에 lombok 코드가 뭐하는지 알 필요가 없이, 우리는 생성된 코드만 필요하니까요)하고, 런타임 (코드가 실행중) 시점에는 필요가 없는 경우에 사용합니다.
compileClasspath 소스를 컴파일하는 시점의 Compile classpath를 말합니다. (아래의 그림과 함께보세요)

- dependencies 를 추가하는 부분에서는 사용하지 않습니다.
- 2.2 SourceSet 부분에서 compileClasspath를 한번 출력한거와 같이, compileClasspath 그 자체를 나타냅니다.
annotationProcessor 컴파일 시점에 어노테이션이 붙은 코드에 대해서 코드를 생성합니다.

- annotationProcessor 'org.projectlombok:lombok' 을 제외하고 빌드한다면, 롬복이 제공하는 어노테이션들을 읽지 못해 build 아래의 코드가 텅텅 비어있는 것을 볼 수 있습니다.

- 한번 해보고 싶은 분들은 주석처리하고 빌드해보시면 이해가 될거에요.
runtimeOnly compileOnly 와는 다르게 프로덕션 코드 실행시, 런타임에만 필요하다는 의미입니다.

- 가장 주로 사용되는 경우는 h2 의존성을 사용할 때 입니다.
- runtimeOnly 'com.h2database:h2'

런타임에만 필요하다?
- 우리의 코드를 컴파일하는 과정에서 h2는 아마 필요없을겁니다.
- 하지만 실행하기 위해서 빌드 출력물에는 있어야합니다.

위와 같은 경우 사용합니다.
runtimeClasspath 위에 설명한 compileClasspath와 동일하게
- 런타임에서 사용하는 runtimeClasspath 그 자체를 나타냅니다. (아래의 그림과 함께보세요)

- runtimeOnly 에 추가한 의존성들 + implementation 한 의존성들이 포함되어있겠죠?

main source set dependency configurations

testImplementation 테스트 코드를 컴파일 (위에 설명했다시피, 테스트 코드를 컴파일하면 프로덕션 코드 컴파일에 대한 의존성이 있어서 Implementation 의존성도 테스트에서 사용할 수 있겠죠?) 하는데 필요한 라이브러리 의존성을 추가합니다.

- 가장 주로 사용되는 경우는 아래와 같이 junit 의존성을 추가할 때입니다.
- testImplementation 'org.springframework.boot:spring-boot-starter-test'
testCompileOnly 테스트 코드 컴파일시 필요한 경우
testCompileClasspath 테스트 코드의 컴파일에서 사용하는 testRuntimeClasspath 그 자체
testRuntimeOnly 테스트 코드 실행시, 런타임에만 필요한 경우
testRuntimeClasspath 테스트 코드의 런타임에서 사용하는 testRuntimeClasspath 그 자체

test source set dependency configurations


 

봐주셔서 감사함다..^_^

'Gradle basic' 카테고리의 다른 글

Springboot Initializer build.gradle (미완)  (0) 2020.10.24