See https://stackoverflow.com/questions/55124900/how-to-create-native-executable-using-quarkus#
In this case we should probably modify the existing manifest to add the Main-Class entry
Oh, that's the issue! But why did Eclipse try to create a MANIFEST.MF?
So, basically the logic would be to check for the existence of target/classes/META-INF/MANIFEST.MF and if there inject the classpath and main class.
(when I say inject, read replace if already set, with a warning)
How about replacing the old file with a fresh copy on each build?
I don't know why Eclipse IDE/m2e generates MANIFEST.MF files, but in any case, having some application builds that generate some MANIFEST.MF is not so unusual and this case should be embraced by Quarkus rather than rejected.
So indeed, the approach of merging MANIFEST.MF (and failing by default in case of conflicting directives in the MANIFEST.MF) seems to be the most sustainable and portable path.
From what I understand, it seems there various steps that are run :
AugmentPhase will ... augment various aspects ? (I don't know which ones)RunnerJarPhase will generate from the augmented (?) sources the output jar.AppCreationPhaseWe probably want a build item (optional) for the input manifest, and something for the output. The key is to ensure that we don't replace the manifest in-place, as cumulative changes would prevent build repeatability.
@dmlloyd I'm failing to see how that would prevent the IDE from generating its own MANIFEST.MF?
I think I would just update DevMojo and RunnerJarPhase to update a possibly existing MANIFEST with our entries (it's just the classpath and the main class). We could bail out (and either throw an error or log a warning) if they are already defined.
What would be interesting would be to have an example of this generated MANIFEST.MF so that we can see if it defines the classpath or main class.
@Riduidel would you like to give it a try?
@gsmet I would be very happy to attempt a solution as a PR. I will try to do that this week-end
Howevern strangely, quarkus dev mode works correctly, so it seems to me the dev mode is not impacted (but I would love to have a way to make sure dev mode is not impacted)
I'm quite confident now that I understand how RunnerJarPhase produces the manifest. Let me explain what I understand to see if I'm right.
So, in RunnerJarPhase#buildRunner(....) we have (in lines 330-350) that code
final Manifest manifest = new Manifest();
manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0");
manifest.getMainAttributes().put(Attributes.Name.CLASS_PATH, classPath.toString());
manifest.getMainAttributes().put(Attributes.Name.MAIN_CLASS, mainClass);
try (OutputStream os = Files.newOutputStream(runnerZipFs.getPath("META-INF", "MANIFEST.MF"))) {
manifest.write(os);
}
copyFiles(augmentOutcome.getAppClassesDir(), runnerZipFs);
copyFiles(augmentOutcome.getTransformedClassesDir(), runnerZipFs);
for (Map.Entry<String, List<byte[]>> entry : services.entrySet()) {
try (OutputStream os = Files.newOutputStream(runnerZipFs.getPath(entry.getKey()))) {
for (byte[] i : entry.getValue()) {
os.write(i);
os.write('\n');
}
}
}
What I understant is that quarkus first create its own manifest, then copy existing files. It explains how previously existing manifest will overwrite quarkus one (hence my bug). How to fix that ? I will move the manifest generation code in its own method, move call to that method after file copy and in that method add declarations to existing manifest instead of creating a new one.
Is it ok for you ?
@gsmet I took time to answer, but yes, I'll give it a try ;-)
We should be modifying the manifest via build items IMO. Maybe extensions should produce manifest entry items, which are then merged into the final product.
it could
Something like
copyFiles(augmentOutcome.getAppClassesDir(), runnerZipFs);
copyFiles(augmentOutcome.getTransformedClassesDir(), runnerZipFs);
for (Map.Entry<String, List<byte[]>> entry : services.entrySet()) {
try (OutputStream os = Files.newOutputStream(runnerZipFs.getPath(entry.getKey()))) {
for (byte[] i : entry.getValue()) {
os.write(i);
os.write('\n');
}
}
}
final Manifest manifest = new Manifest();
Path targetManifestPath = runnerZipFs.getPath("META-INF", "MANIFEST.MF"));
if (Files.exists(targetManifestPath)) {
try (InputStream stream = Files.newInputStream(targetManifestPath)) {
manifest.read(stream);
}
}
manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0");
manifest.getMainAttributes().put(Attributes.Name.CLASS_PATH, classPath.toString());
manifest.getMainAttributes().put(Attributes.Name.MAIN_CLASS, mainClass);
try (OutputStream os = Files.newOutputStream(targetManifestPath) {
manifest.write(os);
}
Well, I suggest you take a look at the pull request #1540 :-)
Most helpful comment
So, basically the logic would be to check for the existence of
target/classes/META-INF/MANIFEST.MFand if there inject the classpath and main class.