diff --git a/itests/brokendeps_jar.java b/itests/brokendeps_jar.java new file mode 100644 index 000000000..799fd3551 --- /dev/null +++ b/itests/brokendeps_jar.java @@ -0,0 +1,6 @@ +//DEPS missing.jar + +class brokendeps { + public static void main(String... args) throws Exception { + } +} diff --git a/itests/brokendeps_source.java b/itests/brokendeps_source.java new file mode 100644 index 000000000..46650c51e --- /dev/null +++ b/itests/brokendeps_source.java @@ -0,0 +1,6 @@ +//DEPS missing.java + +class brokendeps_source { + public static void main(String... args) throws Exception { + } +} diff --git a/src/main/java/dev/jbang/cli/Info.java b/src/main/java/dev/jbang/cli/Info.java index 521969d13..36eba6e90 100644 --- a/src/main/java/dev/jbang/cli/Info.java +++ b/src/main/java/dev/jbang/cli/Info.java @@ -117,6 +117,7 @@ static class ScriptInfo { String gav; String module; Map> docs; + List subProjects; public ScriptInfo(Project prj, Path buildDir, boolean assureJdkInstalled) { originalResource = prj.getResourceRef().getOriginalResource(); @@ -181,10 +182,24 @@ private void init(Project prj) { if (!opts.isEmpty()) { runtimeOptions = opts; } + List subps = prj.getSubProjects(); + if (!subps.isEmpty()) { + subProjects = subps.stream() + .map(Project::getResourceRef) + .map(ProjectFile::new) + .collect(Collectors.toList()); + if (dependencies == null) { + dependencies = new ArrayList<>(); + } + dependencies.addAll( + subps.stream().map(p -> p.getResourceRef().getOriginalResource()).collect(Collectors.toList())); + } } private void init(SourceSet ss) { - List deps = ss.getDependencies(); + List deps = new ArrayList<>(); + deps.addAll(ss.getDependencies()); + deps.addAll(ss.getClassPaths()); if (!deps.isEmpty()) { dependencies = deps; } diff --git a/src/main/java/dev/jbang/dependencies/DependencyResolver.java b/src/main/java/dev/jbang/dependencies/DependencyResolver.java index 70a0068f5..3068c471c 100644 --- a/src/main/java/dev/jbang/dependencies/DependencyResolver.java +++ b/src/main/java/dev/jbang/dependencies/DependencyResolver.java @@ -1,10 +1,14 @@ package dev.jbang.dependencies; import java.io.File; +import java.nio.file.Path; import java.util.*; import java.util.stream.Collectors; import java.util.stream.Stream; +import org.jspecify.annotations.NonNull; + +import dev.jbang.resources.ResourceNotFoundException; import dev.jbang.util.Util; public class DependencyResolver { @@ -73,10 +77,17 @@ public ModularClassPath resolve() { // WARN need File here because it's more lenient about paths than Path! Stream cpas = classPaths .stream() - .map(p -> new ArtifactInfo(null, new File(p).toPath())); + .map(p -> new ArtifactInfo(null, assertExists(new File(p).toPath()))); List arts = Stream.concat(mcp.getArtifacts().stream(), cpas) .collect(Collectors.toList()); return new ModularClassPath(arts); } } + + private @NonNull Path assertExists(@NonNull Path p) { + if (!p.toFile().exists()) { + throw new ResourceNotFoundException(p.toString(), "Jar dependency not found: " + p); + } + return p; + } } diff --git a/src/main/java/dev/jbang/dependencies/DependencyUtil.java b/src/main/java/dev/jbang/dependencies/DependencyUtil.java index e0bf4f00a..258167edf 100644 --- a/src/main/java/dev/jbang/dependencies/DependencyUtil.java +++ b/src/main/java/dev/jbang/dependencies/DependencyUtil.java @@ -12,6 +12,7 @@ import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.function.Predicate; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -197,4 +198,32 @@ public static MavenRepo toMavenRepo(String repoReference) { return new MavenRepo(repoid, reporef); } } + + public static Predicate gavDepFilter() { + return DependencyUtil::isGav; + } + + private static boolean isGav(String ref) { + return looksLikeAPossibleGav(ref) || !JitPackUtil.ensureGAV(ref).equals(ref); + } + + public static List filterGavDeps(List deps) { + return deps.stream().filter(gavDepFilter()).collect(Collectors.toList()); + } + + public static Predicate jarDepFilter() { + return d -> d.endsWith(".jar"); + } + + public static List filterJarDeps(List deps) { + return deps.stream().filter(jarDepFilter()).collect(Collectors.toList()); + } + + public static Predicate sourceDepFilter() { + return gavDepFilter().negate().and(jarDepFilter().negate()); + } + + public static List filterSourceDeps(List deps) { + return deps.stream().filter(sourceDepFilter()).collect(Collectors.toList()); + } } \ No newline at end of file diff --git a/src/main/java/dev/jbang/source/ProjectBuilder.java b/src/main/java/dev/jbang/source/ProjectBuilder.java index ec322aadd..8786ef526 100644 --- a/src/main/java/dev/jbang/source/ProjectBuilder.java +++ b/src/main/java/dev/jbang/source/ProjectBuilder.java @@ -338,7 +338,8 @@ private Project createJbangProject(ResourceRef resourceRef) { SourceSet ss = prj.getMainSourceSet(); ss.addResources(allToFileRef(directives.files(), resourceRef, sibRes1)); - ss.addDependencies(directives.binaryDependencies()); + ss.addDependencies(DependencyUtil.filterGavDeps(directives.dependencies())); + ss.addClassPaths(DependencyUtil.filterJarDeps(directives.dependencies())); ss.addCompileOptions(directives.compileOptions()); ss.addNativeOptions(directives.nativeOptions()); prj.addRepositories(directives.repositories()); @@ -362,10 +363,7 @@ private Project createJbangProject(ResourceRef resourceRef) { ResourceResolver resolver = getAliasResourceResolver(null); ResourceResolver sibRes2 = getSiblingResolver(resourceRef, resolver); - for (String srcDep : directives.sourceDependencies()) { - ResourceRef subRef = resolver.resolve(srcDep, true); - prj.addSubProject(new ProjectBuilder(buildRefs).build(subRef)); - } + handleSourceDeps(DependencyUtil.filterSourceDeps(directives.dependencies()), resolver, prj); boolean first = true; List sources = directives.sources(); @@ -397,6 +395,20 @@ private Project createJbangProject(ResourceRef resourceRef) { return updateProject(prj); } + private void handleSourceDeps(List srcDeps, ResourceResolver resolver, Project prj) { + for (String srcDep : srcDeps) { + ResourceRef subRef = resolver.resolve(srcDep, true); + Project subPrj; + if (subRef != null) { + subPrj = new ProjectBuilder(buildRefs).build(subRef); + } else { + subPrj = new Project(ResourceRef.forUnresolvable(srcDep, + "Could not be resolved from " + resolver.description())); + } + prj.addSubProject(subPrj); + } + } + private Project createSourceProject(ResourceRef resourceRef) { Source src = createSource(resourceRef); Project prj = new Project(src); @@ -657,7 +669,8 @@ private Project updateProject(Source src, Project prj, ResourceResolver resolver } ResourceResolver sibRes1 = getSiblingResolver(srcRef); ss.addResources(allToFileRef(src.getDirectives().files(), srcRef, sibRes1)); - ss.addDependencies(src.collectBinaryDependencies()); + ss.addDependencies(DependencyUtil.filterGavDeps(src.collectDependencies())); + ss.addClassPaths(DependencyUtil.filterJarDeps(src.collectDependencies())); ss.addCompileOptions(src.getCompileOptions()); ss.addNativeOptions(src.getNativeOptions()); prj.addRepositories(src.getDirectives().repositories()); @@ -680,10 +693,7 @@ private Project updateProject(Source src, Project prj, ResourceResolver resolver prj.setJavaVersion(version); } } - for (String srcDep : src.collectSourceDependencies()) { - ResourceRef subRef = sibRes1.resolve(srcDep, true); - prj.addSubProject(new ProjectBuilder(buildRefs).build(subRef)); - } + handleSourceDeps(DependencyUtil.filterSourceDeps(src.collectDependencies()), sibRes1, prj); ResourceResolver sibRes2 = getSiblingResolver(srcRef, resolver); List includedSources = allToSource(src.getDirectives().sources(), srcRef, sibRes2); for (Source includedSource : includedSources) { @@ -723,11 +733,7 @@ private ModularClassPath resolveDependency(String dep) { if (mcp == null) { DependencyResolver resolver = new DependencyResolver() .addDependency(dep) - .addRepositories(allToMavenRepo( - replaceAllProps(additionalRepos))) - .addDependencies(replaceAllProps(additionalDeps)) - .addClassPaths( - replaceAllProps(additionalClasspaths)); + .addRepositories(allToMavenRepo(replaceAllProps(additionalRepos))); mcp = resolver.resolve(); } return mcp; diff --git a/src/main/java/dev/jbang/source/Source.java b/src/main/java/dev/jbang/source/Source.java index f2ae9c2ac..c0e87aaf4 100644 --- a/src/main/java/dev/jbang/source/Source.java +++ b/src/main/java/dev/jbang/source/Source.java @@ -83,12 +83,8 @@ protected String getContents() { public abstract @NonNull Type getType(); - protected List collectBinaryDependencies() { - return getDirectives().binaryDependencies(); - } - - protected List collectSourceDependencies() { - return getDirectives().sourceDependencies(); + protected List collectDependencies() { + return getDirectives().dependencies(); } protected abstract List getCompileOptions(); diff --git a/src/main/java/dev/jbang/source/parser/Directives.java b/src/main/java/dev/jbang/source/parser/Directives.java index 403007d98..cd20de3f0 100644 --- a/src/main/java/dev/jbang/source/parser/Directives.java +++ b/src/main/java/dev/jbang/source/parser/Directives.java @@ -20,7 +20,6 @@ import org.jspecify.annotations.Nullable; import dev.jbang.dependencies.DependencyUtil; -import dev.jbang.dependencies.JitPackUtil; import dev.jbang.dependencies.MavenCoordinate; import dev.jbang.dependencies.MavenRepo; import dev.jbang.util.JavaUtil; @@ -56,21 +55,11 @@ public abstract static class Names { public abstract Stream getAll(); - public List binaryDependencies() { + public List dependencies() { return getAll() .filter(this::isDependDeclare) .map(Directive::getValue) .flatMap(v -> quotedStringToList(Q2TL_SSCT, v).stream()) - .filter(Directives::isGav) - .collect(Collectors.toList()); - } - - public List sourceDependencies() { - return getAll() - .filter(this::isDependDeclare) - .map(Directive::getValue) - .flatMap(v -> quotedStringToList(Q2TL_SSCT, v).stream()) - .filter(it -> !isGav(it)) .collect(Collectors.toList()); } @@ -78,10 +67,6 @@ protected boolean isDependDeclare(Directive d) { return Names.DEPS.equals(d.getName()); } - private static boolean isGav(String ref) { - return DependencyUtil.looksLikeAPossibleGav(ref) || !JitPackUtil.ensureGAV(ref).equals(ref); - } - public List repositories() { return getAll() .filter(this::isRepoDeclare) diff --git a/src/main/java/dev/jbang/source/sources/GroovySource.java b/src/main/java/dev/jbang/source/sources/GroovySource.java index 9e40deb30..2a4a22b96 100644 --- a/src/main/java/dev/jbang/source/sources/GroovySource.java +++ b/src/main/java/dev/jbang/source/sources/GroovySource.java @@ -47,8 +47,8 @@ protected List getRuntimeOptions() { } @Override - protected List collectBinaryDependencies() { - final List allDependencies = super.collectBinaryDependencies(); + protected List collectDependencies() { + final List allDependencies = super.collectDependencies(); final String groovyVersion = getGroovyVersion(); if (groovyVersion.startsWith("4.") || groovyVersion.startsWith("5.")) { allDependencies.add("org.apache.groovy:groovy:" + groovyVersion); diff --git a/src/main/java/dev/jbang/source/sources/KotlinSource.java b/src/main/java/dev/jbang/source/sources/KotlinSource.java index d3b3f7f1d..a132db661 100644 --- a/src/main/java/dev/jbang/source/sources/KotlinSource.java +++ b/src/main/java/dev/jbang/source/sources/KotlinSource.java @@ -30,8 +30,8 @@ public KotlinSource(ResourceRef script, Function replaceProperti } @Override - protected List collectBinaryDependencies() { - final List allDependencies = super.collectBinaryDependencies(); + protected List collectDependencies() { + final List allDependencies = super.collectDependencies(); allDependencies.add("org.jetbrains.kotlin:kotlin-stdlib:" + getKotlinVersion()); return allDependencies; } diff --git a/src/test/java/dev/jbang/cli/TestRun.java b/src/test/java/dev/jbang/cli/TestRun.java index e592a1f1f..0af19be4b 100644 --- a/src/test/java/dev/jbang/cli/TestRun.java +++ b/src/test/java/dev/jbang/cli/TestRun.java @@ -346,7 +346,7 @@ void testHelloWorldJar() throws IOException { CommandLine.ParseResult pr = JBang.getCommandLine() .parseArgs("run", "--deps", "info.picocli:picocli:4.6.3", - "--cp", "dummy.jar", jar); + "--cp", jar, examplesTestFolder.resolve("echo.java").toString()); Run run = (Run) pr.subcommand().commandSpec().userObject(); ProjectBuilder pb = run.createProjectBuilderForRun(); @@ -357,7 +357,6 @@ void testHelloWorldJar() throws IOException { assertThat(code.getMainClass(), not(nullValue())); assertThat(result, containsString("picocli-4.6.3.jar")); - assertThat(result, containsString("dummy.jar")); assertThat(result, containsString("hellojar.jar")); assertThat(code.getResourceRef().getFile().toString(), equalTo(jar)); @@ -2175,9 +2174,10 @@ void testGAVCliReposAndDepsTwoRepos(@TempDir File output) throws IOException { Run run = (Run) pr.subcommand().commandSpec().userObject(); ProjectBuilder pb = run.createProjectBuilderForRun(); + Project prj = pb.build(jar); try { - pb.build(jar); + run.updateGeneratorForRun(prj.codeBuilder().build()).build().generate(); fail("Should have thrown exception"); } catch (ExitException ex) { StringWriter sw = new StringWriter(); diff --git a/src/test/java/dev/jbang/source/TestBuilder.java b/src/test/java/dev/jbang/source/TestBuilder.java index 8e25a67b6..13ed30bf3 100644 --- a/src/test/java/dev/jbang/source/TestBuilder.java +++ b/src/test/java/dev/jbang/source/TestBuilder.java @@ -5,6 +5,7 @@ import static org.hamcrest.CoreMatchers.endsWith; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.*; +import static org.junit.jupiter.api.Assertions.assertThrowsExactly; import java.io.File; import java.io.IOException; @@ -30,6 +31,7 @@ import dev.jbang.catalog.Alias; import dev.jbang.catalog.CatalogUtil; import dev.jbang.cli.ExitException; +import dev.jbang.resources.ResourceNotFoundException; import dev.jbang.source.buildsteps.IntegrationBuildStep; import dev.jbang.source.buildsteps.JarBuildStep; import dev.jbang.source.buildsteps.NativeBuildStep; @@ -554,4 +556,22 @@ protected void runNativeBuilder(List optionList) throws IOException { } }.get().build(); } + + @Test + void testMissingJarDeps() { + ProjectBuilder pb = Project.builder(); + Path src = examplesTestFolder.resolve("brokendeps_jar.java"); + Project prj = pb.build(src); + BuildContext ctx = BuildContext.forProject(prj); + assertThrowsExactly(ResourceNotFoundException.class, ctx::resolveClassPath); + } + + @Test + void testMissingSourceDeps() { + ProjectBuilder pb = Project.builder(); + Path src = examplesTestFolder.resolve("brokendeps_source.java"); + Project prj = pb.build(src); + BuildContext ctx = BuildContext.forProject(prj); + assertThrowsExactly(ResourceNotFoundException.class, ctx::resolveClassPath); + } } diff --git a/src/test/java/dev/jbang/source/parser/TestDirectives.java b/src/test/java/dev/jbang/source/parser/TestDirectives.java index 54bd2857b..3a6c1c937 100644 --- a/src/test/java/dev/jbang/source/parser/TestDirectives.java +++ b/src/test/java/dev/jbang/source/parser/TestDirectives.java @@ -1,5 +1,8 @@ package dev.jbang.source.parser; +import static dev.jbang.dependencies.DependencyUtil.filterGavDeps; +import static dev.jbang.dependencies.DependencyUtil.filterJarDeps; +import static dev.jbang.dependencies.DependencyUtil.filterSourceDeps; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.hasItem; @@ -16,13 +19,17 @@ public class TestDirectives { @Test void testExtractDependencies() { Directives tr = new Directives.Extended( - "//DEPS foo:bar, abc:DEF:123, https://github.com/jbangdev/jbang, something", null); + "//DEPS foo:bar, abc:DEF:123, https://github.com/jbangdev/jbang, local.jar, something", null); - List deps = tr.binaryDependencies(); - assertThat(deps, hasSize(3)); - assertThat(deps, containsInAnyOrder("foo:bar", "abc:DEF:123", "https://github.com/jbangdev/jbang")); + List deps1 = filterGavDeps(tr.dependencies()); + assertThat(deps1, hasSize(3)); + assertThat(deps1, containsInAnyOrder("foo:bar", "abc:DEF:123", "https://github.com/jbangdev/jbang")); + + List deps2 = filterJarDeps(tr.dependencies()); + assertThat(deps2, hasSize(1)); + assertThat(deps2, containsInAnyOrder("local.jar")); - List subs = tr.sourceDependencies(); + List subs = filterSourceDeps(tr.dependencies()); assertThat(subs, containsInAnyOrder("something")); } @@ -31,11 +38,11 @@ void testExtractDependenciesSeparator() { Directives tr = new Directives.Extended( "//DEPS foo:bar, abc:DEF:123, \thttps://github.com/jbangdev/jbang \tsomething\t ", null); - List deps = tr.binaryDependencies(); + List deps = filterGavDeps(tr.dependencies()); assertThat(deps, hasSize(3)); assertThat(deps, containsInAnyOrder("foo:bar", "abc:DEF:123", "https://github.com/jbangdev/jbang")); - List subs = tr.sourceDependencies(); + List subs = filterSourceDeps(tr.dependencies()); assertThat(subs, containsInAnyOrder("something")); } @@ -44,11 +51,11 @@ void testExtractDependenciesQuoted() { Directives tr = new Directives.Extended( "//DEPS abc:DEF:123, 'ch.qos.reload4j:reload4j:[1.2.18,1.2.19)', 'some thing'", null); - List deps = tr.binaryDependencies(); + List deps = filterGavDeps(tr.dependencies()); assertThat(deps, hasSize(2)); assertThat(deps, containsInAnyOrder("abc:DEF:123", "ch.qos.reload4j:reload4j:[1.2.18,1.2.19)")); - List subs = tr.sourceDependencies(); + List subs = filterSourceDeps(tr.dependencies()); assertThat(subs, containsInAnyOrder("some thing")); }