Skip to content

Commit

Permalink
Use ModuleFinder to collect names of application modules
Browse files Browse the repository at this point in the history
  • Loading branch information
olpaw authored and jerboaa committed Dec 21, 2023
1 parent fb08a7e commit 75aff5f
Showing 1 changed file with 26 additions and 90 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,10 @@
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Properties;
import java.util.Set;
Expand All @@ -62,7 +60,6 @@
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.jar.Attributes;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
Expand Down Expand Up @@ -1528,8 +1525,6 @@ protected int buildImage(List<String> javaArgs, LinkedHashSet<Path> cp, LinkedHa
arguments.addAll(strings);
}

String javaExecutable = canonicalize(config.getJavaExecutable()).toString();

if (useBundle()) {
LogUtils.warning("Native Image Bundles are an experimental feature.");
}
Expand All @@ -1542,20 +1537,24 @@ protected int buildImage(List<String> javaArgs, LinkedHashSet<Path> cp, LinkedHa
List<Path> finalImageClassPath = imagecp.stream().map(substituteClassPath).collect(Collectors.toList());

Function<Path, Path> substituteModulePath = useBundle() ? bundleSupport::substituteModulePath : Function.identity();
List<Path> substitutedImageModulePath = imagemp.stream().map(substituteModulePath).toList();
List<Path> finalImageModulePath = imagemp.stream().map(substituteModulePath).toList();

Map<String, Path> modules = listModulesFromPath(javaExecutable, Stream.concat(mp.stream(), imagemp.stream()).distinct().toList());
Map<String, Path> applicationModules = getApplicationModules(finalImageModulePath);
if (!addModules.isEmpty()) {

arguments.add("-D" + ModuleSupport.PROPERTY_IMAGE_EXPLICITLY_ADDED_MODULES + "=" +
String.join(",", addModules));

List<String> addModulesForBuilderVM = new ArrayList<>();
for (String module : addModules) {
Path jarPath = modules.get(module);
if (jarPath == null) {
// boot module
addModulesForBuilderVM.add(module);
for (String moduleNameInAddModules : addModules) {
if (!applicationModules.containsKey(moduleNameInAddModules)) {
/*
* Module names given to native-image --add-modules that are not referring
* modules that are passed to native-image via -p/--module-path are considered
* to be part of the module-layer that contains the builder itself. Those module
* names need to be passed as --add-modules arguments to the builder VM.
*/
addModulesForBuilderVM.add(moduleNameInAddModules);
}
}

Expand Down Expand Up @@ -1586,43 +1585,20 @@ protected int buildImage(List<String> javaArgs, LinkedHashSet<Path> cp, LinkedHa
Path keepAliveFileInContainer = Path.of("/keep_alive");
arguments.addAll(Arrays.asList(SubstrateOptions.KEEP_ALIVE_PREFIX, (useContainer ? keepAliveFileInContainer : keepAliveFile).toString()));

/*
* Workaround for GR-47186: Native image cannot handle modules on the image module path,
* that are also already installed in the JDK as boot module. As a workaround we filter all
* modules from the module-path that are either already installed in the JDK as boot module,
* or were explicitly added to the builder module-path.
*
* First compute all module-jar paths that are not on the builder module-path.
*/
Set<Path> nonBuilderModulePaths = modules.values().stream()
.filter(Objects::nonNull)
.filter(Predicate.not(mp::contains))
.collect(Collectors.toSet());

/*
* Now we need to filter the substituted module path list for all the modules that may
* remain on the module-path.
*
* This should normally not be necessary, as the nonBuilderModulePaths should already be the
* set of jar files for the image module path. Nevertheless, we use the original definition
* of the module path to preserve the order of the original module path and as a precaution
* to protect against --list-modules returning too many modules.
*/
List<Path> finalImageModulePath = substitutedImageModulePath.stream()
.filter(nonBuilderModulePaths::contains)
.toList();

List<String> finalImageBuilderArgs = createImageBuilderArgs(finalImageArgs, finalImageClassPath, finalImageModulePath);

/* Construct ProcessBuilder command from final arguments */
List<String> command = new ArrayList<>();
List<String> completeCommandList = new ArrayList<>();

String javaExecutable;
if (useContainer) {
ContainerSupport.replacePaths(arguments, config.getJavaHome(), bundleSupport.rootDir);
ContainerSupport.replacePaths(finalImageBuilderArgs, config.getJavaHome(), bundleSupport.rootDir);
Path binJava = Paths.get("bin", "java");
javaExecutable = ContainerSupport.GRAAL_VM_HOME.resolve(binJava).toString();
} else {
javaExecutable = canonicalize(config.getJavaExecutable()).toString();
}

Path argFile = createVMInvocationArgumentFile(arguments);
Expand Down Expand Up @@ -1724,65 +1700,25 @@ protected int buildImage(List<String> javaArgs, LinkedHashSet<Path> cp, LinkedHa
}
}

/**
* Resolves and lists all modules given a module path.
*
* @see #callListModules(String, List)
*/
private Map<String, Path> listModulesFromPath(String javaExecutable, Collection<Path> modulePath) {
if (modulePath.isEmpty() || !config.modulePathBuild) {
private Map<String, Path> getApplicationModules(Collection<Path> modulePath) {
if (!config.modulePathBuild || modulePath.isEmpty()) {
return Map.of();
}
String modulePathEntries = modulePath.stream()
.map(Path::toString)
.collect(Collectors.joining(File.pathSeparator));
return callListModules(javaExecutable, List.of("-p", modulePathEntries));
}

/**
* Calls <code>java $arguments --list-modules</code> to list all modules and parse the output.
* The output consists of a map with module name as key and {@link Path} to jar file if the
* module is not installed as part of the JDK. If the module is installed as part of the
* jdk/boot-layer then a <code>null</code> path will be returned.
* <p>
* This is a much more robust solution then trying to parse the JDK file structure manually.
*/
private static Map<String, Path> callListModules(String javaExecutable, List<String> arguments) {
Process listModulesProcess = null;
Map<String, Path> result = new LinkedHashMap<>();
Map<String, Path> applicationModules = new HashMap<>();
try {
var pb = new ProcessBuilder(javaExecutable);
pb.command().addAll(arguments);
pb.command().add("--list-modules");
pb.environment().clear();
listModulesProcess = pb.start();

List<String> lines;
try (var br = new BufferedReader(new InputStreamReader(listModulesProcess.getInputStream()))) {
lines = br.lines().toList();
}
int exitStatus = listModulesProcess.waitFor();
if (exitStatus != 0) {
throw showError("Determining image-builder observable modules failed (Exit status %d). Process output: %n%s".formatted(exitStatus, String.join(System.lineSeparator(), lines)));
}
for (String line : lines) {
String[] splitString = StringUtil.split(line, " ", 3);
String[] splitModuleNameAndVersion = StringUtil.split(splitString[0], "@", 2);
Path externalPath = null;
if (splitString.length > 1) {
String pathURI = splitString[1]; // url: file://path/to/file
externalPath = Path.of(URI.create(pathURI)).toAbsolutePath();
ModuleFinder finder = ModuleFinder.of(modulePath.toArray(Path[]::new));
for (ModuleReference mref : finder.findAll()) {
Optional<URI> optionalURI = mref.location();
if (optionalURI.isEmpty()) {
continue;
}
result.put(splitModuleNameAndVersion[0], externalPath);
}
} catch (IOException | InterruptedException e) {
throw showError(e.getMessage());
} finally {
if (listModulesProcess != null) {
listModulesProcess.destroy();
applicationModules.put(mref.descriptor().name(), Path.of(optionalURI.get()));
}
} catch (FindException e) {
throw showError("Failed to collect ModuleReferences for module-path entries " + modulePath, e);
}
return result;
return applicationModules;
}

boolean useBundle() {
Expand Down

0 comments on commit 75aff5f

Please sign in to comment.