Skip to content

Commit

Permalink
Bootstrap workspace discovery: fix infinite loop, pom model cache
Browse files Browse the repository at this point in the history
  • Loading branch information
aloubyansky committed Jul 8, 2020
1 parent 29a7fcb commit 297a82b
Show file tree
Hide file tree
Showing 15 changed files with 429 additions and 78 deletions.
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package io.quarkus.bootstrap.resolver.maven.workspace;

// import io.quarkus.bootstrap.BootstrapConstants;
// import io.quarkus.bootstrap.BootstrapConstants;
import io.quarkus.bootstrap.model.AppArtifact;
import io.quarkus.bootstrap.model.AppArtifactKey;
import io.quarkus.bootstrap.resolver.maven.BootstrapMavenContext;
Expand All @@ -11,8 +9,10 @@
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.maven.model.Dependency;
import org.apache.maven.model.Model;
Expand All @@ -30,6 +30,125 @@ public class LocalProject {
private static final String PROJECT_BASEDIR = "${project.basedir}";
private static final String POM_XML = "pom.xml";

private static class WorkspaceLoader {

private final LocalWorkspace workspace = new LocalWorkspace();
private final Map<Path, Model> cachedModels = new HashMap<>();
private final Path currentProjectPom;
private Path workspaceRootPom;

private WorkspaceLoader(Path currentProjectPom) throws BootstrapMavenException {
this.currentProjectPom = isPom(currentProjectPom) ? currentProjectPom
: locateCurrentProjectPom(currentProjectPom, true);
}

private boolean isPom(Path p) {
if (Files.exists(p) && !Files.isDirectory(p)) {
try {
loadAndCache(p);
return true;
} catch (BootstrapMavenException e) {
// not a POM file
}
}
return false;
}

private Model model(Path pomFile) throws BootstrapMavenException {
Model model = cachedModels.get(pomFile.getParent());
if (model == null) {
model = loadAndCache(pomFile);
}
return model;
}

private Model loadAndCache(Path pomFile) throws BootstrapMavenException {
final Model model = readModel(pomFile);
cachedModels.put(pomFile.getParent(), model);
return model;
}

void setWorkspaceRootPom(Path rootPom) {
this.workspaceRootPom = rootPom;
}

private Path getWorkspaceRootPom() throws BootstrapMavenException {
return workspaceRootPom == null ? workspaceRootPom = resolveWorkspaceRootPom() : workspaceRootPom;
}

private Path resolveWorkspaceRootPom() throws BootstrapMavenException {
Path rootPom = null;
Path projectPom = currentProjectPom;
Model model = model(projectPom);
do {
rootPom = projectPom;
final Parent parent = model.getParent();
if (parent != null
&& parent.getRelativePath() != null
&& !parent.getRelativePath().isEmpty()) {
projectPom = projectPom.getParent().resolve(parent.getRelativePath()).normalize();
if (Files.isDirectory(projectPom)) {
projectPom = projectPom.resolve(POM_XML);
}
} else {
final Path parentDir = projectPom.getParent().getParent();
if (parentDir == null) {
break;
}
projectPom = parentDir.resolve(POM_XML);
}
model = null;
if (Files.exists(projectPom)) {
model = cachedModels.get(projectPom.getParent());
if (model == null) {
model = loadAndCache(projectPom);
} else {
// if the parent is not at the top of the FS tree, it might have already been parsed
model = null;
for (Map.Entry<Path, Model> entry : cachedModels.entrySet()) {
// we are looking for the root dir of the workspace
if (rootPom.getNameCount() > entry.getKey().getNameCount()) {
rootPom = entry.getValue().getPomFile().toPath();
}
}
}
}
} while (model != null);
return rootPom;
}

LocalProject load() throws BootstrapMavenException {
load(null, getWorkspaceRootPom());
if (workspace.getCurrentProject() == null) {
if (!currentProjectPom.equals(getWorkspaceRootPom())) {
load(null, currentProjectPom);
}
if (workspace.getCurrentProject() == null) {
throw new BootstrapMavenException(
"Failed to locate project " + currentProjectPom + " in the loaded workspace");
}
}
return workspace.getCurrentProject();
}

private void load(LocalProject parent, Path pom) throws BootstrapMavenException {
final Model model = model(pom);
final LocalProject project = new LocalProject(model, workspace);
if (parent != null) {
parent.modules.add(project);
}
if (workspace.getCurrentProject() == null && currentProjectPom.getParent().equals(project.getDir())) {
workspace.setCurrentProject(project);
}
final List<String> modules = project.getRawModel().getModules();
if (!modules.isEmpty()) {
for (String module : modules) {
load(project, project.getDir().resolve(module).resolve(POM_XML));
}
}
}
}

public static LocalProject load(Path path) throws BootstrapMavenException {
return load(path, true);
}
Expand All @@ -52,28 +171,14 @@ public static LocalProject loadWorkspace(Path path) throws BootstrapMavenExcepti
}

public static LocalProject loadWorkspace(Path path, boolean required) throws BootstrapMavenException {
path = path.normalize().toAbsolutePath();
Path currentProjectPom = null;
Model rootModel = null;
if (!Files.isDirectory(path)) {
// see if that's an actual pom
try {
rootModel = loadRootModel(path);
if (rootModel != null) {
currentProjectPom = path;
}
} catch (BootstrapMavenException e) {
// ignore, it's not a POM file, we'll be looking for the POM later
}
}
if (currentProjectPom == null) {
currentProjectPom = locateCurrentProjectPom(path, required);
if (currentProjectPom == null) {
return null;
try {
return new WorkspaceLoader(path.normalize().toAbsolutePath()).load();
} catch (Exception e) {
if (required) {
throw e;
}
rootModel = loadRootModel(currentProjectPom);
return null;
}
return loadWorkspace(currentProjectPom, rootModel);
}

/**
Expand All @@ -90,66 +195,15 @@ public static LocalProject loadWorkspace(BootstrapMavenContext ctx) throws Boots
return null;
}
final Path rootProjectBaseDir = ctx.getRootProjectBaseDir();
final Model rootModel = rootProjectBaseDir == null || rootProjectBaseDir.equals(currentProjectPom.getParent())
? loadRootModel(currentProjectPom)
: readModel(rootProjectBaseDir.resolve(POM_XML));
final LocalProject lp = loadWorkspace(currentProjectPom, rootModel);
final WorkspaceLoader wsLoader = new WorkspaceLoader(currentProjectPom);
if (rootProjectBaseDir != null && !rootProjectBaseDir.equals(currentProjectPom.getParent())) {
wsLoader.setWorkspaceRootPom(rootProjectBaseDir.resolve(POM_XML));
}
final LocalProject lp = wsLoader.load();
lp.getWorkspace().setBootstrapMavenContext(ctx);
return lp;
}

private static LocalProject loadWorkspace(Path currentProjectPom, Model rootModel) throws BootstrapMavenException {
final LocalWorkspace ws = new LocalWorkspace();
LocalProject project = load(ws, null, rootModel, currentProjectPom.getParent());
if (project == null) {
project = load(ws, null, readModel(currentProjectPom), currentProjectPom.getParent());
}
ws.setCurrentProject(project);
return project;
}

private static LocalProject load(LocalWorkspace workspace, LocalProject parent, Model model, Path currentProjectDir)
throws BootstrapMavenException {
final LocalProject project = new LocalProject(model, workspace);
if (parent != null) {
parent.modules.add(project);
}
LocalProject result = currentProjectDir == null || !currentProjectDir.equals(project.getDir()) ? null : project;
final List<String> modules = project.getRawModel().getModules();
if (!modules.isEmpty()) {
Path dirArg = result == null ? currentProjectDir : null;
for (String module : modules) {
final LocalProject loaded = load(workspace, project,
readModel(project.getDir().resolve(module).resolve(POM_XML)), dirArg);
if (loaded != null && result == null) {
result = loaded;
dirArg = null;
}
}
}
return result;
}

private static Model loadRootModel(Path pomXml) throws BootstrapMavenException {
Model model = null;
while (pomXml != null && Files.exists(pomXml)) {
model = readModel(pomXml);
final Parent parent = model.getParent();
if (parent != null
&& parent.getRelativePath() != null
&& !parent.getRelativePath().isEmpty()) {
pomXml = pomXml.getParent().resolve(parent.getRelativePath()).normalize();
if (Files.isDirectory(pomXml)) {
pomXml = pomXml.resolve(POM_XML);
}
} else {
final Path parentDir = pomXml.getParent().getParent();
pomXml = parentDir == null ? null : parentDir.resolve(POM_XML);
}
}
return model;
}

private static final Model readModel(Path pom) throws BootstrapMavenException {
try {
final Model model = ModelUtils.readModel(pom);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,10 @@ void setResolvedVersion(String resolvedVersion) {
this.resolvedVersion = resolvedVersion;
}

LocalProject getCurrentProject() {
return currentProject;
}

void setCurrentProject(LocalProject currentProject) {
this.currentProject = currentProject;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,78 @@ public static void cleanup() {
IoUtils.recursiveDelete(workDir);
}

@Test
public void loadWorkspaceFromRootDirWithParentInChildDir() throws Exception {
final URL projectUrl = Thread.currentThread().getContextClassLoader().getResource("workspace-parent-is-not-root-dir");
assertNotNull(projectUrl);
final Path projectDir = Paths.get(projectUrl.toURI());
assertTrue(Files.exists(projectDir));
final LocalProject project = LocalProject.loadWorkspace(projectDir);

assertEquals("acme", project.getArtifactId());
assertWorkspaceWithParentInChildDir(project);
}

@Test
public void loadWorkspaceFromModuleDirWithParentInChildDir() throws Exception {
final URL projectUrl = Thread.currentThread().getContextClassLoader()
.getResource("workspace-parent-is-not-root-dir/acme-application");
assertNotNull(projectUrl);
final Path projectDir = Paths.get(projectUrl.toURI());
assertTrue(Files.exists(projectDir));
final LocalProject project = LocalProject.loadWorkspace(projectDir);

assertEquals("acme-application", project.getArtifactId());
assertWorkspaceWithParentInChildDir(project);
}

private void assertWorkspaceWithParentInChildDir(final LocalProject project) {
final LocalWorkspace workspace = project.getWorkspace();
assertNotNull(workspace.getProject("org.acme", "acme"));
assertNotNull(workspace.getProject("org.acme", "acme-parent"));
assertNotNull(workspace.getProject("org.acme", "acme-dependencies"));
assertNotNull(workspace.getProject("org.acme", "acme-backend"));
assertNotNull(workspace.getProject("org.acme", "acme-backend-rest-api"));
assertNotNull(workspace.getProject("org.acme", "acme-application"));
assertEquals(6, workspace.getProjects().size());
}

@Test
public void loadWorkspaceWithAlternatePomDefaultPom() throws Exception {
final URL projectUrl = Thread.currentThread().getContextClassLoader()
.getResource("workspace-alternate-pom/root/module1");
assertNotNull(projectUrl);
final Path projectDir = Paths.get(projectUrl.toURI());
assertTrue(Files.exists(projectDir));
final LocalProject project = LocalProject.loadWorkspace(projectDir);

assertEquals("root-module1", project.getArtifactId());
final LocalWorkspace workspace = project.getWorkspace();
assertNotNull(workspace.getProject("org.acme", "root"));
assertNotNull(workspace.getProject("org.acme", "root-module1"));
assertNull(workspace.getProject("org.acme", "root-module2"));
assertNotNull(workspace.getProject("org.acme", "root-submodule"));
assertEquals(3, workspace.getProjects().size());
}

@Test
public void loadWorkspaceWithAlternatePom() throws Exception {
final URL projectUrl = Thread.currentThread().getContextClassLoader()
.getResource("workspace-alternate-pom/root/module1/pom2.xml");
assertNotNull(projectUrl);
final Path projectDir = Paths.get(projectUrl.toURI());
assertTrue(Files.exists(projectDir));
final LocalProject project = LocalProject.loadWorkspace(projectDir);

assertEquals("root-module1", project.getArtifactId());
final LocalWorkspace workspace = project.getWorkspace();
assertNotNull(workspace.getProject("org.acme", "root"));
assertNotNull(workspace.getProject("org.acme", "root-module1"));
assertNotNull(workspace.getProject("org.acme", "root-module2"));
assertNull(workspace.getProject("org.acme", "root-submodule"));
assertEquals(3, workspace.getProjects().size());
}

@Test
public void loadIndependentProjectInTheWorkspaceTree() throws Exception {
final LocalProject project = LocalProject
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<project>
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>org.acme</groupId>
<artifactId>root</artifactId>
<version>1.0</version>
<relativePath>../</relativePath>
</parent>

<artifactId>root-module1</artifactId>

<modules>
<module>submodule</module>
</modules>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<project>
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>org.acme</groupId>
<artifactId>root</artifactId>
<version>1.0</version>
<relativePath>../pom2.xml</relativePath>
</parent>

<artifactId>root-module1</artifactId>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<project>
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>org.acme</groupId>
<artifactId>root-module1</artifactId>
<version>1.0</version>
<relativePath>../</relativePath>
</parent>

<artifactId>root-submodule</artifactId>
</project>
Loading

0 comments on commit 297a82b

Please sign in to comment.