Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Packaging multiproject build into fat jar fails #7

Closed
MikaelAmborn opened this issue Dec 3, 2020 · 7 comments
Closed

Packaging multiproject build into fat jar fails #7

MikaelAmborn opened this issue Dec 3, 2020 · 7 comments

Comments

@MikaelAmborn
Copy link

With a JPMS enabled gradle multi-project I fail to build a fat jar using this plugin and the Gradle shadow plugin.
I created a minimal test project to showcase the error https://github.com/MikaelAmborn/javamodules. Clone it and run ./gradlew shadowJar and it will fails on ExtraModuleInfoTransform.java:85 with this error:

Could not determine the dependencies of task ':app:shadowJar'.
> Could not resolve all files for configuration ':app:runtimeClasspath'.
   > Failed to transform utilities.jar (project :utilities) to match attributes {artifactType=jar, javaModule=true, org.gradle.category=library, org.gradle.dependency.bundling=external, org.gradle.jvm.version=11, org.gradle.libraryelements=jar, org.gradle.usage=java-runtime}.
      > Execution failed for ExtraModuleInfoTransform: /Users/mikaelamborn/development/javamodules/utilities/build/libs/utilities.jar.
         > java.io.FileNotFoundException: /Users/mikaelamborn/development/javamodules/utilities/build/libs/utilities.jar (No such file or directory)

But if you first build a jar for the utilities project with ./gradlew utilities:jar and then ./gradlew shadowJar everything works.
Could it be related to gradle/gradle#10484?

@iherasymenko
Copy link
Contributor

iherasymenko commented Dec 10, 2020

Building a fat jar of a properly modularized application, imo, kind of contradicts the idea of JPMS. You won't be able to run such a jar on the module path and experience all the benefits of JPMS. Have you considered using the standard 'application' plugin instead? Gradle has a first class support of JPMS there.

@MikaelAmborn
Copy link
Author

Thank you, you are right that we lose the benefits of module boundaries and constraints during runtime if we package it as a fat jar. But we still get all the benefits from the module system during development.
I will look into the application plugin and see if we can use that instead of building a fat jar.

@jjohannes
Copy link
Member

@MikaelAmborn this seems to reveal an issue in the shadow plugin and how it defines its input for the shadowJar task. I had a quick look, but the setup is rather complex. This seem to trigger things in the wrong order. We would need to dig deeper to see what exactly is wrong.

You can disable the functionality of this plugin for the "runtimeClasspath" of the application. I think this is fine/good as you throw away the module information at runtime in any case. If you do it, the shadow plugin works as expected.

app/build.gradle

configurations {
    runtimeClasspath {
        attributes { attribute(Attribute.of("javaModule", Boolean), false) }
    }
}

@MikaelAmborn
Copy link
Author

Thank you for the suggested solution. The problem with it is that it breaks ./gradlew run and IntelliJ starts complaining that the reference to commons.collections in module-info.java is invalid.

@jjohannes
Copy link
Member

@MikaelAmborn yes I did not think about this. I think you can also keep the normal runtimeClasspath untouched, but instead create a specific one for the shadowJar only. Something like this:

val shadowRuntime = configurations.create("shadowRuntime") {
   extendsFrom(configurations.runtimeClasspath)
   attributes { attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage.JAVA_RUNTIME)) }
}

tasks.shadowJar {
  configurations = listOf(shadowRuntime)
}

Does that work for you?

@MikaelAmborn
Copy link
Author

MikaelAmborn commented Feb 9, 2021

Thank you for your reply, with some minor changes everyting seems to work:

def shadowRuntime = configurations.create("shadowRuntime") {
    extendsFrom(configurations.runtimeClasspath)
    attributes {
        attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage, Usage.JAVA_RUNTIME))
    }
}

tasks.shadowJar {
    configurations = [ shadowRuntime ]
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants