All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.scoverage.plugin.SCoveragePreCompileMojo Maven / Gradle / Ivy

/*
 * Copyright 2014-2019 Grzegorz Slowikowski (gslowikowski at gmail dot com)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package org.scoverage.plugin;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.factory.ArtifactFactory;
import org.apache.maven.artifact.repository.ArtifactRepository;
import org.apache.maven.artifact.resolver.ArtifactNotFoundException;
import org.apache.maven.artifact.resolver.ArtifactResolutionException;
import org.apache.maven.artifact.resolver.ArtifactResolver;
import org.apache.maven.model.Dependency;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugins.annotations.Component;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.project.MavenProject;

import org.codehaus.plexus.util.StringUtils;

/**
 * Configures project for compilation with SCoverage instrumentation.
 * 
*
* Supported compiler plugins: * *
* This is internal mojo, executed in forked {@code scoverage} life cycle. *
* * @author Grzegorz Slowikowski * @since 1.0.0 */ @Mojo( name = "pre-compile", defaultPhase = LifecyclePhase.GENERATE_RESOURCES ) public class SCoveragePreCompileMojo extends AbstractMojo { /** * Allows SCoverage to be skipped. *
* * @since 1.0.0 */ @Parameter( property = "scoverage.skip", defaultValue = "false" ) private boolean skip; /** * Scala version used for compiler plugin artifact resolution. *
    *
  • if specified, and equals {@code 2.10} or starts with {@code 2.10.} - {@code scalac-scoverage-plugin_2.10} will be used
  • *
  • if specified, and equals {@code 2.11} or starts with {@code 2.11.} - {@code scalac-scoverage-plugin_2.11} will be used
  • *
  • if specified, and equals {@code 2.12} or starts with {@code 2.12.} - {@code scalac-scoverage-plugin_2.12} will be used
  • *
  • if specified, and equals {@code 2.13.0-M5} - {@code scalac-scoverage-plugin_2.13.0-M5} will be used
  • *
  • if specified, but does not meet any of the above conditions or if not specified - plugin execution will be skipped
  • *
* * @since 1.0.0 */ @Parameter( property = "scala.version" ) private String scalaVersion; /** * Directory where the coverage files should be written. *
* * @since 1.0.0 */ @Parameter( property = "scoverage.dataDirectory", defaultValue = "${project.build.directory}/scoverage-data", required = true, readonly = true ) private File dataDirectory; /** * Semicolon-separated list of regular expressions for packages to exclude, "(empty)" for default package. *
*
* Example: *
* {@code (empty);Reverse.*;.*AuthService.*;models\.data\..*} *
*
* See https://github.com/scoverage/sbt-scoverage#exclude-classes-and-packages for additional documentation. *
* * @since 1.0.0 */ @Parameter( property = "scoverage.excludedPackages", defaultValue = "" ) private String excludedPackages; /** * Semicolon-separated list of regular expressions for source paths to exclude. *
* * @since 1.0.0 */ @Parameter( property = "scoverage.excludedFiles", defaultValue = "" ) private String excludedFiles; /** * See https://github.com/scoverage/sbt-scoverage#highlighting. *
* * @since 1.0.0 */ @Parameter( property = "scoverage.highlighting", defaultValue = "false" ) private boolean highlighting; /** * Force scalac-scoverage-plugin version used. *
* * @since 1.0.0 */ @Parameter( property = "scoverage.scalacPluginVersion", defaultValue = "" ) private String scalacPluginVersion; /** * Semicolon-separated list of project properties set in forked {@code scoverage} life cycle. *
*
* Example: *
* {@code prop1=val1;prop2=val2;prop3=val3} *
* * @since 1.4.0 */ @Parameter( property = "scoverage.additionalForkedProjectProperties", defaultValue = "" ) private String additionalForkedProjectProperties; /** * Maven project to interact with. */ @Parameter( defaultValue = "${project}", readonly = true, required = true ) private MavenProject project; /** * All Maven projects in the reactor. */ @Parameter( defaultValue = "${reactorProjects}", required = true, readonly = true ) private List reactorProjects; /** * Artifact factory used to look up artifacts in the remote repository. */ @Component private ArtifactFactory factory; /** * Artifact resolver used to resolve artifacts. */ @Component private ArtifactResolver resolver; /** * Location of the local repository. */ @Parameter( property = "localRepository", readonly = true, required = true ) private ArtifactRepository localRepo; /** * Remote repositories used by the resolver */ @Parameter( property = "project.remoteArtifactRepositories", readonly = true, required = true ) private List remoteRepos; /** * List of artifacts this plugin depends on. */ @Parameter( property = "plugin.artifacts", readonly = true, required = true ) private List pluginArtifacts; /** * Configures project for compilation with SCoverage instrumentation. * * @throws MojoExecutionException if unexpected problem occurs */ @Override public void execute() throws MojoExecutionException { if ( "pom".equals( project.getPackaging() ) ) { getLog().info( "Skipping SCoverage execution for project with packaging type 'pom'" ); //for aggragetor mojo - list of submodules: List modules = project.getCollectedProjects(); return; } if ( skip ) { getLog().info( "Skipping Scoverage execution" ); Properties projectProperties = project.getProperties(); // for maven-resources-plugin (testResources), maven-compiler-plugin (testCompile), // sbt-compiler-maven-plugin (testCompile), scala-maven-plugin (testCompile), // maven-surefire-plugin and scalatest-maven-plugin setProperty( projectProperties, "maven.test.skip", "true" ); // for scalatest-maven-plugin and specs2-maven-plugin setProperty( projectProperties, "skipTests", "true" ); return; } long ts = System.currentTimeMillis(); String scalaBinaryVersion = null; String resolvedScalaVersion = resolveScalaVersion(); if ( resolvedScalaVersion != null ) { if ( "2.10".equals( resolvedScalaVersion ) || resolvedScalaVersion.startsWith( "2.10." ) ) { scalaBinaryVersion = "2.10"; } else if ( "2.11".equals( resolvedScalaVersion ) || resolvedScalaVersion.startsWith( "2.11." ) ) { scalaBinaryVersion = "2.11"; } else if ( "2.12".equals( resolvedScalaVersion ) || resolvedScalaVersion.startsWith( "2.12." ) ) { scalaBinaryVersion = "2.12"; } else if ( "2.13.0-M5".equals( resolvedScalaVersion ) ) { scalaBinaryVersion = "2.13.0-M5"; } else { getLog().warn( String.format( "Skipping SCoverage execution - unsupported Scala version \"%s\"", resolvedScalaVersion ) ); return; } } else { getLog().warn( "Skipping SCoverage execution - Scala version not set" ); return; } Map additionalProjectPropertiesMap = null; if ( additionalForkedProjectProperties != null && !additionalForkedProjectProperties.isEmpty() ) { String[] props = additionalForkedProjectProperties.split( ";" ); additionalProjectPropertiesMap = new HashMap( props.length ); for ( String propVal: props ) { String[] tmp = propVal.split( "=", 2 ); if ( tmp.length == 2 ) { String propName = tmp[ 0 ].trim(); String propValue = tmp[ 1 ].trim(); additionalProjectPropertiesMap.put( propName, propValue ); } else { getLog().warn( String.format( "Skipping invalid additional forked project property \"%s\", must be in \"key=value\" format", propVal ) ); } } } SCoverageForkedLifecycleConfigurator.afterForkedLifecycleEnter( project, reactorProjects, additionalProjectPropertiesMap ); try { Artifact pluginArtifact = getScalaScoveragePluginArtifact( scalaBinaryVersion ); Artifact runtimeArtifact = getScalaScoverageRuntimeArtifact( scalaBinaryVersion ); if ( pluginArtifact == null ) { return; // scoverage plugin will not be configured } addScoverageDependenciesToClasspath( runtimeArtifact ); String arg = DATA_DIR_OPTION + dataDirectory.getAbsolutePath(); String _scalacOptions = quoteArgument( arg ); String addScalacArgs = arg; if ( !StringUtils.isEmpty( excludedPackages ) ) { arg = EXCLUDED_PACKAGES_OPTION + excludedPackages.replace( "(empty)", "" ); _scalacOptions = _scalacOptions + SPACE + quoteArgument( arg ); addScalacArgs = addScalacArgs + PIPE + arg; } if ( !StringUtils.isEmpty( excludedFiles ) ) { arg = EXCLUDED_FILES_OPTION + excludedFiles; _scalacOptions = _scalacOptions + SPACE + quoteArgument( arg ); addScalacArgs = addScalacArgs + PIPE + arg; } if ( highlighting ) { _scalacOptions = _scalacOptions + SPACE + "-Yrangepos"; addScalacArgs = addScalacArgs + PIPE + "-Yrangepos"; } String _scalacPlugins = String.format( "%s:%s:%s", pluginArtifact.getGroupId(), pluginArtifact.getArtifactId(), pluginArtifact.getVersion() ); arg = PLUGIN_OPTION + pluginArtifact.getFile().getAbsolutePath(); addScalacArgs = addScalacArgs + PIPE + arg; Properties projectProperties = project.getProperties(); // for sbt-compiler-maven-plugin (version 1.0.0-beta5+) setProperty( projectProperties, "sbt._scalacOptions", _scalacOptions ); // for sbt-compiler-maven-plugin (version 1.0.0-beta5+) setProperty( projectProperties, "sbt._scalacPlugins", _scalacPlugins ); // for scala-maven-plugin (version 3.0.0+) setProperty( projectProperties, "addScalacArgs", addScalacArgs ); // for scala-maven-plugin (version 3.1.0+) setProperty( projectProperties, "analysisCacheFile", "${project.build.directory}/scoverage-analysis/compile" ); // for maven-surefire-plugin and scalatest-maven-plugin setProperty( projectProperties, "maven.test.failure.ignore", "true" ); // for maven-jar-plugin // VERY IMPORTANT! Prevents from overwriting regular project artifact file // with instrumented one during "integration-check" or "integration-report" execution. project.getBuild().setFinalName( "scoverage-" + project.getBuild().getFinalName() ); saveSourceRootsToFile(); } catch ( ArtifactNotFoundException e ) { throw new MojoExecutionException( "SCoverage preparation failed", e ); } catch ( ArtifactResolutionException e ) { throw new MojoExecutionException( "SCoverage preparation failed", e ); } catch ( IOException e ) { throw new MojoExecutionException( "SCoverage preparation failed", e ); } long te = System.currentTimeMillis(); getLog().debug( String.format( "Mojo execution time: %d ms", te - ts ) ); } // Private utility methods private static final String SCALA_LIBRARY_GROUP_ID = "org.scala-lang"; private static final String SCALA_LIBRARY_ARTIFACT_ID = "scala-library"; private static final String DATA_DIR_OPTION = "-P:scoverage:dataDir:"; private static final String EXCLUDED_PACKAGES_OPTION = "-P:scoverage:excludedPackages:"; private static final String EXCLUDED_FILES_OPTION = "-P:scoverage:excludedFiles:"; private static final String PLUGIN_OPTION = "-Xplugin:"; private static final char DOUBLE_QUOTE = '\"'; private static final char SPACE = ' '; private static final char PIPE = '|'; private String quoteArgument( String arg ) { return arg.indexOf( SPACE ) >= 0 ? DOUBLE_QUOTE + arg + DOUBLE_QUOTE : arg; } private String resolveScalaVersion() { String result = scalaVersion; if ( result == null ) { // check project direct dependencies (transitive dependencies cannot be checked in this Maven lifecycle phase) @SuppressWarnings( "unchecked" ) List dependencies = project.getDependencies(); for ( Dependency dependency: dependencies ) { if ( SCALA_LIBRARY_GROUP_ID.equals( dependency.getGroupId() ) && SCALA_LIBRARY_ARTIFACT_ID.equals( dependency.getArtifactId() ) ) { result = dependency.getVersion(); break; } } } return result; } private void setProperty( Properties projectProperties, String propertyName, String newValue ) { if ( projectProperties.containsKey( propertyName ) ) { String oldValue = projectProperties.getProperty( propertyName ); projectProperties.put( "scoverage.backup." + propertyName, oldValue ); } else { projectProperties.remove( "scoverage.backup." + propertyName ); } if ( newValue != null ) { projectProperties.put( propertyName, newValue ); } else { projectProperties.remove( propertyName ); } } private Artifact getScalaScoveragePluginArtifact( String scalaMainVersion ) throws ArtifactNotFoundException, ArtifactResolutionException { Artifact result = null; String resolvedScalacPluginVersion = scalacPluginVersion; if ( resolvedScalacPluginVersion == null || "".equals( resolvedScalacPluginVersion ) ) { for ( Artifact artifact : pluginArtifacts ) { if ( "org.scoverage".equals( artifact.getGroupId() ) && "scalac-scoverage-plugin_2.12".equals( artifact.getArtifactId() ) ) { if ( "2.12".equals( scalaMainVersion ) ) { return artifact; // shortcut, use the same artifact plugin uses } resolvedScalacPluginVersion = artifact.getVersion(); break; } } } result = getResolvedArtifact( "org.scoverage", "scalac-scoverage-plugin_" + scalaMainVersion, resolvedScalacPluginVersion ); return result; } private Artifact getScalaScoverageRuntimeArtifact( String scalaMainVersion ) throws ArtifactNotFoundException, ArtifactResolutionException { Artifact result = null; String resolvedScalacRuntimeVersion = scalacPluginVersion; if ( resolvedScalacRuntimeVersion == null || "".equals( resolvedScalacRuntimeVersion ) ) { for ( Artifact artifact : pluginArtifacts ) { if ( "org.scoverage".equals( artifact.getGroupId() ) && "scalac-scoverage-plugin_2.12".equals( artifact.getArtifactId() ) ) { resolvedScalacRuntimeVersion = artifact.getVersion(); break; } } } result = getResolvedArtifact( "org.scoverage", "scalac-scoverage-runtime_" + scalaMainVersion, resolvedScalacRuntimeVersion ); return result; } /** * We need to tweak our test classpath for Scoverage. * * @throws MojoExecutionException */ private void addScoverageDependenciesToClasspath( Artifact scalaScoveragePluginArtifact ) throws MojoExecutionException { @SuppressWarnings( "unchecked" ) Set set = new LinkedHashSet( project.getDependencyArtifacts() ); set.add( scalaScoveragePluginArtifact ); project.setDependencyArtifacts( set ); } private Artifact getResolvedArtifact( String groupId, String artifactId, String version ) throws ArtifactNotFoundException, ArtifactResolutionException { Artifact artifact = factory.createArtifact( groupId, artifactId, version, Artifact.SCOPE_COMPILE, "jar" ); resolver.resolve( artifact, remoteRepos, localRepo ); return artifact; } private void saveSourceRootsToFile() throws IOException { List sourceRoots = project.getCompileSourceRoots(); if ( !sourceRoots.isEmpty() ) { if ( !dataDirectory.exists() && !dataDirectory.mkdirs() ) { throw new IOException( String.format( "Cannot create \"%s\" directory ", dataDirectory.getAbsolutePath() ) ); } File sourceRootsFile = new File( dataDirectory, "source.roots" ); BufferedWriter writer = new BufferedWriter( new OutputStreamWriter( new FileOutputStream( sourceRootsFile ), "UTF-8" ) ); try { for ( String sourceRoot: sourceRoots ) { writer.write( sourceRoot ); writer.newLine(); } } finally { writer.close(); } } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy