From 8c9a43bc8b929215d6d5320838fd9dfbac3608f6 Mon Sep 17 00:00:00 2001 From: marko-bekhta Date: Wed, 2 Oct 2024 10:41:35 +0200 Subject: [PATCH] Make the build reproducible and add a check --- .../workflows/reproducible-build-check.yml | 38 +++++++++++++ buildSrc/src/main/groovy/java-module.gradle | 8 ++- ci/compare-build-results.sh | 54 +++++++++++++++++++ 3 files changed, 99 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/reproducible-build-check.yml create mode 100755 ci/compare-build-results.sh diff --git a/.github/workflows/reproducible-build-check.yml b/.github/workflows/reproducible-build-check.yml new file mode 100644 index 0000000..d078c5a --- /dev/null +++ b/.github/workflows/reproducible-build-check.yml @@ -0,0 +1,38 @@ +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. +# This workflow will build a Java project with Gradle and cache/restore any dependencies to improve the workflow execution time +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-gradle + +name: Java CI Reproducible build check + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +permissions: + contents: read + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - name: Set up JDK 17 + uses: actions/setup-java@v3 + with: + java-version: '17' + distribution: 'temurin' + - name: Setup Gradle + uses: gradle/actions/setup-gradle@af1da67850ed9a4cedd57bfd976089dd991e2582 # v4.0.0 + - name: Check artifacts + shell: bash + run: | + ./gradlew --no-daemon clean publishToMavenLocal --no-build-cache -Dmaven.repo.local=${GITHUB_WORKSPACE}/build1 + ./gradlew --no-daemon clean publishToMavenLocal --no-build-cache -Dmaven.repo.local=${GITHUB_WORKSPACE}/build2 + ./ci/compare-build-results.sh ${GITHUB_WORKSPACE}/build1 ${GITHUB_WORKSPACE}/build2 diff --git a/buildSrc/src/main/groovy/java-module.gradle b/buildSrc/src/main/groovy/java-module.gradle index 5e98804..e78c227 100644 --- a/buildSrc/src/main/groovy/java-module.gradle +++ b/buildSrc/src/main/groovy/java-module.gradle @@ -50,6 +50,12 @@ tasks.withType( JavaCompile ).configureEach {javaCompile-> options.warnings false } +// To force the build produce the same byte-for-byte archives and hence make Hibernate Models build reproducible. +// See also https://docs.gradle.org/current/userguide/working_with_files.html#sec:reproducible_archives +tasks.withType( AbstractArchiveTask ).configureEach { + preserveFileTimestamps = false + reproducibleFileOrder = true +} // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Javadoc @@ -150,4 +156,4 @@ tasks.named( "check" ) { dependsOn enforceRulesTask dependsOn tasks.named( "spotlessCheck" ) dependsOn jacocoReportTask -} \ No newline at end of file +} diff --git a/ci/compare-build-results.sh b/ci/compare-build-results.sh new file mode 100755 index 0000000..8092ccc --- /dev/null +++ b/ci/compare-build-results.sh @@ -0,0 +1,54 @@ +#!/usr/bin/env bash + +# This is a simple script to check if builds are reproducible. The steps are: +# 1. Build Hibernate Models with `./gradlew --no-daemon clean publishToMavenLocal --no-build-cache -Dmaven.repo.local=some-path/out/build1` +# 2. Build Hibernate Models 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 +set -xv + +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