From 6d9fb28d0e73f0a897ec75f085152cdb198df5a1 Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Wed, 20 Sep 2023 22:58:10 +0200 Subject: [PATCH] Multithreaded project builder --- .../maven/project/DefaultProjectBuilder.java | 1272 ++++++++--------- .../model/building/DefaultModelBuilder.java | 318 +++-- 2 files changed, 804 insertions(+), 786 deletions(-) diff --git a/maven-core/src/main/java/org/apache/maven/project/DefaultProjectBuilder.java b/maven-core/src/main/java/org/apache/maven/project/DefaultProjectBuilder.java index 682d9d6609ab..151363619f95 100644 --- a/maven-core/src/main/java/org/apache/maven/project/DefaultProjectBuilder.java +++ b/maven-core/src/main/java/org/apache/maven/project/DefaultProjectBuilder.java @@ -25,8 +25,13 @@ import java.io.File; import java.io.IOException; import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ForkJoinPool; +import java.util.concurrent.ForkJoinTask; +import java.util.function.Supplier; import java.util.stream.Collectors; +import org.apache.maven.ProjectCycleException; import org.apache.maven.RepositoryUtils; import org.apache.maven.api.feature.Features; import org.apache.maven.artifact.Artifact; @@ -34,7 +39,6 @@ import org.apache.maven.artifact.InvalidRepositoryException; import org.apache.maven.artifact.repository.ArtifactRepository; import org.apache.maven.bridge.MavenRepositorySystem; -import org.apache.maven.internal.impl.DefaultSession; import org.apache.maven.model.Build; import org.apache.maven.model.Dependency; import org.apache.maven.model.DependencyManagement; @@ -45,6 +49,7 @@ import org.apache.maven.model.Profile; import org.apache.maven.model.ReportPlugin; import org.apache.maven.model.building.ArtifactModelSource; +import org.apache.maven.model.building.DefaultModelBuilder; import org.apache.maven.model.building.DefaultModelBuildingRequest; import org.apache.maven.model.building.DefaultModelProblem; import org.apache.maven.model.building.FileModelSource; @@ -63,6 +68,8 @@ import org.apache.maven.repository.internal.ArtifactDescriptorUtils; import org.apache.maven.repository.internal.ModelCacheFactory; import org.codehaus.plexus.util.Os; +import org.codehaus.plexus.util.StringUtils; +import org.codehaus.plexus.util.dag.CycleDetectedException; import org.eclipse.aether.RepositorySystem; import org.eclipse.aether.RepositorySystemSession; import org.eclipse.aether.RequestTrace; @@ -81,6 +88,8 @@ @Named @Singleton public class DefaultProjectBuilder implements ProjectBuilder { + public static final String BUILDER_PARALLELISM = "maven.projectBuilder.parallelism"; + public static final int DEFAULT_BUILDER_PARALLELISM = 4; private final Logger logger = LoggerFactory.getLogger(getClass()); private final ModelBuilder modelBuilder; @@ -122,486 +131,449 @@ public DefaultProjectBuilder( @Override public ProjectBuildingResult build(File pomFile, ProjectBuildingRequest request) throws ProjectBuildingException { - InternalConfig config = new InternalConfig(request, null, modelBuilder.newTransformerContextBuilder()); - return build(pomFile, new FileModelSource(pomFile), config); + return new BuildSession(request, false).build(pomFile, new FileModelSource(pomFile)); } @Override public ProjectBuildingResult build(ModelSource modelSource, ProjectBuildingRequest request) throws ProjectBuildingException { - return build(null, modelSource, new InternalConfig(request, null, null)); + return new BuildSession(request, false).build(null, modelSource); } - private ProjectBuildingResult build(File pomFile, ModelSource modelSource, InternalConfig config) + @Override + public ProjectBuildingResult build(Artifact artifact, ProjectBuildingRequest request) throws ProjectBuildingException { - ClassLoader oldContextClassLoader = Thread.currentThread().getContextClassLoader(); - - try { - ProjectBuildingRequest projectBuildingRequest = config.request; - - MavenProject project = projectBuildingRequest.getProject(); - - List modelProblems = null; - Throwable error = null; - - if (project == null) { - ModelBuildingRequest request = getModelBuildingRequest(config); - - project = new MavenProject(); - project.setFile(pomFile); + return build(artifact, false, request); + } - DefaultModelBuildingListener listener = - new DefaultModelBuildingListener(project, projectBuildingHelper, projectBuildingRequest); + @Override + public ProjectBuildingResult build(Artifact artifact, boolean allowStubModel, ProjectBuildingRequest request) + throws ProjectBuildingException { + return new BuildSession(request, false).build(artifact, allowStubModel); + } - request.setModelBuildingListener(listener); + @Override + public List build(List pomFiles, boolean recursive, ProjectBuildingRequest request) + throws ProjectBuildingException { + return new BuildSession(request, true).build(pomFiles, recursive); + } - request.setPomFile(pomFile); - request.setModelSource(modelSource); - request.setLocationTracking(true); + static class InterimResult { - if (pomFile != null) { - project.setRootDirectory( - rootLocator.findRoot(pomFile.getParentFile().toPath())); - } + File pomFile; - ModelBuildingResult result; - try { - result = modelBuilder.build(request); - } catch (ModelBuildingException e) { - result = e.getResult(); - if (result == null || result.getEffectiveModel() == null) { - throw new ProjectBuildingException(e.getModelId(), e.getMessage(), pomFile, e); - } - // validation error, continue project building and delay failing to help IDEs - error = e; - } + ModelBuildingRequest request; - modelProblems = result.getProblems(); + ModelBuildingResult result; - initProject(project, Collections.emptyMap(), true, result, new HashMap<>(), projectBuildingRequest); - } else if (projectBuildingRequest.isResolveDependencies()) { - projectBuildingHelper.selectProjectRealm(project); - } + MavenProject project; - DependencyResolutionResult resolutionResult = null; + boolean root; - if (projectBuildingRequest.isResolveDependencies()) { - resolutionResult = resolveDependencies(project, config.session); - } + List modules = Collections.emptyList(); - ProjectBuildingResult result = new DefaultProjectBuildingResult(project, modelProblems, resolutionResult); + ProjectBuildingResult projectBuildingResult; - if (error != null) { - ProjectBuildingException e = new ProjectBuildingException(Arrays.asList(result)); - e.initCause(error); - throw e; - } + InterimResult( + File pomFile, + ModelBuildingRequest request, + ModelBuildingResult result, + MavenProject project, + boolean root) { + this.pomFile = pomFile; + this.request = request; + this.result = result; + this.project = project; + this.root = root; + } - return result; - } finally { - Thread.currentThread().setContextClassLoader(oldContextClassLoader); + InterimResult(ModelBuildingRequest request, ProjectBuildingResult projectBuildingResult) { + this.request = request; + this.projectBuildingResult = projectBuildingResult; + this.pomFile = projectBuildingResult.getPomFile(); + this.project = projectBuildingResult.getProject(); } } - private DependencyResolutionResult resolveDependencies(MavenProject project, RepositorySystemSession session) { - DependencyResolutionResult resolutionResult; - - try { - DefaultDependencyResolutionRequest resolution = new DefaultDependencyResolutionRequest(project, session); - resolutionResult = dependencyResolver.resolve(resolution); - } catch (DependencyResolutionException e) { - resolutionResult = e.getResult(); - } + class BuildSession { + private final ProjectBuildingRequest request; + private final RepositorySystemSession session; + private final List repositories; + private final ReactorModelPool modelPool; + private final TransformerContextBuilder transformerContextBuilder; + private final ForkJoinPool forkJoinPool; - Set artifacts = new LinkedHashSet<>(); - if (resolutionResult.getDependencyGraph() != null) { - RepositoryUtils.toArtifacts( - artifacts, - resolutionResult.getDependencyGraph().getChildren(), - Collections.singletonList(project.getArtifact().getId()), - null); - - // Maven 2.x quirk: an artifact always points at the local repo, regardless whether resolved or not - LocalRepositoryManager lrm = session.getLocalRepositoryManager(); - for (Artifact artifact : artifacts) { - if (!artifact.isResolved()) { - String path = lrm.getPathForLocalArtifact(RepositoryUtils.toArtifact(artifact)); - artifact.setFile(new File(lrm.getRepository().getBasedir(), path)); - } + BuildSession(ProjectBuildingRequest request, boolean localProjects) { + this.request = request; + this.session = + RepositoryUtils.overlay(request.getLocalRepository(), request.getRepositorySession(), repoSystem); + this.repositories = RepositoryUtils.toRepos(request.getRemoteRepositories()); + if (localProjects) { + this.modelPool = new ReactorModelPool(); + this.transformerContextBuilder = modelBuilder.newTransformerContextBuilder(); + this.forkJoinPool = new ForkJoinPool(getParallelism(request)); + } else { + this.modelPool = null; + this.transformerContextBuilder = null; + this.forkJoinPool = null; } } - project.setResolvedArtifacts(artifacts); - project.setArtifacts(artifacts); - - return resolutionResult; - } - - private List getProfileIds(List profiles) { - return profiles.stream().map(org.apache.maven.model.Profile::getId).collect(Collectors.toList()); - } - private ModelBuildingRequest getModelBuildingRequest(InternalConfig config) { - ProjectBuildingRequest configuration = config.request; - - ModelBuildingRequest request = new DefaultModelBuildingRequest(); - - RequestTrace trace = RequestTrace.newChild(null, configuration).newChild(request); - - ModelResolver resolver = new ProjectModelResolver( - config.session, - trace, - repoSystem, - repositoryManager, - config.repositories, - configuration.getRepositoryMerging(), - config.modelPool); - - request.setValidationLevel(configuration.getValidationLevel()); - request.setProcessPlugins(configuration.isProcessPlugins()); - request.setProfiles(configuration.getProfiles()); - request.setActiveProfileIds(configuration.getActiveProfileIds()); - request.setInactiveProfileIds(configuration.getInactiveProfileIds()); - request.setSystemProperties(configuration.getSystemProperties()); - request.setUserProperties(configuration.getUserProperties()); - request.setBuildStartTime(configuration.getBuildStartTime()); - request.setModelResolver(resolver); - // this is a hint that we want to build 1 file, so don't cache. See MNG-7063 - if (config.modelPool != null) { - request.setModelCache(modelCacheFactory.createCache(config.session)); - } - request.setTransformerContextBuilder(config.transformerContextBuilder); - DefaultSession session = (DefaultSession) config.session.getData().get(DefaultSession.class); - if (session != null) { + private int getParallelism(ProjectBuildingRequest request) { + int parallelism = DEFAULT_BUILDER_PARALLELISM; try { - request.setRootDirectory(session.getRootDirectory()); - } catch (IllegalStateException e) { - // can happen if root directory cannot be found, just ignore + String str = request.getUserProperties().getProperty(BUILDER_PARALLELISM); + if (str == null) { + str = request.getSystemProperties().getProperty(BUILDER_PARALLELISM); + } + if (str != null) { + parallelism = Integer.parseInt(str); + } + } catch (Exception e) { + // ignore } + return Math.max(1, Math.min(parallelism, Runtime.getRuntime().availableProcessors())); } - return request; - } + ProjectBuildingResult build(File pomFile, ModelSource modelSource) throws ProjectBuildingException { + ClassLoader oldContextClassLoader = Thread.currentThread().getContextClassLoader(); - @Override - public ProjectBuildingResult build(Artifact artifact, ProjectBuildingRequest request) - throws ProjectBuildingException { - return build(artifact, false, request); - } + try { + MavenProject project = request.getProject(); - @Override - public ProjectBuildingResult build(Artifact artifact, boolean allowStubModel, ProjectBuildingRequest request) - throws ProjectBuildingException { - org.eclipse.aether.artifact.Artifact pomArtifact = RepositoryUtils.toArtifact(artifact); - pomArtifact = ArtifactDescriptorUtils.toPomArtifact(pomArtifact); + List modelProblems = null; + Throwable error = null; - InternalConfig config = new InternalConfig(request, null, null); + if (project == null) { + ModelBuildingRequest request = getModelBuildingRequest(); - boolean localProject; + project = new MavenProject(); + project.setFile(pomFile); - try { - ArtifactRequest pomRequest = new ArtifactRequest(); - pomRequest.setArtifact(pomArtifact); - pomRequest.setRepositories(config.repositories); - ArtifactResult pomResult = repoSystem.resolveArtifact(config.session, pomRequest); + DefaultModelBuildingListener listener = + new DefaultModelBuildingListener(project, projectBuildingHelper, this.request); + request.setModelBuildingListener(listener); - pomArtifact = pomResult.getArtifact(); - localProject = pomResult.getRepository() instanceof WorkspaceRepository; - } catch (org.eclipse.aether.resolution.ArtifactResolutionException e) { - if (e.getResults().get(0).isMissing() && allowStubModel) { - return build(null, createStubModelSource(artifact), config); - } - throw new ProjectBuildingException( - artifact.getId(), "Error resolving project artifact: " + e.getMessage(), e); - } + request.setPomFile(pomFile); + request.setModelSource(modelSource); + request.setLocationTracking(true); - File pomFile = pomArtifact.getFile(); + if (pomFile != null) { + project.setRootDirectory( + rootLocator.findRoot(pomFile.getParentFile().toPath())); + } - if ("pom".equals(artifact.getType())) { - artifact.selectVersion(pomArtifact.getVersion()); - artifact.setFile(pomFile); - artifact.setResolved(true); - } + ModelBuildingResult result; + try { + result = modelBuilder.build(request); + } catch (ModelBuildingException e) { + result = e.getResult(); + if (result == null || result.getEffectiveModel() == null) { + throw new ProjectBuildingException(e.getModelId(), e.getMessage(), pomFile, e); + } + // validation error, continue project building and delay failing to help IDEs + error = e; + } - if (localProject) { - return build(pomFile, new FileModelSource(pomFile), config); - } else { - return build( - null, - new ArtifactModelSource( - pomFile, artifact.getGroupId(), artifact.getArtifactId(), artifact.getVersion()), - config); - } - } + modelProblems = result.getProblems(); - private ModelSource createStubModelSource(Artifact artifact) { - StringBuilder buffer = new StringBuilder(1024); + initProject(project, Collections.emptyMap(), result); + } else if (request.isResolveDependencies()) { + projectBuildingHelper.selectProjectRealm(project); + } - buffer.append(""); - buffer.append(""); - buffer.append("4.0.0"); - buffer.append("").append(artifact.getGroupId()).append(""); - buffer.append("").append(artifact.getArtifactId()).append(""); - buffer.append("").append(artifact.getBaseVersion()).append(""); - buffer.append("").append(artifact.getType()).append(""); - buffer.append(""); + DependencyResolutionResult resolutionResult = null; - return new StringModelSource(buffer.toString(), artifact.getId()); - } + if (request.isResolveDependencies()) { + resolutionResult = resolveDependencies(project); + } - @Override - public List build(List pomFiles, boolean recursive, ProjectBuildingRequest request) - throws ProjectBuildingException { - List results = new ArrayList<>(); + ProjectBuildingResult result = + new DefaultProjectBuildingResult(project, modelProblems, resolutionResult); - List interimResults = new ArrayList<>(); + if (error != null) { + ProjectBuildingException e = new ProjectBuildingException(Arrays.asList(result)); + e.initCause(error); + throw e; + } - ReactorModelPool pool = new ReactorModelPool(); + return result; + } finally { + Thread.currentThread().setContextClassLoader(oldContextClassLoader); + } + } - InternalConfig config = new InternalConfig(request, pool, modelBuilder.newTransformerContextBuilder()); + ProjectBuildingResult build(Artifact artifact, boolean allowStubModel) throws ProjectBuildingException { + org.eclipse.aether.artifact.Artifact pomArtifact = RepositoryUtils.toArtifact(artifact); + pomArtifact = ArtifactDescriptorUtils.toPomArtifact(pomArtifact); - Map projectIndex = new HashMap<>(256); + boolean localProject; - // phase 1: get file Models from the reactor. - boolean noErrors = build( - results, interimResults, projectIndex, pomFiles, new LinkedHashSet<>(), true, recursive, config, pool); + try { + ArtifactRequest pomRequest = new ArtifactRequest(); + pomRequest.setArtifact(pomArtifact); + pomRequest.setRepositories(repositories); + ArtifactResult pomResult = repoSystem.resolveArtifact(session, pomRequest); + + pomArtifact = pomResult.getArtifact(); + localProject = pomResult.getRepository() instanceof WorkspaceRepository; + } catch (org.eclipse.aether.resolution.ArtifactResolutionException e) { + if (e.getResults().get(0).isMissing() && allowStubModel) { + return build(null, createStubModelSource(artifact)); + } + throw new ProjectBuildingException( + artifact.getId(), "Error resolving project artifact: " + e.getMessage(), e); + } - ClassLoader oldContextClassLoader = Thread.currentThread().getContextClassLoader(); + File pomFile = pomArtifact.getFile(); - try { - // Phase 2: get effective models from the reactor - noErrors = build( - results, - new ArrayList<>(), - projectIndex, - interimResults, - request, - new HashMap<>(), - config.session) - && noErrors; - } finally { - Thread.currentThread().setContextClassLoader(oldContextClassLoader); - } + if ("pom".equals(artifact.getType())) { + artifact.selectVersion(pomArtifact.getVersion()); + artifact.setFile(pomFile); + artifact.setResolved(true); + } - if (Features.buildConsumer(request.getUserProperties())) { - request.getRepositorySession() - .getData() - .set(TransformerContext.KEY, config.transformerContextBuilder.build()); + if (localProject) { + return build(pomFile, new FileModelSource(pomFile)); + } else { + return build( + null, + new ArtifactModelSource( + pomFile, artifact.getGroupId(), artifact.getArtifactId(), artifact.getVersion())); + } } - if (!noErrors) { - throw new ProjectBuildingException(results); - } + List build(List pomFiles, boolean recursive) throws ProjectBuildingException { + ForkJoinTask> task = forkJoinPool.submit(() -> doBuild(pomFiles, recursive)); - return results; - } + // ForkJoinTask.getException rewraps the exception in a weird way + // which cause an additional layer of exception, so try to unwrap it + task.quietlyJoin(); + if (task.isCompletedAbnormally()) { + Throwable e = task.getException(); + Throwable c = e.getCause(); + uncheckedThrow(c != null && c.getClass() == e.getClass() ? c : e); + } - @SuppressWarnings("checkstyle:parameternumber") - private boolean build( - List results, - List interimResults, - Map projectIndex, - List pomFiles, - Set aggregatorFiles, - boolean root, - boolean recursive, - InternalConfig config, - ReactorModelPool pool) { - boolean noErrors = true; - - for (File pomFile : pomFiles) { - aggregatorFiles.add(pomFile); - - if (!build( - results, interimResults, projectIndex, pomFile, aggregatorFiles, root, recursive, config, pool)) { - noErrors = false; + List results = task.getRawResult(); + if (results.stream() + .flatMap(r -> r.getProblems().stream()) + .anyMatch(p -> p.getSeverity() != ModelProblem.Severity.WARNING)) { + ModelProblem cycle = results.stream() + .flatMap(r -> r.getProblems().stream()) + .filter(p -> p.getException() instanceof CycleDetectedException) + .findAny() + .orElse(null); + if (cycle != null) { + throw new RuntimeException(new ProjectCycleException( + "The projects in the reactor contain a cyclic reference: " + cycle.getMessage(), + (CycleDetectedException) cycle.getException())); + } + throw new ProjectBuildingException(results); } - aggregatorFiles.remove(pomFile); + return results; } - return noErrors; - } - - @SuppressWarnings("checkstyle:parameternumber") - private boolean build( - List results, - List interimResults, - Map projectIndex, - File pomFile, - Set aggregatorFiles, - boolean isRoot, - boolean recursive, - InternalConfig config, - ReactorModelPool pool) { - boolean noErrors = true; - - MavenProject project = new MavenProject(); - project.setFile(pomFile); - - project.setRootDirectory(rootLocator.findRoot(pomFile.getParentFile().toPath())); - - ModelBuildingRequest request = getModelBuildingRequest(config) - .setPomFile(pomFile) - .setTwoPhaseBuilding(true) - .setLocationTracking(true); - - DefaultModelBuildingListener listener = - new DefaultModelBuildingListener(project, projectBuildingHelper, config.request); - request.setModelBuildingListener(listener); - - ModelBuildingResult result; - try { - result = modelBuilder.build(request); - } catch (ModelBuildingException e) { - result = e.getResult(); - if (result == null || result.getFileModel() == null) { - results.add(new DefaultProjectBuildingResult(e.getModelId(), pomFile, e.getProblems())); - - return false; - } - // validation error, continue project building and delay failing to help IDEs - // result.getProblems().addAll(e.getProblems()) ? - noErrors = false; - } + List doBuild(List pomFiles, boolean recursive) { + Map projectIndex = new ConcurrentHashMap<>(256); - Model model = request.getFileModel(); + // phase 1: get file Models from the reactor. + List interimResults = build(projectIndex, pomFiles, new LinkedHashSet<>(), true, recursive); - pool.put(model.getPomFile().toPath(), model); + ClassLoader oldContextClassLoader = Thread.currentThread().getContextClassLoader(); - InterimResult interimResult = new InterimResult(pomFile, request, result, listener, isRoot); - interimResults.add(interimResult); + try { + // Phase 2: get effective models from the reactor + List results = build(projectIndex, interimResults); - if (recursive) { - File basedir = pomFile.getParentFile(); - List moduleFiles = new ArrayList<>(); - for (String module : model.getModules()) { - if (module == null || module.isEmpty()) { - continue; + if (Features.buildConsumer(request.getUserProperties())) { + request.getRepositorySession() + .getData() + .set(TransformerContext.KEY, transformerContextBuilder.build()); } - module = module.replace('\\', File.separatorChar).replace('/', File.separatorChar); - - File moduleFile = modelProcessor.locateExistingPom(new File(basedir, module)); - - if (moduleFile == null) { - ModelProblem problem = new DefaultModelProblem( - "Child module " + module + " of " + pomFile + " does not exist", - ModelProblem.Severity.ERROR, - ModelProblem.Version.BASE, - model, - -1, - -1, - null); - result.getProblems().add(problem); + return results; + } finally { + Thread.currentThread().setContextClassLoader(oldContextClassLoader); + } + } - noErrors = false; + @SuppressWarnings("checkstyle:parameternumber") + private List build( + Map projectIndex, + List pomFiles, + Set aggregatorFiles, + boolean root, + boolean recursive) { + List> tasks = pomFiles.stream() + .map(pomFile -> ForkJoinTask.adapt( + () -> build(projectIndex, pomFile, concat(aggregatorFiles, pomFile), root, recursive))) + .collect(Collectors.toList()); + + return ForkJoinTask.invokeAll(tasks).stream() + .map(ForkJoinTask::getRawResult) + .collect(Collectors.toList()); + } - continue; - } + private Set concat(Set set, T elem) { + Set newSet = new HashSet<>(set); + newSet.add(elem); + return newSet; + } - if (Os.isFamily(Os.FAMILY_WINDOWS)) { - // we don't canonicalize on unix to avoid interfering with symlinks - try { - moduleFile = moduleFile.getCanonicalFile(); - } catch (IOException e) { - moduleFile = moduleFile.getAbsoluteFile(); - } - } else { - moduleFile = new File(moduleFile.toURI().normalize()); + @SuppressWarnings("checkstyle:parameternumber") + private InterimResult build( + Map projectIndex, + File pomFile, + Set aggregatorFiles, + boolean isRoot, + boolean recursive) { + MavenProject project = new MavenProject(); + project.setFile(pomFile); + + ModelBuildingRequest modelBuildingRequest = getModelBuildingRequest() + .setPomFile(pomFile) + .setTwoPhaseBuilding(true) + .setLocationTracking(true); + + DefaultModelBuildingListener listener = + new DefaultModelBuildingListener(project, projectBuildingHelper, request); + modelBuildingRequest.setModelBuildingListener(listener); + + ModelBuildingResult result; + try { + result = modelBuilder.build(modelBuildingRequest); + } catch (ModelBuildingException e) { + result = e.getResult(); + if (result == null || result.getFileModel() == null) { + return new InterimResult( + modelBuildingRequest, + new DefaultProjectBuildingResult(e.getModelId(), pomFile, e.getProblems())); } + // validation error, continue project building and delay failing to help IDEs + // result.getProblems().addAll(e.getProblems()) ? + } - if (aggregatorFiles.contains(moduleFile)) { - StringBuilder buffer = new StringBuilder(256); - for (File aggregatorFile : aggregatorFiles) { - buffer.append(aggregatorFile).append(" -> "); - } - buffer.append(moduleFile); + Model model = modelBuildingRequest.getFileModel(); - ModelProblem problem = new DefaultModelProblem( - "Child module " + moduleFile + " of " + pomFile + " forms aggregation cycle " + buffer, - ModelProblem.Severity.ERROR, - ModelProblem.Version.BASE, - model, - -1, - -1, - null); - result.getProblems().add(problem); + modelPool.put(model.getPomFile().toPath(), model); - noErrors = false; + InterimResult interimResult = new InterimResult(pomFile, modelBuildingRequest, result, project, isRoot); - continue; - } + if (recursive) { + File basedir = pomFile.getParentFile(); + List moduleFiles = new ArrayList<>(); + for (String module : model.getModules()) { + if (StringUtils.isEmpty(module)) { + continue; + } - moduleFiles.add(moduleFile); - } + module = module.replace('\\', File.separatorChar).replace('/', File.separatorChar); - interimResult.modules = new ArrayList<>(); - - if (!build( - results, - interimResult.modules, - projectIndex, - moduleFiles, - aggregatorFiles, - false, - recursive, - config, - pool)) { - noErrors = false; - } - } + File moduleFile = modelProcessor.locateExistingPom(new File(basedir, module)); - projectIndex.put(pomFile, project); + if (moduleFile == null) { + ModelProblem problem = new DefaultModelProblem( + "Child module " + moduleFile + " of " + pomFile + " does not exist", + ModelProblem.Severity.ERROR, + ModelProblem.Version.BASE, + model, + -1, + -1, + null); + result.getProblems().add(problem); - return noErrors; - } + continue; + } - static class InterimResult { + if (Os.isFamily(Os.FAMILY_WINDOWS)) { + // we don't canonicalize on unix to avoid interfering with symlinks + try { + moduleFile = moduleFile.getCanonicalFile(); + } catch (IOException e) { + moduleFile = moduleFile.getAbsoluteFile(); + } + } else { + moduleFile = new File(moduleFile.toURI().normalize()); + } - File pomFile; + if (aggregatorFiles.contains(moduleFile)) { + StringBuilder buffer = new StringBuilder(256); + for (File aggregatorFile : aggregatorFiles) { + buffer.append(aggregatorFile).append(" -> "); + } + buffer.append(moduleFile); + + ModelProblem problem = new DefaultModelProblem( + "Child module " + moduleFile + " of " + pomFile + " forms aggregation cycle " + buffer, + ModelProblem.Severity.ERROR, + ModelProblem.Version.BASE, + model, + -1, + -1, + null); + result.getProblems().add(problem); + + continue; + } - ModelBuildingRequest request; + moduleFiles.add(moduleFile); + } - ModelBuildingResult result; + if (!moduleFiles.isEmpty()) { + interimResult.modules = build(projectIndex, moduleFiles, aggregatorFiles, false, recursive); + } + } - DefaultModelBuildingListener listener; + projectIndex.put(pomFile, project); - boolean root; + return interimResult; + } - List modules = Collections.emptyList(); + private List build( + Map projectIndex, List interimResults) { + // The transformation may need to access dependencies raw models, + // which may cause some re-entrance in the build() method and can + // actually cause deadlocks. In order to workaround the problem, + // we do a first pass by reading all rawModels in order. + if (modelBuilder instanceof DefaultModelBuilder) { + List results = new ArrayList<>(); + DefaultModelBuilder dmb = (DefaultModelBuilder) modelBuilder; + boolean failure = false; + for (InterimResult r : interimResults) { + DefaultProjectBuildingResult res; + try { + Model model = dmb.buildRawModel(r.request); + res = new DefaultProjectBuildingResult(model.getId(), model.getPomFile(), null); + } catch (ModelBuildingException e) { + failure = true; + res = new DefaultProjectBuildingResult(e.getModelId(), r.request.getPomFile(), e.getProblems()); + } + results.add(res); + } + if (failure) { + return results; + } + } - InterimResult( - File pomFile, - ModelBuildingRequest request, - ModelBuildingResult result, - DefaultModelBuildingListener listener, - boolean root) { - this.pomFile = pomFile; - this.request = request; - this.result = result; - this.listener = listener; - this.root = root; + return interimResults.parallelStream() + .map(interimResult -> doBuild(projectIndex, interimResult)) + .flatMap(List::stream) + .collect(Collectors.toList()); } - } - private boolean build( - List results, - List projects, - Map projectIndex, - List interimResults, - ProjectBuildingRequest request, - Map profilesXmls, - RepositorySystemSession session) { - boolean noErrors = true; - - for (InterimResult interimResult : interimResults) { - MavenProject project = interimResult.listener.getProject(); + private List doBuild(Map projectIndex, InterimResult interimResult) { + if (interimResult.projectBuildingResult != null) { + return Collections.singletonList(interimResult.projectBuildingResult); + } + MavenProject project = interimResult.project; try { ModelBuildingResult result = modelBuilder.build(interimResult.request, interimResult.result); // 2nd pass of initialization: resolve and build parent if necessary try { - initProject(project, projectIndex, true, result, profilesXmls, request); + initProject(project, projectIndex, result); } catch (InvalidArtifactRTException iarte) { result.getProblems() .add(new DefaultModelProblem( @@ -614,299 +586,318 @@ private boolean build( iarte)); } - List modules = new ArrayList<>(); - noErrors = build(results, modules, projectIndex, interimResult.modules, request, profilesXmls, session) - && noErrors; - - projects.addAll(modules); - projects.add(project); + List results = build(projectIndex, interimResult.modules); project.setExecutionRoot(interimResult.root); - project.setCollectedProjects(modules); + project.setCollectedProjects( + results.stream().map(ProjectBuildingResult::getProject).collect(Collectors.toList())); DependencyResolutionResult resolutionResult = null; if (request.isResolveDependencies()) { - resolutionResult = resolveDependencies(project, session); + resolutionResult = resolveDependencies(project); } results.add(new DefaultProjectBuildingResult(project, result.getProblems(), resolutionResult)); + + return results; } catch (ModelBuildingException e) { - DefaultProjectBuildingResult result = null; + DefaultProjectBuildingResult result; if (project == null || interimResult.result.getEffectiveModel() == null) { result = new DefaultProjectBuildingResult(e.getModelId(), interimResult.pomFile, e.getProblems()); } else { project.setModel(interimResult.result.getEffectiveModel()); - result = new DefaultProjectBuildingResult(project, e.getProblems(), null); } - results.add(result); - - noErrors = false; + return Collections.singletonList(result); } } - return noErrors; - } - - @SuppressWarnings("checkstyle:methodlength") - private void initProject( - MavenProject project, - Map projects, - boolean buildParentIfNotExisting, - ModelBuildingResult result, - Map profilesXmls, - ProjectBuildingRequest projectBuildingRequest) { - project.setModel(result.getEffectiveModel()); - project.setOriginalModel(result.getFileModel()); - - initParent(project, projects, buildParentIfNotExisting, result, projectBuildingRequest); - - Artifact projectArtifact = repositorySystem.createArtifact( - project.getGroupId(), project.getArtifactId(), project.getVersion(), null, project.getPackaging()); - project.setArtifact(projectArtifact); - - if (project.getFile() != null && buildParentIfNotExisting) // only set those on 2nd phase, ignore on 1st pass - { - Build build = project.getBuild(); - project.addScriptSourceRoot(build.getScriptSourceDirectory()); - project.addCompileSourceRoot(build.getSourceDirectory()); - project.addTestCompileSourceRoot(build.getTestSourceDirectory()); - } - - List activeProfiles = new ArrayList<>(); - activeProfiles.addAll(result.getActivePomProfiles(result.getModelIds().get(0))); - activeProfiles.addAll(result.getActiveExternalProfiles()); - project.setActiveProfiles(activeProfiles); + @SuppressWarnings("checkstyle:methodlength") + private void initProject(MavenProject project, Map projects, ModelBuildingResult result) { + project.setModel(result.getEffectiveModel()); + project.setOriginalModel(result.getFileModel()); - project.setInjectedProfileIds("external", getProfileIds(result.getActiveExternalProfiles())); - for (String modelId : result.getModelIds()) { - project.setInjectedProfileIds(modelId, getProfileIds(result.getActivePomProfiles(modelId))); - } + initParent(project, projects, result); - // - // All the parts that were taken out of MavenProject for Maven 4.0.0 - // + Artifact projectArtifact = repositorySystem.createArtifact( + project.getGroupId(), project.getArtifactId(), project.getVersion(), null, project.getPackaging()); + project.setArtifact(projectArtifact); - project.setProjectBuildingRequest(projectBuildingRequest); + // only set those on 2nd phase, ignore on 1st pass + if (project.getFile() != null) { + Build build = project.getBuild(); + project.addScriptSourceRoot(build.getScriptSourceDirectory()); + project.addCompileSourceRoot(build.getSourceDirectory()); + project.addTestCompileSourceRoot(build.getTestSourceDirectory()); + } - // pluginArtifacts - Set pluginArtifacts = new HashSet<>(); - for (Plugin plugin : project.getBuildPlugins()) { - Artifact artifact = repositorySystem.createPluginArtifact(plugin); + List activeProfiles = new ArrayList<>(); + activeProfiles.addAll( + result.getActivePomProfiles(result.getModelIds().get(0))); + activeProfiles.addAll(result.getActiveExternalProfiles()); + project.setActiveProfiles(activeProfiles); - if (artifact != null) { - pluginArtifacts.add(artifact); + project.setInjectedProfileIds("external", getProfileIds(result.getActiveExternalProfiles())); + for (String modelId : result.getModelIds()) { + project.setInjectedProfileIds(modelId, getProfileIds(result.getActivePomProfiles(modelId))); } - } - project.setPluginArtifacts(pluginArtifacts); - // reportArtifacts - Set reportArtifacts = new HashSet<>(); - for (ReportPlugin report : project.getReportPlugins()) { - Plugin pp = new Plugin(); - pp.setGroupId(report.getGroupId()); - pp.setArtifactId(report.getArtifactId()); - pp.setVersion(report.getVersion()); + // + // All the parts that were taken out of MavenProject for Maven 4.0.0 + // - Artifact artifact = repositorySystem.createPluginArtifact(pp); + project.setProjectBuildingRequest(request); - if (artifact != null) { - reportArtifacts.add(artifact); - } - } - project.setReportArtifacts(reportArtifacts); - - // extensionArtifacts - Set extensionArtifacts = new HashSet<>(); - List extensions = project.getBuildExtensions(); - if (extensions != null) { - for (Extension ext : extensions) { - String version; - if (ext.getVersion() == null || ext.getVersion().isEmpty()) { - version = "RELEASE"; - } else { - version = ext.getVersion(); + // pluginArtifacts + Set pluginArtifacts = new HashSet<>(); + for (Plugin plugin : project.getBuildPlugins()) { + Artifact artifact = repositorySystem.createPluginArtifact(plugin); + + if (artifact != null) { + pluginArtifacts.add(artifact); } + } + project.setPluginArtifacts(pluginArtifacts); + + // reportArtifacts + Set reportArtifacts = new HashSet<>(); + for (ReportPlugin report : project.getReportPlugins()) { + Plugin pp = new Plugin(); + pp.setGroupId(report.getGroupId()); + pp.setArtifactId(report.getArtifactId()); + pp.setVersion(report.getVersion()); - Artifact artifact = - repositorySystem.createArtifact(ext.getGroupId(), ext.getArtifactId(), version, null, "jar"); + Artifact artifact = repositorySystem.createPluginArtifact(pp); if (artifact != null) { - extensionArtifacts.add(artifact); + reportArtifacts.add(artifact); } } - } - project.setExtensionArtifacts(extensionArtifacts); - - // managedVersionMap - Map map = null; - if (repositorySystem != null) { - final DependencyManagement dependencyManagement = project.getDependencyManagement(); - if ((dependencyManagement != null) - && ((dependencyManagement.getDependencies()) != null) - && (dependencyManagement.getDependencies().size() > 0)) { - map = new AbstractMap() { - HashMap delegate; - - @Override - public Set> entrySet() { - return Collections.unmodifiableSet(compute().entrySet()); + project.setReportArtifacts(reportArtifacts); + + // extensionArtifacts + Set extensionArtifacts = new HashSet<>(); + List extensions = project.getBuildExtensions(); + if (extensions != null) { + for (Extension ext : extensions) { + String version; + if (StringUtils.isEmpty(ext.getVersion())) { + version = "RELEASE"; + } else { + version = ext.getVersion(); } - @Override - public Set keySet() { - return Collections.unmodifiableSet(compute().keySet()); - } + Artifact artifact = repositorySystem.createArtifact( + ext.getGroupId(), ext.getArtifactId(), version, null, "jar"); - @Override - public Collection values() { - return Collections.unmodifiableCollection(compute().values()); + if (artifact != null) { + extensionArtifacts.add(artifact); } + } + } + project.setExtensionArtifacts(extensionArtifacts); - @Override - public boolean containsValue(Object value) { - return compute().containsValue(value); + // managedVersionMap + Map map = Collections.emptyMap(); + final DependencyManagement dependencyManagement = project.getDependencyManagement(); + if (dependencyManagement != null + && dependencyManagement.getDependencies() != null + && !dependencyManagement.getDependencies().isEmpty()) { + map = new LazyMap<>(() -> { + Map tmp = new HashMap<>(); + for (Dependency d : dependencyManagement.getDependencies()) { + Artifact artifact = repositorySystem.createDependencyArtifact(d); + if (artifact != null) { + tmp.put(d.getManagementKey(), artifact); + } } + return Collections.unmodifiableMap(tmp); + }); + } + project.setManagedVersionMap(map); - @Override - public boolean containsKey(Object key) { - return compute().containsKey(key); + // release artifact repository + if (project.getDistributionManagement() != null + && project.getDistributionManagement().getRepository() != null) { + try { + DeploymentRepository r = project.getDistributionManagement().getRepository(); + if (!StringUtils.isEmpty(r.getId()) && !StringUtils.isEmpty(r.getUrl())) { + ArtifactRepository repo = MavenRepositorySystem.buildArtifactRepository(r); + repositorySystem.injectProxy(request.getRepositorySession(), Arrays.asList(repo)); + repositorySystem.injectAuthentication(request.getRepositorySession(), Arrays.asList(repo)); + project.setReleaseArtifactRepository(repo); } + } catch (InvalidRepositoryException e) { + throw new IllegalStateException( + "Failed to create release distribution repository for " + project.getId(), e); + } + } - @Override - public Artifact get(Object key) { - return compute().get(key); + // snapshot artifact repository + if (project.getDistributionManagement() != null + && project.getDistributionManagement().getSnapshotRepository() != null) { + try { + DeploymentRepository r = project.getDistributionManagement().getSnapshotRepository(); + if (!StringUtils.isEmpty(r.getId()) && !StringUtils.isEmpty(r.getUrl())) { + ArtifactRepository repo = MavenRepositorySystem.buildArtifactRepository(r); + repositorySystem.injectProxy(request.getRepositorySession(), Arrays.asList(repo)); + repositorySystem.injectAuthentication(request.getRepositorySession(), Arrays.asList(repo)); + project.setSnapshotArtifactRepository(repo); } + } catch (InvalidRepositoryException e) { + throw new IllegalStateException( + "Failed to create snapshot distribution repository for " + project.getId(), e); + } + } + } - HashMap compute() { - if (delegate == null) { - delegate = new HashMap<>(); - for (Dependency d : dependencyManagement.getDependencies()) { - Artifact artifact = repositorySystem.createDependencyArtifact(d); - - if (artifact != null) { - delegate.put(d.getManagementKey(), artifact); - } + private void initParent(MavenProject project, Map projects, ModelBuildingResult result) { + Model parentModel = result.getModelIds().size() > 1 + && !result.getModelIds().get(1).isEmpty() + ? result.getRawModel(result.getModelIds().get(1)) + : null; + + if (parentModel != null) { + final String parentGroupId = inheritedGroupId(result, 1); + final String parentVersion = inheritedVersion(result, 1); + + project.setParentArtifact(repositorySystem.createProjectArtifact( + parentGroupId, parentModel.getArtifactId(), parentVersion)); + + // org.apache.maven.its.mng4834:parent:0.1 + String parentModelId = result.getModelIds().get(1); + File parentPomFile = result.getRawModel(parentModelId).getPomFile(); + MavenProject parent = parentPomFile != null ? projects.get(parentPomFile) : null; + if (parent == null) { + // + // At this point the DefaultModelBuildingListener has fired and it populates the + // remote repositories with those found in the pom.xml, along with the existing externally + // defined repositories. + // + request.setRemoteRepositories(project.getRemoteArtifactRepositories()); + if (parentPomFile != null) { + project.setParentFile(parentPomFile); + try { + parent = build(parentPomFile, new FileModelSource(parentPomFile)) + .getProject(); + } catch (ProjectBuildingException e) { + // MNG-4488 where let invalid parents slide on by + if (logger.isDebugEnabled()) { + // Message below is checked for in the MNG-2199 core IT. + logger.warn("Failed to build parent project for " + project.getId(), e); + } else { + // Message below is checked for in the MNG-2199 core IT. + logger.warn("Failed to build parent project for " + project.getId()); + } + } + } else { + Artifact parentArtifact = project.getParentArtifact(); + try { + parent = build(parentArtifact, false).getProject(); + } catch (ProjectBuildingException e) { + // MNG-4488 where let invalid parents slide on by + if (logger.isDebugEnabled()) { + // Message below is checked for in the MNG-2199 core IT. + logger.warn("Failed to build parent project for " + project.getId(), e); + } else { + // Message below is checked for in the MNG-2199 core IT. + logger.warn("Failed to build parent project for " + project.getId()); } } - - return delegate; } - }; - } else { - map = Collections.emptyMap(); + } + project.setParent(parent); + if (project.getParentFile() == null && parent != null) { + project.setParentFile(parent.getFile()); + } } } - project.setManagedVersionMap(map); - // release artifact repository - if (project.getDistributionManagement() != null - && project.getDistributionManagement().getRepository() != null) { - try { - DeploymentRepository r = project.getDistributionManagement().getRepository(); - if (r.getId() != null - && !r.getId().isEmpty() - && r.getUrl() != null - && !r.getUrl().isEmpty()) { - ArtifactRepository repo = MavenRepositorySystem.buildArtifactRepository(r); - repositorySystem.injectProxy(projectBuildingRequest.getRepositorySession(), Arrays.asList(repo)); - repositorySystem.injectAuthentication( - projectBuildingRequest.getRepositorySession(), Arrays.asList(repo)); - project.setReleaseArtifactRepository(repo); - } - } catch (InvalidRepositoryException e) { - throw new IllegalStateException( - "Failed to create release distribution repository for " + project.getId(), e); + private ModelBuildingRequest getModelBuildingRequest() { + ModelBuildingRequest modelBuildingRequest = new DefaultModelBuildingRequest(); + + RequestTrace trace = RequestTrace.newChild(null, request).newChild(modelBuildingRequest); + + ModelResolver resolver = new ProjectModelResolver( + session, + trace, + repoSystem, + repositoryManager, + repositories, + request.getRepositoryMerging(), + modelPool); + + modelBuildingRequest.setValidationLevel(request.getValidationLevel()); + modelBuildingRequest.setProcessPlugins(request.isProcessPlugins()); + modelBuildingRequest.setProfiles(request.getProfiles()); + modelBuildingRequest.setActiveProfileIds(request.getActiveProfileIds()); + modelBuildingRequest.setInactiveProfileIds(request.getInactiveProfileIds()); + modelBuildingRequest.setSystemProperties(request.getSystemProperties()); + modelBuildingRequest.setUserProperties(request.getUserProperties()); + modelBuildingRequest.setBuildStartTime(request.getBuildStartTime()); + modelBuildingRequest.setModelResolver(resolver); + // this is a hint that we want to build 1 file, so don't cache. See MNG-7063 + if (modelPool != null) { + modelBuildingRequest.setModelCache(modelCacheFactory.createCache(session)); } + modelBuildingRequest.setTransformerContextBuilder(transformerContextBuilder); + + return modelBuildingRequest; } - // snapshot artifact repository - if (project.getDistributionManagement() != null - && project.getDistributionManagement().getSnapshotRepository() != null) { + private DependencyResolutionResult resolveDependencies(MavenProject project) { + DependencyResolutionResult resolutionResult; + try { - DeploymentRepository r = project.getDistributionManagement().getSnapshotRepository(); - if (r.getId() != null - && !r.getId().isEmpty() - && r.getUrl() != null - && !r.getUrl().isEmpty()) { - ArtifactRepository repo = MavenRepositorySystem.buildArtifactRepository(r); - repositorySystem.injectProxy(projectBuildingRequest.getRepositorySession(), Arrays.asList(repo)); - repositorySystem.injectAuthentication( - projectBuildingRequest.getRepositorySession(), Arrays.asList(repo)); - project.setSnapshotArtifactRepository(repo); - } - } catch (InvalidRepositoryException e) { - throw new IllegalStateException( - "Failed to create snapshot distribution repository for " + project.getId(), e); + DefaultDependencyResolutionRequest resolution = + new DefaultDependencyResolutionRequest(project, session); + resolutionResult = dependencyResolver.resolve(resolution); + } catch (DependencyResolutionException e) { + resolutionResult = e.getResult(); } - } - } - private void initParent( - MavenProject project, - Map projects, - boolean buildParentIfNotExisting, - ModelBuildingResult result, - ProjectBuildingRequest projectBuildingRequest) { - Model parentModel = - result.getModelIds().size() > 1 && !result.getModelIds().get(1).isEmpty() - ? result.getRawModel(result.getModelIds().get(1)) - : null; - - if (parentModel != null) { - final String parentGroupId = inheritedGroupId(result, 1); - final String parentVersion = inheritedVersion(result, 1); - - project.setParentArtifact( - repositorySystem.createProjectArtifact(parentGroupId, parentModel.getArtifactId(), parentVersion)); - - // org.apache.maven.its.mng4834:parent:0.1 - String parentModelId = result.getModelIds().get(1); - File parentPomFile = result.getRawModel(parentModelId).getPomFile(); - MavenProject parent = projects.get(parentPomFile); - if (parent == null && buildParentIfNotExisting) { - // - // At this point the DefaultModelBuildingListener has fired and it populates the - // remote repositories with those found in the pom.xml, along with the existing externally - // defined repositories. - // - projectBuildingRequest.setRemoteRepositories(project.getRemoteArtifactRepositories()); - if (parentPomFile != null) { - project.setParentFile(parentPomFile); - try { - parent = build(parentPomFile, projectBuildingRequest).getProject(); - } catch (ProjectBuildingException e) { - // MNG-4488 where let invalid parents slide on by - if (logger.isDebugEnabled()) { - // Message below is checked for in the MNG-2199 core IT. - logger.warn("Failed to build parent project for " + project.getId(), e); - } else { - // Message below is checked for in the MNG-2199 core IT. - logger.warn("Failed to build parent project for " + project.getId()); - } - } - } else { - Artifact parentArtifact = project.getParentArtifact(); - try { - parent = build(parentArtifact, projectBuildingRequest).getProject(); - } catch (ProjectBuildingException e) { - // MNG-4488 where let invalid parents slide on by - if (logger.isDebugEnabled()) { - // Message below is checked for in the MNG-2199 core IT. - logger.warn("Failed to build parent project for " + project.getId(), e); - } else { - // Message below is checked for in the MNG-2199 core IT. - logger.warn("Failed to build parent project for " + project.getId()); - } + Set artifacts = new LinkedHashSet<>(); + if (resolutionResult.getDependencyGraph() != null) { + RepositoryUtils.toArtifacts( + artifacts, + resolutionResult.getDependencyGraph().getChildren(), + Collections.singletonList(project.getArtifact().getId()), + null); + + // Maven 2.x quirk: an artifact always points at the local repo, regardless whether resolved or not + LocalRepositoryManager lrm = session.getLocalRepositoryManager(); + for (Artifact artifact : artifacts) { + if (!artifact.isResolved()) { + String path = lrm.getPathForLocalArtifact(RepositoryUtils.toArtifact(artifact)); + artifact.setFile(new File(lrm.getRepository().getBasedir(), path)); } } } - project.setParent(parent); - if (project.getParentFile() == null && parent != null) { - project.setParentFile(parent.getFile()); - } + project.setResolvedArtifacts(artifacts); + project.setArtifacts(artifacts); + + return resolutionResult; } } + private List getProfileIds(List profiles) { + return profiles.stream().map(org.apache.maven.model.Profile::getId).collect(Collectors.toList()); + } + + private static ModelSource createStubModelSource(Artifact artifact) { + StringBuilder buffer = new StringBuilder(1024); + + buffer.append(""); + buffer.append(""); + buffer.append("4.0.0"); + buffer.append("").append(artifact.getGroupId()).append(""); + buffer.append("").append(artifact.getArtifactId()).append(""); + buffer.append("").append(artifact.getBaseVersion()).append(""); + buffer.append("").append(artifact.getType()).append(""); + buffer.append(""); + + return new StringModelSource(buffer, artifact.getId()); + } + private static String inheritedGroupId(final ModelBuildingResult result, final int modelIndex) { String groupId = null; final String modelId = result.getModelIds().get(modelIndex); @@ -933,31 +924,28 @@ private static String inheritedVersion(final ModelBuildingResult result, final i return version; } - /** - * InternalConfig - */ - class InternalConfig { - - private final ProjectBuildingRequest request; - - private final RepositorySystemSession session; - - private final List repositories; - - private final ReactorModelPool modelPool; + static void uncheckedThrow(Throwable t) throws T { + throw (T) t; // rely on vacuous cast + } - private final TransformerContextBuilder transformerContextBuilder; + static class LazyMap extends AbstractMap { + private final Supplier> supplier; + private volatile Map delegate; - InternalConfig( - ProjectBuildingRequest request, - ReactorModelPool modelPool, - TransformerContextBuilder transformerContextBuilder) { - this.request = request; - this.modelPool = modelPool; - this.transformerContextBuilder = transformerContextBuilder; + LazyMap(Supplier> supplier) { + this.supplier = supplier; + } - session = RepositoryUtils.overlay(request.getLocalRepository(), request.getRepositorySession(), repoSystem); - repositories = RepositoryUtils.toRepos(request.getRemoteRepositories()); + @Override + public Set> entrySet() { + if (delegate == null) { + synchronized (this) { + if (delegate == null) { + delegate = supplier.get(); + } + } + } + return delegate.entrySet(); } } } diff --git a/maven-model-builder/src/main/java/org/apache/maven/model/building/DefaultModelBuilder.java b/maven-model-builder/src/main/java/org/apache/maven/model/building/DefaultModelBuilder.java index c8b882ae1918..da14e985353f 100644 --- a/maven-model-builder/src/main/java/org/apache/maven/model/building/DefaultModelBuilder.java +++ b/maven-model-builder/src/main/java/org/apache/maven/model/building/DefaultModelBuilder.java @@ -25,15 +25,7 @@ import java.io.File; import java.io.IOException; import java.lang.reflect.Field; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.Iterator; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Properties; +import java.util.*; import java.util.concurrent.Callable; import java.util.concurrent.ForkJoinTask; import java.util.function.Function; @@ -48,7 +40,6 @@ import org.apache.maven.artifact.versioning.VersionRange; import org.apache.maven.building.Source; import org.apache.maven.model.Activation; -import org.apache.maven.model.ActivationFile; import org.apache.maven.model.Build; import org.apache.maven.model.Dependency; import org.apache.maven.model.DependencyManagement; @@ -58,7 +49,6 @@ import org.apache.maven.model.Plugin; import org.apache.maven.model.PluginManagement; import org.apache.maven.model.Profile; -import org.apache.maven.model.Repository; import org.apache.maven.model.building.ModelProblem.Severity; import org.apache.maven.model.building.ModelProblem.Version; import org.apache.maven.model.composition.DependencyManagementImporter; @@ -712,7 +702,7 @@ private void activateFileModel( profileActivationContext.setUserProperties(profileProps); } - profileActivationContext.setProjectProperties(inputModel.getProperties()); + profileActivationContext.setProjectProperties(inputModel.getDelegate().getProperties()); problems.setSource(inputModel); List activePomProfiles = profileSelector.getActiveProfiles(inputModel.getProfiles(), profileActivationContext, problems); @@ -741,6 +731,9 @@ private Model readEffectiveModel( DefaultModelProblemCollector problems) throws ModelBuildingException { Model inputModel = readRawModel(request, problems); + if (problems.hasFatalErrors()) { + throw problems.newModelBuildingException(); + } problems.setRootModel(inputModel); @@ -769,50 +762,43 @@ private Model readEffectiveModel( String modelId = currentData.getId(); result.addModelId(modelId); - Model rawModel = currentData.getModel(); - result.setRawModel(modelId, rawModel); - - profileActivationContext.setProjectProperties(rawModel.getProperties()); - problems.setSource(rawModel); - List activePomProfiles = - profileSelector.getActiveProfiles(rawModel.getProfiles(), profileActivationContext, problems); - result.setActivePomProfiles(modelId, activePomProfiles); - - Model tmpModel = rawModel.clone(); - - problems.setSource(tmpModel); + Model model = currentData.getModel(); + result.setRawModel(modelId, model); + problems.setSource(model); + org.apache.maven.api.model.Model modelv4 = model.getDelegate(); // model normalization - tmpModel = new Model(modelNormalizer.mergeDuplicates(tmpModel.getDelegate(), request, problems)); + modelv4 = modelNormalizer.mergeDuplicates(modelv4, request, problems); - profileActivationContext.setProjectProperties(tmpModel.getProperties()); + // profile activation + profileActivationContext.setProjectProperties(modelv4.getProperties()); - Map interpolatedActivations = - getInterpolatedActivations(rawModel, profileActivationContext, problems); - injectProfileActivations(tmpModel, interpolatedActivations); + List interpolatedProfiles = + interpolateActivations(modelv4.getProfiles(), profileActivationContext, problems); // profile injection - for (Profile activeProfile : result.getActivePomProfiles(modelId)) { - profileInjector.injectProfile(tmpModel, activeProfile, request, problems); - } - + List activePomProfiles = + profileSelector.getActiveProfilesV4(interpolatedProfiles, profileActivationContext, problems); + result.setActivePomProfiles( + modelId, activePomProfiles.stream().map(Profile::new).collect(Collectors.toList())); + modelv4 = profileInjector.injectProfiles(modelv4, activePomProfiles, request, problems); if (currentData == resultData) { for (Profile activeProfile : activeExternalProfiles) { - profileInjector.injectProfile(tmpModel, activeProfile, request, problems); + modelv4 = profileInjector.injectProfile(modelv4, activeProfile.getDelegate(), request, problems); } - result.setEffectiveModel(tmpModel); } - lineage.add(tmpModel); + lineage.add(new Model(modelv4)); if (currentData == superData) { break; } - configureResolver(request.getModelResolver(), tmpModel, problems); + // add repositories specified by the current model so that we can resolve the parent + configureResolver(request.getModelResolver(), modelv4, problems, false); - ModelData parentData = - readParent(currentData.getModel(), currentData.getSource(), request, result, problems); + // we pass a cloned model, so that resolving the parent version does not affect the returned model + ModelData parentData = readParent(new Model(modelv4), currentData.getSource(), request, problems); if (parentData == null) { currentData = superData; @@ -832,7 +818,22 @@ private Model readEffectiveModel( } } - problems.setSource(result.getRawModel()); + Model tmpModel = lineage.get(0); + + // inject interpolated activations + List interpolated = + interpolateActivations(tmpModel.getDelegate().getProfiles(), profileActivationContext, problems); + if (interpolated != tmpModel.getDelegate().getProfiles()) { + tmpModel.update(tmpModel.getDelegate().withProfiles(interpolated)); + } + + // inject external profile into current model + tmpModel.update(profileInjector.injectProfiles( + tmpModel.getDelegate(), + activeExternalProfiles.stream().map(Profile::getDelegate).collect(Collectors.toList()), + request, + problems)); + checkPluginVersions(lineage, request, problems); // inheritance assembly @@ -852,44 +853,76 @@ private Model readEffectiveModel( result.setEffectiveModel(resultModel); // Now the fully interpolated model is available: reconfigure the resolver - configureResolver(request.getModelResolver(), resultModel, problems, true); + configureResolver(request.getModelResolver(), resultModel.getDelegate(), problems, true); return resultModel; } - private Map getInterpolatedActivations( - Model rawModel, DefaultProfileActivationContext context, DefaultModelProblemCollector problems) { - Map interpolatedActivations = getProfileActivations(rawModel, true); - for (Activation activation : interpolatedActivations.values()) { - if (activation.getFile() != null) { - replaceWithInterpolatedValue(activation.getFile(), context, problems); + private List interpolateActivations( + List profiles, + DefaultProfileActivationContext context, + DefaultModelProblemCollector problems) { + List newProfiles = null; + for (int index = 0; index < profiles.size(); index++) { + org.apache.maven.api.model.Profile profile = profiles.get(index); + org.apache.maven.api.model.Activation activation = profile.getActivation(); + if (activation != null) { + org.apache.maven.api.model.ActivationFile file = activation.getFile(); + if (file != null) { + String oldExists = file.getExists(); + if (isNotEmpty(oldExists)) { + try { + String newExists = interpolate(oldExists, context); + if (!Objects.equals(oldExists, newExists)) { + if (newProfiles == null) { + newProfiles = new ArrayList<>(profiles); + } + newProfiles.set( + index, profile.withActivation(activation.withFile(file.withExists(newExists)))); + } + } catch (InterpolationException e) { + addInterpolationProblem(problems, file, oldExists, e, "exists"); + } + } else { + String oldMissing = file.getMissing(); + if (isNotEmpty(oldMissing)) { + try { + String newMissing = interpolate(oldMissing, context); + if (!Objects.equals(oldMissing, newMissing)) { + if (newProfiles == null) { + newProfiles = new ArrayList<>(profiles); + } + newProfiles.set( + index, + profile.withActivation(activation.withFile(file.withMissing(newMissing)))); + } + } catch (InterpolationException e) { + addInterpolationProblem(problems, file, oldMissing, e, "missing"); + } + } + } + } } } - return interpolatedActivations; + return newProfiles != null ? newProfiles : profiles; } - private void replaceWithInterpolatedValue( - ActivationFile activationFile, ProfileActivationContext context, DefaultModelProblemCollector problems) { - try { - if (isNotEmpty(activationFile.getExists())) { - String path = activationFile.getExists(); - String absolutePath = profileActivationFilePathInterpolator.interpolate(path, context); - activationFile.setExists(absolutePath); - } else if (isNotEmpty(activationFile.getMissing())) { - String path = activationFile.getMissing(); - String absolutePath = profileActivationFilePathInterpolator.interpolate(path, context); - activationFile.setMissing(absolutePath); - } - } catch (InterpolationException e) { - String path = - isNotEmpty(activationFile.getExists()) ? activationFile.getExists() : activationFile.getMissing(); + private static void addInterpolationProblem( + DefaultModelProblemCollector problems, + org.apache.maven.api.model.ActivationFile file, + String path, + InterpolationException e, + String locationKey) { + problems.add(new ModelProblemCollectorRequest(Severity.ERROR, Version.BASE) + .setMessage("Failed to interpolate file location " + path + ": " + e.getMessage()) + .setLocation(Optional.ofNullable(file.getLocation(locationKey)) + .map(InputLocation::new) + .orElse(null)) + .setException(e)); + } - problems.add(new ModelProblemCollectorRequest(Severity.ERROR, Version.BASE) - .setMessage("Failed to interpolate file location " + path + ": " + e.getMessage()) - .setLocation( - activationFile.getLocation(isNotEmpty(activationFile.getExists()) ? "exists" : "missing")) - .setException(e)); - } + private String interpolate(String path, ProfileActivationContext context) throws InterpolationException { + return isNotEmpty(path) ? profileActivationFilePathInterpolator.interpolate(path, context) : path; } private static boolean isNotEmpty(String string) { @@ -902,6 +935,15 @@ public ModelBuildingResult build(final ModelBuildingRequest request, final Model return build(request, result, new LinkedHashSet<>()); } + public Model buildRawModel(final ModelBuildingRequest request) throws ModelBuildingException { + DefaultModelProblemCollector problems = new DefaultModelProblemCollector(new DefaultModelBuildingResult()); + Model model = readRawModel(request, problems); + if (hasModelErrors(problems)) { + throw problems.newModelBuildingException(); + } + return model; + } + private ModelBuildingResult build( final ModelBuildingRequest request, final ModelBuildingResult phaseOneResult, Collection imports) throws ModelBuildingException { @@ -1119,41 +1161,49 @@ Model readRawModel(ModelBuildingRequest request, DefaultModelProblemCollector pr throws ModelBuildingException { ModelSource modelSource = request.getModelSource(); - ModelData cachedData = cache(request.getModelCache(), modelSource, ModelCacheTag.RAW, () -> { - Model rawModel; - if (Features.buildConsumer(request.getUserProperties()) && modelSource instanceof FileModelSource) { - rawModel = readFileModel(request, problems); - File pomFile = ((FileModelSource) modelSource).getFile(); + ModelData modelData = cache( + request.getModelCache(), + modelSource, + ModelCacheTag.RAW, + () -> doReadRawModel(modelSource, request, problems)); + + return modelData.getModel(); + } - try { - if (request.getTransformerContextBuilder() != null) { - TransformerContext context = - request.getTransformerContextBuilder().initialize(request, problems); - transformer.transform(pomFile.toPath(), context, rawModel); - } - } catch (TransformerException e) { - problems.add(new ModelProblemCollectorRequest(Severity.FATAL, Version.V40).setException(e)); + private ModelData doReadRawModel( + ModelSource modelSource, ModelBuildingRequest request, DefaultModelProblemCollector problems) + throws ModelBuildingException { + Model rawModel; + if (Features.buildConsumer(request.getUserProperties()) && modelSource instanceof FileModelSource) { + rawModel = readFileModel(request, problems); + File pomFile = ((FileModelSource) modelSource).getFile(); + + try { + if (request.getTransformerContextBuilder() != null) { + TransformerContext context = + request.getTransformerContextBuilder().initialize(request, problems); + transformer.transform(pomFile.toPath(), context, rawModel); } - } else if (request.getFileModel() == null) { - rawModel = readFileModel(request, problems); - } else { - rawModel = request.getFileModel().clone(); + } catch (TransformerException e) { + problems.add(new ModelProblemCollectorRequest(Severity.FATAL, Version.V40).setException(e)); } + } else if (request.getFileModel() == null) { + rawModel = readFileModel(request, problems); + } else { + rawModel = request.getFileModel().clone(); + } - modelValidator.validateRawModel(rawModel, request, problems); + modelValidator.validateRawModel(rawModel, request, problems); - if (hasFatalErrors(problems)) { - throw problems.newModelBuildingException(); - } + if (hasFatalErrors(problems)) { + throw problems.newModelBuildingException(); + } - String groupId = getGroupId(rawModel); - String artifactId = rawModel.getArtifactId(); - String version = getVersion(rawModel); + String groupId = getGroupId(rawModel); + String artifactId = rawModel.getArtifactId(); + String version = getVersion(rawModel); - ModelData modelData = new ModelData(modelSource, rawModel, groupId, artifactId, version); - return modelData; - }); - return cachedData != null ? cachedData.getModel() : null; + return new ModelData(modelSource, rawModel, groupId, artifactId, version); } String getGroupId(Model model) { @@ -1196,31 +1246,21 @@ private DefaultProfileActivationContext getProfileActivationContext(ModelBuildin return context; } - private void configureResolver(ModelResolver modelResolver, Model model, DefaultModelProblemCollector problems) { - configureResolver(modelResolver, model, problems, false); - } - private void configureResolver( ModelResolver modelResolver, - Model model, + org.apache.maven.api.model.Model model, DefaultModelProblemCollector problems, boolean replaceRepositories) { - if (modelResolver == null) { - return; - } - - problems.setSource(model); - - List repositories = model.getRepositories(); - - for (Repository repository : repositories) { - try { - modelResolver.addRepository(repository, replaceRepositories); - } catch (InvalidRepositoryException e) { - problems.add(new ModelProblemCollectorRequest(Severity.ERROR, Version.BASE) - .setMessage("Invalid repository " + repository.getId() + ": " + e.getMessage()) - .setLocation(repository.getLocation("")) - .setException(e)); + if (modelResolver != null) { + for (org.apache.maven.api.model.Repository repository : model.getRepositories()) { + try { + modelResolver.addRepository(repository, replaceRepositories); + } catch (InvalidRepositoryException e) { + problems.add(new ModelProblemCollectorRequest(Severity.ERROR, Version.BASE) + .setMessage("Invalid repository " + repository.getId() + ": " + e.getMessage()) + .setLocation(new InputLocation(repository.getLocation(""))) + .setException(e)); + } } } } @@ -1345,19 +1385,15 @@ private Model interpolateModel(Model model, ModelBuildingRequest request, ModelP } private ModelData readParent( - Model childModel, - Source childSource, - ModelBuildingRequest request, - ModelBuildingResult result, - DefaultModelProblemCollector problems) + Model childModel, Source childSource, ModelBuildingRequest request, DefaultModelProblemCollector problems) throws ModelBuildingException { ModelData parentData = null; Parent parent = childModel.getParent(); if (parent != null) { - parentData = readParentLocally(childModel, childSource, request, result, problems); + parentData = readParentLocally(childModel, childSource, request, problems); if (parentData == null) { - parentData = readParentExternally(childModel, request, result, problems); + parentData = readParentExternally(childModel, request, problems); } Model parentModel = parentData.getModel(); @@ -1373,11 +1409,7 @@ private ModelData readParent( } private ModelData readParentLocally( - Model childModel, - Source childSource, - ModelBuildingRequest request, - ModelBuildingResult result, - DefaultModelProblemCollector problems) + Model childModel, Source childSource, ModelBuildingRequest request, DefaultModelProblemCollector problems) throws ModelBuildingException { final Parent parent = childModel.getParent(); final ModelSource2 candidateSource; @@ -1516,10 +1548,7 @@ private ModelSource2 getParentPomFile(Model childModel, Source source) { } private ModelData readParentExternally( - Model childModel, - ModelBuildingRequest request, - ModelBuildingResult result, - DefaultModelProblemCollector problems) + Model childModel, ModelBuildingRequest request, DefaultModelProblemCollector problems) throws ModelBuildingException { problems.setSource(childModel); @@ -1693,12 +1722,14 @@ private DependencyManagement loadDependencyManagement( return null; } - org.apache.maven.api.model.DependencyManagement importMgmt = - cache(request.getModelCache(), groupId, artifactId, version, ModelCacheTag.IMPORT, () -> { - DependencyManagement importMgmtV3 = doLoadDependencyManagement( - model, request, problems, dependency, groupId, artifactId, version, importIds); - return importMgmtV3 != null ? importMgmtV3.getDelegate() : null; - }); + org.apache.maven.api.model.DependencyManagement importMgmt = cache( + request.getModelCache(), + groupId, + artifactId, + version, + ModelCacheTag.IMPORT, + () -> doLoadDependencyManagement( + model, request, problems, dependency, groupId, artifactId, version, importIds)); // [MNG-5600] Dependency management import should support exclusions. List exclusions = dependency.getDelegate().getExclusions(); @@ -1724,7 +1755,7 @@ private boolean match(String match, String text) { } @SuppressWarnings("checkstyle:parameternumber") - private DependencyManagement doLoadDependencyManagement( + private org.apache.maven.api.model.DependencyManagement doLoadDependencyManagement( Model model, ModelBuildingRequest request, DefaultModelProblemCollector problems, @@ -1802,7 +1833,7 @@ private DependencyManagement doLoadDependencyManagement( if (importMgmt == null) { importMgmt = new DependencyManagement(); } - return importMgmt; + return importMgmt.getDelegate(); } private static T cache( @@ -1855,8 +1886,7 @@ private void fireEvent( Model model, ModelBuildingRequest request, ModelProblemCollector problems, - ModelBuildingEventCatapult catapult) - throws ModelBuildingException { + ModelBuildingEventCatapult catapult) { ModelBuildingListener listener = request.getModelBuildingListener(); if (listener != null) {