View Javadoc
1   package org.apache.maven.plugin.assembly.archive.task;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import org.apache.maven.artifact.Artifact;
23  import org.apache.maven.model.Dependency;
24  import org.apache.maven.model.Model;
25  import org.apache.maven.plugin.assembly.AssemblerConfigurationSource;
26  import org.apache.maven.plugin.assembly.InvalidAssemblerConfigurationException;
27  import org.apache.maven.plugin.assembly.archive.ArchiveCreationException;
28  import org.apache.maven.plugin.assembly.format.AssemblyFormattingException;
29  import org.apache.maven.plugin.assembly.format.ReaderFormatter;
30  import org.apache.maven.plugin.assembly.model.DependencySet;
31  import org.apache.maven.plugin.assembly.model.UnpackOptions;
32  import org.apache.maven.plugin.assembly.utils.AssemblyFormatUtils;
33  import org.apache.maven.plugin.assembly.utils.FilterUtils;
34  import org.apache.maven.plugin.assembly.utils.TypeConversionUtils;
35  import org.apache.maven.project.MavenProject;
36  import org.apache.maven.project.MavenProjectBuilder;
37  import org.apache.maven.project.ProjectBuildingException;
38  import org.apache.maven.shared.artifact.filter.ScopeArtifactFilter;
39  import org.codehaus.plexus.archiver.Archiver;
40  import org.codehaus.plexus.archiver.ArchiverException;
41  import org.codehaus.plexus.components.io.functions.InputStreamTransformer;
42  import org.codehaus.plexus.interpolation.fixed.FixedStringSearchInterpolator;
43  import org.codehaus.plexus.logging.Logger;
44  
45  import java.io.File;
46  import java.util.ArrayList;
47  import java.util.Collections;
48  import java.util.LinkedHashSet;
49  import java.util.List;
50  import java.util.Set;
51  
52  import static org.apache.maven.plugin.assembly.utils.AssemblyFormatUtils.artifactProjectInterpolator;
53  import static org.apache.maven.plugin.assembly.utils.AssemblyFormatUtils.moduleProjectInterpolator;
54  
55  /**
56   * @version $Id: AddDependencySetsTask.java 1639422 2014-11-13 18:08:07Z krosenvold $
57   */
58  public class AddDependencySetsTask
59  {
60  
61      private static final List<String> NON_ARCHIVE_DEPENDENCY_TYPES;
62  
63      static
64      {
65          final List<String> nonArch = new ArrayList<String>();
66  
67          nonArch.add( "pom" );
68  
69          NON_ARCHIVE_DEPENDENCY_TYPES = Collections.unmodifiableList( nonArch );
70      }
71  
72      private final List<DependencySet> dependencySets;
73  
74      private final Logger logger;
75  
76      private final MavenProject project;
77  
78      private MavenProject moduleProject;
79  
80      private final MavenProjectBuilder projectBuilder;
81  
82      private String defaultOutputDirectory;
83  
84      private String defaultOutputFileNameMapping;
85  
86      private Artifact moduleArtifact;
87  
88      private final Set<Artifact> resolvedArtifacts;
89  
90  
91      public AddDependencySetsTask( final List<DependencySet> dependencySets, final Set<Artifact> resolvedArtifacts,
92                                    final MavenProject project, final MavenProjectBuilder projectBuilder,
93                                    final Logger logger )
94      {
95          this.dependencySets = dependencySets;
96          this.resolvedArtifacts = resolvedArtifacts;
97          this.project = project;
98          this.projectBuilder = projectBuilder;
99          this.logger = logger;
100     }
101 
102     public void execute( final Archiver archiver, final AssemblerConfigurationSource configSource )
103         throws ArchiveCreationException, AssemblyFormattingException, InvalidAssemblerConfigurationException
104     {
105         if ( ( dependencySets == null ) || dependencySets.isEmpty() )
106         {
107             logger.debug( "No dependency sets specified." );
108             return;
109         }
110 
111         @SuppressWarnings( "unchecked" ) final List<Dependency> deps = project.getDependencies();
112         if ( ( deps == null ) || deps.isEmpty() )
113         {
114             logger.debug( "Project " + project.getId() + " has no dependencies. Skipping dependency set addition." );
115         }
116 
117         for ( final DependencySet dependencySet : dependencySets )
118         {
119             addDependencySet( dependencySet, archiver, configSource );
120         }
121     }
122 
123     void addDependencySet( final DependencySet dependencySet, final Archiver archiver,
124                            final AssemblerConfigurationSource configSource )
125         throws AssemblyFormattingException, ArchiveCreationException, InvalidAssemblerConfigurationException
126     {
127         logger.debug( "Processing DependencySet (output=" + dependencySet.getOutputDirectory() + ")" );
128 
129         if ( !dependencySet.isUseTransitiveDependencies() && dependencySet.isUseTransitiveFiltering() )
130         {
131             logger.warn( "DependencySet has nonsensical configuration: useTransitiveDependencies == false "
132                              + "AND useTransitiveFiltering == true. Transitive filtering flag will be ignored." );
133         }
134 
135         final Set<Artifact> dependencyArtifacts = resolveDependencyArtifacts( dependencySet );
136 
137         final UnpackOptions opts = dependencySet.getUnpackOptions();
138         if ( dependencySet.isUnpack() && opts != null && ( opts.isFiltered() || opts.getLineEnding() != null ) )
139         {
140             // find out if we can just ditch this empty block
141         }
142         else if ( dependencyArtifacts.size() > 1 )
143         {
144             checkMultiArtifactOutputConfig( dependencySet );
145         }
146 
147         logger.debug( "Adding " + dependencyArtifacts.size() + " dependency artifacts." );
148 
149         InputStreamTransformer fileSetTransformers =
150             dependencySet.isUnpack() && dependencySet.getUnpackOptions() != null
151                 ? ReaderFormatter.getFileSetTransformers( configSource, dependencySet.getUnpackOptions().isFiltered(),
152                                                           dependencySet.getUnpackOptions().getLineEnding() )
153                 : null;
154 
155         for ( final Artifact depArtifact : dependencyArtifacts )
156         {
157             MavenProject depProject;
158             try
159             {
160                 depProject = projectBuilder.buildFromRepository( depArtifact, configSource.getRemoteRepositories(),
161                                                                  configSource.getLocalRepository() );
162             }
163             catch ( final ProjectBuildingException e )
164             {
165                 logger.debug(
166                     "Error retrieving POM of module-dependency: " + depArtifact.getId() + "; Reason: " + e.getMessage()
167                         + "\n\nBuilding stub project instance." );
168 
169                 depProject = buildProjectStub( depArtifact );
170             }
171 
172             if ( NON_ARCHIVE_DEPENDENCY_TYPES.contains( depArtifact.getType() ) )
173             {
174                 addNonArchiveDependency( depArtifact, depProject, dependencySet, archiver, configSource );
175             }
176             else
177             {
178                 addNormalArtifact( dependencySet, depArtifact, depProject, archiver, configSource,
179                                    fileSetTransformers );
180             }
181         }
182     }
183 
184     private void checkMultiArtifactOutputConfig( final DependencySet dependencySet )
185     {
186         String dir = dependencySet.getOutputDirectory();
187         if ( dir == null )
188         {
189             dir = defaultOutputDirectory;
190         }
191 
192         String mapping = dependencySet.getOutputFileNameMapping();
193         if ( mapping == null )
194         {
195             mapping = defaultOutputFileNameMapping;
196         }
197 
198         if ( ( dir == null || !dir.contains( "${" ) ) && ( mapping == null || !mapping.contains( "${" ) ) )
199         {
200             logger.warn(
201                 "NOTE: Your assembly specifies a dependencySet that matches multiple artifacts, but specifies a concrete output format. "
202                     + "THIS MAY RESULT IN ONE OR MORE ARTIFACTS BEING OBSCURED!\n\nOutput directory: '" + dir
203                     + "'\nOutput filename mapping: '" + mapping + "'" );
204         }
205     }
206 
207     private void addNormalArtifact( final DependencySet dependencySet, final Artifact depArtifact,
208                                     final MavenProject depProject, final Archiver archiver,
209                                     final AssemblerConfigurationSource configSource,
210                                     InputStreamTransformer fileSetTransformers )
211         throws AssemblyFormattingException, ArchiveCreationException
212     {
213         logger.debug( "Adding dependency artifact " + depArtifact.getId() + "." );
214 
215         final AddArtifactTask task = new AddArtifactTask( depArtifact, logger, fileSetTransformers );
216 
217         task.setProject( depProject );
218         task.setModuleProject( moduleProject );
219         task.setModuleArtifact( moduleArtifact );
220         task.setOutputDirectory( dependencySet.getOutputDirectory(), defaultOutputDirectory );
221         task.setFileNameMapping( dependencySet.getOutputFileNameMapping(), defaultOutputFileNameMapping );
222 
223         final int dirMode = TypeConversionUtils.modeToInt( dependencySet.getDirectoryMode(), logger );
224         if ( dirMode != -1 )
225         {
226             task.setDirectoryMode( dirMode );
227         }
228 
229         final int fileMode = TypeConversionUtils.modeToInt( dependencySet.getFileMode(), logger );
230         if ( fileMode != -1 )
231         {
232             task.setFileMode( fileMode );
233         }
234 
235         task.setUnpack( dependencySet.isUnpack() );
236 
237         final UnpackOptions opts = dependencySet.getUnpackOptions();
238         if ( dependencySet.isUnpack() && ( opts != null ) )
239         {
240             task.setIncludes( opts.getIncludes() );
241             task.setExcludes( opts.getExcludes() );
242         }
243 
244         task.execute( archiver, configSource );
245 
246     }
247 
248     private MavenProject buildProjectStub( final Artifact depArtifact )
249     {
250         final Model model = new Model();
251         model.setGroupId( depArtifact.getGroupId() );
252         model.setArtifactId( depArtifact.getArtifactId() );
253         model.setVersion( depArtifact.getBaseVersion() );
254         model.setPackaging( depArtifact.getType() );
255 
256         model.setDescription( "Stub for " + depArtifact.getId() );
257 
258         final MavenProject project = new MavenProject( model );
259         project.setArtifact( depArtifact );
260 
261         return project;
262     }
263 
264     Set<Artifact> resolveDependencyArtifacts( final DependencySet dependencySet )
265         throws InvalidAssemblerConfigurationException
266     {
267         final Set<Artifact> dependencyArtifacts = new LinkedHashSet<Artifact>();
268         if ( resolvedArtifacts != null )
269         {
270             dependencyArtifacts.addAll( resolvedArtifacts );
271         }
272 
273         if ( dependencySet.isUseProjectArtifact() )
274         {
275             final Artifact projectArtifact = project.getArtifact();
276             if ( ( projectArtifact != null ) && ( projectArtifact.getFile() != null ) )
277             {
278                 dependencyArtifacts.add( projectArtifact );
279             }
280             else
281             {
282                 logger.warn( "Cannot include project artifact: " + projectArtifact
283                                  + "; it doesn't have an associated file or directory." );
284             }
285         }
286 
287         if ( dependencySet.isUseProjectAttachments() )
288         {
289             @SuppressWarnings( "unchecked" ) final List<Artifact> attachments = project.getAttachedArtifacts();
290             if ( attachments != null )
291             {
292                 for ( final Artifact attachment : attachments )
293                 {
294                     if ( attachment.getFile() != null )
295                     {
296                         dependencyArtifacts.add( attachment );
297                     }
298                     else
299                     {
300                         logger.warn(
301                             "Cannot include attached artifact: " + project.getId() + " for project: " + project.getId()
302                                 + "; it doesn't have an associated file or directory." );
303                     }
304                 }
305             }
306         }
307 
308         if ( dependencySet.isUseTransitiveFiltering() )
309         {
310             logger.debug( "Filtering dependency artifacts USING transitive dependency path information." );
311         }
312         else
313         {
314             logger.debug( "Filtering dependency artifacts WITHOUT transitive dependency path information." );
315         }
316 
317         final ScopeArtifactFilter filter = new ScopeArtifactFilter( dependencySet.getScope() );
318 
319         FilterUtils.filterArtifacts( dependencyArtifacts, dependencySet.getIncludes(), dependencySet.getExcludes(),
320                                      dependencySet.isUseStrictFiltering(), dependencySet.isUseTransitiveFiltering(),
321                                      logger, filter );
322 
323         return dependencyArtifacts;
324     }
325 
326     private void addNonArchiveDependency( final Artifact depArtifact, final MavenProject depProject,
327                                           final DependencySet dependencySet, final Archiver archiver,
328                                           final AssemblerConfigurationSource configSource )
329         throws AssemblyFormattingException, ArchiveCreationException
330     {
331         final File source = depArtifact.getFile();
332 
333         String outputDirectory = dependencySet.getOutputDirectory();
334 
335         FixedStringSearchInterpolator moduleProjectInterpolator = moduleProjectInterpolator( moduleProject );
336         FixedStringSearchInterpolator artifactProjectInterpolator = artifactProjectInterpolator( depProject );
337         outputDirectory =
338             AssemblyFormatUtils.getOutputDirectory( outputDirectory, depProject.getBuild().getFinalName(), configSource,
339                                                     moduleProjectInterpolator, artifactProjectInterpolator );
340 
341         final String destName =
342             AssemblyFormatUtils.evaluateFileNameMapping( dependencySet.getOutputFileNameMapping(), depArtifact,
343                                                          configSource.getProject(), moduleArtifact, configSource,
344                                                          moduleProjectInterpolator, artifactProjectInterpolator );
345 
346         String target;
347 
348         // omit the last char if ends with / or \\
349         if ( outputDirectory.endsWith( "/" ) || outputDirectory.endsWith( "\\" ) )
350         {
351             target = outputDirectory + destName;
352         }
353         else
354         {
355             target = outputDirectory + "/" + destName;
356         }
357 
358         try
359         {
360             final int mode = TypeConversionUtils.modeToInt( dependencySet.getFileMode(), logger );
361             if ( mode > -1 )
362             {
363                 archiver.addFile( source, target, mode );
364             }
365             else
366             {
367                 archiver.addFile( source, target );
368             }
369         }
370         catch ( final ArchiverException e )
371         {
372             throw new ArchiveCreationException( "Error adding file to archive: " + e.getMessage(), e );
373         }
374     }
375 
376     public List<DependencySet> getDependencySets()
377     {
378         return dependencySets;
379     }
380 
381     public Logger getLogger()
382     {
383         return logger;
384     }
385 
386     public void setDefaultOutputDirectory( final String defaultOutputDirectory )
387     {
388         this.defaultOutputDirectory = defaultOutputDirectory;
389     }
390 
391     public void setDefaultOutputFileNameMapping( final String defaultOutputFileNameMapping )
392     {
393         this.defaultOutputFileNameMapping = defaultOutputFileNameMapping;
394     }
395 
396     public void setModuleProject( final MavenProject moduleProject )
397     {
398         this.moduleProject = moduleProject;
399     }
400 
401     public void setModuleArtifact( final Artifact moduleArtifact )
402     {
403         this.moduleArtifact = moduleArtifact;
404     }
405 }