Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bootstrap workspace discovery: fix infinite loop, pom model cache #10576

Merged
merged 1 commit into from
Jul 8, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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