View Javadoc
1   package org.codehaus.mojo.natives.plugin;
2   
3   /*
4    * The MIT License
5    *
6    * Copyright (c) 2004, The Codehaus
7    *
8    * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
9    * associated documentation files (the "Software"), to deal in the Software without restriction,
10   * including without limitation the rights to use, copy, modify, merge, publish, distribute,
11   * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
12   * furnished to do so, subject to the following conditions:
13   *
14   * The above copyright notice and this permission notice shall be included in all copies or
15   * substantial portions of the Software.
16   *
17   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
18   * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19   * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
20   * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21   * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22   */
23  
24  import java.io.File;
25  import java.io.IOException;
26  import java.util.ArrayList;
27  import java.util.Iterator;
28  import java.util.List;
29  import java.util.Set;
30  
31  import org.apache.maven.artifact.Artifact;
32  import org.apache.maven.artifact.DefaultArtifact;
33  import org.apache.maven.artifact.factory.ArtifactFactory;
34  import org.apache.maven.plugin.MojoExecutionException;
35  import org.apache.maven.plugins.annotations.Component;
36  import org.apache.maven.plugins.annotations.LifecyclePhase;
37  import org.apache.maven.plugins.annotations.Mojo;
38  import org.apache.maven.plugins.annotations.Parameter;
39  import org.apache.maven.plugins.annotations.ResolutionScope;
40  import org.codehaus.mojo.natives.NativeBuildException;
41  import org.codehaus.mojo.natives.linker.Linker;
42  import org.codehaus.mojo.natives.linker.LinkerConfiguration;
43  import org.codehaus.mojo.natives.manager.LinkerManager;
44  import org.codehaus.mojo.natives.manager.NoSuchNativeProviderException;
45  import org.codehaus.plexus.util.FileUtils;
46  import org.codehaus.plexus.util.StringUtils;
47  
48  /**
49   * Link all previously built object and dependent library files into final build artifact
50   */
51  @Mojo(name = "link", defaultPhase = LifecyclePhase.PACKAGE, requiresDependencyResolution = ResolutionScope.COMPILE)
52  public class NativeLinkMojo
53      extends AbstractNativeMojo
54  {
55  
56      /**
57       * Override this property if permitted by compilerProvider
58       * @since 1.0-alpha-2
59       */
60      @Parameter(defaultValue = "generic", required = true)
61      private String compilerProvider;
62  
63      /**
64       * Default value is ${compilerProvider}
65       * @since 1.0-alpha-2
66       */
67      @Parameter
68      private String linkerProvider;
69  
70      /**
71       * Override this property if permitted by linkerProvider. Default to compilerType if not provided
72       * @since 1.0-alpha-2
73       */
74      @Parameter
75      private String linkerExecutable;
76  
77      /**
78       * Additional linker command options
79       * @since 1.0-alpha-2
80       */
81      @Parameter
82      private List linkerStartOptions;
83  
84      /**
85       * Additional linker command options
86       * @since 1.0-alpha-2
87       */
88      @Parameter
89      private List linkerMiddleOptions;
90  
91      /**
92       * Additional linker command options
93       * @since 1.0-alpha-2
94       */
95      @Parameter
96      private List linkerEndOptions;
97  
98      /**
99       * Option to reorder dependency list, each item has the format of ${groupId}:${artifactId}
100      * @since 1.0-alpha-2
101      */
102     @Parameter
103     private List linkingOrderLibs;
104 
105     /**
106      * Comma separated extension type to be installed/deployed. Use this option to deploy library file produced by dll
107      * build on windows
108      * @since 1.0-alpha-2
109      */
110     @Parameter(defaultValue = "")
111     private String linkerSecondaryOutputExtensions;
112 
113     /**
114      * Where to place the final packaging
115      * @since 1.0-alpha-2
116      */
117     @Parameter(defaultValue = "${project.build.directory}", required = true)
118     protected File linkerOutputDirectory;
119 
120     /**
121      * The name of the generated file
122      * @since 1.0-alpha-8
123      */
124     @Parameter(defaultValue = "${project.build.finalName}", required = true)
125     private String linkerFinalName;
126 
127     /**
128      * The extension of the generated file. Unless specified, the extension of the main project
129      * artifact is used.
130      * @since 1.0-alpha-9
131      */
132     @Parameter(defaultValue = "${project.artifact.artifactHandler.extension}", required = true)
133     private String linkerFinalNameExt;
134 
135     /**
136      * Internal
137      * @since 1.0-alpha-2
138      */
139     @Component
140     private LinkerManager manager;
141 
142     /**
143      * Internal
144      * @since 1.0-alpha-2
145      */
146     @Component
147     private ArtifactFactory artifactFactory;
148 
149     /**
150      * Dependent libraries with version + classifier removed are copied to this directory to be linked to the build
151      * artifact
152      */
153     @Parameter(defaultValue = "${project.build.directory}/lib", required = true)
154     private File externalLibDirectory;
155 
156     /**
157      * Option to install primary artifact as a classifier, useful to install/deploy debug artifacts
158      * @since 1.0-alpha-2
159      */
160     @Parameter
161     private String classifier;
162 
163     /**
164      * Attach the linker's outputs to maven project be installed/deployed. Turn this off if you have other mean of
165      * deployment, for example using maven-assembly-plugin to deploy your own bundle
166      * @since 1.0-alpha-2
167      */
168     @Parameter(defaultValue = "true")
169     private boolean attach;
170 
171     /**
172      * For project with lots of object files on windows, turn this flag to resolve Windows commandline length limit
173      * @since 1.0-alpha-7
174      */
175     @Parameter(defaultValue = "false")
176     private boolean usingLinkerResponseFile;
177 
178     /**
179      * Enable this option to speed up linkage for large project with no dependencies changes
180      * @since 1.0-alpha-8
181      */
182     @Parameter(defaultValue = "false")
183     private boolean checkStaleLinkage;
184 
185     public void execute()
186         throws MojoExecutionException
187     {
188 
189         if ( StringUtils.isEmpty( this.classifier ) )
190         {
191             this.classifier = null;
192         }
193 
194         Linker linker = this.getLinker();
195 
196         this.config = this.createLinkerConfiguration();
197 
198         try
199         {
200             List allCompilerOuputFiles = this.getAllCompilersOutputFileList();
201 
202             File outputFile = linker.link( config, allCompilerOuputFiles );
203 
204             // to be used by post linker mojo like native:manifest
205             this.getPluginContext().put( AbstractNativeMojo.LINKER_OUTPUT_PATH, outputFile );
206 
207         }
208         catch ( IOException ioe )
209         {
210             throw new MojoExecutionException( ioe.getMessage(), ioe );
211         }
212         catch ( NativeBuildException nbe )
213         {
214             throw new MojoExecutionException( nbe.getMessage(), nbe );
215         }
216 
217         if ( this.attach )
218         {
219             this.attachPrimaryArtifact();
220 
221             this.attachSecondaryArtifacts();
222         }
223     }
224 
225     private LinkerConfiguration createLinkerConfiguration()
226         throws MojoExecutionException
227     {
228         LinkerConfiguration config = new LinkerConfiguration();
229         config.setWorkingDirectory( this.workingDirectory );
230         config.setExecutable( this.linkerExecutable );
231         config.setStartOptions( removeEmptyOptions( this.linkerStartOptions ) );
232         config.setMiddleOptions( removeEmptyOptions( this.linkerMiddleOptions ) );
233         config.setEndOptions( removeEmptyOptions( this.linkerEndOptions ) );
234         config.setOutputDirectory( this.linkerOutputDirectory );
235         config.setOutputFileName( this.linkerFinalName );
236         config.setOutputFileExtension( this.linkerFinalNameExt );
237         config.setExternalLibDirectory( this.externalLibDirectory );
238         config.setExternalLibFileNames( this.getLibFileNames() );
239         config.setEnvFactory( this.getEnvFactory() );
240         config.setUsingLinkerResponseFile( usingLinkerResponseFile );
241         config.setCheckStaleLinkage( this.checkStaleLinkage );
242 
243         return config;
244     }
245 
246     private Linker getLinker()
247         throws MojoExecutionException
248     {
249         Linker linker;
250 
251         try
252         {
253             if ( this.linkerProvider == null )
254             {
255                 this.linkerProvider = this.compilerProvider;
256             }
257 
258             linker = this.manager.getLinker( this.linkerProvider );
259         }
260         catch ( NoSuchNativeProviderException pe )
261         {
262             throw new MojoExecutionException( pe.getMessage() );
263         }
264 
265         return linker;
266     }
267 
268     /**
269      *
270      */
271     private void attachPrimaryArtifact()
272     {
273         Artifact artifact = this.project.getArtifact();
274 
275         if ( null == this.classifier )
276         {
277             artifact.setFile( new File( this.linkerOutputDirectory + "/" + this.project.getBuild().getFinalName() + "."
278                 + this.project.getArtifact().getArtifactHandler().getExtension() ) );
279         }
280         else
281         {
282             // install primary artifact as a classifier
283 
284             DefaultArtifact clone =
285                 new DefaultArtifact( artifact.getGroupId(), artifact.getArtifactId(),
286                                      artifact.getVersionRange().cloneOf(), artifact.getScope(), artifact.getType(),
287                                      classifier, artifact.getArtifactHandler(), artifact.isOptional() );
288 
289             clone.setRelease( artifact.isRelease() );
290             clone.setResolvedVersion( artifact.getVersion() );
291             clone.setResolved( artifact.isResolved() );
292             clone.setFile( artifact.getFile() );
293 
294             if ( artifact.getAvailableVersions() != null )
295             {
296                 clone.setAvailableVersions( new ArrayList( artifact.getAvailableVersions() ) );
297             }
298 
299             clone.setBaseVersion( artifact.getBaseVersion() );
300             clone.setDependencyFilter( artifact.getDependencyFilter() );
301 
302             if ( artifact.getDependencyTrail() != null )
303             {
304                 clone.setDependencyTrail( new ArrayList( artifact.getDependencyTrail() ) );
305             }
306 
307             clone.setDownloadUrl( artifact.getDownloadUrl() );
308             clone.setRepository( artifact.getRepository() );
309 
310             clone.setFile( new File( this.linkerOutputDirectory + "/" + this.project.getBuild().getFinalName() + "."
311                 + this.project.getArtifact().getArtifactHandler().getExtension() ) );
312 
313             project.setArtifact( clone );
314         }
315     }
316 
317     private void attachSecondaryArtifacts()
318     {
319         final String[] tokens;
320         if(this.linkerSecondaryOutputExtensions != null) {
321             tokens = StringUtils.split( this.linkerSecondaryOutputExtensions, "," );
322         } else {
323             tokens = new String[0];
324         }
325 
326         for ( int i = 0; i < tokens.length; ++i )
327         {
328             // TODO: shouldn't need classifier
329             Artifact artifact =
330                 artifactFactory.createArtifact( project.getGroupId(), project.getArtifactId(), project.getVersion(),
331                                                 this.classifier, tokens[i].trim() );
332             artifact.setFile( new File( this.linkerOutputDirectory + "/" + this.project.getBuild().getFinalName() + "."
333                 + tokens[i].trim() ) );
334 
335             project.addAttachedArtifact( artifact );
336         }
337 
338     }
339 
340     private List getLibFileNames()
341         throws MojoExecutionException
342     {
343         List libList = new ArrayList();
344 
345         Set artifacts = this.project.getArtifacts();
346 
347         for ( Iterator iter = artifacts.iterator(); iter.hasNext(); )
348         {
349             Artifact artifact = (Artifact) iter.next();
350 
351             if ( INCZIP_TYPE.equals( artifact.getType() ) )
352             {
353                 continue;
354             }
355 
356             String libFileName = FileUtils.filename( this.getDependencyFile( artifact, true ).getPath() );
357 
358             libList.add( libFileName );
359         }
360 
361         libList = this.reorderLibDependencies( libList );
362 
363         return libList;
364     }
365 
366     /**
367      * convert dependencyLinkingOrders to a file list
368      *
369      * @return
370      */
371     private List getDependenciesFileOrderList()
372         throws MojoExecutionException
373     {
374         List list = new ArrayList();
375 
376         if ( this.linkingOrderLibs != null )
377         {
378             for ( Iterator i = linkingOrderLibs.iterator(); i.hasNext(); )
379             {
380                 String element = i.next().toString();
381 
382                 Artifact artifact = lookupDependencyUsingGroupArtifactIdPair( element );
383 
384                 if ( artifact != null )
385                 {
386                     String libFileName = FileUtils.filename( this.getDependencyFile( artifact, false ).getPath() );
387 
388                     list.add( libFileName );
389                 }
390                 else
391                 {
392                     throw new MojoExecutionException( element + " not found on project dependencies." );
393                 }
394             }
395         }
396 
397         return list;
398     }
399 
400     /**
401      * Look up library in dependency list using groupId:artifactId key Note: we can not use project.artifactMap due the
402      * introduction of inczip dependency where 2 dependency with the same artifactId and groupId, but differs by
403      * extension type make the map not suitable for lookup
404      *
405      * @param groupArtifactIdPair
406      * @return
407      * @throws MojoExecutionException
408      */
409     private Artifact lookupDependencyUsingGroupArtifactIdPair( String groupArtifactIdPair )
410         throws MojoExecutionException
411     {
412         String[] tokens = StringUtils.split( groupArtifactIdPair, ":" );
413 
414         if ( tokens.length != 2 )
415         {
416             throw new MojoExecutionException( "Invalid groupId and artifactId pair: " + groupArtifactIdPair );
417         }
418 
419         Set allDependencyArtifacts = project.getDependencyArtifacts();
420 
421         for ( Iterator iter = allDependencyArtifacts.iterator(); iter.hasNext(); )
422         {
423             Artifact artifact = (Artifact) iter.next();
424             if ( INCZIP_TYPE.equals( artifact.getType() ) )
425             {
426                 continue;
427             }
428 
429             if ( tokens[0].equals( artifact.getGroupId() ) && tokens[1].equals( artifact.getArtifactId() ) )
430             {
431                 return artifact;
432             }
433         }
434 
435         return null;
436 
437     }
438 
439     private List reorderLibDependencies( List libs )
440         throws MojoExecutionException
441     {
442         List requestedOrderList = getDependenciesFileOrderList();
443 
444         if ( requestedOrderList.size() != 0 )
445         {
446             // remove from original list first
447             for ( Iterator i = requestedOrderList.iterator(); i.hasNext(); )
448             {
449                 libs.remove( i.next() );
450             }
451 
452             for ( Iterator i = libs.iterator(); i.hasNext(); )
453             {
454                 requestedOrderList.add( i.next() );
455             }
456 
457             return requestedOrderList;
458         }
459         else
460         {
461             return libs;
462         }
463     }
464 
465     private File getDependencyFile( Artifact artifact, boolean doCopy )
466         throws MojoExecutionException
467     {
468 
469         File newLocation =
470             new File( this.externalLibDirectory, artifact.getArtifactId() + "."
471                 + artifact.getArtifactHandler().getExtension() );
472 
473         try
474         {
475             if ( doCopy && !artifact.getFile().isDirectory()
476                 && ( !newLocation.exists() || newLocation.lastModified() <= artifact.getFile().lastModified() ) )
477             {
478                 FileUtils.copyFile( artifact.getFile(), newLocation );
479             }
480         }
481         catch ( IOException ioe )
482         {
483             throw new MojoExecutionException( "Unable to copy dependency to staging area.  Could not copy "
484                 + artifact.getFile() + " to " + newLocation, ioe );
485         }
486 
487         return newLocation;
488     }
489 
490     // //////////////////////////////////// UNIT TEST HELPERS //////////////////////////////////
491 
492     /**
493      * For unit test only
494      */
495     private LinkerConfiguration config;
496 
497     protected LinkerConfiguration getLgetLinkerConfiguration()
498     {
499         return this.config;
500     }
501 
502 }