maven scala plugin 实现jvmArgs,执行过程原理解析笔记

浏览: 174 发布日期: 2016-08-14 分类: scala

scala-maven-plugin 构建scala,springboot工程

我们在使用maven构建scala工程的时候, 要使用scala-maven-plugin插件,常规配置如下 :




<plugin>
            <groupId>org.scala-tools</groupId>
            <artifactId>maven-scala-plugin</artifactId>
            <configuration>
                <launchers>
                    <launcher>
                        <id>pdata</id>
                        <mainClass>com.pdata.PDataApplication</mainClass>
                        <!-- args are optional -->
                        <!--<args>-->
                        <!--<arg>arg1</arg>-->
                        <!--</args>-->
                        <!-- jvmArgs are optional -->
                        <jvmArgs>
                            <jvmArg>-Xmx1024m</jvmArg>
                            <jvmArg>-Dsword.autokey.port=9202</jvmArg>
                        </jvmArgs>
                    </launcher>
                    <!-- you could define other launcher -->
                </launchers>
            </configuration>
</plugin>

这样,就可以直接使用如下命令动态注入sword.autokey.prot的值, 启动应用:


mvn scala:run -Dlauncher=pdata


实现原理剖析

源码地址:

https://github.com/universsky/scala-maven-plugin

mvn scala:run -Dlauncher=pdata 执行过程讲解

自己动手编写Maven插件

参考: (Maven Plugin示例:自己动手编写Maven插件: http://blog.csdn.net/vking_wang/article/details/8612981 )

Maven插件项目的POM有两个特殊的地方:

  1. 它的packaging必须为maven-plugin,这种特殊的打包类型能够控制Maven为其在生命周期阶段绑定插件处理相关的目标,例如在compile阶段,Maven需要为插件项目构建一个特殊插件描述符文件。

  1. maven-plugin-api依赖中包含了插件开发所必须得类。

首先,每个插件目标类,或者说MOJO,都必须继承AbstractMojo类并实现execute()方法,只有这样Maven才能识别该插件目标,并执行execute()方法中的行为。

mvn scala:run -Dlauncher=pdata 执行过程

项目运行run生命周期对应的处理类 scala_maven.ScalaRunMojo


package scala_maven;

import org.apache.maven.toolchain.Toolchain;
import org.codehaus.plexus.util.StringUtils;

import scala_maven_executions.JavaMainCaller;
import scala_maven_executions.JavaMainCallerByFork;
import scala_maven_executions.MainHelper;


/**
 * Run a Scala class using the Scala runtime
 *
 * @goal run
 * @requiresDependencyResolution test
 * @execute phase="test-compile"
 * @threadSafe
 */
public class ScalaRunMojo extends ScalaMojoSupport {

    /**
     * The class to use when launching a scala program
     *
     * @parameter property="launcher"
     */
    protected String launcher;

    /**
     * Additional parameter to use to call the main class
     * Using this parameter only from command line ("-DaddArgs=arg1|arg2|arg3|..."), not from pom.xml.
     * @parameter property="addArgs"
     */
    protected String addArgs;

    /**
     * A list of launcher definition (to avoid rewriting long command line or share way to call an application)
     * launchers could be define by :
     * <pre>
     *   &lt;launchers>
     *     &lt;launcher>
     *       &lt;id>myLauncher&lt;/id>
     *       &lt;mainClass>my.project.Main&lt;/mainClass>
     *       &lt;args>
     *         &lt;arg>arg1&lt;/arg>
     *       &lt;/args>
     *       &lt;jvmArgs>
     *         &lt;jvmArg>-Xmx64m&lt;/jvmArg>
     *       &lt;/jvmArgs>
     *     &lt;/launcher>
     *     &lt;launcher>
     *       &lt;id>myLauncher2&lt;/id>
     *       ...
     *       &lt;>&lt;>
     *     &lt;/launcher>
     *   &lt;/launchers>
     * </pre>
     * @parameter
     */
    protected Launcher[] launchers;

    /**
     * Main class to call, the call use the jvmArgs and args define in the pom.xml, and the addArgs define in the command line if define.
     *
     * Higher priority to launcher parameter)
     * Using this parameter only from command line (-DmainClass=...), not from pom.xml.
     * @parameter property="mainClass"
     */
    protected String mainClass;

    @Override
    protected void doExecute() throws Exception {
        JavaMainCaller jcmd = null;
        Toolchain toolchain = toolchainManager.getToolchainFromBuildContext("jdk", session);
        if (StringUtils.isNotEmpty(mainClass)) {
            jcmd = new JavaMainCallerByFork(this, mainClass, MainHelper.toMultiPath(project.getTestClasspathElements()), jvmArgs, args, forceUseArgFile, toolchain);
        } else if ((launchers != null) && (launchers.length > 0)) {
            if (StringUtils.isNotEmpty(launcher)) {
                for(int i = 0; (i < launchers.length) && (jcmd == null); i++) {
                    if (launcher.equals(launchers[i].id)) {
                        getLog().info("launcher '"+ launchers[i].id + "' selected => "+ launchers[i].mainClass );
                        jcmd = new JavaMainCallerByFork(this, launchers[i].mainClass, MainHelper.toMultiPath(project.getTestClasspathElements()), launchers[i].jvmArgs, launchers[i].args, forceUseArgFile, toolchain);
                    }
                }
            } else {
                getLog().info("launcher '"+ launchers[0].id + "' selected => "+ launchers[0].mainClass );
                jcmd = new JavaMainCallerByFork(this, launchers[0].mainClass, MainHelper.toMultiPath(project.getTestClasspathElements()), launchers[0].jvmArgs, launchers[0].args, forceUseArgFile, toolchain);
            }
        }
        if (jcmd != null) {
            if (StringUtils.isNotEmpty(addArgs)) {
                jcmd.addArgs(StringUtils.split(addArgs, "|"));
            }
            jcmd.run(displayCmd);
        } else {
            getLog().warn("Not mainClass or valid launcher found/define");
        }
    }
}



JavaMainCaller



package scala_maven_executions;

import java.io.File;
/**
 * This interface is used to create a call on a main method of a java class.
 *
 * The important implementations are JavaCommand and ReflectionJavaCaller
 *
 * @author J. Suereth
 *
 */
public interface JavaMainCaller {
    /** Adds an environemnt variable */
    public abstract void addEnvVar(String key, String value);
    /** Adds a JVM arg.  Note: This is not available for in-process "forks" */
    public abstract void addJvmArgs(String... args);
    /** Adds arguments for the process */
    public abstract void addArgs(String... args);
    /** Adds option (basically two arguments) */
    public abstract void addOption(String key, String value);
    /** Adds an option (key-file pair). This will pull the absolute path of the file */
    public abstract void addOption(String key, File value);
    /** Adds the key if the value is true */
    public abstract void addOption(String key, boolean value);

    /** request run to be redirected to maven/requester logger */
    public abstract void redirectToLog();

    // TODO: avoid to have several Thread to pipe stream
    // TODO: add support to inject startup command and shutdown command (on :quit)
    public abstract void run(boolean displayCmd) throws Exception;
    /** Runs the JavaMain with all the built up arguments/options */
    public abstract boolean run(boolean displayCmd, boolean throwFailure) throws Exception;

    /**
     * run the command without stream redirection nor waiting for exit
     *
     * @param displayCmd
     * @return the spawn Process (or null if no process was spawned)
     * @throws Exception
     */
    public abstract SpawnMonitor spawn(boolean displayCmd) throws Exception;

}

ToolchainManager

这个接口是maven-core里面的.

Launcher领域对象模型





package scala_maven;

public class Launcher {
    protected String id;

    protected String mainClass;

    /**
     * Jvm Arguments
     *
     * @parameter
     */
    protected String[] jvmArgs;

    /**
     * compiler additionnals arguments
     *
     * @parameter
     */
    protected String[] args;
}

核心处理类ScalaMojoSupport, ScalaCompilerSupport

这个处理类中, 有对scala编译器和运行时依赖的实现处理.

基于 Zinc 的编译器:

com.typesafe.zinc:zinc:0.3.5

我们来看一下源码:



package com.typesafe.zinc
class Compiler(scalac : sbt.compiler.AnalyzingCompiler, javac : xsbti.compile.JavaCompiler) extends scala.AnyRef {
  def compile(inputs : com.typesafe.zinc.Inputs)(log : xsbti.Logger) : sbt.inc.Analysis = { /* compiled code */ }
  def compile(inputs : com.typesafe.zinc.Inputs, cwd : scala.Option[java.io.File])(log : xsbti.Logger) : sbt.inc.Analysis = { /* compiled code */ }
  def autoClasspath(classesDirectory : java.io.File, allScalaJars : scala.Seq[java.io.File], javaOnly : scala.Boolean, classpath : scala.Seq[java.io.File]) : scala.Seq[java.io.File] = { /* compiled code */ }
  override def toString() : scala.Predef.String = { /* compiled code */ }
}
object Compiler extends scala.AnyRef {
  val CompilerInterfaceId : java.lang.String = { /* compiled code */ }
  val JavaClassVersion : java.lang.String = { /* compiled code */ }
  val compilerCache : com.typesafe.zinc.Cache[com.typesafe.zinc.Setup, com.typesafe.zinc.Compiler] = { /* compiled code */ }
  val residentCache : xsbti.compile.GlobalsCache = { /* compiled code */ }
  val analysisCache : com.typesafe.zinc.Cache[com.typesafe.zinc.FileFPrint, scala.Option[scala.Tuple2[sbt.inc.Analysis, sbt.CompileSetup]]] = { /* compiled code */ }
  def apply(setup : com.typesafe.zinc.Setup, log : xsbti.Logger) : com.typesafe.zinc.Compiler = { /* compiled code */ }
  def getOrCreate(setup : com.typesafe.zinc.Setup, log : xsbti.Logger) : com.typesafe.zinc.Compiler = { /* compiled code */ }
  def create(setup : com.typesafe.zinc.Setup, log : xsbti.Logger) : com.typesafe.zinc.Compiler = { /* compiled code */ }
  def newScalaCompiler(instance : sbt.ScalaInstance, interfaceJar : java.io.File, log : xsbti.Logger) : sbt.compiler.AnalyzingCompiler = { /* compiled code */ }
  def newJavaCompiler(instance : sbt.ScalaInstance, javaHome : scala.Option[java.io.File], fork : scala.Boolean) : xsbti.compile.JavaCompiler = { /* compiled code */ }
  def createResidentCache(maxCompilers : scala.Int) : xsbti.compile.GlobalsCache = { /* compiled code */ }
  def analysisStore(cacheFile : java.io.File) : sbt.inc.AnalysisStore = { /* compiled code */ }
  def analysis(cacheFile : java.io.File) : sbt.inc.Analysis = { /* compiled code */ }
  def analysisIsEmpty(cacheFile : java.io.File) : scala.Boolean = { /* compiled code */ }
  def scalaInstance(setup : com.typesafe.zinc.Setup) : sbt.ScalaInstance = { /* compiled code */ }
  def scalaLoader(jars : scala.Seq[java.io.File]) : java.net.URLClassLoader = { /* compiled code */ }
  def scalaVersion(scalaLoader : java.lang.ClassLoader) : scala.Option[scala.Predef.String] = { /* compiled code */ }
  def compilerInterface(setup : com.typesafe.zinc.Setup, scalaInstance : sbt.ScalaInstance, log : xsbti.Logger) : java.io.File = { /* compiled code */ }
  def interfaceId(scalaVersion : scala.Predef.String) : java.lang.String = { /* compiled code */ }
}

可以看出,编译的过程是,

  • scalac把scala代码编译成.class文件

  • ClassLoader对.class文件的读写操作, 寻找classpath下面的类, 加载到jvm执行引擎中

  • 最后在jvm中执行.class字节码.

增量编译: ScalaCompilerSupport.incrementalCompile





//
// Incremental compilation
//

@SuppressWarnings("unchecked")
protected int incrementalCompile(List<String> classpathElements, List<File> sourceRootDirs, File outputDir, File cacheFile, boolean compileInLoop) throws Exception, InterruptedException {
    List<File> sources = findSourceWithFilters(sourceRootDirs);
    if (sources.isEmpty()) {
        return -1;
    }

    if (incremental == null) {
        File libraryJar = getLibraryJar();
        File compilerJar = getCompilerJar();
        List<File> extraJars = getCompilerDependencies();
        extraJars.remove(libraryJar);
        String sbtGroupId = SbtIncrementalCompiler.SBT_GROUP_ID;
        String xsbtiArtifactId = SbtIncrementalCompiler.XSBTI_ARTIFACT_ID;
        String compilerInterfaceArtifactId = SbtIncrementalCompiler.COMPILER_INTERFACE_ARTIFACT_ID;
        String compilerInterfaceClassifier = SbtIncrementalCompiler.COMPILER_INTERFACE_CLASSIFIER;
        String sbtVersion = findVersionFromPluginArtifacts(sbtGroupId, SbtIncrementalCompiler.COMPILER_INTEGRATION_ARTIFACT_ID);
        File xsbtiJar = getPluginArtifactJar(sbtGroupId, xsbtiArtifactId, sbtVersion);
        List<String> zincArgs = StringUtils.isEmpty(addZincArgs) ? new LinkedList<String>() : (List<String>) Arrays.asList(addZincArgs.split("\\|"));
        File interfaceSrcJar = getPluginArtifactJar(sbtGroupId, compilerInterfaceArtifactId, sbtVersion, compilerInterfaceClassifier);
           incremental = new SbtIncrementalCompiler(useZincServer, zincPort, libraryJar, compilerJar, extraJars, xsbtiJar, interfaceSrcJar, getLog(), zincArgs);
    }

    classpathElements.remove(outputDir.getAbsolutePath());
    List<String> scalacOptions = getScalaOptions();
    List<String> javacOptions = getJavacOptions();
    Map<File, File> cacheMap = getAnalysisCacheMap();

    try {
        incremental.compile(project.getBasedir(), classpathElements, sources, outputDir, scalacOptions, javacOptions, cacheFile, cacheMap, compileOrder);
    } catch (xsbti.CompileFailed e) {
        if (compileInLoop) {
            compileErrors = true;
        } else {
            throw e;
        }
    }

    return 1;
}





调用的compile方法:



public void compile(File baseDir, List<String> classpathElements, List<File> sources, File classesDirectory, List<String> scalacOptions, List<String> javacOptions, File cacheFile, Map<File, File> cacheMap, String compileOrder) throws Exception {
    if (useServer) {
        zincCompile(baseDir, classpathElements, sources, classesDirectory, scalacOptions, javacOptions, cacheFile, cacheMap, compileOrder);
    } else {
        if (log.isDebugEnabled()) log.debug("Incremental compiler = " + compiler + " [" + Integer.toHexString(compiler.hashCode()) + "]");
        List<File> classpath = pathsToFiles(classpathElements);
        Inputs inputs = Inputs.create(classpath, sources, classesDirectory, scalacOptions, javacOptions, cacheFile, cacheMap, compileOrder, defaultOptions(), true);
        if (log.isDebugEnabled()) Inputs.debug(inputs, logger);
        compiler.compile(inputs, logger);
    }
}

如果useServer编译,执行zincCompile方法:


private void zincCompile(File baseDir, List<String> classpathElements, List<File> sources, File classesDirectory, List<String> scalacOptions, List<String> javacOptions, File cacheFile, Map<File, File> cacheMap, String compileOrder) throws Exception {
    List<String> arguments = new ArrayList<String>(extraArgs);
    arguments.add("-log-level");
    arguments.add(logLevelToString(log));
    arguments.add("-scala-compiler");
    arguments.add(compilerJar.getAbsolutePath());
    arguments.add("-scala-library");
    arguments.add(libraryJar.getAbsolutePath());
    arguments.add("-scala-extra");
    List<String> extraPaths = new ArrayList<String>();
    for (File extraJar : extraJars) {
        extraPaths.add(extraJar.getAbsolutePath());
    }
    arguments.add(MainHelper.toMultiPath(extraPaths));
    if (!classpathElements.isEmpty()) {
      arguments.add("-classpath");
      arguments.add(MainHelper.toMultiPath(classpathElements));
    }
    arguments.add("-d");
    arguments.add(classesDirectory.getAbsolutePath());
    for (String scalacOption : scalacOptions) {
        arguments.add("-S" + scalacOption);
    }
    for (String javacOption : javacOptions) {
        arguments.add("-C" + javacOption);
    }
    arguments.add("-compile-order");
    arguments.add(compileOrder);
    arguments.add("-analysis-cache");
    arguments.add(cacheFile.getAbsolutePath());
    arguments.add("-analysis-map");
    arguments.add(cacheMapToString(cacheMap));
    for (File source : sources) {
        arguments.add(source.getAbsolutePath());
    }

    int exitCode = zinc.run(arguments, baseDir, System.out, System.err);

    if (exitCode != 0) {
        xsbti.Problem[] problems = null;
        throw new sbt.compiler.CompileFailed(arguments.toArray(new String[arguments.size()]), "Compile failed via zinc server", problems);
    }
}

ZincClient.run方法



/**
 * Java API for sending a zinc command to a currently running nailgun server.
 * All output goes to specified output streams. Exit code is returned.
 * @throws java.net.ConnectException if the zinc server is not available
 */
@throws(classOf[java.net.ConnectException])
def run(args: JList[String], cwd: File, out: OutputStream, err: OutputStream): Int =
  send("zinc", args.asScala, cwd, out, err)


/**
 * Send a command to a currently running nailgun server.
 * Possible commands are "zinc", "status", and "shutdown".
 * All output goes to specified output streams. Exit code is returned.
 * @throws java.net.ConnectException if the zinc server is not available
 */
def send(command: String, args: Seq[String], cwd: File, out: OutputStream, err: OutputStream): Int = {
  val socket  = new Socket(address, port)
  val sockout = socket.getOutputStream
  val sockin  = new DataInputStream(socket.getInputStream)
  sendCommand(command, args, cwd, sockout)
  val exitCode = receiveOutput(sockin, out, err)
  sockout.close(); sockin.close(); socket.close()
  exitCode
}

ZincClient是

Client for talking directly to a nailgun server from another JVM.

增量编译是只编译那些源代码在上一次编译之后有修改的类,及那些受这些修改影响到的类,它可以大大减少 Scala 的编译时间。频繁编译代码的增量部分是非常有用的,因为在开发时我们经常要这样做。

Scala 插件现在通过集成 Zinc 来支持增量编译, 它是 sbt 增量 Scala 编译器的一个单机版本。

com.typesafe.zinc.Compiler.compile方法



/**
 * Run a compile. The resulting analysis is also cached in memory.
 */
def compile(inputs: Inputs, cwd: Option[File])(log: Logger): Analysis = {
  import inputs._
  if (forceClean && Compiler.analysisIsEmpty(cacheFile)) Util.cleanAllClasses(classesDirectory)
  val getAnalysis: File => Option[Analysis] = analysisMap.get _
  val aggressive    = new AggressiveCompile(cacheFile)
  val cp            = autoClasspath(classesDirectory, scalac.scalaInstance.allJars, javaOnly, classpath)
  val compileOutput = CompileOutput(classesDirectory)
  val globalsCache  = Compiler.residentCache
  val progress      = None
  val maxErrors     = 100
  val reporter      = new LoggerReporter(maxErrors, log, identity)
  val skip          = false
  val incOpts       = incOptions.options
  val compileSetup  = new CompileSetup(compileOutput, new CompileOptions(scalacOptions, javacOptions), scalac.scalaInstance.actualVersion, compileOrder, incOpts.nameHashing)
  val analysisStore = Compiler.analysisStore(cacheFile)
  val analysis      = aggressive.compile1(sources, cp, compileSetup, progress, analysisStore, getAnalysis, definesClass, scalac, javac, reporter, skip, globalsCache, incOpts)(log)
  if (mirrorAnalysis) {
    SbtAnalysis.printRelations(analysis, Some(new File(cacheFile.getPath() + ".relations")), cwd)
  }
  SbtAnalysis.printOutputs(analysis, outputRelations, outputProducts, cwd, classesDirectory)
  analysis
}

返回顶部