Skip to content

Commit b9f1258

Browse files
Merge pull request #10576 from aloubyansky/10549
Bootstrap workspace discovery: fix infinite loop, pom model cache
2 parents 91b6981 + 297a82b commit b9f1258

File tree

15 files changed

+429
-78
lines changed

15 files changed

+429
-78
lines changed

independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/workspace/LocalProject.java

Lines changed: 132 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
package io.quarkus.bootstrap.resolver.maven.workspace;
22

3-
// import io.quarkus.bootstrap.BootstrapConstants;
4-
// import io.quarkus.bootstrap.BootstrapConstants;
53
import io.quarkus.bootstrap.model.AppArtifact;
64
import io.quarkus.bootstrap.model.AppArtifactKey;
75
import io.quarkus.bootstrap.resolver.maven.BootstrapMavenContext;
@@ -11,8 +9,10 @@
119
import java.nio.file.Path;
1210
import java.util.ArrayList;
1311
import java.util.Collections;
12+
import java.util.HashMap;
1413
import java.util.HashSet;
1514
import java.util.List;
15+
import java.util.Map;
1616
import java.util.Set;
1717
import org.apache.maven.model.Dependency;
1818
import org.apache.maven.model.Model;
@@ -30,6 +30,125 @@ public class LocalProject {
3030
private static final String PROJECT_BASEDIR = "${project.basedir}";
3131
private static final String POM_XML = "pom.xml";
3232

33+
private static class WorkspaceLoader {
34+
35+
private final LocalWorkspace workspace = new LocalWorkspace();
36+
private final Map<Path, Model> cachedModels = new HashMap<>();
37+
private final Path currentProjectPom;
38+
private Path workspaceRootPom;
39+
40+
private WorkspaceLoader(Path currentProjectPom) throws BootstrapMavenException {
41+
this.currentProjectPom = isPom(currentProjectPom) ? currentProjectPom
42+
: locateCurrentProjectPom(currentProjectPom, true);
43+
}
44+
45+
private boolean isPom(Path p) {
46+
if (Files.exists(p) && !Files.isDirectory(p)) {
47+
try {
48+
loadAndCache(p);
49+
return true;
50+
} catch (BootstrapMavenException e) {
51+
// not a POM file
52+
}
53+
}
54+
return false;
55+
}
56+
57+
private Model model(Path pomFile) throws BootstrapMavenException {
58+
Model model = cachedModels.get(pomFile.getParent());
59+
if (model == null) {
60+
model = loadAndCache(pomFile);
61+
}
62+
return model;
63+
}
64+
65+
private Model loadAndCache(Path pomFile) throws BootstrapMavenException {
66+
final Model model = readModel(pomFile);
67+
cachedModels.put(pomFile.getParent(), model);
68+
return model;
69+
}
70+
71+
void setWorkspaceRootPom(Path rootPom) {
72+
this.workspaceRootPom = rootPom;
73+
}
74+
75+
private Path getWorkspaceRootPom() throws BootstrapMavenException {
76+
return workspaceRootPom == null ? workspaceRootPom = resolveWorkspaceRootPom() : workspaceRootPom;
77+
}
78+
79+
private Path resolveWorkspaceRootPom() throws BootstrapMavenException {
80+
Path rootPom = null;
81+
Path projectPom = currentProjectPom;
82+
Model model = model(projectPom);
83+
do {
84+
rootPom = projectPom;
85+
final Parent parent = model.getParent();
86+
if (parent != null
87+
&& parent.getRelativePath() != null
88+
&& !parent.getRelativePath().isEmpty()) {
89+
projectPom = projectPom.getParent().resolve(parent.getRelativePath()).normalize();
90+
if (Files.isDirectory(projectPom)) {
91+
projectPom = projectPom.resolve(POM_XML);
92+
}
93+
} else {
94+
final Path parentDir = projectPom.getParent().getParent();
95+
if (parentDir == null) {
96+
break;
97+
}
98+
projectPom = parentDir.resolve(POM_XML);
99+
}
100+
model = null;
101+
if (Files.exists(projectPom)) {
102+
model = cachedModels.get(projectPom.getParent());
103+
if (model == null) {
104+
model = loadAndCache(projectPom);
105+
} else {
106+
// if the parent is not at the top of the FS tree, it might have already been parsed
107+
model = null;
108+
for (Map.Entry<Path, Model> entry : cachedModels.entrySet()) {
109+
// we are looking for the root dir of the workspace
110+
if (rootPom.getNameCount() > entry.getKey().getNameCount()) {
111+
rootPom = entry.getValue().getPomFile().toPath();
112+
}
113+
}
114+
}
115+
}
116+
} while (model != null);
117+
return rootPom;
118+
}
119+
120+
LocalProject load() throws BootstrapMavenException {
121+
load(null, getWorkspaceRootPom());
122+
if (workspace.getCurrentProject() == null) {
123+
if (!currentProjectPom.equals(getWorkspaceRootPom())) {
124+
load(null, currentProjectPom);
125+
}
126+
if (workspace.getCurrentProject() == null) {
127+
throw new BootstrapMavenException(
128+
"Failed to locate project " + currentProjectPom + " in the loaded workspace");
129+
}
130+
}
131+
return workspace.getCurrentProject();
132+
}
133+
134+
private void load(LocalProject parent, Path pom) throws BootstrapMavenException {
135+
final Model model = model(pom);
136+
final LocalProject project = new LocalProject(model, workspace);
137+
if (parent != null) {
138+
parent.modules.add(project);
139+
}
140+
if (workspace.getCurrentProject() == null && currentProjectPom.getParent().equals(project.getDir())) {
141+
workspace.setCurrentProject(project);
142+
}
143+
final List<String> modules = project.getRawModel().getModules();
144+
if (!modules.isEmpty()) {
145+
for (String module : modules) {
146+
load(project, project.getDir().resolve(module).resolve(POM_XML));
147+
}
148+
}
149+
}
150+
}
151+
33152
public static LocalProject load(Path path) throws BootstrapMavenException {
34153
return load(path, true);
35154
}
@@ -52,28 +171,14 @@ public static LocalProject loadWorkspace(Path path) throws BootstrapMavenExcepti
52171
}
53172

54173
public static LocalProject loadWorkspace(Path path, boolean required) throws BootstrapMavenException {
55-
path = path.normalize().toAbsolutePath();
56-
Path currentProjectPom = null;
57-
Model rootModel = null;
58-
if (!Files.isDirectory(path)) {
59-
// see if that's an actual pom
60-
try {
61-
rootModel = loadRootModel(path);
62-
if (rootModel != null) {
63-
currentProjectPom = path;
64-
}
65-
} catch (BootstrapMavenException e) {
66-
// ignore, it's not a POM file, we'll be looking for the POM later
67-
}
68-
}
69-
if (currentProjectPom == null) {
70-
currentProjectPom = locateCurrentProjectPom(path, required);
71-
if (currentProjectPom == null) {
72-
return null;
174+
try {
175+
return new WorkspaceLoader(path.normalize().toAbsolutePath()).load();
176+
} catch (Exception e) {
177+
if (required) {
178+
throw e;
73179
}
74-
rootModel = loadRootModel(currentProjectPom);
180+
return null;
75181
}
76-
return loadWorkspace(currentProjectPom, rootModel);
77182
}
78183

79184
/**
@@ -90,66 +195,15 @@ public static LocalProject loadWorkspace(BootstrapMavenContext ctx) throws Boots
90195
return null;
91196
}
92197
final Path rootProjectBaseDir = ctx.getRootProjectBaseDir();
93-
final Model rootModel = rootProjectBaseDir == null || rootProjectBaseDir.equals(currentProjectPom.getParent())
94-
? loadRootModel(currentProjectPom)
95-
: readModel(rootProjectBaseDir.resolve(POM_XML));
96-
final LocalProject lp = loadWorkspace(currentProjectPom, rootModel);
198+
final WorkspaceLoader wsLoader = new WorkspaceLoader(currentProjectPom);
199+
if (rootProjectBaseDir != null && !rootProjectBaseDir.equals(currentProjectPom.getParent())) {
200+
wsLoader.setWorkspaceRootPom(rootProjectBaseDir.resolve(POM_XML));
201+
}
202+
final LocalProject lp = wsLoader.load();
97203
lp.getWorkspace().setBootstrapMavenContext(ctx);
98204
return lp;
99205
}
100206

101-
private static LocalProject loadWorkspace(Path currentProjectPom, Model rootModel) throws BootstrapMavenException {
102-
final LocalWorkspace ws = new LocalWorkspace();
103-
LocalProject project = load(ws, null, rootModel, currentProjectPom.getParent());
104-
if (project == null) {
105-
project = load(ws, null, readModel(currentProjectPom), currentProjectPom.getParent());
106-
}
107-
ws.setCurrentProject(project);
108-
return project;
109-
}
110-
111-
private static LocalProject load(LocalWorkspace workspace, LocalProject parent, Model model, Path currentProjectDir)
112-
throws BootstrapMavenException {
113-
final LocalProject project = new LocalProject(model, workspace);
114-
if (parent != null) {
115-
parent.modules.add(project);
116-
}
117-
LocalProject result = currentProjectDir == null || !currentProjectDir.equals(project.getDir()) ? null : project;
118-
final List<String> modules = project.getRawModel().getModules();
119-
if (!modules.isEmpty()) {
120-
Path dirArg = result == null ? currentProjectDir : null;
121-
for (String module : modules) {
122-
final LocalProject loaded = load(workspace, project,
123-
readModel(project.getDir().resolve(module).resolve(POM_XML)), dirArg);
124-
if (loaded != null && result == null) {
125-
result = loaded;
126-
dirArg = null;
127-
}
128-
}
129-
}
130-
return result;
131-
}
132-
133-
private static Model loadRootModel(Path pomXml) throws BootstrapMavenException {
134-
Model model = null;
135-
while (pomXml != null && Files.exists(pomXml)) {
136-
model = readModel(pomXml);
137-
final Parent parent = model.getParent();
138-
if (parent != null
139-
&& parent.getRelativePath() != null
140-
&& !parent.getRelativePath().isEmpty()) {
141-
pomXml = pomXml.getParent().resolve(parent.getRelativePath()).normalize();
142-
if (Files.isDirectory(pomXml)) {
143-
pomXml = pomXml.resolve(POM_XML);
144-
}
145-
} else {
146-
final Path parentDir = pomXml.getParent().getParent();
147-
pomXml = parentDir == null ? null : parentDir.resolve(POM_XML);
148-
}
149-
}
150-
return model;
151-
}
152-
153207
private static final Model readModel(Path pom) throws BootstrapMavenException {
154208
try {
155209
final Model model = ModelUtils.readModel(pom);

independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/workspace/LocalWorkspace.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,10 @@ void setResolvedVersion(String resolvedVersion) {
215215
this.resolvedVersion = resolvedVersion;
216216
}
217217

218+
LocalProject getCurrentProject() {
219+
return currentProject;
220+
}
221+
218222
void setCurrentProject(LocalProject currentProject) {
219223
this.currentProject = currentProject;
220224
}

independent-projects/bootstrap/maven-resolver/src/test/java/io/quarkus/bootstrap/workspace/test/LocalWorkspaceDiscoveryTest.java

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,78 @@ public static void cleanup() {
116116
IoUtils.recursiveDelete(workDir);
117117
}
118118

119+
@Test
120+
public void loadWorkspaceFromRootDirWithParentInChildDir() throws Exception {
121+
final URL projectUrl = Thread.currentThread().getContextClassLoader().getResource("workspace-parent-is-not-root-dir");
122+
assertNotNull(projectUrl);
123+
final Path projectDir = Paths.get(projectUrl.toURI());
124+
assertTrue(Files.exists(projectDir));
125+
final LocalProject project = LocalProject.loadWorkspace(projectDir);
126+
127+
assertEquals("acme", project.getArtifactId());
128+
assertWorkspaceWithParentInChildDir(project);
129+
}
130+
131+
@Test
132+
public void loadWorkspaceFromModuleDirWithParentInChildDir() throws Exception {
133+
final URL projectUrl = Thread.currentThread().getContextClassLoader()
134+
.getResource("workspace-parent-is-not-root-dir/acme-application");
135+
assertNotNull(projectUrl);
136+
final Path projectDir = Paths.get(projectUrl.toURI());
137+
assertTrue(Files.exists(projectDir));
138+
final LocalProject project = LocalProject.loadWorkspace(projectDir);
139+
140+
assertEquals("acme-application", project.getArtifactId());
141+
assertWorkspaceWithParentInChildDir(project);
142+
}
143+
144+
private void assertWorkspaceWithParentInChildDir(final LocalProject project) {
145+
final LocalWorkspace workspace = project.getWorkspace();
146+
assertNotNull(workspace.getProject("org.acme", "acme"));
147+
assertNotNull(workspace.getProject("org.acme", "acme-parent"));
148+
assertNotNull(workspace.getProject("org.acme", "acme-dependencies"));
149+
assertNotNull(workspace.getProject("org.acme", "acme-backend"));
150+
assertNotNull(workspace.getProject("org.acme", "acme-backend-rest-api"));
151+
assertNotNull(workspace.getProject("org.acme", "acme-application"));
152+
assertEquals(6, workspace.getProjects().size());
153+
}
154+
155+
@Test
156+
public void loadWorkspaceWithAlternatePomDefaultPom() throws Exception {
157+
final URL projectUrl = Thread.currentThread().getContextClassLoader()
158+
.getResource("workspace-alternate-pom/root/module1");
159+
assertNotNull(projectUrl);
160+
final Path projectDir = Paths.get(projectUrl.toURI());
161+
assertTrue(Files.exists(projectDir));
162+
final LocalProject project = LocalProject.loadWorkspace(projectDir);
163+
164+
assertEquals("root-module1", project.getArtifactId());
165+
final LocalWorkspace workspace = project.getWorkspace();
166+
assertNotNull(workspace.getProject("org.acme", "root"));
167+
assertNotNull(workspace.getProject("org.acme", "root-module1"));
168+
assertNull(workspace.getProject("org.acme", "root-module2"));
169+
assertNotNull(workspace.getProject("org.acme", "root-submodule"));
170+
assertEquals(3, workspace.getProjects().size());
171+
}
172+
173+
@Test
174+
public void loadWorkspaceWithAlternatePom() throws Exception {
175+
final URL projectUrl = Thread.currentThread().getContextClassLoader()
176+
.getResource("workspace-alternate-pom/root/module1/pom2.xml");
177+
assertNotNull(projectUrl);
178+
final Path projectDir = Paths.get(projectUrl.toURI());
179+
assertTrue(Files.exists(projectDir));
180+
final LocalProject project = LocalProject.loadWorkspace(projectDir);
181+
182+
assertEquals("root-module1", project.getArtifactId());
183+
final LocalWorkspace workspace = project.getWorkspace();
184+
assertNotNull(workspace.getProject("org.acme", "root"));
185+
assertNotNull(workspace.getProject("org.acme", "root-module1"));
186+
assertNotNull(workspace.getProject("org.acme", "root-module2"));
187+
assertNull(workspace.getProject("org.acme", "root-submodule"));
188+
assertEquals(3, workspace.getProjects().size());
189+
}
190+
119191
@Test
120192
public void loadIndependentProjectInTheWorkspaceTree() throws Exception {
121193
final LocalProject project = LocalProject
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project>
3+
<modelVersion>4.0.0</modelVersion>
4+
5+
<parent>
6+
<groupId>org.acme</groupId>
7+
<artifactId>root</artifactId>
8+
<version>1.0</version>
9+
<relativePath>../</relativePath>
10+
</parent>
11+
12+
<artifactId>root-module1</artifactId>
13+
14+
<modules>
15+
<module>submodule</module>
16+
</modules>
17+
</project>
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project>
3+
<modelVersion>4.0.0</modelVersion>
4+
5+
<parent>
6+
<groupId>org.acme</groupId>
7+
<artifactId>root</artifactId>
8+
<version>1.0</version>
9+
<relativePath>../pom2.xml</relativePath>
10+
</parent>
11+
12+
<artifactId>root-module1</artifactId>
13+
</project>
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project>
3+
<modelVersion>4.0.0</modelVersion>
4+
5+
<parent>
6+
<groupId>org.acme</groupId>
7+
<artifactId>root-module1</artifactId>
8+
<version>1.0</version>
9+
<relativePath>../</relativePath>
10+
</parent>
11+
12+
<artifactId>root-submodule</artifactId>
13+
</project>

0 commit comments

Comments
 (0)