View Javadoc
1   package org.apache.maven.plugin.cxx;
2   
3   /*
4    * Copyright (C) 2011-2016, Neticoa SAS France - Tous droits réservés.
5    * Author(s) : Franck Bonin, Neticoa SAS France
6    *
7    * Licensed under the Apache License, Version 2.0 (the "License");
8    * you may not use this file except in compliance with the License.
9    * You may obtain a copy of the License at
10   *
11   *     http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the License for the specific language governing permissions and
17   * limitations under the License.
18   *
19   */
20  
21  import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
22  
23  import java.io.ByteArrayInputStream;
24  import java.io.ByteArrayOutputStream;
25  import java.io.DataOutputStream;
26  import java.io.File;
27  import java.io.FileOutputStream;
28  import java.io.IOException;
29  import java.io.InputStream;
30  import java.io.OutputStream;
31  import java.io.UnsupportedEncodingException;
32  import java.util.ArrayList;
33  import java.util.Arrays;
34  import java.util.HashMap;
35  import java.util.Iterator;
36  import java.util.List;
37  import java.util.Map;
38  import java.util.Properties;
39  import java.util.regex.Matcher;
40  import java.util.regex.Pattern;
41  
42  import org.apache.commons.exec.CommandLine;
43  import org.apache.commons.exec.DefaultExecutor;
44  import org.apache.commons.exec.ExecuteException;
45  import org.apache.commons.exec.Executor;
46  import org.codehaus.plexus.util.StringUtils;
47  
48  import org.apache.maven.plugin.MojoExecutionException;
49  
50  /* Use FileSet and the FileManager provided in this project*/
51  import org.apache.maven.model.FileSet;
52  
53  import org.apache.maven.plugins.annotations.LifecyclePhase;
54  import org.apache.maven.plugins.annotations.Mojo;
55  import org.apache.maven.plugins.annotations.Parameter;
56  
57  import org.apache.maven.plugin.cxx.utils.ExecutorService;
58  import org.apache.maven.plugin.cxx.utils.FileSetManager;
59  
60  /**
61   * Goal which vera++ check sources.
62   *
63   * @author Franck Bonin 
64   */
65  @Mojo( name = "veraxx", defaultPhase = LifecyclePhase.TEST )
66  public class VeraxxMojo extends AbstractLaunchMojo
67  {
68      @Override
69      protected List<String> getArgsList()
70      {
71          return null;
72      }
73      
74      private int veraxxVersion = 0;
75      
76      @Override
77      protected void preExecute( Executor exec, CommandLine commandLine, Properties enviro ) throws MojoExecutionException
78      {
79          OutputStream outStream = /*System.out;*/new ByteArrayOutputStream();
80          OutputStream errStream = new ByteArrayOutputStream();
81  
82          CommandLine commandLineCheck = new CommandLine( getExecutable() );
83          Executor execCheck = new DefaultExecutor();
84          String[] args = parseCommandlineArgs( "--version" );
85          commandLineCheck.addArguments( args, false );
86          execCheck.setWorkingDirectory( exec.getWorkingDirectory() );
87          
88          getLog().info( "Executing command line: " + commandLineCheck );
89  
90          int res = 0;
91          try
92          {
93              res = ExecutorService.executeCommandLine( execCheck, commandLineCheck, enviro,
94                  outStream/*getOutputStreamOut()*/, errStream/*getOutputStreamErr()*/, getInputStream() );
95          }
96          catch ( ExecuteException e )
97          {
98              getLog().info( "Exec Exception while detecting Vera++ version."
99                  + " Assume old Vera++ v1.1.x (and less) output parsing style" );
100             getLog().info( "Vera++ err output is : " + errStream.toString() ) ;
101             veraxxVersion = 0;
102             /*throw new MojoExecutionException( "preExecute Command execution failed.", e );*/
103             return;
104         }
105         catch ( IOException e )
106         {
107             getLog().info( "Vera++ detected version is : " + outStream.toString() ) ;
108             getLog().info( "Vera++ err output is : " + errStream.toString() ) ;
109             // due to jdk8 bug :: https://bugs.openjdk.java.net/browse/JDK-8054565
110             // we use this dirty try/catch ...
111             // because this quick command line call can close the output stream before jvm does
112             getLog().info( "jvm " + System.getProperty( "java.version" )
113                 + " (8u11 - 9) workaround, ignoring a " + e.toString() + " during vera++ test command line." );
114             //throw new MojoExecutionException( "preExecute Command execution failed.", e );
115         }
116         
117         if ( isResultCodeAFailure( res ) )
118         {
119              getLog().info( "Vera++ returned a failure result code : " + res );
120             //throw new MojoExecutionException( "preExecute Result of " + commandLineCheck 
121             //    + " execution is: '" + res + "'." );
122         }
123         DefaultArtifactVersion newFormatMinVersion = new DefaultArtifactVersion( "1.2.0" );
124         DefaultArtifactVersion currentVeraVersion = new DefaultArtifactVersion( outStream.toString() );
125         
126         getLog().debug( "Vera++ detected version is : " + outStream.toString() ) ;
127         getLog().debug( "Vera++ version as ArtefactVersion is : " + currentVeraVersion.toString() );
128 
129         if ( currentVeraVersion.compareTo( newFormatMinVersion ) < 0 )
130         {
131             getLog().info( "Use old Vera++ v1.1.x (and less) output parsing style" );
132             veraxxVersion = 0;
133         }
134         else
135         {
136             getLog().info( "Use Vera++ v1.2.0 (and more) output parsing style" );
137             veraxxVersion = 1;
138         }
139     }
140 
141     /**
142      * Arguments for vera++ program. Shall be -nodup -showrules 
143      * 
144      */
145     @Parameter( property = "veraxx.args", defaultValue = "-nodup -showrules" )
146     private String commandArgs;
147     
148     @Override
149     protected String getCommandArgs()
150     {
151         String params = "- " + commandArgs + " ";
152         return params;
153     }
154     
155     /**
156      * The Report OutputFile Location.
157      * 
158      * @since 0.0.4
159      */
160     @Parameter( property = "veraxx.reportsfilePath", defaultValue = "${project.build.directory}/vera++-reports" )
161     private File reportsfileDir;
162 
163     /**
164      * The Report OutputFile name identifier.
165      * 
166      * @since 0.0.4
167      */
168     @Parameter( property = "veraxx.reportIdentifier", defaultValue = "" )
169     private String reportIdentifier;
170     
171     private String getReportFileName()
172     {
173         return "vera++-result-" + reportIdentifier + ".xml";
174     }
175     
176     @Override
177     protected OutputStream getOutputStreamErr()
178     {
179         String outputReportName = new String();
180         if ( reportsfileDir.isAbsolute() )
181         {
182             outputReportName = reportsfileDir.getAbsolutePath() + File.separator + getReportFileName();
183         }
184         else
185         {
186             outputReportName = basedir.getAbsolutePath() + File.separator + reportsfileDir.getPath()
187                 + File.separator + getReportFileName();
188         }
189         getLog().info( "Vera++ report location " + outputReportName );
190          
191         OutputStream output = System.err;
192         File file = new File( outputReportName );
193         try
194         {
195             new File( file.getParent() ).mkdirs();
196             file.createNewFile();
197             output = new FileOutputStream( file );
198         }
199         catch ( IOException e )
200         {
201             getLog().error( "Vera++ report redirected to stderr since " + outputReportName + " can't be opened" );
202             return output;
203         }
204 
205         final DataOutputStream out = new DataOutputStream( output );
206         
207         try
208         {
209             out.writeBytes( "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" );
210             out.writeBytes( "<checkstyle version=\"5.0\">\n" );
211         }
212         catch ( IOException e )
213         {
214             getLog().error( "Vera++ xml report write failure" );
215         }
216         
217         OutputStream outErrFilter = new OutputStream()
218         {
219             StringBuffer sb = new StringBuffer();
220             public void write( int b ) throws IOException
221             {
222                 if ( ( b == '\n' ) || ( b == '\r' ) )
223                 {
224                     transformCurrentLine();
225                     // cleanup for next line
226                     sb.delete( 0, sb.length() );
227                 }
228                 else 
229                 {
230                     sb.append( (char) b );
231                 }
232             }
233             
234             public void flush() throws IOException
235             {
236                 transformCurrentLine();
237                 getLog().debug( "Vera++ xml flush() called" );
238                 if ( !StringUtils.isEmpty( lastfile ) )
239                 {
240                     out.writeBytes( "\t</file>\n" );
241                 }
242                 out.writeBytes( "</checkstyle>\n" );
243                 out.flush();
244             }
245             
246             String lastfile;
247             private void transformCurrentLine()
248             {
249                 if ( sb.length() > 0 )
250                 {
251                     // parse current line
252                     
253                     // try to replace ' (RULENumber) ' with 'RULENumber:'
254                     String p = "^(.+) \\((.+)\\) (.+)$";
255                     Pattern pattern = Pattern.compile( p );
256                     Matcher matcher = pattern.matcher( sb );
257                     getLog().debug( "match " + sb + " on " + p );
258                     
259                     boolean bWinPath = false;
260                     if ( sb.charAt( 1 ) == ':' )
261                     {
262                         bWinPath = true;
263                         sb.setCharAt( 1, '_' );
264                     }
265                     
266                     if ( matcher.matches() )
267                     {
268                         String sLine = matcher.group( 1 ) + matcher.group( 2 ) + ":" + matcher.group( 3 );
269                         getLog().debug( "rebuild line = " + sLine );
270                         
271                         // extract informations
272                         pattern = Pattern.compile( ":" );
273                         String[] items = pattern.split( sLine );
274                         
275                         String file, line, rule, comment, severity;
276                         file = items.length > 0 ? items[0] : "";
277                         line = items.length > 1 ? items[1] : "";
278                         rule = items.length > 2 ? items[2] : "";
279                         comment = items.length > 3 ? items[3] : "";
280                         severity = "warning";
281                         
282                         if ( bWinPath )
283                         {
284                             StringBuilder s = new StringBuilder( file );
285                             s.setCharAt( 1, ':' );
286                             file = s.toString();
287                         }
288                         
289                         // output Xml errors
290                         try
291                         {
292                             // handle <file/> tags
293                             if ( !file.equals( lastfile ) )
294                             {
295                                 if ( !StringUtils.isEmpty( lastfile ) )
296                                 {
297                                     out.writeBytes( "\t</file>\n" );
298                                 }
299                                 out.writeBytes( "\t<file name=\"" + file + "\">\n" );
300                                 lastfile = file;
301                             }
302                             out.writeBytes( "\t\t<error line=\"" + line + "\" severity=\"" + severity 
303                                 + "\" message=\"" + comment + "\" source=\"" + rule + "\"/>\n" );
304                         }
305                         catch ( IOException e )
306                         {
307                             getLog().error( "Vera++ xml report write failure" );
308                         }
309                     }
310                 }
311             }
312         };
313         return outErrFilter;
314     }
315     
316     /**
317      *  Excludes files/folder from analysis (comma separated list of filter)
318      *
319      *  @since 0.0.5
320      */
321     @Parameter( property = "veraxx.excludes", defaultValue = "" )
322     private String excludes;
323      
324     /**
325      * Directory where vera++ should search for source files
326      * 
327      * @since 0.0.4
328      */
329     @Parameter()
330     private List sourceDirs = new ArrayList();
331     
332     @Override
333     protected InputStream getInputStream()
334     {
335         StringBuilder sourceListString = new StringBuilder();
336         Iterator it = sourceDirs.iterator();
337         while ( it.hasNext() )
338         {
339             FileSet afileSet = new FileSet();
340             String dir = it.next().toString();
341             afileSet.setDirectory( new File( dir ).getAbsolutePath() );
342             
343             afileSet.setIncludes( Arrays.asList( new String[]{"**/*.cpp", "**/*.h", "**/*.cxx", "**/*.hxx"} ) );
344             if ( StringUtils.isNotEmpty( excludes ) )
345             {
346                 afileSet.setExcludes( Arrays.asList( excludes.split( "," ) ) );
347             }
348             getLog().debug( "vera++ excludes are :" + Arrays.toString( afileSet.getExcludes().toArray() ) );
349             
350             FileSetManager aFileSetManager = new FileSetManager();
351             String[] found = aFileSetManager.getIncludedFiles( afileSet );
352             
353             for ( int i = 0; i < found.length; i++ )
354             {
355                 sourceListString.append( dir + File.separator + found[i] + "\n" );
356             }
357         }
358         InputStream is = System.in;
359         try
360         {
361             getLog().debug( "vera++ sources are :" + sourceListString );
362             is = new ByteArrayInputStream( sourceListString.toString().getBytes( "UTF-8" ) );
363         }
364         catch ( UnsupportedEncodingException e )
365         {
366             getLog().error( "vera++ source list from stdin failure" );
367         }
368         return is;
369     }
370 
371     @Override
372     protected String getExecutable()
373     {
374         return "vera++";
375     }
376 
377     /**
378      * Environment variables passed to vera++ program.
379      * 
380      * @since 0.0.4
381      */
382     @Parameter()
383     private Map environmentVariables = new HashMap();
384     
385     @Override
386     protected Map getMoreEnvironmentVariables()
387     {
388         return environmentVariables;
389     }
390 
391     @Override
392     protected List<String> getSuccesCode()
393     {
394         return null;
395     }
396 
397     /**
398      * The current working directory. Optional. If not specified, basedir will be used.
399      * 
400      * @since 0.0.4
401      */
402     @Parameter( property = "veraxx.workingdir" )
403     private File workingDir;
404     
405     @Override
406     protected File getWorkingDir()
407     {
408         if ( null == workingDir )
409         {
410             workingDir = new File( basedir.getPath() );
411         }
412         return workingDir;
413     }
414     
415     /**
416      * Set this to "true" to skip running tests, but still compile them. Its use is NOT RECOMMENDED, but quite
417      * convenient on occasion.
418      *
419      * @since 0.0.5
420      */
421     @Parameter( property = "skipTests", defaultValue = "false" )
422     protected boolean skipTests;
423     
424     /**
425      * Set this to "true" to bypass unit tests entirely. Its use is NOT RECOMMENDED, especially if you enable
426      * it using the "maven.test.skip" property, because maven.test.skip shall disables both running the tests
427      * and compiling the tests. Consider using the <code>skipTests</code> parameter instead.
428      *
429      * @since 0.0.5
430      */
431     @Parameter( property = "maven.test.skip", defaultValue = "false" )
432     protected boolean skip;
433     
434     @Override
435     protected boolean isSkip()
436     {
437         return skipTests || skip;
438     }
439 }