From d270c717f0599eb2aa10e503f8e34c20f5b98694 Mon Sep 17 00:00:00 2001 From: Arnab Nandy Date: Fri, 2 Oct 2026 19:30:32 +0530 Subject: [PATCH 1/6] feat: add info source-jar and source-path commands Signed-off-by: Arnab Nandy --- docs/modules/ROOT/pages/troubleshooting.adoc | 6 + docs/modules/cli/nav.adoc | 2 + .../cli/pages/jbang-info-source-jar.adoc | 85 ++++++++++++ .../cli/pages/jbang-info-source-path.adoc | 88 +++++++++++++ docs/modules/cli/pages/jbang-info.adoc | 2 + docs/modules/cli/partials/nav-jbang.adoc | 2 + src/main/java/dev/jbang/cli/Info.java | 124 +++++++++++++++++- .../dev/jbang/dependencies/ArtifactInfo.java | 25 ++++ .../jbang/dependencies/ArtifactResolver.java | 30 ++++- .../jbang/dependencies/DependencyCache.java | 13 +- .../jbang/dependencies/DependencyUtil.java | 34 ++++- .../jbang/dependencies/ModularClassPath.java | 20 +++ src/test/java/dev/jbang/cli/TestInfo.java | 80 +++++++++++ 13 files changed, 495 insertions(+), 16 deletions(-) create mode 100644 docs/modules/cli/pages/jbang-info-source-jar.adoc create mode 100644 docs/modules/cli/pages/jbang-info-source-path.adoc diff --git a/docs/modules/ROOT/pages/troubleshooting.adoc b/docs/modules/ROOT/pages/troubleshooting.adoc index 0a50781e91..3a2959e8e3 100644 --- a/docs/modules/ROOT/pages/troubleshooting.adoc +++ b/docs/modules/ROOT/pages/troubleshooting.adoc @@ -149,6 +149,12 @@ jbang info classpath script.java # Test dependency resolution jbang info classpath script.java +# Resolve dependency source JARs (source-path) +jbang info source-path script.java + +# Resolve source JAR for a specific JAR or dependency +jbang info source-jar xyz.jar + # Check repositories jbang info docs script.java diff --git a/docs/modules/cli/nav.adoc b/docs/modules/cli/nav.adoc index 46b26f6180..0443af4780 100644 --- a/docs/modules/cli/nav.adoc +++ b/docs/modules/cli/nav.adoc @@ -57,6 +57,8 @@ *** xref:jbang:cli:jbang-info-tools.adoc[tools] *** xref:jbang:cli:jbang-info-classpath.adoc[classpath] *** xref:jbang:cli:jbang-info-jar.adoc[jar] +*** xref:jbang:cli:jbang-info-source-path.adoc[source-path] +*** xref:jbang:cli:jbang-info-source-jar.adoc[source-jar] *** xref:jbang:cli:jbang-info-docs.adoc[docs] ** xref:jbang:cli:jbang-version.adoc[version] ** xref:jbang:cli:jbang-wrapper.adoc[wrapper] diff --git a/docs/modules/cli/pages/jbang-info-source-jar.adoc b/docs/modules/cli/pages/jbang-info-source-jar.adoc new file mode 100644 index 0000000000..a42984f300 --- /dev/null +++ b/docs/modules/cli/pages/jbang-info-source-jar.adoc @@ -0,0 +1,85 @@ += JBANG-INFO-SOURCE-JAR + +== NAME + +jbang info source-jar -- Prints the path to this application's or artifact's source JAR file. + +== SYNOPSIS + +[source] +---- +jbang info source-jar [-h] [-s=] [--files=] [-T=] [--jsh] [--catalog=] [-D=] [--deps=] [--repos=] [--cp=] [--ignore-transitive-repositories] [--build-dir=] [--module] [--config=] [--insecure] [--[no-]verbose] [--[no-]quiet] [--[no-]offline] [--[no-]fresh] [--[no-]stacktrace] [] +---- + +== OPTIONS + +*-h*, *--help*:: +Display this help and exit + +*-s*, *--sources*=:: +Add additional sources. + +*--files*=:: +Add additional files. + +*-T*, *--source-type*=:: +Force input to be interpreted as the given type. Can be: java, jshell, groovy, kotlin, or markdown + +*--jsh*:: +Force input to be interpreted with jsh/jshell. Deprecated: use '--source-type jshell' + +*--catalog*=:: +Path to catalog file to be used instead of the default + +*-D*==:: +set a system property ++ +Default: `true` + +*--deps*=:: +Add additional dependencies (Use commas to separate them). + +*--repos*=:: +Add additional repositories. + +*--cp*=:: +Add class path entries. ++ +Aliases: `--class-path` + +*--ignore-transitive-repositories*:: +Ignore remote repositories found in transitive dependencies ++ +Aliases: `--itr` + +*--build-dir*=:: +Use given directory for build results + +*--module*=:: +Treat resource as a module. Optionally with the given module name + +*--config*=:: +Path to config file to be used instead of the default + +*--insecure*:: +Enable insecure trust of all SSL certificates. + +*--[no-]verbose*:: +jbang will be verbose on what it does. + +*--[no-]quiet*:: +jbang will be quiet, only print when error occurs. + +*-o*, *--[no-]offline*:: +Work offline. Fail-fast if dependencies are missing. + +*--[no-]fresh*:: +Make sure we use fresh (i.e. non-cached) resources. + +*-x*, *--[no-]stacktrace*:: +Print exceptions stacktraces to stderr (even when quiet). + +== ARGUMENTS + +**:: +A file or URL to a Java code file diff --git a/docs/modules/cli/pages/jbang-info-source-path.adoc b/docs/modules/cli/pages/jbang-info-source-path.adoc new file mode 100644 index 0000000000..65d6c3ed59 --- /dev/null +++ b/docs/modules/cli/pages/jbang-info-source-path.adoc @@ -0,0 +1,88 @@ += JBANG-INFO-SOURCE-PATH + +== NAME + +jbang info source-path -- Prints source-path used for this application using operating system specific path separation. + +== SYNOPSIS + +[source] +---- +jbang info source-path [-h] [--deps-only] [-s=] [--files=] [-T=] [--jsh] [--catalog=] [-D=] [--deps=] [--repos=] [--cp=] [--ignore-transitive-repositories] [--build-dir=] [--module] [--config=] [--insecure] [--[no-]verbose] [--[no-]quiet] [--[no-]offline] [--[no-]fresh] [--[no-]stacktrace] [] +---- + +== OPTIONS + +*-h*, *--help*:: +Display this help and exit + +*--deps-only*:: +Only include the dependencies in the output, not the application jar itself + +*-s*, *--sources*=:: +Add additional sources. + +*--files*=:: +Add additional files. + +*-T*, *--source-type*=:: +Force input to be interpreted as the given type. Can be: java, jshell, groovy, kotlin, or markdown + +*--jsh*:: +Force input to be interpreted with jsh/jshell. Deprecated: use '--source-type jshell' + +*--catalog*=:: +Path to catalog file to be used instead of the default + +*-D*==:: +set a system property ++ +Default: `true` + +*--deps*=:: +Add additional dependencies (Use commas to separate them). + +*--repos*=:: +Add additional repositories. + +*--cp*=:: +Add class path entries. ++ +Aliases: `--class-path` + +*--ignore-transitive-repositories*:: +Ignore remote repositories found in transitive dependencies ++ +Aliases: `--itr` + +*--build-dir*=:: +Use given directory for build results + +*--module*=:: +Treat resource as a module. Optionally with the given module name + +*--config*=:: +Path to config file to be used instead of the default + +*--insecure*:: +Enable insecure trust of all SSL certificates. + +*--[no-]verbose*:: +jbang will be verbose on what it does. + +*--[no-]quiet*:: +jbang will be quiet, only print when error occurs. + +*-o*, *--[no-]offline*:: +Work offline. Fail-fast if dependencies are missing. + +*--[no-]fresh*:: +Make sure we use fresh (i.e. non-cached) resources. + +*-x*, *--[no-]stacktrace*:: +Print exceptions stacktraces to stderr (even when quiet). + +== ARGUMENTS + +**:: +A file or URL to a Java code file diff --git a/docs/modules/cli/pages/jbang-info.adoc b/docs/modules/cli/pages/jbang-info.adoc index c3db56ce2d..d67b580dae 100644 --- a/docs/modules/cli/pages/jbang-info.adoc +++ b/docs/modules/cli/pages/jbang-info.adoc @@ -42,5 +42,7 @@ Print exceptions stacktraces to stderr (even when quiet). * xref:jbang:cli:jbang-info-tools.adoc[*tools*] - Prints a json description usable for tools/IDE's to get classpath and more info for a jbang script/application. * xref:jbang:cli:jbang-info-classpath.adoc[*classpath*] - Prints class-path used for this application using operating system specific path separation. * xref:jbang:cli:jbang-info-jar.adoc[*jar*] - Prints the path to this application's JAR file. +* xref:jbang:cli:jbang-info-source-path.adoc[*source-path*] - Prints source-path used for this application using operating system specific path separation. +* xref:jbang:cli:jbang-info-source-jar.adoc[*source-jar*] - Prints the path to this application's or artifact's source JAR file. * xref:jbang:cli:jbang-info-docs.adoc[*docs*] - Open the documentation file in the default browser. diff --git a/docs/modules/cli/partials/nav-jbang.adoc b/docs/modules/cli/partials/nav-jbang.adoc index 93e28df659..eff02105e5 100644 --- a/docs/modules/cli/partials/nav-jbang.adoc +++ b/docs/modules/cli/partials/nav-jbang.adoc @@ -38,6 +38,8 @@ *** xref:jbang:cli:jbang-info-tools.adoc[tools] *** xref:jbang:cli:jbang-info-classpath.adoc[classpath] *** xref:jbang:cli:jbang-info-jar.adoc[jar] +*** xref:jbang:cli:jbang-info-source-path.adoc[source-path] +*** xref:jbang:cli:jbang-info-source-jar.adoc[source-jar] *** xref:jbang:cli:jbang-info-docs.adoc[docs] ** xref:jbang:cli:jbang-app.adoc[app] *** xref:jbang:cli:jbang-app-install.adoc[install] diff --git a/src/main/java/dev/jbang/cli/Info.java b/src/main/java/dev/jbang/cli/Info.java index a0ea78ec9e..c91a0cfd51 100644 --- a/src/main/java/dev/jbang/cli/Info.java +++ b/src/main/java/dev/jbang/cli/Info.java @@ -18,6 +18,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; @@ -30,6 +31,7 @@ import dev.jbang.ExitException; import dev.jbang.dependencies.ArtifactInfo; +import dev.jbang.dependencies.DependencyUtil; import dev.jbang.dependencies.MavenRepo; import dev.jbang.devkitman.Jdk; import dev.jbang.devkitman.JdkManager; @@ -47,6 +49,7 @@ @CommandDefinition(name = "info", description = "Provides info about the script for tools (and humans who are tools).", groupCommands = { Info.Tools.class, Info.ClassPath.class, Info.Jar.class, + Info.SourcePath.class, Info.SourceJar.class, Info.Docs.class }, generateHelp = true) public class Info extends BaseCommand { @@ -109,12 +112,14 @@ static class ScriptInfo { String originalResource; String backingResource; String applicationJar; + String applicationSourceJar; String applicationJsa; String nativeImage; String mainClass; List dependencies; List repositories; List resolvedDependencies; + List resolvedSourceDependencies; String javaVersion; String requestedJavaVersion; String availableJdkPath; @@ -128,6 +133,10 @@ static class ScriptInfo { Map> docs; public ScriptInfo(Project prj, Path buildDir, boolean assureJdkInstalled) { + this(prj, buildDir, assureJdkInstalled, false); + } + + public ScriptInfo(Project prj, Path buildDir, boolean assureJdkInstalled, boolean downloadSources) { originalResource = prj.getResourceRef().getOriginalResource(); if (scripts.add(originalResource)) { @@ -137,7 +146,7 @@ public ScriptInfo(Project prj, Path buildDir, boolean assureJdkInstalled) { try { BuildContext ctx = BuildContext.forProject(prj, buildDir); - init(ctx); + init(ctx, downloadSources); } catch (Exception e) { Util.warnMsg("Unable to obtain full information, the script probably contains errors", e); } @@ -207,7 +216,7 @@ private void init(SourceSet ss) { } } - private void init(BuildContext ctx) { + private void init(BuildContext ctx, boolean downloadSources) { applicationJar = ctx.getJarFile() == null ? null : ctx.getJarFile().toAbsolutePath().toString(); applicationJsa = ctx.getJsaFile() != null && Files.isRegularFile(ctx.getJsaFile()) @@ -220,11 +229,19 @@ private void init(BuildContext ctx) { List artifacts = ctx.resolveClassPath().getArtifacts(); if (artifacts.isEmpty()) { resolvedDependencies = Collections.emptyList(); + resolvedSourceDependencies = Collections.emptyList(); } else { resolvedDependencies = artifacts .stream() .map(a -> a.getFile().toString()) .collect(Collectors.toList()); + resolvedSourceDependencies = artifacts + .stream() + .map(ArtifactInfo::getSourceFile) + .filter(Objects::nonNull) + .filter(Files::exists) + .map(Path::toString) + .collect(Collectors.toList()); } if (ctx.getJarFile() != null && Files.exists(ctx.getJarFile())) { @@ -233,6 +250,50 @@ private void init(BuildContext ctx) { gav = jarProject.getGav().orElse(gav); module = ModuleUtil.getModuleName(jarProject); } + + if (downloadSources) { + resolveApplicationSourceJar(ctx); + } + } + + private void resolveApplicationSourceJar(BuildContext ctx) { + Project prj = ctx.getProject(); + if (!prj.isExecutableArchive()) { + return; + } + Path jar = ctx.getJarFile(); + if (jar == null || !Files.exists(jar)) { + return; + } + + String fileName = jar.getFileName().toString(); + if (fileName.endsWith(".jar")) { + Path sibling = jar.resolveSibling(fileName.substring(0, fileName.length() - 4) + "-sources.jar"); + if (Files.isRegularFile(sibling)) { + applicationSourceJar = sibling.toAbsolutePath().toString(); + return; + } + } + + List artifacts = ctx.resolveClassPath().getArtifacts(); + for (ArtifactInfo art : artifacts) { + if (jar.equals(art.getFile()) && art.getSourceFile() != null && Files.exists(art.getSourceFile())) { + applicationSourceJar = art.getSourceFile().toAbsolutePath().toString(); + return; + } + } + + String targetGav = gav != null ? gav : prj.getGav().orElse(null); + if (targetGav == null && prj.getResourceRef().getOriginalResource() != null + && DependencyUtil.looksLikeAGav(prj.getResourceRef().getOriginalResource())) { + targetGav = prj.getResourceRef().getOriginalResource(); + } + if (targetGav != null) { + Optional srcJar = DependencyUtil.resolveSource(targetGav, prj.getRepositories()); + if (srcJar.isPresent() && Files.exists(srcJar.get())) { + applicationSourceJar = srcJar.get().toAbsolutePath().toString(); + } + } } /** @@ -260,15 +321,21 @@ Map> getDocsMap(List docs) { private static Set scripts; ScriptInfo getInfo(boolean assureJdkInstalled) { - scriptMixin.validate(); + return getInfo(assureJdkInstalled, false); + } + ScriptInfo getInfo(boolean assureJdkInstalled, boolean downloadSources) { + scriptMixin.validate(); + if (downloadSources) { + Util.setDownloadSources(true); + } ProjectBuilder pb = createProjectBuilder(); Project prj = pb.build(scriptMixin.scriptOrFile); scripts = new HashSet<>(); Path bd = buildDir != null ? Paths.get(buildDir) : null; - return new ScriptInfo(prj, bd, assureJdkInstalled); + return new ScriptInfo(prj, bd, assureJdkInstalled, downloadSources || Util.downloadSources()); } ProjectBuilder createProjectBuilder() { @@ -293,11 +360,16 @@ public static class Tools extends BaseInfoCommand { @Option(name = "select", description = "Indicate the name of the field to select and return from the full info result") String select; + @Option(name = "download-sources", hasValue = false, description = "Resolve and include source JARs in the output") + boolean downloadSources; + @Override public Integer doCall() throws IOException { Gson parser = new GsonBuilder().disableHtmlEscaping().setPrettyPrinting().create(); - ScriptInfo info = getInfo(true); + boolean needSources = downloadSources + || (select != null && select.toLowerCase().contains("source")); + ScriptInfo info = getInfo(true, needSources); if (select != null) { try { Field f = info.getClass().getDeclaredField(select); @@ -362,6 +434,48 @@ public Integer doCall() throws IOException { } } + @CommandDefinition(name = "source-path", aliases = { "sourcepath", + "sources-path" }, description = "Prints source-path used for this application using operating system specific path separation.", generateHelp = true) + public static class SourcePath extends BaseInfoCommand { + + @Option(name = "deps-only", hasValue = false, description = "Only include the dependencies in the output, not the application jar itself") + boolean dependenciesOnly; + + @Override + public Integer doCall() throws IOException { + + ScriptInfo info = getInfo(false, true); + List deps = info.resolvedSourceDependencies != null ? info.resolvedSourceDependencies + : Collections.emptyList(); + List sp = new ArrayList<>(deps.size() + 1); + if (!dependenciesOnly && info.applicationSourceJar != null + && !deps.contains(info.applicationSourceJar)) { + sp.add(info.applicationSourceJar); + } + sp.addAll(deps); + out.println(String.join(CP_SEPARATOR, sp)); + + return ExitException.EXIT_OK; + } + } + + @CommandDefinition(name = "source-jar", aliases = { "sourcejar", + "sources-jar" }, description = "Prints the path to this application's or artifact's source JAR file.", generateHelp = true) + public static class SourceJar extends BaseInfoCommand { + + @Override + public Integer doCall() throws IOException { + ScriptInfo info = getInfo(false, true); + if (info.applicationSourceJar != null) { + out.println(info.applicationSourceJar); + return ExitException.EXIT_OK; + } else { + throw new ExitException(ExitException.EXIT_GENERIC_ERROR, + "No source JAR found for: " + scriptMixin.scriptOrFile); + } + } + } + @CommandDefinition(name = "docs", description = "Open the documentation file in the default browser.", generateHelp = true) public static class Docs extends BaseInfoCommand { diff --git a/src/main/java/dev/jbang/dependencies/ArtifactInfo.java b/src/main/java/dev/jbang/dependencies/ArtifactInfo.java index 1813216c79..59dbe84b7b 100644 --- a/src/main/java/dev/jbang/dependencies/ArtifactInfo.java +++ b/src/main/java/dev/jbang/dependencies/ArtifactInfo.java @@ -4,6 +4,8 @@ import java.nio.file.Path; import java.util.Objects; +import org.jspecify.annotations.Nullable; + import dev.jbang.util.ModuleUtil; /** @@ -13,17 +15,32 @@ public class ArtifactInfo { private final MavenCoordinate coordinate; private final Path file; + private final @Nullable Path sourceFile; + private final boolean sourcesChecked; private final long timestamp; ArtifactInfo(MavenCoordinate coordinate, Path file) { + this(coordinate, file, null, false); + } + + ArtifactInfo(MavenCoordinate coordinate, Path file, @Nullable Path sourceFile, boolean sourcesChecked) { this.coordinate = coordinate; this.file = file; + this.sourceFile = sourceFile; + this.sourcesChecked = sourcesChecked; this.timestamp = Files.exists(file) ? file.toFile().lastModified() : 0; } ArtifactInfo(MavenCoordinate coordinate, Path file, long cachedTimestamp) { + this(coordinate, file, null, false, cachedTimestamp); + } + + ArtifactInfo(MavenCoordinate coordinate, Path file, @Nullable Path sourceFile, boolean sourcesChecked, + long cachedTimestamp) { this.coordinate = coordinate; this.file = file; + this.sourceFile = sourceFile; + this.sourcesChecked = sourcesChecked; this.timestamp = cachedTimestamp; } @@ -35,6 +52,14 @@ public Path getFile() { return file; } + public @Nullable Path getSourceFile() { + return sourceFile; + } + + public boolean isSourcesChecked() { + return sourcesChecked; + } + public long getTimestamp() { return timestamp; } diff --git a/src/main/java/dev/jbang/dependencies/ArtifactResolver.java b/src/main/java/dev/jbang/dependencies/ArtifactResolver.java index 2725037596..1f495425a8 100644 --- a/src/main/java/dev/jbang/dependencies/ArtifactResolver.java +++ b/src/main/java/dev/jbang/dependencies/ArtifactResolver.java @@ -180,9 +180,9 @@ public void close() { context.close(); } - public void downloadSources(Artifact artifact) { + public Optional downloadSources(Artifact artifact) { try { - context.repositorySystem() + ArtifactResult result = context.repositorySystem() .resolveArtifact(context.repositorySystemSession(), new ArtifactRequest() .setArtifact( new SubArtifact( @@ -191,9 +191,21 @@ public void downloadSources(Artifact artifact) { "jar")) .setRepositories( context.remoteRepositories())); + if (result.getArtifact() != null && result.getArtifact().getFile() != null) { + return Optional.of(result.getArtifact().getFile().toPath()); + } } catch (ArtifactResolutionException e) { Util.verboseMsg("Could not resolve sources for " + artifact.toString()); } + return Optional.empty(); + } + + public Optional resolveSource(MavenCoordinate coord) { + return downloadSources(toArtifact(coord)); + } + + public Optional resolveSource(String coord) { + return downloadSources(toArtifact(coord)); } public List resolve(List depIds) { @@ -242,12 +254,12 @@ public List resolve(List depIds) { return artifacts.stream() .map(ar -> { + Path sourcePath = null; if (downloadSources) { - downloadSources(ar.getArtifact()); + sourcePath = downloadSources(ar.getArtifact()).orElse(null); } - return ar.getArtifact(); + return toArtifactInfo(ar.getArtifact(), sourcePath, downloadSources); }) - .map(ArtifactResolver::toArtifactInfo) .collect(Collectors.toList()); } catch (DependencyResolutionException ex) { throw new ExitException(1, "Could not resolve dependencies: " + ex.getMessage(), ex); @@ -434,10 +446,14 @@ private Artifact toArtifact(MavenCoordinate coord) { return new DefaultArtifact(coord.getGroupId(), coord.getArtifactId(), cls, ext, coord.getVersion()); } - private static ArtifactInfo toArtifactInfo(Artifact artifact) { + private static ArtifactInfo toArtifactInfo(Artifact artifact, Path sourcePath, boolean sourcesChecked) { MavenCoordinate coord = new MavenCoordinate(artifact.getGroupId(), artifact.getArtifactId(), artifact.getVersion(), artifact.getClassifier(), artifact.getExtension()); - return new ArtifactInfo(coord, artifact.getFile().toPath()); + return new ArtifactInfo(coord, artifact.getFile().toPath(), sourcePath, sourcesChecked); + } + + private static ArtifactInfo toArtifactInfo(Artifact artifact) { + return toArtifactInfo(artifact, null, false); } public static Path getLocalMavenRepo() { diff --git a/src/main/java/dev/jbang/dependencies/DependencyCache.java b/src/main/java/dev/jbang/dependencies/DependencyCache.java index 6e9687e388..1552e5f99e 100644 --- a/src/main/java/dev/jbang/dependencies/DependencyCache.java +++ b/src/main/java/dev/jbang/dependencies/DependencyCache.java @@ -37,7 +37,12 @@ private static Map> getCache() { MavenCoordinate gav = MavenCoordinate.fromCanonicalString(jsonObject.get("gav").getAsString()); Path file = Paths.get(jsonObject.get("file").getAsString()); long ts = jsonObject.has("ts") ? jsonObject.get("ts").getAsLong() : 0; - return new ArtifactInfo(gav, file, ts); + Path sourceFile = jsonObject.has("sourceFile") + ? Paths.get(jsonObject.get("sourceFile").getAsString()) + : null; + boolean sourcesChecked = jsonObject.has("sourcesChecked") + && jsonObject.get("sourcesChecked").getAsBoolean(); + return new ArtifactInfo(gav, file, sourceFile, sourcesChecked, ts); }; Gson parser = new GsonBuilder() .setPrettyPrinting() @@ -87,6 +92,12 @@ public static void cache(String depsHash, List artifacts) { JsonObject json = new JsonObject(); json.addProperty("gav", src.getCoordinate().toCanonicalForm()); json.addProperty("file", src.getFile().toString()); + if (src.getSourceFile() != null) { + json.addProperty("sourceFile", src.getSourceFile().toString()); + } + if (src.isSourcesChecked()) { + json.addProperty("sourcesChecked", true); + } json.addProperty("ts", src.getTimestamp()); return json; }; diff --git a/src/main/java/dev/jbang/dependencies/DependencyUtil.java b/src/main/java/dev/jbang/dependencies/DependencyUtil.java index c3405e4b65..579f6cfb06 100644 --- a/src/main/java/dev/jbang/dependencies/DependencyUtil.java +++ b/src/main/java/dev/jbang/dependencies/DependencyUtil.java @@ -7,6 +7,7 @@ import static dev.jbang.util.Util.isWindows; import static dev.jbang.util.Util.verboseMsg; +import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; @@ -93,9 +94,14 @@ public static ModularClassPath resolveDependencies(List deps, List ai.isSourcesChecked() + && (ai.getSourceFile() == null || Files.exists(ai.getSourceFile()))); + if (sourcesValid) { + ModularClassPath mcp = new ModularClassPath(cachedDeps); + verboseMsg(String.format("Resolved artifact(s) from cache: %s", mcp)); + return mcp; + } } } @@ -220,4 +226,26 @@ private static boolean isRelativePathRepoReference(String reporef) { // use / return isWindows() && (reporef.startsWith(".\\") || reporef.startsWith("..\\")); } + + public static Optional resolveSource(String coord, List repos) { + return resolveSource(MavenCoordinate.fromString(coord), repos); + } + + public static Optional resolveSource(MavenCoordinate coord, List repos) { + List actualRepos = repos != null && !repos.isEmpty() ? repos + : Collections.singletonList(toMavenRepo("central")); + try (ArtifactResolver resolver = ArtifactResolver.Builder + .create() + .repositories(actualRepos) + .withUserSettings(true) + .localFolder(getJBangLocalMavenRepoOverride()) + .offline(dev.jbang.util.Util.isOffline()) + .ignoreTransitiveRepositories(dev.jbang.util.Util.isIgnoreTransitiveRepositories()) + .forceCacheUpdate(dev.jbang.util.Util.isFresh()) + .logging(!dev.jbang.util.Util.isQuiet()) + .downloadSources(true) + .build()) { + return resolver.resolveSource(coord); + } + } } diff --git a/src/main/java/dev/jbang/dependencies/ModularClassPath.java b/src/main/java/dev/jbang/dependencies/ModularClassPath.java index 9adcbda7e0..fa7def0a35 100644 --- a/src/main/java/dev/jbang/dependencies/ModularClassPath.java +++ b/src/main/java/dev/jbang/dependencies/ModularClassPath.java @@ -5,6 +5,7 @@ import java.io.File; import java.io.IOException; import java.net.URI; +import java.nio.file.Files; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -32,6 +33,7 @@ public class ModularClassPath { private final List artifacts; private List classPaths; + private List sourcePaths; private Optional javafx = Optional.empty(); public ModularClassPath(List artifacts) { @@ -53,6 +55,24 @@ public String getClassPath() { return String.join(CP_SEPARATOR, getClassPaths()); } + public List getSourcePaths() { + if (sourcePaths == null) { + sourcePaths = artifacts + .stream() + .map(ArtifactInfo::getSourceFile) + .filter(Objects::nonNull) + .filter(Files::exists) + .map(it -> it.toAbsolutePath().toString()) + .distinct() + .collect(Collectors.toList()); + } + return sourcePaths; + } + + public String getSourcePath() { + return String.join(CP_SEPARATOR, getSourcePaths()); + } + public String getManifestPath() { return artifacts.stream() .map(it -> it.getFile().toAbsolutePath().toUri()) diff --git a/src/test/java/dev/jbang/cli/TestInfo.java b/src/test/java/dev/jbang/cli/TestInfo.java index 448955d140..831a6d4c9d 100644 --- a/src/test/java/dev/jbang/cli/TestInfo.java +++ b/src/test/java/dev/jbang/cli/TestInfo.java @@ -11,9 +11,11 @@ import java.util.Collection; import org.hamcrest.Matchers; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import dev.jbang.BaseTest; +import dev.jbang.ExitException; import dev.jbang.util.WarTestFixtures; public class TestInfo extends BaseTest { @@ -201,4 +203,82 @@ void testInfoClasspathForWar() throws IOException { Files.deleteIfExists(warPath); } } + + @Test + void testInfoSourceJarSibling() throws IOException { + Path testJar = cwdDir.resolve("mylib.jar"); + Path testSrcJar = cwdDir.resolve("mylib-sources.jar"); + Files.write(testJar, "dummy jar".getBytes()); + Files.write(testSrcJar, "dummy sources".getBytes()); + + Info.SourceJar sourceJar = JBang.parseCommand("info", "source-jar", testJar.toString()); + Info.BaseInfoCommand.ScriptInfo info = sourceJar.getInfo(false, true); + assertThat(info.applicationSourceJar, equalTo(testSrcJar.toAbsolutePath().toString())); + } + + @Test + void testInfoSourcePathSibling() throws IOException { + Path testJar = cwdDir.resolve("mylib2.jar"); + Path testSrcJar = cwdDir.resolve("mylib2-sources.jar"); + Files.write(testJar, "dummy jar".getBytes()); + Files.write(testSrcJar, "dummy sources".getBytes()); + + Info.SourcePath sourcePath = JBang.parseCommand("info", "source-path", testJar.toString()); + Info.BaseInfoCommand.ScriptInfo info = sourcePath.getInfo(false, true); + assertThat(info.applicationSourceJar, equalTo(testSrcJar.toAbsolutePath().toString())); + } + + @Test + void testInfoSourceJarNotFound() { + Path testJar = cwdDir.resolve("nosrc.jar"); + try { + Files.write(testJar, "dummy jar".getBytes()); + Info.SourceJar sourceJar = JBang.parseCommand("info", "source-jar", testJar.toString()); + Assertions.assertThrows(ExitException.class, sourceJar::doCall); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Test + void testInfoSourcePathWithDeps() { + String src = examplesTestFolder.resolve("quote.java").toString(); + Info.SourcePath sourcePath = JBang.parseCommand("info", "source-path", src); + Info.BaseInfoCommand.ScriptInfo info = sourcePath.getInfo(false, true); + assertThat(info.resolvedSourceDependencies, Matchers.>allOf( + hasSize(equalTo(1)), + everyItem(allOf(containsString("picocli"), endsWith("-sources.jar"))))); + } + + @Test + void testInfoSourceJarForGav() { + Info.SourceJar sourceJar = JBang.parseCommand("info", "source-jar", "info.picocli:picocli:4.6.3"); + Info.BaseInfoCommand.ScriptInfo info = sourceJar.getInfo(false, true); + assertThat(info.applicationSourceJar, allOf(containsString("picocli"), endsWith("-sources.jar"))); + } + + @Test + void testInfoToolsWithDownloadSources() { + String src = examplesTestFolder.resolve("quote.java").toString(); + Info.Tools tools = JBang.parseCommand("info", "tools", "--download-sources", src); + Info.BaseInfoCommand.ScriptInfo info = tools.getInfo(false, true); + assertThat(info.resolvedSourceDependencies, Matchers.>allOf( + hasSize(equalTo(1)), + everyItem(allOf(containsString("picocli"), endsWith("-sources.jar"))))); + } + + @Test + void testInfoSourcePathExecution() throws Exception { + String src = examplesTestFolder.resolve("quote.java").toString(); + CaptureResult result = checkedRun("info", "source-path", src); + assertThat(result.result, equalTo(0)); + assertThat(result.normalizedOut(), allOf(containsString("picocli"), containsString("-sources.jar"))); + } + + @Test + void testInfoSourceJarExecution() throws Exception { + CaptureResult result = checkedRun("info", "source-jar", "info.picocli:picocli:4.6.3"); + assertThat(result.result, equalTo(0)); + assertThat(result.normalizedOut(), allOf(containsString("picocli"), containsString("-sources.jar"))); + } } From 68c612776b034bc7ef8cfbbe7464a33536f9df9c Mon Sep 17 00:00:00 2001 From: Arnab Nandy Date: Wed, 7 Oct 2026 13:21:01 +0530 Subject: [PATCH 2/6] refactor: reuse resolveDependencies in DependencyUtil.resolveSource Signed-off-by: Arnab Nandy --- .../jbang/dependencies/ArtifactResolver.java | 7 ----- .../jbang/dependencies/DependencyUtil.java | 31 +++++++++---------- 2 files changed, 15 insertions(+), 23 deletions(-) diff --git a/src/main/java/dev/jbang/dependencies/ArtifactResolver.java b/src/main/java/dev/jbang/dependencies/ArtifactResolver.java index 72db415625..29b04f87f3 100644 --- a/src/main/java/dev/jbang/dependencies/ArtifactResolver.java +++ b/src/main/java/dev/jbang/dependencies/ArtifactResolver.java @@ -203,13 +203,6 @@ public Optional downloadSources(Artifact artifact) { return Optional.empty(); } - public Optional resolveSource(MavenCoordinate coord) { - return downloadSources(toArtifact(coord)); - } - - public Optional resolveSource(String coord) { - return downloadSources(toArtifact(coord)); - } public List resolve(List depIds) { context.repositorySystemSession().getData().set("depIds", depIds); diff --git a/src/main/java/dev/jbang/dependencies/DependencyUtil.java b/src/main/java/dev/jbang/dependencies/DependencyUtil.java index 579f6cfb06..5b399b988e 100644 --- a/src/main/java/dev/jbang/dependencies/DependencyUtil.java +++ b/src/main/java/dev/jbang/dependencies/DependencyUtil.java @@ -15,6 +15,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Optional; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -228,24 +229,22 @@ private static boolean isRelativePathRepoReference(String reporef) { } public static Optional resolveSource(String coord, List repos) { - return resolveSource(MavenCoordinate.fromString(coord), repos); + List actualRepos = repos != null ? repos : Collections.emptyList(); + ModularClassPath mcp = resolveDependencies(Collections.singletonList(coord), actualRepos, + dev.jbang.util.Util.isOffline(), + dev.jbang.util.Util.isIgnoreTransitiveRepositories(), + dev.jbang.util.Util.isFresh(), + !dev.jbang.util.Util.isQuiet(), + true); + return mcp.getArtifacts() + .stream() + .map(ArtifactInfo::getSourceFile) + .filter(Objects::nonNull) + .filter(Files::exists) + .findFirst(); } public static Optional resolveSource(MavenCoordinate coord, List repos) { - List actualRepos = repos != null && !repos.isEmpty() ? repos - : Collections.singletonList(toMavenRepo("central")); - try (ArtifactResolver resolver = ArtifactResolver.Builder - .create() - .repositories(actualRepos) - .withUserSettings(true) - .localFolder(getJBangLocalMavenRepoOverride()) - .offline(dev.jbang.util.Util.isOffline()) - .ignoreTransitiveRepositories(dev.jbang.util.Util.isIgnoreTransitiveRepositories()) - .forceCacheUpdate(dev.jbang.util.Util.isFresh()) - .logging(!dev.jbang.util.Util.isQuiet()) - .downloadSources(true) - .build()) { - return resolver.resolveSource(coord); - } + return resolveSource(coord.toMavenString(), repos); } } From d4f5a36fbfa8c462892b86df16b3fe02046bf935 Mon Sep 17 00:00:00 2001 From: Arnab Nandy Date: Wed, 7 Oct 2026 13:30:51 +0530 Subject: [PATCH 3/6] style: fix spotless formatting in ArtifactResolver and DependencyUtil Signed-off-by: Arnab Nandy --- .../java/dev/jbang/dependencies/ArtifactResolver.java | 1 - src/main/java/dev/jbang/dependencies/DependencyUtil.java | 9 ++++----- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/main/java/dev/jbang/dependencies/ArtifactResolver.java b/src/main/java/dev/jbang/dependencies/ArtifactResolver.java index 29b04f87f3..dffd250a81 100644 --- a/src/main/java/dev/jbang/dependencies/ArtifactResolver.java +++ b/src/main/java/dev/jbang/dependencies/ArtifactResolver.java @@ -203,7 +203,6 @@ public Optional downloadSources(Artifact artifact) { return Optional.empty(); } - public List resolve(List depIds) { context.repositorySystemSession().getData().set("depIds", depIds); // Maven is by default "forgiving" for dependency POM loading: here we want to diff --git a/src/main/java/dev/jbang/dependencies/DependencyUtil.java b/src/main/java/dev/jbang/dependencies/DependencyUtil.java index 5b399b988e..937008f386 100644 --- a/src/main/java/dev/jbang/dependencies/DependencyUtil.java +++ b/src/main/java/dev/jbang/dependencies/DependencyUtil.java @@ -22,6 +22,7 @@ import java.util.stream.Collectors; import dev.jbang.ExitException; +import dev.jbang.util.Util; public class DependencyUtil { @@ -230,11 +231,9 @@ private static boolean isRelativePathRepoReference(String reporef) { public static Optional resolveSource(String coord, List repos) { List actualRepos = repos != null ? repos : Collections.emptyList(); - ModularClassPath mcp = resolveDependencies(Collections.singletonList(coord), actualRepos, - dev.jbang.util.Util.isOffline(), - dev.jbang.util.Util.isIgnoreTransitiveRepositories(), - dev.jbang.util.Util.isFresh(), - !dev.jbang.util.Util.isQuiet(), + ModularClassPath mcp = resolveDependencies( + Collections.singletonList(coord), actualRepos, + Util.isOffline(), Util.isIgnoreTransitiveRepositories(), Util.isFresh(), !Util.isQuiet(), true); return mcp.getArtifacts() .stream() From dafad9eee93f33123a266de24bb6da5244c2a794 Mon Sep 17 00:00:00 2001 From: Arnab Nandy Date: Wed, 7 Oct 2026 16:57:04 +0530 Subject: [PATCH 4/6] fix: prevent transitive source jar leak and standardize cache format Signed-off-by: Arnab Nandy --- src/main/java/dev/jbang/cli/Info.java | 6 ++-- .../jbang/dependencies/DependencyCache.java | 23 ++++++++---- .../jbang/dependencies/DependencyUtil.java | 4 +++ src/test/java/dev/jbang/cli/TestInfo.java | 15 ++++++++ .../jbang/dependencies/TestArtifactInfo.java | 36 +++++++++++++++++++ 5 files changed, 75 insertions(+), 9 deletions(-) diff --git a/src/main/java/dev/jbang/cli/Info.java b/src/main/java/dev/jbang/cli/Info.java index 014bff69e6..3b82897ac2 100644 --- a/src/main/java/dev/jbang/cli/Info.java +++ b/src/main/java/dev/jbang/cli/Info.java @@ -277,8 +277,10 @@ private void resolveApplicationSourceJar(BuildContext ctx) { List artifacts = ctx.resolveClassPath().getArtifacts(); for (ArtifactInfo art : artifacts) { - if (jar.equals(art.getFile()) && art.getSourceFile() != null && Files.exists(art.getSourceFile())) { - applicationSourceJar = art.getSourceFile().toAbsolutePath().toString(); + if (jar.equals(art.getFile())) { + if (art.getSourceFile() != null && Files.exists(art.getSourceFile())) { + applicationSourceJar = art.getSourceFile().toAbsolutePath().toString(); + } return; } } diff --git a/src/main/java/dev/jbang/dependencies/DependencyCache.java b/src/main/java/dev/jbang/dependencies/DependencyCache.java index 1552e5f99e..d30c6e6a25 100644 --- a/src/main/java/dev/jbang/dependencies/DependencyCache.java +++ b/src/main/java/dev/jbang/dependencies/DependencyCache.java @@ -37,11 +37,20 @@ private static Map> getCache() { MavenCoordinate gav = MavenCoordinate.fromCanonicalString(jsonObject.get("gav").getAsString()); Path file = Paths.get(jsonObject.get("file").getAsString()); long ts = jsonObject.has("ts") ? jsonObject.get("ts").getAsLong() : 0; - Path sourceFile = jsonObject.has("sourceFile") - ? Paths.get(jsonObject.get("sourceFile").getAsString()) - : null; - boolean sourcesChecked = jsonObject.has("sourcesChecked") - && jsonObject.get("sourcesChecked").getAsBoolean(); + Path sourceFile = null; + if (jsonObject.has("sources-file") && !jsonObject.get("sources-file").isJsonNull()) { + sourceFile = Paths.get(jsonObject.get("sources-file").getAsString()); + } else if (jsonObject.has("sourceFile") && !jsonObject.get("sourceFile").isJsonNull()) { + sourceFile = Paths.get(jsonObject.get("sourceFile").getAsString()); + } + boolean sourcesChecked = false; + if (jsonObject.has("sources-checked")) { + sourcesChecked = jsonObject.get("sources-checked").getAsBoolean(); + } else if (jsonObject.has("sourcesChecked")) { + sourcesChecked = jsonObject.get("sourcesChecked").getAsBoolean(); + } else if (sourceFile != null) { + sourcesChecked = true; + } return new ArtifactInfo(gav, file, sourceFile, sourcesChecked, ts); }; Gson parser = new GsonBuilder() @@ -93,10 +102,10 @@ public static void cache(String depsHash, List artifacts) { json.addProperty("gav", src.getCoordinate().toCanonicalForm()); json.addProperty("file", src.getFile().toString()); if (src.getSourceFile() != null) { - json.addProperty("sourceFile", src.getSourceFile().toString()); + json.addProperty("sources-file", src.getSourceFile().toString()); } if (src.isSourcesChecked()) { - json.addProperty("sourcesChecked", true); + json.addProperty("sources-checked", true); } json.addProperty("ts", src.getTimestamp()); return json; diff --git a/src/main/java/dev/jbang/dependencies/DependencyUtil.java b/src/main/java/dev/jbang/dependencies/DependencyUtil.java index 937008f386..bbffbbe80f 100644 --- a/src/main/java/dev/jbang/dependencies/DependencyUtil.java +++ b/src/main/java/dev/jbang/dependencies/DependencyUtil.java @@ -235,8 +235,12 @@ public static Optional resolveSource(String coord, List repos) Collections.singletonList(coord), actualRepos, Util.isOffline(), Util.isIgnoreTransitiveRepositories(), Util.isFresh(), !Util.isQuiet(), true); + MavenCoordinate target = MavenCoordinate.fromString(coord); return mcp.getArtifacts() .stream() + .filter(ai -> ai.getCoordinate() != null + && ai.getCoordinate().getGroupId().equals(target.getGroupId()) + && ai.getCoordinate().getArtifactId().equals(target.getArtifactId())) .map(ArtifactInfo::getSourceFile) .filter(Objects::nonNull) .filter(Files::exists) diff --git a/src/test/java/dev/jbang/cli/TestInfo.java b/src/test/java/dev/jbang/cli/TestInfo.java index ed3541bfde..af1d7204f5 100644 --- a/src/test/java/dev/jbang/cli/TestInfo.java +++ b/src/test/java/dev/jbang/cli/TestInfo.java @@ -297,4 +297,19 @@ void testInfoToolsSelectResolvedSourceDependencies() throws Exception { assertThat(result.result, equalTo(0)); assertThat(result.normalizedOut(), allOf(containsString("picocli"), containsString("-sources.jar"))); } + + @Test + void testInfoSourceJarForGavWithoutSources() { + Info.SourceJar sourceJar = JBang.parseCommand("info", "source-jar", + "io.netty:netty-tcnative-boringssl-static:2.0.61.Final"); + Assertions.assertThrows(ExitException.class, sourceJar::doCall); + } + + @Test + void testInfoToolsForGavWithoutSources() { + Info.Tools tools = JBang.parseCommand("info", "tools", "--download-sources", + "io.netty:netty-tcnative-boringssl-static:2.0.61.Final"); + Info.BaseInfoCommand.ScriptInfo info = tools.getInfo(false, true); + assertThat(info.applicationSourceJar, is(nullValue())); + } } diff --git a/src/test/java/dev/jbang/dependencies/TestArtifactInfo.java b/src/test/java/dev/jbang/dependencies/TestArtifactInfo.java index d6e2b0e768..322d76a744 100644 --- a/src/test/java/dev/jbang/dependencies/TestArtifactInfo.java +++ b/src/test/java/dev/jbang/dependencies/TestArtifactInfo.java @@ -4,6 +4,9 @@ import static org.hamcrest.Matchers.*; import static org.hamcrest.io.FileMatchers.aFileWithSize; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -13,6 +16,7 @@ import dev.jbang.BaseTest; import dev.jbang.Settings; +import dev.jbang.util.Util; public class TestArtifactInfo extends BaseTest { @@ -39,4 +43,36 @@ public void testDependencyCache() { assertThat(wonka, contains(classpath.getArtifacts().toArray())); } + + @Test + public void testDependencyCacheWithSources() throws IOException { + DependencyCache.clear(); + + Path jar1 = Files.createFile(jbangTempDir.resolve("test.jar")); + Path src1 = Files.createFile(jbangTempDir.resolve("test-sources.jar")); + Path jar2 = Files.createFile(jbangTempDir.resolve("nosrc.jar")); + + MavenCoordinate coord = new MavenCoordinate("org.example", "test", "1.0", null, "jar"); + ArtifactInfo ai1 = new ArtifactInfo(coord, jar1, src1, true); + ArtifactInfo ai2 = new ArtifactInfo(new MavenCoordinate("org.example", "nosrc", "1.0", null, "jar"), + jar2, null, true); + + DependencyCache.cache("sources-test", Arrays.asList(ai1, ai2)); + + String content = Util.readFileContent(Settings.getCacheDependencyFile()); + assertThat(content, containsString("\"sources-file\":")); + assertThat(content, containsString("test-sources.jar")); + assertThat(content, containsString("\"sources-checked\": true")); + assertThat(content, not(containsString("sourceFile"))); + assertThat(content, not(containsString("sourcesChecked"))); + + DependencyCache.clear(); + List cached = DependencyCache.findDependenciesByHash("sources-test"); + assertThat(cached, notNullValue()); + assertThat(cached, hasSize(2)); + assertThat(cached.get(0).getSourceFile(), equalTo(src1)); + assertThat(cached.get(0).isSourcesChecked(), equalTo(true)); + assertThat(cached.get(1).getSourceFile(), is(nullValue())); + assertThat(cached.get(1).isSourcesChecked(), equalTo(true)); + } } From d38081ad590690b740011ff812581c5791c5fd77 Mon Sep 17 00:00:00 2001 From: Arnab Nandy Date: Wed, 7 Oct 2026 19:21:49 +0530 Subject: [PATCH 5/6] refactor: simplify cache deserialization without legacy compatibility Signed-off-by: Arnab Nandy --- .../dev/jbang/dependencies/DependencyCache.java | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/src/main/java/dev/jbang/dependencies/DependencyCache.java b/src/main/java/dev/jbang/dependencies/DependencyCache.java index d30c6e6a25..bbe5094ac0 100644 --- a/src/main/java/dev/jbang/dependencies/DependencyCache.java +++ b/src/main/java/dev/jbang/dependencies/DependencyCache.java @@ -40,17 +40,10 @@ private static Map> getCache() { Path sourceFile = null; if (jsonObject.has("sources-file") && !jsonObject.get("sources-file").isJsonNull()) { sourceFile = Paths.get(jsonObject.get("sources-file").getAsString()); - } else if (jsonObject.has("sourceFile") && !jsonObject.get("sourceFile").isJsonNull()) { - sourceFile = Paths.get(jsonObject.get("sourceFile").getAsString()); - } - boolean sourcesChecked = false; - if (jsonObject.has("sources-checked")) { - sourcesChecked = jsonObject.get("sources-checked").getAsBoolean(); - } else if (jsonObject.has("sourcesChecked")) { - sourcesChecked = jsonObject.get("sourcesChecked").getAsBoolean(); - } else if (sourceFile != null) { - sourcesChecked = true; } + boolean sourcesChecked = (sourceFile != null) + || (jsonObject.has("sources-checked") + && jsonObject.get("sources-checked").getAsBoolean()); return new ArtifactInfo(gav, file, sourceFile, sourcesChecked, ts); }; Gson parser = new GsonBuilder() From 31ba3eed5b4709b539d8a6a7e2c681cde3c07a7f Mon Sep 17 00:00:00 2001 From: Arnab Nandy Date: Thu, 8 Oct 2026 12:01:33 +0530 Subject: [PATCH 6/6] perf: restore direct single-artifact source resolution Signed-off-by: Arnab Nandy --- src/main/java/dev/jbang/cli/Info.java | 5 ++- .../jbang/dependencies/ArtifactResolver.java | 8 +++++ .../jbang/dependencies/DependencyUtil.java | 34 +++++++++---------- 3 files changed, 28 insertions(+), 19 deletions(-) diff --git a/src/main/java/dev/jbang/cli/Info.java b/src/main/java/dev/jbang/cli/Info.java index 3b82897ac2..0eb09e82e8 100644 --- a/src/main/java/dev/jbang/cli/Info.java +++ b/src/main/java/dev/jbang/cli/Info.java @@ -280,8 +280,11 @@ private void resolveApplicationSourceJar(BuildContext ctx) { if (jar.equals(art.getFile())) { if (art.getSourceFile() != null && Files.exists(art.getSourceFile())) { applicationSourceJar = art.getSourceFile().toAbsolutePath().toString(); + return; + } else if (art.isSourcesChecked()) { + return; } - return; + break; } } diff --git a/src/main/java/dev/jbang/dependencies/ArtifactResolver.java b/src/main/java/dev/jbang/dependencies/ArtifactResolver.java index dffd250a81..72db415625 100644 --- a/src/main/java/dev/jbang/dependencies/ArtifactResolver.java +++ b/src/main/java/dev/jbang/dependencies/ArtifactResolver.java @@ -203,6 +203,14 @@ public Optional downloadSources(Artifact artifact) { return Optional.empty(); } + public Optional resolveSource(MavenCoordinate coord) { + return downloadSources(toArtifact(coord)); + } + + public Optional resolveSource(String coord) { + return downloadSources(toArtifact(coord)); + } + public List resolve(List depIds) { context.repositorySystemSession().getData().set("depIds", depIds); // Maven is by default "forgiving" for dependency POM loading: here we want to diff --git a/src/main/java/dev/jbang/dependencies/DependencyUtil.java b/src/main/java/dev/jbang/dependencies/DependencyUtil.java index bbffbbe80f..579f6cfb06 100644 --- a/src/main/java/dev/jbang/dependencies/DependencyUtil.java +++ b/src/main/java/dev/jbang/dependencies/DependencyUtil.java @@ -15,14 +15,12 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.Optional; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; import dev.jbang.ExitException; -import dev.jbang.util.Util; public class DependencyUtil { @@ -230,24 +228,24 @@ private static boolean isRelativePathRepoReference(String reporef) { } public static Optional resolveSource(String coord, List repos) { - List actualRepos = repos != null ? repos : Collections.emptyList(); - ModularClassPath mcp = resolveDependencies( - Collections.singletonList(coord), actualRepos, - Util.isOffline(), Util.isIgnoreTransitiveRepositories(), Util.isFresh(), !Util.isQuiet(), - true); - MavenCoordinate target = MavenCoordinate.fromString(coord); - return mcp.getArtifacts() - .stream() - .filter(ai -> ai.getCoordinate() != null - && ai.getCoordinate().getGroupId().equals(target.getGroupId()) - && ai.getCoordinate().getArtifactId().equals(target.getArtifactId())) - .map(ArtifactInfo::getSourceFile) - .filter(Objects::nonNull) - .filter(Files::exists) - .findFirst(); + return resolveSource(MavenCoordinate.fromString(coord), repos); } public static Optional resolveSource(MavenCoordinate coord, List repos) { - return resolveSource(coord.toMavenString(), repos); + List actualRepos = repos != null && !repos.isEmpty() ? repos + : Collections.singletonList(toMavenRepo("central")); + try (ArtifactResolver resolver = ArtifactResolver.Builder + .create() + .repositories(actualRepos) + .withUserSettings(true) + .localFolder(getJBangLocalMavenRepoOverride()) + .offline(dev.jbang.util.Util.isOffline()) + .ignoreTransitiveRepositories(dev.jbang.util.Util.isIgnoreTransitiveRepositories()) + .forceCacheUpdate(dev.jbang.util.Util.isFresh()) + .logging(!dev.jbang.util.Util.isQuiet()) + .downloadSources(true) + .build()) { + return resolver.resolveSource(coord); + } } }