Exclude files from Application plugin lib/ folder

Vaadin 25 way is to include vaadin-dev dependency on classpath, but don’t package it into the production app. I’m using Gradle Application plugin to package Vaadin 25 and I’m trying to exclude the “vaadin-dev*.jar” from the Application zip file. No matter what I do, I can’t change the behavior and/or contents of the zip file - all runtimeClasspath jars are always placed into lib.

I know I should probably use multiple configurations, but it’s even harder to persuade Gradle Application plugin to use configuration other than runtimeClasspath, and I don’t want to actually do that: I want gradle run to run app including vaadin-dev, but the zip package of the production app should exclude vaadin-dev.

Thanks!

If it were only the vaadin-dev artifact, you could simply do

dependencies {
    runtimeOnly("com.vaadin:vaadin-dev:25.0.0")
}

distributions.main {
    contents {
        exclude("**/vaadin-dev-25.0.0.jar")
    }
}

But that will most probably not sufficiently work for you, as you would still get the transitive dependencies of vaadin-dev, even if they are not needed by any other dependency, which are 28 ones.

So yes, you should use an additional configuration like for example

val vaadinDevDependency = configurations.dependencyScope("vaadinDevDependency")
val vaadinDevRuntimeClasspath = configurations.resolvable("vaadinDevRuntimeClasspath") {
    extendsFrom(vaadinDevDependency.get())
    attributes {
        attribute(CATEGORY_ATTRIBUTE, objects.named(LIBRARY))
        attribute(USAGE_ATTRIBUTE, objects.named(JAVA_RUNTIME))
        attribute(LIBRARY_ELEMENTS_ATTRIBUTE, objects.named(JAR))
        attribute(BUNDLING_ATTRIBUTE, objects.named(EXTERNAL))
        attribute(TARGET_JVM_ENVIRONMENT_ATTRIBUTE, objects.named(STANDARD_JVM))
    }
}

dependencies {
    vaadinDevDependency("com.vaadin:vaadin-dev:25.0.0")
}

tasks.run.configure {
    classpath = objects.fileCollection().from(classpath, vaadinDevRuntimeClasspath)
}

Thank you so much for super-helpful tips! The first tip works (which is a bit strange because I would swear I tried the same thing yesterday and the jars were not excluded… could it be that the runtimeOnly() helped? ), but you’re right - it won’t exclude transitive deps which are simply too many to exclude manually, and that would be a fragile solution in this case.

The second solution is indeed cleaner and works really well for production build (dev artifacts are excluded) and for gradle run, but unfortunately when running the app via Intellij IDEA, the vaadin-dev artifacts aren’t placed on the classpath, causing Vaadin Dev mode to be disabled.

I learned that IDEA creates a temporary task of type JavaExec when running Java apps; unfortunately placing the following into build.gradle.kts doesn’t seem to help, probably because it’s only being used for existing tasks only):

tasks.withType<JavaExec> {
    classpath = objects.fileCollection().from(classpath, vaadinDevRuntimeClasspath)
}

Maybe the proper solution would be to add vaadin-dev on the runtimeClasspath, then create a configuration which extends runtimeClasspath but excludes vaadin-dev and all of its transitive deps, and use that to create Application bundle… however that could be fragile since the app itself may depend on some transitive dependency of vaadin-dev, and excluding that would cause the app to crash.

So far the cleanest solution I was able to come up with my limited Gradle understanding, is to use properties: -Pproduction combined with dependencies { if (!production) { implementation(libs.vaadin.dev) } }

could it be that the runtimeOnly() helped?

No.

it won’t exclude transitive deps which are simply too many to exclude manually, and that would be a fragile solution in this case.

I’s not only tedious, the fragility is also that some other library might need the same library and thus excluding it would break it.

I learned that IDEA creates a temporary task of type JavaExec when running Java apps;

There are again two things to consider.

  1. You can just run gradle run from IntelliJ and it will behave the same as from CLI, just do not go to the class and use the Gutter icon to run.
  2. If for some reason you have to use that Gutter icon to run the class directly, then whether IntelliJ creates a temporary Gradle task or not depends on your IDE settings. If you have Gradle delegation enabled, then yes. If you have Gradle delegation disabled, then it does not use Gradle at all to run the class.
    For this case, using the class-run and Gradle delegation enabled, you can indeed just enrich the task in your build script if you don’t mind having IDE-specifics in the build script.

unfortunately placing the following into build.gradle.kts doesn’t seem to help, probably because it’s only being used for existing tasks only):

No, that’s wrong.
It is executed also for that task, but it is overwritten again by IntelliJ, as its integration uses the bad-practice afterEvaluate to do its configuration. Due to that, you also have to use afterEvaluate to do your customization after IntelliJ did theirs.
The usual shenenigans when using that aweful construct.

Besides that, never use ´tasks.withType<…> { … }, or you break task-configuration avoidance for all those tasks and thus always configure all tasks of that type for each and every build, even if you are not going to run any of it. Always use tasks.withType<…>.configureEach { … }instead which will preserve task-configuration avoidance. So add theconfigureEach, and wrap it in a bad-practice afterEvaluate { … }` and it will work.

But maybe you should not enrich all tasks of type JavaExec or you also add these dependencies to a lot more tasks where it just pollutes classpaths or potentially breaks tasks. So what you most probably want is afterEvaluate { tasks.withType<JavaExec>().matching { it.name.endsWith(".main()") }.configureEach { ... } }.

So far the cleanest solution I was able to come up with my limited Gradle understanding, is to use properties: -Pproduction combined with dependencies { if (!production) { implementation(libs.vaadin.dev) } }

That’s of course also an option, yes.