咱們在使用maven構建scala工程的時候, 要使用scala-maven-plugin插件,常規配置以下 :java
<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的值, 啓動應用:git
mvn scala:run -Dlauncher=pdata
https://github.com/universsky/scala-maven-plugingithub
參考: (Maven Plugin示例:本身動手編寫Maven插件: http://blog.csdn.net/vking_wang/article/details/8612981 ) spring
Maven插件項目的POM有兩個特殊的地方:apache
它的packaging必須爲maven-plugin,這種特殊的打包類型可以控制Maven爲其在生命週期階段綁定插件處理相關的目標,例如在compile階段,Maven須要爲插件項目構建一個特殊插件描述符文件。api
maven-plugin-api依賴中包含了插件開發所必須得類。springboot
首先,每一個插件目標類,或者說MOJO,都必須繼承AbstractMojo類並實現execute()方法,只有這樣Maven才能識別該插件目標,並執行execute()方法中的行爲。session
項目運行run生命週期對應的處理類 scala_maven.ScalaRunMojoapp
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> * <launchers> * <launcher> * <id>myLauncher</id> * <mainClass>my.project.Main</mainClass> * <args> * <arg>arg1</arg> * </args> * <jvmArgs> * <jvmArg>-Xmx64m</jvmArg> * </jvmArgs> * </launcher> * <launcher> * <id>myLauncher2</id> * ... * <><> * </launcher> * </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"); } } }
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; }
這個接口是maven-core裏面的.jvm
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; }
這個處理類中, 有對scala編譯器和運行時依賴的實現處理.
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字節碼.
// // 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 編譯器的一個單機版本。
/** * 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 }