diff --git a/Jenkinsfile b/Jenkinsfile index c0765ea..44694f8 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -8,6 +8,10 @@ pipeline { agent any + options { + disableConcurrentBuilds() + } + tools { maven 'maven-3' } @@ -16,66 +20,92 @@ pipeline { // BUILD_NUMBER wird automatisch durch Jenkins vergeben. // Die resultierende Versionsnummer lautet: MAJOR.MINOR.BUILD_NUMBER parameters { - string( - name: 'MAJOR', - defaultValue: '3', - description: 'Hauptversionsnummer (manuell pflegen)' - ) - string( - name: 'MINOR', - defaultValue: '0', - description: 'Nebenversionsnummer (manuell pflegen)' - ) - } - - environment { - // Effektive Versionsteile – übernommen aus Parametern oder State-Datei. - // Hinweis: Wenn MAJOR/MINOR aus einer persistierten State-Datei gelesen - // werden sollen (z. B. /builds/version.state), muss die Logik unten in - // der Stage 'Version bestimmen' entsprechend ergänzt werden. - // Im Minimalbetrieb werden die Parameter direkt übernommen. - EFFECTIVE_MAJOR = "${params.MAJOR}" - EFFECTIVE_MINOR = "${params.MINOR}" + string(name: 'MAJOR', defaultValue: '3', description: 'SemVer MAJOR (manuell)') + string(name: 'MINOR', defaultValue: '0', description: 'SemVer MINOR (manuell)') } stages { - // Optionaler Stub: MAJOR/MINOR aus persistierter State-Datei laden. - // Wenn das bestehende Jenkins-Setup die Versionsnummern in einer - // State-Datei unter /builds/version.state persistiert, kann diese - // Stage die Umgebungsvariablen EFFECTIVE_MAJOR und EFFECTIVE_MINOR - // vor dem Build überschreiben. Ansonsten gelten die Parameter-Werte. stage('Version bestimmen') { steps { script { - // Platzhalter: hier bei Bedarf State-Datei einlesen, - // z. B.: - // def state = readFile('/builds/version.state').trim() - // env.EFFECTIVE_MAJOR = state.split('\\.')[0] - // env.EFFECTIVE_MINOR = state.split('\\.')[1] - // - // Im Minimalbetrieb werden die Parameter-Werte verwendet: - echo "Buildversion: ${env.EFFECTIVE_MAJOR}.${env.EFFECTIVE_MINOR}.${env.BUILD_NUMBER}" + 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 { - // -Drevision übergibt die vollständige Versionsnummer an Maven. - // Das flatten-maven-plugin im Parent-POM löst ${revision} in - // allen installierten POMs auf, sodass kein unaufgelöstes - // ${revision} in ~/.m2 verbleibt. - sh "mvn clean verify -Drevision=${env.EFFECTIVE_MAJOR}.${env.EFFECTIVE_MINOR}.${env.BUILD_NUMBER}" + 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('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 (#!/usr/bin/env bash, set -euo pipefail), - // weil Jenkins-Agenten standardmäßig sh (dash) verwenden, das kein - // mapfile kennt. mapfile zählt exakt die gefundenen Shade-JARs und - // bricht den Build ab, wenn nicht genau eines vorhanden ist. + // 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 @@ -92,25 +122,10 @@ echo "Shade-JAR archiviert als: $JAR_NAME" ''' archiveArtifacts artifacts: 'pdf-ki-renamer-*.jar', fingerprint: true } - } - - stage('Berichte') { - steps { - // JUnit-Testergebnisse einlesen - junit testResults: '**/target/surefire-reports/*.xml', allowEmptyResults: true - - // JaCoCo-Coverage (falls im Build erzeugt) - // jacoco execPattern: '**/target/jacoco.exec' - - // PIT-Mutationstest-Bericht (falls im Build erzeugt) - // publishHTML(target: [reportDir: 'target/pit-reports', ...]) - } - } + } // stage: Archive JAR stage('Artefakt ablegen') { steps { - // JAR-Kopie in zentrales Build-Verzeichnis ablegen. - // Pfad /builds/ muss auf dem Jenkins-Agent gemountet sein. sh '''#!/usr/bin/env bash set -euo pipefail @@ -120,19 +135,39 @@ 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 { - // Lokale JAR-Kopie im Workspace entfernen (Artefakt ist archiviert) sh '''#!/usr/bin/env bash set -euo pipefail rm -f pdf-ki-renamer-*.jar echo "Aufräumen abgeschlossen." ''' } - } - } + } // stage: Aufräumen + + } // stages post { success { @@ -142,8 +177,8 @@ echo "Aufräumen abgeschlossen." echo "Build ${env.EFFECTIVE_MAJOR}.${env.EFFECTIVE_MINOR}.${env.BUILD_NUMBER} fehlgeschlagen." } always { - // Workspace nach Abschluss bereinigen deleteDir() } } -} + +} // pipeline \ No newline at end of file