diff --git a/substratevm/mx.substratevm/mx_substratevm.py b/substratevm/mx.substratevm/mx_substratevm.py index 27b9de816729..4fbcc1c25a3c 100644 --- a/substratevm/mx.substratevm/mx_substratevm.py +++ b/substratevm/mx.substratevm/mx_substratevm.py @@ -177,7 +177,7 @@ def _run_graalvm_cmd(cmd_args, config, nonZeroIsFatal=True, out=None, err=None, config_args += ['--components=' + ','.join(c.name for c in components)] dynamic_imports = [x for x, _ in mx.get_dynamic_imports()] if dynamic_imports: - config_args += ['--dynamicimports', ','.join(dynamic_imports)] + config_args += ['--dynamicimports=' + ','.join(dynamic_imports)] primary_suite_dir = None args = config_args + cmd_args diff --git a/substratevm/src/com.oracle.svm.core.jdk11/src/com/oracle/svm/core/jdk11/Target_java_lang_Module_JDK11OrLater.java b/substratevm/src/com.oracle.svm.core.jdk11/src/com/oracle/svm/core/jdk11/Target_java_lang_Module_JDK11OrLater.java index d4f19b46db79..6def479b8a56 100644 --- a/substratevm/src/com.oracle.svm.core.jdk11/src/com/oracle/svm/core/jdk11/Target_java_lang_Module_JDK11OrLater.java +++ b/substratevm/src/com.oracle.svm.core.jdk11/src/com/oracle/svm/core/jdk11/Target_java_lang_Module_JDK11OrLater.java @@ -44,10 +44,12 @@ @TargetClass(value = java.lang.Module.class, onlyWith = JDK11OrLater.class) public final class Target_java_lang_Module_JDK11OrLater { + @Alias private String name; + @SuppressWarnings("static-method") @Substitute - public InputStream getResourceAsStream(String name) { - ResourceStorageEntry res = Resources.get(name); + public InputStream getResourceAsStream(String resourceName) { + ResourceStorageEntry res = Resources.get(name, resourceName); return res == null ? null : new ByteArrayInputStream(res.getData().get(0)); } diff --git a/substratevm/src/com.oracle.svm.core.jdk11/src/com/oracle/svm/core/jdk11/Target_java_lang_module_ModuleReference.java b/substratevm/src/com.oracle.svm.core.jdk11/src/com/oracle/svm/core/jdk11/Target_java_lang_module_ModuleReference.java deleted file mode 100644 index fa6d8ce4a1c0..000000000000 --- a/substratevm/src/com.oracle.svm.core.jdk11/src/com/oracle/svm/core/jdk11/Target_java_lang_module_ModuleReference.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (c) 2021, 2021, Oracle and/or its affiliates. All rights reserved. - * 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 com.oracle.svm.core.jdk11; - -import com.oracle.svm.core.annotate.TargetClass; -import com.oracle.svm.core.jdk.JDK11OrLater; - -@TargetClass(value = java.lang.module.ModuleReference.class, onlyWith = JDK11OrLater.class) -@SuppressWarnings("unused") -public final class Target_java_lang_module_ModuleReference { - -} diff --git a/substratevm/src/com.oracle.svm.core.jdk11/src/com/oracle/svm/core/jdk11/Target_jdk_internal_loader_BuiltinClassLoader.java b/substratevm/src/com.oracle.svm.core.jdk11/src/com/oracle/svm/core/jdk11/Target_jdk_internal_loader_BuiltinClassLoader.java index 3807a6d73568..8c7ad6d7522e 100644 --- a/substratevm/src/com.oracle.svm.core.jdk11/src/com/oracle/svm/core/jdk11/Target_jdk_internal_loader_BuiltinClassLoader.java +++ b/substratevm/src/com.oracle.svm.core.jdk11/src/com/oracle/svm/core/jdk11/Target_jdk_internal_loader_BuiltinClassLoader.java @@ -26,6 +26,7 @@ import java.io.IOException; import java.io.InputStream; +import java.lang.module.ModuleReference; import java.net.URL; import java.util.Enumeration; import java.util.List; @@ -57,7 +58,7 @@ protected Class loadClass(String name, boolean resolve) throws ClassNotFoundE @Substitute public URL findResource(String mn, String name) { - return ResourcesHelper.nameToResourceURL(name); + return ResourcesHelper.nameToResourceURL(mn, name); } @Substitute @@ -81,8 +82,8 @@ private List findMiscResource(String name) { } @Substitute - private URL findResource(Target_java_lang_module_ModuleReference mref, String name) { - return ResourcesHelper.nameToResourceURL(name); + private URL findResource(ModuleReference mref, String name) { + return ResourcesHelper.nameToResourceURL(mref.descriptor().name(), name); } @Substitute diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/ClassLoaderSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/ClassLoaderSupport.java index 9bc4850a6e9c..2e868d52fe53 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/ClassLoaderSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/ClassLoaderSupport.java @@ -24,6 +24,7 @@ */ package com.oracle.svm.core; +import java.io.InputStream; import java.util.List; import java.util.Locale; import java.util.ResourceBundle; @@ -47,5 +48,16 @@ public boolean isNativeImageClassLoader(ClassLoader classLoader) { protected abstract boolean isNativeImageClassLoaderImpl(ClassLoader classLoader); + public interface ResourceCollector { + + boolean isIncluded(String moduleName, String resourceName); + + void addResource(String moduleName, String resourceName, InputStream resourceStream); + + void addDirectoryResource(String moduleName, String dir, String content); + } + + public abstract void collectResources(ResourceCollector resourceCollector); + public abstract List getResourceBundle(String bundleName, Locale locale); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHub.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHub.java index 0acdec7d882b..3d77bd9e125c 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHub.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHub.java @@ -686,7 +686,9 @@ public Enum[] getEnumConstantsShared() { @Substitute public InputStream getResourceAsStream(String resourceName) { - return Resources.createInputStream(resolveName(resourceName)); + String moduleName = module == null ? null : SubstrateUtil.cast(module, Target_java_lang_Module.class).name; + String resolvedName = resolveName(resourceName); + return Resources.createInputStream(moduleName, resolvedName); } @KeepOriginal diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Resources.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Resources.java index 7a365e2666c0..20aa4e505b46 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Resources.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Resources.java @@ -38,6 +38,7 @@ import java.util.List; import org.graalvm.collections.EconomicMap; +import org.graalvm.collections.Pair; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; @@ -65,17 +66,22 @@ public static Resources singleton() { return ImageSingletons.lookup(Resources.class); } - /** The hosted map used to collect registered resources. */ - private final EconomicMap resources = ImageHeapMap.create(); + /** + * The hosted map used to collect registered resources. Using a {@link Pair} of (moduleName, + * resourceName) provides implementations for {@code hashCode()} and {@code equals()} needed for + * the map keys. + */ + private final EconomicMap, ResourceStorageEntry> resources = ImageHeapMap.create(); Resources() { } - public EconomicMap resources() { + public EconomicMap, ResourceStorageEntry> resources() { return resources; } public static byte[] inputStreamToByteArray(InputStream is) { + // TODO: Replace this with is.readAllBytes() once Java 8 support is removed byte[] arr = new byte[4096]; int pos = 0; try { @@ -100,29 +106,40 @@ public static byte[] inputStreamToByteArray(InputStream is) { return data; } - private static void addEntry(String resourceName, boolean isDirectory, byte[] data) { + private static void addEntry(String moduleName, String resourceName, boolean isDirectory, byte[] data) { Resources support = singleton(); - ResourceStorageEntry entry = support.resources.get(resourceName); + Pair key = Pair.create(moduleName, resourceName); + ResourceStorageEntry entry = support.resources.get(key); if (entry == null) { entry = new ResourceStorageEntry(isDirectory); - support.resources.put(resourceName, entry); + support.resources.put(key, entry); } entry.getData().add(data); } @Platforms(Platform.HOSTED_ONLY.class) public static void registerResource(String resourceName, InputStream is) { - addEntry(resourceName, false, inputStreamToByteArray(is)); + registerResource(null, resourceName, is); + } + + @Platforms(Platform.HOSTED_ONLY.class) + public static void registerResource(String moduleName, String resourceName, InputStream is) { + addEntry(moduleName, resourceName, false, inputStreamToByteArray(is)); } @Platforms(Platform.HOSTED_ONLY.class) public static void registerDirectoryResource(String resourceDirName, String content) { + registerDirectoryResource(null, resourceDirName, content); + } + + @Platforms(Platform.HOSTED_ONLY.class) + public static void registerDirectoryResource(String moduleName, String resourceDirName, String content) { /* * A directory content represents the names of all files and subdirectories located in the * specified directory, separated with new line delimiter and joined into one string which * is later converted into a byte array and placed into the resources map. */ - addEntry(resourceDirName, true, content.getBytes()); + addEntry(moduleName, resourceDirName, true, content.getBytes()); } /** @@ -135,7 +152,11 @@ public static String toCanonicalForm(String resourceName) { } public static ResourceStorageEntry get(String name) { - return singleton().resources.get(name); + return singleton().resources.get(Pair.createRight(name)); + } + + public static ResourceStorageEntry get(String moduleName, String resourceName) { + return singleton().resources.get(Pair.create(moduleName, resourceName)); } private static URL createURL(String resourceName, int index) { @@ -154,21 +175,29 @@ protected URLConnection openConnection(URL url) { } public static URL createURL(String resourceName) { + return createURL(null, resourceName); + } + + public static URL createURL(String moduleName, String resourceName) { if (resourceName == null) { return null; } - Enumeration urls = createURLs(toCanonicalForm(resourceName)); + Enumeration urls = createURLs(moduleName, toCanonicalForm(resourceName)); return urls.hasMoreElements() ? urls.nextElement() : null; } - /* Avoid pulling in the URL class when only an InputStream is needed. */ public static InputStream createInputStream(String resourceName) { + return createInputStream(null, resourceName); + } + + /* Avoid pulling in the URL class when only an InputStream is needed. */ + public static InputStream createInputStream(String moduleName, String resourceName) { if (resourceName == null) { return null; } - ResourceStorageEntry entry = Resources.get(toCanonicalForm(resourceName)); + ResourceStorageEntry entry = Resources.get(moduleName, toCanonicalForm(resourceName)); if (entry == null) { return null; } @@ -177,12 +206,16 @@ public static InputStream createInputStream(String resourceName) { } public static Enumeration createURLs(String resourceName) { + return createURLs(null, resourceName); + } + + public static Enumeration createURLs(String moduleName, String resourceName) { if (resourceName == null) { return null; } String canonicalResourceName = toCanonicalForm(resourceName); - ResourceStorageEntry entry = Resources.get(canonicalResourceName); + ResourceStorageEntry entry = Resources.get(moduleName, canonicalResourceName); if (entry == null) { return Collections.emptyEnumeration(); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/ResourcesHelper.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/ResourcesHelper.java index 6df5c428a893..9bef4e5b4e48 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/ResourcesHelper.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/ResourcesHelper.java @@ -24,9 +24,6 @@ */ package com.oracle.svm.core.jdk; -import com.oracle.svm.core.util.VMError; -import org.graalvm.nativeimage.ImageSingletons; - import java.io.IOException; import java.io.InputStream; import java.net.URL; @@ -36,6 +33,10 @@ import java.util.Enumeration; import java.util.List; +import org.graalvm.nativeimage.ImageSingletons; + +import com.oracle.svm.core.util.VMError; + @SuppressWarnings("unchecked") public class ResourcesHelper { @@ -72,6 +73,10 @@ public static URL nameToResourceURL(String resourceName) { return Resources.createURL(resourceName); } + public static URL nameToResourceURL(String moduleName, String resourceName) { + return Resources.createURL(moduleName, resourceName); + } + public static InputStream nameToResourceInputStream(String resourceName) throws IOException { URL url = nameToResourceURL(resourceName); return url != null ? url.openStream() : null; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_java_lang_Module.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_java_lang_Module.java index d8fac0b61361..a7a0a723b8a5 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_java_lang_Module.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_java_lang_Module.java @@ -24,9 +24,12 @@ */ package com.oracle.svm.core.jdk; +import com.oracle.svm.core.annotate.Alias; import com.oracle.svm.core.annotate.TargetClass; @SuppressWarnings("unused") @TargetClass(className = "java.lang.Module", onlyWith = JDK11OrLater.class) public final class Target_java_lang_Module { + @Alias // + public String name; } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_jdk_internal_loader_Loader.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_jdk_internal_loader_Loader.java index 09192ce95386..ee877964705c 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_jdk_internal_loader_Loader.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_jdk_internal_loader_Loader.java @@ -54,7 +54,7 @@ private Class findClassInModuleOrNull(Target_jdk_internal_loader_Loader_Loade @Substitute protected URL findResource(String mn, String name) { - return ResourcesHelper.nameToResourceURL(name); + return ResourcesHelper.nameToResourceURL(mn, name); } @Substitute diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/resources/NativeImageResourceFileSystem.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/resources/NativeImageResourceFileSystem.java index 77878fd75a23..28c2a4918abc 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/resources/NativeImageResourceFileSystem.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/resources/NativeImageResourceFileSystem.java @@ -85,6 +85,9 @@ import java.util.regex.Pattern; import org.graalvm.collections.MapCursor; +import org.graalvm.collections.Pair; + +import com.oracle.svm.core.jdk.Resources; /** *

@@ -647,9 +650,9 @@ private void update(Entry e) { } private void readAllEntries() { - MapCursor entries = NativeImageResourceFileSystemUtil.iterator(); + MapCursor, ResourceStorageEntry> entries = Resources.singleton().resources().getEntries(); while (entries.advance()) { - byte[] name = getBytes(entries.getKey()); + byte[] name = getBytes(entries.getKey().getRight()); if (!entries.getValue().isDirectory()) { IndexNode newIndexNode = new IndexNode(name, false); inodes.put(newIndexNode, newIndexNode); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/resources/NativeImageResourceFileSystemUtil.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/resources/NativeImageResourceFileSystemUtil.java index e3debb494652..d9ed91412cc2 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/resources/NativeImageResourceFileSystemUtil.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/resources/NativeImageResourceFileSystemUtil.java @@ -28,7 +28,6 @@ import java.io.InputStream; import java.util.Arrays; -import org.graalvm.collections.MapCursor; import org.graalvm.compiler.serviceprovider.JavaVersionUtil; import com.oracle.svm.core.jdk.Resources; @@ -38,12 +37,8 @@ public final class NativeImageResourceFileSystemUtil { private NativeImageResourceFileSystemUtil() { } - public static MapCursor iterator() { - return Resources.singleton().resources().getEntries(); - } - public static byte[] getBytes(String resourceName, boolean readOnly) { - ResourceStorageEntry entry = Resources.singleton().resources().get(resourceName); + ResourceStorageEntry entry = Resources.get(resourceName); if (entry == null) { return new byte[0]; } @@ -56,7 +51,7 @@ public static byte[] getBytes(String resourceName, boolean readOnly) { } public static int getSize(String resourceName) { - ResourceStorageEntry entry = Resources.singleton().resources().get(resourceName); + ResourceStorageEntry entry = Resources.get(resourceName); if (entry == null) { return 0; } else { diff --git a/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/DefaultOptionHandler.java b/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/DefaultOptionHandler.java index a564fd10f79f..6db93f072db9 100644 --- a/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/DefaultOptionHandler.java +++ b/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/DefaultOptionHandler.java @@ -156,6 +156,7 @@ public boolean consume(ArgumentQueue args) { } if (nativeImage.config.useJavaModules()) { nativeImage.addImageBuilderJavaArgs(addModulesOption, addModulesArgs); + nativeImage.addAddedModules(addModulesArgs); } else { NativeImage.showWarning("Ignoring unsupported module option: " + addModulesOption + " " + addModulesArgs); } @@ -337,6 +338,7 @@ public boolean consume(ArgumentQueue args) { } if (nativeImage.config.useJavaModules()) { nativeImage.addImageBuilderJavaArgs(addModulesOption, addModulesArgs); + nativeImage.addAddedModules(addModulesArgs); } else { NativeImage.showWarning("Ignoring unsupported module option: " + addModulesOption + " " + addModulesArgs); } diff --git a/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/NativeImage.java b/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/NativeImage.java index 3a446a2ed9f3..5e9167422da9 100644 --- a/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/NativeImage.java +++ b/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/NativeImage.java @@ -257,6 +257,7 @@ private static String oR(OptionKey option) { private LinkedHashSet enabledLanguages; private final List excludedConfigs = new ArrayList<>(); + private final LinkedHashSet addModules = new LinkedHashSet<>(); protected static class BuildConfiguration { @@ -1161,6 +1162,10 @@ private int completeImageBuild() { return 2; } + if (!addModules.isEmpty()) { + imageBuilderJavaArgs.add("-D" + ModuleSupport.PROPERTY_IMAGE_EXPLICITLY_ADDED_MODULES + "=" + String.join(",", addModules)); + } + List finalImageBuilderJavaArgs = Stream.concat(config.getBuilderJavaArgs().stream(), imageBuilderJavaArgs.stream()).collect(Collectors.toList()); return buildImage(finalImageBuilderJavaArgs, imageBuilderBootClasspath, imageBuilderClasspath, imageBuilderModulePath, imageBuilderArgs, finalImageClasspath, finalImageModulePath); } @@ -1508,6 +1513,10 @@ public void addImageBuilderModulePath(Path modulePathEntry) { imageBuilderModulePath.add(canonicalize(modulePathEntry)); } + public void addAddedModules(String addModulesArg) { + addModules.addAll(Arrays.asList(SubstrateUtil.split(addModulesArg, ","))); + } + void addImageBuilderClasspath(Path classpath) { imageBuilderClasspath.add(canonicalize(classpath)); } diff --git a/substratevm/src/com.oracle.svm.hosted.jdk11/src/com/oracle/svm/hosted/ModuleLayerFeature.java b/substratevm/src/com.oracle.svm.hosted.jdk11/src/com/oracle/svm/hosted/ModuleLayerFeature.java index 3c5130849d29..7105fed83840 100644 --- a/substratevm/src/com.oracle.svm.hosted.jdk11/src/com/oracle/svm/hosted/ModuleLayerFeature.java +++ b/substratevm/src/com.oracle.svm.hosted.jdk11/src/com/oracle/svm/hosted/ModuleLayerFeature.java @@ -49,19 +49,21 @@ import java.util.stream.Collectors; import java.util.stream.Stream; +import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; +import org.graalvm.nativeimage.hosted.Feature; + import com.oracle.graal.pointsto.meta.AnalysisType; import com.oracle.graal.pointsto.meta.AnalysisUniverse; +import com.oracle.svm.core.SubstrateUtil; import com.oracle.svm.core.annotate.AutomaticFeature; import com.oracle.svm.core.hub.DynamicHub; -import com.oracle.svm.core.jdk11.BootModuleLayerSupport; import com.oracle.svm.core.jdk.JDK11OrLater; +import com.oracle.svm.core.jdk11.BootModuleLayerSupport; import com.oracle.svm.core.util.VMError; import com.oracle.svm.util.ModuleSupport; import com.oracle.svm.util.ReflectionUtil; -import org.graalvm.nativeimage.ImageSingletons; -import org.graalvm.nativeimage.Platform; -import org.graalvm.nativeimage.Platforms; -import org.graalvm.nativeimage.hosted.Feature; /** * This feature: @@ -142,6 +144,23 @@ public void afterAnalysis(AfterAnalysisAccess access) { .filter(Module::isNamed) .collect(Collectors.toSet()); + Set extraModules = new HashSet<>(); + + extraModules.addAll(ImageSingletons.lookup(ResourcesFeature.class).includedResourcesModules); + + String explicitlyAddedModules = System.getProperty(ModuleSupport.PROPERTY_IMAGE_EXPLICITLY_ADDED_MODULES, ""); + if (!explicitlyAddedModules.isEmpty()) { + extraModules.addAll(Arrays.asList(SubstrateUtil.split(explicitlyAddedModules, ","))); + } + + extraModules.forEach(moduleName -> { + Optional module = accessImpl.imageClassLoader.findModule(moduleName); + if (module.isEmpty()) { + VMError.shouldNotReachHere("Explicitly required module " + moduleName + " is not available"); + } + analysisReachableNamedModules.add((Module) module.get()); + }); + Set analysisReachableSyntheticModules = analysisReachableNamedModules .stream() .filter(ModuleLayerFeatureUtils::isModuleSynthetic) diff --git a/substratevm/src/com.oracle.svm.hosted.jdk11/src/com/oracle/svm/hosted/jdk/ClassLoaderSupportImplJDK11OrLater.java b/substratevm/src/com.oracle.svm.hosted.jdk11/src/com/oracle/svm/hosted/jdk/ClassLoaderSupportImplJDK11OrLater.java index 40d14bf8d916..fb7be59be2d6 100644 --- a/substratevm/src/com.oracle.svm.hosted.jdk11/src/com/oracle/svm/hosted/jdk/ClassLoaderSupportImplJDK11OrLater.java +++ b/substratevm/src/com.oracle.svm.hosted.jdk11/src/com/oracle/svm/hosted/jdk/ClassLoaderSupportImplJDK11OrLater.java @@ -25,6 +25,11 @@ package com.oracle.svm.hosted.jdk; +import java.io.IOException; +import java.io.InputStream; +import java.lang.module.ModuleReader; +import java.lang.module.ModuleReference; +import java.lang.module.ResolvedModule; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -32,6 +37,7 @@ import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Optional; import java.util.ResourceBundle; import java.util.Set; import java.util.stream.Collectors; @@ -42,25 +48,55 @@ import com.oracle.svm.core.ClassLoaderSupport; import com.oracle.svm.core.annotate.AutomaticFeature; +import com.oracle.svm.core.util.VMError; +import com.oracle.svm.hosted.ClassLoaderSupportImpl; import com.oracle.svm.hosted.FeatureImpl; -import com.oracle.svm.hosted.NativeImageSystemClassLoader; import jdk.internal.module.Modules; -public final class ClassLoaderSupportImplJDK11OrLater extends ClassLoaderSupport { +public final class ClassLoaderSupportImplJDK11OrLater extends ClassLoaderSupportImpl { private final NativeImageClassLoaderSupportJDK11OrLater classLoaderSupport; private final Map> packageToModules; ClassLoaderSupportImplJDK11OrLater(NativeImageClassLoaderSupportJDK11OrLater classLoaderSupport) { + super(classLoaderSupport); this.classLoaderSupport = classLoaderSupport; packageToModules = new HashMap<>(); buildPackageToModulesMap(classLoaderSupport); } @Override - protected boolean isNativeImageClassLoaderImpl(ClassLoader loader) { - return loader == classLoaderSupport.getClassLoader() || loader instanceof NativeImageSystemClassLoader; + public void collectResources(ResourceCollector resourceCollector) { + /* Collect resources from modules */ + NativeImageClassLoaderSupportJDK11OrLater.allLayers(classLoaderSupport.moduleLayerForImageBuild).stream() + .flatMap(moduleLayer -> moduleLayer.configuration().modules().stream()) + .forEach(resolvedModule -> collectResourceFromModule(resourceCollector, resolvedModule)); + + /* Collect remaining resources from classpath */ + super.collectResources(resourceCollector); + } + + private static void collectResourceFromModule(ResourceCollector resourceCollector, ResolvedModule resolvedModule) { + ModuleReference moduleReference = resolvedModule.reference(); + try (ModuleReader moduleReader = moduleReference.open()) { + String moduleName = resolvedModule.name(); + List foundResources = moduleReader.list() + .filter(resourceName -> resourceCollector.isIncluded(moduleName, resourceName)) + .collect(Collectors.toList()); + + for (String resName : foundResources) { + Optional content = moduleReader.open(resName); + if (content.isEmpty()) { + continue; + } + try (InputStream is = content.get()) { + resourceCollector.addResource(moduleName, resName, is); + } + } + } catch (IOException e) { + throw VMError.shouldNotReachHere(e); + } } @Override @@ -84,7 +120,7 @@ public List getResourceBundle(String bundleSpec, Locale locale) } if (modules.isEmpty()) { /* If bundle is not located in any module get it via classloader (from ALL_UNNAMED) */ - return Collections.singletonList(ResourceBundle.getBundle(bundleName, locale, classLoaderSupport.getClassLoader())); + return super.getResourceBundle(bundleName, locale); } ArrayList resourceBundles = new ArrayList<>(); for (Module module : modules) { diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ClassLoaderSupportImpl.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ClassLoaderSupportImpl.java index 2417b18b2fe3..daeb397558c2 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ClassLoaderSupportImpl.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ClassLoaderSupportImpl.java @@ -25,10 +25,28 @@ package com.oracle.svm.hosted; +import static com.oracle.svm.core.jdk.Resources.RESOURCES_INTERNAL_PATH_SEPARATOR; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayDeque; +import java.util.ArrayList; import java.util.Collections; +import java.util.Comparator; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Locale; +import java.util.Map; import java.util.ResourceBundle; +import java.util.Set; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.stream.Stream; import org.graalvm.compiler.serviceprovider.JavaVersionUtil; import org.graalvm.nativeimage.ImageSingletons; @@ -36,12 +54,16 @@ import com.oracle.svm.core.ClassLoaderSupport; import com.oracle.svm.core.annotate.AutomaticFeature; +import com.oracle.svm.core.util.ClasspathUtils; +import com.oracle.svm.core.util.UserError; -public final class ClassLoaderSupportImpl extends ClassLoaderSupport { +public class ClassLoaderSupportImpl extends ClassLoaderSupport { + private final AbstractNativeImageClassLoaderSupport classLoaderSupport; private final ClassLoader imageClassLoader; - ClassLoaderSupportImpl(NativeImageClassLoaderSupport classLoaderSupport) { + protected ClassLoaderSupportImpl(AbstractNativeImageClassLoaderSupport classLoaderSupport) { + this.classLoaderSupport = classLoaderSupport; this.imageClassLoader = classLoaderSupport.getClassLoader(); } @@ -50,6 +72,92 @@ protected boolean isNativeImageClassLoaderImpl(ClassLoader loader) { return loader == imageClassLoader || loader instanceof NativeImageSystemClassLoader; } + @Override + public void collectResources(ResourceCollector resourceCollector) { + classLoaderSupport.classpath().stream().forEach(classpathFile -> { + try { + if (Files.isDirectory(classpathFile)) { + scanDirectory(classpathFile, resourceCollector); + } else if (ClasspathUtils.isJar(classpathFile)) { + scanJar(classpathFile, resourceCollector); + } + } catch (IOException ex) { + throw UserError.abort("Unable to handle classpath element '%s'. Make sure that all classpath entries are either directories or valid jar files.", classpathFile); + } + }); + } + + private static void scanDirectory(Path root, ResourceCollector collector) throws IOException { + Map> matchedDirectoryResources = new HashMap<>(); + Set allEntries = new HashSet<>(); + + ArrayDeque queue = new ArrayDeque<>(); + queue.push(root); + while (!queue.isEmpty()) { + Path entry = queue.pop(); + + /* Resources always use / as the separator, as do our resource inclusion patterns */ + String relativeFilePath; + if (entry != root) { + relativeFilePath = root.relativize(entry).toString().replace(File.separatorChar, RESOURCES_INTERNAL_PATH_SEPARATOR); + allEntries.add(relativeFilePath); + } else { + relativeFilePath = ""; + } + + if (Files.isDirectory(entry)) { + if (collector.isIncluded(null, relativeFilePath)) { + matchedDirectoryResources.put(relativeFilePath, new ArrayList<>()); + } + try (Stream files = Files.list(entry)) { + files.forEach(queue::push); + } + } else { + if (collector.isIncluded(null, relativeFilePath)) { + try (InputStream is = Files.newInputStream(entry)) { + collector.addResource(null, relativeFilePath, is); + } + } + } + } + + for (String entry : allEntries) { + int last = entry.lastIndexOf(RESOURCES_INTERNAL_PATH_SEPARATOR); + String key = last == -1 ? "" : entry.substring(0, last); + List dirContent = matchedDirectoryResources.get(key); + if (dirContent != null && !dirContent.contains(entry)) { + dirContent.add(entry.substring(last + 1)); + } + } + + matchedDirectoryResources.forEach((dir, content) -> { + content.sort(Comparator.naturalOrder()); + collector.addDirectoryResource(null, dir, String.join(System.lineSeparator(), content)); + }); + } + + private static void scanJar(Path jarPath, ResourceCollector collector) throws IOException { + try (JarFile jf = new JarFile(jarPath.toFile())) { + Enumeration entries = jf.entries(); + while (entries.hasMoreElements()) { + JarEntry entry = entries.nextElement(); + if (entry.isDirectory()) { + String dirName = entry.getName().substring(0, entry.getName().length() - 1); + if (collector.isIncluded(null, dirName)) { + // Register the directory with empty content to preserve Java behavior + collector.addDirectoryResource(null, dirName, ""); + } + } else { + if (collector.isIncluded(null, entry.getName())) { + try (InputStream is = jf.getInputStream(entry)) { + collector.addResource(null, entry.getName(), is); + } + } + } + } + } + } + @Override public List getResourceBundle(String bundleName, Locale locale) { return Collections.singletonList(ResourceBundle.getBundle(bundleName, locale, imageClassLoader)); @@ -66,6 +174,6 @@ public boolean isInConfiguration(IsInConfigurationAccess access) { @Override public void afterRegistration(AfterRegistrationAccess a) { FeatureImpl.AfterRegistrationAccessImpl access = (FeatureImpl.AfterRegistrationAccessImpl) a; - ImageSingletons.add(ClassLoaderSupport.class, new ClassLoaderSupportImpl((NativeImageClassLoaderSupport) access.getImageClassLoader().classLoaderSupport)); + ImageSingletons.add(ClassLoaderSupport.class, new ClassLoaderSupportImpl(access.getImageClassLoader().classLoaderSupport)); } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ResourcesFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ResourcesFeature.java index a6b7aba6637e..7d1252f684d7 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ResourcesFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ResourcesFeature.java @@ -27,59 +27,45 @@ import static com.oracle.svm.core.jdk.Resources.RESOURCES_INTERNAL_PATH_SEPARATOR; -import java.io.File; -import java.io.IOException; import java.io.InputStream; import java.nio.file.FileSystem; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.ArrayDeque; -import java.util.ArrayList; import java.util.Collection; import java.util.Collections; -import java.util.Comparator; -import java.util.Enumeration; -import java.util.HashMap; import java.util.HashSet; -import java.util.List; import java.util.Locale; -import java.util.Map; +import java.util.Optional; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; -import java.util.jar.JarEntry; -import java.util.jar.JarFile; import java.util.regex.Pattern; import java.util.stream.Collectors; -import java.util.stream.Stream; import org.graalvm.compiler.debug.DebugContext; import org.graalvm.compiler.options.Option; import org.graalvm.compiler.options.OptionType; -import org.graalvm.compiler.serviceprovider.JavaVersionUtil; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.hosted.Feature; import org.graalvm.nativeimage.impl.ConfigurationCondition; +import com.oracle.svm.core.ClassLoaderSupport; +import com.oracle.svm.core.ClassLoaderSupport.ResourceCollector; +import com.oracle.svm.core.SubstrateUtil; import com.oracle.svm.core.annotate.AutomaticFeature; import com.oracle.svm.core.configure.ConfigurationFile; import com.oracle.svm.core.configure.ConfigurationFiles; import com.oracle.svm.core.configure.ResourceConfigurationParser; import com.oracle.svm.core.configure.ResourcesRegistry; import com.oracle.svm.core.jdk.Resources; -import com.oracle.svm.hosted.jdk.localization.LocalizationFeature; import com.oracle.svm.core.jdk.resources.NativeImageResourceFileAttributes; import com.oracle.svm.core.jdk.resources.NativeImageResourceFileAttributesView; import com.oracle.svm.core.jdk.resources.NativeImageResourceFileSystem; import com.oracle.svm.core.jdk.resources.NativeImageResourceFileSystemProvider; import com.oracle.svm.core.option.HostedOptionKey; import com.oracle.svm.core.option.LocatableMultiOptionValue; -import com.oracle.svm.core.util.ClasspathUtils; import com.oracle.svm.core.util.UserError; import com.oracle.svm.core.util.VMError; -import com.oracle.svm.hosted.FeatureImpl.BeforeAnalysisAccessImpl; import com.oracle.svm.hosted.FeatureImpl.DuringAnalysisAccessImpl; import com.oracle.svm.hosted.config.ConfigurationParserUtils; -import com.oracle.svm.util.ModuleSupport; +import com.oracle.svm.hosted.jdk.localization.LocalizationFeature; /** *

@@ -124,6 +110,9 @@ public static class Options { private final Set resourcePatternWorkSet = Collections.newSetFromMap(new ConcurrentHashMap<>()); private final Set excludedResourcePatterns = Collections.newSetFromMap(new ConcurrentHashMap<>()); private int loadedConfigurations; + private ImageClassLoader imageClassLoader; + + public final Set includedResourcesModules = new HashSet<>(); private class ResourcesRegistryImpl extends ConditionalConfigurationRegistry implements ResourcesRegistry { private ConfigurationTypeResolver configurationTypeResolver; @@ -183,7 +172,7 @@ public void addResourceBundles(ConfigurationCondition condition, String basename @Override public void afterRegistration(AfterRegistrationAccess a) { FeatureImpl.AfterRegistrationAccessImpl access = (FeatureImpl.AfterRegistrationAccessImpl) a; - ImageClassLoader imageClassLoader = access.getImageClassLoader(); + imageClassLoader = access.getImageClassLoader(); ImageSingletons.add(ResourcesRegistry.class, new ResourcesRegistryImpl(new ConfigurationTypeResolver("resource configuration", imageClassLoader, NativeImageOptions.AllowIncompleteClasspath.getValue()))); } @@ -194,7 +183,6 @@ private static ResourcesRegistryImpl resourceRegistryImpl() { @Override public void beforeAnalysis(BeforeAnalysisAccess access) { - ImageClassLoader imageClassLoader = ((BeforeAnalysisAccessImpl) access).getImageClassLoader(); ResourceConfigurationParser parser = new ResourceConfigurationParser(ImageSingletons.lookup(ResourcesRegistry.class), ConfigurationFiles.Options.StrictConfiguration.getValue()); loadedConfigurations = ConfigurationParserUtils.parseAndRegisterConfigurations(parser, imageClassLoader, "resource", ConfigurationFiles.Options.ResourceConfigurationFiles, ConfigurationFiles.Options.ResourceConfigurationResources, @@ -205,6 +193,64 @@ public void beforeAnalysis(BeforeAnalysisAccess access) { resourceRegistryImpl().flushConditionalConfiguration(access); } + private static final class ResourceCollectorImpl implements ResourceCollector { + private final DebugContext debugContext; + private final ResourcePattern[] includePatterns; + private final ResourcePattern[] excludePatterns; + private final Set includedResourcesModules; + + private ResourceCollectorImpl(DebugContext debugContext, ResourcePattern[] includePatterns, ResourcePattern[] excludePatterns, Set includedResourcesModules) { + this.debugContext = debugContext; + this.includePatterns = includePatterns; + this.excludePatterns = excludePatterns; + this.includedResourcesModules = includedResourcesModules; + } + + @Override + public boolean isIncluded(String moduleName, String resourceName) { + VMError.guarantee(!resourceName.contains("\\"), "Resource path contains backslash!"); + String relativePathWithTrailingSlash = resourceName + RESOURCES_INTERNAL_PATH_SEPARATOR; + + for (ResourcePattern rp : excludePatterns) { + if (rp.moduleName != null && !rp.moduleName.equals(moduleName)) { + continue; + } + if (rp.pattern.matcher(resourceName).matches() || rp.pattern.matcher(relativePathWithTrailingSlash).matches()) { + return false; + } + } + + for (ResourcePattern rp : includePatterns) { + if (rp.moduleName != null && !rp.moduleName.equals(moduleName)) { + continue; + } + if (rp.pattern.matcher(resourceName).matches() || rp.pattern.matcher(relativePathWithTrailingSlash).matches()) { + return true; + } + } + + return false; + } + + @Override + public void addResource(String moduleName, String resourceName, InputStream resourceStream) { + collectModuleName(moduleName); + registerResource(debugContext, moduleName, resourceName, resourceStream); + } + + @Override + public void addDirectoryResource(String moduleName, String dir, String content) { + collectModuleName(moduleName); + registerDirectoryResource(debugContext, moduleName, dir, content); + } + + private void collectModuleName(String moduleName) { + if (moduleName != null) { + includedResourcesModules.add(moduleName); + } + } + } + @Override public void duringAnalysis(DuringAnalysisAccess access) { resourceRegistryImpl().flushConditionalConfiguration(access); @@ -213,52 +259,47 @@ public void duringAnalysis(DuringAnalysisAccess access) { } access.requireAnalysisIteration(); - DuringAnalysisAccessImpl accessImpl = (DuringAnalysisAccessImpl) access; - DebugContext debugContext = accessImpl.getDebugContext(); - final Pattern[] includePatterns = compilePatterns(resourcePatternWorkSet); - final Pattern[] excludePatterns = compilePatterns(excludedResourcePatterns); - - if (JavaVersionUtil.JAVA_SPEC > 8) { - try { - ModuleSupport.findResourcesInModules(name -> matches(includePatterns, excludePatterns, name), - (resName, content) -> registerResource(debugContext, resName, content)); - } catch (IOException ex) { - throw UserError.abort(ex, "Can not read resources from modules. This is possible due to incorrect module path or missing module visibility directives"); - } - } - /* - * Since IncludeResources takes regular expressions it's safer to disallow passing - * more than one regex with a single IncludeResources option. Note that it's still - * possible pass multiple IncludeResources regular expressions by passing each as - * its own IncludeResources option. E.g. - * @formatter:off - * -H:IncludeResources=nobel/prizes.json -H:IncludeResources=fields/prizes.json - * @formatter:on - */ - - ImageClassLoader loader = accessImpl.imageClassLoader; - Stream.concat(loader.modulepath().stream(), loader.classpath().stream()).distinct().forEach(classpathFile -> { - try { - if (Files.isDirectory(classpathFile)) { - scanDirectory(debugContext, classpathFile, includePatterns, excludePatterns); - } else if (ClasspathUtils.isJar(classpathFile)) { - scanJar(debugContext, classpathFile, includePatterns, excludePatterns); - } - } catch (IOException ex) { - throw UserError.abort("Unable to handle classpath element '%s'. Make sure that all classpath entries are either directories or valid jar files.", classpathFile); - } - }); + DebugContext debugContext = ((DuringAnalysisAccessImpl) access).getDebugContext(); + ResourcePattern[] includePatterns = compilePatterns(resourcePatternWorkSet); + ResourcePattern[] excludePatterns = compilePatterns(excludedResourcePatterns); + ResourceCollectorImpl collector = new ResourceCollectorImpl(debugContext, includePatterns, excludePatterns, includedResourcesModules); + + ImageSingletons.lookup(ClassLoaderSupport.class).collectResources(collector); resourcePatternWorkSet.clear(); } - private static Pattern[] compilePatterns(Set patterns) { + private ResourcePattern[] compilePatterns(Set patterns) { return patterns.stream() .filter(s -> s.length() > 0) - .map(Pattern::compile) + .map(this::makeResourcePattern) .collect(Collectors.toList()) - .toArray(new Pattern[]{}); + .toArray(new ResourcePattern[]{}); + } + + private ResourcePattern makeResourcePattern(String rawPattern) { + String[] moduleNameWithPattern = SubstrateUtil.split(rawPattern, ":", 2); + if (moduleNameWithPattern.length < 2) { + return new ResourcePattern(null, Pattern.compile(moduleNameWithPattern[0])); + } else { + Optional optModule = imageClassLoader.findModule(moduleNameWithPattern[0]); + if (optModule.isPresent()) { + return new ResourcePattern(moduleNameWithPattern[0], Pattern.compile(moduleNameWithPattern[1])); + } else { + throw UserError.abort("Resource pattern \"" + rawPattern + "\"s specifies unknown module " + moduleNameWithPattern[0]); + } + } + } + + private static final class ResourcePattern { + final String moduleName; + final Pattern pattern; + + private ResourcePattern(String moduleName, Pattern pattern) { + this.moduleName = moduleName; + this.pattern = pattern; + } } @Override @@ -277,108 +318,21 @@ public void beforeCompilation(BeforeCompilationAccess access) { } } - private static void scanDirectory(DebugContext debugContext, Path root, Pattern[] includePatterns, Pattern[] excludePatterns) throws IOException { - Map> matchedDirectoryResources = new HashMap<>(); - Set allEntries = new HashSet<>(); - - ArrayDeque queue = new ArrayDeque<>(); - queue.push(root); - while (!queue.isEmpty()) { - Path entry = queue.pop(); - - /* Resources always use / as the separator, as do our resource inclusion patterns */ - String relativeFilePath; - if (entry != root) { - relativeFilePath = root.relativize(entry).toString().replace(File.separatorChar, RESOURCES_INTERNAL_PATH_SEPARATOR); - allEntries.add(relativeFilePath); - } else { - relativeFilePath = ""; - } - - if (Files.isDirectory(entry)) { - if (matches(includePatterns, excludePatterns, relativeFilePath)) { - matchedDirectoryResources.put(relativeFilePath, new ArrayList<>()); - } - try (Stream files = Files.list(entry)) { - files.forEach(queue::push); - } - } else { - if (matches(includePatterns, excludePatterns, relativeFilePath)) { - try (InputStream is = Files.newInputStream(entry)) { - registerResource(debugContext, relativeFilePath, is); - } - } - } - } - - for (String entry : allEntries) { - int last = entry.lastIndexOf(RESOURCES_INTERNAL_PATH_SEPARATOR); - String key = last == -1 ? "" : entry.substring(0, last); - List dirContent = matchedDirectoryResources.get(key); - if (dirContent != null && !dirContent.contains(entry)) { - dirContent.add(entry.substring(last + 1)); - } - } - - matchedDirectoryResources.forEach((dir, content) -> { - content.sort(Comparator.naturalOrder()); - registerDirectoryResource(debugContext, dir, String.join(System.lineSeparator(), content)); - }); - } - - private static void scanJar(DebugContext debugContext, Path jarPath, Pattern[] includePatterns, Pattern[] excludePatterns) throws IOException { - try (JarFile jf = new JarFile(jarPath.toFile())) { - Enumeration entries = jf.entries(); - while (entries.hasMoreElements()) { - JarEntry entry = entries.nextElement(); - if (entry.isDirectory()) { - String dirName = entry.getName().substring(0, entry.getName().length() - 1); - if (matches(includePatterns, excludePatterns, dirName)) { - // Register the directory with empty content to preserve Java behavior - registerDirectoryResource(debugContext, dirName, ""); - } - } else { - if (matches(includePatterns, excludePatterns, entry.getName())) { - try (InputStream is = jf.getInputStream(entry)) { - registerResource(debugContext, entry.getName(), is); - } - } - } - } - } - } - - private static boolean matches(Pattern[] includePatterns, Pattern[] excludePatterns, String relativePath) { - VMError.guarantee(!relativePath.contains("\\"), "Resource path contains backslash!"); - String relativePathWithTrailingSlash = relativePath + RESOURCES_INTERNAL_PATH_SEPARATOR; - for (Pattern p : excludePatterns) { - if (p.matcher(relativePath).matches() || p.matcher(relativePathWithTrailingSlash).matches()) { - return false; - } - } - - for (Pattern p : includePatterns) { - if (p.matcher(relativePath).matches() || p.matcher(relativePathWithTrailingSlash).matches()) { - return true; - } - } - - return false; - } - @SuppressWarnings("try") - private static void registerResource(DebugContext debugContext, String resourceName, InputStream resourceStream) { + private static void registerResource(DebugContext debugContext, String moduleName, String resourceName, InputStream resourceStream) { try (DebugContext.Scope s = debugContext.scope("registerResource")) { - debugContext.log(DebugContext.VERBOSE_LEVEL, "ResourcesFeature: registerResource: " + resourceName); - Resources.registerResource(resourceName, resourceStream); + String moduleNamePrefix = moduleName == null ? "" : moduleName + ":"; + debugContext.log(DebugContext.VERBOSE_LEVEL, "ResourcesFeature: registerResource: %s%s", moduleNamePrefix, resourceName); + Resources.registerResource(moduleName, resourceName, resourceStream); } } @SuppressWarnings("try") - private static void registerDirectoryResource(DebugContext debugContext, String dir, String content) { + private static void registerDirectoryResource(DebugContext debugContext, String moduleName, String dir, String content) { try (DebugContext.Scope s = debugContext.scope("registerResource")) { - debugContext.log(DebugContext.VERBOSE_LEVEL, "ResourcesFeature: registerResource: " + dir); - Resources.registerDirectoryResource(dir, content); + String moduleNamePrefix = moduleName == null ? "" : moduleName + ":"; + debugContext.log(DebugContext.VERBOSE_LEVEL, "ResourcesFeature: registerResource: %s%s", moduleNamePrefix, moduleName, dir); + Resources.registerDirectoryResource(moduleName, dir, content); } } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ServiceLoaderFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ServiceLoaderFeature.java index a77b865d2793..3f504ac8b767 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ServiceLoaderFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ServiceLoaderFeature.java @@ -362,7 +362,7 @@ private boolean handleType(AnalysisType type, DuringAnalysisAccessImpl access) { try (DebugContext.Scope s = debugContext.scope("registerResource")) { debugContext.log("ServiceLoaderFeature: registerResource: " + serviceResourceLocation); } - Resources.registerResource(serviceResourceLocation, new ByteArrayInputStream(newResourceValue.toString().getBytes(StandardCharsets.UTF_8))); + Resources.registerResource(null, serviceResourceLocation, new ByteArrayInputStream(newResourceValue.toString().getBytes(StandardCharsets.UTF_8))); /* Ensure that the static analysis runs again for the new implementation classes. */ access.requireAnalysisIteration(); diff --git a/substratevm/src/com.oracle.svm.util.jdk11/src/com/oracle/svm/util/ModuleSupport.java b/substratevm/src/com.oracle.svm.util.jdk11/src/com/oracle/svm/util/ModuleSupport.java index 7fd67edbf257..477f4e449790 100644 --- a/substratevm/src/com.oracle.svm.util.jdk11/src/com/oracle/svm/util/ModuleSupport.java +++ b/substratevm/src/com.oracle.svm.util.jdk11/src/com/oracle/svm/util/ModuleSupport.java @@ -24,18 +24,9 @@ */ package com.oracle.svm.util; -import java.io.IOException; -import java.io.InputStream; -import java.lang.module.ModuleReader; -import java.lang.module.ModuleReference; -import java.lang.module.ResolvedModule; -import java.util.List; import java.util.NoSuchElementException; import java.util.Optional; import java.util.Set; -import java.util.function.BiConsumer; -import java.util.function.Predicate; -import java.util.stream.Collectors; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; @@ -133,35 +124,4 @@ public static void exportAndOpenPackageToUnnamed(String name, String pkg, boolea public static String getModuleName(Class clazz) { return clazz.getModule().getName(); } - - /** - * In the modules of the boot module layer, filters all resources that match the given - * predicate, and calls the operation on the matched resources. This is a temporary solution - * until we fully support modules in native-image - * - * @param resourceNameFilter predicate applied to all resource names in the module - * @param operation a function to process matched resources, it receives the name of the - * resources as the first argument and an open stream as the second argument - */ - @SuppressWarnings("unused") - public static void findResourcesInModules(Predicate resourceNameFilter, BiConsumer operation) throws IOException { - for (ResolvedModule resolvedModule : ModuleLayer.boot().configuration().modules()) { - ModuleReference modRef = resolvedModule.reference(); - try (ModuleReader moduleReader = modRef.open()) { - final List resources = moduleReader.list() - .filter(resourceNameFilter) - .collect(Collectors.toList()); - - for (String resName : resources) { - Optional content = moduleReader.open(resName); - if (content.isEmpty()) { - continue; - } - InputStream is = content.get(); - operation.accept(resName, is); - is.close(); - } - } - } - } } diff --git a/substratevm/src/com.oracle.svm.util/src/com/oracle/svm/util/ModuleSupport.java b/substratevm/src/com.oracle.svm.util/src/com/oracle/svm/util/ModuleSupport.java index 677786c41e06..512fe2af9f99 100644 --- a/substratevm/src/com.oracle.svm.util/src/com/oracle/svm/util/ModuleSupport.java +++ b/substratevm/src/com.oracle.svm.util/src/com/oracle/svm/util/ModuleSupport.java @@ -24,11 +24,7 @@ */ package com.oracle.svm.util; -import java.io.IOException; -import java.io.InputStream; import java.util.NoSuchElementException; -import java.util.function.BiConsumer; -import java.util.function.Predicate; import org.graalvm.compiler.serviceprovider.JavaVersionUtil; import org.graalvm.nativeimage.Platform; @@ -90,20 +86,4 @@ public static String getModuleName(Class clazz) { assert JavaVersionUtil.JAVA_SPEC <= 8; return null; } - - /** - * In the modules of the boot module layer, filters all resources that match the given - * predicate, and calls the operation on the matched resources. This is a temporary solution - * until we fully support modules in native-image - * - * @param resourceNameFilter predicate applied to all resource names in the module - * @param operation a function to process matched resources, it receives the name of the - * resources as the first argument and an open stream as the second argument - */ - @SuppressWarnings("unused") - public static void findResourcesInModules(Predicate resourceNameFilter, BiConsumer operation) throws IOException { - /* Nothing to do in JDK 8 version. JDK 11 version provides a proper implementation. */ - assert JavaVersionUtil.JAVA_SPEC <= 8; - throw new IOException("find resources in modules can not be called in java 8 or less"); - } } diff --git a/substratevm/src/com.oracle.svm.util/src/com/oracle/svm/util/ModuleSupportBase.java b/substratevm/src/com.oracle.svm.util/src/com/oracle/svm/util/ModuleSupportBase.java index ccde5b99230f..81db8ea69e57 100644 --- a/substratevm/src/com.oracle.svm.util/src/com/oracle/svm/util/ModuleSupportBase.java +++ b/substratevm/src/com.oracle.svm.util/src/com/oracle/svm/util/ModuleSupportBase.java @@ -29,6 +29,8 @@ public class ModuleSupportBase { public static final String ENV_VAR_USE_MODULE_SYSTEM = "USE_NATIVE_IMAGE_JAVA_PLATFORM_MODULE_SYSTEM"; + public static final String PROPERTY_IMAGE_EXPLICITLY_ADDED_MODULES = "org.graalvm.nativeimage.module.addmods"; + public static final boolean modulePathBuild = Boolean.parseBoolean(System.getenv().get(ENV_VAR_USE_MODULE_SYSTEM)); } diff --git a/substratevm/src/native-image-module-tests/hello.app/pom.xml b/substratevm/src/native-image-module-tests/hello.app/pom.xml index a80c3542f343..11f29d669e09 100644 --- a/substratevm/src/native-image-module-tests/hello.app/pom.xml +++ b/substratevm/src/native-image-module-tests/hello.app/pom.xml @@ -48,8 +48,8 @@ questions. maven-compiler-plugin 3.8.0 - 11 - 11 + 9 + 9 --add-exports=moduletests.hello.lib/hello.privateLib=moduletests.hello.app --add-exports=moduletests.hello.lib/hello.privateLib2=moduletests.hello.app diff --git a/substratevm/src/native-image-module-tests/hello.app/src/main/java/hello/Main.java b/substratevm/src/native-image-module-tests/hello.app/src/main/java/hello/Main.java index 387386b55820..ea42cf3f5aa2 100644 --- a/substratevm/src/native-image-module-tests/hello.app/src/main/java/hello/Main.java +++ b/substratevm/src/native-image-module-tests/hello.app/src/main/java/hello/Main.java @@ -26,9 +26,11 @@ import hello.lib.Greeter; +import java.io.IOException; import java.lang.module.Configuration; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.util.Scanner; import java.util.Set; import java.util.stream.Collectors; @@ -52,6 +54,8 @@ public static void main(String[] args) throws NoSuchMethodException, InvocationT greetMethod.invoke(null); testBootLayer(helloAppModule, helloLibModule); + + testResourceAccess(helloAppModule, helloLibModule); } private static void failIfAssertionsAreDisabled() { @@ -61,7 +65,7 @@ private static void failIfAssertionsAreDisabled() { throw new AssertionError("This example requires that assertions are enabled (-ea)"); } } - + private static void testModuleObjects(Module helloAppModule, Module helloLibModule) { assert helloAppModule.getName().equals("moduletests.hello.app"); assert helloAppModule.isExported(Main.class.getPackageName()); @@ -116,4 +120,33 @@ private static void testBootLayer(Module helloAppModule, Module helloLibModule) assert ModuleLayer.boot().modules().contains(helloAppModule); assert ModuleLayer.boot().modules().contains(helloLibModule); } + + private static void testResourceAccess(Module helloAppModule, Module helloLibModule) { + System.out.println("Now testing resources in modules"); + + String helloAppModuleResourceContents; + String sameResourcePathName = "resource-file.txt"; + + // Test Module.getResourceAsStream(String) + try (Scanner s = new Scanner(helloAppModule.getResourceAsStream(sameResourcePathName))) { + helloAppModuleResourceContents = s.nextLine(); + } catch (IOException e) { + throw new AssertionError("Unable to access resource " + sameResourcePathName + " from " + helloAppModule); + } + String helloLibModuleResourceContents; + try (Scanner s = new Scanner(helloLibModule.getResourceAsStream(sameResourcePathName))) { + helloLibModuleResourceContents = s.nextLine(); + } catch (IOException e) { + throw new AssertionError("Unable to access resource " + sameResourcePathName + " from " + helloLibModule); + } + assert !helloAppModuleResourceContents.equals(helloLibModuleResourceContents) : sameResourcePathName + " not recognized as different resources"; + + // Test Class.getResourceAsStream(String) + try (Scanner s = new Scanner(Main.class.getResourceAsStream("/" + sameResourcePathName))) { + assert helloAppModuleResourceContents.equals(s.nextLine()) : "Class.getResourceAsStream(String) result differs from Module.getResourceAsStream(String) result"; + } + try (Scanner s = new Scanner(Greeter.class.getResourceAsStream("/" + sameResourcePathName))) { + assert helloLibModuleResourceContents.equals(s.nextLine()) : "Class.getResourceAsStream(String) result differs from Module.getResourceAsStream(String) result"; + } + } } diff --git a/substratevm/src/native-image-module-tests/hello.app/src/main/resources/META-INF/native-image/resource-config.json b/substratevm/src/native-image-module-tests/hello.app/src/main/resources/META-INF/native-image/resource-config.json new file mode 100644 index 000000000000..4eb8c21e66f9 --- /dev/null +++ b/substratevm/src/native-image-module-tests/hello.app/src/main/resources/META-INF/native-image/resource-config.json @@ -0,0 +1,10 @@ +{ + "resources":{ + "includes":[ + { + "pattern": "^resource-file.txt$" + } + ] + }, + "bundles":[] +} diff --git a/substratevm/src/native-image-module-tests/hello.app/src/main/resources/resource-file.txt b/substratevm/src/native-image-module-tests/hello.app/src/main/resources/resource-file.txt new file mode 100644 index 000000000000..89928fd05e61 --- /dev/null +++ b/substratevm/src/native-image-module-tests/hello.app/src/main/resources/resource-file.txt @@ -0,0 +1 @@ +Resource file in module moduletests.hello.app \ No newline at end of file diff --git a/substratevm/src/native-image-module-tests/hello.lib/src/main/resources/resource-file.txt b/substratevm/src/native-image-module-tests/hello.lib/src/main/resources/resource-file.txt new file mode 100644 index 000000000000..acb9ccba802e --- /dev/null +++ b/substratevm/src/native-image-module-tests/hello.lib/src/main/resources/resource-file.txt @@ -0,0 +1 @@ +Resource file in module moduletests.hello.lib \ No newline at end of file