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 1640245 2014-11-17 22:23:49Z hboutemy $
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( "NOTE: Your assembly specifies a dependencySet that matches multiple artifacts, but "
201                 + "specifies a concrete output format. THIS MAY RESULT IN ONE OR MORE ARTIFACTS BEING OBSCURED!\n\n"
202                 + "Output directory: '" + dir + "'\nOutput filename mapping: '" + mapping + "'" );
203         }
204     }
205 
206     private void addNormalArtifact( final DependencySet dependencySet, final Artifact depArtifact,
207                                     final MavenProject depProject, final Archiver archiver,
208                                     final AssemblerConfigurationSource configSource,
209                                     InputStreamTransformer fileSetTransformers )
210         throws AssemblyFormattingException, ArchiveCreationException
211     {
212         logger.debug( "Adding dependency artifact " + depArtifact.getId() + "." );
213 
214         final AddArtifactTask task = new AddArtifactTask( depArtifact, logger, fileSetTransformers );
215 
216         task.setProject( depProject );
217         task.setModuleProject( moduleProject );
218         task.setModuleArtifact( moduleArtifact );
219         task.setOutputDirectory( dependencySet.getOutputDirectory(), defaultOutputDirectory );
220         task.setFileNameMapping( dependencySet.getOutputFileNameMapping(), defaultOutputFileNameMapping );
221 
222         final int dirMode = TypeConversionUtils.modeToInt( dependencySet.getDirectoryMode(), logger );
223         if ( dirMode != -1 )
224         {
225             task.setDirectoryMode( dirMode );
226         }
227 
228         final int fileMode = TypeConversionUtils.modeToInt( dependencySet.getFileMode(), logger );
229         if ( fileMode != -1 )
230         {
231             task.setFileMode( fileMode );
232         }
233 
234         task.setUnpack( dependencySet.isUnpack() );
235 
236         final UnpackOptions opts = dependencySet.getUnpackOptions();
237         if ( dependencySet.isUnpack() && ( opts != null ) )
238         {
239             task.setIncludes( opts.getIncludes() );
240             task.setExcludes( opts.getExcludes() );
241         }
242 
243         task.execute( archiver, configSource );
244 
245     }
246 
247     private MavenProject buildProjectStub( final Artifact depArtifact )
248     {
249         final Model model = new Model();
250         model.setGroupId( depArtifact.getGroupId() );
251         model.setArtifactId( depArtifact.getArtifactId() );
252         model.setVersion( depArtifact.getBaseVersion() );
253         model.setPackaging( depArtifact.getType() );
254 
255         model.setDescription( "Stub for " + depArtifact.getId() );
256 
257         final MavenProject project = new MavenProject( model );
258         project.setArtifact( depArtifact );
259 
260         return project;
261     }
262 
263     Set<Artifact> resolveDependencyArtifacts( final DependencySet dependencySet )
264         throws InvalidAssemblerConfigurationException
265     {
266         final Set<Artifact> dependencyArtifacts = new LinkedHashSet<Artifact>();
267         if ( resolvedArtifacts != null )
268         {
269             dependencyArtifacts.addAll( resolvedArtifacts );
270         }
271 
272         if ( dependencySet.isUseProjectArtifact() )
273         {
274             final Artifact projectArtifact = project.getArtifact();
275             if ( ( projectArtifact != null ) && ( projectArtifact.getFile() != null ) )
276             {
277                 dependencyArtifacts.add( projectArtifact );
278             }
279             else
280             {
281                 logger.warn( "Cannot include project artifact: " + projectArtifact
282                                  + "; it doesn't have an associated file or directory." );
283             }
284         }
285 
286         if ( dependencySet.isUseProjectAttachments() )
287         {
288             @SuppressWarnings( "unchecked" ) final List<Artifact> attachments = project.getAttachedArtifacts();
289             if ( attachments != null )
290             {
291                 for ( final Artifact attachment : attachments )
292                 {
293                     if ( attachment.getFile() != null )
294                     {
295                         dependencyArtifacts.add( attachment );
296                     }
297                     else
298                     {
299                         logger.warn(
300                             "Cannot include attached artifact: " + project.getId() + " for project: " + project.getId()
301                                 + "; it doesn't have an associated file or directory." );
302                     }
303                 }
304             }
305         }
306 
307         if ( dependencySet.isUseTransitiveFiltering() )
308         {
309             logger.debug( "Filtering dependency artifacts USING transitive dependency path information." );
310         }
311         else
312         {
313             logger.debug( "Filtering dependency artifacts WITHOUT transitive dependency path information." );
314         }
315 
316         final ScopeArtifactFilter filter = new ScopeArtifactFilter( dependencySet.getScope() );
317 
318         FilterUtils.filterArtifacts( dependencyArtifacts, dependencySet.getIncludes(), dependencySet.getExcludes(),
319                                      dependencySet.isUseStrictFiltering(), dependencySet.isUseTransitiveFiltering(),
320                                      logger, filter );
321 
322         return dependencyArtifacts;
323     }
324 
325     private void addNonArchiveDependency( final Artifact depArtifact, final MavenProject depProject,
326                                           final DependencySet dependencySet, final Archiver archiver,
327                                           final AssemblerConfigurationSource configSource )
328         throws AssemblyFormattingException, ArchiveCreationException
329     {
330         final File source = depArtifact.getFile();
331 
332         String outputDirectory = dependencySet.getOutputDirectory();
333 
334         FixedStringSearchInterpolator moduleProjectInterpolator = moduleProjectInterpolator( moduleProject );
335         FixedStringSearchInterpolator artifactProjectInterpolator = artifactProjectInterpolator( depProject );
336         outputDirectory =
337             AssemblyFormatUtils.getOutputDirectory( outputDirectory, depProject.getBuild().getFinalName(), configSource,
338                                                     moduleProjectInterpolator, artifactProjectInterpolator );
339 
340         final String destName =
341             AssemblyFormatUtils.evaluateFileNameMapping( dependencySet.getOutputFileNameMapping(), depArtifact,
342                                                          configSource.getProject(), moduleArtifact, configSource,
343                                                          moduleProjectInterpolator, artifactProjectInterpolator );
344 
345         String target;
346 
347         // omit the last char if ends with / or \\
348         if ( outputDirectory.endsWith( "/" ) || outputDirectory.endsWith( "\\" ) )
349         {
350             target = outputDirectory + destName;
351         }
352         else
353         {
354             target = outputDirectory + "/" + destName;
355         }
356 
357         try
358         {
359             final int mode = TypeConversionUtils.modeToInt( dependencySet.getFileMode(), logger );
360             if ( mode > -1 )
361             {
362                 archiver.addFile( source, target, mode );
363             }
364             else
365             {
366                 archiver.addFile( source, target );
367             }
368         }
369         catch ( final ArchiverException e )
370         {
371             throw new ArchiveCreationException( "Error adding file to archive: " + e.getMessage(), e );
372         }
373     }
374 
375     public List<DependencySet> getDependencySets()
376     {
377         return dependencySets;
378     }
379 
380     public Logger getLogger()
381     {
382         return logger;
383     }
384 
385     public void setDefaultOutputDirectory( final String defaultOutputDirectory )
386     {
387         this.defaultOutputDirectory = defaultOutputDirectory;
388     }
389 
390     public void setDefaultOutputFileNameMapping( final String defaultOutputFileNameMapping )
391     {
392         this.defaultOutputFileNameMapping = defaultOutputFileNameMapping;
393     }
394 
395     public void setModuleProject( final MavenProject moduleProject )
396     {
397         this.moduleProject = moduleProject;
398     }
399 
400     public void setModuleArtifact( final Artifact moduleArtifact )
401     {
402         this.moduleArtifact = moduleArtifact;
403     }
404 }