001package 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
022import java.io.File;
023import java.io.IOException;
024import java.util.ArrayList;
025import java.util.Arrays;
026import java.util.Collections;
027import java.util.HashMap;
028import java.util.LinkedHashSet;
029import java.util.List;
030import java.util.Map;
031import java.util.Set;
032
033import org.apache.maven.RepositoryUtils;
034import org.apache.maven.artifact.Artifact;
035import org.apache.maven.artifact.repository.LegacyLocalRepositoryManager;
036import org.apache.maven.model.Build;
037import org.apache.maven.model.Model;
038import org.apache.maven.model.Profile;
039import org.apache.maven.model.building.DefaultModelBuildingRequest;
040import org.apache.maven.model.building.DefaultModelProblem;
041import org.apache.maven.model.building.FileModelSource;
042import org.apache.maven.model.building.ModelBuilder;
043import org.apache.maven.model.building.ModelBuildingException;
044import org.apache.maven.model.building.ModelBuildingRequest;
045import org.apache.maven.model.building.ModelBuildingResult;
046import org.apache.maven.model.building.ModelProblem;
047import org.apache.maven.model.building.ModelProcessor;
048import org.apache.maven.model.building.ModelSource;
049import org.apache.maven.model.building.StringModelSource;
050import org.apache.maven.model.resolution.ModelResolver;
051import org.apache.maven.repository.RepositorySystem;
052import org.apache.maven.repository.internal.ArtifactDescriptorUtils;
053import org.codehaus.plexus.component.annotations.Component;
054import org.codehaus.plexus.component.annotations.Requirement;
055import org.codehaus.plexus.logging.Logger;
056import org.codehaus.plexus.util.Os;
057import org.codehaus.plexus.util.StringUtils;
058import org.eclipse.aether.RepositorySystemSession;
059import org.eclipse.aether.RequestTrace;
060import org.eclipse.aether.impl.RemoteRepositoryManager;
061import org.eclipse.aether.repository.LocalRepositoryManager;
062import org.eclipse.aether.repository.RemoteRepository;
063import org.eclipse.aether.repository.WorkspaceRepository;
064import org.eclipse.aether.resolution.ArtifactRequest;
065import org.eclipse.aether.resolution.ArtifactResult;
066
067/**
068 */
069@Component( role = ProjectBuilder.class )
070public 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}