
io.helidon.linker.JavaDependencies Maven / Gradle / Ivy
/*
* Copyright (c) 2019, 2020 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.helidon.linker;
import java.io.ByteArrayOutputStream;
import java.lang.module.ModuleDescriptor;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.spi.ToolProvider;
import java.util.stream.Stream;
import io.helidon.build.util.Log;
import io.helidon.build.util.StreamUtils;
import io.helidon.linker.util.JavaRuntime;
import static io.helidon.build.util.StreamUtils.toPrintStream;
import static io.helidon.linker.util.Constants.EOL;
import static io.helidon.linker.util.Constants.EXCLUDED_MODULES;
import static io.helidon.linker.util.Constants.JDEPS_REQUIRES_MISSING_DEPS_OPTION;
import static java.util.Objects.requireNonNull;
/**
* Collects Java module dependencies for a set of jars.
*/
public final class JavaDependencies {
private static final ToolProvider JDEPS = ToolProvider.findFirst("jdeps")
.orElseThrow(() -> new IllegalStateException("jdeps not found"));
private static final String MULTI_RELEASE_ARG = "--multi-release";
private static final String SYSTEM_ARG = "--system";
private static final String LIST_DEPS_ARG = "--list-deps";
private static final String IGNORE_MISSING_DEPS_ARG = "--ignore-missing-deps";
private static final String JAVA_BASE_MODULE_NAME = "java.base";
private static final Set KNOWN_SPLIT_PACKAGES = Set.of("javax.annotation", "javax.activation");
private static final Map> PREFIX_HANDLERS = Map.of(
"split package", JavaDependencies::split,
"not found", JavaDependencies::ignore,
"unnamed module", JavaDependencies::ignore,
"JDK removed internal API", JavaDependencies::debug
);
private final JavaRuntime javaHome;
private final Set javaModuleNames;
private final Set dependencies;
/**
* Collect the dependencies of the given jars on the given Java Runtime.
*
* @param jars The jars.
* @param javaHome The Java Home.
* @return The module names.
*/
public static Set collect(Stream jars, JavaRuntime javaHome) {
return new JavaDependencies(javaHome).collect(jars);
}
private JavaDependencies(JavaRuntime javaHome) {
this.javaHome = requireNonNull(javaHome);
this.javaModuleNames = javaHome.moduleNames();
this.dependencies = new HashSet<>();
this.dependencies.add(JAVA_BASE_MODULE_NAME);
}
private Set collect(Stream jars) {
jars.forEach(jar -> {
if (jar.hasModuleDescriptor()) {
addModule(jar);
} else {
addJar(jar);
}
});
final Set closure = new HashSet<>();
dependencies.forEach(moduleName -> addDependency(moduleName, closure));
return closure;
}
private void addDependency(String moduleName, Set result) {
if (!result.contains(moduleName)) {
result.add(moduleName);
final Jar jar = javaHome.jmod(moduleName);
jar.moduleDescriptor().requires().forEach(r -> addDependency(r.name(), result));
}
}
private void addModule(Jar module) {
final ModuleDescriptor descriptor = module.moduleDescriptor();
Log.info(" checking module %s", descriptor.name());
descriptor.requires()
.stream()
.map(ModuleDescriptor.Requires::name)
.filter(javaModuleNames::contains)
.forEach(dependencies::add);
}
private void addJar(Jar jar) {
Log.info(" checking %s", jar);
final List args = new ArrayList<>();
if (!javaHome.isCurrent()) {
args.add(SYSTEM_ARG);
args.add(javaHome.path().toString());
}
if (jar.isMultiRelease()) {
args.add(MULTI_RELEASE_ARG);
args.add(javaHome.featureVersion());
}
if (JDEPS_REQUIRES_MISSING_DEPS_OPTION) {
args.add(IGNORE_MISSING_DEPS_ARG);
}
args.add(LIST_DEPS_ARG);
args.add(jar.path().toString());
final ByteArrayOutputStream out = new ByteArrayOutputStream();
final int result = JDEPS.run(toPrintStream(out, false), System.err, args.toArray(new String[0]));
if (result != 0) {
throw new RuntimeException("Could not collect dependencies of " + jar);
}
Arrays.stream(StreamUtils.toString(out).split(EOL))
.map(String::trim)
.filter(line -> !line.isEmpty())
.forEach(line -> handleJdepsResultLine(line, jar));
}
private void handleJdepsResultLine(String line, Jar jar) {
if (javaModuleNames.contains(line)) {
dependencies.add(line);
} else {
for (Map.Entry> entry : PREFIX_HANDLERS.entrySet()) {
if (line.contains(entry.getKey())) {
entry.getValue().accept(line, jar);
return;
}
}
if (!line.contains(":") && line.contains("/")) {
handleJdepsResultLine(line.split("/")[0], jar);
} else if (!EXCLUDED_MODULES.contains(line)) {
throw new IllegalStateException("Unhandled dependency: " + toString(line, jar));
}
}
}
private static String toString(String line, Jar jar) {
return jar + " -> " + line;
}
private static void ignore(String line, Jar jar) {
}
private static void debug(String line, Jar jar) {
Log.debug(toString(line, jar));
}
private static void warn(String line, Jar jar) {
Log.warn(toString(line, jar));
}
private static void split(String line, Jar jar) {
for (String ignored : KNOWN_SPLIT_PACKAGES) {
if (line.contains(ignored)) {
return;
}
}
warn(line, jar);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy