Skip to content

Commit 1b9a338

Browse files
committed
fix: self-referencing dependencies now show proper error message
1 parent a3aaba7 commit 1b9a338

File tree

4 files changed

+113
-80
lines changed

4 files changed

+113
-80
lines changed

itests/selfdep.java

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
//DEPS selfdep.java
2+
3+
class selfdep {
4+
public static void main(String... args) {
5+
System.out.println("Hello, world!");
6+
}
7+
}
8+

src/main/java/dev/jbang/source/ProjectBuilder.java

+93-8
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
import java.util.jar.JarFile;
1313
import java.util.stream.Collectors;
1414

15+
import javax.annotation.Nonnull;
16+
1517
import org.apache.maven.model.Model;
1618
import org.apache.maven.model.io.xpp3.MavenXpp3Reader;
1719
import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
@@ -48,14 +50,21 @@ public class ProjectBuilder {
4850
private List<String> nativeOptions = Collections.emptyList();
4951
private Map<String, String> manifestOptions = new HashMap<>();
5052
private File catalogFile;
51-
52-
private ModularClassPath mcp;
5353
private Boolean nativeImage;
5454
private String javaVersion;
55-
private Properties contextProperties;
5655
private boolean enablePreview;
5756

57+
// Cached values
58+
private Properties contextProperties;
59+
private ModularClassPath mcp;
60+
private final Set<ResourceRef> buildRefs;
61+
5862
ProjectBuilder() {
63+
buildRefs = new HashSet<>();
64+
}
65+
66+
private ProjectBuilder(Set<ResourceRef> buildRefs) {
67+
this.buildRefs = buildRefs;
5968
}
6069

6170
public ProjectBuilder setProperties(Map<String, String> properties) {
@@ -245,6 +254,11 @@ public Project build(Path resourceFile) {
245254
}
246255

247256
public Project build(ResourceRef resourceRef) {
257+
if (!buildRefs.add(resourceRef)) {
258+
throw new ExitException(BaseCommand.EXIT_INVALID_INPUT,
259+
"Self-referencing project dependency found for: '" + resourceRef.getOriginalResource() + "'");
260+
}
261+
248262
Project prj;
249263
if (resourceRef.getFile().getFileName().toString().endsWith(".jar")) {
250264
prj = createJarProject(resourceRef);
@@ -306,12 +320,12 @@ private Project createJbangProject(ResourceRef resourceRef) {
306320

307321
for (String srcDep : tagReader.collectSourceDependencies()) {
308322
ResourceRef subRef = resolver.resolve(srcDep, true);
309-
prj.addSubProject(new ProjectBuilder().build(subRef));
323+
prj.addSubProject(new ProjectBuilder(buildRefs).build(subRef));
310324
}
311325

312326
boolean first = true;
313327
for (Source includedSource : tagReader.collectSources(resourceRef, siblingResolver)) {
314-
includedSource.updateProject(prj, resolver);
328+
updateProject(includedSource, prj, resolver);
315329
if (first) {
316330
prj.setMainSource(includedSource);
317331
first = false;
@@ -324,7 +338,7 @@ private Project createJbangProject(ResourceRef resourceRef) {
324338
private Project createSourceProject(ResourceRef resourceRef) {
325339
Source src = createSource(resourceRef);
326340
Project prj = new Project(src);
327-
return updateProject(src.updateProjectMain(prj, getResourceResolver()));
341+
return updateProject(updateProjectMain(src, prj, getResourceResolver()));
328342
}
329343

330344
private Source createSource(ResourceRef resourceRef) {
@@ -335,7 +349,7 @@ private Source createSource(ResourceRef resourceRef) {
335349

336350
public Project build(Source src) {
337351
Project prj = new Project(src);
338-
return updateProject(src.updateProjectMain(prj, getResourceResolver()));
352+
return updateProject(updateProjectMain(src, prj, getResourceResolver()));
339353
}
340354

341355
private Project importJarMetadata(Project prj) {
@@ -422,7 +436,78 @@ private void updateAllSources(Project prj, List<String> sources) {
422436
.flatMap(f -> Util.explode(null, Util.getCwd(), f).stream())
423437
.map(s -> resolveChecked(resolver, s))
424438
.map(this::createSource)
425-
.forEach(src -> src.updateProject(prj, resolver));
439+
.forEach(src -> updateProject(src, prj, resolver));
440+
}
441+
442+
/**
443+
* Updates the given <code>Project</code> with all the information from this
444+
* <code>Source</code> when that source is the main file. It updates certain
445+
* things at the project level and then calls <code>updateProject()</code> which
446+
* will update things at the <code>SourceSet</code> level.
447+
*
448+
* @param prj The <code>Project</code> to update
449+
* @param resolver The resolver to use for dependent (re)sources
450+
* @return A <code>Project</code>
451+
*/
452+
public Project updateProjectMain(Source src, Project prj, ResourceResolver resolver) {
453+
prj.setDescription(src.tagReader.getDescription().orElse(null));
454+
prj.setGav(src.tagReader.getGav().orElse(null));
455+
prj.setMainClass(src.tagReader.getMain().orElse(null));
456+
prj.setModuleName(src.tagReader.getModule().orElse(null));
457+
prj.getMainSourceSet().addCompileOption("-g");
458+
return updateProject(src, prj, resolver);
459+
}
460+
461+
/**
462+
* Updates the given <code>Project</code> with all the information from this
463+
* <code>Source</code>. This includes the current source file with all other
464+
* source files it references, all resource files, anything to do with
465+
* dependencies, repositories and class paths as well as compile time and
466+
* runtime options.
467+
*
468+
* @param prj The <code>Project</code> to update
469+
* @param resolver The resolver to use for dependent (re)sources
470+
* @return The given <code>Project</code>
471+
*/
472+
@Nonnull
473+
public Project updateProject(Source src, Project prj, ResourceResolver resolver) {
474+
ResourceRef srcRef = src.getResourceRef();
475+
if (!prj.getMainSourceSet().getSources().contains(srcRef)) {
476+
ResourceResolver sibRes1 = new SiblingResourceResolver(srcRef, ResourceResolver.forResources());
477+
SourceSet ss = prj.getMainSourceSet();
478+
ss.addSource(srcRef);
479+
ss.addResources(src.tagReader.collectFiles(srcRef, sibRes1));
480+
ss.addDependencies(src.collectBinaryDependencies());
481+
ss.addCompileOptions(src.getCompileOptions());
482+
ss.addNativeOptions(src.getNativeOptions());
483+
prj.addRepositories(src.tagReader.collectRepositories());
484+
prj.addRuntimeOptions(src.getRuntimeOptions());
485+
src.tagReader.collectManifestOptions().forEach(kv -> {
486+
if (!kv.getKey().isEmpty()) {
487+
prj.getManifestAttributes().put(kv.getKey(), kv.getValue() != null ? kv.getValue() : "true");
488+
}
489+
});
490+
src.tagReader.collectAgentOptions().forEach(kv -> {
491+
if (!kv.getKey().isEmpty()) {
492+
prj.getManifestAttributes().put(kv.getKey(), kv.getValue() != null ? kv.getValue() : "true");
493+
}
494+
});
495+
String version = src.tagReader.getJavaVersion();
496+
if (version != null && JavaUtil.checkRequestedVersion(version)) {
497+
if (new JavaUtil.RequestedVersionComparator().compare(prj.getJavaVersion(), version) > 0) {
498+
prj.setJavaVersion(version);
499+
}
500+
}
501+
for (String srcDep : src.collectSourceDependencies()) {
502+
ResourceRef subRef = sibRes1.resolve(srcDep, true);
503+
prj.addSubProject(new ProjectBuilder(buildRefs).build(subRef));
504+
}
505+
ResourceResolver sibRes2 = new SiblingResourceResolver(srcRef, resolver);
506+
for (Source includedSource : src.tagReader.collectSources(srcRef, sibRes2)) {
507+
updateProject(includedSource, prj, resolver);
508+
}
509+
}
510+
return prj;
426511
}
427512

428513
private List<RefTarget> allToFileRef(List<String> resources) {

src/main/java/dev/jbang/source/Source.java

-72
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,9 @@
1111

1212
import dev.jbang.cli.BaseCommand;
1313
import dev.jbang.cli.ExitException;
14-
import dev.jbang.source.resolvers.SiblingResourceResolver;
1514
import dev.jbang.source.sources.*;
1615
import dev.jbang.source.sources.KotlinSource;
1716
import dev.jbang.source.sources.MarkdownSource;
18-
import dev.jbang.util.JavaUtil;
1917
import dev.jbang.util.Util;
2018

2119
/**
@@ -113,76 +111,6 @@ public boolean enablePreview() {
113111
return !tagReader.collectRawOptions("PREVIEW").isEmpty();
114112
}
115113

116-
/**
117-
* Updates the given <code>Project</code> with all the information from this
118-
* <code>Source</code> when that source is the main file. It updates certain
119-
* things at the project level and then calls <code>updateProject()</code> which
120-
* will update things at the <code>SourceSet</code> level.
121-
*
122-
* @param prj The <code>Project</code> to update
123-
* @param resolver The resolver to use for dependent (re)sources
124-
* @return A <code>Project</code>
125-
*/
126-
public Project updateProjectMain(Project prj, ResourceResolver resolver) {
127-
prj.setDescription(tagReader.getDescription().orElse(null));
128-
prj.setGav(tagReader.getGav().orElse(null));
129-
prj.setMainClass(tagReader.getMain().orElse(null));
130-
prj.setModuleName(tagReader.getModule().orElse(null));
131-
prj.getMainSourceSet().addCompileOption("-g");
132-
return updateProject(prj, resolver);
133-
}
134-
135-
/**
136-
* Updates the given <code>Project</code> with all the information from this
137-
* <code>Source</code>. This includes the current source file with all other
138-
* source files it references, all resource files, anything to do with
139-
* dependencies, repositories and class paths as well as compile time and
140-
* runtime options.
141-
*
142-
* @param prj The <code>Project</code> to update
143-
* @param resolver The resolver to use for dependent (re)sources
144-
* @return The given <code>Project</code>
145-
*/
146-
@Nonnull
147-
public Project updateProject(Project prj, ResourceResolver resolver) {
148-
if (!prj.getMainSourceSet().getSources().contains(getResourceRef())) {
149-
ResourceResolver sibRes1 = new SiblingResourceResolver(resourceRef, ResourceResolver.forResources());
150-
SourceSet ss = prj.getMainSourceSet();
151-
ss.addSource(this.getResourceRef());
152-
ss.addResources(tagReader.collectFiles(resourceRef, sibRes1));
153-
ss.addDependencies(collectBinaryDependencies());
154-
ss.addCompileOptions(getCompileOptions());
155-
ss.addNativeOptions(getNativeOptions());
156-
prj.addRepositories(tagReader.collectRepositories());
157-
prj.addRuntimeOptions(getRuntimeOptions());
158-
tagReader.collectManifestOptions().forEach(kv -> {
159-
if (!kv.getKey().isEmpty()) {
160-
prj.getManifestAttributes().put(kv.getKey(), kv.getValue() != null ? kv.getValue() : "true");
161-
}
162-
});
163-
tagReader.collectAgentOptions().forEach(kv -> {
164-
if (!kv.getKey().isEmpty()) {
165-
prj.getManifestAttributes().put(kv.getKey(), kv.getValue() != null ? kv.getValue() : "true");
166-
}
167-
});
168-
String version = tagReader.getJavaVersion();
169-
if (version != null && JavaUtil.checkRequestedVersion(version)) {
170-
if (new JavaUtil.RequestedVersionComparator().compare(prj.getJavaVersion(), version) > 0) {
171-
prj.setJavaVersion(version);
172-
}
173-
}
174-
for (String srcDep : collectSourceDependencies()) {
175-
ResourceRef subRef = sibRes1.resolve(srcDep, true);
176-
prj.addSubProject(new ProjectBuilder().build(subRef));
177-
}
178-
ResourceResolver sibRes2 = new SiblingResourceResolver(getResourceRef(), resolver);
179-
for (Source includedSource : tagReader.collectSources(resourceRef, sibRes2)) {
180-
includedSource.updateProject(prj, resolver);
181-
}
182-
}
183-
return prj;
184-
}
185-
186114
// Used only by tests
187115
static Source forResource(String resource, Function<String, String> replaceProperties) {
188116
return forResource(ResourceResolver.forResources(), resource, replaceProperties);

src/test/java/dev/jbang/source/TestBuilder.java

+12
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import dev.jbang.Settings;
3030
import dev.jbang.catalog.Alias;
3131
import dev.jbang.catalog.CatalogUtil;
32+
import dev.jbang.cli.ExitException;
3233
import dev.jbang.source.buildsteps.JarBuildStep;
3334
import dev.jbang.source.buildsteps.NativeBuildStep;
3435
import dev.jbang.source.sources.JavaSource;
@@ -393,6 +394,17 @@ void testSourceDep() throws IOException {
393394
assertThat(callCount.get(), equalTo(2));
394395
}
395396

397+
@Test
398+
void testSourceSelfDep() throws IOException {
399+
try {
400+
Path selfdep = examplesTestFolder.resolve("selfdep.java").toAbsolutePath();
401+
ProjectBuilder pb = Project.builder();
402+
Project prj = pb.build(selfdep.toString());
403+
} catch (ExitException ex) {
404+
assertThat(ex.getMessage(), startsWith("Self-referencing project dependency found for:"));
405+
}
406+
}
407+
396408
private void runBuild(BuildContext ctx, BiConsumer<BuildContext, List<String>> compileStep,
397409
Function<BuildContext, Project> jarStep, BiConsumer<BuildContext, List<String>> nativeStep)
398410
throws IOException {

0 commit comments

Comments
 (0)