8311302: Implement JEP 493: Linking Run-Time Images without JMODs

Co-authored-by: Mandy Chung <mchung@openjdk.org>
Reviewed-by: mchung, alanb, erikj, ihse
This commit is contained in:
Severin Gehwolf 2024-11-11 13:35:25 +00:00
parent f3ba767604
commit 2ec358082f
47 changed files with 4170 additions and 251 deletions

View File

@ -96,6 +96,10 @@ JLINK_DISABLE_WARNINGS := | ( $(GREP) -v -e "WARNING: Using incubator module" ||
JDK_IMAGE_SUPPORT_DIR := $(SUPPORT_OUTPUTDIR)/images/jdk JDK_IMAGE_SUPPORT_DIR := $(SUPPORT_OUTPUTDIR)/images/jdk
JRE_IMAGE_SUPPORT_DIR := $(SUPPORT_OUTPUTDIR)/images/jre JRE_IMAGE_SUPPORT_DIR := $(SUPPORT_OUTPUTDIR)/images/jre
ifeq ($(JLINK_PRODUCE_LINKABLE_RUNTIME), true)
JLINK_JDK_EXTRA_OPTS += --generate-linkable-runtime
endif
$(eval $(call SetupExecute, jlink_jdk, \ $(eval $(call SetupExecute, jlink_jdk, \
WARN := Creating jdk image, \ WARN := Creating jdk image, \
DEPS := $(JDK_JMODS) $(BASE_RELEASE_FILE) \ DEPS := $(JDK_JMODS) $(BASE_RELEASE_FILE) \

View File

@ -586,13 +586,42 @@ AC_DEFUN_ONCE([JDKOPT_SETUP_JMOD_OPTIONS],
################################################################################ ################################################################################
# #
# jlink options. # jlink options.
# We always keep packaged modules in JDK image.
# #
AC_DEFUN_ONCE([JDKOPT_SETUP_JLINK_OPTIONS], AC_DEFUN_ONCE([JDKOPT_SETUP_JLINK_OPTIONS],
[ [
UTIL_ARG_ENABLE(NAME: keep-packaged-modules, DEFAULT: true,
################################################################################
#
# Configure option for building a JDK that is suitable for linking from the
# run-time image without JMODs.
#
# Determines whether or not a suitable run-time image is being produced from
# packaged modules. If set to 'true, changes the *default* of packaged
# modules to 'false'.
#
UTIL_ARG_ENABLE(NAME: linkable-runtime, DEFAULT: false,
RESULT: JLINK_PRODUCE_LINKABLE_RUNTIME,
DESC: [enable a JDK build suitable for linking from the run-time image],
CHECKING_MSG: [whether or not a JDK suitable for linking from the run-time image should be produced])
AC_SUBST(JLINK_PRODUCE_LINKABLE_RUNTIME)
if test "x$JLINK_PRODUCE_LINKABLE_RUNTIME" = xtrue; then
DEFAULT_PACKAGED_MODULES=false
else
DEFAULT_PACKAGED_MODULES=true
fi
################################################################################
#
# Configure option for packaged modules
#
# We keep packaged modules in the JDK image unless --enable-linkable-runtime is
# requested.
#
UTIL_ARG_ENABLE(NAME: keep-packaged-modules, DEFAULT: $DEFAULT_PACKAGED_MODULES,
RESULT: JLINK_KEEP_PACKAGED_MODULES, RESULT: JLINK_KEEP_PACKAGED_MODULES,
DESC: [enable keeping of packaged modules in jdk image], DESC: [enable keeping of packaged modules in jdk image],
DEFAULT_DESC: [enabled by default unless --enable-linkable-runtime is set],
CHECKING_MSG: [if packaged modules are kept]) CHECKING_MSG: [if packaged modules are kept])
AC_SUBST(JLINK_KEEP_PACKAGED_MODULES) AC_SUBST(JLINK_KEEP_PACKAGED_MODULES)
]) ])

View File

@ -706,6 +706,7 @@ NEW_JAVADOC = $(INTERIM_LANGTOOLS_ARGS) $(JAVADOC_MAIN_CLASS)
JMOD_COMPRESS := @JMOD_COMPRESS@ JMOD_COMPRESS := @JMOD_COMPRESS@
JLINK_KEEP_PACKAGED_MODULES := @JLINK_KEEP_PACKAGED_MODULES@ JLINK_KEEP_PACKAGED_MODULES := @JLINK_KEEP_PACKAGED_MODULES@
JLINK_PRODUCE_LINKABLE_RUNTIME := @JLINK_PRODUCE_LINKABLE_RUNTIME@
RCFLAGS := @RCFLAGS@ RCFLAGS := @RCFLAGS@

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2014, 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -24,29 +24,44 @@
*/ */
package jdk.tools.jlink.internal; package jdk.tools.jlink.internal;
import static jdk.tools.jlink.internal.LinkableRuntimeImage.DIFF_PATTERN;
import static jdk.tools.jlink.internal.LinkableRuntimeImage.RESPATH_PATTERN;
import java.io.BufferedOutputStream; import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream; import java.io.DataOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
import java.nio.ByteOrder; import java.nio.ByteOrder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
import jdk.tools.jlink.internal.Archive.Entry; import jdk.tools.jlink.internal.Archive.Entry;
import jdk.tools.jlink.internal.Archive.Entry.EntryType; import jdk.tools.jlink.internal.Archive.Entry.EntryType;
import jdk.tools.jlink.internal.JRTArchive.ResourceFileEntry;
import jdk.tools.jlink.internal.ResourcePoolManager.CompressedModuleData; import jdk.tools.jlink.internal.ResourcePoolManager.CompressedModuleData;
import jdk.tools.jlink.internal.runtimelink.JimageDiffGenerator;
import jdk.tools.jlink.internal.runtimelink.JimageDiffGenerator.ImageResource;
import jdk.tools.jlink.internal.runtimelink.ResourceDiff;
import jdk.tools.jlink.internal.runtimelink.ResourcePoolReader;
import jdk.tools.jlink.internal.runtimelink.RuntimeImageLinkException;
import jdk.tools.jlink.plugin.PluginException; import jdk.tools.jlink.plugin.PluginException;
import jdk.tools.jlink.plugin.ResourcePool; import jdk.tools.jlink.plugin.ResourcePool;
import jdk.tools.jlink.plugin.ResourcePoolBuilder;
import jdk.tools.jlink.plugin.ResourcePoolEntry; import jdk.tools.jlink.plugin.ResourcePoolEntry;
import jdk.tools.jlink.plugin.ResourcePoolModule;
/** /**
* An image (native endian.) * An image (native endian.)
@ -68,38 +83,61 @@ import jdk.tools.jlink.plugin.ResourcePoolEntry;
* }</pre> * }</pre>
*/ */
public final class ImageFileCreator { public final class ImageFileCreator {
private static final byte[] EMPTY_RESOURCE_BYTES = new byte[] {};
private static final String JLINK_MOD_NAME = "jdk.jlink";
private static final String RESPATH = "/" + JLINK_MOD_NAME + "/" + RESPATH_PATTERN;
private static final String DIFF_PATH = "/" + JLINK_MOD_NAME + "/" + DIFF_PATTERN;
private final Map<String, List<Entry>> entriesForModule = new HashMap<>(); private final Map<String, List<Entry>> entriesForModule = new HashMap<>();
private final ImagePluginStack plugins; private final ImagePluginStack plugins;
private ImageFileCreator(ImagePluginStack plugins) { private final boolean generateRuntimeImage;
private final TaskHelper helper;
private ImageFileCreator(ImagePluginStack plugins,
boolean generateRuntimeImage,
TaskHelper taskHelper) {
this.plugins = Objects.requireNonNull(plugins); this.plugins = Objects.requireNonNull(plugins);
this.generateRuntimeImage = generateRuntimeImage;
this.helper = taskHelper;
} }
public static ExecutableImage create(Set<Archive> archives, /**
ImagePluginStack plugins) * Create an executable image based on a set of input archives and a given
throws IOException { * plugin stack for a given byte order. It optionally generates a runtime
return ImageFileCreator.create(archives, ByteOrder.nativeOrder(), * that can be used for linking from the run-time image if
plugins); * {@code generateRuntimeImage} is set to {@code true}.
} *
* @param archives The set of input archives
public static ExecutableImage create(Set<Archive> archives, * @param byteOrder The desired byte order of the result
ByteOrder byteOrder) * @param plugins The plugin stack to apply to the input
throws IOException { * @param generateRuntimeImage if a runtime suitable for linking from the
return ImageFileCreator.create(archives, byteOrder, * run-time image should get created.
new ImagePluginStack()); * @return The executable image.
} * @throws IOException
*/
public static ExecutableImage create(Set<Archive> archives, public static ExecutableImage create(Set<Archive> archives,
ByteOrder byteOrder, ByteOrder byteOrder,
ImagePluginStack plugins) ImagePluginStack plugins,
boolean generateRuntimeImage,
TaskHelper taskHelper)
throws IOException throws IOException
{ {
ImageFileCreator image = new ImageFileCreator(plugins); ImageFileCreator image = new ImageFileCreator(plugins,
generateRuntimeImage,
taskHelper);
try { try {
image.readAllEntries(archives); image.readAllEntries(archives);
// write to modular image // write to modular image
image.writeImage(archives, byteOrder); image.writeImage(archives, byteOrder);
} catch (RuntimeImageLinkException e) {
// readAllEntries() might throw this exception.
// Propagate as IOException with appropriate message for
// jlink runs from the run-time image. This handles better
// error messages for the case of modified files in the run-time
// image.
throw image.newIOException(e);
} finally { } finally {
//Close all archives // Close all archives
for (Archive a : archives) { for (Archive a : archives) {
a.close(); a.close();
} }
@ -125,7 +163,8 @@ public final class ImageFileCreator {
public static void recreateJimage(Path jimageFile, public static void recreateJimage(Path jimageFile,
Set<Archive> archives, Set<Archive> archives,
ImagePluginStack pluginSupport) ImagePluginStack pluginSupport,
boolean generateRuntimeImage)
throws IOException { throws IOException {
try { try {
Map<String, List<Entry>> entriesForModule Map<String, List<Entry>> entriesForModule
@ -142,7 +181,7 @@ public final class ImageFileCreator {
try (OutputStream fos = Files.newOutputStream(jimageFile); try (OutputStream fos = Files.newOutputStream(jimageFile);
BufferedOutputStream bos = new BufferedOutputStream(fos); BufferedOutputStream bos = new BufferedOutputStream(fos);
DataOutputStream out = new DataOutputStream(bos)) { DataOutputStream out = new DataOutputStream(bos)) {
generateJImage(pool, writer, pluginSupport, out); generateJImage(pool, writer, pluginSupport, out, generateRuntimeImage);
} }
} finally { } finally {
//Close all archives //Close all archives
@ -158,9 +197,14 @@ public final class ImageFileCreator {
BasicImageWriter writer = new BasicImageWriter(byteOrder); BasicImageWriter writer = new BasicImageWriter(byteOrder);
ResourcePoolManager allContent = createPoolManager(archives, ResourcePoolManager allContent = createPoolManager(archives,
entriesForModule, byteOrder, writer); entriesForModule, byteOrder, writer);
ResourcePool result; ResourcePool result = null;
try (DataOutputStream out = plugins.getJImageFileOutputStream()) { try (DataOutputStream out = plugins.getJImageFileOutputStream()) {
result = generateJImage(allContent, writer, plugins, out); result = generateJImage(allContent, writer, plugins, out, generateRuntimeImage);
} catch (RuntimeImageLinkException e) {
// Propagate as IOException with appropriate message for
// jlink runs from the run-time image. This handles better
// error messages for the case of --patch-module.
throw newIOException(e);
} }
//Handle files. //Handle files.
@ -174,14 +218,53 @@ public final class ImageFileCreator {
} }
} }
private IOException newIOException(RuntimeImageLinkException e) throws IOException {
if (JlinkTask.DEBUG) {
e.printStackTrace();
}
String message = switch (e.getReason()) {
case PATCH_MODULE -> helper.getMessage("err.runtime.link.patched.module", e.getFile());
case MODIFIED_FILE -> helper.getMessage("err.runtime.link.modified.file", e.getFile());
default -> throw new AssertionError("Unexpected value: " + e.getReason());
};
throw new IOException(message);
}
/**
* Create a jimage based on content of the given ResourcePoolManager,
* optionally creating a runtime that can be used for linking from the
* run-time image
*
* @param allContent The content that needs to get added to the resulting
* lib/modules (jimage) file.
* @param writer The writer for the jimage file.
* @param pluginSupport The stack of all plugins to apply.
* @param out The output stream to write the jimage to.
* @param generateRuntimeImage if a runtime suitable for linking from the
* run-time image should get created.
* @return A pool of the actual result resources.
* @throws IOException
*/
private static ResourcePool generateJImage(ResourcePoolManager allContent, private static ResourcePool generateJImage(ResourcePoolManager allContent,
BasicImageWriter writer, BasicImageWriter writer,
ImagePluginStack pluginSupport, ImagePluginStack pluginSupport,
DataOutputStream out DataOutputStream out,
boolean generateRuntimeImage
) throws IOException { ) throws IOException {
ResourcePool resultResources; ResourcePool resultResources;
try { try {
resultResources = pluginSupport.visitResources(allContent); resultResources = pluginSupport.visitResources(allContent);
if (generateRuntimeImage) {
// Keep track of non-modules resources for linking from a run-time image
resultResources = addNonClassResourcesTrackFiles(resultResources,
writer);
// Generate the diff between the input resources from packaged
// modules in 'allContent' to the plugin- or otherwise
// generated-content in 'resultResources'
resultResources = addResourceDiffFiles(allContent.resourcePool(),
resultResources,
writer);
}
} catch (PluginException pe) { } catch (PluginException pe) {
if (JlinkTask.DEBUG) { if (JlinkTask.DEBUG) {
pe.printStackTrace(); pe.printStackTrace();
@ -198,7 +281,7 @@ public final class ImageFileCreator {
List<ResourcePoolEntry> content = new ArrayList<>(); List<ResourcePoolEntry> content = new ArrayList<>();
List<String> paths = new ArrayList<>(); List<String> paths = new ArrayList<>();
// the order of traversing the resources and the order of // the order of traversing the resources and the order of
// the module content being written must be the same // the module content being written must be the same
resultResources.entries().forEach(res -> { resultResources.entries().forEach(res -> {
if (res.type().equals(ResourcePoolEntry.Type.CLASS_OR_RESOURCE)) { if (res.type().equals(ResourcePoolEntry.Type.CLASS_OR_RESOURCE)) {
@ -248,11 +331,225 @@ public final class ImageFileCreator {
return resultResources; return resultResources;
} }
/**
* Support for creating a runtime suitable for linking from the run-time
* image.
*
* Generates differences between the packaged modules "view" in
* {@code jmodContent} to the optimized image in {@code resultContent} and
* adds the result to the returned resource pool.
*
* @param jmodContent The resource pool view of packaged modules
* @param resultContent The optimized result generated from the jmodContent
* input by applying the plugin stack.
* @param writer The image writer.
* @return The resource pool with the difference file resources added to
* the {@code resultContent}
*/
@SuppressWarnings("try")
private static ResourcePool addResourceDiffFiles(ResourcePool jmodContent,
ResourcePool resultContent,
BasicImageWriter writer) {
JimageDiffGenerator generator = new JimageDiffGenerator();
List<ResourceDiff> diff;
try (ImageResource jmods = new ResourcePoolReader(jmodContent);
ImageResource jimage = new ResourcePoolReader(resultContent)) {
diff = generator.generateDiff(jmods, jimage);
} catch (Exception e) {
throw new AssertionError("Failed to generate the runtime image diff", e);
}
Set<String> modules = resultContent.moduleView().modules()
.map(a -> a.name())
.collect(Collectors.toSet());
// Add resource diffs for the resource files we are about to add
modules.stream().forEach(m -> {
String resourceName = String.format(DIFF_PATH, m);
ResourceDiff.Builder builder = new ResourceDiff.Builder();
ResourceDiff d = builder.setKind(ResourceDiff.Kind.ADDED)
.setName(resourceName)
.build();
diff.add(d);
});
Map<String, List<ResourceDiff>> perModDiffs = preparePerModuleDiffs(diff,
modules);
return addDiffResourcesFiles(modules, perModDiffs, resultContent, writer);
}
private static Map<String, List<ResourceDiff>> preparePerModuleDiffs(List<ResourceDiff> resDiffs,
Set<String> modules) {
Map<String, List<ResourceDiff>> modToDiff = new HashMap<>();
resDiffs.forEach(d -> {
int secondSlash = d.getName().indexOf("/", 1);
if (secondSlash == -1) {
throw new AssertionError("Module name not present");
}
String module = d.getName().substring(1, secondSlash);
List<ResourceDiff> perModDiff = modToDiff.computeIfAbsent(module,
a -> new ArrayList<>());
perModDiff.add(d);
});
Map<String, List<ResourceDiff>> allModsToDiff = new HashMap<>();
modules.stream().forEach(m -> {
List<ResourceDiff> d = modToDiff.get(m);
if (d == null) {
// Not all modules will have a diff
allModsToDiff.put(m, Collections.emptyList());
} else {
allModsToDiff.put(m, d);
}
});
return allModsToDiff;
}
private static ResourcePool addDiffResourcesFiles(Set<String> modules,
Map<String, List<ResourceDiff>> perModDiffs,
ResourcePool resultResources,
BasicImageWriter writer) {
ResourcePoolManager mgr = createPoolManager(resultResources, writer);
ResourcePoolBuilder out = mgr.resourcePoolBuilder();
modules.stream().sorted().forEach(module -> {
String mResource = String.format(DIFF_PATH, module);
List<ResourceDiff> diff = perModDiffs.get(module);
// Note that for modules without diff to the packaged modules view
// we create resource diff files with just the header and no content.
ByteArrayOutputStream bout = new ByteArrayOutputStream();
try {
ResourceDiff.write(diff, bout);
} catch (IOException e) {
throw new AssertionError("Failed to write resource diff file" +
" for module " + module, e);
}
out.add(ResourcePoolEntry.create(mResource, bout.toByteArray()));
});
return out.build();
}
/**
* Support for creating runtimes that can be used for linking from the
* run-time image. Adds meta-data files for resources not in the lib/modules
* file of the JDK. That is, mapping files for which on-disk files belong to
* which module.
*
* @param resultResources
* The original resources which serve as the basis for generating
* the meta-data files.
* @param writer
* The image writer.
*
* @return An amended resource pool which includes meta-data files.
*/
private static ResourcePool addNonClassResourcesTrackFiles(ResourcePool resultResources,
BasicImageWriter writer) {
// Only add resources if jdk.jlink module is present in the target image
Optional<ResourcePoolModule> jdkJlink = resultResources.moduleView()
.findModule(JLINK_MOD_NAME);
if (jdkJlink.isPresent()) {
Map<String, List<String>> nonClassResources = recordAndFilterEntries(resultResources);
return addModuleResourceEntries(resultResources, nonClassResources, writer);
} else {
return resultResources; // No-op
}
}
/**
* Support for creating runtimes that can be used for linking from the
* run-time image. Adds the given mapping of files as a meta-data file to
* the given resource pool.
*
* @param resultResources
* The resource pool to add files to.
* @param nonClassResEntries
* The per module mapping for which to create the meta-data files
* for.
* @param writer
* The image writer.
*
* @return A resource pool with meta-data files added.
*/
private static ResourcePool addModuleResourceEntries(ResourcePool resultResources,
Map<String, List<String>> nonClassResEntries,
BasicImageWriter writer) {
Set<String> inputModules = resultResources.moduleView().modules()
.map(rm -> rm.name())
.collect(Collectors.toSet());
ResourcePoolManager mgr = createPoolManager(resultResources, writer);
ResourcePoolBuilder out = mgr.resourcePoolBuilder();
inputModules.stream().sorted().forEach(module -> {
String mResource = String.format(RESPATH, module);
List<String> mResources = nonClassResEntries.get(module);
if (mResources == null) {
// We create empty resource files for modules in the resource
// pool view that don't themselves contain native resources
// or config files.
out.add(ResourcePoolEntry.create(mResource, EMPTY_RESOURCE_BYTES));
} else {
String mResContent = mResources.stream().sorted()
.collect(Collectors.joining("\n"));
out.add(ResourcePoolEntry.create(mResource,
mResContent.getBytes(StandardCharsets.UTF_8)));
}
});
return out.build();
}
/**
* Support for creating runtimes that can be used for linking from the
* run-time image. Generates a per module mapping of files not part of the
* modules image (jimage). This mapping is needed so as to know which files
* of the installed JDK belong to which module.
*
* @param resultResources
* The resources from which the mapping gets generated
* @return A mapping with the module names as keys and the list of files not
* part of the modules image (jimage) as values.
*/
private static Map<String, List<String>> recordAndFilterEntries(ResourcePool resultResources) {
Map<String, List<String>> nonClassResEntries = new HashMap<>();
Platform platform = getTargetPlatform(resultResources);
resultResources.entries().forEach(entry -> {
// Note that the fs_$module_files file is a resource file itself, so
// we cannot add fs_$module_files themselves due to the
// not(class_or_resources) condition. However, we also don't want
// to track 'release' file entries (not(top) condition) as those are
// handled by the release info plugin.
if (entry.type() != ResourcePoolEntry.Type.CLASS_OR_RESOURCE &&
entry.type() != ResourcePoolEntry.Type.TOP) {
List<String> mRes = nonClassResEntries.computeIfAbsent(entry.moduleName(),
a -> new ArrayList<>());
ResourceFileEntry rfEntry = ResourceFileEntry.toResourceFileEntry(entry,
platform);
mRes.add(rfEntry.encodeToString());
}
});
return nonClassResEntries;
}
private static Platform getTargetPlatform(ResourcePool in) {
String platform = in.moduleView().findModule("java.base")
.map(ResourcePoolModule::targetPlatform)
.orElseThrow(() -> new AssertionError("java.base not found"));
return Platform.parsePlatform(platform);
}
private static ResourcePoolManager createPoolManager(Set<Archive> archives, private static ResourcePoolManager createPoolManager(Set<Archive> archives,
Map<String, List<Entry>> entriesForModule, Map<String, List<Entry>> entriesForModule,
ByteOrder byteOrder, ByteOrder byteOrder,
BasicImageWriter writer) throws IOException { BasicImageWriter writer) throws IOException {
ResourcePoolManager resources = new ResourcePoolManager(byteOrder, new StringTable() { ResourcePoolManager resources = createBasicResourcePoolManager(byteOrder, writer);
archives.stream()
.map(Archive::moduleName)
.sorted()
.flatMap(mn ->
entriesForModule.get(mn).stream()
.map(e -> new ArchiveEntryResourcePoolEntry(mn,
e.getResourcePoolEntryName(), e)))
.forEach(resources::add);
return resources;
}
private static ResourcePoolManager createBasicResourcePoolManager(ByteOrder byteOrder,
BasicImageWriter writer) {
return new ResourcePoolManager(byteOrder, new StringTable() {
@Override @Override
public int addString(String str) { public int addString(String str) {
@ -264,14 +561,25 @@ public final class ImageFileCreator {
return writer.getString(id); return writer.getString(id);
} }
}); });
archives.stream() }
.map(Archive::moduleName)
.sorted() /**
.flatMap(mn -> * Creates a ResourcePoolManager from existing resources so that more
entriesForModule.get(mn).stream() * resources can be appended.
.map(e -> new ArchiveEntryResourcePoolEntry(mn, *
e.getResourcePoolEntryName(), e))) * @param resultResources The existing resources to initially add.
.forEach(resources::add); * @param writer The basic image writer.
* @return An appendable ResourcePoolManager.
*/
private static ResourcePoolManager createPoolManager(ResourcePool resultResources,
BasicImageWriter writer) {
ResourcePoolManager resources = createBasicResourcePoolManager(resultResources.byteOrder(),
writer);
// Note that resources are already sorted in the correct order.
// The underlying ResourcePoolManager keeps track of entries via
// LinkedHashMap, which keeps values in insertion order. Therefore
// adding resources here, preserving that same order is OK.
resultResources.entries().forEach(resources::add);
return resources; return resources;
} }

View File

@ -0,0 +1,525 @@
/*
* Copyright (c) 2024, Red Hat, Inc.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package jdk.tools.jlink.internal;
import static jdk.tools.jlink.internal.LinkableRuntimeImage.RESPATH_PATTERN;
import static jdk.tools.jlink.internal.runtimelink.RuntimeImageLinkException.Reason.MODIFIED_FILE;
import static jdk.tools.jlink.internal.runtimelink.RuntimeImageLinkException.Reason.PATCH_MODULE;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.lang.module.ModuleFinder;
import java.lang.module.ModuleReference;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HexFormat;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import jdk.internal.util.OperatingSystem;
import jdk.tools.jlink.internal.Archive.Entry.EntryType;
import jdk.tools.jlink.internal.runtimelink.ResourceDiff;
import jdk.tools.jlink.internal.runtimelink.RuntimeImageLinkException;
import jdk.tools.jlink.plugin.ResourcePoolEntry;
import jdk.tools.jlink.plugin.ResourcePoolEntry.Type;
/**
* An archive implementation based on the JDK's run-time image. That is, classes
* and resources from the modules image (lib/modules, or jimage) and other
* associated files from the filesystem of the JDK installation.
*/
public class JRTArchive implements Archive {
private final String module;
private final Path path;
private final ModuleReference ref;
// The collection of files of this module
private final List<JRTFile> files = new ArrayList<>();
// Files not part of the lib/modules image of the JDK install.
// Thus, native libraries, binaries, legal files, etc.
private final List<String> otherRes;
// Maps a module resource path to the corresponding diff to packaged
// modules for that resource (if any)
private final Map<String, ResourceDiff> resDiff;
private final boolean errorOnModifiedFile;
private final TaskHelper taskHelper;
/**
* JRTArchive constructor
*
* @param module The module name this archive refers to
* @param path The JRT filesystem path.
* @param errorOnModifiedFile Whether or not modified files of the JDK
* install aborts the link.
* @param perModDiff The lib/modules (a.k.a jimage) diff for this module,
* possibly an empty list if there are no differences.
*/
JRTArchive(String module,
Path path,
boolean errorOnModifiedFile,
List<ResourceDiff> perModDiff,
TaskHelper taskHelper) {
this.module = module;
this.path = path;
this.ref = ModuleFinder.ofSystem()
.find(module)
.orElseThrow(() ->
new IllegalArgumentException(
"Module " + module +
" not part of the JDK install"));
this.errorOnModifiedFile = errorOnModifiedFile;
this.otherRes = readModuleResourceFile(module);
this.resDiff = Objects.requireNonNull(perModDiff).stream()
.collect(Collectors.toMap(ResourceDiff::getName, Function.identity()));
this.taskHelper = taskHelper;
}
@Override
public String moduleName() {
return module;
}
@Override
public Path getPath() {
return path;
}
@Override
public Stream<Entry> entries() {
try {
collectFiles();
} catch (IOException e) {
throw new UncheckedIOException(e);
}
return files.stream().map(JRTFile::toEntry);
}
@Override
public void open() throws IOException {
if (files.isEmpty()) {
collectFiles();
}
}
@Override
public void close() throws IOException {
if (!files.isEmpty()) {
files.clear();
}
}
@Override
public int hashCode() {
return Objects.hash(module, path);
}
@Override
public boolean equals(Object obj) {
return (obj instanceof JRTArchive other &&
Objects.equals(module, other.module) &&
Objects.equals(path, other.path));
}
private void collectFiles() throws IOException {
if (files.isEmpty()) {
addNonClassResources();
// Add classes/resources from the run-time image,
// patched with the run-time image diff
files.addAll(ref.open().list()
.filter(i -> {
String lookupKey = String.format("/%s/%s", module, i);
ResourceDiff rd = resDiff.get(lookupKey);
// Filter all resources with a resource diff
// that are of kind MODIFIED.
// Note that REMOVED won't happen since in
// that case the module listing won't have
// the resource anyway.
// Note as well that filter removes files
// of kind ADDED. Those files are not in
// the packaged modules, so ought not to
// get returned from the pipeline.
return (rd == null ||
rd.getKind() == ResourceDiff.Kind.MODIFIED);
})
.map(s -> {
String lookupKey = String.format("/%s/%s", module, s);
return new JRTArchiveFile(JRTArchive.this, s,
EntryType.CLASS_OR_RESOURCE,
null /* hashOrTarget */,
false /* symlink */,
resDiff.get(lookupKey));
})
.toList());
// Finally add all files only present in the resource diff
// That is, removed items in the run-time image.
files.addAll(resDiff.values().stream()
.filter(rd -> rd.getKind() == ResourceDiff.Kind.REMOVED)
.map(s -> {
int secondSlash = s.getName().indexOf("/", 1);
assert secondSlash != -1;
String pathWithoutModule = s.getName().substring(secondSlash + 1);
return new JRTArchiveFile(JRTArchive.this,
pathWithoutModule,
EntryType.CLASS_OR_RESOURCE,
null /* hashOrTarget */,
false /* symlink */,
s);
})
.toList());
}
}
/*
* no need to keep track of the warning produced since this is eagerly
* checked once.
*/
private void addNonClassResources() {
// Not all modules will have other resources like bin, lib, legal etc.
// files. In that case the list will be empty.
if (!otherRes.isEmpty()) {
files.addAll(otherRes.stream()
.filter(Predicate.not(String::isEmpty))
.map(s -> {
ResourceFileEntry m = ResourceFileEntry.decodeFromString(s);
// Read from the base JDK image.
Path path = BASE.resolve(m.resPath);
if (shaSumMismatch(path, m.hashOrTarget, m.symlink)) {
if (errorOnModifiedFile) {
throw new RuntimeImageLinkException(path.toString(), MODIFIED_FILE);
} else {
taskHelper.warning("err.runtime.link.modified.file", path.toString());
}
}
return new JRTArchiveFile(JRTArchive.this,
m.resPath,
toEntryType(m.resType),
m.hashOrTarget,
m.symlink,
/* diff only for resources */
null);
})
.toList());
}
}
static boolean shaSumMismatch(Path res, String expectedSha, boolean isSymlink) {
if (isSymlink) {
return false;
}
// handle non-symlink resources
try {
HexFormat format = HexFormat.of();
byte[] expected = format.parseHex(expectedSha);
MessageDigest digest = MessageDigest.getInstance("SHA-512");
try (InputStream is = Files.newInputStream(res)) {
byte[] buf = new byte[1024];
int readBytes = -1;
while ((readBytes = is.read(buf)) != -1) {
digest.update(buf, 0, readBytes);
}
}
byte[] actual = digest.digest();
return !MessageDigest.isEqual(expected, actual);
} catch (Exception e) {
throw new AssertionError("SHA-512 sum check failed!", e);
}
}
private static EntryType toEntryType(Type input) {
return switch(input) {
case CLASS_OR_RESOURCE -> EntryType.CLASS_OR_RESOURCE;
case CONFIG -> EntryType.CONFIG;
case HEADER_FILE -> EntryType.HEADER_FILE;
case LEGAL_NOTICE -> EntryType.LEGAL_NOTICE;
case MAN_PAGE -> EntryType.MAN_PAGE;
case NATIVE_CMD -> EntryType.NATIVE_CMD;
case NATIVE_LIB -> EntryType.NATIVE_LIB;
case TOP -> throw new IllegalArgumentException(
"TOP files should be handled by ReleaseInfoPlugin!");
default -> throw new IllegalArgumentException("Unknown type: " + input);
};
}
public record ResourceFileEntry(Type resType,
boolean symlink,
String hashOrTarget,
String resPath) {
// Type file format:
// '<type>|{0,1}|<sha-sum>|<file-path>'
// (1) (2) (3) (4)
//
// Where fields are:
//
// (1) The resource type as specified by ResourcePoolEntry.type()
// (2) Symlink designator. 0 => regular resource, 1 => symlinked resource
// (3) The SHA-512 sum of the resources' content. The link to the target
// for symlinked resources.
// (4) The relative file path of the resource
private static final String TYPE_FILE_FORMAT = "%d|%d|%s|%s";
private static final Map<Integer, Type> typeMap = Arrays.stream(Type.values())
.collect(Collectors.toMap(Type::ordinal, Function.identity()));
public String encodeToString() {
return String.format(TYPE_FILE_FORMAT,
resType.ordinal(),
symlink ? 1 : 0,
hashOrTarget,
resPath);
}
/**
* line: <int>|<int>|<hashOrTarget>|<path>
*
* Take the integer before '|' convert it to a Type. The second
* token is an integer representing symlinks (or not). The third token is
* a hash sum (sha512) of the file denoted by the fourth token (path).
*/
static ResourceFileEntry decodeFromString(String line) {
assert !line.isEmpty();
String[] tokens = line.split("\\|", 4);
Type type = null;
int symlinkNum = -1;
try {
Integer typeInt = Integer.valueOf(tokens[0]);
type = typeMap.get(typeInt);
if (type == null) {
throw new AssertionError("Illegal type ordinal: " + typeInt);
}
symlinkNum = Integer.valueOf(tokens[1]);
} catch (NumberFormatException e) {
throw new AssertionError(e); // must not happen
}
if (symlinkNum < 0 || symlinkNum > 1) {
throw new AssertionError(
"Symlink designator out of range [0,1] got: " +
symlinkNum);
}
return new ResourceFileEntry(type,
symlinkNum == 1,
tokens[2] /* hash or target */,
tokens[3] /* resource path */);
}
public static ResourceFileEntry toResourceFileEntry(ResourcePoolEntry entry,
Platform platform) {
String resPathWithoutMod = dropModuleFromPath(entry, platform);
// Symlinks don't have a hash sum, but a link to the target instead
String hashOrTarget = entry.linkedTarget() == null
? computeSha512(entry)
: dropModuleFromPath(entry.linkedTarget(),
platform);
return new ResourceFileEntry(entry.type(),
entry.linkedTarget() != null,
hashOrTarget,
resPathWithoutMod);
}
private static String computeSha512(ResourcePoolEntry entry) {
try {
assert entry.linkedTarget() == null;
MessageDigest digest = MessageDigest.getInstance("SHA-512");
try (InputStream is = entry.content()) {
byte[] buf = new byte[1024];
int bytesRead = -1;
while ((bytesRead = is.read(buf)) != -1) {
digest.update(buf, 0, bytesRead);
}
}
byte[] db = digest.digest();
HexFormat format = HexFormat.of();
return format.formatHex(db);
} catch (Exception e) {
throw new AssertionError("Failed to generate hash sum for " +
entry.path());
}
}
private static String dropModuleFromPath(ResourcePoolEntry entry,
Platform platform) {
String resPath = entry.path()
.substring(
// + 2 => prefixed and suffixed '/'
// For example: '/java.base/'
entry.moduleName().length() + 2);
if (!isWindows(platform)) {
return resPath;
}
// For Windows the libraries live in the 'bin' folder rather than
// the 'lib' folder in the final image. Note that going by the
// NATIVE_LIB type only is insufficient since only files with suffix
// .dll/diz/map/pdb are transplanted to 'bin'.
// See: DefaultImageBuilder.nativeDir()
return nativeDir(entry, resPath);
}
private static boolean isWindows(Platform platform) {
return platform.os() == OperatingSystem.WINDOWS;
}
private static String nativeDir(ResourcePoolEntry entry, String resPath) {
if (entry.type() != ResourcePoolEntry.Type.NATIVE_LIB) {
return resPath;
}
// precondition: Native lib, windows platform
if (resPath.endsWith(".dll") || resPath.endsWith(".diz")
|| resPath.endsWith(".pdb") || resPath.endsWith(".map")) {
if (resPath.startsWith(LIB_DIRNAME + "/")) {
return BIN_DIRNAME + "/" +
resPath.substring((LIB_DIRNAME + "/").length());
}
}
return resPath;
}
private static final String BIN_DIRNAME = "bin";
private static final String LIB_DIRNAME = "lib";
}
private static final Path BASE = Paths.get(System.getProperty("java.home"));
interface JRTFile {
Entry toEntry();
}
record JRTArchiveFile(Archive archive,
String resPath,
EntryType resType,
String sha,
boolean symlink,
ResourceDiff diff) implements JRTFile {
public Entry toEntry() {
return new Entry(archive,
String.format("/%s/%s",
archive.moduleName(),
resPath),
resPath,
resType) {
@Override
public long size() {
try {
if (resType != EntryType.CLASS_OR_RESOURCE) {
// Read from the base JDK image, special casing
// symlinks, which have the link target in the
// hashOrTarget field
if (symlink) {
return Files.size(BASE.resolve(sha));
}
return Files.size(BASE.resolve(resPath));
} else {
if (diff != null) {
// If the resource has a diff to the
// packaged modules, use the diff. Diffs of kind
// ADDED have been filtered out in collectFiles();
assert diff.getKind() != ResourceDiff.Kind.ADDED;
assert diff.getName().equals(String.format("/%s/%s",
archive.moduleName(),
resPath));
return diff.getResourceBytes().length;
}
// Read from the module image. This works, because
// the underlying base path is a JrtPath with the
// JrtFileSystem underneath which is able to handle
// this size query.
try {
return Files.size(archive.getPath().resolve(resPath));
} catch (NoSuchFileException file) {
// This indicates that we don't find the class in the
// modules image using the JRT FS provider. Yet, we find
// the class using the system module finder. Therefore,
// we have a patched module. Mention that module patching
// is not supported.
throw new RuntimeImageLinkException(file.getFile(), PATCH_MODULE);
}
}
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
@Override
public InputStream stream() throws IOException {
if (resType != EntryType.CLASS_OR_RESOURCE) {
// Read from the base JDK image.
Path path = symlink ? BASE.resolve(sha) : BASE.resolve(resPath);
return Files.newInputStream(path);
} else {
// Read from the module image. Use the diff to the
// packaged modules if we have one. Diffs of kind
// ADDED have been filtered out in collectFiles();
if (diff != null) {
assert diff.getKind() != ResourceDiff.Kind.ADDED;
assert diff.getName().equals(String.format("/%s/%s",
archive.moduleName(),
resPath));
return new ByteArrayInputStream(diff.getResourceBytes());
}
String module = archive.moduleName();
ModuleReference mRef = ModuleFinder.ofSystem()
.find(module).orElseThrow();
return mRef.open().open(resPath).orElseThrow();
}
}
};
}
}
private static List<String> readModuleResourceFile(String modName) {
String resName = String.format(RESPATH_PATTERN, modName);
try {
try (InputStream inStream = JRTArchive.class.getModule()
.getResourceAsStream(resName)) {
String input = new String(inStream.readAllBytes(), StandardCharsets.UTF_8);
if (input.isEmpty()) {
// Not all modules have non-class resources
return Collections.emptyList();
} else {
return Arrays.asList(input.split("\n"));
}
}
} catch (IOException e) {
throw new UncheckedIOException("Failed to process resources from the " +
"run-time image for module " + modName, e);
}
}
}

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2015, 2023, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2015, 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -26,7 +26,6 @@ package jdk.tools.jlink.internal;
import java.lang.module.Configuration; import java.lang.module.Configuration;
import java.lang.module.ModuleFinder; import java.lang.module.ModuleFinder;
import java.nio.ByteOrder;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
@ -148,6 +147,9 @@ public final class Jlink {
private final Path output; private final Path output;
private final Set<String> modules; private final Set<String> modules;
private final ModuleFinder finder; private final ModuleFinder finder;
private final boolean linkFromRuntimeImage;
private final boolean ignoreModifiedRuntime;
private final boolean generateRuntimeImage;
/** /**
* jlink configuration, * jlink configuration,
@ -158,10 +160,16 @@ public final class Jlink {
*/ */
public JlinkConfiguration(Path output, public JlinkConfiguration(Path output,
Set<String> modules, Set<String> modules,
ModuleFinder finder) { ModuleFinder finder,
boolean linkFromRuntimeImage,
boolean ignoreModifiedRuntime,
boolean generateRuntimeImage) {
this.output = output; this.output = output;
this.modules = Objects.requireNonNull(modules); this.modules = Objects.requireNonNull(modules);
this.finder = finder; this.finder = finder;
this.linkFromRuntimeImage = linkFromRuntimeImage;
this.ignoreModifiedRuntime = ignoreModifiedRuntime;
this.generateRuntimeImage = generateRuntimeImage;
} }
/** /**
@ -186,6 +194,18 @@ public final class Jlink {
return finder; return finder;
} }
public boolean linkFromRuntimeImage() {
return linkFromRuntimeImage;
}
public boolean ignoreModifiedRuntime() {
return ignoreModifiedRuntime;
}
public boolean isGenerateRuntimeImage() {
return generateRuntimeImage;
}
/** /**
* Returns a {@link Configuration} of the given module path, * Returns a {@link Configuration} of the given module path,
* root modules with full service binding. * root modules with full service binding.

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2015, 2023, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2015, 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -24,6 +24,8 @@
*/ */
package jdk.tools.jlink.internal; package jdk.tools.jlink.internal;
import static jdk.tools.jlink.internal.TaskHelper.JLINK_BUNDLE;
import java.io.BufferedInputStream; import java.io.BufferedInputStream;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
@ -39,14 +41,15 @@ import java.lang.module.ResolutionException;
import java.lang.module.ResolvedModule; import java.lang.module.ResolvedModule;
import java.net.URI; import java.net.URI;
import java.nio.ByteOrder; import java.nio.ByteOrder;
import java.nio.file.Files;
import java.nio.file.FileVisitResult; import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor; import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes; import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.Date; import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
@ -60,18 +63,18 @@ import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
import jdk.internal.module.ModulePath;
import jdk.internal.module.ModuleReferenceImpl; import jdk.internal.module.ModuleReferenceImpl;
import jdk.tools.jlink.internal.TaskHelper.BadArgs; import jdk.internal.module.ModuleResolution;
import static jdk.tools.jlink.internal.TaskHelper.JLINK_BUNDLE; import jdk.internal.opt.CommandLine;
import jdk.tools.jlink.internal.ImagePluginStack.ImageProvider;
import jdk.tools.jlink.internal.Jlink.JlinkConfiguration; import jdk.tools.jlink.internal.Jlink.JlinkConfiguration;
import jdk.tools.jlink.internal.Jlink.PluginsConfiguration; import jdk.tools.jlink.internal.Jlink.PluginsConfiguration;
import jdk.tools.jlink.internal.TaskHelper.BadArgs;
import jdk.tools.jlink.internal.TaskHelper.Option; import jdk.tools.jlink.internal.TaskHelper.Option;
import jdk.tools.jlink.internal.TaskHelper.OptionsHelper; import jdk.tools.jlink.internal.TaskHelper.OptionsHelper;
import jdk.tools.jlink.internal.ImagePluginStack.ImageProvider; import jdk.tools.jlink.internal.runtimelink.RuntimeImageLinkException;
import jdk.tools.jlink.plugin.PluginException; import jdk.tools.jlink.plugin.PluginException;
import jdk.internal.opt.CommandLine;
import jdk.internal.module.ModulePath;
import jdk.internal.module.ModuleResolution;
/** /**
* Implementation for the jlink tool. * Implementation for the jlink tool.
@ -86,7 +89,6 @@ public class JlinkTask {
private static final TaskHelper taskHelper private static final TaskHelper taskHelper
= new TaskHelper(JLINK_BUNDLE); = new TaskHelper(JLINK_BUNDLE);
private static final Option<?>[] recognizedOptions = { private static final Option<?>[] recognizedOptions = {
new Option<JlinkTask>(false, (task, opt, arg) -> { new Option<JlinkTask>(false, (task, opt, arg) -> {
task.options.help = true; task.options.help = true;
@ -182,7 +184,17 @@ public class JlinkTask {
}, true, "--full-version"), }, true, "--full-version"),
new Option<JlinkTask>(false, (task, opt, arg) -> { new Option<JlinkTask>(false, (task, opt, arg) -> {
task.options.ignoreSigning = true; task.options.ignoreSigning = true;
}, "--ignore-signing-information"),}; }, "--ignore-signing-information"),
new Option<JlinkTask>(false, (task, opt, arg) -> {
task.options.ignoreModifiedRuntime = true;
}, true, "--ignore-modified-runtime"),
// option for generating a runtime that can then
// be used for linking from the run-time image.
new Option<JlinkTask>(false, (task, opt, arg) -> {
task.options.generateLinkableRuntime = true;
}, true, "--generate-linkable-runtime")
};
private static final String PROGNAME = "jlink"; private static final String PROGNAME = "jlink";
private final OptionsValues options = new OptionsValues(); private final OptionsValues options = new OptionsValues();
@ -222,6 +234,8 @@ public class JlinkTask {
boolean ignoreSigning = false; boolean ignoreSigning = false;
boolean bindServices = false; boolean bindServices = false;
boolean suggestProviders = false; boolean suggestProviders = false;
boolean ignoreModifiedRuntime = false;
boolean generateLinkableRuntime = false;
} }
public static final String OPTIONS_RESOURCE = "jdk/tools/jlink/internal/options"; public static final String OPTIONS_RESOURCE = "jdk/tools/jlink/internal/options";
@ -252,7 +266,7 @@ public class JlinkTask {
.showUsage(true); .showUsage(true);
} }
if (options.help) { if (options.help) {
optionsHelper.showHelp(PROGNAME); optionsHelper.showHelp(PROGNAME, LinkableRuntimeImage.isLinkableRuntime());
return EXIT_OK; return EXIT_OK;
} }
if (optionsHelper.shouldListPlugins()) { if (optionsHelper.shouldListPlugins()) {
@ -270,11 +284,6 @@ public class JlinkTask {
if (jmods != null) { if (jmods != null) {
options.modulePath.add(jmods); options.modulePath.add(jmods);
} }
if (options.modulePath.isEmpty()) {
throw taskHelper.newBadArgs("err.modulepath.must.be.specified")
.showUsage(true);
}
} }
JlinkConfiguration config = initJlinkConfig(); JlinkConfiguration config = initJlinkConfig();
@ -300,7 +309,7 @@ public class JlinkTask {
} }
cleanupOutput(outputPath); cleanupOutput(outputPath);
return EXIT_ERROR; return EXIT_ERROR;
} catch (IllegalArgumentException | ResolutionException e) { } catch (IllegalArgumentException | ResolutionException | RuntimeImageLinkException e) {
log.println(taskHelper.getMessage("error.prefix") + " " + e.getMessage()); log.println(taskHelper.getMessage("error.prefix") + " " + e.getMessage());
if (DEBUG) { if (DEBUG) {
e.printStackTrace(log); e.printStackTrace(log);
@ -356,6 +365,7 @@ public class JlinkTask {
false, false,
null, null,
false, false,
new OptionsValues(),
null); null);
// Then create the Plugin Stack // Then create the Plugin Stack
@ -370,7 +380,7 @@ public class JlinkTask {
private JlinkConfiguration initJlinkConfig() throws BadArgs { private JlinkConfiguration initJlinkConfig() throws BadArgs {
Set<String> roots = new HashSet<>(); Set<String> roots = new HashSet<>();
for (String mod : options.addMods) { for (String mod : options.addMods) {
if (mod.equals(ALL_MODULE_PATH)) { if (mod.equals(ALL_MODULE_PATH) && options.modulePath.size() > 0) {
ModuleFinder finder = newModuleFinder(options.modulePath, options.limitMods, Set.of()); ModuleFinder finder = newModuleFinder(options.modulePath, options.limitMods, Set.of());
// all observable modules are roots // all observable modules are roots
finder.findAll() finder.findAll()
@ -392,9 +402,75 @@ public class JlinkTask {
finder = newModuleFinder(options.modulePath, options.limitMods, roots); finder = newModuleFinder(options.modulePath, options.limitMods, roots);
} }
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 have
// the case where we link from the run-time image. In that case, we take
// the JDK modules from the run-time image (ModuleFinder.ofSystem()).
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");
}
return new JlinkConfiguration(options.output, return new JlinkConfiguration(options.output,
roots, roots,
finder); finder,
isLinkFromRuntime,
options.ignoreModifiedRuntime,
options.generateLinkableRuntime);
}
/**
* Creates a combined module finder of {@code finder} and
* {@code runtimeImageFinder} that first looks-up modules in the
* {@code runtimeImageFinder} and if not present in {@code finder}.
*
* @param runtimeImageFinder A system modules finder.
* @param finder A module finder based on packaged modules.
* @param limitMods The set of limited modules for the resulting
* finder (if any).
* @param roots All module roots.
*
* @return A combined finder, or the input finder, potentially applying
* module limits.
*/
private ModuleFinder combinedFinders(ModuleFinder runtimeImageFinder,
ModuleFinder finder,
Set<String> limitMods,
Set<String> roots) {
ModuleFinder combined = new ModuleFinder() {
@Override
public Optional<ModuleReference> find(String name) {
Optional<ModuleReference> mref = runtimeImageFinder.find(name);
if (mref.isEmpty()) {
return finder.find(name);
}
return mref;
}
@Override
public Set<ModuleReference> findAll() {
Set<ModuleReference> all = new HashSet<>();
all.addAll(runtimeImageFinder.findAll());
all.addAll(finder.findAll());
return Collections.unmodifiableSet(all);
}
};
// if limitmods is specified then limit the universe
if (limitMods != null && !limitMods.isEmpty()) {
return limitFinder(combined, limitMods, Objects.requireNonNull(roots));
}
return combined;
} }
private void createImage(JlinkConfiguration config) throws Exception { private void createImage(JlinkConfiguration config) throws Exception {
@ -413,6 +489,7 @@ public class JlinkTask {
options.bindServices, options.bindServices,
options.endian, options.endian,
options.verbose, options.verbose,
options,
log); log);
// Then create the Plugin Stack // Then create the Plugin Stack
@ -433,10 +510,10 @@ public class JlinkTask {
} }
/* /*
* Returns a module finder of the given module path that limits * Returns a module finder of the given module path or the system modules
* the observable modules to those in the transitive closure of * if the module path is empty that limits the observable modules to those
* the modules specified in {@code limitMods} plus other modules * in the transitive closure of the modules specified in {@code limitMods}
* specified in the {@code roots} set. * plus other modules specified in the {@code roots} set.
* *
* @throws IllegalArgumentException if java.base module is present * @throws IllegalArgumentException if java.base module is present
* but its descriptor has no version * but its descriptor has no version
@ -445,14 +522,10 @@ public class JlinkTask {
Set<String> limitMods, Set<String> limitMods,
Set<String> roots) Set<String> roots)
{ {
if (Objects.requireNonNull(paths).isEmpty()) {
throw new IllegalArgumentException(taskHelper.getMessage("err.empty.module.path"));
}
Path[] entries = paths.toArray(new Path[0]);
Runtime.Version version = Runtime.version(); Runtime.Version version = Runtime.version();
ModuleFinder finder = ModulePath.of(version, true, entries); Path[] entries = paths.toArray(new Path[0]);
ModuleFinder finder = paths.isEmpty() ? ModuleFinder.ofSystem()
: ModulePath.of(version, true, entries);
if (finder.find("java.base").isPresent()) { if (finder.find("java.base").isPresent()) {
// use the version of java.base module, if present, as // use the version of java.base module, if present, as
// the release version for multi-release JAR files // the release version for multi-release JAR files
@ -505,8 +578,9 @@ public class JlinkTask {
private static Path toPathLocation(ResolvedModule m) { private static Path toPathLocation(ResolvedModule m) {
Optional<URI> ouri = m.reference().location(); Optional<URI> ouri = m.reference().location();
if (ouri.isEmpty()) if (ouri.isEmpty()) {
throw new InternalError(m + " does not have a location"); throw new InternalError(m + " does not have a location");
}
URI uri = ouri.get(); URI uri = ouri.get();
return Paths.get(uri); return Paths.get(uri);
} }
@ -518,6 +592,7 @@ public class JlinkTask {
boolean bindService, boolean bindService,
ByteOrder endian, ByteOrder endian,
boolean verbose, boolean verbose,
OptionsValues opts,
PrintWriter log) PrintWriter log)
throws IOException throws IOException
{ {
@ -534,12 +609,35 @@ public class JlinkTask {
taskHelper.getMessage("err.automatic.module", mref.descriptor().name(), loc)); taskHelper.getMessage("err.automatic.module", mref.descriptor().name(), loc));
}); });
// Perform some sanity checks for linking from the run-time image
if (config.linkFromRuntimeImage()) {
if (!LinkableRuntimeImage.isLinkableRuntime()) {
String msg = taskHelper.getMessage("err.runtime.link.not.linkable.runtime");
throw new IllegalArgumentException(msg);
}
// Do not permit linking from run-time image and also including jdk.jlink module
if (cf.findModule(JlinkTask.class.getModule().getName()).isPresent()) {
String msg = taskHelper.getMessage("err.runtime.link.jdk.jlink.prohibited");
throw new IllegalArgumentException(msg);
}
// Print info message indicating linking from the run-time image
if (verbose && log != null) {
log.println(taskHelper.getMessage("runtime.link.info"));
}
}
if (verbose && log != null) { if (verbose && log != null) {
// print modules to be linked in // print modules to be linked in
cf.modules().stream() cf.modules().stream()
.sorted(Comparator.comparing(ResolvedModule::name)) .sorted(Comparator.comparing(ResolvedModule::name))
.forEach(rm -> log.format("%s %s%n", .forEach(rm -> log.format("%s %s%s%n",
rm.name(), rm.reference().location().get())); rm.name(),
rm.reference().location().get(),
// We have a link from run-time image when scheme is 'jrt'
"jrt".equals(rm.reference().location().get().getScheme())
? " " + taskHelper.getMessage("runtime.link.jprt.path.extra")
: ""));
// print provider info // print provider info
Set<ModuleReference> references = cf.modules().stream() Set<ModuleReference> references = cf.modules().stream()
@ -559,14 +657,15 @@ public class JlinkTask {
.map(ModuleDescriptor::name) .map(ModuleDescriptor::name)
.collect(Collectors.joining(", ")); .collect(Collectors.joining(", "));
if (!"".equals(im)) if (!"".equals(im)) {
log.println("WARNING: Using incubator modules: " + im); log.println("WARNING: Using incubator modules: " + im);
}
} }
Map<String, Path> mods = cf.modules().stream() Map<String, Path> mods = cf.modules().stream()
.collect(Collectors.toMap(ResolvedModule::name, JlinkTask::toPathLocation)); .collect(Collectors.toMap(ResolvedModule::name, JlinkTask::toPathLocation));
// determine the target platform of the image being created // determine the target platform of the image being created
Platform targetPlatform = targetPlatform(cf, mods); Platform targetPlatform = targetPlatform(cf, mods, config.linkFromRuntimeImage());
// if the user specified any --endian, then it must match the target platform's native // if the user specified any --endian, then it must match the target platform's native
// endianness // endianness
if (endian != null && endian != targetPlatform.arch().byteOrder()) { if (endian != null && endian != targetPlatform.arch().byteOrder()) {
@ -580,7 +679,92 @@ public class JlinkTask {
targetPlatform.arch().byteOrder(), targetPlatform); targetPlatform.arch().byteOrder(), targetPlatform);
} }
} }
return new ImageHelper(cf, mods, targetPlatform, retainModulesPath, ignoreSigning);
// use the version of java.base module, if present, as
// the release version for multi-release JAR files
var version = cf.findModule("java.base")
.map(ResolvedModule::reference)
.map(ModuleReference::descriptor)
.flatMap(ModuleDescriptor::version)
.map(ModuleDescriptor.Version::toString)
.map(Runtime.Version::parse)
.orElse(Runtime.version());
Set<Archive> archives = mods.entrySet().stream()
.map(e -> newArchive(e.getKey(),
e.getValue(),
version,
ignoreSigning,
config,
log))
.collect(Collectors.toSet());
return new ImageHelper(archives,
targetPlatform,
retainModulesPath,
config.isGenerateRuntimeImage());
}
private static Archive newArchive(String module,
Path path,
Runtime.Version version,
boolean ignoreSigning,
JlinkConfiguration config,
PrintWriter log) {
if (path.toString().endsWith(".jmod")) {
return new JmodArchive(module, path);
} else if (path.toString().endsWith(".jar")) {
ModularJarArchive modularJarArchive = new ModularJarArchive(module, path, version);
try (Stream<Archive.Entry> entries = modularJarArchive.entries()) {
boolean hasSignatures = entries.anyMatch((entry) -> {
String name = entry.name().toUpperCase(Locale.ROOT);
return name.startsWith("META-INF/") && name.indexOf('/', 9) == -1 && (
name.endsWith(".SF") ||
name.endsWith(".DSA") ||
name.endsWith(".RSA") ||
name.endsWith(".EC") ||
name.startsWith("META-INF/SIG-")
);
});
if (hasSignatures) {
if (ignoreSigning) {
System.err.println(taskHelper.getMessage("warn.signing", path));
} else {
throw new IllegalArgumentException(taskHelper.getMessage("err.signing", path));
}
}
}
return modularJarArchive;
} else if (Files.isDirectory(path) && !"jrt".equals(path.toUri().getScheme())) {
// The jrt URI path scheme conditional is there since we'd otherwise
// enter this branch for linking from the run-time image where the
// path is a jrt path. Note that the specific module would be a
// directory. I.e. Files.isDirectory() would be true.
Path modInfoPath = path.resolve("module-info.class");
if (Files.isRegularFile(modInfoPath)) {
return new DirArchive(path, findModuleName(modInfoPath));
} else {
throw new IllegalArgumentException(
taskHelper.getMessage("err.not.a.module.directory", path));
}
} else if (config.linkFromRuntimeImage()) {
return LinkableRuntimeImage.newArchive(module, path, config.ignoreModifiedRuntime(), taskHelper);
} else {
throw new IllegalArgumentException(
taskHelper.getMessage("err.not.modular.format", module, path));
}
}
private static String findModuleName(Path modInfoPath) {
try (BufferedInputStream bis = new BufferedInputStream(
Files.newInputStream(modInfoPath))) {
return ModuleDescriptor.read(bis).name();
} catch (IOException exp) {
throw new IllegalArgumentException(taskHelper.getMessage(
"err.cannot.read.module.info", modInfoPath), exp);
}
} }
/* /*
@ -626,10 +810,12 @@ public class JlinkTask {
}; };
} }
private static Platform targetPlatform(Configuration cf, Map<String, Path> modsPaths) throws IOException { private static Platform targetPlatform(Configuration cf,
Map<String, Path> modsPaths,
boolean runtimeImageLink) throws IOException {
Path javaBasePath = modsPaths.get("java.base"); Path javaBasePath = modsPaths.get("java.base");
assert javaBasePath != null : "java.base module path is missing"; assert javaBasePath != null : "java.base module path is missing";
if (isJavaBaseFromDefaultModulePath(javaBasePath)) { if (runtimeImageLink || isJavaBaseFromDefaultModulePath(javaBasePath)) {
// this implies that the java.base module used for the target image // this implies that the java.base module used for the target image
// will correspond to the current platform. So this isn't an attempt to // will correspond to the current platform. So this isn't an attempt to
// build a cross-platform image. We use the current platform's endianness // build a cross-platform image. We use the current platform's endianness
@ -720,8 +906,9 @@ public class JlinkTask {
String header, String header,
Set<ModuleReference> modules, Set<ModuleReference> modules,
Map<String, Set<String>> serviceToUses) { Map<String, Set<String>> serviceToUses) {
if (modules.isEmpty()) if (modules.isEmpty()) {
return; return;
}
// Build a map of a service type to the provider modules // Build a map of a service type to the provider modules
Map<String, Set<ModuleDescriptor>> providers = new HashMap<>(); Map<String, Set<ModuleDescriptor>> providers = new HashMap<>();
@ -845,95 +1032,14 @@ public class JlinkTask {
return sb.toString(); return sb.toString();
} }
private static class ImageHelper implements ImageProvider { private static record ImageHelper(Set<Archive> archives,
final Platform targetPlatform; Platform targetPlatform,
final Path packagedModulesPath; Path packagedModulesPath,
final boolean ignoreSigning; boolean generateRuntimeImage) implements ImageProvider {
final Runtime.Version version;
final Set<Archive> archives;
ImageHelper(Configuration cf,
Map<String, Path> modsPaths,
Platform targetPlatform,
Path packagedModulesPath,
boolean ignoreSigning) throws IOException {
Objects.requireNonNull(targetPlatform);
this.targetPlatform = targetPlatform;
this.packagedModulesPath = packagedModulesPath;
this.ignoreSigning = ignoreSigning;
// use the version of java.base module, if present, as
// the release version for multi-release JAR files
this.version = cf.findModule("java.base")
.map(ResolvedModule::reference)
.map(ModuleReference::descriptor)
.flatMap(ModuleDescriptor::version)
.map(ModuleDescriptor.Version::toString)
.map(Runtime.Version::parse)
.orElse(Runtime.version());
this.archives = modsPaths.entrySet().stream()
.map(e -> newArchive(e.getKey(), e.getValue()))
.collect(Collectors.toSet());
}
private Archive newArchive(String module, Path path) {
if (path.toString().endsWith(".jmod")) {
return new JmodArchive(module, path);
} else if (path.toString().endsWith(".jar")) {
ModularJarArchive modularJarArchive = new ModularJarArchive(module, path, version);
try (Stream<Archive.Entry> entries = modularJarArchive.entries()) {
boolean hasSignatures = entries.anyMatch((entry) -> {
String name = entry.name().toUpperCase(Locale.ROOT);
return name.startsWith("META-INF/") && name.indexOf('/', 9) == -1 && (
name.endsWith(".SF") ||
name.endsWith(".DSA") ||
name.endsWith(".RSA") ||
name.endsWith(".EC") ||
name.startsWith("META-INF/SIG-")
);
});
if (hasSignatures) {
if (ignoreSigning) {
System.err.println(taskHelper.getMessage("warn.signing", path));
} else {
throw new IllegalArgumentException(taskHelper.getMessage("err.signing", path));
}
}
}
return modularJarArchive;
} else if (Files.isDirectory(path)) {
Path modInfoPath = path.resolve("module-info.class");
if (Files.isRegularFile(modInfoPath)) {
return new DirArchive(path, findModuleName(modInfoPath));
} else {
throw new IllegalArgumentException(
taskHelper.getMessage("err.not.a.module.directory", path));
}
} else {
throw new IllegalArgumentException(
taskHelper.getMessage("err.not.modular.format", module, path));
}
}
private static String findModuleName(Path modInfoPath) {
try (BufferedInputStream bis = new BufferedInputStream(
Files.newInputStream(modInfoPath))) {
return ModuleDescriptor.read(bis).name();
} catch (IOException exp) {
throw new IllegalArgumentException(taskHelper.getMessage(
"err.cannot.read.module.info", modInfoPath), exp);
}
}
@Override @Override
public ExecutableImage retrieve(ImagePluginStack stack) throws IOException { public ExecutableImage retrieve(ImagePluginStack stack) throws IOException {
ExecutableImage image = ImageFileCreator.create(archives, ExecutableImage image = ImageFileCreator.create(archives,
targetPlatform.arch().byteOrder(), stack); targetPlatform.arch().byteOrder(), stack, generateRuntimeImage, taskHelper);
if (packagedModulesPath != null) { if (packagedModulesPath != null) {
// copy the packaged modules to the given path // copy the packaged modules to the given path
Files.createDirectories(packagedModulesPath); Files.createDirectories(packagedModulesPath);

View File

@ -0,0 +1,88 @@
/*
* Copyright (c) 2024, Red Hat, Inc.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package jdk.tools.jlink.internal;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Path;
import java.util.List;
import jdk.tools.jlink.internal.runtimelink.ResourceDiff;
/**
* Class that supports the feature of running jlink based on the current
* run-time image.
*/
public class LinkableRuntimeImage {
// meta-data files per module for supporting linking from the run-time image
public static final String RESPATH_PATTERN = "jdk/tools/jlink/internal/runtimelink/fs_%s_files";
// The diff files per module for supporting linking from the run-time image
public static final String DIFF_PATTERN = "jdk/tools/jlink/internal/runtimelink/diff_%s";
/**
* In order to be able to show whether or not a runtime is capable of
* linking from it in {@code jlink --help} we need to look for the delta
* files in the {@code jdk.jlink} module. If present we have the capability.
*
* @return {@code true} iff this jlink is capable of linking from the
* run-time image.
*/
public static boolean isLinkableRuntime() {
try (InputStream in = getDiffInputStream("java.base")) {
return in != null;
} catch (IOException e) {
// fall-through
}
return false;
}
private static InputStream getDiffInputStream(String module) throws IOException {
String resourceName = String.format(DIFF_PATTERN, module);
return LinkableRuntimeImage.class.getModule().getResourceAsStream(resourceName);
}
public static Archive newArchive(String module,
Path path,
boolean ignoreModifiedRuntime,
TaskHelper taskHelper) {
assert isLinkableRuntime();
// Here we retrieve the per module difference file, which is
// potentially empty, from the modules image and pass that on to
// JRTArchive for further processing. When streaming resources from
// the archive, the diff is being applied.
List<ResourceDiff> perModuleDiff = null;
try (InputStream in = getDiffInputStream(module)){
perModuleDiff = ResourceDiff.read(in);
} catch (IOException e) {
throw new AssertionError("Failure to retrieve resource diff for " +
"module " + module, e);
}
return new JRTArchive(module, path, !ignoreModifiedRuntime, perModuleDiff, taskHelper);
}
}

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2015, 2023, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2015, 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -28,23 +28,21 @@ import java.io.IOException;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.MessageFormat; import java.text.MessageFormat;
import java.util.Map;
import java.util.HashMap;
import java.util.Map.Entry;
import java.util.Set;
import java.util.HashSet;
import java.util.List;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.stream.Stream;
import java.util.Collections; import java.util.Collections;
import java.util.Locale;
import java.util.ResourceBundle;
import java.util.MissingResourceException;
import java.util.Comparator; import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import java.util.Set;
import java.util.stream.Stream;
import jdk.tools.jlink.builder.DefaultImageBuilder; import jdk.tools.jlink.builder.DefaultImageBuilder;
import jdk.tools.jlink.builder.ImageBuilder; import jdk.tools.jlink.builder.ImageBuilder;
@ -55,7 +53,6 @@ import jdk.tools.jlink.internal.plugins.ExcludeJmodSectionPlugin;
import jdk.tools.jlink.internal.plugins.PluginsResourceBundle; import jdk.tools.jlink.internal.plugins.PluginsResourceBundle;
import jdk.tools.jlink.plugin.Plugin; import jdk.tools.jlink.plugin.Plugin;
import jdk.tools.jlink.plugin.Plugin.Category; import jdk.tools.jlink.plugin.Plugin.Category;
import jdk.tools.jlink.plugin.PluginException;
/** /**
* *
@ -584,7 +581,7 @@ public final class TaskHelper {
return null; return null;
} }
public void showHelp(String progName) { public void showHelp(String progName, boolean linkableRuntimeEnabled) {
log.println(bundleHelper.getMessage("main.usage", progName)); log.println(bundleHelper.getMessage("main.usage", progName));
Stream.concat(options.stream(), pluginOptions.mainOptions.stream()) Stream.concat(options.stream(), pluginOptions.mainOptions.stream())
.filter(option -> !option.isHidden()) .filter(option -> !option.isHidden())
@ -594,6 +591,17 @@ public final class TaskHelper {
}); });
log.println(bundleHelper.getMessage("main.command.files")); log.println(bundleHelper.getMessage("main.command.files"));
// If the JDK build has the run-time image capability show it
// in the help output in human readable form.
String qualifier = null;
if (linkableRuntimeEnabled) {
qualifier = bundleHelper.getMessage("main.runtime.image.linking.cap.enabled");
} else {
qualifier = bundleHelper.getMessage("main.runtime.image.linking.cap.disabled");
}
log.println(bundleHelper.getMessage("main.runtime.image.linking.cap.sect.header"));
log.println(bundleHelper.getMessage("main.runtime.image.linking.cap.msg",
qualifier));
} }
public void listPlugins() { public void listPlugins() {

View File

@ -0,0 +1,113 @@
/*
* Copyright (c) 2024, Red Hat, Inc.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package jdk.tools.jlink.internal.runtimelink;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* Generates a delta between packaged modules (as an ImageResource) and an
* optimized jimage (lib/modules) as an ImageResource. The result can be
* serialized to a file using {@link ResourceDiff}.
*/
public class JimageDiffGenerator {
/**
* A resource used for linking. Either packaged modules or
* packaged modules transformed to an optimized run-time image by applying
* the jlink plug-in pipeline. The canonical source, the packaged modules,
* are being used to devise the delta to the transformed run-time image. The
* delta can can then be used for jlink input together *with* a prepared
* run-time image.
*/
@SuppressWarnings("try")
public interface ImageResource extends AutoCloseable {
public List<String> getEntries();
public byte[] getResourceBytes(String name);
}
/**
* Produce a difference between packaged modules' resources (base) and the
* result of all plug-ins being applied on those resources (image).
*
* @param base
* The ImageResource view of unmodified resources coming from
* packaged modules.
* @param image
* The ImageResource view of the jlink plug-in pipeline having
* been applied to the resources in base.
* @return The list of resource differences across all modules.
*/
public List<ResourceDiff> generateDiff(ImageResource base, ImageResource image) throws Exception {
List<String> baseResources;
Set<String> resources = new HashSet<>();
List<ResourceDiff> diffs = new ArrayList<>();
try (base; image) {
resources.addAll(image.getEntries());
baseResources = base.getEntries();
for (String item: baseResources) {
byte[] baseBytes = base.getResourceBytes(item);
// First check that every item in the base image exist in
// the optimized image as well. If it does not, it's a removed
// item in the optimized image.
if (!resources.remove(item)) {
// keep track of original bytes for removed item in the
// optimized image, since we need to restore them for the
// runtime image link
ResourceDiff.Builder builder = new ResourceDiff.Builder();
ResourceDiff diff = builder.setKind(ResourceDiff.Kind.REMOVED)
.setName(item)
.setResourceBytes(baseBytes)
.build();
diffs.add(diff);
continue;
}
// Verify resource bytes are equal if present in both images
boolean contentEquals = Arrays.equals(baseBytes, image.getResourceBytes(item));
if (!contentEquals) {
// keep track of original bytes (non-optimized)
ResourceDiff.Builder builder = new ResourceDiff.Builder();
ResourceDiff diff = builder.setKind(ResourceDiff.Kind.MODIFIED)
.setName(item)
.setResourceBytes(baseBytes)
.build();
diffs.add(diff);
}
}
}
// What's now left in the set are the resources only present in the
// optimized image (generated by some plugins; not present in jmods)
for (String e: resources) {
ResourceDiff.Builder builder = new ResourceDiff.Builder();
ResourceDiff diff = builder.setKind(ResourceDiff.Kind.ADDED)
.setName(e)
.build();
diffs.add(diff);
}
return diffs;
}
}

View File

@ -0,0 +1,286 @@
/*
* Copyright (c) 2024, Red Hat, Inc.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package jdk.tools.jlink.internal.runtimelink;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
/**
* Class representing a difference of a jimage resource. For all intents
* and purposes this represents a difference between a resource in an optimized
* jimage (e.g. images/jdk/lib/modules) and the underlying basic resources from
* which the optimized image got derived from (e.g. packaged modules). The
* differences are being used in JRTArchive so as to back-track from an optimized
* jimage to the original (i.e. it restores original resources using the diff).
*/
public class ResourceDiff implements Comparable<ResourceDiff> {
private static final int MAGIC = 0xabba;
public static enum Kind {
ADDED((short)1), // Resource added
REMOVED((short)2), // Resource removed
MODIFIED((short)3); // Resource modified
private short value;
private Kind(short value) {
this.value = value;
}
public short value() {
return value;
}
static Kind fromShort(short v) {
if (v > 3 || v < 1) {
throw new IllegalArgumentException("Must be within range [1-3]");
}
switch (v) {
case 1: return ADDED;
case 2: return REMOVED;
case 3: return MODIFIED;
}
throw new AssertionError("Must not reach here!");
}
}
private final Kind kind;
private final byte[] resourceBytes;
private final String name;
private ResourceDiff(Kind kind, String name, byte[] resourceBytes) {
this.kind = kind;
this.name = name;
if ((kind == Kind.REMOVED || kind == Kind.MODIFIED) &&
resourceBytes == null) {
throw new AssertionError("Resource bytes must be set for REMOVED or MODIFIED");
}
this.resourceBytes = resourceBytes;
}
public Kind getKind() {
return kind;
}
public byte[] getResourceBytes() {
return resourceBytes;
}
public String getName() {
return name;
}
@Override
public int compareTo(ResourceDiff o) {
int kindComp = kind.value() - o.kind.value();
if (kindComp == 0) {
return getName().compareTo(o.getName());
} else {
return kindComp;
}
}
public static class Builder {
private Kind kind;
private String name;
private byte[] resourceBytes;
public Builder setKind(Kind kind) {
this.kind = kind;
return this;
}
public Builder setName(String name) {
this.name = Objects.requireNonNull(name);
return this;
}
public Builder setResourceBytes(byte[] resourceBytes) {
this.resourceBytes = Objects.requireNonNull(resourceBytes);
return this;
}
public ResourceDiff build() {
if (kind == null || name == null) {
throw new AssertionError("kind and name must be set");
}
switch (kind) {
case ADDED:
{
break; // null bytes for added is OK.
}
case MODIFIED: // fall-through
case REMOVED:
{
if (resourceBytes == null) {
throw new AssertionError("Original bytes needed for MODIFIED, REMOVED!");
}
break;
}
default:
break;
}
return new ResourceDiff(kind, name, resourceBytes);
}
}
/**
* Writes a list of resource diffs to an output stream
*
* @param diffs The list of resource diffs to write.
* @param out The stream to write the serialized bytes to.
*/
public static void write(List<ResourceDiff> diffs, OutputStream out) throws IOException {
/*
* Simple binary format:
*
* <header>|<items>
*
* ****************************************
* HEADER info
* ****************************************
*
* where <header> is ('|' separation for clarity):
*
* <int>|<int>
*
* The first integer is the MAGIC, 0xabba. The second integer is the
* total number of items.
*
* *****************************************
* ITEMS info
* *****************************************
*
* Each <item> consists of ('|' separation for clarity):
*
* <short>|<int>|<name-bytes-utf>|<int>|<resource-bytes>
*
* Where the individual items are:
*
* <short>:
* The value of the respective ResourceDiff.Kind.
* <int>:
* The length of the name bytes (in UTF-8).
* <name-bytes-utf>:
* The resource name bytes in UTF-8.
* <int>:
* The length of the resource bytes. 0 (zero) if no resource bytes.
* A.k.a 'null'.
* <resource-bytes>:
* The bytes of the resource as stored in the jmod files.
*/
try (DataOutputStream dataOut = new DataOutputStream(out)) {
dataOut.writeInt(MAGIC);
dataOut.writeInt(diffs.size());
for (ResourceDiff d: diffs) {
dataOut.writeShort(d.kind.value());
byte[] buf = d.name.getBytes(StandardCharsets.UTF_8);
dataOut.writeInt(buf.length);
dataOut.write(buf);
buf = d.resourceBytes;
dataOut.writeInt(buf == null ? 0 : buf.length);
if (buf != null) {
dataOut.write(buf);
}
}
}
}
/**
* Read a list of resource diffs from an input stream.
*
* @param in The input stream to read from
* @return The list of resource diffs.
*/
public static List<ResourceDiff> read(InputStream in) throws IOException {
/*
* See write() for the details how this is being written
*/
List<ResourceDiff> diffs = new ArrayList<>();
try (DataInputStream din = new DataInputStream(in)) {
int magic = din.readInt();
if (magic != MAGIC) {
throw new IllegalArgumentException("Not a ResourceDiff data stream!");
}
int numItems = din.readInt();
for (int i = 0; i < numItems; i++) {
Kind k = Kind.fromShort(din.readShort());
int numBytes = din.readInt();
byte[] buf = readBytesFromStream(din, numBytes);
String name = new String(buf, StandardCharsets.UTF_8);
numBytes = din.readInt();
byte[] resBytes = null;
if (numBytes != 0) {
resBytes = readBytesFromStream(din, numBytes);
}
Builder builder = new Builder();
builder.setKind(k)
.setName(name);
if (resBytes != null) {
builder.setResourceBytes(resBytes);
}
diffs.add(builder.build());
}
}
return Collections.unmodifiableList(diffs);
}
private static byte[] readBytesFromStream(DataInputStream din, int numBytes) throws IOException {
byte[] b = new byte[numBytes];
for (int i = 0; i < numBytes; i++) {
int data = din.read();
if (data == -1) {
throw new IOException("Short read!");
}
b[i] = (byte)data;
}
return b;
}
public static void printDiffs(List<ResourceDiff> diffs) {
for (ResourceDiff diff: diffs.stream().sorted().toList()) {
switch (diff.getKind()) {
case ADDED:
System.out.println("Only added in opt: " + diff.getName());
break;
case MODIFIED:
System.out.println("Modified in opt: " + diff.getName());
break;
case REMOVED:
System.out.println("Removed in opt: " + diff.getName());
break;
default:
break;
}
}
}
}

View File

@ -0,0 +1,57 @@
/*
* Copyright (c) 2024, Red Hat, Inc.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package jdk.tools.jlink.internal.runtimelink;
import java.util.List;
import java.util.Objects;
import jdk.tools.jlink.internal.runtimelink.JimageDiffGenerator.ImageResource;
import jdk.tools.jlink.plugin.ResourcePool;
import jdk.tools.jlink.plugin.ResourcePoolEntry;
@SuppressWarnings("try")
public class ResourcePoolReader implements ImageResource {
private final ResourcePool pool;
public ResourcePoolReader(ResourcePool pool) {
this.pool = Objects.requireNonNull(pool);
}
@Override
public void close() throws Exception {
// nothing
}
@Override
public List<String> getEntries() {
return pool.entries().map(ResourcePoolEntry::path).toList();
}
@Override
public byte[] getResourceBytes(String name) {
return pool.findEntry(name).orElseThrow().contentBytes();
}
}

View File

@ -0,0 +1,62 @@
/*
* Copyright (c) 2024, Red Hat, Inc.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package jdk.tools.jlink.internal.runtimelink;
import java.util.Objects;
/**
* Exception thrown when linking from the run-time image
*/
public class RuntimeImageLinkException extends RuntimeException {
private static final long serialVersionUID = -1848914673073119403L;
public static enum Reason {
PATCH_MODULE, /* link exception due to patched module */
MODIFIED_FILE, /* link exception due to modified file */
}
private final String file;
private final Reason reason;
public RuntimeImageLinkException(String file, Reason reason) {
this.file = Objects.requireNonNull(file);
this.reason = Objects.requireNonNull(reason);
}
public String getFile() {
return file;
}
public Reason getReason() {
return reason;
}
@Override
public String getMessage() {
return reason + ", file: " + file;
}
}

View File

@ -1,5 +1,5 @@
# #
# Copyright (c) 2015, 2023, Oracle and/or its affiliates. All rights reserved. # Copyright (c) 2015, 2024, Oracle and/or its affiliates. All rights reserved.
# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
# #
# This code is free software; you can redistribute it and/or modify it # This code is free software; you can redistribute it and/or modify it
@ -111,10 +111,22 @@ main.extended.help.footer=\
\ used, one pattern per line\n\ \ used, one pattern per line\n\
\n\ \n\
main.runtime.image.linking.cap.enabled=enabled
main.runtime.image.linking.cap.disabled=disabled
main.runtime.image.linking.cap.sect.header=Capabilities:
main.runtime.image.linking.cap.msg=\ Linking from run-time image {0}
error.prefix=Error: error.prefix=Error:
warn.prefix=Warning: warn.prefix=Warning:
err.runtime.link.not.linkable.runtime=This JDK does not support linking from the current run-time image
err.runtime.link.jdk.jlink.prohibited=This JDK does not contain packaged modules\
\ and cannot be used to create another image with the jdk.jlink module
err.runtime.link.packaged.mods=This JDK has no packaged modules.\
\ --keep-packaged-modules is not supported
err.runtime.link.modified.file={0} has been modified
err.runtime.link.patched.module=File {0} not found in the modules image.\
\ --patch-module is not supported when linking from the run-time image
err.empty.module.path=empty module path err.empty.module.path=empty module path
err.jlink.version.mismatch=jlink version {0}.{1} does not match target java.base version {2}.{3} err.jlink.version.mismatch=jlink version {0}.{1} does not match target java.base version {2}.{3}
err.automatic.module:automatic module cannot be used with jlink: {0} from {1} err.automatic.module:automatic module cannot be used with jlink: {0} from {1}
@ -123,7 +135,7 @@ err.launcher.main.class.empty:launcher main class name cannot be empty: {0}
err.launcher.module.name.empty:launcher module name cannot be empty: {0} err.launcher.module.name.empty:launcher module name cannot be empty: {0}
err.launcher.value.format:launcher value should be of form <command>=<module>[/<main-class>]: {0} err.launcher.value.format:launcher value should be of form <command>=<module>[/<main-class>]: {0}
err.output.must.be.specified:--output must be specified err.output.must.be.specified:--output must be specified
err.modulepath.must.be.specified:--module-path is not specified and this runtime image does not contain jmods directory. err.modulepath.must.be.specified:--module-path is not specified and this run-time image does not contain a jmods directory
err.mods.must.be.specified:no modules specified to {0} err.mods.must.be.specified:no modules specified to {0}
err.path.not.found=path not found: {0} err.path.not.found=path not found: {0}
err.path.not.valid=invalid path: {0} err.path.not.valid=invalid path: {0}
@ -157,3 +169,6 @@ warn.provider.notfound=No provider found for service specified to --suggest-prov
no.suggested.providers=--bind-services option is specified. No additional providers suggested. no.suggested.providers=--bind-services option is specified. No additional providers suggested.
suggested.providers.header=Suggested providers suggested.providers.header=Suggested providers
providers.header=Providers providers.header=Providers
runtime.link.info=Linking based on the current run-time image
runtime.link.jprt.path.extra=(run-time image)

View File

@ -86,7 +86,9 @@ requires.properties= \
vm.flagless \ vm.flagless \
container.support \ container.support \
systemd.support \ systemd.support \
jdk.containerized jdk.containerized \
jlink.runtime.linkable \
jlink.packagedModules
# Minimum jtreg version # Minimum jtreg version
requiredVersion=7.4+1 requiredVersion=7.4+1

View File

@ -102,7 +102,9 @@ requires.properties= \
systemd.support \ systemd.support \
release.implementor \ release.implementor \
jdk.containerized \ jdk.containerized \
jdk.foreign.linker jdk.foreign.linker \
jlink.runtime.linkable \
jlink.packagedModules
# Minimum jtreg version # Minimum jtreg version
requiredVersion=7.4+1 requiredVersion=7.4+1

View File

@ -25,6 +25,7 @@
* @test * @test
* @bug 8159927 * @bug 8159927
* @modules java.base/jdk.internal.util * @modules java.base/jdk.internal.util
* @requires jlink.packagedModules
* @run main JmodExcludedFiles * @run main JmodExcludedFiles
* @summary Test that JDK JMOD files do not include native debug symbols * @summary Test that JDK JMOD files do not include native debug symbols
*/ */

View File

@ -34,11 +34,12 @@ import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.stream.Stream; import java.util.stream.Stream;
import jdk.tools.jlink.builder.ImageBuilder;
import jdk.tools.jlink.internal.Archive; import jdk.tools.jlink.internal.Archive;
import jdk.tools.jlink.internal.ExecutableImage;
import jdk.tools.jlink.internal.ImageFileCreator; import jdk.tools.jlink.internal.ImageFileCreator;
import jdk.tools.jlink.internal.ImagePluginStack; import jdk.tools.jlink.internal.ImagePluginStack;
import jdk.tools.jlink.internal.ExecutableImage;
import jdk.tools.jlink.builder.ImageBuilder;
import jdk.tools.jlink.plugin.ResourcePool; import jdk.tools.jlink.plugin.ResourcePool;
@ -223,6 +224,6 @@ public class ImageFileCreatorTest {
ImagePluginStack stack = new ImagePluginStack(noopBuilder, Collections.emptyList(), ImagePluginStack stack = new ImagePluginStack(noopBuilder, Collections.emptyList(),
null, false); null, false);
ImageFileCreator.create(archives, ByteOrder.nativeOrder(), stack); ImageFileCreator.create(archives, ByteOrder.nativeOrder(), stack, false, null);
} }
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2015, 2023, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2015, 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -25,7 +25,6 @@ import java.io.File;
import java.io.FileReader; import java.io.FileReader;
import java.io.IOException; import java.io.IOException;
import java.io.UncheckedIOException; import java.io.UncheckedIOException;
import java.nio.ByteOrder;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
@ -38,20 +37,18 @@ import java.util.Map;
import java.util.Properties; import java.util.Properties;
import java.util.Set; import java.util.Set;
import java.util.function.Function; import java.util.function.Function;
import jdk.tools.jlink.internal.Jlink;
import jdk.tools.jlink.internal.JlinkTask;
import jdk.tools.jlink.builder.DefaultImageBuilder; import jdk.tools.jlink.builder.DefaultImageBuilder;
import jdk.tools.jlink.internal.Platform;
import jdk.tools.jlink.plugin.ResourcePool;
import jdk.tools.jlink.plugin.ResourcePoolBuilder;
import jdk.tools.jlink.plugin.Plugin;
import jdk.tools.jlink.internal.ExecutableImage; import jdk.tools.jlink.internal.ExecutableImage;
import jdk.tools.jlink.internal.Jlink;
import jdk.tools.jlink.internal.Jlink.JlinkConfiguration; import jdk.tools.jlink.internal.Jlink.JlinkConfiguration;
import jdk.tools.jlink.internal.Jlink.PluginsConfiguration; import jdk.tools.jlink.internal.Jlink.PluginsConfiguration;
import jdk.tools.jlink.internal.JlinkTask;
import jdk.tools.jlink.internal.Platform;
import jdk.tools.jlink.internal.PostProcessor; import jdk.tools.jlink.internal.PostProcessor;
import jdk.tools.jlink.internal.plugins.DefaultCompressPlugin; import jdk.tools.jlink.plugin.Plugin;
import jdk.tools.jlink.internal.plugins.DefaultStripDebugPlugin; import jdk.tools.jlink.plugin.ResourcePool;
import jdk.tools.jlink.plugin.ResourcePoolBuilder;
import tests.Helper; import tests.Helper;
import tests.JImageGenerator; import tests.JImageGenerator;
@ -74,8 +71,6 @@ import tests.JImageGenerator;
*/ */
public class IntegrationTest { public class IntegrationTest {
private static final List<Integer> ordered = new ArrayList<>();
public static class MyPostProcessor implements PostProcessor, Plugin { public static class MyPostProcessor implements PostProcessor, Plugin {
public static final String NAME = "mypostprocessor"; public static final String NAME = "mypostprocessor";
@ -162,7 +157,7 @@ public class IntegrationTest {
limits.add("java.management"); limits.add("java.management");
JlinkConfiguration config = new Jlink.JlinkConfiguration(output, JlinkConfiguration config = new Jlink.JlinkConfiguration(output,
mods, mods,
JlinkTask.newModuleFinder(modulePaths, limits, mods)); JlinkTask.newModuleFinder(modulePaths, limits, mods), false, false, false);
List<Plugin> lst = new ArrayList<>(); List<Plugin> lst = new ArrayList<>();

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -21,14 +21,16 @@
* questions. * questions.
*/ */
import jdk.test.lib.compiler.CompilerUtils;
import tests.JImageGenerator;
import java.io.File; import java.io.File;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import jdk.test.lib.compiler.CompilerUtils;
import jdk.tools.jlink.internal.LinkableRuntimeImage;
import tests.JImageGenerator;
/* /*
* @test * @test
* @summary Make sure that modules can be linked using jlink * @summary Make sure that modules can be linked using jlink
@ -54,10 +56,6 @@ public class JLinkDedupTestBatchSizeOne {
private static final Path SRC_DIR = Paths.get(TEST_SRC, "dedup", "src"); private static final Path SRC_DIR = Paths.get(TEST_SRC, "dedup", "src");
private static final Path MODS_DIR = Paths.get("mods"); private static final Path MODS_DIR = Paths.get("mods");
private static final String MODULE_PATH =
Paths.get(JAVA_HOME, "jmods").toString() +
File.pathSeparator + MODS_DIR.toString();
// the names of the modules in this test // the names of the modules in this test
private static String[] modules = new String[]{"m1", "m2", "m3", "m4"}; private static String[] modules = new String[]{"m1", "m2", "m3", "m4"};
@ -69,8 +67,13 @@ public class JLinkDedupTestBatchSizeOne {
return true; return true;
} }
public static void compileAll() throws Throwable { private static String modulePath(boolean linkableRuntime) {
if (!hasJmods()) return; return (linkableRuntime ? "" : (Paths.get(JAVA_HOME, "jmods").toString() +
File.pathSeparator)) + MODS_DIR.toString();
}
public static void compileAll(boolean linkableRuntime) throws Throwable {
if (!linkableRuntime && !hasJmods()) return;
for (String mn : modules) { for (String mn : modules) {
Path msrc = SRC_DIR.resolve(mn); Path msrc = SRC_DIR.resolve(mn);
@ -80,11 +83,15 @@ public class JLinkDedupTestBatchSizeOne {
} }
public static void main(String[] args) throws Throwable { public static void main(String[] args) throws Throwable {
compileAll(); boolean linkableRuntime = LinkableRuntimeImage.isLinkableRuntime();
System.out.println("Running test on " +
(linkableRuntime ? "enabled" : "disabled") +
" capability of linking from the run-time image.");
compileAll(linkableRuntime);
Path image = Paths.get("bug8311591"); Path image = Paths.get("bug8311591");
JImageGenerator.getJLinkTask() JImageGenerator.getJLinkTask()
.modulePath(MODULE_PATH) .modulePath(modulePath(linkableRuntime))
.output(image.resolve("out-jlink-dedup")) .output(image.resolve("out-jlink-dedup"))
.addMods("m1") .addMods("m1")
.addMods("m2") .addMods("m2")

View File

@ -0,0 +1,78 @@
/*
* Copyright (c) 2024, Red Hat, Inc.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.spi.ToolProvider;
import jdk.tools.jlink.internal.LinkableRuntimeImage;
/*
* @test
* @summary Test jlink --help for capability output
* @modules jdk.jlink/jdk.tools.jlink.internal
* @requires vm.compMode != "Xcomp"
* @run main/othervm -Duser.language=en JLinkHelpCapabilityTest
*/
public class JLinkHelpCapabilityTest {
static final ToolProvider JLINK_TOOL = ToolProvider.findFirst("jlink")
.orElseThrow(() ->
new RuntimeException("jlink tool not found")
);
public static void main(String[] args) throws Exception {
boolean runtimeLinkCap = LinkableRuntimeImage.isLinkableRuntime();
String capabilities = String.format("Linking from run-time image %s",
runtimeLinkCap ? "enabled" : "disabled");
{
// Verify capability in --help output
StringWriter writer = new StringWriter();
PrintWriter pw = new PrintWriter(writer);
JLINK_TOOL.run(pw, pw, "--help");
String output = writer.toString().trim();
String lines[] = output.split("\n");
String capabilitiesMsg = null;
boolean seenCap = false;
for (int i = 0; i < lines.length; i++) {
if (lines[i].startsWith("Capabilities:")) {
seenCap = true;
continue; // skip 'Capabilities:'
}
if (!seenCap) {
continue;
} else {
// Line after capabilities is the message we care about
capabilitiesMsg = lines[i].trim();
break;
}
}
System.out.println("DEBUG: Capabilities:");
System.out.println("DEBUG: " + capabilitiesMsg);
if (!capabilities.equals(capabilitiesMsg)) {
System.err.println(output);
throw new AssertionError("'--help': Capabilities mismatch. Expected: '" +
capabilities +"' but got '" + capabilitiesMsg + "'");
}
}
}
}

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2017, 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -27,6 +27,7 @@
* @summary jlink should use the version from java.base.jmod to find modules * @summary jlink should use the version from java.base.jmod to find modules
* @bug 8185130 * @bug 8185130
* @summary jlink should throw error if target image and current JDK versions don't match * @summary jlink should throw error if target image and current JDK versions don't match
* @requires jlink.packagedModules
* @modules java.base/jdk.internal.module * @modules java.base/jdk.internal.module
* @library /test/lib * @library /test/lib
* @build jdk.test.lib.process.* CheckRuntimeVersion * @build jdk.test.lib.process.* CheckRuntimeVersion

View File

@ -21,6 +21,9 @@
* questions. * questions.
*/ */
import static java.lang.constant.ConstantDescs.CD_Object;
import static java.lang.constant.ConstantDescs.CD_int;
import java.io.IOException; import java.io.IOException;
import java.lang.classfile.ClassFile; import java.lang.classfile.ClassFile;
import java.lang.constant.MethodTypeDesc; import java.lang.constant.MethodTypeDesc;
@ -32,16 +35,15 @@ import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.testng.Assert; import org.testng.Assert;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.Test;
import jdk.tools.jlink.internal.LinkableRuntimeImage;
import tests.Helper; import tests.Helper;
import tests.JImageGenerator; import tests.JImageGenerator;
import tests.JImageValidator; import tests.JImageValidator;
import tests.Result; import tests.Result;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.Test;
import static java.lang.constant.ConstantDescs.CD_Object;
import static java.lang.constant.ConstantDescs.CD_int;
/* /*
* @test * @test
@ -63,12 +65,20 @@ public class GenerateJLIClassesPluginTest {
@BeforeTest @BeforeTest
public static void setup() throws Exception { public static void setup() throws Exception {
helper = Helper.newHelper(); boolean isLinkableRuntime = LinkableRuntimeImage.isLinkableRuntime();
System.out.println("DEBUG: Tests run on " +
(isLinkableRuntime ? "enabled" : "disabled") +
" capability of linking from the run-time image.");
System.out.println("DEBUG: default module-path, 'jmods', " +
(Helper.jdkHasPackagedModules() ? "" : "NOT ") +
"present.");
helper = Helper.newHelper(isLinkableRuntime);
if (helper == null) { if (helper == null) {
// In case of no linkable run-time image and also no packaged
// modules, helper will be null.
System.err.println("Test not run"); System.err.println("Test not run");
return; return;
} }
helper.generateDefaultModules();
} }
@Test @Test
@ -79,7 +89,6 @@ public class GenerateJLIClassesPluginTest {
String fileString = "[SPECIES_RESOLVE] java.lang.invoke.BoundMethodHandle$Species_" + species + " (salvaged)\n"; String fileString = "[SPECIES_RESOLVE] java.lang.invoke.BoundMethodHandle$Species_" + species + " (salvaged)\n";
Files.write(baseFile, fileString.getBytes(Charset.defaultCharset())); Files.write(baseFile, fileString.getBytes(Charset.defaultCharset()));
Result result = JImageGenerator.getJLinkTask() Result result = JImageGenerator.getJLinkTask()
.modulePath(helper.defaultModulePath())
.output(helper.createNewImageDir("generate-jli-file")) .output(helper.createNewImageDir("generate-jli-file"))
.option("--generate-jli-classes=@" + baseFile.toString()) .option("--generate-jli-classes=@" + baseFile.toString())
.addMods("java.base") .addMods("java.base")
@ -105,7 +114,6 @@ public class GenerateJLIClassesPluginTest {
fileString = "[LF_RESOLVE] java.lang.invoke.DirectMethodHandle$Holder invokeVirtual L_L (success)\n"; fileString = "[LF_RESOLVE] java.lang.invoke.DirectMethodHandle$Holder invokeVirtual L_L (success)\n";
Files.write(failFile, fileString.getBytes(Charset.defaultCharset())); Files.write(failFile, fileString.getBytes(Charset.defaultCharset()));
Result result = JImageGenerator.getJLinkTask() Result result = JImageGenerator.getJLinkTask()
.modulePath(helper.defaultModulePath())
.output(helper.createNewImageDir("invalid-signature")) .output(helper.createNewImageDir("invalid-signature"))
.option("--generate-jli-classes=@" + failFile.toString()) .option("--generate-jli-classes=@" + failFile.toString())
.addMods("java.base") .addMods("java.base")
@ -118,7 +126,6 @@ public class GenerateJLIClassesPluginTest {
@Test @Test
public static void nonExistentTraceFile() throws IOException { public static void nonExistentTraceFile() throws IOException {
Result result = JImageGenerator.getJLinkTask() Result result = JImageGenerator.getJLinkTask()
.modulePath(helper.defaultModulePath())
.output(helper.createNewImageDir("non-existent-tracefile")) .output(helper.createNewImageDir("non-existent-tracefile"))
.option("--generate-jli-classes=@NON_EXISTENT_FILE") .option("--generate-jli-classes=@NON_EXISTENT_FILE")
.addMods("java.base") .addMods("java.base")
@ -134,7 +141,6 @@ public class GenerateJLIClassesPluginTest {
Path invokersTrace = Files.createTempFile("invokers", "trace"); Path invokersTrace = Files.createTempFile("invokers", "trace");
Files.writeString(invokersTrace, fileString, Charset.defaultCharset()); Files.writeString(invokersTrace, fileString, Charset.defaultCharset());
Result result = JImageGenerator.getJLinkTask() Result result = JImageGenerator.getJLinkTask()
.modulePath(helper.defaultModulePath())
.output(helper.createNewImageDir("jli-invokers")) .output(helper.createNewImageDir("jli-invokers"))
.option("--generate-jli-classes=@" + invokersTrace.toString()) .option("--generate-jli-classes=@" + invokersTrace.toString())
.addMods("java.base") .addMods("java.base")
@ -183,4 +189,5 @@ public class GenerateJLIClassesPluginTest {
.map(s -> "/java.base/java/lang/invoke/BoundMethodHandle$Species_" + s + ".class") .map(s -> "/java.base/java/lang/invoke/BoundMethodHandle$Species_" + s + ".class")
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
} }

View File

@ -22,25 +22,27 @@
*/ */
import java.nio.file.Path; import java.nio.file.Path;
import java.util.Arrays;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import jdk.tools.jlink.plugin.PluginException; import jdk.tools.jlink.internal.LinkableRuntimeImage;
import jdk.tools.jlink.internal.TaskHelper; import jdk.tools.jlink.internal.TaskHelper;
import jdk.tools.jlink.internal.plugins.PluginsResourceBundle; import jdk.tools.jlink.internal.plugins.PluginsResourceBundle;
import jdk.tools.jlink.plugin.PluginException;
import tests.Helper; import tests.Helper;
import tests.JImageGenerator; import tests.JImageGenerator;
import tests.JImageValidator; import tests.JImageValidator;
import tests.Result; import tests.Result;
/* /*
* @test * @test
* @bug 8152143 8152704 8155649 8165804 8185841 8176841 8190918 * @bug 8152143 8152704 8155649 8165804 8185841 8176841 8190918
* 8179071 8202537 8221432 8222098 8251317 8258794 8265315 * 8179071 8202537 8221432 8222098 8251317 8258794 8265315
* 8296248 8306116 8174269 8333582 * 8296248 8306116 8174269
* @summary IncludeLocalesPlugin tests * @summary IncludeLocalesPlugin tests
* @author Naoto Sato * @author Naoto Sato
* @requires (vm.compMode != "Xcomp" & os.maxMemory >= 2g) * @requires (vm.compMode != "Xcomp" & os.maxMemory >= 2g)
@ -59,14 +61,14 @@ import tests.Result;
*/ */
public class IncludeLocalesPluginTest { public class IncludeLocalesPluginTest {
private final static String moduleName = "IncludeLocalesTest"; private static final String moduleName = "IncludeLocalesTest";
private static Helper helper; private static Helper helper;
private final static int INCLUDE_LOCALES_OPTION = 0; private static final int INCLUDE_LOCALES_OPTION = 0;
private final static int ADDMODS_OPTION = 1; private static final int ADDMODS_OPTION = 1;
private final static int EXPECTED_LOCATIONS = 2; private static final int EXPECTED_LOCATIONS = 2;
private final static int UNEXPECTED_PATHS = 3; private static final int UNEXPECTED_PATHS = 3;
private final static int AVAILABLE_LOCALES = 4; private static final int AVAILABLE_LOCALES = 4;
private final static int ERROR_MESSAGE = 5; private static final int ERROR_MESSAGE = 5;
private static int errors; private static int errors;
@ -413,11 +415,18 @@ public class IncludeLocalesPluginTest {
}; };
public static void main(String[] args) throws Exception { public static void main(String[] args) throws Exception {
helper = Helper.newHelper(); boolean isLinkableRuntime = LinkableRuntimeImage.isLinkableRuntime();
System.out.println("Running test on " +
(isLinkableRuntime ? "enabled" : "disabled") +
" capability of linking from the run-time image.");
System.out.println("Default module-path, 'jmods', " +
(Helper.jdkHasPackagedModules() ? "" : "NOT ") +
"present.");
helper = Helper.newHelper(isLinkableRuntime);
if (helper == null) { if (helper == null) {
throw new RuntimeException("Helper could not be initialized"); throw new RuntimeException("Helper could not be initialized");
} }
helper.generateDefaultModules();
for (Object[] data : testData) { for (Object[] data : testData) {
// create image for each test data // create image for each test data
@ -425,14 +434,12 @@ public class IncludeLocalesPluginTest {
if (data[INCLUDE_LOCALES_OPTION].toString().isEmpty()) { if (data[INCLUDE_LOCALES_OPTION].toString().isEmpty()) {
System.out.println("Invoking jlink with no --include-locales option"); System.out.println("Invoking jlink with no --include-locales option");
result = JImageGenerator.getJLinkTask() result = JImageGenerator.getJLinkTask()
.modulePath(helper.defaultModulePath())
.output(helper.createNewImageDir(moduleName)) .output(helper.createNewImageDir(moduleName))
.addMods((String) data[ADDMODS_OPTION]) .addMods((String) data[ADDMODS_OPTION])
.call(); .call();
} else { } else {
System.out.println("Invoking jlink with \"" + data[INCLUDE_LOCALES_OPTION] + "\""); System.out.println("Invoking jlink with \"" + data[INCLUDE_LOCALES_OPTION] + "\"");
result = JImageGenerator.getJLinkTask() result = JImageGenerator.getJLinkTask()
.modulePath(helper.defaultModulePath())
.output(helper.createNewImageDir(moduleName)) .output(helper.createNewImageDir(moduleName))
.addMods((String) data[ADDMODS_OPTION]) .addMods((String) data[ADDMODS_OPTION])
.option((String) data[INCLUDE_LOCALES_OPTION]) .option((String) data[INCLUDE_LOCALES_OPTION])

View File

@ -0,0 +1,705 @@
/*
* Copyright (c) 2024, Red Hat, Inc.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.FileVisitor;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.StandardCopyOption;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Scanner;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import jdk.test.lib.process.OutputAnalyzer;
import jdk.test.lib.process.ProcessTools;
import jdk.tools.jlink.internal.LinkableRuntimeImage;
import tests.Helper;
import tests.JImageGenerator;
import tests.JImageGenerator.JLinkTask;
import tests.JImageValidator;
public abstract class AbstractLinkableRuntimeTest {
protected static final boolean DEBUG = true;
public void run() throws Exception {
boolean isLinkableRuntime = LinkableRuntimeImage.isLinkableRuntime();
Helper helper = Helper.newHelper(isLinkableRuntime);
if (helper == null) {
System.err.println(AbstractLinkableRuntimeTest.class.getSimpleName() +
": Test not run");
return;
}
runTest(helper, isLinkableRuntime);
System.out.println(getClass().getSimpleName() + " PASSED!");
}
/**
* Main test entry point that actual tests ought to override.
*
* @param helper The jlink helper
* @param isLinkableRuntime {@code true} iff the JDK build under test already
* includes the linkable runtime capability in jlink.
* @throws Exception
*/
abstract void runTest(Helper helper, boolean isLinkableRuntime) throws Exception;
/**
* Ensure 'java --list-modules' lists the correct set of modules in the given
* image.
*
* @param jlinkImage
* @param expectedModules
*/
protected void verifyListModules(Path image,
List<String> expectedModules) throws Exception {
OutputAnalyzer out = runJavaCmd(image, List.of("--list-modules"));
List<String> actual = parseListMods(out.getStdout());
Collections.sort(actual);
if (!expectedModules.equals(actual)) {
throw new AssertionError("Different modules! Expected " + expectedModules + " got: " + actual);
}
}
protected OutputAnalyzer runJavaCmd(Path image, List<String> options) throws Exception {
Path targetJava = image.resolve("bin").resolve(getJava());
List<String> cmd = new ArrayList<>();
cmd.add(targetJava.toString());
for (String opt: options) {
cmd.add(opt);
}
List<String> javaCmd = Collections.unmodifiableList(cmd);
OutputAnalyzer out;
try {
out = ProcessTools.executeCommand(javaCmd.toArray(new String[0]));
} catch (Throwable e) {
throw new Exception("Process failed to execute", e);
}
if (out.getExitValue() != 0) {
if (DEBUG) {
System.err.println("Process stdout was: ");
System.err.println(out.getStdout());
System.err.println("Process stderr was: ");
System.err.println(out.getStderr());
}
throw new AssertionError("'" + javaCmd.stream().collect(Collectors.joining(" ")) + "'"
+ " expected to succeed!");
}
return out;
}
protected Path createJavaImageRuntimeLink(BaseJlinkSpec baseSpec) throws Exception {
return createJavaImageRuntimeLink(baseSpec, Collections.emptySet() /* exclude all jmods */);
}
protected Path createJavaImageRuntimeLink(BaseJlinkSpec baseSpec, Set<String> excludedJmods) throws Exception {
// Be sure we have a JDK without JMODs
Path runtimeJlinkImage = createRuntimeLinkImage(baseSpec, excludedJmods);
// On Windows jvm.dll is in 'bin' after the jlink
Path libjvm = Path.of((isWindows() ? "bin" : "lib"), "server", System.mapLibraryName("jvm"));
JlinkSpecBuilder builder = new JlinkSpecBuilder();
// And expect libjvm (not part of the jimage) to be present in the resulting image
builder.expectedFile(libjvm.toString())
.helper(baseSpec.getHelper())
.name(baseSpec.getName())
.validatingModule(baseSpec.getValidatingModule())
.imagePath(runtimeJlinkImage)
.expectedLocation("/java.base/java/lang/String.class");
for (String m: baseSpec.getModules()) {
builder.addModule(m);
}
for (String extra: baseSpec.getExtraOptions()) {
builder.extraJlinkOpt(extra);
}
return jlinkUsingImage(builder.build());
}
protected Path jlinkUsingImage(JlinkSpec spec) throws Exception {
return jlinkUsingImage(spec, new RuntimeLinkOutputAnalyzerHandler());
}
protected Path jlinkUsingImage(JlinkSpec spec, OutputAnalyzerHandler handler) throws Exception {
return jlinkUsingImage(spec, handler, new DefaultSuccessExitPredicate());
}
protected Path jlinkUsingImage(JlinkSpec spec, OutputAnalyzerHandler handler, Predicate<OutputAnalyzer> exitChecker) throws Exception {
String generatedImage = "target-run-time-" + spec.getName();
Path targetImageDir = spec.getHelper().createNewImageDir(generatedImage);
Path targetJlink = spec.getImageToUse().resolve("bin").resolve(getJlink());
String[] jlinkCmdArray = new String[] {
targetJlink.toString(),
"--output", targetImageDir.toString(),
"--verbose",
"--add-modules", spec.getModules().stream().collect(Collectors.joining(","))
};
List<String> jlinkCmd = new ArrayList<>();
jlinkCmd.addAll(Arrays.asList(jlinkCmdArray));
if (spec.getExtraJlinkOpts() != null && !spec.getExtraJlinkOpts().isEmpty()) {
jlinkCmd.addAll(spec.getExtraJlinkOpts());
}
if (spec.getModulePath() != null) {
for (String mp: spec.getModulePath()) {
jlinkCmd.add("--module-path");
jlinkCmd.add(mp);
}
}
jlinkCmd = Collections.unmodifiableList(jlinkCmd); // freeze
System.out.println("DEBUG: run-time image based jlink command: " +
jlinkCmd.stream().collect(Collectors.joining(" ")));
OutputAnalyzer analyzer = null;
try {
analyzer = ProcessTools.executeProcess(jlinkCmd.toArray(new String[0]));
} catch (Throwable t) {
throw new AssertionError("Executing process failed!", t);
}
if (!exitChecker.test(analyzer)) {
if (DEBUG) {
System.err.println("Process stdout was: ");
System.err.println(analyzer.getStdout());
System.err.println("Process stderr was: ");
System.err.println(analyzer.getStderr());
}
// if the exit checker failed, we expected the other outcome
// i.e. fail for success and success for fail.
boolean successExit = analyzer.getExitValue() == 0;
String msg = String.format("Expected jlink to %s given a jmodless image. Exit code was: %d",
(successExit ? "fail" : "pass"), analyzer.getExitValue());
throw new AssertionError(msg);
}
handler.handleAnalyzer(analyzer); // Give tests a chance to process in/output
// validate the resulting image; Includes running 'java -version', only do this
// if the jlink succeeded.
if (analyzer.getExitValue() == 0) {
JImageValidator validator = new JImageValidator(spec.getValidatingModule(), spec.getExpectedLocations(),
targetImageDir.toFile(), spec.getUnexpectedLocations(), Collections.emptyList(), spec.getExpectedFiles());
validator.validate(); // This doesn't validate locations
if (!spec.getExpectedLocations().isEmpty() || !spec.getUnexpectedLocations().isEmpty()) {
JImageValidator.validate(targetImageDir.resolve("lib").resolve("modules"), spec.getExpectedLocations(), spec.getUnexpectedLocations());
}
}
return targetImageDir;
}
/**
* Prepares the test for execution. This assumes the current runtime
* supports linking from it. However, since the 'jmods' dir might be present
* (default jmods module path), the 'jmods' directory needs to get removed
* to provoke actual linking from the run-time image.
*
* @param baseSpec
* @return A path to a JDK that is capable for linking from the run-time
* image.
* @throws Exception
*/
protected Path createRuntimeLinkImage(BaseJlinkSpec baseSpec) throws Exception {
return createRuntimeLinkImage(baseSpec, Collections.emptySet() /* exclude all jmods */);
}
/**
* Prepares the test for execution. Creates a JDK with a jlink that has the
* capability to link from the run-time image (if needed). It further
* ensures that if packaged modules ('jmods' dir) are present, to remove
* them entirely or as specified in the {@link excludedJmodFiles} set. If
* that set is empty, all packaged modules will be removed. Note that with
* packaged modules present no run-time image based linking would be done.
*
* @param baseSpec
* The specification for the custom - run-time image link capable
* - JDK to create via jlink (if any)
* @param excludedJmods
* The set of jmod files to exclude in the base JDK. Empty set if
* all JMODs should be removed.
* @return A path to a JDK, including jdk.jlink, that has the run-time image
* link capability.
*
* @throws Exception
*/
protected Path createRuntimeLinkImage(BaseJlinkSpec baseSpec,
Set<String> excludedJmodFiles) throws Exception {
// Depending on the shape of the JDK under test, we either only filter
// jmod files or create a run-time image link capable JDK on-the-fly.
Path from = null;
Path runtimeJlinkImage = null;
String finalName = baseSpec.getName() + "-jlink";
if (baseSpec.isLinkableRuntime()) {
// The build is already run-time image link capable
String javaHome = System.getProperty("java.home");
from = Path.of(javaHome);
} else {
// Create a run-time image capable JDK using --generate-linkable-runtime
Path tempRuntimeImage = Path.of(finalName + "-tmp");
JLinkTask task = JImageGenerator.getJLinkTask();
task.output(tempRuntimeImage)
.addMods("jdk.jlink") // jdk.jlink module is always needed for the test
.option("--generate-linkable-runtime");
if (baseJDKhasPackagedModules()) {
Path jmodsPath = tempRuntimeImage.resolve("jmods");
task.option("--keep-packaged-modules=" + jmodsPath);
}
for (String module: baseSpec.getModules()) {
task.addMods(module);
}
task.call().assertSuccess();
from = tempRuntimeImage;
}
// Create the target directory
runtimeJlinkImage = baseSpec.getHelper().createNewImageDir(finalName);
// Remove JMODs as needed for the test
copyJDKTreeWithoutSpecificJmods(from, runtimeJlinkImage, excludedJmodFiles);
// Verify the base image is actually without desired packaged modules
if (excludedJmodFiles.isEmpty()) {
if (Files.exists(runtimeJlinkImage.resolve("jmods"))) {
throw new AssertionError("Must not contain 'jmods' directory");
}
} else {
Path basePath = runtimeJlinkImage.resolve("jmods");
for (String jmodFile: excludedJmodFiles) {
Path unexpectedFile = basePath.resolve(Path.of(jmodFile));
if (Files.exists(unexpectedFile)) {
throw new AssertionError("Must not contain jmod: " + unexpectedFile);
}
}
}
return runtimeJlinkImage;
}
private boolean baseJDKhasPackagedModules() {
Path jmodsPath = Path.of(System.getProperty("java.home"), "jmods");
return jmodsPath.toFile().exists();
}
private void copyJDKTreeWithoutSpecificJmods(Path from,
Path to,
Set<String> excludedJmods) throws Exception {
if (Files.exists(to)) {
throw new AssertionError("Expected target dir '" + to + "' to exist");
}
FileVisitor<Path> fileVisitor = null;
if (excludedJmods.isEmpty()) {
fileVisitor = new ExcludeAllJmodsFileVisitor(from, to);
} else {
fileVisitor = new FileExcludingFileVisitor(excludedJmods,
from,
to);
}
Files.walkFileTree(from, fileVisitor);
}
private List<String> parseListMods(String output) throws Exception {
List<String> outputLines = new ArrayList<>();
try (Scanner lineScan = new Scanner(output)) {
while (lineScan.hasNextLine()) {
outputLines.add(lineScan.nextLine());
}
}
return outputLines.stream()
.map(a -> { return a.split("@", 2)[0];})
.filter(a -> !a.isBlank())
.collect(Collectors.toList());
}
private String getJlink() {
return getBinary("jlink");
}
private String getJava() {
return getBinary("java");
}
private String getBinary(String binary) {
return isWindows() ? binary + ".exe" : binary;
}
protected static boolean isWindows() {
return System.getProperty("os.name").startsWith("Windows");
}
static class ExcludeAllJmodsFileVisitor extends SimpleFileVisitor<Path> {
private final Path root;
private final Path destination;
private ExcludeAllJmodsFileVisitor(Path root,
Path destination) {
this.destination = destination;
this.root = root;
}
@Override
public FileVisitResult preVisitDirectory(Path dir,
BasicFileAttributes attrs) throws IOException {
Objects.requireNonNull(dir);
Path relative = root.relativize(dir);
if (relative.getFileName().equals(Path.of("jmods"))) {
return FileVisitResult.SKIP_SUBTREE;
}
// Create dir in destination location
Path targetDir = destination.resolve(relative);
if (!Files.exists(targetDir)) {
Files.createDirectory(targetDir);
}
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
throws IOException {
Path relative = root.relativize(file);
Files.copy(file, destination.resolve(relative), StandardCopyOption.REPLACE_EXISTING);
return FileVisitResult.CONTINUE;
}
}
static class FileExcludingFileVisitor extends SimpleFileVisitor<Path> {
private final Set<String> filesToExclude;
private final Path root;
private final Path destination;
private FileExcludingFileVisitor(Set<String> filesToExclude,
Path root,
Path destination) {
this.filesToExclude = filesToExclude;
this.destination = destination;
this.root = root;
}
@Override
public FileVisitResult preVisitDirectory(Path dir,
BasicFileAttributes attrs) throws IOException {
Objects.requireNonNull(dir);
Path relative = root.relativize(dir);
// Create dir in destination location
Path targetDir = destination.resolve(relative);
if (!Files.exists(targetDir)) {
Files.createDirectory(targetDir);
}
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
throws IOException {
Path relative = root.relativize(file);
// Skip files as determined by the exclude set
String fileName = file.getFileName().toString();
if (!filesToExclude.contains(fileName)) {
Files.copy(file, destination.resolve(relative), StandardCopyOption.REPLACE_EXISTING);
}
return FileVisitResult.CONTINUE;
}
}
static class BaseJlinkSpec {
final Helper helper;
final String name;
final String validatingModule;
final List<String> modules;
final List<String> extraOptions;
final boolean isLinkableRuntime;
BaseJlinkSpec(Helper helper, String name, String validatingModule,
List<String> modules, List<String> extraOptions, boolean isLinkableRuntime) {
this.helper = helper;
this.name = name;
this.modules = modules;
this.extraOptions = extraOptions;
this.validatingModule = validatingModule;
this.isLinkableRuntime = isLinkableRuntime;
}
public String getValidatingModule() {
return validatingModule;
}
public Helper getHelper() {
return helper;
}
public String getName() {
return name;
}
public List<String> getModules() {
return modules;
}
public List<String> getExtraOptions() {
return extraOptions;
}
public boolean isLinkableRuntime() {
return isLinkableRuntime;
}
}
static class BaseJlinkSpecBuilder {
Helper helper;
String name;
String validatingModule;
List<String> modules = new ArrayList<>();
List<String> extraOptions = new ArrayList<>();
boolean isLinkableRuntime;
BaseJlinkSpecBuilder addModule(String module) {
modules.add(module);
return this;
}
BaseJlinkSpecBuilder addExtraOption(String option) {
extraOptions.add(option);
return this;
}
BaseJlinkSpecBuilder setLinkableRuntime() {
isLinkableRuntime = true;
return this;
}
BaseJlinkSpecBuilder helper(Helper helper) {
this.helper = helper;
return this;
}
BaseJlinkSpecBuilder name(String name) {
this.name = name;
return this;
}
BaseJlinkSpecBuilder validatingModule(String module) {
this.validatingModule = module;
return this;
}
BaseJlinkSpec build() {
if (name == null) {
throw new IllegalStateException("Name must be set");
}
if (helper == null) {
throw new IllegalStateException("helper must be set");
}
if (modules.isEmpty()) {
throw new IllegalStateException("modules must be set");
}
if (validatingModule == null) {
throw new IllegalStateException("the module which should get validated must be set");
}
return new BaseJlinkSpec(helper, name, validatingModule, modules, extraOptions, isLinkableRuntime);
}
}
static class JlinkSpec {
final Path imageToUse;
final Helper helper;
final String name;
final List<String> modules;
final String validatingModule;
final List<String> expectedLocations;
final List<String> unexpectedLocations;
final String[] expectedFiles;
final List<String> extraJlinkOpts;
final List<String> modulePath;
JlinkSpec(Path imageToUse, Helper helper, String name, List<String> modules,
String validatingModule, List<String> expectedLocations,
List<String> unexpectedLocations, String[] expectedFiles,
List<String> extraJlinkOpts,
List<String> modulePath) {
this.imageToUse = imageToUse;
this.helper = helper;
this.name = name;
this.modules = modules;
this.validatingModule = validatingModule;
this.expectedLocations = expectedLocations;
this.unexpectedLocations = unexpectedLocations;
this.expectedFiles = expectedFiles;
this.extraJlinkOpts = extraJlinkOpts;
this.modulePath = modulePath;
}
public Path getImageToUse() {
return imageToUse;
}
public Helper getHelper() {
return helper;
}
public String getName() {
return name;
}
public List<String> getModules() {
return modules;
}
public String getValidatingModule() {
return validatingModule;
}
public List<String> getExpectedLocations() {
return expectedLocations;
}
public List<String> getUnexpectedLocations() {
return unexpectedLocations;
}
public String[] getExpectedFiles() {
return expectedFiles;
}
public List<String> getExtraJlinkOpts() {
return extraJlinkOpts;
}
public List<String> getModulePath() {
return modulePath;
}
}
static class JlinkSpecBuilder {
Path imageToUse;
Helper helper;
String name;
List<String> modules = new ArrayList<>();
String validatingModule;
List<String> expectedLocations = new ArrayList<>();
List<String> unexpectedLocations = new ArrayList<>();
List<String> expectedFiles = new ArrayList<>();
List<String> extraJlinkOpts = new ArrayList<>();
List<String> modulePath = new ArrayList<>();
JlinkSpec build() {
if (imageToUse == null) {
throw new IllegalStateException("No image to use for jlink specified!");
}
if (helper == null) {
throw new IllegalStateException("No helper specified!");
}
if (name == null) {
throw new IllegalStateException("No name for the image location specified!");
}
if (validatingModule == null) {
throw new IllegalStateException("No module specified for after generation validation!");
}
return new JlinkSpec(imageToUse,
helper,
name,
modules,
validatingModule,
expectedLocations,
unexpectedLocations,
expectedFiles.toArray(new String[0]),
extraJlinkOpts,
modulePath);
}
JlinkSpecBuilder imagePath(Path image) {
this.imageToUse = image;
return this;
}
JlinkSpecBuilder helper(Helper helper) {
this.helper = helper;
return this;
}
JlinkSpecBuilder name(String name) {
this.name = name;
return this;
}
JlinkSpecBuilder addModule(String module) {
modules.add(module);
return this;
}
JlinkSpecBuilder validatingModule(String module) {
this.validatingModule = module;
return this;
}
JlinkSpecBuilder addModulePath(String modulePath) {
this.modulePath.add(modulePath);
return this;
}
JlinkSpecBuilder expectedLocation(String location) {
expectedLocations.add(location);
return this;
}
JlinkSpecBuilder unexpectedLocation(String location) {
unexpectedLocations.add(location);
return this;
}
JlinkSpecBuilder expectedFile(String file) {
expectedFiles.add(file);
return this;
}
JlinkSpecBuilder extraJlinkOpt(String opt) {
extraJlinkOpts.add(opt);
return this;
}
}
static abstract class OutputAnalyzerHandler {
public abstract void handleAnalyzer(OutputAnalyzer out);
}
static class RuntimeLinkOutputAnalyzerHandler extends OutputAnalyzerHandler {
@Override
public void handleAnalyzer(OutputAnalyzer out) {
out.shouldContain("Linking based on the current run-time image");
}
}
static class DefaultSuccessExitPredicate implements Predicate<OutputAnalyzer> {
@Override
public boolean test(OutputAnalyzer t) {
return t.getExitValue() == 0;
}
}
}

View File

@ -0,0 +1,86 @@
/*
* Copyright (c) 2024, Red Hat, Inc.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
import java.nio.file.Path;
import java.util.List;
import java.util.Scanner;
import jdk.test.lib.process.OutputAnalyzer;
import tests.Helper;
/*
* @test
* @summary Test --add-options jlink plugin when linking from the run-time image
* @requires (vm.compMode != "Xcomp" & os.maxMemory >= 2g)
* @library ../../lib /test/lib
* @enablePreview
* @modules java.base/jdk.internal.jimage
* jdk.jlink/jdk.tools.jlink.internal
* jdk.jlink/jdk.tools.jlink.plugin
* jdk.jlink/jdk.tools.jimage
* @build tests.* jdk.test.lib.process.OutputAnalyzer
* jdk.test.lib.process.ProcessTools
* @run main/othervm -Xmx1g AddOptionsTest
*/
public class AddOptionsTest extends AbstractLinkableRuntimeTest {
public static void main(String[] args) throws Exception {
AddOptionsTest test = new AddOptionsTest();
test.run();
}
@Override
void runTest(Helper helper, boolean isLinkableRuntime) throws Exception {
BaseJlinkSpecBuilder builder = new BaseJlinkSpecBuilder()
.addExtraOption("--add-options")
.addExtraOption("-Xlog:gc=info:stderr -XX:+UseParallelGC")
.name("java-base-with-opts")
.addModule("java.base")
.validatingModule("java.base")
.helper(helper);
if (isLinkableRuntime) {
builder.setLinkableRuntime();
}
Path finalImage = createJavaImageRuntimeLink(builder.build());
verifyListModules(finalImage, List.of("java.base"));
verifyParallelGCInUse(finalImage);
}
private void verifyParallelGCInUse(Path finalImage) throws Exception {
OutputAnalyzer analyzer = runJavaCmd(finalImage, List.of("--version"));
boolean foundMatch = false;
try (Scanner lineScan = new Scanner(analyzer.getStderr())) {
while (lineScan.hasNextLine()) {
String line = lineScan.nextLine();
if (line.endsWith("Using Parallel")) {
foundMatch = true;
break;
}
}
}
if (!foundMatch) {
throw new AssertionError("Expected Parallel GC in place for jlinked image");
}
}
}

View File

@ -0,0 +1,72 @@
/*
* Copyright (c) 2024, Red Hat, Inc.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
import java.nio.file.Path;
import java.util.List;
import java.util.Set;
import tests.Helper;
/*
* @test
* @summary Test basic linking from the run-time image with java.base.jmod missing
* but java.xml.jmod present. It should link from the run-time image without errors.
* @requires (jlink.packagedModules & vm.compMode != "Xcomp" & os.maxMemory >= 2g)
* @library ../../lib /test/lib
* @enablePreview
* @modules java.base/jdk.internal.jimage
* jdk.jlink/jdk.tools.jlink.internal
* jdk.jlink/jdk.tools.jlink.plugin
* jdk.jlink/jdk.tools.jimage
* @build tests.* jdk.test.lib.process.OutputAnalyzer
* jdk.test.lib.process.ProcessTools
* @run main/othervm -Xmx1g BasicJlinkMissingJavaBase
*/
public class BasicJlinkMissingJavaBase extends AbstractLinkableRuntimeTest {
@Override
public void runTest(Helper helper, boolean isLinkableRuntime) throws Exception {
Path finalImage = createJavaXMLRuntimeLink(helper, "java-xml", isLinkableRuntime);
verifyListModules(finalImage, List.of("java.base", "java.xml"));
}
private Path createJavaXMLRuntimeLink(Helper helper, String name, boolean isLinkableRuntime) throws Exception {
BaseJlinkSpecBuilder builder = new BaseJlinkSpecBuilder();
builder.helper(helper)
.name(name)
.addModule("java.xml")
.validatingModule("java.xml");
if (isLinkableRuntime) {
builder.setLinkableRuntime();
}
Set<String> excludedJmods = Set.of("java.base.jmod");
return createJavaImageRuntimeLink(builder.build(), excludedJmods);
}
public static void main(String[] args) throws Exception {
BasicJlinkMissingJavaBase test = new BasicJlinkMissingJavaBase();
test.run();
}
}

View File

@ -0,0 +1,69 @@
/*
* Copyright (c) 2024, Red Hat, Inc.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
import java.nio.file.Path;
import java.util.List;
import tests.Helper;
/*
* @test
* @summary Test basic linking from the run-time image
* @requires (vm.compMode != "Xcomp" & os.maxMemory >= 2g)
* @library ../../lib /test/lib
* @enablePreview
* @modules java.base/jdk.internal.jimage
* jdk.jlink/jdk.tools.jlink.internal
* jdk.jlink/jdk.tools.jlink.plugin
* jdk.jlink/jdk.tools.jimage
* @build tests.* jdk.test.lib.process.OutputAnalyzer
* jdk.test.lib.process.ProcessTools
* @run main/othervm -Xmx1g BasicJlinkTest false
*/
public class BasicJlinkTest extends AbstractLinkableRuntimeTest {
@Override
public void runTest(Helper helper, boolean isLinkableRuntime) throws Exception {
Path finalImage = createJavaBaseRuntimeLink(helper, "java-base", isLinkableRuntime);
verifyListModules(finalImage, List.of("java.base"));
}
private Path createJavaBaseRuntimeLink(Helper helper, String name, boolean isLinkableRuntime) throws Exception {
BaseJlinkSpecBuilder builder = new BaseJlinkSpecBuilder();
builder.helper(helper)
.name(name)
.addModule("java.base")
.validatingModule("java.base");
if (isLinkableRuntime) {
builder.setLinkableRuntime();
}
return createJavaImageRuntimeLink(builder.build());
}
public static void main(String[] args) throws Exception {
BasicJlinkTest test = new BasicJlinkTest();
test.run();
}
}

View File

@ -0,0 +1,42 @@
/*
* Copyright (c) 2024, Red Hat, Inc.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
import jdk.test.lib.process.OutputAnalyzer;
class CapturingHandler extends AbstractLinkableRuntimeTest.OutputAnalyzerHandler {
private OutputAnalyzer output;
public String stdErr() {
return output.getStderr();
}
public OutputAnalyzer analyzer() {
return output;
}
@Override
public void handleAnalyzer(OutputAnalyzer out) {
this.output = out;
}
}

View File

@ -0,0 +1,84 @@
/*
* Copyright (c) 2024, Red Hat, Inc.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
import java.nio.file.Path;
import java.util.List;
import tests.Helper;
/*
* @test
* @summary Test jmod-less jlink with a custom module
* @requires (vm.compMode != "Xcomp" & os.maxMemory >= 2g)
* @library ../../lib /test/lib
* @enablePreview
* @modules java.base/jdk.internal.jimage
* jdk.jlink/jdk.tools.jlink.internal
* jdk.jlink/jdk.tools.jlink.plugin
* jdk.jlink/jdk.tools.jimage
* @build tests.* jdk.test.lib.process.OutputAnalyzer
* jdk.test.lib.process.ProcessTools
* @run main/othervm -Xmx1g CustomModuleJlinkTest
*/
public class CustomModuleJlinkTest extends AbstractLinkableRuntimeTest {
public static void main(String[] args) throws Exception {
CustomModuleJlinkTest test = new CustomModuleJlinkTest();
test.run();
}
@Override
void runTest(Helper helper, boolean isLinkableRuntime) throws Exception {
String customModule = "leaf1";
helper.generateDefaultJModule(customModule);
// create a base image for linking from the run-time image
BaseJlinkSpecBuilder builder = new BaseJlinkSpecBuilder()
.helper(helper)
.name("cmod-jlink")
.addModule("java.base")
.validatingModule("java.base");
if (isLinkableRuntime) {
builder.setLinkableRuntime();
}
Path jlinkImage = createRuntimeLinkImage(builder.build());
// Next jlink using the run-time image for java.base, but take
// the custom module from the module path.
Path finalImage = jlinkUsingImage(new JlinkSpecBuilder()
.imagePath(jlinkImage)
.helper(helper)
.name(customModule)
.addModulePath(helper.defaultModulePath(false))
.expectedLocation(String.format("/%s/%s/com/foo/bar/X.class", customModule, customModule))
.addModule(customModule)
.validatingModule(customModule)
.build());
// Expected only the transitive closure of "leaf1" module in the --list-modules
// output of the java launcher.
List<String> expectedModules = List.of("java.base", customModule);
verifyListModules(finalImage, expectedModules);
}
}

View File

@ -0,0 +1,89 @@
/*
* Copyright (c) 2024, Red Hat, Inc.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import tests.Helper;
/*
* @test
* @summary Verify JLI class generation in run-time image link mode
* @requires (vm.compMode != "Xcomp" & os.maxMemory >= 2g)
* @library ../../lib /test/lib
* @enablePreview
* @modules java.base/jdk.internal.jimage
* jdk.jlink/jdk.tools.jlink.internal
* jdk.jlink/jdk.tools.jlink.plugin
* jdk.jlink/jdk.tools.jimage
* @build tests.* jdk.test.lib.process.OutputAnalyzer
* jdk.test.lib.process.ProcessTools
* @run main/othervm -Xmx1g GenerateJLIClassesTest
*/
public class GenerateJLIClassesTest extends AbstractLinkableRuntimeTest {
public static void main(String[] args) throws Exception {
GenerateJLIClassesTest test = new GenerateJLIClassesTest();
test.run();
}
/*
* java.lang.invoke.BoundMethodHandle$Species_* classes get generated
* by the GenerateJLiClassesPlugin. This test ensures that potentially
* generated JLI classes from the run-time image don't populate to the
* target image in the run-time image based link mode.
*/
@Override
void runTest(Helper helper, boolean isLinkableRuntime) throws Exception {
Path baseFile = Files.createTempFile("base", "trace");
String species = "LLLLLLLLLLLLLLLLLLL";
String fileString = "[SPECIES_RESOLVE] java.lang.invoke.BoundMethodHandle$Species_" + species + " (salvaged)\n";
Files.write(baseFile, fileString.getBytes(StandardCharsets.UTF_8));
BaseJlinkSpecBuilder builder = new BaseJlinkSpecBuilder()
.helper(helper)
.addModule("java.base")
.name("jlink.jli-jmodless")
.validatingModule("java.base");
if (isLinkableRuntime) {
builder.setLinkableRuntime();
}
Path runtimeLinkableImage = createRuntimeLinkImage(builder.build());
// Finally attempt another jmodless link reducing modules to java.base only,
// and asking for specific jli classes.
jlinkUsingImage(new JlinkSpecBuilder()
.helper(helper)
.imagePath(runtimeLinkableImage)
.name("java.base-jli-derived")
.addModule("java.base")
.extraJlinkOpt("--generate-jli-classes=@" + baseFile.toString())
.expectedLocation("/java.base/java/lang/invoke/BoundMethodHandle$Species_" + species + ".class")
.expectedLocation("/java.base/java/lang/invoke/BoundMethodHandle$Species_L.class")
.unexpectedLocation("/java.base/java/lang/invoke/BoundMethodHandle$Species_" + species.substring(1) + ".class")
.unexpectedLocation("/java.base/java/lang/invoke/BoundMethodHandle$Species_LL.class")
.validatingModule("java.base")
.build());
}
}

View File

@ -0,0 +1,65 @@
/*
* Copyright (c) 2023, Red Hat, Inc.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
import java.io.IOException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import jdk.internal.jimage.BasicImageReader;
import jdk.internal.jimage.ImageLocation;
/**
*
* JDK Modular image iterator
*/
public class JImageHelper {
private JImageHelper() {
// Don't instantiate
}
public static List<String> listContents(Path jimage) throws IOException {
try(BasicImageReader reader = BasicImageReader.open(jimage)) {
List<String> entries = new ArrayList<>();
for (String s : reader.getEntryNames()) {
entries.add(s);
}
Collections.sort(entries);
return entries;
}
}
public static byte[] getLocationBytes(String location, Path jimage) throws IOException {
try(BasicImageReader reader = BasicImageReader.open(jimage)) {
ImageLocation il = reader.findLocation(location);
byte[] r = reader.getResource(il);
if (r == null) {
throw new IllegalStateException(String.format("bytes for %s not found!", location));
}
return r;
}
}
}

View File

@ -0,0 +1,75 @@
/*
* Copyright (c) 2024, Red Hat, Inc.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
import java.nio.file.Files;
import java.nio.file.Path;
import tests.Helper;
/*
* @test
* @summary Test reproducibility of linking an java.se image using the run-time
* image.
* @requires (vm.compMode != "Xcomp" & os.maxMemory >= 2g)
* @library ../../lib /test/lib
* @enablePreview
* @modules java.base/jdk.internal.jimage
* jdk.jlink/jdk.tools.jlink.internal
* jdk.jlink/jdk.tools.jlink.plugin
* jdk.jlink/jdk.tools.jimage
* @build tests.* jdk.test.lib.process.OutputAnalyzer
* jdk.test.lib.process.ProcessTools
* @run main/othervm -Xmx1g JavaSEReproducibleTest
*/
public class JavaSEReproducibleTest extends AbstractLinkableRuntimeTest {
public static void main(String[] args) throws Exception {
JavaSEReproducibleTest test = new JavaSEReproducibleTest();
test.run();
}
@Override
void runTest(Helper helper, boolean isLinkableRuntime) throws Exception {
String javaSeModule = "java.se";
// create a java.se using jmod-less approach
BaseJlinkSpecBuilder builder = new BaseJlinkSpecBuilder()
.helper(helper)
.addModule(javaSeModule)
.validatingModule(javaSeModule);
if (isLinkableRuntime) {
builder.setLinkableRuntime();
}
builder.name("java-se-repro1");
Path javaSEJmodLess1 = createJavaImageRuntimeLink(builder.build());
// create another java.se version using jmod-less approach
builder.name("java-se-repro2");
Path javaSEJmodLess2 = createJavaImageRuntimeLink(builder.build());
if (Files.mismatch(javaSEJmodLess1.resolve("lib").resolve("modules"),
javaSEJmodLess2.resolve("lib").resolve("modules")) != -1L) {
throw new RuntimeException("jlink producing inconsistent result for " + javaSeModule + " (jmod-less)");
}
}
}

View File

@ -0,0 +1,92 @@
/*
* Copyright (c) 2024, Red Hat, Inc.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
import java.nio.file.Path;
import java.util.function.Predicate;
import jdk.test.lib.process.OutputAnalyzer;
import tests.Helper;
/*
* @test
* @summary Verify that jlink with an empty module path, but trying to use
* --keep-packaged-modules fails as expected.
* @requires (vm.compMode != "Xcomp" & os.maxMemory >= 2g)
* @library ../../lib /test/lib
* @enablePreview
* @modules java.base/jdk.internal.jimage
* jdk.jlink/jdk.tools.jlink.internal
* jdk.jlink/jdk.tools.jlink.plugin
* jdk.jlink/jdk.tools.jimage
* @build tests.* jdk.test.lib.process.OutputAnalyzer
* jdk.test.lib.process.ProcessTools
* @run main/othervm -Xmx1g KeepPackagedModulesFailTest
*/
public class KeepPackagedModulesFailTest extends AbstractLinkableRuntimeTest {
public static void main(String[] args) throws Exception {
KeepPackagedModulesFailTest test = new KeepPackagedModulesFailTest();
test.run();
}
@Override
void runTest(Helper helper, boolean isLinkableRuntime) throws Exception {
// create a base image for linking from the run-time image
BaseJlinkSpecBuilder builder = new BaseJlinkSpecBuilder()
.helper(helper)
.name("jlink-fail")
.addModule("java.base")
.validatingModule("java.base");
if (isLinkableRuntime) {
builder.setLinkableRuntime();
}
Path baseImage = createRuntimeLinkImage(builder.build());
CapturingHandler handler = new CapturingHandler();
Predicate<OutputAnalyzer> exitFailPred = new Predicate<>() {
@Override
public boolean test(OutputAnalyzer t) {
return t.getExitValue() != 0; // expect failure
}
};
// Attempt a jlink using the run-time image and also using option
// --keep-packaged-modules, which should fail.
jlinkUsingImage(new JlinkSpecBuilder()
.helper(helper)
.imagePath(baseImage)
.name("java-base-jlink-keep-packaged-target")
.addModule("java.base")
.extraJlinkOpt("--keep-packaged-modules=foo")
.validatingModule("java.base")
.build(), handler, exitFailPred);
OutputAnalyzer analyzer = handler.analyzer();
if (analyzer.getExitValue() == 0) {
throw new AssertionError("Expected jlink to have failed!");
}
analyzer.stdoutShouldContain("Error");
analyzer.stdoutShouldContain("--keep-packaged-modules");
}
}

View File

@ -0,0 +1,85 @@
/*
* Copyright (c) 2024, Red Hat, Inc.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
import java.nio.file.Path;
import java.util.function.Predicate;
import jdk.test.lib.process.OutputAnalyzer;
import tests.Helper;
/*
* @test
* @summary Verify jlink fails by default when linking from the run-time image
* and files have been modified
* @requires (vm.compMode != "Xcomp" & os.maxMemory >= 2g)
* @library ../../lib /test/lib
* @enablePreview
* @modules java.base/jdk.internal.jimage
* jdk.jlink/jdk.tools.jlink.internal
* jdk.jlink/jdk.tools.jlink.plugin
* jdk.jlink/jdk.tools.jimage
* @build tests.* jdk.test.lib.process.OutputAnalyzer
* jdk.test.lib.process.ProcessTools
* @run main/othervm -Xmx1g ModifiedFilesExitTest
*/
public class ModifiedFilesExitTest extends ModifiedFilesTest {
public static void main(String[] args) throws Exception {
ModifiedFilesExitTest test = new ModifiedFilesExitTest();
test.run();
}
@Override
String initialImageName() {
return "java-base-jlink-with-mod-exit";
}
@Override
void testAndAssert(Path modifiedFile, Helper helper, Path initialImage)
throws Exception {
CapturingHandler handler = new CapturingHandler();
Predicate<OutputAnalyzer> exitFailPred = new Predicate<>() {
@Override
public boolean test(OutputAnalyzer t) {
return t.getExitValue() != 0; // expect failure
}
};
jlinkUsingImage(new JlinkSpecBuilder()
.helper(helper)
.imagePath(initialImage)
.name("java-base-jlink-with-mod-exit-target")
.addModule("java.base")
.validatingModule("java.base")
.build(), handler, exitFailPred);
OutputAnalyzer analyzer = handler.analyzer();
if (analyzer.getExitValue() == 0) {
throw new AssertionError("Expected jlink to fail due to modified file!");
}
analyzer.stdoutShouldContain(modifiedFile.toString() + " has been modified");
// Verify the error message is reasonable
analyzer.stdoutShouldNotContain("jdk.tools.jlink.internal.RunImageLinkException");
analyzer.stdoutShouldNotContain("java.lang.IllegalArgumentException");
}
}

View File

@ -0,0 +1,72 @@
/*
* Copyright (c) 2024, Red Hat, Inc.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Properties;
import tests.Helper;
public abstract class ModifiedFilesTest extends AbstractLinkableRuntimeTest {
abstract String initialImageName();
abstract void testAndAssert(Path modifiedFile, Helper helper, Path initialImage) throws Exception;
@Override
void runTest(Helper helper, boolean isLinkableRuntime) throws Exception {
BaseJlinkSpecBuilder builder = new BaseJlinkSpecBuilder()
.name(initialImageName())
.addModule("java.base")
.validatingModule("java.base")
.helper(helper);
if (isLinkableRuntime) {
builder.setLinkableRuntime();
}
Path initialImage = createRuntimeLinkImage(builder.build());
Path netPropertiesFile = modifyFileInImage(initialImage);
testAndAssert(netPropertiesFile, helper, initialImage);
}
protected Path modifyFileInImage(Path jmodLessImg)
throws IOException, AssertionError {
// modify net.properties config file
Path netPropertiesFile = jmodLessImg.resolve("conf").resolve("net.properties");
Properties props = new Properties();
try (InputStream is = Files.newInputStream(netPropertiesFile)) {
props.load(is);
}
String prevVal = (String)props.put("java.net.useSystemProxies", Boolean.TRUE.toString());
if (prevVal == null || Boolean.getBoolean(prevVal) != false) {
throw new AssertionError("Expected previous value to be false!");
}
try (OutputStream out = Files.newOutputStream(netPropertiesFile)) {
props.store(out, "Modified net.properties file!");
}
return netPropertiesFile;
}
}

View File

@ -0,0 +1,75 @@
/*
* Copyright (c) 2024, Red Hat, Inc.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
import java.nio.file.Path;
import jdk.test.lib.process.OutputAnalyzer;
import tests.Helper;
/*
* @test
* @summary Verify warnings are being produced when linking from the run-time
* image and files have been modified
* @requires (vm.compMode != "Xcomp" & os.maxMemory >= 2g)
* @library ../../lib /test/lib
* @enablePreview
* @modules java.base/jdk.internal.jimage
* jdk.jlink/jdk.tools.jlink.internal
* jdk.jlink/jdk.tools.jlink.plugin
* jdk.jlink/jdk.tools.jimage
* @build tests.* jdk.test.lib.process.OutputAnalyzer
* jdk.test.lib.process.ProcessTools
* @run main/othervm -Xmx1g ModifiedFilesWarningTest
*/
public class ModifiedFilesWarningTest extends ModifiedFilesTest {
protected static final String IGNORE_MODIFIED_RUNTIME_OPT = "--ignore-modified-runtime";
public static void main(String[] args) throws Exception {
ModifiedFilesWarningTest test = new ModifiedFilesWarningTest();
test.run();
}
@Override
String initialImageName() {
return "java-base-jlink-with-mod-warn";
}
@Override
void testAndAssert(Path modifiedFile, Helper helper, Path initialImage) throws Exception {
CapturingHandler handler = new CapturingHandler();
jlinkUsingImage(new JlinkSpecBuilder()
.helper(helper)
.imagePath(initialImage)
.name("java-base-jlink-with-mod-warn-target")
.addModule("java.base")
.validatingModule("java.base")
.extraJlinkOpt(IGNORE_MODIFIED_RUNTIME_OPT) // only generate a warning
.build(), handler);
OutputAnalyzer out = handler.analyzer();
// verify we get the warning message
out.stdoutShouldMatch("Warning: .* has been modified");
out.stdoutShouldNotContain("java.lang.IllegalArgumentException");
out.stdoutShouldNotContain("jdk.tools.jlink.internal.RunImageLinkException");
}
}

View File

@ -0,0 +1,93 @@
/*
* Copyright (c) 2024, Red Hat, Inc.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
import java.nio.file.Path;
import java.util.function.Predicate;
import jdk.test.lib.process.OutputAnalyzer;
import tests.Helper;
/*
* @test
* @summary Verify that a jlink using the run-time image cannot include jdk.jlink
* @requires (vm.compMode != "Xcomp" & os.maxMemory >= 2g)
* @library ../../lib /test/lib
* @enablePreview
* @modules java.base/jdk.internal.jimage
* jdk.jlink/jdk.tools.jlink.internal
* jdk.jlink/jdk.tools.jlink.plugin
* jdk.jlink/jdk.tools.jimage
* @build tests.* jdk.test.lib.process.OutputAnalyzer
* jdk.test.lib.process.ProcessTools
* @run main/othervm -Xmx1g MultiHopTest
*/
public class MultiHopTest extends AbstractLinkableRuntimeTest {
@Override
void runTest(Helper helper, boolean isLinkableRuntime) throws Exception {
Path jdkJlinkJmodless = createJDKJlinkJmodLess(helper, "jdk.jlink-multi-hop1", isLinkableRuntime);
CapturingHandler handler = new CapturingHandler();
Predicate<OutputAnalyzer> exitFailPred = new Predicate<>() {
@Override
public boolean test(OutputAnalyzer a) {
return a.getExitValue() != 0; // expect failure
}
};
jlinkUsingImage(new JlinkSpecBuilder()
.helper(helper)
.imagePath(jdkJlinkJmodless)
.name("jdk-jlink-multi-hop1-target")
.addModule("jdk.jlink")
.validatingModule("java.base")
.build(), handler, exitFailPred);
OutputAnalyzer analyzer = handler.analyzer();
if (analyzer.getExitValue() == 0) {
throw new AssertionError("Expected jlink to fail due to including jdk.jlink");
}
String expectedMsg = "This JDK does not contain packaged modules " +
"and cannot be used to create another image with " +
"the jdk.jlink module";
analyzer.stdoutShouldContain(expectedMsg);
analyzer.stdoutShouldNotContain("Exception"); // ensure error message is sane
}
private Path createJDKJlinkJmodLess(Helper helper, String name, boolean isLinkableRuntime) throws Exception {
BaseJlinkSpecBuilder builder = new BaseJlinkSpecBuilder();
builder.helper(helper)
.name(name)
.addModule("jdk.jlink")
.validatingModule("java.base");
if (isLinkableRuntime) {
builder.setLinkableRuntime();
}
return createRuntimeLinkImage(builder.build());
}
public static void main(String[] args) throws Exception {
MultiHopTest test = new MultiHopTest();
test.run();
}
}

View File

@ -0,0 +1,175 @@
/*
* Copyright (c) 2024, Red Hat, Inc.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import tests.Helper;
import tests.JImageGenerator;
/*
* @test
* @summary Compare packaged-modules jlink with a run-time image based jlink to
* produce the same result
* @requires (jlink.packagedModules & vm.compMode != "Xcomp" & os.maxMemory >= 2g)
* @library ../../lib /test/lib
* @enablePreview
* @modules java.base/jdk.internal.jimage
* jdk.jlink/jdk.tools.jlink.internal
* jdk.jlink/jdk.tools.jlink.plugin
* jdk.jlink/jdk.tools.jimage
* @build tests.* jdk.test.lib.process.OutputAnalyzer
* jdk.test.lib.process.ProcessTools
* @run main/othervm -Xmx1g PackagedModulesVsRuntimeImageLinkTest
*/
public class PackagedModulesVsRuntimeImageLinkTest extends AbstractLinkableRuntimeTest {
public static void main(String[] args) throws Exception {
PackagedModulesVsRuntimeImageLinkTest test = new PackagedModulesVsRuntimeImageLinkTest();
test.run();
}
@Override
void runTest(Helper helper, boolean isLinkableRuntime) throws Exception {
// create a java.se using jmod-less approach
BaseJlinkSpecBuilder builder = new BaseJlinkSpecBuilder()
.helper(helper)
.name("java-se-jmodless")
.addModule("java.se")
.validatingModule("java.se");
if (isLinkableRuntime) {
builder.setLinkableRuntime();
}
Path javaSEruntimeLink = createJavaImageRuntimeLink(builder.build());
// create a java.se using packaged modules (jmod-full)
Path javaSEJmodFull = JImageGenerator.getJLinkTask()
.output(helper.createNewImageDir("java-se-jmodfull"))
.addMods("java.se").call().assertSuccess();
compareRecursively(javaSEruntimeLink, javaSEJmodFull);
}
// Visit all files in the given directories checking that they're byte-by-byte identical
private static void compareRecursively(Path javaSEJmodLess,
Path javaSEJmodFull) throws IOException, AssertionError {
FilesCapturingVisitor jmodFullVisitor = new FilesCapturingVisitor(javaSEJmodFull);
FilesCapturingVisitor jmodLessVisitor = new FilesCapturingVisitor(javaSEJmodLess);
Files.walkFileTree(javaSEJmodFull, jmodFullVisitor);
Files.walkFileTree(javaSEJmodLess, jmodLessVisitor);
List<String> jmodFullFiles = jmodFullVisitor.filesVisited();
List<String> jmodLessFiles = jmodLessVisitor.filesVisited();
Collections.sort(jmodFullFiles);
Collections.sort(jmodLessFiles);
if (jmodFullFiles.size() != jmodLessFiles.size()) {
throw new AssertionError(String.format("Size of files different for jmod-less (%d) vs jmod-full (%d) java.se jlink", jmodLessFiles.size(), jmodFullFiles.size()));
}
String jimageFile = Path.of("lib").resolve("modules").toString();
// Compare all files except the modules image
for (int i = 0; i < jmodFullFiles.size(); i++) {
String jmodFullPath = jmodFullFiles.get(i);
String jmodLessPath = jmodLessFiles.get(i);
if (!jmodFullPath.equals(jmodLessPath)) {
throw new AssertionError(String.format("jmod-full path (%s) != jmod-less path (%s)", jmodFullPath, jmodLessPath));
}
if (jmodFullPath.equals(jimageFile)) {
continue;
}
Path a = javaSEJmodFull.resolve(Path.of(jmodFullPath));
Path b = javaSEJmodLess.resolve(Path.of(jmodLessPath));
if (Files.mismatch(a, b) != -1L) {
handleFileMismatch(a, b);
}
}
// Compare jimage contents by iterating its entries and comparing their
// paths and content bytes
//
// Note: The files aren't byte-by-byte comparable (probably due to string hashing
// and offset differences in container bytes)
Path jimageJmodLess = javaSEJmodLess.resolve(Path.of("lib")).resolve(Path.of("modules"));
Path jimageJmodFull = javaSEJmodFull.resolve(Path.of("lib")).resolve(Path.of("modules"));
List<String> jimageContentJmodLess = JImageHelper.listContents(jimageJmodLess);
List<String> jimageContentJmodFull = JImageHelper.listContents(jimageJmodFull);
if (jimageContentJmodLess.size() != jimageContentJmodFull.size()) {
throw new AssertionError(String.format("Size of jimage content differs for jmod-less (%d) v. jmod-full (%d)", jimageContentJmodLess.size(), jimageContentJmodFull.size()));
}
for (int i = 0; i < jimageContentJmodFull.size(); i++) {
if (!jimageContentJmodFull.get(i).equals(jimageContentJmodLess.get(i))) {
throw new AssertionError(String.format("Jimage content differs at index %d: jmod-full was: '%s' jmod-less was: '%s'",
i,
jimageContentJmodFull.get(i),
jimageContentJmodLess.get(i)
));
}
String loc = jimageContentJmodFull.get(i);
if (isTreeInfoResource(loc)) {
// Skip container bytes as those are offsets to the content
// of the container which might be different between jlink runs.
continue;
}
byte[] resBytesFull = JImageHelper.getLocationBytes(loc, jimageJmodFull);
byte[] resBytesLess = JImageHelper.getLocationBytes(loc, jimageJmodLess);
if (resBytesFull.length != resBytesLess.length || Arrays.mismatch(resBytesFull, resBytesLess) != -1) {
throw new AssertionError("Content bytes mismatch for " + loc);
}
}
}
private static boolean isTreeInfoResource(String path) {
return path.startsWith("/packages") || path.startsWith("/modules");
}
private static void handleFileMismatch(Path a, Path b) {
throw new AssertionError("Files mismatch: " + a + " vs. " + b);
}
static class FilesCapturingVisitor extends SimpleFileVisitor<Path> {
private final Path basePath;
private final List<String> filePaths = new ArrayList<>();
public FilesCapturingVisitor(Path basePath) {
this.basePath = basePath;
}
@Override
public FileVisitResult visitFile(Path path, BasicFileAttributes attrs) {
Path relative = basePath.relativize(path);
filePaths.add(relative.toString());
return FileVisitResult.CONTINUE;
}
List<String> filesVisited() {
return filePaths;
}
}
}

View File

@ -0,0 +1,124 @@
/*
* Copyright (c) 2024, Red Hat, Inc.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.function.Predicate;
import java.util.spi.ToolProvider;
import jdk.test.lib.process.OutputAnalyzer;
import tests.Helper;
/*
* @test
* @summary Test run-time link with --patch-module. Expect failure.
* @requires (vm.compMode != "Xcomp" & os.maxMemory >= 2g)
* @library ../../lib /test/lib
* @enablePreview
* @modules java.base/jdk.internal.jimage
* jdk.jlink/jdk.tools.jlink.internal
* jdk.jlink/jdk.tools.jlink.plugin
* jdk.jlink/jdk.tools.jimage
* @build tests.* jdk.test.lib.process.OutputAnalyzer
* jdk.test.lib.process.ProcessTools
* @run main/othervm -Xmx1g PatchedJDKModuleJlinkTest
*/
public class PatchedJDKModuleJlinkTest extends AbstractLinkableRuntimeTest {
@Override
public void runTest(Helper helper, boolean isLinkableRuntime) throws Exception {
String imageName = "java-base-patched";
Path runtimeLinkImage = createRuntimeLinkImage(helper, imageName + "-base", isLinkableRuntime);
// Prepare patched module content
Path patchSource = Path.of("java-base-patch-src");
Path pkg = patchSource.resolve("java", "lang");
Path extraClass = pkg.resolve("MyJlinkPatchInteger.java");
String source = """
package java.lang;
public class MyJlinkPatchInteger {
public int add(int a, int b) {
return a + b;
}
}
""";
Files.createDirectories(pkg);
Files.writeString(extraClass, source);
Path patchClasses = Path.of("java-base-patch-classes");
Files.createDirectories(patchClasses);
ToolProvider javac = ToolProvider.findFirst("javac")
.orElseThrow(() -> new AssertionError("javac not found"));
javac.run(System.out, System.err, new String[] {
"-d", patchClasses.toString(),
"--patch-module=java.base=" + patchSource.toAbsolutePath().toString(),
extraClass.toAbsolutePath().toString()
});
// Perform a run-time image link expecting a failure
CapturingHandler handler = new CapturingHandler();
Predicate<OutputAnalyzer> exitFailPred = new Predicate<>() {
@Override
public boolean test(OutputAnalyzer t) {
return t.getExitValue() != 0; // expect failure
}
};
jlinkUsingImage(new JlinkSpecBuilder()
.helper(helper)
.imagePath(runtimeLinkImage)
.name(imageName + "-derived")
.addModule("java.base")
.validatingModule("java.base")
.extraJlinkOpt("-J--patch-module=java.base=" +
patchClasses.toAbsolutePath().toString())
.build(), handler, exitFailPred);
OutputAnalyzer analyzer = handler.analyzer();
if (analyzer.getExitValue() == 0) {
throw new AssertionError("Expected jlink to fail due to patched module!");
}
analyzer.stdoutShouldContain("MyJlinkPatchInteger.class not found in the modules image.");
analyzer.stdoutShouldContain("--patch-module is not supported");
// Verify the error message is reasonable
analyzer.stdoutShouldNotContain("jdk.tools.jlink.internal.RunImageLinkException");
analyzer.stdoutShouldNotContain("java.lang.IllegalArgumentException");
}
private Path createRuntimeLinkImage(Helper helper, String name, boolean isLinkableRuntime) throws Exception {
BaseJlinkSpecBuilder builder = new BaseJlinkSpecBuilder()
.name(name)
.addModule("java.base")
.validatingModule("java.base")
.helper(helper);
if (isLinkableRuntime) {
builder.setLinkableRuntime();
}
return createRuntimeLinkImage(builder.build());
}
public static void main(String[] args) throws Exception {
PatchedJDKModuleJlinkTest test = new PatchedJDKModuleJlinkTest();
test.run();
}
}

View File

@ -0,0 +1,81 @@
/*
* Copyright (c) 2024, Red Hat, Inc.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
import java.nio.file.Path;
import java.util.Collections;
import java.util.List;
import tests.Helper;
import tests.JImageValidator;
/*
* @test
* @summary Test appropriate handling of generated SystemModules* classes in run-time image link mode
* @requires (vm.compMode != "Xcomp" & os.maxMemory >= 2g)
* @library ../../lib /test/lib
* @enablePreview
* @modules java.base/jdk.internal.jimage
* jdk.jlink/jdk.tools.jlink.internal
* jdk.jlink/jdk.tools.jlink.plugin
* jdk.jlink/jdk.tools.jimage
* @build tests.* jdk.test.lib.process.OutputAnalyzer
* jdk.test.lib.process.ProcessTools
* @run main/othervm -Xmx1g SystemModulesTest
*/
public class SystemModulesTest extends AbstractLinkableRuntimeTest {
public static void main(String[] args) throws Exception {
SystemModulesTest test = new SystemModulesTest();
test.run();
}
/*
* SystemModule classes are module specific. If the jlink is based on the
* modules image, then earlier generated SystemModule classes shall not get
* propagated.
*/
@Override
void runTest(Helper helper, boolean isLinkableRuntime) throws Exception {
// create an image with a module containing a main entrypoint (jdk.httpserver),
// thus producing the SystemModules$0.class. Add jdk.jdwp.agent as a module which
// isn't resolved by default, so as to generate SystemModules$default.class
BaseJlinkSpecBuilder builder = new BaseJlinkSpecBuilder()
.helper(helper)
.name("httpserver-jlink-jmodless-derived")
.addModule("jdk.httpserver")
.addModule("jdk.jdwp.agent")
.validatingModule("java.base");
if (isLinkableRuntime) {
builder.setLinkableRuntime();
}
Path javaseJmodless = createJavaImageRuntimeLink(builder.build());
// Verify that SystemModules$0.class etc. are there, due to httpserver and jdwp.agent
JImageValidator.validate(javaseJmodless.resolve("lib").resolve("modules"),
List.of("/java.base/jdk/internal/module/SystemModules$default.class",
"/java.base/jdk/internal/module/SystemModules$0.class",
"/java.base/jdk/internal/module/SystemModules$all.class"),
Collections.emptyList());
}
}

View File

@ -0,0 +1,76 @@
/*
* Copyright (c) 2024, Red Hat, Inc.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
import java.nio.file.Path;
import java.util.Collections;
import java.util.List;
import tests.Helper;
import tests.JImageValidator;
/*
* @test
* @summary Test disabled SystemModulesPlugin in run-time image link mode. Expect
* generated classes to not be there.
* @requires (vm.compMode != "Xcomp" & os.maxMemory >= 2g)
* @library ../../lib /test/lib
* @enablePreview
* @modules java.base/jdk.internal.jimage
* jdk.jlink/jdk.tools.jlink.internal
* jdk.jlink/jdk.tools.jlink.plugin
* jdk.jlink/jdk.tools.jimage
* @build tests.* jdk.test.lib.process.OutputAnalyzer
* jdk.test.lib.process.ProcessTools
* @run main/othervm -Xmx1g SystemModulesTest2
*/
public class SystemModulesTest2 extends AbstractLinkableRuntimeTest {
public static void main(String[] args) throws Exception {
SystemModulesTest2 test = new SystemModulesTest2();
test.run();
}
@Override
void runTest(Helper helper, boolean isLinkableRuntime) throws Exception {
// See SystemModulesTest which enables the system-modules plugin. With
// it disabled, we expect for the generated classes to not be there.
BaseJlinkSpecBuilder builder = new BaseJlinkSpecBuilder()
.helper(helper)
.name("jlink-jmodless-sysmod2")
.addModule("jdk.httpserver")
.validatingModule("java.base")
.addExtraOption("--disable-plugin")
.addExtraOption("system-modules");
if (isLinkableRuntime) {
builder.setLinkableRuntime();
}
Path runtimeImageLinkTarget = createJavaImageRuntimeLink(builder.build());
JImageValidator.validate(runtimeImageLinkTarget.resolve("lib").resolve("modules"),
Collections.emptyList(),
List.of("/java.base/jdk/internal/module/SystemModules$all.class",
"/java.base/jdk/internal/module/SystemModules$default.class",
"/java.base/jdk/internal/module/SystemModules$0.class"));
}
}

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2015, 2023, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2015, 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -64,20 +64,30 @@ public class Helper {
private final Map<String, List<String>> moduleDependencies = new HashMap<>(); private final Map<String, List<String>> moduleDependencies = new HashMap<>();
private final List<String> bootClasses; private final List<String> bootClasses;
private final FileSystem fs; private final FileSystem fs;
private final boolean linkableRuntime;
private static final Path JDK_HOME = Paths.get(System.getProperty("test.jdk"));
public static Helper newHelper() throws IOException { public static Helper newHelper() throws IOException {
Path jdkHome = Paths.get(System.getProperty("test.jdk")); return newHelper(false);
if (!Files.exists(jdkHome.resolve("jmods"))) { }
public static Helper newHelper(boolean linkableRuntime) throws IOException {
if (!linkableRuntime && !jdkHasPackagedModules()) {
// Skip test if the jmods directory is missing (e.g. exploded image) // Skip test if the jmods directory is missing (e.g. exploded image)
System.err.println("Test not run, NO jmods directory"); System.err.println("Test not run, NO jmods directory");
return null; return null;
} }
return new Helper(jdkHome); return new Helper(JDK_HOME, linkableRuntime);
} }
private Helper(Path jdkHome) throws IOException { public static boolean jdkHasPackagedModules() {
return Files.exists(JDK_HOME.resolve("jmods"));
}
private Helper(Path jdkHome, boolean linkableRuntime) throws IOException {
this.linkableRuntime = linkableRuntime;
this.stdjmods = jdkHome.resolve("jmods").normalize(); this.stdjmods = jdkHome.resolve("jmods").normalize();
if (!Files.exists(stdjmods)) { if (!linkableRuntime && !Files.exists(stdjmods)) {
throw new IOException("Standard jMods do not exist."); throw new IOException("Standard jMods do not exist.");
} }
this.fs = FileSystems.getFileSystem(URI.create("jrt:/")); this.fs = FileSystems.getFileSystem(URI.create("jrt:/"));
@ -140,7 +150,8 @@ public class Helper {
} }
public String defaultModulePath(boolean includeStdMods) { public String defaultModulePath(boolean includeStdMods) {
return (includeStdMods? stdjmods.toAbsolutePath().toString() : "") + File.pathSeparator String standardMods = linkableRuntime ? "" : stdjmods.toAbsolutePath().toString() + File.pathSeparator;
return (includeStdMods? standardMods : "")
+ jmods.toAbsolutePath().toString() + File.pathSeparator + jmods.toAbsolutePath().toString() + File.pathSeparator
+ jars.toAbsolutePath().toString() + File.pathSeparator + jars.toAbsolutePath().toString() + File.pathSeparator
+ explodedmodsclasses.toAbsolutePath().toString(); + explodedmodsclasses.toAbsolutePath().toString();
@ -184,7 +195,7 @@ public class Helper {
generateGarbage(jmodsclasses.resolve(moduleName)); generateGarbage(jmodsclasses.resolve(moduleName));
Path jmodFile = jmods.resolve(moduleName + ".jmod"); Path jmodFile = jmods.resolve(moduleName + ".jmod");
JModTask task = JImageGenerator.getJModTask() JModTask task = JImageGenerator.getJModTask(linkableRuntime)
.jmod(jmodFile) .jmod(jmodFile)
.addJmods(stdjmods) .addJmods(stdjmods)
.addJmods(jmods.toAbsolutePath()) .addJmods(jmods.toAbsolutePath())

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2015, 2023, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2015, 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -159,7 +159,11 @@ public class JImageGenerator {
} }
public static JModTask getJModTask() { public static JModTask getJModTask() {
return new JModTask(); return getJModTask(false);
}
public static JModTask getJModTask(boolean linkableRuntime) {
return new JModTask(linkableRuntime);
} }
public static JLinkTask getJLinkTask() { public static JLinkTask getJLinkTask() {
@ -350,11 +354,16 @@ public class JImageGenerator {
private final List<Path> jars = new ArrayList<>(); private final List<Path> jars = new ArrayList<>();
private final List<Path> jmods = new ArrayList<>(); private final List<Path> jmods = new ArrayList<>();
private final List<String> options = new ArrayList<>(); private final List<String> options = new ArrayList<>();
private final boolean linkableRuntime;
private Path output; private Path output;
private String hashModules; private String hashModules;
private String mainClass; private String mainClass;
private String moduleVersion; private String moduleVersion;
private JModTask(boolean linkableRuntime) {
this.linkableRuntime = linkableRuntime;
}
public JModTask addNativeLibraries(Path cp) { public JModTask addNativeLibraries(Path cp) {
this.libs.add(cp); this.libs.add(cp);
return this; return this;
@ -414,7 +423,7 @@ public class JImageGenerator {
// This is expect FIRST jmods THEN jars, if you change this, some tests could fail // This is expect FIRST jmods THEN jars, if you change this, some tests could fail
String jmods = toPath(this.jmods); String jmods = toPath(this.jmods);
String jars = toPath(this.jars); String jars = toPath(this.jars);
return jmods + File.pathSeparator + jars; return linkableRuntime ? jars : jmods + File.pathSeparator + jars;
} }
private String toPath(List<Path> paths) { private String toPath(List<Path> paths) {

View File

@ -138,6 +138,7 @@ public class VMProps implements Callable<Map<String, String>> {
map.put("jdk.containerized", this::jdkContainerized); map.put("jdk.containerized", this::jdkContainerized);
map.put("vm.flagless", this::isFlagless); map.put("vm.flagless", this::isFlagless);
map.put("jdk.foreign.linker", this::jdkForeignLinker); map.put("jdk.foreign.linker", this::jdkForeignLinker);
map.put("jlink.packagedModules", this::packagedModules);
vmGC(map); // vm.gc.X = true/false vmGC(map); // vm.gc.X = true/false
vmGCforCDS(map); // may set vm.gc vmGCforCDS(map); // may set vm.gc
vmOptFinalFlags(map); vmOptFinalFlags(map);
@ -715,6 +716,21 @@ public class VMProps implements Callable<Map<String, String>> {
return "" + "true".equalsIgnoreCase(isEnabled); return "" + "true".equalsIgnoreCase(isEnabled);
} }
private String packagedModules() {
// Some jlink tests require packaged modules being present (jmods).
// For a runtime linkable image build packaged modules aren't present
try {
Path jmodsDir = Path.of(System.getProperty("java.home"), "jmods");
if (jmodsDir.toFile().exists()) {
return Boolean.TRUE.toString();
} else {
return Boolean.FALSE.toString();
}
} catch (Throwable t) {
return Boolean.FALSE.toString();
}
}
/** /**
* Checks if we are in <i>almost</i> out-of-box configuration, i.e. the flags * Checks if we are in <i>almost</i> out-of-box configuration, i.e. the flags
* which JVM is started with don't affect its behavior "significantly". * which JVM is started with don't affect its behavior "significantly".

View File

@ -30,7 +30,7 @@
* jdk.compiler/com.sun.tools.javac.main * jdk.compiler/com.sun.tools.javac.main
* jdk.jlink * jdk.jlink
* @build toolbox.ToolBox toolbox.JavacTask toolbox.JarTask * @build toolbox.ToolBox toolbox.JavacTask toolbox.JarTask
* @run main AutostartPlugins * @run main/othervm AutostartPlugins
*/ */
import java.io.IOException; import java.io.IOException;

View File

@ -31,7 +31,7 @@
* jdk.compiler/com.sun.tools.javac.main * jdk.compiler/com.sun.tools.javac.main
* jdk.jlink * jdk.jlink
* @build toolbox.ToolBox toolbox.JavacTask toolbox.JarTask * @build toolbox.ToolBox toolbox.JavacTask toolbox.JarTask
* @run main InternalAPI * @run main/othervm InternalAPI
*/ */
import java.io.IOException; import java.io.IOException;