Publish existing JAR using Gradle

I want to publish an existing JAR to a Maven repository, along with Gradle module metadata.

(This JAR is the result of a separate build process which I can’t easily migrate to Gradle since it’s fairly complex. So for now I just want to take the final JAR and publish it, so that other projects can reference it as a dependency in their build scripts.)

So far the best I could come up with is this:

plugins {
    id 'java-library'
    id 'maven-publish'
}

group = "example"
version = "2.1.0"
var inputfile = "foo020100.jar"

tasks.named('jar') {
    from zipTree(inputfile)
    manifest {
        from {zipTree(inputfile).matching{ include "/META-INF/MANIFEST.MF" }.getSingleFile()}
    }
}

tasks.named('classes') { enabled = false }
tasks.named('compileJava') { enabled = false }
tasks.named('processResources') { enabled = false }

publishing {
    publications {
        foo(MavenPublication) {
            from components.java
        }
    }

    repositories {
        maven {
            url = '...'
        }
    }
}

This does work, though it entails unzipping and re-zipping the JAR. I don’t really mind the performance overhead, but it means the published file isn’t identical at the byte level, and I’m worried this might cause confusion e.g. if someone were to compare checksums.

Therefore I’d prefer for Gradle to publish the input JAR exactly as it is. Can this be achieved somehow?

It might be easier to just create the Gradle Module Metadata file in your other build system.

Besides that without any feature variants, and not even sources and javadoc variants, there might anyway not be too much value in creating Gradle Module Metadata.
Except if the example was just greatly stripped down and you will add other feature variants to be published.

But to answer the actual questions, I think your enabled = false are pointless, classes is anyway just a lifecycle task without own actions, and compileJava and processResources will not do anything if they do not have input files. Except of course if your gradle build is in the normal source tree where the files are in the default places, then the latter two might be needed.

For the jar task it might be most appropriate to do actions.clear() to remove the actual task actions and configure the archive file property of the task directly to your pre-built jar, then this should be used as output. Alternatively it might even work to set enabled = false for the jar task and set the archive file ot the pre-built jar, I think downstream actions that use its output file should still use it, even if the task is disabled.

This was indeed just my starting point, which I now extended with dependencies and feature variants - I suppose I should have pointed out that this is what I’m aiming for.

Regarding enabled = false, yes, these are rather pointless, I just kept them to make sure nothing unexpected happens if someone were to put any files into the source tree.

I first tried your second suggestion, disabling the jar task entirely, since it seemed cleaner to me, like this:

tasks.register("copyJar", Copy) {
    from inputfile 
    into jar.destinationDirectory
    rename '.*', jar.archiveFileName.get()
}

tasks.named('jar') {
    enabled = false
    dependsOn copyJar
}

This almost works: It copies the JAR in the correct place, with the correct filename, but then the publishing step fails with the message “Artifact foo-2.1.0.jar wasn’t produced by this build.”

So I tried clearing the actions of the jar task instead, and it does the trick:

tasks.register("copyJar", Copy) {
    from inputfile 
    into jar.destinationDirectory
    rename '.*', jar.archiveFileName.get()
}

tasks.named('jar') {
    dependsOn copyJar
    actions.clear()
}

This does exactly what I was looking for. Thanks!

1 Like

I first tried your second suggestion

That was not my suggestion.
I did not say anything about a second task.
Especially not one that has overlapping outputs with an existing task which is always a bad idea.
More like

tasks.named('jar') {
    enabled = false
    archiveFile = inputfile
}

But this stale-check you are hitting will probably still complain.
So if you actually are after the clean solution, it will probably something like:

configurations {
   [apiElements, runtimeElements].each {
       it.outgoing.artifacts.removeIf { it.buildDependencies.getDependencies(null).contains(jar) }
       it.outgoing.artifact(inputfile)
   }
}

So I tried clearing the actions of the jar task instead, and it does the trick:

Also here, the additional copy task is pointless, if going this route, just declare the input file as archive file of the jar task.

Or go the clean route as just written.
This is from Upgrading your build from Gradle 6.x to 7.0 which talks about a similar problem.