잊지 않겠습니다.

queryDSL을 이용한 java project의 경우에는 gradle로는 조금 과정이 복잡하게 됩니다. 

queryDSL의 경우에는 pre-compile 과정이라고 해서, 기존의 domain entity 들을 이용한 Q class들을 생성해야지 됩니다.
이런 Q class들을 생성하는 build 과정을 먼저 거친 다음에, 기존의 class들을 compile시키는 과정을 거쳐야지 됩니다. 

따라서, compileJava보다 먼저 선행되는 작업이 반드시 필요하게 됩니다. 

먼저, generate될 code가 위치할 곳을 지정해줍니다. 

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

그리고, 새로운 task를 생성시켜줍니다. 
task generateQueryDSL(type: JavaCompile, group: 'build') {
     source = sourceSets.main.java
     classpath = configurations.compile
     options.compilerArgs = [
          "-proc:only",
          "-processor", "com.mysema.query.apt.jpa.JPAAnnotationProcessor"
     ]
     destinationDir = sourceSets.generated.java.srcDirs.iterator().next()
}

위 코드는 compile type의 code라는 것을 먼저 생성시켜주고, 원 source와 Q code를 생성해주는 pre processor를 지정해주는 코드입니다. 마지막줄에서 code를 어느곳에 생성할지를 결정하고 있습니다.
그리고, compileJava에 종속성을 주입해야지 됩니다. 기존에 java plugin을 사용하고 있기 때문에, task를 새로 생성하는 것이 아닌 overwrite 시키는 개념으로 접근하면 됩니다. 

compileJava {
     dependsOn generateQueryDSL
     source generateQueryDSL.destinationDir
}
compileGeneratedJava {
     dependsOn generateQueryDSL
     options.warnings = false
     classpath += sourceSets.main.runtimeClasspath
}

이 두 task를 통해서, 기존의 compileJava의 경우 반드시 generateQueryDSL task를 실행 후, 동작하게 되고 source는 generateQueryDSL.destinationDir을 참고해서 compile을 행하게 됩니다. 또한 compileGeneratedJava task 역시 generateQueryDSL task를 실행하고, compile 된 객체들을 같이 이용해서 jar를 만들어주는 cycle을 통하게 됩니다. 


마지막으로, project를 모두 clean 시키기 위한 코드 역시 수정이 필요합니다. generated 되는 코드들도 같이 지워주기 위해서는 clean task를 다음과 같이 재정의하는 것이 좋습니다. 

clean {
     delete sourceSets.generated.java.srcDirs
}

다음은 build.gradle 파일의 전체입니다. 참고해보시길 바랍니다. 

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

dependencies {
    def querydslVersion = '3.2.4'
    def slf4jVersion = "1.7.2"

    def queryDSL = '3.2.4'
    compile project(':common')
    compile group: 'mysql', name: 'mysql-connector-java', version:'5.1.22'
    compile group: 'com.jolbox', name: 'bonecp', version:'0.7.1.RELEASE'
    compile group: 'com.google.guava', name: 'guava', version:'14.0'
    compile group: 'org.springframework', name: 'spring-core', version:'3.2.3.RELEASE'
    compile group: 'org.springframework', name: 'spring-orm', version:'3.2.3.RELEASE'
    compile group: 'org.springframework', name: 'spring-tx', version:'3.2.3.RELEASE'
    compile group: 'org.springframework', name: 'spring-context', version:'3.2.3.RELEASE'
    compile group: 'org.springframework', name: 'spring-context-support', version:'3.2.3.RELEASE'
    compile group: 'org.hibernate', name: 'hibernate-core', version:'4.1.10.Final'
    compile group: 'org.hibernate.javax.persistence', name: 'hibernate-jpa-2.0-api', version:'1.0.1. Final'
    compile group: 'org.hibernate', name: 'hibernate-entitymanager', version:'4.1.10.Final'
    compile group: 'org.hibernate', name: 'hibernate-validator', version:'4.3.1.Final'

    compile group: 'org.springframework.data', name: 'spring-data-jpa', version:'1.4.2.RELEASE'

    compile group: 'com.mysema.querydsl', name: 'querydsl-core', version:"$queryDSL"
    compile group: 'com.mysema.querydsl', name: 'querydsl-jpa', version:"$queryDSL"
    compile group: 'com.mysema.querydsl', name: 'querydsl-sql', version:"$queryDSL"

    compile "com.mysema.querydsl:querydsl-apt:$queryDSL"
    provided 'org.projectlombok:lombok:0.12.0'

    compile "org.slf4j:jcl-over-slf4j:$slf4jVersion"
    compile "org.slf4j:jul-to-slf4j:$slf4jVersion"

    provided "com.mysema.querydsl:querydsl-apt:$querydslVersion"
    provided 'org.projectlombok:lombok:0.12.0'
}

task generateQueryDSL(type: JavaCompile, group: 'build', description: 'Generates the QueryDSL query types') {
    source = sourceSets.main.java
    classpath = configurations.compile + configurations.provided
    options.compilerArgs = [
            "-proc:only",
            "-processor", "com.mysema.query.apt.jpa.JPAAnnotationProcessor"
    ]
    destinationDir = sourceSets.generated.java.srcDirs.iterator().next()
}

compileJava {
    dependsOn generateQueryDSL
    source generateQueryDSL.destinationDir
}

compileGeneratedJava {
    dependsOn generateQueryDSL
    options.warnings = false
    classpath += sourceSets.main.runtimeClasspath
}

clean {
    delete sourceSets.generated.java.srcDirs
}

idea {
    module {
        sourceDirs += file('src/main/generated')
    }
}


gradle build를 실행하면 이제 Q class들이 모두 compile 되고, jar 안에도 역시 Q class들이 모두 위치한 것을 알 수 있습니다. 



Posted by Y2K
,

gradle 정리 - code analysis

Java 2013. 10. 28. 13:07
code의 품질을 높이기 위해서 자주 쓰이는 방법중 하나는 static java code analysis를 통해서 code의 품질을 평가하는 겁니다. 

자주 쓰이는 방법으로는 checkstyle, jdepend, PMD, findbugs 등이 있고, 이러한 방법들을 체계적으로 잘 관리해주는 tool로는 sonar가 있습니다. 

1. checkstyle

apply plugin : 'checkstyle'

을 사용해서 checkstyle을 사용할 수 있습니다. checkstyle은 사용되는 check rule에 대한 xml이 반드시 존재해야지 되며, 이는 다음과 같이 설정이 가능합니다. 

checkstyle {
    configFIle = file('config/checkstyle/sun_checks.xml')
}

그리고, checkstyle의 결과는 xml 파일로 나오게 되며 이 결과는 jenkins, hudson 등의 CI tool에서 결과를 확인하는데 사용하게 됩니다. 
report path는 기본적으로 build/checkstyle 폴더에 위치하게 되며, 이 폴더의 위치와 xml 파일의 이름은 다음과 같이 설정하면 됩니다. 

checkstyle {
    reportDir = file("${buildDir}/checkstyle-output")
}
checkStyleMain {
    reports {
       xml.destination = file("${checkstyle.reportsDir}/checkstyle.xml")
     }
}

gradle에서 사용하기 위해서는 gradle checkStyleMain task를 구동하면 checkStyle 검사를 행하게 됩니다. 

2. PMD

PMD는 code style에서 발생할 수 있는 error들을 검증하는 방법입니다. checkstyle은 coding에 있어서 style만을 본다면 PMD는 io에 대한 close check, exception 등에 대한 정확한 handling이 되었는지 판별하게 됩니다. 대체적으로 PMD는 경험적인 rule이 만들어진 결과입니다. 나중에 보실 findbugs와는 조금 다른 code analysis를 보여줍니다.

PMD는 pmd plugin을 설치해서 사용 가능합니다. 

apply plugin : 'pmd'

pmd의 결과는 기본적으로 html format으로 나오게 됩니다. 또한 결과를 xml로도 볼수 있는데, xml결과는 후에 jenkins와 같은 CI tool에서 이용이 가능합니다. 이에 대한 설정은 다음과 같이 해주면 됩니다. 

pmdMain {
     reports {
          xml.destination = file("${pmd.reportsDir}/pmd.xml")
          html.enabled = false
          xml.enabled = true
     }
}


3. findbugs

findbugs는 정형적인 버그를 발견할 수 있는 tool입니다. findbugs를 이용하면 버그가 발생할 수 있는 상황을 확인하는 것이 가능합니다. type의 변환시에 값의 크기가 작아서 오작동을 일으킨다던지, 무한 loop를 돌 수 있는 pattern을 찾아낼 수 있는 방법들을 제공합니다. 

findbugs는 findbugs plugin을 설치해서 사용합니다. 


apply plugin: 'findbugs'


역시 findbugs도 PMD와 checkstyle과 같이 xml/html의 결과를 같이 보여주게 됩니다. report에 대한 설정은 다음과 같습니다. 

findbugsMain {
     reports {
          xml.enabled = true
          html.enabled = false
          xml.destination = file("${findbugs.reportsDir}/findbugs.xml")
     }
}


multi project에서 checkstyle, PMD, findbugs의 이용

multi project의 경우에는 위의 설정이 모두 subprojects에 들어가면 됩니다. 그렇지만, 위의 설정대로라면 조금 문제가 발생하게 됩니다. 각 subproject들의 checkstyle, PMD, findbugs에 warning이 발견되면 즉시 build가 중지되기 때문입니다. 따라서, 각 plugin들에 failure가 발생되더라도 계속해서 진행하는 option을 걸어줘야지 됩니다. 
또한, 각 static code analysis의 결과를 모아서 볼 필요가 있습니다. 각각의 결과들을 깊은 depth를 갖는 folder명에서 보는 것은 조금 힘듭니다. 따라서 root project가 report 결과를 모으는 작업을 할 필요가 존재합니다. 

output folder에 모든 report의 결과는 다음과 같이 표시하면 됩니다. 

ext {
     checkStylePath = file('config/checkstyle.xml')
     reportPath = file('output/report');
     reportPathA = reportPath.absolutePath.toString()
}

if(!ext.reportPath.exists()) { // 폴더가 없는 경우에 신규 생성
     ext.reportPath.mkdir()
}


subprojects {

     apply plugin: 'java'
     apply plugin: 'checkstyle'
     apply plugin: 'pmd'
     apply plugin: 'findbugs'

     sourceCompatibility = 1.7
     targetCompatibility = 1.7

     checkstyle {
          configFile = rootProject.ext.checkStylePath
          ignoreFailures = true
     }

     pmd {
          ignoreFailures = true
     }

     findbugs {
          ignoreFailures = true
     }

     findbugsMain {
          reports {
               xml.enabled = true
               html.enabled = false
               xml.destination = file("${rootProject.ext.reportPathA}/findbug/${project.name}.xml")
          }
     } 

     pmdMain {
          reports {
               xml.destination = file("${rootProject.ext.reportPathA}/pmd/${project. name}.xml")
               html.enabled = false
          }
     }

     checkstyleMain {
          reports {
               xml.enabled = true
               xml.destination = file("${rootProject.ext.reportPathA}/checkstyle/ ${project.name}.xml")
               // html.enabled = false
          }
     }

}


위와 같은 build script를 이용하면 다음과 같이 한개의 폴더 안에 잘 정리된 코드 분석 결과를 얻어낼 수 있습니다.


Posted by Y2K
,
일반적인 개발환경에서 한개의 Project만으로 구성되는 경우는 거의 없습니다. 
간단히 다음과 같은 구조를 알아보도록 하겠습니다. 

bookstore
- common             : dependency none
- commonTest       : dependenced common
- domain               : dependenced common, test dependenced commonTest
- publicWeb           : dependenced domain, common, test dependenced commonTest
- privateWeb          : dependenced domain, common, test dependenced commonTest

이와 같은 '간단한' 프로젝트 구성이 있다고 할 때, 이런 구성에 있어서 기존 maven은 multiple project를 구성해서, 선행 project들을 local repository 등에 배포 후, dependency를 얻어와야지 되는 문제가 발생한다. 또한 각각의 project를 통해 생성되는 xml, wsdl등을 만약에 다른 project에서 이용한다면 maven에서는 매우 힘든 일을 거쳐야지 된다. (하는 방법은 잘 모르겠습니다.)

Gradle은 이와 같은 문제를 project dependency를 이용해서 쉽게 해결하고 있습니다. 이런 subproject들을 가진 project의 구성은 project의 folder와 동일하게 구성됩니다.


이러한 folder구조를 만들어주고, root project에서 gradle setupBuild 를 실행시켜줍니다.
지금까지 주로 사용하고 있던 build.gradle 파일뿐 아니라, sub project가 있는 부분은 settings.gradle 파일을 이용해서 구성합니다. 

settings.gradle파일안에 다음과 같이 sub projects들을 모두 추가해줍니다.

rootProject.name = 'GradleBookStore'
include 'common', 'commonTest', 'domain', 'privateWeb', 'publicWeb'
include 시에 순서는 상관이 없습니다. 또는 다음과 같은 코드를 이용해서 처리가 가능합니다. 

rootProject.name = 'GradleBookStore'
String[] modules = ['common', 'commonTest', 'domain', 'privateWeb', 'publicWeb']
include modules

GradleBookStore project를 rootProject라고 칭하고, 나머지 project들은 모두 subproject라고 칭합니다. 


1. 공통 설정

rootProject를 비롯한 모든 project에 필요한 설정이 필요할 때가 있습니다. maven repository설정 및 모든 공용 library들이 바로 여기에 속하겠지요. 
모든 project에서 사용될 설정은 다음과 같이 수정합니다. 

allprojects {
    apply plugin: 'java'    
    repositories {
        mavenCentral()
    }

    dependencies {
        compile 'org.slf4j:slf4j-api:1.7.5'
        testCompile "junit:junit:4.11"
    }
}

allprojects로 설정이 들어가게 되면, 모든 project들은 위 속성을 모두 가지게 됩니다. 이는 모든 sub project 폴더 안에 build.gradle 파일을 만들어주고, 위 내용을 적어주는 것과 동일한 효과를 가지고 옵니다. 

2. SubProject 설정

위 공통설정과 비슷하지만 하나의 차이가 있습니다. 공통 설정으로 넣는 경우, root project 역시 java project로 구성이 됩니다. root project 역시 코드를 가질 수 있는 구조가 되게 됩니다. 그런데 이와 같은 구조는 현업에서는 잘 사용되지 않는 편입니다. root project의 경우에는 project의 이름으로 만들어주고, 내부에 subproject들로 구성이 되는 것이 일반적이지요. 

따라서, 대부분의 project들은 allprojects 보다는 subprojects를 주로 사용하게 되는 것이 일반적입니다. 

3. 특정 project 설정

특정 project에 설정을 해주는 방법은 두가지가 있습니다. 

1) subproject의 build.gradle을 작성해주는 것
2) root project의 build.gradle에서 configure를 이용하는 방법

첫번째 방법의 경우, 매우 단순합니다. 그냥 build.gradle 파일을 만들어서 넣어주면 됩니다. 그렇지만, root project의 build.gradle에서 configure를 이용하는 방법은 조금 생각을 해봐야지 됩니다. configure의 경우에는 약간 이름과 다르게, filtering을 지원하는 방법입니다. 특정 project에서 사용할 설정을 넘겨주는 것을 담당합니다. 지금 구성되어 있는 project 중에서 publicWeb, privateWeb의 경우 war plugin을 이용해서 web으로 구성할 예정입니다. 이 경우에는 다음과 같이 구성을 하면 됩니다. 

task webProjects << {
    subprojects.findAll {
        project.project.name.endsWith('Web')
    }
}

configure(webProjects) {
    apply plugin: 'war'
    task isWar << {
        println "${project.name} is web project"
    }
}

project의 이름이 Web으로 끝나는 project에서만 war plugin을 적용한 코드입니다. task를 작성해서, target이 되는 project가 무엇인지를 확인시켜줘야지 됩니다. groovy에서는 마지막에 실행된 내용이 return 값이기 때문에 전체 build가 실행이 될때, subproject중에서 이름이 'Web'으로 끝나는 project만이 configure에 있는 속성이 적용됩니다. 

이 부분을 잘 사용한다면, android project에서 target project의 모든 classes 파일들을 test project의 classes 폴더로 copy 해와서 처리를 하는 것이 가능하게 됩니다. 

4. Project간의 종속성 설정

Gradle에서 가장 강력한 기능중 하나입니다. project간의 종속성을 설정은 기존 maven에 비하여 가장 강력한 기능입니다. 
Project의 종속성은 만들어진 jar를 상대 project에서 이용하게 됩니다. 그리고, 하나의 Project에만 사용되는 설정이기 때문에, 이는 각 subproject의 build.gradle 파일을 이용합니다. 

위에서 이야기드린 것 처럼, 각각의 dependency를 다시 보면 다음과 같습니다. 

bookstore
- common             : dependency none
- commonTest       : dependenced common
- domain               : dependenced common, test dependenced commonTest
- publicWeb           : dependenced domain, common, test dependenced commonTest
- privateWeb          : dependenced domain, common, test dependenced commonTest

* common의 경우에는 dependecy가 없기 때문에 build.gradle의 내용이 없습니다.

domain jar를 gradle :domain:build를 통해 build 할때, 다음과 같은 코드가 동작합니다.

c:\workspace\GradleBookStore>gradle :domain:build
:common:compileJava UP-TO-DATE
:common:processResources UP-TO-DATE
:common:classes UP-TO-DATE
:common:jar UP-TO-DATE
:domain:compileJava UP-TO-DATE
:domain:processResources UP-TO-DATE
:domain:classes UP-TO-DATE
:domain:jar UP-TO-DATE
:domain:assemble UP-TO-DATE
:commonTest:compileJava UP-TO-DATE
:commonTest:processResources UP-TO-DATE
:commonTest:classes UP-TO-DATE
:commonTest:jar UP-TO-DATE
:domain:compileTestJava UP-TO-DATE
:domain:processTestResources UP-TO-DATE
:domain:testClasses UP-TO-DATE
:domain:test UP-TO-DATE

:domain:check UP-TO-DATE
:domain:build UP-TO-DATE


compile시에 필요한 project가 먼저 build가 되고, 그걸 이용해서 새로운 jar가 만들어지고 있는 것을 알 수 있습니다. 
특정 project만을 처리하는 것 역시 가능합니다. 기존 maven에서 문제가 되던 project -> local repository -> another project 의 build문제를 해결하고 있음을 알 수 있습니다.



Posted by Y2K
,