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

JlinkPlugin: properly handle external modules #1247

Closed
nigredo-tori opened this issue Jul 11, 2019 · 3 comments · Fixed by #1248
Closed

JlinkPlugin: properly handle external modules #1247

nigredo-tori opened this issue Jul 11, 2019 · 3 comments · Fixed by #1248
Labels

Comments

@nigredo-tori
Copy link
Collaborator

I finally got around to jlink'ing actual modular JARs (in a larger project), and the current implementation doesn't work.

  1. We build --module-path using commas as separators. Jlink documentation for Java 9 and 10 seems to suggest semicolons instead. Documentation for Java 11 and Java 12 doesn't mention that at all, but colons (!) seem to work.

  2. --module-path supports following items:

    • non-existent paths;
    • expanded class directories;
    • modular JARs.
    • non-modular JARs.

    For non-modular JARs jlink tries to generate an automatic module descriptor, using the JAR's file name. If that results in an illegal module name (like, say, cats.core.2.12), then it raises an error:

    Error: Unable to derive module descriptor for foo.jar

What happens now, is that comma-separated --module-path list is treated as a single path. Since such file doesn't exist, the effective --module-path is empty. This works when using the standard modules, but fails once we start adding external modular JARs. Also, this huge path can reach OS path size limits, resulting in an exception when running jlink.

A short-term fix for this looks like this:

  1. Fix the --module-path separator issue.
  2. Filter out non-modular JARs from --module-path list. One way to do this would be jar -d -f <file> (though that looks troublesome, since it also generates an automatic module descriptor).

However, I have reservations about adding more command-line utilities to the mix. I think we've come to the point where it makes more sense to leverage java.lang.module for things like that, even though that would mean that JlinkPlugin would require SBT to run in Java 9+.

Therefore, a better fix looks like this:

  1. Move JlinkPlugin to a separate artifact or project (sbt-native-packager-jlink), that would require Java 9 to build/use.
  2. Use java.lang.module to detect module JARs.
  3. Replace the jdeps package graph analysis with directly analyzing working with modules via java.lang.module.

This way we are isolated from changes in jdeps/jar output, and we have a lot of flexibility when dealing with modules. We lose jdeps package dependency check, but I'd argue it is not in the scope of this plugin.

@nigredo-tori
Copy link
Collaborator Author

nigredo-tori commented Jul 11, 2019

One more problem with jdeps - our implementation only finds modules that were referenced from some other jar/folder. Just adding a modular JAR is not enough. And I don't see a way to fix this without rewriting the whole thing. So that's one more reason to go for the long-term solution.

@nigredo-tori
Copy link
Collaborator Author

nigredo-tori commented Jul 11, 2019

Here's a less breaking way to manage the split:

  1. Factor out some JlinkHelper interface for all parts of the plugin that would require Java 9. Keep the API compatible with Java 8.
  2. Move it to some other project (and artifact - e.g. snp-jlink-api), build it with Java 8.
  3. Reimplement the logic in another artifact (snp-jlink), build it with Java 9. Include service binding.
  4. In sbt-native-packager depend on both these artifacts.
  5. In JlinkPlugin summon the implementation via ServiceLoader.

This way we get to keep all the tests in sbt-native-packager, and the users don't need to do anything.

@muuki88 muuki88 added the jlink label Jul 11, 2019
@nigredo-tori
Copy link
Collaborator Author

nigredo-tori commented Jul 11, 2019

Few more possibilities:

  1. Only link discovered standard modules, and leave --module-path empty. This looks like the easiest - and the most robust - short-term solution.
  2. The opposite: make sure all the JARs make it into the resulting image. We can find which JARs have names that won't produce legal module names with the specified algorithm. Then we can symlink or copy these to files with nicer names. This shouldn't be hard, but feels too specific.
    UPD: jdeps and jlink don't support automatic modules. So this is out.
    UPD2: for posterity: jdeps supports automatic JARs, but those have to be passed through --module-path (not through args).
  3. Same as above, but instead of renaming we generate a module descriptor ourselves, and add it to a copy of the JAR. This is very flexible, but it means copying/reinventing a chunk of the JDK implementation.

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

Successfully merging a pull request may close this issue.

2 participants