native-build-tools icon indicating copy to clipboard operation
native-build-tools copied to clipboard

Add `<excludeself>` configuration to skip adding the main build path to the classpath

Open cstancu opened this issue 5 months ago • 5 comments

While https://github.com/graalvm/native-build-tools/pull/684 introduced the possibility to exclude artifacts from native-image compilation via the <exclusions> section in <configuration> this doesn't apply to the jar produced for the configured project itself. Thus, every native-image build command contains the main jar in its classpath. For projects like Native Image Layers/Project Crema it would be useful to be able to exclude the main jar from the class path too. Projects needing this feature usually have an additional profile that adds a custom configuration for the Native Image build of the base layer artifact. Doing it in the same project is convenient because you automatically get the 3rd party dependencies on the build class path of the base layer. However, the application specific classes and resources should be excluded from the base layer because it should be application agnostic, and only included for the application layer artifact. In our current Micronaut Layered Native Image Demo we go to great lengths to do this. First, the base-layer profile needs to exclude the app layer artifacts to ensure they are not included in the base image:

<profile>
   <id>base-layer</id>
       [...] 
       <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <configuration>
              <excludes>
                <exclude>**/example/micronaut/**</exclude>
              </excludes>
              <testExcludes>
                <exclude>**/example/micronaut/**</exclude>
              </testExcludes>
            </configuration>
          </plugin>
        [...] 
</profile>

However, the generated jar, even though it's mostly empty is still added to the classpath, e.g.,

[INFO] Executing: [...]/graalvm-jdk-25e1-25.0.0-ea.01_linux-x64_bin/graalvm-jdk-25+37.1/bin/native-image -cp [...]/graalvm-demos/native-image/microservices/micronaut-hello-rest-maven-layered/app-layer-target/micronaut-hello-rest-maven-layered-0.1.jar:...

Moreover, since the app-layer classpath needs to be a strict superset of the base-layer classpath, verification introduced by https://github.com/oracle/graal/pull/12264, we need to install this jar and then add it as a dependency in the app-layer config:

<profile>
  <id>base-layer</id>
        [...] 
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-install-plugin</artifactId>
            <executions>
              <execution>
                <phase>install</phase>
                <goals>
                  <goal>install-file</goal>
                </goals>
                <configuration>
                  <groupId>${project.groupId}</groupId>
                  <file>${project.build.directory}/${project.artifactId}-${project.version}.jar</file>
                  <artifactId>micronaut-base-layer</artifactId>
                  <packaging>jar</packaging>
                </configuration>
              </execution>
            </executions>
          </plugin>
</profile>
 <profile>
      <id>app-layer</id>
      <dependencies>
        <dependency>
          <groupId>${project.groupId}</groupId>
          <artifactId>micronaut-base-layer</artifactId>
          <version>${project.version}</version>
        </dependency>
      </dependencies>
         [...]
  </profile>

I think this could be simplified by adding an additional flag to the <configuration> section of org.graalvm.buildtools plugin, tentatively called <excludeself>true</excludeself>, which would be false by default. Then AbstractNativeImageMojo.populateApplicationClasspath would conditionally skip adding the main build path imageClasspath.add(getMainBuildPath());. This would allow us to eliminate all the extra configuration that I mentioned above.

cstancu avatar Oct 20 '25 14:10 cstancu

For a truly multi-layer project, meaning multiple projects will share the same list, wouldn't it be easier to simply specify a list of artifacts that you want in the layer? This can be done by inheritance in maven, for example.

The <excludeself>true</excludeself> seems like you are making a layer for one project which does not make much sense except if it should improve compilation time. But in that case we would have a special flag for that like --incremental or --dev which would do excludeself.

vjovanov avatar Oct 20 '25 16:10 vjovanov

Sure, using inheritance in Maven would be a good solution to structure multiple projects that depend on the same base layer config. But, we'd still have the problem that I described above: when you build the base layer the NBT adds the main build path to the classpath. We still need the <excludeself>true</excludeself> solution.

cstancu avatar Oct 21 '25 11:10 cstancu

OK, so the issue comes from: the app-layer classpath needs to be a strict superset of the base-layer classpath, verification introduced by oracle/graal#12264

Is there a strong reason for such a constraint? Is this a documented constraint that will stay forever? I am asking because I would not like new features in NBT for something that is not permanent.

If this is a documented constraint that we will keep, have you tried to use 'pom' packaging projects that don't include sources into the build?

vjovanov avatar Oct 24 '25 16:10 vjovanov

Yes, it's documented in the design doc under compatibility rules and it's not temporary https://github.com/oracle/graal/blob/master/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/imagelayer/NativeImageLayers.md#compatibility-rules. I tried playing with the packaging plugin, but the target dir is always added to the -cp of native-image, even if you disable target jar creation. Judging by the NBT source at https://github.com/graalvm/native-build-tools/blob/master/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/AbstractNativeImageMojo.java#L425 it always adds the main build path to the cp, so I don't think this can be achieved via pom manipulation only.

cstancu avatar Oct 25 '25 16:10 cstancu

I will evaluate the possibility of using the pom packaging for this. This is reported in: https://github.com/graalvm/native-build-tools/issues/727

vjovanov avatar Nov 26 '25 15:11 vjovanov