View Javadoc
1   /*
2    * #%L
3    * Mojo's Maven plugin for Cobertura
4    * %%
5    * Copyright (C) 2005 - 2013 Codehaus
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   * #L%
19   */
20  package org.codehaus.mojo.cobertura;
21  
22  import org.apache.maven.artifact.Artifact;
23  import org.apache.maven.artifact.ArtifactUtils;
24  import org.apache.maven.artifact.factory.ArtifactFactory;
25  import org.apache.maven.artifact.handler.ArtifactHandler;
26  import org.apache.maven.plugin.MojoExecutionException;
27  import org.apache.maven.project.MavenProjectHelper;
28  import org.codehaus.mojo.cobertura.configuration.ConfigInstrumentation;
29  import org.codehaus.mojo.cobertura.tasks.InstrumentTask;
30  import org.codehaus.plexus.util.FileUtils;
31  import org.codehaus.plexus.util.IOUtil;
32  
33  import java.io.File;
34  import java.io.FileOutputStream;
35  import java.io.IOException;
36  import java.util.ArrayList;
37  import java.util.LinkedHashSet;
38  import java.util.List;
39  import java.util.Map;
40  import java.util.Properties;
41  import java.util.Set;
42  
43  /**
44   * Instrument the compiled classes.
45   *
46   * @author <a href="mailto:joakim@erdfelt.com">Joakim Erdfelt</a>
47   * @goal instrument
48   * @requiresDependencyResolution compile
49   */
50  public class CoberturaInstrumentMojo
51      extends AbstractCoberturaMojo
52  {
53      /**
54       * Artifact factory.
55       *
56       * @component
57       */
58      private ArtifactFactory factory;
59  
60      /**
61       * Specifies whether or not to attach the Cobertura artifact to the project.
62       *
63       * @parameter expression="${cobertura.attach}" default-value="false"
64       */
65      private boolean attach;
66  
67      /**
68       * Specifies the classifier to use for the attached ser artifact.
69       *
70       * @parameter expression="${cobertura.classifier}" default-value="cobertura"
71       */
72      private String classifier;
73  
74      /**
75       * Used for attaching the source JAR to the project.
76       *
77       * @component
78       */
79      private MavenProjectHelper projectHelper;
80  
81      /**
82       * The <a href="usage.html#Instrumentation">Instrumentation Configuration</a>.
83       *
84       * @parameter expression="${instrumentation}"
85       */
86      private ConfigInstrumentation instrumentation;
87  
88      /**
89       * Build up a command line from the parameters and run Cobertura to instrument the code.
90       *
91       * @throws MojoExecutionException
92       */
93      public void execute()
94          throws MojoExecutionException
95      {
96          if ( skipMojo() )
97          {
98              return;
99          }
100 
101         ArtifactHandler artifactHandler = getProject().getArtifact().getArtifactHandler();
102         if ( !"java".equals( artifactHandler.getLanguage() ) )
103         {
104             getLog().info(
105                 "Not executing cobertura:instrument as the project is not a Java classpath-capable package" );
106         }
107         else
108         {
109             File instrumentedDirectory =
110                 new File( getProject().getBuild().getDirectory(), "generated-classes/cobertura" );
111 
112             if ( !instrumentedDirectory.exists() )
113             {
114                 instrumentedDirectory.mkdirs();
115             }
116 
117             // ensure that instrumentation config is set here, not via maven plugin api @required attribute, as this is
118             // not a required object from the pom configuration's point of view.
119             if ( instrumentation == null )
120             {
121                 instrumentation = new ConfigInstrumentation();
122             }
123 
124             /* ensure that the default includes is set */
125             if ( instrumentation.getIncludes().isEmpty() )
126             {
127                 instrumentation.addInclude( "**/*.class" );
128             }
129 
130             File outputDirectory = new File( getProject().getBuild().getOutputDirectory() );
131             if ( !outputDirectory.exists() )
132             {
133                 outputDirectory.mkdirs();
134             }
135 
136             // Copy all of the classes into the instrumentation basedir.
137             try
138             {
139                 FileUtils.copyDirectoryStructure( outputDirectory, instrumentedDirectory );
140             }
141             catch ( IOException e )
142             {
143                 throw new MojoExecutionException( "Unable to prepare instrumentation directory.", e );
144             }
145 
146             instrumentation.setBasedir( instrumentedDirectory );
147 
148             // Cobertura requires an existing dir
149             if ( !getDataFile().getParentFile().exists() )
150             {
151                 getDataFile().getParentFile().mkdirs();
152             }
153 
154             // Execute the instrumentation task.
155             InstrumentTask task = new InstrumentTask();
156             setTaskDefaults( task );
157             List<Artifact> classpath = new ArrayList<Artifact>();
158             /* need project class path */
159             classpath.addAll( pluginClasspathList );
160             classpath.addAll( getProject().getArtifacts() );
161             task.setPluginClasspathList( classpath );
162             task.setConfig( instrumentation );
163             task.setDestinationDir( instrumentedDirectory );
164             task.setDataFile( getDataFile() );
165 
166             task.execute();
167 
168             addCoberturaDependenciesToTestClasspath();
169 
170             // Old, Broken way
171             System.setProperty( "net.sourceforge.cobertura.datafile", getDataFile().getPath() );
172 
173             /*
174              * New, Fixed way. See
175              * https://sourceforge.net/tracker/index.php?func=detail&aid=1543280&group_id=130558&atid=720017 for patch
176              * to Cobertura 1.8 that fixes the datafile location.
177              */
178             Properties props = new Properties();
179             props.setProperty( "net.sourceforge.cobertura.datafile", getDataFile().getPath() );
180 
181             File coberturaPropertiesFile = new File( instrumentedDirectory, "cobertura.properties" );
182             FileOutputStream fos = null;
183             try
184             {
185                 fos = new FileOutputStream( coberturaPropertiesFile );
186                 props.store( fos, "Generated by maven-cobertura-plugin for project " + getProject().getId() );
187             }
188             catch ( IOException e )
189             {
190                 throw new MojoExecutionException( "Unable to write cobertura.properties file.", e );
191             }
192             finally
193             {
194                 IOUtil.close( fos );
195             }
196 
197             // Set the instrumented classes to be the new output directory (for other plugins to pick up)
198             getProject().getBuild().setOutputDirectory( instrumentedDirectory.getPath() );
199             System.setProperty( "project.build.outputDirectory", instrumentedDirectory.getPath() );
200             attachCoberturaArtifactIfAppropriate();
201         }
202     }
203 
204     private void attachCoberturaArtifactIfAppropriate()
205     {
206         if ( attach )
207         {
208             if ( getDataFile().exists() )
209             {
210                 projectHelper.attachArtifact( getProject(), "ser", classifier, getDataFile() );
211             }
212             else
213             {
214                 getLog().info( "No cobertura ser file exists to include in the attached artifacts list." );
215             }
216         }
217         else
218         {
219             getLog().info( "NOT adding cobertura ser file to attached artifacts list." );
220         }
221     }
222 
223     /**
224      * We need to tweak our test classpath for cobertura.
225      *
226      * @throws MojoExecutionException
227      */
228     private void addCoberturaDependenciesToTestClasspath()
229         throws MojoExecutionException
230     {
231         Map<String, Artifact> pluginArtifactMap = ArtifactUtils.artifactMapByVersionlessId( pluginClasspathList );
232         Artifact coberturaArtifact = pluginArtifactMap.get( "net.sourceforge.cobertura:cobertura-runtime" );
233 
234         if ( coberturaArtifact == null )
235         {
236             getLog().error( "pluginArtifactMap: " + pluginArtifactMap );
237 
238             throw new MojoExecutionException( "Couldn't find 'cobertura' artifact in plugin dependencies" );
239         }
240 
241         coberturaArtifact = artifactScopeToProvided( coberturaArtifact );
242 
243         if ( this.getProject().getDependencyArtifacts() != null )
244         {
245             Set<Artifact> set = new LinkedHashSet<Artifact>( this.getProject().getDependencyArtifacts() );
246             set.add( coberturaArtifact );
247             this.getProject().setDependencyArtifacts( set );
248         }
249     }
250 
251     /**
252      * Use provided instead of just test, so it's available on both compile and test classpath (MCOBERTURA-26)
253      *
254      * @param artifact
255      * @return re-scoped artifact
256      */
257     private Artifact artifactScopeToProvided( Artifact artifact )
258     {
259         return factory.createArtifact( artifact.getGroupId(), artifact.getArtifactId(), artifact.getVersion(),
260                                        Artifact.SCOPE_PROVIDED, artifact.getType() );
261     }
262 }