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

Document how to handle MANIFEST.MF in native image with Maven #42412

Closed
krezovic opened this issue Sep 22, 2024 · 3 comments
Closed

Document how to handle MANIFEST.MF in native image with Maven #42412

krezovic opened this issue Sep 22, 2024 · 3 comments
Assignees
Labels
type: documentation A documentation update
Milestone

Comments

@krezovic
Copy link

When native image is built, there is no MANIFEST.MF for the application being built. Running "MyApp.class.getPackage().getImplementationVersion()" will return "null" in such scenario.

This is expected, as MANIFEST.MF is written by maven-jar-plugin, but native image build operates on target/classes dir itself.

How would one proceed with "get version of the currently running app" when running as native executable?

Consider the following example: https://github.com/krezovic/native-image-demo

@SpringBootApplication
public class DemoApplication {
	private static final String VERSION;

	static {
		VERSION = DemoApplication.class.getPackage().getImplementationVersion();
	}

	public static void main(String[] args) {
		log.info("Running version: {}", VERSION);
		SpringApplication.run(DemoApplication.class, args);
	}
}
./mvnw clean package -P native
./mvnw native:compile

will produce

$ ls -l target
total 104128
drwxr-xr-x 5 armin armin     4096 Sep 22 13:45 classes
-rwxr-xr-x 1 armin armin 85846072 Sep 22 13:46 demo
-rw-r--r-- 1 armin armin 20596296 Sep 22 13:45 demo-0.0.1-SNAPSHOT.jar
-rw-r--r-- 1 armin armin   137454 Sep 22 13:45 demo-0.0.1-SNAPSHOT.jar.original
drwxr-xr-x 3 armin armin     4096 Sep 22 13:45 generated-sources
drwxr-xr-x 3 armin armin     4096 Sep 22 13:45 generated-test-sources
drwxr-xr-x 3 armin armin     4096 Sep 22 13:45 graalvm-reachability-metadata
drwxr-xr-x 2 armin armin     4096 Sep 22 13:45 maven-archiver
drwxr-xr-x 3 armin armin     4096 Sep 22 13:45 maven-status
drwxr-xr-x 3 armin armin     4096 Sep 22 13:45 spring-aot
drwxr-xr-x 2 armin armin     4096 Sep 22 13:45 surefire-reports
drwxr-xr-x 3 armin armin     4096 Sep 22 13:45 test-classes
drwxr-xr-x 2 armin armin     4096 Sep 22 13:45 test-ids

Running

$ java -jar target/demo-0.0.1-SNAPSHOT.jar
13:47:41.896 [main] INFO com.example.demo.DemoApplication -- Running version: 0.0.1-SNAPSHOT

Running native executable, however

$ target/demo
13:48:04.685 [main] INFO com.example.demo.DemoApplication -- Running version: null

As expected, there's no MANIFEST.MF inside target/classes/META-INF

$ ls -l target/classes/META-INF
total 4
drwxr-xr-x 6 armin armin 4096 Sep 22 13:45 native-image

It is only present inside final JAR file

$ jar -tf target/demo-0.0.1-SNAPSHOT.jar | grep META-INF
META-INF/
META-INF/MANIFEST.MF
META-INF/services/
META-INF/services/java.nio.file.spi.FileSystemProvider
META-INF/native-image/
META-INF/native-image/ch.qos.logback/
META-INF/native-image/ch.qos.logback/logback-classic/
META-INF/native-image/ch.qos.logback/logback-classic/1.5.8/
META-INF/native-image/com.example/
META-INF/native-image/com.example/demo/
META-INF/native-image/com.fasterxml.jackson.core/
META-INF/native-image/com.fasterxml.jackson.core/jackson-databind/
META-INF/native-image/com.fasterxml.jackson.core/jackson-databind/2.17.2/
META-INF/native-image/org.apache.tomcat.embed/
META-INF/native-image/org.apache.tomcat.embed/tomcat-embed-core/
META-INF/native-image/org.apache.tomcat.embed/tomcat-embed-core/10.1.30/
META-INF/maven/
META-INF/maven/com.example/
META-INF/maven/com.example/demo/
META-INF/native-image/ch.qos.logback/logback-classic/1.5.8/reflect-config.json
META-INF/native-image/ch.qos.logback/logback-classic/1.5.8/resource-config.json
META-INF/native-image/com.example/demo/reflect-config.json
META-INF/native-image/com.example/demo/resource-config.json
META-INF/native-image/com.example/demo/native-image.properties
META-INF/native-image/com.fasterxml.jackson.core/jackson-databind/2.17.2/reflect-config.json
META-INF/native-image/org.apache.tomcat.embed/tomcat-embed-core/10.1.30/reflect-config.json
META-INF/native-image/org.apache.tomcat.embed/tomcat-embed-core/10.1.30/resource-config.json
META-INF/maven/com.example/demo/pom.xml
META-INF/maven/com.example/demo/pom.properties
@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged label Sep 22, 2024
@krezovic
Copy link
Author

More details ... The default classes directory is set to "target/classes" by native-maven-plugin

If I run it like this

./mvnw native:compile-no-fork -DclassesDirectory=target/demo-0.0.1-SNAPSHOT.jar.original

Then the app behaves as expected

$ target/demo 
14:06:19.307 [main] INFO com.example.demo.DemoApplication -- Running version: 0.0.1-SNAPSHOT

I wonder if this is something worthy of a documentation, or a pre-configuration to native-maven-plugin - since it comes from spring boot parent in this case.

native-image invocation before

Executing: /home/armin/.sdkman/candidates/java/current/bin/native-image -cp /home/armin/projects/native-image-demo/target/classes:/home/armin/.m2/repository/org/springframework/boot/spring-boot-starter-web/3.4.0-M3/spring-boot-starter-web-3.4.0-M3.jar...

native-image invocation after

[INFO] Executing: /home/armin/.sdkman/candidates/java/current/bin/native-image -cp /home/armin/projects/native-image-demo/target/demo-0.0.1-SNAPSHOT.jar.original:/home/armin/.m2/repository/org/springframework/boot/spring-boot-starter-web/3.4.0-M3/spring-boot-starter-web-3.4.0-M3.jar...

Using JAR without .original suffix (spring boot fatjar) obviously fails

[INFO] Executing: /home/armin/.sdkman/candidates/java/current/bin/native-image -cp /home/armin/projects/native-image-demo/target/demo-0.0.1-SNAPSHOT.jar:/home/armin/.m2/repository/org/springframework/boot/spring-boot-starter-web/3.4.0-M3/spring-boot-starter-web-3.4.0-M3.jar:...
...
Error: Main entry point class 'com.example.demo.DemoApplication' neither found on 
classpath: '/home/armin/projects/native-image-demo/target/demo-0.0.1-SNAPSHOT.jar:/home/armin/.m2/repository/org/springframework/boot/spring-boot-starter-web/3.4.0-M3/spring-boot-starter-web-3.4.0-M3.jar:... nor modulepath: '/home/armin/.sdkman/candidates/java/23.1.4.r21-mandrel/lib/svm/library-support.jar'.

@snicoll
Copy link
Member

snicoll commented Sep 30, 2024

@krezovic thanks for the report.

Using JAR without .original suffix (spring boot fatjar) obviously fails

Yes. And that is why our parent configures the native image maven plugin to use the classesDirectory option. It would be better if we didn't have to do this but we can't know upfront if the build is configured to create a repackaged archive or not, and if a classifier is set. We can't configure it to hardcode .original as it may or may not be accurate based on how the app is configured.

Our best course of action is some documentation here.

@snicoll snicoll added type: documentation A documentation update and removed status: waiting-for-triage An issue we've not yet triaged labels Sep 30, 2024
@snicoll snicoll added this to the 3.2.x milestone Sep 30, 2024
@snicoll snicoll changed the title Unable to read application class versions in native image Document how to handle MANIFEST.MF in native image with Maven Sep 30, 2024
@snicoll snicoll self-assigned this Sep 30, 2024
@snicoll snicoll modified the milestones: 3.2.x, 3.2.11 Sep 30, 2024
@ZIRAKrezovic
Copy link

Leaving as a reference

Another hint is required if you dynamically use manifest via ResourceLoader

static class ApplicationRuntimeHints implements RuntimeHintsRegistrar {
    @Override
    public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
        hints.resources().registerPattern("META-INF/MANIFEST.MF");
    }
}

Example code that works after adding the hint, but not before

        var resource =
                new ClassPathResource(
                        "META-INF/MANIFEST.MF", this.getClass().getClassLoader());

        log.info("Manifest resource: " + resource);
        log.info("Manifest class loader: " + this.getClass().getClassLoader());
        log.info("Manifest exists: " + resource.exists());

        Manifest manifest = null;

        if (resource.exists()) {
            try {
                manifest = new Manifest(resource.getInputStream());
                log.info(
                        "Manifest attributes: "
                                + manifest.getMainAttributes().entrySet().stream()
                                        .map(
                                                kv ->
                                                        String.join(
                                                                ": ",
                                                                kv.getKey().toString(),
                                                                kv.getValue().toString()))
                                        .collect(Collectors.joining("\n")));
            } catch (IOException e) {
                log.warn("Could not read manifest", e);
            }
        }

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

No branches or pull requests

4 participants