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

We do not handle existing MANIFEST.MF files properly #1443

Closed
stuartwdouglas opened this issue Mar 12, 2019 · 14 comments
Closed

We do not handle existing MANIFEST.MF files properly #1443

stuartwdouglas opened this issue Mar 12, 2019 · 14 comments
Labels
kind/bug Something isn't working
Milestone

Comments

@stuartwdouglas
Copy link
Member

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

@gsmet gsmet changed the title We do not handled existing MANIFEST.MF files properly We do not handle existing MANIFEST.MF files properly Mar 12, 2019
@cescoffier
Copy link
Member

Oh, that's the issue! But why did Eclipse try to create a MANIFEST.MF?

@cescoffier
Copy link
Member

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.

@cescoffier
Copy link
Member

(when I say inject, read replace if already set, with a warning)

@machi1990
Copy link
Member

How about replacing the old file with a fresh copy on each build?

@gsmet gsmet added the kind/bug Something isn't working label Mar 13, 2019
@mickaelistria
Copy link

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.

@Riduidel
Copy link
Contributor

From what I understand, it seems there various steps that are run :

  1. AugmentPhase will ... augment various aspects ? (I don't know which ones)
  2. RunnerJarPhase will generate from the augmented (?) sources the output jar.
    To my mind, the best way to cope with pre-existing MANIFEST.MF in target would be to add another implementation of AppCreationPhase
    What do you think of that (and particularly what @aloubyansky think about that) ?

@dmlloyd
Copy link
Member

dmlloyd commented Mar 14, 2019

We 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.

@gsmet
Copy link
Member

gsmet commented Mar 15, 2019

@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?

@Riduidel
Copy link
Contributor

@gsmet I would be very happy to attempt a solution as a PR. I will try to do that this week-end

@Riduidel
Copy link
Contributor

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)

@Riduidel
Copy link
Contributor

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 ;-)

@dmlloyd
Copy link
Member

dmlloyd commented Mar 17, 2019

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.

@mickaelistria
Copy link

it could

  1. copy the file
  2. read manifest if existing, or create new one
  3. alter manifest
  4. copy it

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);
        }

Riduidel added a commit to Riduidel/quarkus that referenced this issue Mar 17, 2019
…od, invoked after copying all files

The goal of this method is to make sure the entries we write in manfiest do not exist already or, if they exist, are compatible with quarkus requirements.
This goal is reached for MAIN_CLASS attribute, but not totally for CLASSPATH
@Riduidel
Copy link
Contributor

Well, I suggest you take a look at the pull request #1540 :-)

@gsmet gsmet closed this as completed in 983d992 Mar 19, 2019
gsmet added a commit that referenced this issue Mar 19, 2019
Fixes #1443 by moving manifest generation in a specific method
@gsmet gsmet added this to the 0.12.0 milestone Mar 19, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
kind/bug Something isn't working
Projects
None yet
Development

No branches or pull requests

7 participants