// Jenkins-Pipeline für den PDF KI Renamer // Läuft auf einem Linux-Container (Synology NAS). // Der MSI-Build ist Windows-only (jpackage + WiX Toolset 3.x). Jenkins läuft im // Linux-Container auf Synology NAS und kann kein MSI erzeugen. Der MSI-Build // wird bewusst manuell auf der Windows-Entwicklungsmaschine ausgeführt: // .\mvnw.cmd clean package -P release -pl pdf-umbenenner-packaging --also-make -DskipTests pipeline { agent any options { disableConcurrentBuilds() } tools { maven 'maven-3' } // MAJOR und MINOR werden manuell als Jenkins-Parameter gepflegt. // BUILD_NUMBER wird automatisch durch Jenkins vergeben. // Die resultierende Versionsnummer lautet: MAJOR.MINOR.BUILD_NUMBER parameters { string(name: 'MAJOR', defaultValue: '3', description: 'SemVer MAJOR (manuell)') string(name: 'MINOR', defaultValue: '0', description: 'SemVer MINOR (manuell)') } stages { stage('Version bestimmen') { steps { script { def isManual = !currentBuild.getBuildCauses('hudson.model.Cause$UserIdCause').isEmpty() def jenkinsHome = env.JENKINS_HOME ?: '/var/jenkins_home' def safeJobName = env.JOB_NAME.replaceAll(/[^A-Za-z0-9._-]/, '_') def stateDir = "${jenkinsHome}/version-state" def stateFile = "${stateDir}/${safeJobName}.properties" if (isManual) { env.EFFECTIVE_MAJOR = params.MAJOR env.EFFECTIVE_MINOR = params.MINOR sh """ mkdir -p '${stateDir}' cat > '${stateFile}' <<'EOF' MAJOR=${params.MAJOR} MINOR=${params.MINOR} EOF """ echo "Manueller Build erkannt. Version gespeichert: ${env.EFFECTIVE_MAJOR}.${env.EFFECTIVE_MINOR}" } else { def stateExists = (sh(script: "[ -f '${stateFile}' ]", returnStatus: true) == 0) if (stateExists) { env.EFFECTIVE_MAJOR = sh( script: "grep '^MAJOR=' '${stateFile}' | cut -d= -f2-", returnStdout: true ).trim() env.EFFECTIVE_MINOR = sh( script: "grep '^MINOR=' '${stateFile}' | cut -d= -f2-", returnStdout: true ).trim() echo "Automatischer Build erkannt. Gespeicherte Version verwendet: ${env.EFFECTIVE_MAJOR}.${env.EFFECTIVE_MINOR}" } else { env.EFFECTIVE_MAJOR = params.MAJOR env.EFFECTIVE_MINOR = params.MINOR echo "Automatischer Build ohne gespeicherten Stand. Fallback auf Parameter: ${env.EFFECTIVE_MAJOR}.${env.EFFECTIVE_MINOR}" } } currentBuild.displayName = "#${env.BUILD_NUMBER} ${env.EFFECTIVE_MAJOR}.${env.EFFECTIVE_MINOR}" } } } // stage: Version bestimmen stage('Maven Build') { steps { catchError(buildResult: 'FAILURE', stageResult: 'FAILURE') { // -Drevision übergibt die vollständige Versionsnummer an Maven. // Das flatten-maven-plugin im Parent-POM löst ${revision} in // allen installierten POMs auf. sh "mvn clean verify -Drevision=${env.EFFECTIVE_MAJOR}.${env.EFFECTIVE_MINOR}.${env.BUILD_NUMBER}" } } } // stage: Maven Build stage('SonarQube Analyse') { steps { catchError(buildResult: 'SUCCESS', stageResult: 'FAILURE') { withSonarQubeEnv('SonarQube') { sh "mvn sonar:sonar -Drevision=${env.EFFECTIVE_MAJOR}.${env.EFFECTIVE_MINOR}.${env.BUILD_NUMBER} -Dsonar.projectKey=pdf-umbenenner -Dsonar.projectName='PDF KI Renamer' -Dsonar.branch.name=main" } } } } // stage: SonarQube Analyse stage('Publish PIT Coverage') { steps { recordCoverage( tools: [[ parser: 'PIT', pattern: '**/target/pit-reports/mutations.xml' ]], id: 'pit', name: 'PIT Mutation Coverage', failOnError: true ) } } // stage: Publish PIT Coverage stage('Archive JAR') { steps { // Bash wird explizit erzwungen, weil Jenkins-Agenten standardmäßig // sh (dash) verwenden, das kein mapfile kennt. mapfile zählt exakt // die gefundenen Shade-JARs und bricht ab, wenn nicht genau eines vorhanden ist. sh '''#!/usr/bin/env bash set -euo pipefail mapfile -t JARS < <(find pdf-umbenenner-bootstrap/target \ -maxdepth 1 -name "pdf-umbenenner-bootstrap-*.jar" \ ! -name "*-sources.jar" ! -name "*-javadoc.jar") test "${#JARS[@]}" -eq 1 \ || { echo "FEHLER: Erwartet genau 1 Shade-JAR, gefunden: ${#JARS[@]}"; exit 1; } JAR_NAME="pdf-ki-renamer-${EFFECTIVE_MAJOR}.${EFFECTIVE_MINOR}.${BUILD_NUMBER}.jar" cp "${JARS[0]}" "$JAR_NAME" echo "Shade-JAR archiviert als: $JAR_NAME" ''' archiveArtifacts artifacts: 'pdf-ki-renamer-*.jar', fingerprint: true } } // stage: Archive JAR stage('Artefakt ablegen') { steps { sh '''#!/usr/bin/env bash set -euo pipefail BUILD_DIR="/builds/${EFFECTIVE_MAJOR}.${EFFECTIVE_MINOR}.${BUILD_NUMBER}" mkdir -p "$BUILD_DIR" cp pdf-ki-renamer-*.jar "$BUILD_DIR/" echo "Artefakt abgelegt unter: $BUILD_DIR" ''' } } // stage: Artefakt ablegen stage('Berichte veröffentlichen') { steps { junit testResults: '**/target/surefire-reports/*.xml', allowEmptyResults: true recordCoverage( tools: [[parser: 'JACOCO', pattern: 'pdf-umbenenner-coverage/target/site/jacoco-aggregate/jacoco.xml']], enabledForFailure: true ) publishHTML(target: [ reportName: 'JaCoCo HTML Report', reportDir: 'pdf-umbenenner-coverage/target/site/jacoco-aggregate', reportFiles: 'index.html', keepAll: true, alwaysLinkToLastBuild: true, allowMissing: true ]) } } // stage: Berichte veröffentlichen stage('Aufräumen') { steps { sh '''#!/usr/bin/env bash set -euo pipefail rm -f pdf-ki-renamer-*.jar echo "Aufräumen abgeschlossen." ''' } } // stage: Aufräumen } // stages post { success { echo "Build ${env.EFFECTIVE_MAJOR}.${env.EFFECTIVE_MINOR}.${env.BUILD_NUMBER} erfolgreich abgeschlossen." } failure { echo "Build ${env.EFFECTIVE_MAJOR}.${env.EFFECTIVE_MINOR}.${env.BUILD_NUMBER} fehlgeschlagen." } always { deleteDir() } } } // pipeline