How do you change the default artifact in a multi-project build?


(Scott Palmer) #1

How do you change the default artifact in a multi-project build such that dependent projects get it instead of the original default artifact?

I have a Java project that produces a jar and, if the obfuscation task runs, an obfuscated jar.

If obfuscation happens I want the obfuscated jar to be the main/default artifact.

I have already managed to remove the unobfuscated jar from the archive artifacts and replace it with the obfuscated one. This results in the correct, obfuscated,jar being uploaded to our Artifactory server. The problem is that the jar passed on to downstream projects in a multi-project build remains the original unobfuscated jar.

I also tried adding the obfuscated jar to a new configuration and have the downstream projects depend on that specific configuration. That resulted in no jar being passed at all.

To replace the published artifact I do this:

configurations.archives.artifacts.clear();

artifacts {

archives file: file("$projectDir/build/proguard/${jar.archiveName}"), builtBy: obfuscate

archives file: file("$projectDir/build/proguard/proguard_map.txt"), name: “${project.name}”, classifier: ‘proguard_map’, builtBy: obfuscate

archives file: file("$projectDir/build/proguard/proguard_seeds.txt"), name: “${project.name}”, classifier: ‘proguard_seeds’, builtBy: obfuscate

javadocs javadocJar

archives javadocJar

}

Downstream projects do this to declare the dependency:

dependencies {

myFancyConfig project(path: ‘:MyProject’)

}

but I get the original jar artifact. I tried putting the obfuscated jar in a specific configuration, but it didn’t seem to work:

artifacts {

obfuscatedJar file("$projectDir/build/proguard/${jar.archiveName}"), builtBy: obfuscate

}

then the other project would use:

dependencies {

myFancyConfig project(path: ‘:MyProject’, configuration: ‘obfuscatedJar’)

}

but the jar was not present in myFancyConfig when I did this.

Perhaps I am making a simple mistake. Help please.


(Peter Niederwieser) #2

Try to reconfigure the ‘runtime’ configuration, rather than ‘archives’. The ‘runtime’ configuration affects both ‘archives’ (which is what usually gets published) and ‘default’ (which is usually referenced from other projects).


(Scott Palmer) #3

I already tried that after finding references to it on these forums. I don’t know how, as when I try to change the runtime configuration I always get an error message:

“You can’t change a configuration which is not in unresolved state”

… even though I use the same code that I have for changing the ‘archives’.

I’m not entirely sure how a configuration gets from unresolved to resolved… but I think it means that I would have to do this after the configuration phase by finding a place to tack on a ‘doLast’ block with the same code? I though I tried that already, but I’ll try again.


(Peter Niederwieser) #4

A configuration transitions from unresolved to resolved when it is resolved for the first time, e.g. by iterating over its elements. If you get “…is not in unresolved state”, then you are trying to modify it too late. If you get this error during the configuration phase, there may be a problem lurking somewhere, because in general, configurations should only be resolved in the execution phase.


(Scott Palmer) #5

Thank you for the hints… I think I know what happened now.

My obfuscate task looked at the runtime configuration in its initializer to configure the libraryjars:

configurations.runtime.files.each { libraryjars it }

that made later attempts to modify the runtime configuration impossible.

I still have some issues…

This is all very tricky because I also need this to work with the release plugin, which changes the name of the jar archive fairly late, if it needs to remove a -SNAPSHOT. I therefore set the name of the obfuscation step’s output jar in a ‘doFirst’ block. That is too late to tell the runtime configuration about the file.


(Scott Palmer) #6

So I thought I had things working, but what is happening now is that some downstream projects get the obfuscated jar and some of them get the original jar.

This is clearly not good. All subprojects should see the same thing as the default artifact of a particular project. Is there some way to force the evaluation order to work around this? EDIT Scratch that … I found evaluationDependsOn: … looking to see if it will solve this…


(Scott Palmer) #7

Well it apparently does not solve the problem.

Even when the downstream project has:

evaluationDependsOn(":UpstreamProject")

It can still get the wrong default artifact.


(Peter Niederwieser) #8

As long as the upstream project reconfigures the ‘runtime’ configuration in the configuration phase, and downstream projects only resolve their configurations in the execution phase, I don’t see how this could happen.


(Scott Palmer) #9

It seems that some projects capture the default artifact for the uptstream project before even ‘gradle.taskGraph.whenReady’ has run for that upstream project. Setting’evaluationDependsOn(":UpstreamProject")’ has no effect.

I can see this my doing:

configurations.runtime.artifacts.removeAll { it.archiveTask.is jar }

gradle.taskGraph.whenReady {

// do some stuff and then

// put back the default artifact

artifacts {

runtime jar

}

}

It seems like there is a race condition. Some downstream projects will think the upstream project provides no artifact , while others will.


(Scott Palmer) #10

I think the ‘whenReady’ block is out of sync with the rest of the configuration phase somehow. I am changing the ‘runtime’ configuration in the ‘whenReady’ block.


(Peter Niederwieser) #11

I don’t see how this could go wrong. To help further, I’d need a self-contained reproducible example.


(Scott Palmer) #12

There is definitely something wrong with how the artifacts are handled. Getting the timing right or something is tricky, but I have reproduce a problem.

I think maybe it has to do with the replacement artifact having the same filename (but a different path)?

Not sure, but something as simple as this in a standard java project will reproduce the result. Downstream projects that depend on this one get the original jar instead of the replacement:

def obfuscatedArtifact = “${buildDir}/obfuscatedStuff/${jar.archiveName}”

// Have a task create obfuscatedArtifact

configurations.runtime.artifacts.removeAll { it.archiveTask.is jar }

artifacts {

runtime file: file(obfuscatedArtifact), builtBy: obfuscate

}

If you take away the part that puts in the new artifact, then suddenly the downstream project doesn’t get any files. So clearly the removeAll is working.


(Scott Palmer) #13

I’ve tried changing the filename and providing a classifier in my replacement default artifact and it has no effect.

I can consistently reproduce the effect of most of the sub-projects getting the original jar and one project getting the correct (replaced) jar.

In both sub-projects the dependency is declared the same, but on different configurations. Both configurations are ones that I have declared in those sub-projects. In the projects that fail the configuration is declared for the purpose of copying the file or from the contents of the file. In the sub-project that works the dependency is declared for purposes of getting it on the compile classpath - but I am using a workaround to create a ‘provided’ scope:

configurations {

provided

}

sourceSets {

main.compileClasspath += configurations.provided

test.compileClasspath += configurations.provided

test.runtimeClasspath += configurations.provided

}

in both sub-projects I use this code (on different tasks and using the appropriate configuration) to see the path of the provided dependencies:

compileJava.doFirst {

configurations.provided.findAll {

println “*********** provided jars include: $it”

}

}

One project prints the original jar path, the other prints the path to my obfuscated jar.

I cannot make sense of it. Are you able to reproduce at least the effect of removing the default jar, attempting to add a different file as the default artifact and getting the original jar? That part is easy to replicate on a new project. I’m still trying to replicate the case where I do get the replacement artifact in one sub-project, but hopefully reproducing the failing case will help uncover a solution.


(Scott Palmer) #14

I found the problem!

Some downstream projects were resolving the configuration in the configuration phase before the runtime artifact was replaced. It seems that there may be an issue with

“You can’t change a configuration which is not in unresolved state” when it is being resolved in a different project of a multi-project build. In other words, maybe I should have go that error in cases where I did not?

The specific issue was the use of the configuration in a Copy task. The configuration of the copy task was doing something like this:

task copyStuff(type: Copy, dependsOn: configurations.upstreamJars) {

into ‘someFolder’

from { configurations.upstreamJars.findAll { it.name.startsWith(‘Upstream-’) }.collect { zipTree(it) } }

}

When I changed it to use another task that runs before the copy to configure the copy task it started to work.


(Scott Palmer) #15

I also found it was necessary to explicitly remove the jar from the archive artifacts as well, or it wouldn’t be replaceable as a runtime artifact.