On Fri, 25 Oct 2024 16:29:52 GMT, Severin Gehwolf <sgehw...@openjdk.org> wrote:

>> Please review this patch which adds a jlink mode to the JDK which doesn't 
>> need the packaged modules being present. A.k.a run-time image based jlink. 
>> Fundamentally this patch adds an option to use `jlink` even though your JDK 
>> install might not come with the packaged modules (directory `jmods`). This 
>> is particularly useful to further reduce the size of a jlinked runtime. 
>> After the removal of the concept of a JRE, a common distribution mechanism 
>> is still the full JDK with all modules and packaged modules. However, 
>> packaged modules can incur an additional size tax. For example in a 
>> container scenario it could be useful to have a base JDK container including 
>> all modules, but without also delivering the packaged modules. This comes at 
>> a size advantage of `~25%`. Such a base JDK container could then be used to 
>> `jlink` application specific runtimes, further reducing the size of the 
>> application runtime image (App + JDK runtime; as a single image *or* 
>> separate bundles, depending on the app 
 being modularized).
>> 
>> The basic design of this approach is to add a jlink plugin for tracking 
>> non-class and non-resource files of a JDK install. I.e. files which aren't 
>> present in the jimage (`lib/modules`). This enables producing a `JRTArchive` 
>> class which has all the info of what constitutes the final jlinked runtime.
>> 
>> Basic usage example:
>> 
>> $ diff -u <(./bin/java --list-modules --limit-modules java.se) 
>> <(../linux-x86_64-server-release/images/jdk/bin/java --list-modules 
>> --limit-modules java.se)
>> $ diff -u <(./bin/java --list-modules --limit-modules jdk.jlink) 
>> <(../linux-x86_64-server-release/images/jdk/bin/java --list-modules 
>> --limit-modules jdk.jlink)
>> $ ls ../linux-x86_64-server-release/images/jdk/jmods
>> java.base.jmod            java.net.http.jmod       java.sql.rowset.jmod      
>> jdk.crypto.ec.jmod         jdk.internal.opt.jmod                     
>> jdk.jdi.jmod         jdk.management.agent.jmod  jdk.security.auth.jmod
>> java.compiler.jmod        java.prefs.jmod          java.transaction.xa.jmod  
>> jdk.dynalink.jmod          jdk.internal.vm.ci.jmod                   
>> jdk.jdwp.agent.jmod  jdk.management.jfr.jmod    jdk.security.jgss.jmod
>> java.datatransfer.jmod    java.rmi.jmod            java.xml.crypto.jmod      
>> jdk.editpad.jmod           jdk.internal.vm.compiler.jmod             
>> jdk.jfr.jmod         jdk.management.jmod        jdk.unsupported.desktop.jmod
>> java.desktop.jmod         java.scripting.jmod      java.xml.jmod             
>> jdk.hotspot.agent.jmod     jdk.i...
>
> Severin Gehwolf has updated the pull request incrementally with five 
> additional commits since the last revision:
> 
>  - Better handle patched modules
>    
>    Also add a test which ensures that module patching (if present), will
>    get an appropriate error message.
>  - Add /othervm to some langtools tier1 tests
>    
>    Those tests are using module patches from JTREG. Since the run-time
>    image based link uses ModuleFinder.ofSystem(), which will see the extra
>    classes comming from the module patch. Then the size look-up using the
>    JRT FS provider fails, since that only looks in the module image
>    (lib/modules) and NOT also in the patch. Thus, we get a
>    NoSuchFileException and the link fails.
>    
>    Run the tests with othervm so that the JTREG patch'ed module isn't
>    visible to the test.
>  - Fix tests for builds with --enable-linable-runtime
>    
>    Those builds don't include the packaged modules, `jmods` directory.
>    However, some tests assume that they're there. Add appropriate requires
>    tag.
>  - Fix provider verification when some JMODs are present
>    
>    In some configurations, e.g. when java.base is missing from the packaged
>    modules, but another JDK module is present as JMOD that is linked into
>    an image, then provider verification can fail. Thus, the run-time image
>    link fails. Verify that this doesn't happen.
>    
>    The fix is to return Platform.runtime() for run-time image based links
>    as well. Otherwise this code would return the wrong result.
>  - Show run-time image based capability in help
>    
>    Also add a test for it when it's turned on and off.

I did a pass of the current implementation (not the tests).  Looking quite 
good.  

I see you add the last line in the help message.


Capabilities: +run-time-image

This needs some discussion/consideration how that information be conveyed.

src/jdk.jlink/share/classes/jdk/tools/jlink/internal/ImageFileCreator.java line 
111:

> 109:      * @throws IOException
> 110:      */
> 111:     public static ExecutableImage create(Set<Archive> archives,

I think these `create(Set<Archive>, ImagePluginStack)` and 
`create(Set<Archive>, ByteOrder)` methods are unused.   Can just remove them.

src/jdk.jlink/share/classes/jdk/tools/jlink/internal/JRTArchive.java line 104:

> 102:         this.errorOnModifiedFile = errorOnModifiedFile;
> 103:         this.otherRes = readModuleResourceFile(module);
> 104:         this.resDiff = 
> prepareDiffMap(Objects.requireNonNull(perModDiff));

Suggestion:

                this.resDiff = Objects.requireNonNull(perModDiff).stream()
                            .collect(Collectors.toMap(ResourceDiff::getName, 
Function.identity()));

src/jdk.jlink/share/classes/jdk/tools/jlink/internal/JRTArchive.java line 183:

> 181:                                                        null /* 
> hashOrTarget */,
> 182:                                                        false /* symlink 
> */,
> 183:                                                        
> resDiff.get(lookupKey));

Nit: identation within `{...}` better to use consistent spacing (4-space or 
8-space is fine).

src/jdk.jlink/share/classes/jdk/tools/jlink/internal/JRTArchive.java line 197:

> 195:                                                  String 
> pathWithoutModule = s.getName()
> 196:                                                             
> .substring(secondSlash + 1,
> 197:                                                                        
> s.getName().length());

Suggestion:

                                                 String pathWithoutModule = 
s.getName().substring(secondSlash + 1);

src/jdk.jlink/share/classes/jdk/tools/jlink/internal/JRTArchive.java line 205:

> 203:                                                          s);
> 204:                                          })
> 205:                                          .collect(Collectors.toList()));

Suggestion:

                                         .toList());


Can replace `.collect(Collectors.toList())` with `Stream::toList` in other 
occurrances as well.

src/jdk.jlink/share/classes/jdk/tools/jlink/internal/JRTArchive.java line 326:

> 324:             try {
> 325:                 Integer typeInt = Integer.valueOf(tokens[0]);
> 326:                 type = Type.fromOrdinal(typeInt);

Since encoding and decoding are private to this class, this class can keep a 
map from an ordinal to `Type` and no need to add the static `Type::fromOrdinal` 
method.


        private static final Map<Integer, Type> typeMap = 
Arrays.stream(Type.values())
                .collect(Collectors.toMap(Type::ordinal, Function.identity()));

                
Suggestion:

                type = typeMap.get(typeInt);

src/jdk.jlink/share/classes/jdk/tools/jlink/internal/JRTArchive.java line 526:

> 524:             }
> 525:         } catch (IOException e) {
> 526:             throw new InternalError("Failed to process run-time image 
> resources " +

For unexpected exceptions, some code throws InternalError and some throws 
AssertionError - better to be consistent and I think implementation typically 
throws InternalError.     For IOException, I think `UncheckedIOException` is 
appropriate.

src/jdk.jlink/share/classes/jdk/tools/jlink/internal/JlinkTask.java line 246:

> 244:     public static final String RESPATH_PATTERN = 
> "jdk/tools/jlink/internal/runtimelink/fs_%s_files";
> 245:     // The diff files per module for linkable JDK runtimes
> 246:     public static final String DIFF_PATTERN = 
> "jdk/tools/jlink/internal/runtimelink/diff_%s";

I think it'd be useful to have a `LinkableRuntimeImage` class that declares 
these constants and some other utility methods such as detecting if this is a 
linkable runtime image (e.g. `hasRuntimeImageLinkCap()`)

src/jdk.jlink/share/classes/jdk/tools/jlink/internal/JlinkTask.java line 414:

> 412:         }
> 413:         // Setup and init actions for JDK linkable runtimes
> 414:         LinkableRuntimesResult result = linkableJDKRuntimesInit(finder, 
> roots);

I think it's clearer to the readers if inlining the code of 
`linkablejDKRuntimesInit` here.  Also the `--keep-package-modules` check can be 
moved after `isLinkFromRuntime` is determined so checking `isLinkFromRuntime` 
is more explicit.

Suggestion:

        boolean isLinkFromRuntime = options.modulePath.isEmpty();
        // In case of custom modules outside the JDK we may
        // have a non-empty module path, which must not include
        // java.base. If it did, we link using packaged modules from that
        // module path. If the module path does not include java.base, we must
        // have a linkable JDK runtime. In that case we take the JDK modules
        // from the run-time image.
        if (finder.find("java.base").isEmpty()) {
            isLinkFromRuntime = true;
            ModuleFinder runtimeImageFinder = ModuleFinder.ofSystem();
            finder = combinedFinders(runtimeImageFinder, finder, 
options.limitMods, roots);
        }

        // --keep-packaged-modules doesn't make sense as we are not linking
        // from packaged modules to begin with.
        if (isLinkFromRuntime && options.packagedModulesPath != null) {
            throw taskHelper.newBadArgs("err.runtime.link.packaged.mods");
        }

src/jdk.jlink/share/classes/jdk/tools/jlink/internal/JlinkTask.java line 480:

> 478:      */
> 479:     private ModuleFinder combinedFinders(ModuleFinder finder,
> 480:             ModuleFinder runtimeImageFinder, Set<String> limitMods,

suggest to make `runtimeImageFinder` as the first parameter and `finder` second 
to match the order of the lookup.

src/jdk.jlink/share/classes/jdk/tools/jlink/internal/JlinkTask.java line 486:

> 484:             @Override
> 485:             public Optional<ModuleReference> find(String name) {
> 486:                 Optional<ModuleReference> basic = 
> runtimeImageFinder.find(name);

Suggestion:

                Optional<ModuleReference> mref = runtimeImageFinder.find(name);

src/jdk.jlink/share/classes/jdk/tools/jlink/internal/JlinkTask.java line 559:

> 557:         Runtime.Version version = Runtime.version();
> 558:         Path[] entries = paths.toArray(new Path[0]);
> 559:         ModuleFinder finder = paths.isEmpty() ? ModuleFinder.ofSystem()

The javadoc needs update to something like:


    * Returns a module finder of the given module path or system modules
    * if the module path is empty that limits the observable modules to ...

src/jdk.jlink/share/classes/jdk/tools/jlink/internal/JlinkTask.java line 741:

> 739:         // we don't have the per module resource diffs in the modules 
> image
> 740:         try (InputStream inStream = getDiffInputStream()) {
> 741:             if (inStream == null) {

Suggest to define a method `isLinkableRuntime` in the new 
`LinkableRuntimeImage` class to test if it's a linkable runtime image.

src/jdk.jlink/share/classes/jdk/tools/jlink/internal/JlinkTask.java line 760:

> 758:     }
> 759: 
> 760:     private static boolean hasRuntimeImageLinkCap() {

Move to the new `LinkableRuntimeImage` class

src/jdk.jlink/share/classes/jdk/tools/jlink/internal/JlinkTask.java line 825:

> 823:             }
> 824:         } else if (config.linkFromRuntimeImage()) {
> 825:             // This is after all other archive types, since user-provided

This block can be a utility method in the new `LinkableRuntimeImage` class as 
the logic can be encapsulated.

src/jdk.jlink/share/classes/jdk/tools/jlink/internal/runtimelink/ImageReader.java
 line 39:

> 37:     }
> 38: 
> 39:     public static boolean isNotTreeInfoResource(String path) {

Do we need this?  Can use `ImageResourcesTree::isTreeInfoResource` instead

src/jdk.jlink/share/classes/jdk/tools/jlink/internal/runtimelink/ImageReader.java
 line 48:

> 46:                 .filter(ImageReader::isNotTreeInfoResource)
> 47:                 .sorted()
> 48:                 .collect(Collectors.toList());

Suggestion:

        return Arrays.stream(getEntryNames())
                .filter(Predicate.not(ImageResourcesTree::isTreeInfoResource))
                .sorted()
                .toList();

src/jdk.jlink/share/classes/jdk/tools/jlink/internal/runtimelink/JmodsReader.java
 line 41:

> 39: 
> 40: @SuppressWarnings("try")
> 41: public class JmodsReader implements JimageDiffGenerator.ImageResource {

Is JmodsReader leftover from previous implementation?   It can be removed?

src/jdk.jlink/share/classes/jdk/tools/jlink/internal/runtimelink/ResourceDiff.java
 line 272:

> 270: 
> 271:     public static void printDiffs(List<ResourceDiff> diffs) {
> 272:         for (ResourceDiff diff: 
> diffs.stream().sorted().collect(Collectors.toList())) {

Suggestion:

        for (ResourceDiff diff: diffs.stream().sorted().toList()) {

src/jdk.jlink/share/classes/jdk/tools/jlink/internal/runtimelink/RuntimeImageLinkException.java
 line 29:

> 27: 
> 28: /**
> 29:  * Exception thrown for links without packaged modules. I.e. run-image 
> link.

I found inconsistent terminologies "run-image" "run-time" "JDK run-time".   
Better to use consistent terminologies.

For this one, maybe as simple as this:

Suggestion:

 *  Exception thrown for linking without packaged modules

src/jdk.jlink/share/classes/jdk/tools/jlink/internal/runtimelink/RuntimeImageLinkException.java
 line 38:

> 36:     private final IllegalArgumentException iae;
> 37: 
> 38:     public RuntimeImageLinkException(IllegalArgumentException cause) {

Can you explain why `RuntimeImageLinkException` constructor takes IAE.   The 
places throwing RILE has to create IAE.  I can't see how IAE is necessary as 
other places would catch RILE and throw IAE or IOException instead.  

Can it simply take an exception message parameter?   And possibly change 
`JlinkTask::run` in handling RILE like IAE?


        } catch (IllegalArgumentException | ResolutionException | 
RuntimeImageLinkException e) {
            log.println(taskHelper.getMessage("error.prefix") + " " + 
e.getMessage());
            if (DEBUG) {
                e.printStackTrace(log);
            }
            return EXIT_ERROR;
 ```

src/jdk.jlink/share/classes/jdk/tools/jlink/plugin/ResourcePoolEntry.java line 
73:

> 71:         TOP;
> 72: 
> 73:         public static Type fromOrdinal(int value) {

Can be dropped if `JRTArchive.ResourceFileEntry` keeps a map from ordinal to 
Type instances.

src/jdk.jlink/share/classes/jdk/tools/jlink/resources/jlink.properties line 119:

> 117: warn.prefix=Warning:
> 118: 
> 119: err.runtime.link.not.linkable.runtime=The current run-time image does 
> not support run-time linking.

should we use "runtime" instead of "run-time" in the help message and 
errors/warnings?   "runtime" is already used in the messages in this file.

-------------

PR Review: https://git.openjdk.org/jdk/pull/14787#pullrequestreview-2396177744
PR Review Comment: https://git.openjdk.org/jdk/pull/14787#discussion_r1817204284
PR Review Comment: https://git.openjdk.org/jdk/pull/14787#discussion_r1817236999
PR Review Comment: https://git.openjdk.org/jdk/pull/14787#discussion_r1817238678
PR Review Comment: https://git.openjdk.org/jdk/pull/14787#discussion_r1817241483
PR Review Comment: https://git.openjdk.org/jdk/pull/14787#discussion_r1817240162
PR Review Comment: https://git.openjdk.org/jdk/pull/14787#discussion_r1817246303
PR Review Comment: https://git.openjdk.org/jdk/pull/14787#discussion_r1817251038
PR Review Comment: https://git.openjdk.org/jdk/pull/14787#discussion_r1817219627
PR Review Comment: https://git.openjdk.org/jdk/pull/14787#discussion_r1817225268
PR Review Comment: https://git.openjdk.org/jdk/pull/14787#discussion_r1817226390
PR Review Comment: https://git.openjdk.org/jdk/pull/14787#discussion_r1817229066
PR Review Comment: https://git.openjdk.org/jdk/pull/14787#discussion_r1817228237
PR Review Comment: https://git.openjdk.org/jdk/pull/14787#discussion_r1817230995
PR Review Comment: https://git.openjdk.org/jdk/pull/14787#discussion_r1817231628
PR Review Comment: https://git.openjdk.org/jdk/pull/14787#discussion_r1817233166
PR Review Comment: https://git.openjdk.org/jdk/pull/14787#discussion_r1817253259
PR Review Comment: https://git.openjdk.org/jdk/pull/14787#discussion_r1817253657
PR Review Comment: https://git.openjdk.org/jdk/pull/14787#discussion_r1817205476
PR Review Comment: https://git.openjdk.org/jdk/pull/14787#discussion_r1817261753
PR Review Comment: https://git.openjdk.org/jdk/pull/14787#discussion_r1817208860
PR Review Comment: https://git.openjdk.org/jdk/pull/14787#discussion_r1817216753
PR Review Comment: https://git.openjdk.org/jdk/pull/14787#discussion_r1817265479
PR Review Comment: https://git.openjdk.org/jdk/pull/14787#discussion_r1817269156

Reply via email to