diff --git a/ci/compare-build-results.sh b/ci/compare-build-results.sh new file mode 100644 index 000000000..6b45022ce --- /dev/null +++ b/ci/compare-build-results.sh @@ -0,0 +1,53 @@ +#!/usr/bin/env bash + +# This is a simple script to check if builds are reproducible. The steps are: +# 1. Build Hibernate Reactive with `./gradlew --no-daemon clean publishToMavenLocal --no-build-cache -Dmaven.repo.local=some-path/out/build1` +# 2. Build Hibernate Reactive with `./gradlew --no-daemon clean publishToMavenLocal --no-build-cache -Dmaven.repo.local=some-path/out/build2` second time pointing to a different local maven repository to publish +# 3. Compare the build results with sh ./ci/compare-build-results.sh some-path/out/build1 some-path/out/build2 +# 4. The generated .buildcompare file will also contain the diffscope commands to see/compare the problematic build artifacts + +outputDir1=$1 +outputDir2=$2 +outputDir1=${outputDir1%/} +outputDir2=${outputDir2%/} + +ok=() +okFiles=() +ko=() +koFiles=() + +for f in `find ${outputDir1} -type f | grep -v "javadoc.jar$" | grep -v "maven-metadata-local.xml$" | sort` +do + flocal=${f#$outputDir1} + # echo "comparing ${flocal}" + sha1=`shasum -a 512 $f | cut -f 1 -d ' '` + sha2=`shasum -a 512 $outputDir2$flocal | cut -f 1 -d ' '` + # echo "$sha1" + # echo "$sha2" + if [ "$sha1" = "$sha2" ]; then + ok+=($flocal) + okFiles+=(${flocal##*/}) + else + ko+=($flocal) + koFiles+=(${flocal##*/}) + fi +done + +# generate .buildcompare +buildcompare=".buildcompare" +echo "ok=${#ok[@]}" >> ${buildcompare} +echo "ko=${#ko[@]}" >> ${buildcompare} +echo "okFiles=\"${okFiles[@]}\"" >> ${buildcompare} +echo "koFiles=\"${koFiles[@]}\"" >> ${buildcompare} +echo "" >> ${buildcompare} +echo "# see what caused the mismatch in the checksum by executing the following diffscope commands" >> ${buildcompare} +for f in ${ko[@]} +do + echo "# diffoscope $outputDir1$f $outputDir2$f" >> ${buildcompare} +done + +if [ ${#ko[@]} -eq 0 ]; then + exit 0 +else + exit 1 +fi diff --git a/ci/nightly/Jenkinsfile b/ci/nightly/Jenkinsfile new file mode 100644 index 000000000..945f02606 --- /dev/null +++ b/ci/nightly/Jenkinsfile @@ -0,0 +1,49 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ + +@Library('hibernate-jenkins-pipeline-helpers') _ + +pipeline { + agent none + triggers { + cron '@midnight' + } + tools { + jdk 'OpenJDK 17 Latest' + } + options { + buildDiscarder logRotator(daysToKeepStr: '10', numToKeepStr: '3') + disableConcurrentBuilds(abortPrevious: true) + overrideIndexTriggers(false) + } + stages { + stage('Build reproducibility check') { + agent { + label 'Worker&&Containers' + } + steps { + timeout(time: 30, unit: 'MINUTES') { + script { + def tempDir = pwd(tmp: true) + def repo1 = tempDir + '/repo1' + def repo2 = tempDir + '/repo2' + // build Hibernate Reactive two times without any cache and "publish" the resulting artifacts + // to different maven repositories, so that we can compare them afterwards: + sh "./gradlew --no-daemon clean publishToMavenLocal --no-build-cache -Dmaven.repo.local=${repo1}" + sh "./gradlew --no-daemon clean publishToMavenLocal --no-build-cache -Dmaven.repo.local=${repo2}" + + sh "sh ci/compare-build-results.sh ${repo1} ${repo2}" + sh "cat .buildcompare" + } + } + } + } + } + post { + always { + notifyBuildResult maintainers: 'davide@hibernate.org' + } + } +} diff --git a/hibernate-reactive-core/build.gradle b/hibernate-reactive-core/build.gradle index 87652c572..fca12935f 100644 --- a/hibernate-reactive-core/build.gradle +++ b/hibernate-reactive-core/build.gradle @@ -81,6 +81,15 @@ dependencies { testImplementation "org.testcontainers:oracle-xe:${testcontainersVersion}" } +// Reproducible Builds + +// https://docs.gradle.org/current/userguide/working_with_files.html#sec:reproducible_archives +// Configure archive tasks to produce reproducible archives: +tasks.withType(AbstractArchiveTask).configureEach { + preserveFileTimestamps = false + reproducibleFileOrder = true +} + // Print a summary of the results of the tests (number of failures, successes and skipped) def loggingSummary(db, result, desc) { if ( !desc.parent ) { // will match the outermost suite