gradle reproducible builds - gives different sha1s...
# community-support
s
gradle reproducible builds - gives different sha1sum/md5sum on multiple builds with same jdk/gradle even when there is no change in source code.. and in my case, manifest also empty..
I used below snippets:
Copy code
jar {
    preserveFileTimestamps = false
    reproducibleFileOrder = true
}

//No luck

tasks.withType(JavaCompile) {
    options.compilerArgs.addAll(["-Xlint:unchecked", "-g:source,lines"])
}

//No luck

tasks.withType(JavaCompile) {
    options.compilerArgs.addAll(["-g:none"])
}

//Worked - but this removes all debuging information, which will be a problem in prod if any issue occurs..
j
That -g:none solves it sounds weird.... But you can try this plugin, which configures a few more things: https://github.com/gradlex-org/reproducible-builds
s
Reproducibility settings applied to some of Gradle's built-in tasks, that should really be the default. Compatible with Java 8 and Gradle 8.3 or later. This is blocking to use this plugin.. we are using Gradle-7.6.4 ..
v
that should really be the default
Feel free to open a feature request. 🙂 But actually I disagree to enable features by default that loose information for the small use-case of byte-to-byte reproducible builds that only a very small subset of users are interested in. 🤷‍♂️
s
Moreover, as per documentation, plugin is doing exactly what i pasted above except permissions.. i even tried with UTF-8 encoding option but still getting sha1sum different
d
Did you compare the contents and see where the differences come from?
s
I used beyond compare tool.. it shows no diffs .. but still sha1sum different.. strange
let me extract and run diff in server...
d
If there's no diff in the content it might be the file order, you can list the files in both and compare the order to see if there's any differences (though I'd expect
setReproducibleFileOrder
to sort this)
j
I suggest you to try comparing using
diffoscope
that was specifically designed for that kind of comparisons.
s
sure.. let me give a try
j
d
That looks pretty nice 👀
v
Do you sing the artifact in question?
Signatures typically contain a timestamp when the jar was signed, so that the signature stays valid even if the certificate used to sign expires or was revoked.
s
No.. I dont use any signing.. its simple jar artifact with few java files..
v
Ok, if that tool didn't help, maybe you can provide example jars you expect to be the same but are not
s
sure...i'm trying diffoscope.. will try demo with sample jars once done
This worked:
Copy code
tasks.withType(AbstractArchiveTask) {
    preserveFileTimestamps = false
    reproducibleFileOrder = true
}
but not this:
Copy code
jar {
    preserveFileTimestamps = false
    reproducibleFileOrder = true
}
v
Then you probably did not look at the
jar
task output, but the output of a different task.
Be aware that you should never do
tasks.withType(...) { ... }
as this breaks task-configuration avoidance for all those tasks
Instead you should do
tasks.withType(...).configureEach { ... }
And I recommend you switch to Kotlin DSL. By now it is the default DSL, you immediately get type-safe build scripts, actually helpful error messages if you mess up the syntax, and amazingly better IDE support if you use a good IDE like IntelliJ IDEA or Android Studio. 🙂
👍 1
s
Due to regulatory concerns, we need to share sha1sum of few classes from the jar whenever build happens... we are planning to integrate it as part of build .. is there any built-in feature available in gradle? Or any plugin which provides thise feature? Currently i see 2 options: 1. Invoking CLI as part of gradle exec task (but this won't work in windows machines but works for linux where sha1sum/md5sum in-built tools) 2. Using MessageDigest API which works in both windows & Linux..
Copy code
import java.security.MessageDigest

tasks.register("computeClassFileChecksums") {
    group = "verification"
    description = "Computes SHA1 checksum for all compiled .class files"

    doLast {
        def classesDir = file("${buildDir}/classes") // Adjust for your output path
        if (!classesDir.exists()) {
            println "No class files found. Ensure the project is compiled first."
            return
        }

        def digest = MessageDigest.getInstance("SHA-1")
        def classFiles = classesDir.listFilesRecursively().findAll { it.name.endsWith(".class") }.sort()

        classFiles.each { classFile ->
            def sha1sum = classFile.bytes.inject(digest.clone()) { d, b -> d.update(b); d }.digest().encodeHex().toString()
            println "$sha1sum  ${classFile.path}"
        }
    }
}

// Helper function for recursive file listing
File.metaClass.listFilesRecursively = { ->
    delegate.isDirectory() ? delegate.listFiles().collect { it.listFilesRecursively() }.flatten() : [delegate]
}
So in gitlab build-log it simply shows respective checksums. Which is better approach? or any other valuable suggestions?
j
You should probably go with 2. for portability and performance reasons.
👍 1
v
I would search for some plugin, maybe there is something that can do it. But at least if not, I agree. Using the API is much better than depending on availability of command-line tools and invoking external processes. It can be polished quiet some bit, for example by declaring the source set output as input files, so that you get the necessary task dependencies and do not need to check whether the classes dir exists. That check is quite pointless anyway, as it could also be a stale directory from a previous run so the checksums might not be correct unless you did a clean build. Also for getting the files to checksum, I'd probably simply us a file tree, for such things Gradle has nice API and you do not need to use traditional file-walking APIs.
👍 1
s
Yeah.. i searched for plugins.. hardly found one or two plugins but those very old and not maintained any more.. so i'm writing one myself which takes necessary inputs like files, algorithm like sha1,md5sum etc ...
v
As ad-hoc task in Kotlin DSL it should be something along the lines of
Copy code
val computeClassFileChecksums by tasks.registering {
    group = "verification"
    description = "Computes SHA1 checksum for all compiled .class files"
    inputs
        .files(sourceSets.main.map { it.output })
        .withPropertyName("inputFiles")
        .skipWhenEmpty()

    doLast {
        val digest = MessageDigest.getInstance("SHA-1")

        sourceSets
            .main
            .get()
            .output
            .asFileTree
            .matching { include("**/*.class") }
            .sorted()
            .forEach { file ->
                val sha1sum = digest
                    .apply { reset() }
                    .digest(file.readBytes())
                    .let { it.joinToString("") { "%02x".format(it) } }
                println("$sha1sum  ${file.path}")
            }
    }
}
👍 1