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

scala_maven.ScalaMojoSupport Maven / Gradle / Ivy

/*
 * This is free and unencumbered software released into the public domain.
 * See UNLICENSE.
 */
package scala_maven;

import java.io.File;
import java.util.*;
import java.util.regex.Pattern;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.ArtifactUtils;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.model.Dependency;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.Component;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.project.DefaultProjectBuildingRequest;
import org.apache.maven.project.MavenProject;
import org.apache.maven.project.ProjectBuildingRequest;
import org.apache.maven.repository.RepositorySystem;
import org.apache.maven.shared.dependency.graph.DependencyGraphBuilder;
import org.apache.maven.shared.dependency.graph.DependencyNode;
import org.apache.maven.shared.dependency.graph.filter.AncestorOrSelfDependencyNodeFilter;
import org.apache.maven.shared.dependency.graph.filter.AndDependencyNodeFilter;
import org.apache.maven.shared.dependency.graph.filter.DependencyNodeFilter;
import org.apache.maven.shared.dependency.graph.traversal.CollectingDependencyNodeVisitor;
import org.apache.maven.shared.dependency.graph.traversal.DependencyNodeVisitor;
import org.apache.maven.shared.dependency.graph.traversal.FilteringDependencyNodeVisitor;
import org.apache.maven.toolchain.Toolchain;
import org.apache.maven.toolchain.ToolchainManager;
import org.codehaus.plexus.util.StringUtils;
import scala_maven_dependency.*;
import scala_maven_executions.JavaMainCaller;
import scala_maven_executions.JavaMainCallerByFork;
import scala_maven_executions.JavaMainCallerInProcess;
import util.FileUtils;

public abstract class ScalaMojoSupport extends AbstractMojo {

  /** The maven project. */
  @Parameter(property = "project", required = true, readonly = true)
  protected MavenProject project;

  /**
   * The Maven Session Object
   *
   * 

Note: Allows extending for 3rd-party usages */ @Parameter(property = "session", required = true, readonly = true) protected MavenSession session; /** Used to look up Artifacts in the remote repository. */ @Component RepositorySystem factory; /** * Additional dependencies/jar to add to classpath to run "scalaClassName" (scope and optional * field not supported) ex : * *

   *    <dependencies>
   *      <dependency>
   *        <groupId>org.scala-tools</groupId>
   *        <artifactId>scala-compiler-addon</artifactId>
   *        <version>1.0-SNAPSHOT</version>
   *      </dependency>
   *    </dependencies>
   * 
*/ @Parameter protected BasicArtifact[] dependencies; /** * Compiler plugin dependencies to use when compiling. ex: * *
   * <compilerPlugins>
   *   <compilerPlugin>
   *     <groupId>my.scala.plugin</groupId>
   *     <artifactId>amazingPlugin</artifactId>
   *     <version>1.0-SNAPSHOT</version>
   *   </compilerPlugin>
   * </compilerPlugins>
   * 
*/ @Parameter private BasicArtifact[] compilerPlugins; /** Jvm Arguments. */ @Parameter protected String[] jvmArgs; /** compiler additional arguments */ @Parameter protected String[] args; /** * Additional parameter to use to call the main class. Use this parameter only from command line * ("-DaddScalacArgs=arg1|arg2|arg3|..."), not from pom.xml. To define compiler arguments in * pom.xml see the "args" parameter. */ @Parameter(property = "addScalacArgs") private String addScalacArgs; /** override the className (FQN) of the scala tool */ @Parameter(required = false, property = "maven.scala.className") protected String scalaClassName; /** Scala 's version to use. (property 'maven.scala.version' replaced by 'scala.version') */ @Parameter(property = "scala.version") private String scalaVersion; /** * Organization/group ID of the Scala used in the project. Default value is 'org.scala-lang'. This * is an advanced setting used for clones of the Scala Language. It should be disregarded in * standard use cases. */ @Parameter(property = "scala.organization", defaultValue = "org.scala-lang") private String scalaOrganization; /** * Scala 's version to use to check binary compatibility (like suffix in artifactId of * dependency). If it is defined then it is used to checkMultipleScalaVersions */ @Parameter(property = "scala.compat.version") private String scalaCompatVersion; /** Path to Scala installation to use instead of the artifact (define as dependencies). */ @Parameter(property = "scala.home") private String scalaHome; /** Arguments for javac (when using incremental compiler). */ @Parameter(property = "javacArgs") protected String[] javacArgs; /** * Whether to instruct javac to generate debug symbols (when using incremental compiler) * * @see ://maven.apache.org/plugins/maven-compiler-plugin/compile-mojo.html#debug */ @Parameter(property = "javacGenerateDebugSymbols", defaultValue = "true") protected boolean javacGenerateDebugSymbols = true; /** * Alternative method for specifying javac arguments (when using incremental compiler). Can be * used from command line with -DaddJavacArgs=arg1|arg2|arg3|... rather than in pom.xml. */ @Parameter(property = "addJavacArgs") protected String addJavacArgs; /** The -source argument for the Java compiler (when using incremental compiler). */ @Parameter(property = "maven.compiler.source") protected String source; /** The -target argument for the Java compiler (when using incremental compiler). */ @Parameter(property = "maven.compiler.target") protected String target; /** * The --release argument for the Java compiler (when using incremental compiler), supported since * Java9. */ @Parameter(property = "maven.compiler.release") protected String release; /** The -encoding argument for the Java compiler. (when using incremental compiler). */ @Parameter(property = "project.build.sourceEncoding", defaultValue = "UTF-8") protected String encoding; /** * Display the command line called ? (property 'maven.scala.displayCmd' replaced by 'displayCmd') */ @Parameter(property = "displayCmd", defaultValue = "false", required = true) public boolean displayCmd; /** Forks the execution of scalac into a separate process. */ @Parameter(defaultValue = "true") protected boolean fork = true; /** Force the use of an external ArgFile to run any forked process. */ @Parameter(defaultValue = "false") protected boolean forceUseArgFile = false; /** Check if every dependencies use the same version of scala-library or scala.compat.version. */ @Parameter(property = "maven.scala.checkConsistency", defaultValue = "true") protected boolean checkMultipleScalaVersions; /** * Determines if a detection of multiple scala versions in the dependencies will cause the build * to fail. */ @Parameter(defaultValue = "false") protected boolean failOnMultipleScalaVersions = false; /** * Should use CanonicalPath to normalize path (true => getCanonicalPath, false => * getAbsolutePath) * * @see https://github.com/davidB/scala-maven-plugin/issues/50 */ @Parameter(property = "maven.scala.useCanonicalPath", defaultValue = "true") protected boolean useCanonicalPath = true; /** The dependency tree builder to use. */ @Component private DependencyGraphBuilder dependencyGraphBuilder; /** The toolchain manager to use. */ @Component protected ToolchainManager toolchainManager; /** List of artifacts to run plugin */ @Parameter(defaultValue = "${plugin.artifacts}") private List pluginArtifacts; private MavenArtifactResolver mavenArtifactResolver; public MavenArtifactResolver findMavenArtifactResolver() { if (mavenArtifactResolver == null) { mavenArtifactResolver = new MavenArtifactResolver(factory, session); } return mavenArtifactResolver; } private Context scalaContext; public Context findScalaContext() throws Exception { // reuse/lazy scalaContext creation (doesn't need to be Thread safe, scalaContext should be // stateless) if (scalaContext == null) { VersionNumber scalaVersion = findScalaVersion(); ArtifactIds aids = scalaVersion.major == 3 ? new ArtifactIds4Scala3(scalaVersion) : new ArtifactIds4Scala2(); VersionNumber requiredScalaVersion = StringUtils.isNotEmpty(scalaCompatVersion) ? new VersionNumberMask(scalaCompatVersion) : scalaVersion; if (requiredScalaVersion.compareTo(scalaVersion) != 0) { String msg = String.format( "Scala library detected %s doesn't match scala.compat.version : %s", scalaVersion, requiredScalaVersion); if (failOnMultipleScalaVersions) { getLog().error(msg); throw new MojoFailureException(msg); } getLog().warn(msg); } scalaContext = StringUtils.isNotEmpty(scalaHome) ? new Context4ScalaHome(scalaVersion, requiredScalaVersion, aids, new File(scalaHome)) : new Context4ScalaRemote( scalaVersion, requiredScalaVersion, aids, scalaOrganization, findMavenArtifactResolver()); } return scalaContext; } protected void addToClasspath( String groupId, String artifactId, String version, String classifier, Set classpath, boolean addDependencies) throws Exception { MavenArtifactResolver mar = findMavenArtifactResolver(); if (addDependencies) { for (Artifact a : mar.getJarAndDependencies(groupId, artifactId, version, classifier)) { classpath.add(a.getFile()); } } else { Artifact a = mar.getJar(groupId, artifactId, version, classifier); classpath.add(a.getFile()); } } void addCompilerToClasspath(Set classpath) throws Exception { Context sc = findScalaContext(); for (Artifact dep : sc.findCompilerAndDependencies()) { classpath.add(dep.getFile()); } } void addLibraryToClasspath(Set classpath) throws Exception { Context sc = findScalaContext(); for (Artifact dep : sc.findLibraryAndDependencies()) { classpath.add(dep.getFile()); } } @Override public void execute() throws MojoExecutionException, MojoFailureException { try { String oldWay = System.getProperty("maven.scala.version"); if (oldWay != null) { getLog().warn("using 'maven.scala.version' is deprecated, use 'scala.version' instead"); if (scalaVersion != null) { scalaVersion = oldWay; } } oldWay = System.getProperty("maven.scala.displayCmd"); if (oldWay != null) { getLog().warn("using 'maven.scala.displayCmd' is deprecated, use 'displayCmd' instead"); displayCmd = displayCmd || Boolean.parseBoolean(oldWay); } checkScalaVersion(); doExecute(); } catch (MojoExecutionException exc) { throw exc; } catch (MojoFailureException | RuntimeException exc) { throw exc; } catch (Exception exc) { throw new MojoExecutionException("wrap: " + exc, exc); } } protected List getDependencies() { return project.getCompileDependencies(); } private VersionNumber detectedScalaVersion = null; private VersionNumber findScalaVersion() throws Exception { if (detectedScalaVersion == null) { detectedScalaVersion = findScalaVersion0(); } return detectedScalaVersion; } private VersionNumber findScalaVersion0() throws Exception { String detectedScalaVersion = scalaVersion; if (StringUtils.isEmpty(detectedScalaVersion)) { detectedScalaVersion = findVersionFromDependencies(scalaOrganization, ArtifactIds.SCALA_LIBRARY_PATTERN); } if (StringUtils.isEmpty(detectedScalaVersion)) { if (!MavenArtifactResolver.POM.equals(project.getPackaging())) { String error = String.format( "%s:%s is missing from project dependencies", scalaOrganization, ArtifactIds.SCALA_LIBRARY_PATTERN.pattern()); getLog().error(error); throw new UnsupportedOperationException(error); } } else { // grappy hack to retrieve the SNAPSHOT version without timestamp,... // because if version is -SNAPSHOT and artifact is deploy with uniqueValue then // the version // get from dependency is with the timestamp and a build number (the resolved // version) // but scala-compiler with the same version could have different resolved // version (timestamp,...) boolean isSnapshot = ArtifactUtils.isSnapshot(detectedScalaVersion); if (isSnapshot && !detectedScalaVersion.endsWith("-SNAPSHOT")) { detectedScalaVersion = detectedScalaVersion.substring( 0, detectedScalaVersion.lastIndexOf( '-', detectedScalaVersion.lastIndexOf('-') - 1)) + "-SNAPSHOT"; } } if (StringUtils.isEmpty(detectedScalaVersion)) { throw new MojoFailureException("no scalaVersion detected or set"); } if (StringUtils.isNotEmpty(scalaVersion)) { if (!scalaVersion.equals(detectedScalaVersion)) { getLog() .warn( "scala library version define in dependencies doesn't match the scalaVersion of the plugin"); } // getLog().info("suggestion: remove the scalaVersion from pom.xml"); // //scalaVersion could be define in a parent pom where lib is not required } return new VersionNumber(detectedScalaVersion); } // TODO refactor to do only one scan of dependencies to find version private String findVersionFromDependencies(String groupId, Pattern artifactId) { VersionNumber version = new VersionNumber("0.0.0"); for (Dependency dep : getDependencies()) { if (groupId.equals(dep.getGroupId()) && artifactId.matcher(dep.getArtifactId()).find()) { version = version.max(new VersionNumber(dep.getVersion())); } } if (version.major == 0) { List deps = new ArrayList<>(); deps.addAll(project.getModel().getDependencies()); if (project.getModel().getDependencyManagement() != null) { deps.addAll(project.getModel().getDependencyManagement().getDependencies()); } for (Dependency dep : deps) { if (groupId.equals(dep.getGroupId()) && artifactId.matcher(dep.getArtifactId()).find()) { version = version.max(new VersionNumber(dep.getVersion())); } } } return version.major == 0 ? null : version.toString(); } void checkScalaVersion() throws Exception { String sv = findScalaVersion().toString(); if (StringUtils.isNotEmpty(scalaHome)) { getLog() .warn( String.format( "local scala-library.jar and scala-compiler.jar from scalaHome(%s) used instead of scala %s", scalaHome, sv)); } if (checkMultipleScalaVersions) { checkCorrectVersionsOfScalaLibrary(sv); } } /** * this method checks to see if there are multiple versions of the scala library * * @throws Exception */ private void checkCorrectVersionsOfScalaLibrary(String scalaDefVersion) throws Exception { getLog().debug("Checking for multiple versions of scala"); // TODO - Make sure we handle bad artifacts.... // TODO: note that filter does not get applied due to MNG-3236 VersionNumber sv = new VersionNumber(scalaDefVersion); VersionNumber requiredScalaVersion = StringUtils.isNotEmpty(scalaCompatVersion) ? new VersionNumberMask(scalaCompatVersion) : sv; if (requiredScalaVersion.compareTo(sv) != 0) { String msg = String.format( "Scala library detected %s doesn't match scala.compat.version : %s", sv, requiredScalaVersion); if (failOnMultipleScalaVersions) { getLog().error(msg); throw new MojoFailureException(msg); } getLog().warn(msg); } ProjectBuildingRequest request = new DefaultProjectBuildingRequest(session.getProjectBuildingRequest()); request.setProject(project); checkArtifactForScalaVersion( findScalaContext(), dependencyGraphBuilder.buildDependencyGraph(request, null)); } /** Visits a node (and all dependencies) to see if it contains duplicate scala versions */ private void checkArtifactForScalaVersion(Context scalaContext, DependencyNode rootNode) throws Exception { final CheckScalaVersionVisitor visitor = new CheckScalaVersionVisitor(scalaContext, getLog()); CollectingDependencyNodeVisitor collectingVisitor = new CollectingDependencyNodeVisitor(); DependencyNodeVisitor firstPassVisitor = new FilteringDependencyNodeVisitor(collectingVisitor, createScalaDistroDependencyFilter()); rootNode.accept(firstPassVisitor); DependencyNodeFilter secondPassFilter = new AncestorOrSelfDependencyNodeFilter(collectingVisitor.getNodes()); DependencyNodeVisitor filteredVisitor = new FilteringDependencyNodeVisitor(visitor, secondPassFilter); rootNode.accept(filteredVisitor); if (visitor.isFailed()) { visitor.logScalaDependents(); if (failOnMultipleScalaVersions) { getLog().error("Multiple versions of scala libraries detected!"); throw new MojoFailureException("Multiple versions of scala libraries detected!"); } getLog().warn("Multiple versions of scala libraries detected!"); } } /** @return A filter to only extract artifacts deployed from scala distributions */ private DependencyNodeFilter createScalaDistroDependencyFilter() throws Exception { List filters = new ArrayList<>(); filters.add(new ScalaDistroArtifactFilter(findScalaContext())); return new AndDependencyNodeFilter(filters); } protected abstract void doExecute() throws Exception; /** * Get a {@link JavaMainCaller} used invoke a Java process. Typically, this will be one of the * Scala utilities (Compiler, ScalaDoc, REPL, etc.). * *

This method does some setup on the {@link JavaMainCaller} which is not done by merely * invoking {@code new} on one of the implementations. Specifically, it adds any Scala compiler * plugin options, JVM options, and Scalac options defined on the plugin. * * @param forkOverride override the setting for {@link #fork}. Currently, this should only be set * if you are invoking the REPL. * @param mainClass the JVM main class to invoke. * @return a {@link JavaMainCaller} to use to invoke the given command. */ final JavaMainCaller getScalaCommand(final boolean forkOverride, final String mainClass) throws Exception { JavaMainCaller cmd = getEmptyScalaCommand(mainClass, forkOverride); for (String option : getScalacOptions()) { cmd.addArgs(option); } cmd.addJvmArgs(jvmArgs); return cmd; } /** * Get a {@link JavaMainCaller} used invoke a Java process. Typically this will be one of the * Scala utilities (Compiler, ScalaDoc, REPL, etc.). * * @param mainClass the JVM main class to invoke. * @return a {@link JavaMainCaller} to use to invoke the given command. */ final JavaMainCaller getEmptyScalaCommand(final String mainClass) throws Exception { return getEmptyScalaCommand(mainClass, fork); } /** * Get a {@link JavaMainCaller} used invoke a Java process. Typically this will be one of the * Scala utilities (Compiler, ScalaDoc, REPL, etc.). * * @param mainClass the JVM main class to invoke. * @param forkOverride override the setting for {@link #fork}. Currently this should only be set * if you are invoking the REPL. * @return a {@link JavaMainCaller} to use to invoke the given command. */ private JavaMainCaller getEmptyScalaCommand(final String mainClass, final boolean forkOverride) throws Exception { // If we are deviating from the plugin settings, let the user know // what's going on. if (forkOverride != fork) { super.getLog().info("Fork behavior overridden"); super.getLog() .info(String.format("Fork for this execution is %s.", String.valueOf(forkOverride))); } // TODO - Fork or not depending on configuration? JavaMainCaller cmd; String toolcp = getToolClasspath(); if (forkOverride) { // HACK (better may need refactor) boolean bootcp = true; if (args != null) { for (String arg : args) { bootcp = bootcp && !"-nobootcp".equals(arg); } } String cp = bootcp ? "" : toolcp; bootcp = bootcp && !(StringUtils.isNotEmpty(addScalacArgs) && addScalacArgs.contains("-nobootcp")); // scalac with args in files // * works only since 2.8.0 // * is buggy (don't manage space in path on windows) getLog().debug("use java command with args in file forced : " + forceUseArgFile); cmd = new JavaMainCallerByFork( this, mainClass, cp, null, null, forceUseArgFile, getToolchain()); if (bootcp) { cmd.addJvmArgs("-Xbootclasspath/a:" + toolcp); } } else { cmd = new JavaMainCallerInProcess(this, mainClass, toolcp, null, null); } return cmd; } protected Toolchain getToolchain() { return toolchainManager.getToolchainFromBuildContext("jdk", session); } private String getToolClasspath() throws Exception { Set classpath = new TreeSet<>(); addLibraryToClasspath(classpath); addCompilerToClasspath(classpath); if (dependencies != null) { for (BasicArtifact artifact : dependencies) { addToClasspath( artifact.groupId, artifact.artifactId, artifact.version, "", classpath, true); } } return FileUtils.toMultiPath(classpath); } static String targetOption(String target, VersionNumber scalaVersion) { if (scalaVersion.major == 2) { if (scalaVersion.minor <= 12) { if (target.equals("1.5") || target.equals("5")) { return "jvm-1.5"; } else if (target.equals("1.6") || target.equals("6")) { return "jvm-1.6"; } else if (target.equals("1.7") || target.equals("7")) { return "jvm-1.7"; } else if (target.equals("1.8") || target.equals("8")) { return "jvm-1.8"; } else { // invalid or unsupported option, just ignore return null; } } else if (target.equals("1.5")) { return "5"; } else if (target.equals("1.6")) { return "6"; } else if (target.equals("1.7")) { return "7"; } else if (target.equals("1.8")) { return "8"; } } return target; } protected List getScalacOptions() throws Exception { List options = new ArrayList<>(); if (args != null) Collections.addAll(options, args); if (StringUtils.isNotEmpty(addScalacArgs)) { Collections.addAll(options, StringUtils.split(addScalacArgs, "|")); } options.addAll(getCompilerPluginOptions()); if (target != null && !target.isEmpty()) { String targetOption = targetOption(target, findScalaVersion()); if (targetOption != null) { options.add("-target:" + targetOption); } } if (release != null && !release.isEmpty()) { VersionNumber scalaVersion = findScalaVersion(); if (scalaVersion.major > 2 || (scalaVersion.major == 2 && scalaVersion.minor >= 12)) { options.add("-release"); options.add(release); } } return options; } protected List getJavacOptions() { List options = new ArrayList<>(); if (javacArgs != null) Collections.addAll(options, javacArgs); if (StringUtils.isNotEmpty(addJavacArgs)) { Collections.addAll(options, StringUtils.split(addJavacArgs, "|")); } // issue #116 if (javacGenerateDebugSymbols) { options.add("-g"); } if (release != null && !release.isEmpty()) { options.add("--release"); options.add(release); } else { if (target != null && !target.isEmpty()) { options.add("-target"); options.add(target); } if (source != null && !source.isEmpty()) { options.add("-source"); options.add(source); } } if (encoding != null) { options.add("-encoding"); options.add(encoding); } return options; } /** * @return This returns whether or not the scala version can support having java sent into the * compiler */ protected boolean isJavaSupportedByCompiler() throws Exception { return findScalaVersion().compareTo(new VersionNumber("2.7.2")) >= 0; } /** * Adds appropriate compiler plugins to the scalac command. * * @param scalac * @throws Exception */ protected void addCompilerPluginOptions(JavaMainCaller scalac) throws Exception { for (String option : getCompilerPluginOptions()) { scalac.addArgs(option); } } private List getCompilerPluginOptions() throws Exception { List options = new ArrayList<>(); for (File plugin : getCompilerPlugins()) { options.add("-Xplugin:" + plugin.getPath()); } return options; } /** * Retrieves a list of paths to scala compiler plugins. * * @return The list of plugins * @throws Exception */ private Set getCompilerPlugins() throws Exception { Set plugins = new TreeSet<>(); if (compilerPlugins != null) { Set ignoreClasspath = new TreeSet<>(); addCompilerToClasspath(ignoreClasspath); addLibraryToClasspath(ignoreClasspath); for (BasicArtifact artifact : compilerPlugins) { getLog().info("compiler plugin: " + artifact.toString()); // TODO - Ensure proper scala version for plugins Set pluginClassPath = new TreeSet<>(); addToClasspath( artifact.groupId, artifact.artifactId, artifact.version, artifact.classifier, pluginClassPath, false); pluginClassPath.removeAll(ignoreClasspath); plugins.addAll(pluginClassPath); } } return plugins; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy