com.redhat.ceylon.tools.info.CeylonInfoTool Maven / Gradle / Ivy
package com.redhat.ceylon.tools.info;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.NavigableSet;
import java.util.Set;
import java.util.SortedMap;
import java.util.SortedSet;
import java.util.TreeMap;
import java.util.TreeSet;
import com.redhat.ceylon.cmr.api.ArtifactContext;
import com.redhat.ceylon.cmr.api.ModuleDependencyInfo;
import com.redhat.ceylon.cmr.api.ModuleQuery;
import com.redhat.ceylon.cmr.api.ModuleSearchResult;
import com.redhat.ceylon.cmr.api.ModuleSearchResult.ModuleDetails;
import com.redhat.ceylon.cmr.api.ModuleVersionArtifact;
import com.redhat.ceylon.cmr.api.ModuleVersionDetails;
import com.redhat.ceylon.cmr.api.ModuleVersionQuery;
import com.redhat.ceylon.cmr.api.RepositoryManager;
import com.redhat.ceylon.cmr.api.VersionComparator;
import com.redhat.ceylon.common.ModuleSpec;
import com.redhat.ceylon.common.ModuleUtil;
import com.redhat.ceylon.common.Versions;
import com.redhat.ceylon.common.config.DefaultToolOptions;
import com.redhat.ceylon.common.tool.Argument;
import com.redhat.ceylon.common.tool.Description;
import com.redhat.ceylon.common.tool.Option;
import com.redhat.ceylon.common.tool.OptionArgument;
import com.redhat.ceylon.common.tool.ParsedBy;
import com.redhat.ceylon.common.tool.RemainingSections;
import com.redhat.ceylon.common.tool.StandardArgumentParsers;
import com.redhat.ceylon.common.tool.Summary;
import com.redhat.ceylon.common.tools.CeylonTool;
import com.redhat.ceylon.common.tools.RepoUsingTool;
import com.redhat.ceylon.model.cmr.ModuleScope;
@Summary("Prints information about modules in repositories")
@Description("When passed a search query like `*foo*` it will look at all the modules in all " +
"repositories and see if the word `foo` appears anywhere in the name, description, " +
"version, license or any other field in the module's descriptor and print their names. " +
"\n\n" +
"When passed a partial module name like `com.acme.foo*` it will look at all the " +
"modules in all the repositories and see if their names start with `com.acme.foo` " +
"and print their names." +
"\n\n" +
"When passed a complete module name like `com.acme.foobar` it will print the list " +
"of available versions for that module, with the module repository in which each " +
"version was found. Versions marked with `*` are not currently available on the local " +
"file system but will be downloaded on-demand from remote servers." +
"\n\n" +
"When passed a complete module name and version like `com.acme.foobar/1.0` it will " +
"print information about the contents of a module archive, its description, its licence " +
"and its dependencies")
@RemainingSections("## EXAMPLE\n\n"
+ "First list the available modules in the 'ceylon' namespace:\n\n"
+ " ceylon info 'ceylon.*'\n\n"
+ "Next list the versions of a module:\n\n"
+ " ceylon info ceylon.collection\n\n"
+ "Then view information for a particular version:\n\n"
+ " ceylon info ceylon.collection/1.2.0\n\n")
public class CeylonInfoTool extends RepoUsingTool {
private static final int INFINITE_DEPTH = -1;
public enum Formatting {
simple, fancy
}
public enum Incompatible {
yes, no, auto
}
private List modules;
private boolean includeOptional;
private boolean showVersions;
private boolean showDependencies;
private Incompatible showIncompatible = Incompatible.auto;
private boolean showFullDescription;
private String showType;
private int depth = 1;
private String findMember;
private String findPackage;
private boolean showNames;
private boolean exactMatch;
private boolean requireAll;
private boolean printOverrides;
private Formatting formatting;
private List sourceFolders = DefaultToolOptions.getCompilerSourceDirs();
private Integer jvmBinaryMajor = null;
private Integer jvmBinaryMinor = null;
private Integer jsBinaryMajor = null;
private Integer jsBinaryMinor = null;
private ModuleQuery.Type queryType = ModuleQuery.Type.ALL;
public CeylonInfoTool() {
super(CeylonInfoMessages.RESOURCE_BUNDLE);
}
@Override
protected boolean includeJDK() {
return !noDefRepos;
}
@Override
protected List getSourceDirs() {
return sourceFolders;
}
@OptionArgument(longName="src", argumentName="dir")
@ParsedBy(StandardArgumentParsers.PathArgumentParser.class)
@Description("A directory containing Ceylon and/or Java source code (default: `./source`)")
public void setSourceFolders(List sourceFolders) {
this.sourceFolders = sourceFolders;
}
@Argument(argumentName="module", multiplicity="+")
public void setModules(List modules) {
setModuleSpecs(ModuleSpec.parseEachList(modules));
}
public void setModuleSpecs(List modules) {
this.modules = modules;
}
@Option(longName="include-optional")
@Description("Include optional modules when traversing dependencies")
public void setIncludeOptional(boolean includeOptional) {
this.includeOptional = includeOptional;
}
@Option(longName="show-versions")
@Description("Show the versions when searching for modules")
public void setShowVersions(boolean showVersions) {
this.showVersions = showVersions;
}
@Option(longName="print-overrides")
@Description("Print a usable module overrides file when there are duplicate versions, selecting the latest versions")
public void setPrintOverrides(boolean printOverrides) {
this.printOverrides = printOverrides;
}
@Option(longName="show-dependencies")
@Description("Show the dependencies whenever versions are shown")
public void setShowDependencies(boolean showDependencies) {
this.showDependencies = showDependencies;
}
@OptionArgument(argumentName="mode")
@Description("Also show versions incompatible with the current Ceylon installation. " +
"allowed values are: 'yes', 'no' and 'auto'")
public void setShowIncompatible(Incompatible showIncompatible) {
this.showIncompatible = showIncompatible;
}
@Option(longName="show-full-description")
@Description("Shows the full description for module details")
public void setShowFullDescription(boolean showFullDescription) {
this.showFullDescription = showFullDescription;
}
@Option(longName="require-all")
@Description("Only show those results that have all the requested artifact types")
public void setRequireAll(boolean requireAll) {
this.requireAll = requireAll;
}
@OptionArgument(argumentName = "type")
@Description("The artifact types to show information for. " +
"Allowed values include: `all`, `jvm`, `car`, `jar`, `js`, `dart`, `src`, `code`, `ceylon` (default is `all`).")
public void setShowType(String showType) {
this.showType = showType;
}
@Description("The depth of the dependency tree to show, or `all` for the full tree. " +
"(Allowed values: any positive integer or `all`, default: `1`)")
@OptionArgument(argumentName="depth")
public void setDependencyDepth(String depth) {
if ("all".equals(depth)) {
setDependencyDepth(INFINITE_DEPTH);
} else {
setDependencyDepth(Integer.parseInt(depth));
}
}
public void setDependencyDepth(int depth) {
if (!(depth == INFINITE_DEPTH || depth >= 0)) {
throw new IllegalArgumentException(CeylonInfoMessages.msg("illegal.depth"));
}
this.depth = depth;
}
@OptionArgument(argumentName = "member-name")
@Description("Shows only those modules that contain members whose name match the given argument.")
public void setFindMember(String findMember) {
this.findMember = findMember;
}
@OptionArgument(argumentName = "package-name")
@Description("Shows only those modules that contain packages whose name match the given argument.")
public void setFindPackage(String findPackage) {
this.findPackage = findPackage;
}
@Option(longName="show-names")
@Description("Show the matching items when using the `find-member` or `find-package` option")
public void setShowNames(boolean showNames) {
this.showNames = showNames;
}
@Option(longName="exact-match")
@Description("Only returns exact matches when using the `find-member` or `find-package` option")
public void setExactMatch(boolean exactMatch) {
this.exactMatch = exactMatch;
}
@OptionArgument(argumentName = "formatting")
@Description("Set the output formatting to use, can be `simple` or `fancy`")
public void setFormatting(Formatting formatting) {
this.formatting = formatting;
}
@Override
protected boolean needsSystemRepo() {
return false;
}
@Override
public void initialize(CeylonTool mainTool) throws Exception {
super.initialize(mainTool);
if (showType != null) {
if ("car".equalsIgnoreCase(showType)) {
queryType = ModuleQuery.Type.CAR;
} else if ("jar".equalsIgnoreCase(showType)) {
queryType = ModuleQuery.Type.JAR;
} else if ("jvm".equalsIgnoreCase(showType)) {
queryType = ModuleQuery.Type.JVM;
} else if ("js".equalsIgnoreCase(showType)) {
queryType = ModuleQuery.Type.JS;
} else if ("dart".equalsIgnoreCase(showType)) {
queryType = ModuleQuery.Type.DART;
} else if ("src".equalsIgnoreCase(showType)) {
queryType = ModuleQuery.Type.SRC;
} else if ("all".equalsIgnoreCase(showType)) {
queryType = ModuleQuery.Type.ALL;
} else if ("code".equalsIgnoreCase(showType)) {
queryType = ModuleQuery.Type.CODE;
} else if ("ceylon".equalsIgnoreCase(showType)) {
queryType = ModuleQuery.Type.CEYLON_CODE;
} else {
throw new IllegalArgumentException(CeylonInfoMessages.msg("illegal.type", showType));
}
}
if (findMember != null && "src".equalsIgnoreCase(showType)) {
throw new IllegalArgumentException(CeylonInfoMessages.msg("incompatible.query.and.find"));
}
if (findMember != null && findPackage != null) {
throw new IllegalArgumentException(CeylonInfoMessages.msg("incompatible.find.options"));
}
if (formatting == null) {
formatting = (System.console() != null) ? Formatting.fancy : Formatting.simple;
}
}
@Override
public void run() throws Exception {
if (showIncompatible != Incompatible.yes) {
if (queryType.includes(ArtifactContext.CAR)) {
jvmBinaryMajor = Versions.JVM_BINARY_MAJOR_VERSION;
jvmBinaryMinor = Versions.JVM_BINARY_MINOR_VERSION;
}
if (queryType.includes(ArtifactContext.JS)) {
jsBinaryMajor = Versions.JS_BINARY_MAJOR_VERSION;
jsBinaryMinor = Versions.JS_BINARY_MINOR_VERSION;
}
}
String msgkey = showIncompatible == Incompatible.no ? "module.not.found.compat" : "module.not.found";
for (ModuleSpec module : modules) {
String name = module.getName();
if (!module.isVersioned() && (name.startsWith("*") || name.endsWith("*"))) {
Collection modules = getModules(getRepositoryManager(), module.getNamespace(), name, queryType,
jvmBinaryMajor, jvmBinaryMinor, jsBinaryMajor, jsBinaryMinor);
if (modules.isEmpty()) {
String err;
if (name.startsWith("*") || name.endsWith("*")) {
err = CeylonInfoMessages.msg("no.match", name);
} else {
err = getModuleNotFoundErrorMessage(getRepositoryManager(), module.getName(), module.getVersion(), msgkey);
}
errorAppend(err);
errorNewline();
continue;
}
outputModules(module, modules);
} else {
Collection versions = getModuleVersions(getRepositoryManager(),
module.getNamespace(), module.getName(),
module.getVersion(), false, queryType,
jvmBinaryMajor, jvmBinaryMinor, jsBinaryMajor, jsBinaryMinor);
if (versions.isEmpty()) {
// try from source
ModuleVersionDetails fromSource = getModuleVersionDetailsFromSource(name);
if (fromSource != null) {
// is it the version we're after?
versions = Arrays.asList(fromSource);
} else {
if (showIncompatible == Incompatible.auto &&
(jvmBinaryMajor != null || jvmBinaryMinor != null || jsBinaryMajor != null || jsBinaryMinor != null)) {
// If we were called with a specific version and we didn't find a "compatible"
// artifact then lets see if we can find an "incompatible" one
versions = getModuleVersions(getRepositoryManager(), module.getNamespace(), module.getName(), module.getVersion(), false,
queryType, null, null, null, null);
}
if (versions.isEmpty()) {
String err = getModuleNotFoundErrorMessage(getRepositoryManager(), module.getName(), module.getVersion(), msgkey);
errorAppend(err);
errorNewline();
continue;
}
}
}
if (module.getVersion() == null || module.getVersion().isEmpty() || versions.size() > 1) {
outputVersions(module, versions);
} else {
outputDetails(module, versions.iterator().next());
}
}
}
}
private Collection getModules(RepositoryManager repoMgr, String namespace, String name, ModuleQuery.Type type,
Integer jvmBinaryMajor, Integer jvmBinaryMinor,
Integer jsBinaryMajor, Integer jsBinaryMinor) {
String queryString = name;
if (queryString.startsWith("*")) {
queryString = queryString.substring(1);
}
if (queryString.endsWith("*")) {
queryString = queryString.substring(0, queryString.length() - 1);
}
ModuleVersionQuery query = getModuleVersionQuery(namespace, queryString, null, type,
jvmBinaryMajor, jvmBinaryMinor,
jsBinaryMajor, jsBinaryMinor);
ModuleSearchResult result;
if (!name.startsWith("*") || name.equals("*")) {
result = repoMgr.completeModules(query);
} else {
result = repoMgr.searchModules(query);
}
return result.getResults();
}
@Override
protected ModuleVersionQuery getModuleVersionQuery(String namespace, String name, String version, ModuleQuery.Type type,
Integer jvmBinaryMajor, Integer jvmBinaryMinor,
Integer jsBinaryMajor, Integer jsBinaryMinor) {
ModuleVersionQuery query = super.getModuleVersionQuery(namespace, name, version, type, jvmBinaryMajor, jvmBinaryMinor,
jsBinaryMajor, jsBinaryMinor);
if (findMember != null) {
query.setMemberName(findMember);
}
if (findPackage != null) {
query.setMemberName(findPackage);
query.setMemberSearchPackageOnly(true);
}
query.setMemberSearchExact(exactMatch);
if (requireAll) {
query.setRetrieval(ModuleQuery.Retrieval.ALL);
}
return query;
}
private void outputModules(ModuleSpec query, Collection modules) throws IOException {
if (formatting == Formatting.fancy) {
if (findMember != null) {
msg("module.query.member", query.getName(), findMember).newline();
} else if (findPackage != null) {
msg("module.query.package", query.getName(), findPackage).newline();
} else {
msg("module.query", query.getName()).newline();
}
}
outputModules(modules);
}
private void outputModules(Collection modules) throws IOException {
for (ModuleDetails module : modules) {
outputModule(module);
}
}
private void outputModule(ModuleDetails module) throws IOException {
String prefix = (formatting == Formatting.fancy) ? " " : "";
if (formatting == Formatting.fancy || (!showVersions && !showNames)) {
append(prefix).append(module.getName()).newline();
}
if (showVersions) {
outputVersions(module.getNamespace(), module.getName(), module.getVersions(), prefix + prefix);
} else if (showNames) {
outputNames(module.getNamespace(), module.getName(), module.getLastVersion(), prefix + prefix);
}
}
private void outputVersions(ModuleSpec module, Collection versions) throws IOException {
String prefix = (formatting == Formatting.fancy) ? " " : "";
if (formatting == Formatting.fancy) {
if (findMember != null) {
msg("version.query.member", module.getName(), findMember).newline();
} else if (findPackage != null) {
msg("version.query.package", module.getName(), findPackage).newline();
} else {
msg("version.query", module.getName()).newline();
}
}
outputVersions(module.getNamespace(), module.getName(), versions, prefix);
}
private void outputVersions(String namespace, String moduleName, Collection versions, String prefix) throws IOException {
String namePrefix = (formatting == Formatting.fancy) ? prefix + " " : "";
for (ModuleVersionDetails version : versions) {
if (formatting == Formatting.fancy || (!showDependencies && !showNames)) {
append(prefix);
if (formatting == Formatting.simple) {
if (namespace != null) {
append(namespace).append(":");
}
append(moduleName).append("/");
}
append(version.getVersion());
if (formatting == Formatting.fancy) {
append(" - ").append(version.getOrigin());
if (hasOnlyIncompatibleBinaries(version)) {
append(" ");
msg("label.incompatible.version");
}
if (version.isRemote()) {
append(" ");
msg("label.remote");
}
}
newline();
}
if (showDependencies) {
if (formatting == Formatting.fancy || !version.getDependencies().isEmpty()) {
for (ModuleDependencyInfo dep : version.getDependencies()) {
if(dep.getModuleScope() == ModuleScope.TEST)
continue;
if (formatting == Formatting.fancy) {
append(prefix).append(" ").append(dep);
} else {
if (namespace != null) {
append(namespace).append(":");
}
append(moduleName).append("/").append(version.getVersion()).append(" ").append(dep.getModuleName());
}
newline();
}
} else {
if (namespace != null) {
append(namespace).append(":");
}
append(moduleName).append("/").append(version.getVersion()).newline();
}
}
if (showNames) {
outputNames(namespace, moduleName, version, namePrefix);
}
}
}
private boolean hasOnlyIncompatibleBinaries(ModuleVersionDetails version) {
boolean hasBinaries = false;
for (ModuleVersionArtifact at : version.getArtifactTypes()) {
if (at.getSuffix().equals(ArtifactContext.CAR)) {
if (Versions.isJvmBinaryVersionSupported(
at.getMajorBinaryVersion(),
at.getMinorBinaryVersion())) {
return false;
}
hasBinaries = true;
}
if (at.getSuffix().equals(ArtifactContext.JS)) {
if (Versions.isJvmBinaryVersionSupported(
at.getMajorBinaryVersion(),
at.getMinorBinaryVersion())) {
return false;
}
hasBinaries = true;
}
}
return hasBinaries;
}
private void outputNames(String namespace, String moduleName, ModuleVersionDetails version, String prefix) throws IOException {
for (String member : version.getMembers()) {
if (formatting == Formatting.fancy) {
append(prefix).append(member).newline();
} else {
if (namespace != null) {
append(namespace).append(":");
}
append(moduleName);
if (showVersions) {
append("/").append(version.getVersion());
}
append("::").append(member).newline();
}
}
}
private void outputDetails(ModuleSpec module, ModuleVersionDetails version) throws IOException {
msg("module.namespace");
if (module.getNamespace() != null) {
append(module.getNamespace());
} else {
append("ceylon");
}
newline();
msg("module.name").append(module.getName()).newline();
msg("module.version").append(version.getVersion()).newline();
outputArtifacts(version.getArtifactTypes());
msg("module.available").msg((version.isRemote() ? "available.remote" : "available.local")).newline();
if (version.getOrigin() != null) {
msg("module.origin").append(version.getOrigin()).newline();
}
if (version.getDoc() != null) {
String docs = version.getDoc();
if (!showFullDescription) {
docs = summary(version.getDoc());
}
msg("module.description").append(docs).newline();
}
if (version.getLicense() != null) {
msg("module.license").append(version.getLicense()).newline();
}
if (version.getAuthors() != null && !version.getAuthors().isEmpty()) {
outputAuthors(version.getAuthors());
}
if (!version.getDependencies().isEmpty()) {
msg("module.dependencies.tree", (depth == INFINITE_DEPTH ? "∞" : String.valueOf(depth))).newline();
SortedMap> names = new TreeMap<>();
recurseDependencies(version, names, 0);
if (depth != 1) {
newline();
msg("module.dependencies.flat", (depth == INFINITE_DEPTH ? "∞" : String.valueOf(depth))).newline();
listDependencies(names);
}
listDependencyConflictsIfAny(names);
}
}
private void listDependencyConflictsIfAny(SortedMap> names) throws IOException {
boolean hasNoDuplicate = true;
StringBuilder overridesFile = null;
if(printOverrides){
overridesFile = new StringBuilder();
overridesFile.append("\n");
}
for(Map.Entry> entry : names.entrySet()){
if(entry.getValue().size() > 1){
if(hasNoDuplicate){
hasNoDuplicate = false;
newline();
msg("module.dependencies.conflicts", (depth == INFINITE_DEPTH ? "∞" : String.valueOf(depth))).newline();
}
append(" ");
append(entry.getKey());
if(entry.getValue().size() > 1){
append(": ");
boolean first = true;
for(String v : entry.getValue()){
if(first)
first = false;
else
append(", ");
append(v);
}
if(printOverrides){
overridesFile.append(" \n");
}
}
newline();
}
}
if(printOverrides && !hasNoDuplicate){
overridesFile.append(" \n");
newline();
msg("module.dependencies.overrides").newline();
newline();
append(overridesFile.toString());
}
}
private void listDependencies(SortedMap> names) throws IOException {
for(Map.Entry> entry : names.entrySet()){
append(" ");
append(entry.getKey());
if(entry.getValue().size() > 1){
append(": ");
boolean first = true;
for(String v : entry.getValue()){
if(first)
first = false;
else
append(", ");
append(v);
}
}else{
append("/");
append(entry.getValue().first());
}
newline();
}
}
private String summary(String doc) {
StringBuilder result = new StringBuilder();
String[] lines = doc.split("\n");
for (int i = 0; i < lines.length && i < 5; i++) {
result.append(lines[i]).append('\n');
}
if (lines.length > 5) {
result.append("...").append('\n');
}
return result.toString();
}
private void outputAuthors(NavigableSet authors) throws IOException {
msg("module.authors");
boolean first = true;
for (String author : authors) {
if (!first) {
append(", ");
}
append(author);
first = false;
}
newline();
}
private RepoUsingTool outputArtifacts(Set types) throws IOException {
if(!types.isEmpty()) {
msg("module.artifacts");
boolean skipComma = true;
boolean js = false;
boolean docs = false;
for (ModuleVersionArtifact type : types) {
if (!skipComma) {
append(", ");
}
String suffix = type.getSuffix();
int major = (type.getMajorBinaryVersion() != null) ? type.getMajorBinaryVersion() : 0;
int minor = (type.getMinorBinaryVersion() != null) ? type.getMinorBinaryVersion() : 0;
if (suffix.equalsIgnoreCase(ArtifactContext.CAR)) {
append("JVM (#");
append(major);
if (minor != 0) {
append(".");
append(minor);
}
if (!Versions.isJvmBinaryVersionSupported(major, minor)) {
append(" ");
msg("label.incompatible.version");
}
append(")");
} else if (suffix.equalsIgnoreCase(ArtifactContext.JAR)) {
append("JVM (legacy)");
} else if (suffix.equalsIgnoreCase(ArtifactContext.JS) || suffix.equalsIgnoreCase(ArtifactContext.JS_MODEL)) {
if (js) {
skipComma = true;
continue;
}
append("JavaScript (#");
append(major);
if (minor != 0) {
append(".");
append(minor);
}
if (!Versions.isJsBinaryVersionSupported(major, minor)) {
append(" ");
msg("label.incompatible.version");
}
append(")");
js = true;
} else if (suffix.equalsIgnoreCase(ArtifactContext.DART)) {
append("Dart");
} else if (suffix.equalsIgnoreCase(ArtifactContext.RESOURCES)) {
append("non-JVM Resources");
} else if (suffix.equalsIgnoreCase(ArtifactContext.DOCS)) {
if (docs) {
skipComma = true;
continue;
}
append("Documentation");
docs = true;
} else if (suffix.equalsIgnoreCase(ArtifactContext.SCRIPTS_ZIPPED)) {
append("Script Plugins");
} else if (suffix.equalsIgnoreCase(ArtifactContext.SRC)) {
append("Sources");
} else if (suffix.startsWith(".")) {
append(suffix.substring(1).toUpperCase());
} else {
// We might need to add some special cases
// for these in the above list
append(type);
}
skipComma = false;
}
newline();
}
return this;
}
private void recurseDependencies(ModuleVersionDetails version, Map> names, final int depth) throws IOException {
for (ModuleDependencyInfo dep : version.getDependencies()) {
if(dep.getModuleScope() == ModuleScope.TEST)
continue;
dependency(dep, names, depth+1);
}
}
private void dependency(ModuleDependencyInfo dep, Map> names, final int depth) throws IOException {
for (int ii = 0; ii < depth; ii++) {
append(" ");
}
append(dep);
// Don't even record optional deps if we don't want them recorded (just print)
if(dep.isOptional() && !includeOptional){
newline();
return;
}
SortedSet seenVersions = names.get(dep.getName());
boolean recurse = this.depth == -1 || depth < this.depth;
if(seenVersions != null){
// already seen
if(seenVersions.size() == 1 && seenVersions.first().equals(dep.getVersion()))
append(" (already imported)");
else
append(" (already imported other version)");
recurse = false;
}else{
seenVersions = new TreeSet<>(VersionComparator.INSTANCE);
names.put(dep.getName(), seenVersions);
}
seenVersions.add(dep.getVersion());
newline();
if (recurse && !"ceylon.language".equals(dep.getName())) {
Collection versions = getModuleVersions(dep.getNamespace(), dep.getName(), dep.getVersion(), true, queryType,
jvmBinaryMajor, jvmBinaryMinor, jsBinaryMajor, jsBinaryMinor);
if (!versions.isEmpty()) {
recurseDependencies(versions.iterator().next(), names, depth + 1);
}
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy