001    package org.apache.maven.project;
002    
003    /*
004     * Licensed to the Apache Software Foundation (ASF) under one
005     * or more contributor license agreements.  See the NOTICE file
006     * distributed with this work for additional information
007     * regarding copyright ownership.  The ASF licenses this file
008     * to you under the Apache License, Version 2.0 (the
009     * "License"); you may not use this file except in compliance
010     * with the License.  You may obtain a copy of the License at
011     *
012     *  http://www.apache.org/licenses/LICENSE-2.0
013     *
014     * Unless required by applicable law or agreed to in writing,
015     * software distributed under the License is distributed on an
016     * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017     * KIND, either express or implied.  See the License for the
018     * specific language governing permissions and limitations
019     * under the License.
020     */
021    
022    import java.io.File;
023    import java.io.IOException;
024    import java.util.ArrayList;
025    import java.util.Arrays;
026    import java.util.Collections;
027    import java.util.HashMap;
028    import java.util.LinkedHashSet;
029    import java.util.List;
030    import java.util.Map;
031    import java.util.Set;
032    
033    import org.apache.maven.RepositoryUtils;
034    import org.apache.maven.artifact.Artifact;
035    import org.apache.maven.artifact.repository.LegacyLocalRepositoryManager;
036    import org.apache.maven.model.Build;
037    import org.apache.maven.model.Model;
038    import org.apache.maven.model.Profile;
039    import org.apache.maven.model.building.DefaultModelBuildingRequest;
040    import org.apache.maven.model.building.DefaultModelProblem;
041    import org.apache.maven.model.building.FileModelSource;
042    import org.apache.maven.model.building.ModelBuilder;
043    import org.apache.maven.model.building.ModelBuildingException;
044    import org.apache.maven.model.building.ModelBuildingRequest;
045    import org.apache.maven.model.building.ModelBuildingResult;
046    import org.apache.maven.model.building.ModelProblem;
047    import org.apache.maven.model.building.ModelProcessor;
048    import org.apache.maven.model.building.ModelSource;
049    import org.apache.maven.model.building.StringModelSource;
050    import org.apache.maven.model.resolution.ModelResolver;
051    import org.apache.maven.repository.RepositorySystem;
052    import org.apache.maven.repository.internal.ArtifactDescriptorUtils;
053    import org.codehaus.plexus.component.annotations.Component;
054    import org.codehaus.plexus.component.annotations.Requirement;
055    import org.codehaus.plexus.logging.Logger;
056    import org.codehaus.plexus.util.Os;
057    import org.codehaus.plexus.util.StringUtils;
058    import org.eclipse.aether.RepositorySystemSession;
059    import org.eclipse.aether.RequestTrace;
060    import org.eclipse.aether.impl.RemoteRepositoryManager;
061    import org.eclipse.aether.repository.LocalRepositoryManager;
062    import org.eclipse.aether.repository.RemoteRepository;
063    import org.eclipse.aether.repository.WorkspaceRepository;
064    import org.eclipse.aether.resolution.ArtifactRequest;
065    import org.eclipse.aether.resolution.ArtifactResult;
066    
067    /**
068     */
069    @Component( role = ProjectBuilder.class )
070    public class DefaultProjectBuilder
071        implements ProjectBuilder
072    {
073    
074        @Requirement
075        private Logger logger;
076    
077        @Requirement
078        private ModelBuilder modelBuilder;
079    
080        @Requirement
081        private ModelProcessor modelProcessor;
082    
083        @Requirement
084        private ProjectBuildingHelper projectBuildingHelper;
085    
086        @Requirement
087        private RepositorySystem repositorySystem;
088    
089        @Requirement
090        private org.eclipse.aether.RepositorySystem repoSystem;
091    
092        @Requirement
093        private RemoteRepositoryManager repositoryManager;
094    
095        @Requirement
096        private ProjectDependenciesResolver dependencyResolver;
097    
098        // ----------------------------------------------------------------------
099        // MavenProjectBuilder Implementation
100        // ----------------------------------------------------------------------
101    
102        public ProjectBuildingResult build( File pomFile, ProjectBuildingRequest request )
103            throws ProjectBuildingException
104        {
105            return build( pomFile, new FileModelSource( pomFile ), new InternalConfig( request, null ) );
106        }
107    
108        public ProjectBuildingResult build( ModelSource modelSource, ProjectBuildingRequest request )
109            throws ProjectBuildingException
110        {
111            return build( null, modelSource, new InternalConfig( request, null ) );
112        }
113    
114        private ProjectBuildingResult build( File pomFile, ModelSource modelSource, InternalConfig config )
115            throws ProjectBuildingException
116        {
117            ClassLoader oldContextClassLoader = Thread.currentThread().getContextClassLoader();
118    
119            try
120            {
121                ProjectBuildingRequest configuration = config.request;
122    
123                MavenProject project = configuration.getProject();
124    
125                List<ModelProblem> modelProblems = null;
126                Throwable error = null;
127    
128                if ( project == null )
129                {
130                    ModelBuildingRequest request = getModelBuildingRequest( config );
131    
132                    project = new MavenProject( repositorySystem, this, configuration, logger );
133    
134                    DefaultModelBuildingListener listener =
135                        new DefaultModelBuildingListener( project, projectBuildingHelper, configuration );
136                    request.setModelBuildingListener( listener );
137    
138                    request.setPomFile( pomFile );
139                    request.setModelSource( modelSource );
140                    request.setLocationTracking( true );
141    
142                    ModelBuildingResult result;
143                    try
144                    {
145                        result = modelBuilder.build( request );
146                    }
147                    catch ( ModelBuildingException e )
148                    {
149                        result = e.getResult();
150                        if ( result == null || result.getEffectiveModel() == null )
151                        {
152                            throw new ProjectBuildingException( e.getModelId(), e.getMessage(), pomFile, e );
153                        }
154                        // validation error, continue project building and delay failing to help IDEs
155                        error = e;
156                    }
157    
158                    modelProblems = result.getProblems();
159    
160                    initProject( project, Collections.<String, MavenProject> emptyMap(), result,
161                                 new HashMap<File, Boolean>() );
162                }
163                else if ( configuration.isResolveDependencies() )
164                {
165                    projectBuildingHelper.selectProjectRealm( project );
166                }
167    
168                DependencyResolutionResult resolutionResult = null;
169    
170                if ( configuration.isResolveDependencies() )
171                {
172                    resolutionResult = resolveDependencies( project, config.session );
173                }
174    
175                ProjectBuildingResult result = new DefaultProjectBuildingResult( project, modelProblems, resolutionResult );
176    
177                if ( error != null )
178                {
179                    ProjectBuildingException e = new ProjectBuildingException( Arrays.asList( result ) );
180                    e.initCause( error );
181                    throw e;
182                }
183    
184                return result;
185            }
186            finally
187            {
188                Thread.currentThread().setContextClassLoader( oldContextClassLoader );
189            }
190        }
191    
192        private DependencyResolutionResult resolveDependencies( MavenProject project, RepositorySystemSession session )
193        {
194            DependencyResolutionResult resolutionResult = null;
195    
196            try
197            {
198                DefaultDependencyResolutionRequest resolution = new DefaultDependencyResolutionRequest( project, session );
199                resolutionResult = dependencyResolver.resolve( resolution );
200            }
201            catch ( DependencyResolutionException e )
202            {
203                resolutionResult = e.getResult();
204            }
205    
206            Set<Artifact> artifacts = new LinkedHashSet<Artifact>();
207            if ( resolutionResult.getDependencyGraph() != null )
208            {
209                RepositoryUtils.toArtifacts( artifacts, resolutionResult.getDependencyGraph().getChildren(),
210                                             Collections.singletonList( project.getArtifact().getId() ), null );
211    
212                // Maven 2.x quirk: an artifact always points at the local repo, regardless whether resolved or not
213                LocalRepositoryManager lrm = session.getLocalRepositoryManager();
214                for ( Artifact artifact : artifacts )
215                {
216                    if ( !artifact.isResolved() )
217                    {
218                        String path = lrm.getPathForLocalArtifact( RepositoryUtils.toArtifact( artifact ) );
219                        artifact.setFile( new File( lrm.getRepository().getBasedir(), path ) );
220                    }
221                }
222            }
223            project.setResolvedArtifacts( artifacts );
224            project.setArtifacts( artifacts );
225    
226            return resolutionResult;
227        }
228    
229        private List<String> getProfileIds( List<Profile> profiles )
230        {
231            List<String> ids = new ArrayList<String>( profiles.size() );
232    
233            for ( Profile profile : profiles )
234            {
235                ids.add( profile.getId() );
236            }
237    
238            return ids;
239        }
240    
241        private ModelBuildingRequest getModelBuildingRequest( InternalConfig config )
242        {
243            ProjectBuildingRequest configuration = config.request;
244    
245            ModelBuildingRequest request = new DefaultModelBuildingRequest();
246    
247            RequestTrace trace = RequestTrace.newChild( null, configuration ).newChild( request );
248    
249            ModelResolver resolver =
250                new ProjectModelResolver( config.session, trace, repoSystem, repositoryManager, config.repositories,
251                                          configuration.getRepositoryMerging(), config.modelPool );
252    
253            request.setValidationLevel( configuration.getValidationLevel() );
254            request.setProcessPlugins( configuration.isProcessPlugins() );
255            request.setProfiles( configuration.getProfiles() );
256            request.setActiveProfileIds( configuration.getActiveProfileIds() );
257            request.setInactiveProfileIds( configuration.getInactiveProfileIds() );
258            request.setSystemProperties( configuration.getSystemProperties() );
259            request.setUserProperties( configuration.getUserProperties() );
260            request.setBuildStartTime( configuration.getBuildStartTime() );
261            request.setModelResolver( resolver );
262            request.setModelCache( new ReactorModelCache() );
263    
264            return request;
265        }
266    
267        public ProjectBuildingResult build( Artifact artifact, ProjectBuildingRequest request )
268            throws ProjectBuildingException
269        {
270            return build( artifact, false, request );
271        }
272    
273        public ProjectBuildingResult build( Artifact artifact, boolean allowStubModel, ProjectBuildingRequest request )
274            throws ProjectBuildingException
275        {
276            org.eclipse.aether.artifact.Artifact pomArtifact = RepositoryUtils.toArtifact( artifact );
277            pomArtifact = ArtifactDescriptorUtils.toPomArtifact( pomArtifact );
278    
279            InternalConfig config = new InternalConfig( request, null );
280    
281            boolean localProject;
282    
283            try
284            {
285                ArtifactRequest pomRequest = new ArtifactRequest();
286                pomRequest.setArtifact( pomArtifact );
287                pomRequest.setRepositories( config.repositories );
288                ArtifactResult pomResult = repoSystem.resolveArtifact( config.session, pomRequest );
289    
290                pomArtifact = pomResult.getArtifact();
291                localProject = pomResult.getRepository() instanceof WorkspaceRepository;
292            }
293            catch ( org.eclipse.aether.resolution.ArtifactResolutionException e )
294            {
295                if ( e.getResults().get( 0 ).isMissing() && allowStubModel )
296                {
297                    return build( null, createStubModelSource( artifact ), config );
298                }
299                throw new ProjectBuildingException( artifact.getId(),
300                                                    "Error resolving project artifact: " + e.getMessage(), e );
301            }
302    
303            File pomFile = pomArtifact.getFile();
304    
305            if ( "pom".equals( artifact.getType() ) )
306            {
307                artifact.selectVersion( pomArtifact.getVersion() );
308                artifact.setFile( pomFile );
309                artifact.setResolved( true );
310            }
311    
312            return build( localProject ? pomFile : null, new FileModelSource( pomFile ), config );
313        }
314    
315        private ModelSource createStubModelSource( Artifact artifact )
316        {
317            StringBuilder buffer = new StringBuilder( 1024 );
318    
319            buffer.append( "<?xml version='1.0'?>" );
320            buffer.append( "<project>" );
321            buffer.append( "<modelVersion>4.0.0</modelVersion>" );
322            buffer.append( "<groupId>" ).append( artifact.getGroupId() ).append( "</groupId>" );
323            buffer.append( "<artifactId>" ).append( artifact.getArtifactId() ).append( "</artifactId>" );
324            buffer.append( "<version>" ).append( artifact.getBaseVersion() ).append( "</version>" );
325            buffer.append( "<packaging>" ).append( artifact.getType() ).append( "</packaging>" );
326            buffer.append( "</project>" );
327    
328            return new StringModelSource( buffer, artifact.getId() );
329        }
330    
331        public List<ProjectBuildingResult> build( List<File> pomFiles, boolean recursive, ProjectBuildingRequest request )
332            throws ProjectBuildingException
333        {
334            List<ProjectBuildingResult> results = new ArrayList<ProjectBuildingResult>();
335    
336            List<InterimResult> interimResults = new ArrayList<InterimResult>();
337    
338            ReactorModelPool modelPool = new ReactorModelPool();
339    
340            InternalConfig config = new InternalConfig( request, modelPool );
341    
342            Map<String, MavenProject> projectIndex = new HashMap<String, MavenProject>( 256 );
343    
344            boolean noErrors =
345                build( results, interimResults, projectIndex, pomFiles, new LinkedHashSet<File>(), true, recursive, config );
346    
347            populateReactorModelPool( modelPool, interimResults );
348    
349            ClassLoader oldContextClassLoader = Thread.currentThread().getContextClassLoader();
350    
351            try
352            {
353                noErrors =
354                    build( results, new ArrayList<MavenProject>(), projectIndex, interimResults, request,
355                           new HashMap<File, Boolean>() ) && noErrors;
356            }
357            finally
358            {
359                Thread.currentThread().setContextClassLoader( oldContextClassLoader );
360            }
361    
362            if ( !noErrors )
363            {
364                throw new ProjectBuildingException( results );
365            }
366    
367            return results;
368        }
369    
370        private boolean build( List<ProjectBuildingResult> results, List<InterimResult> interimResults,
371                               Map<String, MavenProject> projectIndex, List<File> pomFiles, Set<File> aggregatorFiles,
372                               boolean isRoot, boolean recursive, InternalConfig config )
373        {
374            boolean noErrors = true;
375    
376            for ( File pomFile : pomFiles )
377            {
378                aggregatorFiles.add( pomFile );
379    
380                if ( !build( results, interimResults, projectIndex, pomFile, aggregatorFiles, isRoot, recursive, config ) )
381                {
382                    noErrors = false;
383                }
384    
385                aggregatorFiles.remove( pomFile );
386            }
387    
388            return noErrors;
389        }
390    
391        private boolean build( List<ProjectBuildingResult> results, List<InterimResult> interimResults,
392                               Map<String, MavenProject> projectIndex, File pomFile, Set<File> aggregatorFiles,
393                               boolean isRoot, boolean recursive, InternalConfig config )
394        {
395            boolean noErrors = true;
396    
397            ModelBuildingRequest request = getModelBuildingRequest( config );
398    
399            MavenProject project = new MavenProject( repositorySystem, this, config.request, logger );
400    
401            request.setPomFile( pomFile );
402            request.setTwoPhaseBuilding( true );
403            request.setLocationTracking( true );
404    
405            DefaultModelBuildingListener listener =
406                new DefaultModelBuildingListener( project, projectBuildingHelper, config.request );
407            request.setModelBuildingListener( listener );
408    
409            try
410            {
411                ModelBuildingResult result = modelBuilder.build( request );
412    
413                Model model = result.getEffectiveModel();
414    
415                projectIndex.put( result.getModelIds().get( 0 ), project );
416    
417                InterimResult interimResult = new InterimResult( pomFile, request, result, listener, isRoot );
418                interimResults.add( interimResult );
419    
420                if ( recursive && !model.getModules().isEmpty() )
421                {
422                    File basedir = pomFile.getParentFile();
423    
424                    List<File> moduleFiles = new ArrayList<File>();
425    
426                    for ( String module : model.getModules() )
427                    {
428                        if ( StringUtils.isEmpty( module ) )
429                        {
430                            continue;
431                        }
432    
433                        module = module.replace( '\\', File.separatorChar ).replace( '/', File.separatorChar );
434    
435                        File moduleFile = new File( basedir, module );
436    
437                        if ( moduleFile.isDirectory() )
438                        {
439                            moduleFile = modelProcessor.locatePom( moduleFile );
440                        }
441    
442                        if ( !moduleFile.isFile() )
443                        {
444                            ModelProblem problem =
445                                new DefaultModelProblem( "Child module " + moduleFile + " of " + pomFile
446                                    + " does not exist", ModelProblem.Severity.ERROR, ModelProblem.Version.BASE, model, -1, -1, null );
447                            result.getProblems().add( problem );
448    
449                            noErrors = false;
450    
451                            continue;
452                        }
453    
454                        if ( Os.isFamily( Os.FAMILY_WINDOWS ) )
455                        {
456                            // we don't canonicalize on unix to avoid interfering with symlinks
457                            try
458                            {
459                                moduleFile = moduleFile.getCanonicalFile();
460                            }
461                            catch ( IOException e )
462                            {
463                                moduleFile = moduleFile.getAbsoluteFile();
464                            }
465                        }
466                        else
467                        {
468                            moduleFile = new File( moduleFile.toURI().normalize() );
469                        }
470    
471                        if ( aggregatorFiles.contains( moduleFile ) )
472                        {
473                            StringBuilder buffer = new StringBuilder( 256 );
474                            for ( File aggregatorFile : aggregatorFiles )
475                            {
476                                buffer.append( aggregatorFile ).append( " -> " );
477                            }
478                            buffer.append( moduleFile );
479    
480                            ModelProblem problem =
481                                new DefaultModelProblem( "Child module " + moduleFile + " of " + pomFile
482                                    + " forms aggregation cycle " + buffer, ModelProblem.Severity.ERROR, ModelProblem.Version.BASE, model, -1, -1,
483                                                         null );
484                            result.getProblems().add( problem );
485    
486                            noErrors = false;
487    
488                            continue;
489                        }
490    
491                        moduleFiles.add( moduleFile );
492                    }
493    
494                    interimResult.modules = new ArrayList<InterimResult>();
495    
496                    if ( !build( results, interimResult.modules, projectIndex, moduleFiles, aggregatorFiles, false,
497                                 recursive, config ) )
498                    {
499                        noErrors = false;
500                    }
501                }
502            }
503            catch ( ModelBuildingException e )
504            {
505                results.add( new DefaultProjectBuildingResult( e.getModelId(), pomFile, e.getProblems() ) );
506    
507                noErrors = false;
508            }
509    
510            return noErrors;
511        }
512    
513        static class InterimResult
514        {
515    
516            File pomFile;
517    
518            ModelBuildingRequest request;
519    
520            ModelBuildingResult result;
521    
522            DefaultModelBuildingListener listener;
523    
524            boolean root;
525    
526            List<InterimResult> modules = Collections.emptyList();
527    
528            InterimResult( File pomFile, ModelBuildingRequest request, ModelBuildingResult result,
529                           DefaultModelBuildingListener listener, boolean root )
530            {
531                this.pomFile = pomFile;
532                this.request = request;
533                this.result = result;
534                this.listener = listener;
535                this.root = root;
536            }
537    
538        }
539    
540        private void populateReactorModelPool( ReactorModelPool reactorModelPool, List<InterimResult> interimResults )
541        {
542            for ( InterimResult interimResult : interimResults )
543            {
544                Model model = interimResult.result.getEffectiveModel();
545                reactorModelPool.put( model.getGroupId(), model.getArtifactId(), model.getVersion(), model.getPomFile() );
546    
547                populateReactorModelPool( reactorModelPool, interimResult.modules );
548            }
549        }
550    
551        private boolean build( List<ProjectBuildingResult> results, List<MavenProject> projects,
552                               Map<String, MavenProject> projectIndex, List<InterimResult> interimResults,
553                               ProjectBuildingRequest request, Map<File, Boolean> profilesXmls )
554        {
555            boolean noErrors = true;
556    
557            for ( InterimResult interimResult : interimResults )
558            {
559                try
560                {
561                    ModelBuildingResult result = modelBuilder.build( interimResult.request, interimResult.result );
562    
563                    MavenProject project = interimResult.listener.getProject();
564                    initProject( project, projectIndex, result, profilesXmls );
565    
566                    List<MavenProject> modules = new ArrayList<MavenProject>();
567                    noErrors =
568                        build( results, modules, projectIndex, interimResult.modules, request, profilesXmls ) && noErrors;
569    
570                    projects.addAll( modules );
571                    projects.add( project );
572    
573                    project.setExecutionRoot( interimResult.root );
574                    project.setCollectedProjects( modules );
575    
576                    results.add( new DefaultProjectBuildingResult( project, result.getProblems(), null ) );
577                }
578                catch ( ModelBuildingException e )
579                {
580                    results.add( new DefaultProjectBuildingResult( e.getModelId(), interimResult.pomFile, e.getProblems() ) );
581    
582                    noErrors = false;
583                }
584            }
585    
586            return noErrors;
587        }
588    
589        private void initProject( MavenProject project, Map<String, MavenProject> projects, ModelBuildingResult result,
590                                  Map<File, Boolean> profilesXmls )
591        {
592            Model model = result.getEffectiveModel();
593    
594            project.setModel( model );
595            project.setOriginalModel( result.getRawModel() );
596    
597            project.setFile( model.getPomFile() );
598    
599            File parentPomFile = result.getRawModel( result.getModelIds().get( 1 ) ).getPomFile();
600            project.setParentFile( parentPomFile );
601    
602            project.setParent( projects.get( result.getModelIds().get( 1 ) ) );
603    
604            Artifact projectArtifact =
605                repositorySystem.createArtifact( project.getGroupId(), project.getArtifactId(), project.getVersion(), null,
606                                                 project.getPackaging() );
607            project.setArtifact( projectArtifact );
608    
609            if ( project.getFile() != null )
610            {
611                Build build = project.getBuild();
612                project.addScriptSourceRoot( build.getScriptSourceDirectory() );
613                project.addCompileSourceRoot( build.getSourceDirectory() );
614                project.addTestCompileSourceRoot( build.getTestSourceDirectory() );
615            }
616    
617            List<Profile> activeProfiles = new ArrayList<Profile>();
618            activeProfiles.addAll( result.getActivePomProfiles( result.getModelIds().get( 0 ) ) );
619            activeProfiles.addAll( result.getActiveExternalProfiles() );
620            project.setActiveProfiles( activeProfiles );
621    
622            project.setInjectedProfileIds( "external", getProfileIds( result.getActiveExternalProfiles() ) );
623            for ( String modelId : result.getModelIds() )
624            {
625                project.setInjectedProfileIds( modelId, getProfileIds( result.getActivePomProfiles( modelId ) ) );
626            }
627    
628            String modelId = findProfilesXml( result, profilesXmls );
629            if ( modelId != null )
630            {
631                ModelProblem problem =
632                    new DefaultModelProblem( "Detected profiles.xml alongside " + modelId
633                        + ", this file is no longer supported and was ignored" + ", please use the settings.xml instead",
634                                             ModelProblem.Severity.WARNING, ModelProblem.Version.V30, model, -1, -1, null );
635                result.getProblems().add( problem );
636            }
637        }
638    
639        private String findProfilesXml( ModelBuildingResult result, Map<File, Boolean> profilesXmls )
640        {
641            for ( String modelId : result.getModelIds() )
642            {
643                Model model = result.getRawModel( modelId );
644    
645                File basedir = model.getProjectDirectory();
646                if ( basedir == null )
647                {
648                    break;
649                }
650    
651                Boolean profilesXml = profilesXmls.get( basedir );
652                if ( profilesXml == null )
653                {
654                    profilesXml = new File( basedir, "profiles.xml" ).exists();
655                    profilesXmls.put( basedir, profilesXml );
656                }
657                if ( profilesXml.booleanValue() )
658                {
659                    return modelId;
660                }
661            }
662    
663            return null;
664        }
665    
666        class InternalConfig
667        {
668    
669            public final ProjectBuildingRequest request;
670    
671            public final RepositorySystemSession session;
672    
673            public final List<RemoteRepository> repositories;
674    
675            public final ReactorModelPool modelPool;
676    
677            InternalConfig( ProjectBuildingRequest request, ReactorModelPool modelPool )
678            {
679                this.request = request;
680                this.modelPool = modelPool;
681                session =
682                    LegacyLocalRepositoryManager.overlay( request.getLocalRepository(), request.getRepositorySession(),
683                                                          repoSystem );
684                repositories = RepositoryUtils.toRepos( request.getRemoteRepositories() );
685            }
686    
687        }
688    
689    }