[JAVA] I wrote a Jenkins file with Declarative Pipeline (Checkstyle, Findbugs, PMD, CPD, etc.)

I wrote a Jenkins file to CI a Gradle Java application with Jenkins. When I push it to Github, the Jenkins pipeline job on AWS runs, and if the test is successful, I use it like deploying to Tomcat on AWS as well.

Declarative Pipeline As it is, Jenkinsfile

node {
  ....
}

It was written as, but if you look at Jenkins official website This is the Scripted Pipelines notation, From version 2.5 migration of Pipeline Plugin

pipeline {
  ....  
}

A notation called Declarative Pipeline was introduced to write like It was simpler and easier to write, so I rewrote it. It's certainly neat (especially the last email, deploying), It's nice to be able to mix traditional Scripted Pipelines in case of emergency.

Added plugin

Jenkins Not included in the Suggested Plugin at the time of initial setup

Jenkinsfile Run a Jenkins pipeline job on a webhook from Github. For pipeline jobs, use Jenkinsfile on Github. The job flow is as follows. Deploy only if static code analysis and testing are successful. image

Jenkinsfile


pipeline {
    agent any
    //Define constants and variables
    environment {
        reportDir = 'build/reports'
        javaDir = 'src/main/java'
        resourcesDir = 'src/main/resources'
        testReportDir = 'build/test-results/test'
        jacocoReportDir = 'build/jacoco' 
        javadocDir = 'build/docs/javadoc'
        libsDir = 'build/libs'
        appName = 'SampleApp'
        appVersion = '1.0.0'
    }
    
    //Define one or more stages in the stages block
    stages {
        stage('Advance preparation') {
            //The actual processing is defined in the steps block
            steps {
                deleteDir()

                //Check out the Github project that triggered this Job
                checkout scm

                //Jenkinsfile and build for investigating the cause of job failure.gradle save first
                archiveArtifacts "Jenkinsfile"
                archiveArtifacts "build.gradle"

                //You can also use the traditional Scripted Pipelines notation with the script block
                script {
                    //Grant execute permission so that you will not get angry with Permission denied
                    if(isUnix()) {
                        sh 'chmod +x gradlew'
                    }
                }
                gradlew 'clean'
            }
        }
        
        stage('compile') {
            steps {
                gradlew 'classes testClasses'
            }
            
            //The post block can define the process to be executed after the steps block
            post {
                //The always block is always executed regardless of whether the steps block processing fails or succeeds.
                always {

                    //If you execute it when JavaDoc is generated, JavaDoc warnings will also be included.
                    //Java compile-time warnings are collected immediately after compile
                    step([

                        //The class specification when executing the plugin does not have to be a fully qualified name.
                        $class: 'WarningsPublisher',

                        //ConsoleParsers, if you want to collect alerts from the console when running a job
                        // pmd.When collecting from a file such as xml, specify parser Configurations.
                        //In the case of parser Configurations, in addition to parser Name, pattern(Path of the file to be aggregated)Must also be specified
                        //Use the parser name defined in the property file below.
                        // https://github.com/jenkinsci/warnings-plugin/blob/master/src/main/resources/hudson/plugins/warnings/parser/Messages.properties
                        consoleParsers: [
                            [parserName: 'Java Compiler (javac)'],
                        ],
                        canComputeNew: false,
                        canResolveRelativesPaths: false,
                        usePreviousBuildAsReference: true
                    ])
                }
            }
        }
        
        stage('Static code analysis') {
            steps {
                //Use parallel method for parallel processing
                parallel(
                    'Static code analysis' : {
                        gradlew 'check -x test'

                        //You can specify the current directory with the dir method
                        dir(reportDir) {
                            step([
                                $class: 'CheckStylePublisher',
                                pattern: "checkstyle/*.xml"
                            ])
                            step([
                                $class: 'FindBugsPublisher',
                                pattern: "findbugs/*.xml"
                            ])
                            step([
                                $class: 'PmdPublisher',
                                pattern: "pmd/*.xml"
                            ])
                            step([
                                $class: 'DryPublisher',
                                pattern: "cpd/*.xml"
                            ])
                
                            archiveArtifacts "checkstyle/*.xml"
                            archiveArtifacts "findbugs/*.xml"
                            archiveArtifacts "pmd/*.xml"
                            archiveArtifacts "cpd/*.xml"
                        }
                    },
                    'Step count': {
                        //Report creation
                        //If you specify outputFile and outputFormat, an Excel file will also be created.
                        stepcounter outputFile: 'stepcount.xls', outputFormat: 'excel', settings: [
                            [key:'Java', filePattern: "${javaDir}/**/*.java"],
                            [key:'SQL', filePattern: "${resourcesDir}/**/*.sql"],
                            [key:'HTML', filePattern: "${resourcesDir}/**/*.html"],
                            [key:'JS', filePattern: "${resourcesDir}/**/*.js"],
                            [key:'CSS', filePattern: "${resourcesDir}/**/*.css"]
                        ]
                        //For the time being, save the Excel file as a deliverable
                        archiveArtifacts "stepcount.xls"
                    },
                    'Task scan': {
                        step([
                            $class: 'TasksPublisher',
                            pattern: './**',
                            //Is it case sensitive when searching for aggregation targets?
                            ignoreCase: true,
                            //Character strings to be aggregated can be specified for each priority
                            //If you specify more than one, specify a comma-separated character string.
                            high: 'System.out.System.err',
                            normal: 'TODO,FIXME,XXX',
                        ])
                    },
                    'JavaDoc': {
                        gradlew 'javadoc -x classes'
                        step([
                            $class: 'JavadocArchiver',
                            //Javadoc index.Specify the path of the folder where the html is located
                            javadocDir: "${javadocDir}",
                            keepAll: true
                        ])
                    }
                )
            }
            
            post {
                always {
                   //Collect Javadoc warnings
                    step([
                        $class: 'WarningsPublisher',
                        consoleParsers: [
                            [parserName: 'JavaDoc Tool']
                        ],
                        canComputeNew: false,
                        canResolveRelativesPaths: false,
                        usePreviousBuildAsReference: true
                    ])
                }
            }
        }
        

        stage('test') {
            steps {
                gradlew 'test jacocoTestReport -x classes -x testClasses'
                
                junit "${testReportDir}/*.xml"
                archiveArtifacts "${testReportDir}/*.xml"

                //Generate coverage report (excluding test classes)
                step([
                    $class: 'JacocoPublisher',
                    execPattern: "${jacocoReportDir}/*.exec",
                    exclusionPattern: '**/*Test.class'
                ])
            }
        }
        
        stage('Deploy') {
            //You can specify the conditions for executing stage in the when block
            when {
                //Do not deploy on static code analysis and test failure
                expression {currentBuild.currentResult == 'SUCCESS'}
            }
            
            steps {
                gradlew 'jar'
                archiveArtifacts "${libsDir}/${appName}-${appVersion}.jar"
                gradlew 'war'
                archiveArtifacts "${libsDir}/${appName}-${appVersion}.war"
                deploy warDir: libsDir, appName: appName, appVersion: appVersion
            }
        }
    }
    
    //If you define a post block at the same level as the stages block
    //It is possible to define the processing after all stage processing is completed.
    post {
        always {
            //Finally delete the contents of the workspace
            deleteDir()
        }
        //Send an email to yourself except when you are successful in a row

        //When the result changes from the last time
        changed {
            sendMail("${currentBuild.previousBuild.result} => ${currentBuild.currentResult}")
        }
        //When it fails
        failure {
            sendMail(currentBuild.currentResult)
        }
        //Instability (mainly when the test fails)
        unstable {
            sendMail(currentBuild.currentResult)
        }
    }
}


//Execute the Gradlew command
def gradlew(command) {
    if(isUnix()) {
        sh "./gradlew ${command} --stacktrace"
    } else {
        bat "./gradlew.bat ${command} --stacktrace"
    }
}

//Deploy
// args.warDir war storage directory
// args.appName app name
// args.appVersion App version
def deploy(Map args) {
    //Private key path * Since the file is transferred to the Tomcat server, it is necessary to store the private key somewhere on the Jenkins server in advance.
    def keyDir = '/var/lib/jenkins/.ssh/xxx'
    //Tomcat server address and username
    def webServerAddress = 'ecX-XX-XXX-X-X.xx-xxxx-x.xxxxxxxx'
    def webServerUser = 'hoge-user'
    def webServer = "${webServerUser}@${webServerAddress}"
    
    def srcWar = "${args.appName}-${args.appVersion}.war"
    def destWar = "${args.appName}.war"
    
    //Transfer files and place war in tomcat webapps
    sh "sudo -S scp -i ${keyDir} ./${args.warDir}/${srcWar} ${webServer}:/home/ec2-user"
    sh "sudo -S ssh -i ${keyDir} ${webServer} \"sudo cp /home/ec2-user/${srcWar} /usr/share/tomcat8/webapps/${destWar}\""
}

//Send an email to Gmail
def sendMail(result) {
    mail to: "[email protected]",
        subject: "${env.JOB_NAME} #${env.BUILD_NUMBER} [${result}]",
        body: "Build URL: ${env.BUILD_URL}.\n\n"
}

Stumble

image

image

image

image

build.gradle Jenkins itself just executes Gradle commands and generates a report based on the output result, so It is necessary that the following processing can be executed in build.gradle of Gradle's Java application. Also, create a Gradle wrapper so that you don't have to install Gradle in Jenkins.

For example,

build.gradle


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

ext {
    appVersion = '1.0.0'
    appName = 'SampleApp'
    javaVersion = 1.8
    defaultEncoding = 'UTF-8'
}

sourceCompatibility = javaVersion
targetCompatibility  = javaVersion
tasks.withType(AbstractCompile)*.options*.encoding = defaultEncoding
tasks.withType(GroovyCompile)*.groovyOptions*.encoding = defaultEncoding
mainClassName = 'jp.takumon.sapmleapp.App'

repositories {
    mavenCentral()
}

dependencies {
    //List dependent libraries

    compile group: 'junit', name: 'junit', version: '4.12'
}

jar {
    baseName = appName
    version =  appVersion
}

war {
    baseName = appName
    version =  appVersion
}

checkstyle {
    //Continue subsequent processing even if it fails
    ignoreFailures = true
    sourceSets = [sourceSets.main]
    toolVersion = '7.6.1'
}

findbugs {
    //Continue subsequent processing even if it fails
    ignoreFailures = true
    sourceSets = [sourceSets.main]
    toolVersion = "3.0.1"
}

pmd {
    //Continue subsequent processing even if it fails
    ignoreFailures = true
    sourceSets = [sourceSets.main]
}

tasks.withType(Pmd) {
    reports {
      xml.enabled = true
    }
}

//Added CPD (Duplicate Code Check Processing) to Check Task
check.doLast {
    File outputDir = new File("$reportsDir/cpd/")
    outputDir.mkdirs()
  
    ant.taskdef(
        name: 'cpd', 
        classname: 'net.sourceforge.pmd.cpd.CPDTask',
        classpath: configurations.pmd.asPath)
  
    ant.cpd(
        minimumTokenCount: '100',
        format: 'xml',
        encoding: defaultEncoding,
        outputFile: new File(outputDir, 'cpd.xml')
    ) {
        fileset(dir: "src/main/java") {
            include(name: '**/*.java')
        }
    }
}

javadoc {
    failOnError = false
    //At your favorite level
    options.memberLevel = JavadocMemberLevel.PRIVATE
}

test {
    //Continue subsequent processing even if it fails
    ignoreFailures = true
    reports {
        junitXml.enabled = true
    }
}

jacoco {
    toolVersion = '0.7.5.201505241946'
}

jacocoTestReport {
    reports {
      xml.enabled = true
    }
    
    //Exclude test classes from coverage report
    afterEvaluate { 
        classDirectories = files(classDirectories.files.collect {
            fileTree(dir: it, exclude: ['**/*Test.class']) 
        })
    }
}

task wrapper (type: Wrapper) {
    gradleVersion = '3.4.1'
}

that's all.

Recommended Posts

I wrote a Jenkins file with Declarative Pipeline (Checkstyle, Findbugs, PMD, CPD, etc.)
I built a Code Pipeline with AWS CDK.
I want to monitor a specific file with WatchService
I tried OCR processing a PDF file with Java
I wrote a test with Spring Boot + JUnit 5 now
I wrote a CRUD test with SpringBoot + MyBatis + DBUnit (Part 1)
I tried OCR processing a PDF file with Java part2