How to inject a deterministic unique buildId into Gradle's java jar manifest


(Nathan) #1

I want to inject a unique ‘buildId’ into the java plugin’s jar task’s manifest (MANIFEST.MF). The requirements are:

  • Unique for different builds: commit is not good enough, since there may be uncommitted changes on dev machines
  • Deterministic: timestamp does not work, since Gradle will see it as a ‘change’ in the dependencies, and rebuild the jar even when the other dependencies don’t change

I think the correct solution is to use a hash of the actual dependencies to the ‘jar’ task, which is ‘classes’, which is all of the compiled class files that will be included in the jar.

I could, in theory, create a task which takes a hash of all the classes, and injects that into the manifest.

However, Gradle is already doing this, and that is how it determines if a task is up to date. It takes a hash of the input and a hash of the output, and checks if that combo is in it’s database. So, is there a way to get access to this information using some rare Gradle API, or by using reflection?

Or maybe there is some other solution?

Also, I posted this question to StackOverflow


(Nathan) #2

One person has responded on SO, but he was confused by my goals. So here is my motivation behind not using just commit id:

We have a cache of computed data stored on disk. We want to know if that computed data came from this version or a different version of the build. Currently we use commit + timestamp stored in MANIFEST.MF as a relatively unique way of identifying the build. However, timestamp causes incremental build issues in gradle, as gradle knows the time has changed, regenerates MANIFEST.MF, and re-jars the jar, even though nothing else has changed.


(Benjamin Muschko) #3

From my perspective the deliverable artifact should never be built and published on a developer’s machine. It should be built on a CI box which means there shouldn’t be any uncommitted changes. Whatever is checked into version control will be used. Thus it should be totally viable to use the commit hash.

You could also guard yourself by checking for uncommitted changes before building any archive. If you find uncommitted changes, fail the build.


(Schalk Cronjé) #4

You could try add a TaskExecutionListener, which in the beforeExecute method could query task.inputs to get all of the files you are interested, then generate the hash and update MANIFEST.


(Nathan) #5

@bmuschko Developers will be testing implementation on their machines, and may have uncommitted changes. I want to use a buildId to invalidate a cache on disk. If buildId does not change, it would not invalidate. So, someone may make a change to the code that generates the cache, and then run it locally to test it. Since their commit had not changed, their cache would not be invalidated, and they could potentially get errors. This is why I want one that is not just the commitId

@Schalk_Cronje Does beforeExecute happen before doFirst? I have found that in the java plugin’s jar task generates the MANIFEST.MF file before doFirst runs. I can overwrite the MANIFEST.MF in doFirst, but it is tricking gradle in a way, which I don’t think is good.

And if I query all the inputs from task.inputs, how would I hash all of them? I do not want to run a sha1 or checksum on them myself, especially since gradle already does this to determine if the task has to run. Is there any java reflection I can do to get this information?


(Schalk Cronjé) #6

Yes, it is executed before any task actions.


(Schalk Cronjé) #7

My idea was that you just hash the files yourself as per task.inputs.files, but as you would want to try to use the cache, I don’t have an answer to that. There is nothing in the public API that I am aware of.


(Malone Hedges) #8

@nathanabercrombie TBT

Any luck with this if you can remember? Trying to do a similar thing. Making the version number based on the current git hash and it won’t filter these files again with the cache thinking nothing has changed.


(Nathan) #9

We gave up on the deterministic part, and just generated a guid each time.