graalvm / native-build-tools

Native-image plugins for various build tools
https://graalvm.github.io/native-build-tools/
Other
363 stars 56 forks source link

Exclude dependencies from native-image #612

Open snicoll opened 1 month ago

snicoll commented 1 month ago

Is your feature request related to a problem? Please describe. Maven has a limited list of dependency scopes and it's not possible to create custom ones. As such, dependencies that are development only can end up on the classpath. This has caused issues in the past for things that need to build an executable based on a Maven project.

Spring Boot has had several issues such as https://github.com/spring-projects/spring-boot/issues/13289 and implemented several features to allow dependencies to be excluded:

The Maven plugin for native build tools urrently works in two modes: it builds the classpath itself, or it lets you define it. It would be nice if we could configure it to be able to exclude certain dependencies that shouldn't be analyzed by native-image in the first place.

Describe the solution you'd like A way to exclude a dependency. Either by providing the GAV in the plugin configuration or via a manifest entry. The latter looks less invasive and would work out of the box.

Describe alternatives you've considered None as building the classpath ourselves isn't really practical with Maven. The plugin is configured in our maven parent and used in a wide range of contexts.

mhalbritter commented 1 month ago

Our usecase, as described in https://github.com/spring-projects/spring-boot/issues/32853 is that we have the devtools on the runtime classpath. Native image automatically includes the whole runtime classpath in the native image (at least what's reachable).

On Spring Boot startup, the spring.factories contained in the devtools instructs the Spring Framework to enable devtools. However, devtools in a native image don't make much sense - these tools are for development only.

We worked around that by adding an if in the devtools startup and let it disable itself if it's running in a native image - however, the code is still contained in the native image.

To fix that problem at the root, it would be nice to do something like this:

<build>
  <plugins>
    <plugin>
      <groupId>org.graalvm.buildtools</groupId>
      <artifactId>native-maven-plugin</artifactId>
      <configuration>
        <excludeDependency> <!-- Or maybe just name the tag 'exclude'? -->
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-devtools</artifactId>
        </excludeDependency>
      </configuration>
    </plugin>
  </plugins>
</build>

and the native-maven-plugin would not include any classes from that jar in the native image.

mhalbritter commented 1 month ago

Another option would be, like Stephane suggested, to add a key to the manifest (e.g. Native-Image-Exclude: true) and the native image build tools then exclude that from the classpath.

This approach would be more flexible, as this doesn't need a hard-coded list of dependencies.

graemerocher commented 1 month ago

We hit similar issues I believe. Just out of interest.. does using provided scope not work?

mhalbritter commented 1 month ago

The devtools should be on the classpath when developing the application on the JVM but not when building the native image. Usually, this is in the pom.xml with devtools:

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-devtools</artifactId>
      <scope>runtime</scope>
      <optional>true</optional>
    </dependency>
alvarosanchez commented 1 week ago

The devtools should be on the classpath when developing the application on the JVM but not when building the native image

You can achieve that with provided scope.

mhalbritter commented 1 week ago

It should be on the runtime classpath when developing the application (it does stuff when you run the application on the JVM, like reloading classes), but it should not be on the classpath when compiling the native image. I don't think this is possible with the provided scope?

provided: This is much like compile, but indicates you expect the JDK or a container to provide the dependency at runtime.

Who would provide this dependency?

alvarosanchez commented 1 week ago

So provided is close to compileOnly in Gradle. Those dependencies are in the compilation classpath, but certainly not at runtime.

That said, if you have control over the classpath of the app running in dev mode, you could choose to include dependencies in provided scope. This is what we do in the Micronaut Maven Plugin, so effectively, provided becomes a sort of "development only" scope.

And this will also have the desired effect of not including such dependencies in the native image.

mhalbritter commented 1 week ago

if you have control over the classpath of the app running in dev mode

While this could be made to work with mvn spring-boot:run, I don't think this will be possible when running the application from the IDE, which a lot of users do. Or did you get this working in Micronaut?

alvarosanchez commented 1 week ago

Yes, in Intellij IDEA at least, provided dependencies are included in the runtime classpath.