├── .gitignore ├── README.md ├── build.sbt ├── project ├── bintray.sbt └── build.properties └── src └── main └── scala └── com └── typesafe └── sbt ├── SbtMultiJvm.scala └── multijvm └── Jvm.scala /.gitignore: -------------------------------------------------------------------------------- 1 | lib_managed/ 2 | project/boot/ 3 | target/ 4 | .idea/ 5 | .idea_modules/ 6 | .cache 7 | .classpath 8 | .settings/ 9 | .project 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | PLUGIN MOVED TO sbt/sbt-multi-jvm 2 | ================================= 3 | In an effort to centralise all plugins under the [sbt](https://github.com/sbt) organisation on github, 4 | this plugin has moved to: 5 | 6 | **https://github.com/sbt/sbt-multi-jvm** 7 | 8 | Where it will remain to be developed under the same terms and licenses as it was developed here. 9 | Please report all issues and pull requests to that repository. 10 | 11 | License 12 | ------- 13 | Copyright 2012 Typesafe, Inc. 14 | 15 | 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 16 | 17 | http://www.apache.org/licenses/LICENSE-2.0 18 | 19 | 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. 20 | -------------------------------------------------------------------------------- /build.sbt: -------------------------------------------------------------------------------- 1 | import bintray.Keys._ 2 | 3 | sbtPlugin := true 4 | 5 | organization := "com.typesafe.sbt" 6 | 7 | name := "sbt-multi-jvm" 8 | 9 | version := "0.3.9" 10 | 11 | publishMavenStyle := false 12 | 13 | repository in bintray := "sbt-plugins" 14 | 15 | bintrayOrganization in bintray := Some("sbt") 16 | 17 | bintrayPublishSettings 18 | 19 | licenses += ("Apache-2.0", url("https://www.apache.org/licenses/LICENSE-2.0.html")) 20 | 21 | addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.12.0") 22 | -------------------------------------------------------------------------------- /project/bintray.sbt: -------------------------------------------------------------------------------- 1 | resolvers += Resolver.url( 2 | "bintray-sbt-plugin-releases", 3 | url("https://dl.bintray.com/content/sbt/sbt-plugin-releases"))( 4 | Resolver.ivyStylePatterns) 5 | 6 | addSbtPlugin("me.lessis" % "bintray-sbt" % "0.1.2") -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=0.13.7 2 | -------------------------------------------------------------------------------- /src/main/scala/com/typesafe/sbt/SbtMultiJvm.scala: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2011-2012 Typesafe Inc. 3 | */ 4 | 5 | package com.typesafe.sbt 6 | 7 | import com.typesafe.sbt.multijvm.{Jvm, JvmLogger} 8 | import sbt._ 9 | import Keys._ 10 | import Cache.seqFormat 11 | import sbinary.DefaultProtocol.StringFormat 12 | import java.io.File 13 | import java.lang.Boolean.getBoolean 14 | 15 | import scala.Console.{ GREEN, RESET } 16 | 17 | import sbtassembly.AssemblyPlugin.assemblySettings 18 | import sbtassembly.{MergeStrategy, AssemblyKeys} 19 | import AssemblyKeys._ 20 | 21 | object SbtMultiJvm extends Plugin { 22 | case class Options(jvm: Seq[String], extra: String => Seq[String], run: String => Seq[String]) 23 | 24 | object MultiJvmKeys { 25 | val MultiJvm = config("multi-jvm") extend(Test) 26 | 27 | val multiJvmMarker = SettingKey[String]("multi-jvm-marker") 28 | 29 | val multiJvmTests = TaskKey[Map[String, Seq[String]]]("multi-jvm-tests") 30 | val multiJvmTestNames = TaskKey[Seq[String]]("multi-jvm-test-names") 31 | 32 | val multiJvmApps = TaskKey[Map[String, Seq[String]]]("multi-jvm-apps") 33 | val multiJvmAppNames = TaskKey[Seq[String]]("multi-jvm-app-names") 34 | 35 | val java = TaskKey[File]("java") 36 | 37 | val jvmOptions = TaskKey[Seq[String]]("jvm-options") 38 | val extraOptions = SettingKey[String => Seq[String]]("extra-options") 39 | 40 | val scalatestRunner = SettingKey[String]("scalatest-runner") 41 | val scalatestOptions = SettingKey[Seq[String]]("scalatest-options") 42 | val scalatestClasspath = TaskKey[Classpath]("scalatest-classpath") 43 | val scalatestScalaOptions = TaskKey[String => Seq[String]]("scalatest-scala-options") 44 | val scalatestMultiNodeScalaOptions = TaskKey[String => Seq[String]]("scalatest-multi-node-scala-options") 45 | val multiTestOptions = TaskKey[Options]("multi-test-options") 46 | val multiNodeTestOptions = TaskKey[Options]("multi-node-test-options") 47 | 48 | val appScalaOptions = TaskKey[String => Seq[String]]("app-scala-options") 49 | val connectInput = SettingKey[Boolean]("connect-input") 50 | val multiRunOptions = TaskKey[Options]("multi-run-options") 51 | 52 | val multiRunCopiedClassLocation = SettingKey[File]("multi-run-copied-class-location") 53 | 54 | val multiJvmTestJar = TaskKey[String]("multi-jvm-test-jar") 55 | val multiJvmTestJarName = TaskKey[String]("multi-jvm-test-jar-name") 56 | 57 | val multiNodeTest = TaskKey[Unit]("multi-node-test") 58 | val multiNodeExecuteTests = TaskKey[Tests.Output]("multi-node-execute-tests") 59 | val multiNodeTestOnly = InputKey[Unit]("multi-node-test-only") 60 | 61 | val multiNodeHosts = SettingKey[Seq[String]]("multi-node-hosts") 62 | val multiNodeHostsFileName = SettingKey[String]("multi-node-hosts-file-name") 63 | val multiNodeProcessedHosts = TaskKey[(IndexedSeq[String], IndexedSeq[String])]("multi-node-processed-hosts") 64 | val multiNodeTargetDirName = SettingKey[String]("multi-node-target-dir-name") 65 | val multiNodeJavaName = SettingKey[String]("multi-node-java-name") 66 | 67 | // TODO fugly workaround for now 68 | val multiNodeWorkAround = TaskKey[(String, (IndexedSeq[String], IndexedSeq[String]), String)]("multi-node-workaround") 69 | } 70 | 71 | import MultiJvmKeys._ 72 | 73 | private[this] def noTestsMessage(scoped: ScopedKey[_])(implicit display: Show[ScopedKey[_]]): String = 74 | "No tests to run for " + display(scoped) 75 | 76 | lazy val multiJvmSettings: Seq[Project.Setting[_]] = inConfig(MultiJvm)(Defaults.configSettings ++ internalMultiJvmSettings) 77 | 78 | private def internalMultiJvmSettings = assemblySettings ++ Seq( 79 | multiJvmMarker := "MultiJvm", 80 | loadedTestFrameworks <<= loadedTestFrameworks in Test, 81 | definedTests <<= Defaults.detectTests, 82 | multiJvmTests <<= (definedTests, multiJvmMarker) map { (d, m) => collectMultiJvm(d.map(_.name), m) }, 83 | multiJvmTestNames <<= multiJvmTests map { _.keys.toSeq } storeAs multiJvmTestNames triggeredBy compile, 84 | multiJvmApps <<= (discoveredMainClasses, multiJvmMarker) map collectMultiJvm, 85 | multiJvmAppNames <<= multiJvmApps map { _.keys.toSeq } storeAs multiJvmAppNames triggeredBy compile, 86 | java <<= javaHome map { javaCommand(_, "java") }, 87 | jvmOptions := Seq.empty, 88 | extraOptions := { (name: String) => Seq.empty }, 89 | scalatestRunner := "org.scalatest.tools.Runner", 90 | scalatestOptions := defaultScalatestOptions, 91 | scalatestClasspath <<= managedClasspath map { _.filter(_.data.name.contains("scalatest")) }, 92 | multiRunCopiedClassLocation <<= target.apply(targetFile => new File(targetFile, "multi-run-copied-libraries")), 93 | scalatestScalaOptions <<= (scalatestRunner, scalatestOptions, fullClasspath, multiRunCopiedClassLocation) map scalaOptionsForScalatest, 94 | scalatestMultiNodeScalaOptions <<= (scalatestRunner, scalatestOptions) map scalaMultiNodeOptionsForScalatest, 95 | multiTestOptions <<= (jvmOptions, extraOptions, scalatestScalaOptions) map Options, 96 | multiNodeTestOptions <<= (jvmOptions, extraOptions, scalatestMultiNodeScalaOptions) map Options, 97 | appScalaOptions <<= fullClasspath map scalaOptionsForApps, 98 | connectInput := true, 99 | multiRunOptions <<= (jvmOptions, extraOptions, appScalaOptions) map Options, 100 | 101 | executeTests <<= multiJvmExecuteTests, 102 | testOnly <<= multiJvmTestOnly, 103 | test <<= (executeTests, streams) map { (results, s) => Tests.showResults(s.log, results, "No tests to run for MultiJvm") }, 104 | run <<= multiJvmRun, 105 | runMain <<= multiJvmRun, 106 | 107 | // TODO try to make sure that this is only generated on a need to have basis 108 | multiJvmTestJar <<= (assembly, assemblyOutputPath in assembly) map { (task, file) => file.getAbsolutePath } , 109 | multiJvmTestJarName <<= (assemblyOutputPath in assembly) map { (file) => file.getAbsolutePath }, 110 | 111 | multiNodeTest <<= (multiNodeExecuteTests, streams, resolvedScoped, state) map { (results, s, scoped, st) => 112 | implicit val display = Project.showContextKey(st) 113 | Tests.showResults(s.log, results, noTestsMessage(scoped)) }, 114 | multiNodeExecuteTests <<= multiNodeExecuteTestsTask, 115 | multiNodeTestOnly <<= multiNodeTestOnlyTask, 116 | multiNodeHosts := Seq.empty, 117 | multiNodeHostsFileName := "multi-node-test.hosts", 118 | multiNodeProcessedHosts <<= (multiNodeHosts, multiNodeHostsFileName, multiNodeJavaName, streams) map processMultiNodeHosts, 119 | multiNodeTargetDirName := "multi-node-test", 120 | multiNodeJavaName := "java", 121 | // TODO there must be a way get at keys in the tasks that I just don't get 122 | multiNodeWorkAround <<= (multiJvmTestJar, multiNodeProcessedHosts, multiNodeTargetDirName) map { case x => x }, 123 | 124 | // here follows the assembly parts of the config 125 | // don't run the tests when creating the assembly 126 | test in assembly := {}, 127 | 128 | // we want everything including the tests and test frameworks 129 | fullClasspath in assembly <<= fullClasspath in MultiJvm, 130 | 131 | // the first class wins just like a classpath 132 | // just concatenate conflicting text files 133 | assemblyMergeStrategy in assembly := { 134 | case n if n.endsWith(".class") => MergeStrategy.first 135 | case n if n.endsWith(".txt") => MergeStrategy.concat 136 | case n if n.endsWith("NOTICE") => MergeStrategy.concat 137 | case n => (assemblyMergeStrategy in assembly).value.apply(n) 138 | }, 139 | 140 | assemblyJarName in assembly := { 141 | name + "_" + scalaVersion + "-" + version + "-multi-jvm-assembly.jar" 142 | } 143 | ) 144 | 145 | def collectMultiJvm(discovered: Seq[String], marker: String): Map[String, Seq[String]] = { 146 | val found = discovered filter (_.contains(marker)) groupBy (multiName(_, marker)) 147 | found map { 148 | case (key, values) => 149 | val totalNodes = sys.props.get(marker + "." + key + ".nrOfNodes").getOrElse(values.size.toString).toInt 150 | val sortedClasses = values.sorted 151 | val totalClasses = sortedClasses.padTo(totalNodes, sortedClasses.last) 152 | (key, totalClasses) 153 | } 154 | } 155 | 156 | def multiName(name: String, marker: String) = name.split(marker).head 157 | 158 | def multiSimpleName(name: String) = name.split("\\.").last 159 | 160 | def javaCommand(javaHome: Option[File], name: String): File = { 161 | val home = javaHome.getOrElse(new File(System.getProperty("java.home"))) 162 | new File(new File(home, "bin"), name) 163 | } 164 | 165 | def defaultScalatestOptions: Seq[String] = { 166 | if (getBoolean("sbt.log.noformat")) Seq("-oW") else Seq("-o") 167 | } 168 | 169 | def scalaOptionsForScalatest(runner: String, options: Seq[String], fullClasspath: Classpath, multiRunCopiedClassDir: File) = { 170 | val directoryBasedClasspathEntries = fullClasspath.files.filter(_.isDirectory) 171 | // Copy over just the jars to this folder. 172 | fullClasspath.files.filter(_.isFile).foreach(classpathFile => IO.copyFile(classpathFile, new File(multiRunCopiedClassDir, classpathFile.getName), true)) 173 | val cp = directoryBasedClasspathEntries.absString + File.pathSeparator + multiRunCopiedClassDir.getAbsolutePath + File.separator + "*" 174 | (testClass: String) => { Seq("-cp", cp, runner, "-s", testClass) ++ options } 175 | } 176 | 177 | def scalaMultiNodeOptionsForScalatest(runner: String, options: Seq[String]) = { 178 | (testClass: String) => { Seq(runner, "-s", testClass) ++ options } 179 | } 180 | 181 | def scalaOptionsForApps(classpath: Classpath) = { 182 | val cp = classpath.files.absString 183 | (mainClass: String) => Seq("-cp", cp, mainClass) 184 | } 185 | 186 | def multiJvmExecuteTests: Def.Initialize[sbt.Task[Tests.Output]] = 187 | (multiJvmTests, multiJvmMarker, java, multiTestOptions, sourceDirectory, streams) map { 188 | (tests, marker, javaBin, options, srcDir, s) => runMultiJvmTests(tests, marker, javaBin, options, srcDir, s.log) 189 | } 190 | 191 | def multiJvmTestOnly: Def.Initialize[sbt.InputTask[Unit]] = InputTask(loadForParser(multiJvmTestNames)((s, i) => Defaults.testOnlyParser(s, i getOrElse Nil))) { result => 192 | (multiJvmTests, multiJvmMarker, java, multiTestOptions, sourceDirectory, streams, result) map { 193 | case (allTests, marker, javaBin, options, srcDir, s, (selected, _extraOptions)) => 194 | val opts = options.copy(extra = (s: String) => { options.extra(s) ++ _extraOptions }) 195 | val tests = selected flatMap { name => allTests.get(name) map ((name, _)) } 196 | val results = runMultiJvmTests(tests.toMap, marker, javaBin, opts, srcDir, s.log) 197 | Tests.showResults(s.log, results, "No tests to run for MultiJvm") 198 | } 199 | } 200 | 201 | def runMultiJvmTests(tests: Map[String, Seq[String]], marker: String, javaBin: File, options: Options, 202 | srcDir: File, log: Logger): Tests.Output = { 203 | val results = 204 | if (tests.isEmpty) 205 | List() 206 | else tests.map { 207 | case (_name, classes) => multi(_name, classes, marker, javaBin, options, srcDir, false, log) 208 | } 209 | Tests.Output(Tests.overall(results.map(_._2)), Map.empty, results.map(result => Tests.Summary("multi-jvm", result._1))) 210 | } 211 | 212 | def multiJvmRun: Def.Initialize[sbt.InputTask[Unit]] = InputTask(loadForParser(multiJvmAppNames)((s, i) => runParser(s, i getOrElse Nil))) { result => 213 | (result, multiJvmApps, multiJvmMarker, java, multiRunOptions, sourceDirectory, connectInput, multiNodeHosts, streams) map { 214 | (name, map, marker, javaBin, options, srcDir, connect, hostsAndUsers, s) => { 215 | val classes = map.getOrElse(name, Seq.empty) 216 | if (classes.isEmpty) s.log.info("No apps to run.") 217 | else multi(name, classes, marker, javaBin, options, srcDir, connect, s.log) 218 | } 219 | } 220 | } 221 | 222 | def runParser: (State, Seq[String]) => complete.Parser[String] = { 223 | import complete.DefaultParsers._ 224 | (state, appClasses) => Space ~> token(NotSpace examples appClasses.toSet) 225 | } 226 | 227 | def multi(name: String, classes: Seq[String], marker: String, javaBin: File, options: Options, srcDir: File, 228 | input: Boolean, log: Logger): (String, TestResult.Value) = { 229 | val logName = "* " + name 230 | log.info(if (log.ansiCodesSupported) GREEN + logName + RESET else logName) 231 | val classesHostsJavas = getClassesHostsJavas(classes, IndexedSeq.empty, IndexedSeq.empty, "") 232 | val hosts = classesHostsJavas.map(_._2) 233 | val processes = classes.zipWithIndex map { 234 | case (testClass, index) => 235 | val jvmName = "JVM-" + (index + 1) 236 | val jvmLogger = new JvmLogger(jvmName) 237 | val className = multiSimpleName(testClass) 238 | val optionsFile = (srcDir ** (className + ".opts")).get.headOption 239 | val optionsFromFile = optionsFile map (IO.read(_)) map (_.trim.replace("\\n", " ").split("\\s+").toList) getOrElse (Seq.empty[String]) 240 | val multiNodeOptions = getMultiNodeCommandLineOptions(hosts, index, classes.size) 241 | val allJvmOptions = options.jvm ++ multiNodeOptions ++ optionsFromFile ++ options.extra(className) 242 | val runOptions = options.run(testClass) 243 | val connectInput = input && index == 0 244 | log.debug("Starting %s for %s" format (jvmName, testClass)) 245 | log.debug(" with JVM options: %s" format allJvmOptions.mkString(" ")) 246 | (testClass, Jvm.startJvm(javaBin, allJvmOptions, runOptions, jvmLogger, connectInput)) 247 | } 248 | processExitCodes(name, processes, log) 249 | } 250 | 251 | def processExitCodes(name: String, processes: Seq[(String, Process)], log: Logger): (String, TestResult.Value) = { 252 | val exitCodes = processes map { 253 | case (testClass, process) => (testClass, process.exitValue()) 254 | } 255 | val failures = exitCodes flatMap { 256 | case (testClass, exit) if exit > 0 => Some("Failed: " + testClass) 257 | case _ => None 258 | } 259 | failures foreach(log.error(_)) 260 | (name, if(failures.nonEmpty) TestResult.Failed else TestResult.Passed) 261 | } 262 | 263 | def multiNodeExecuteTestsTask: Def.Initialize[sbt.Task[Tests.Output]] = 264 | (multiJvmTests, multiJvmMarker, multiNodeJavaName, multiNodeTestOptions, sourceDirectory, multiNodeWorkAround, streams) map { 265 | case (tests, marker, _java, options, srcDir, (_jarName, (hostsAndUsers, javas), targetDir), s) => 266 | runMultiNodeTests(tests, marker, _java, options, srcDir, _jarName, hostsAndUsers, javas, targetDir, s.log) 267 | } 268 | 269 | def multiNodeTestOnlyTask: Def.Initialize[InputTask[Unit]] = InputTask(loadForParser(multiJvmTestNames)((s, i) => Defaults.testOnlyParser(s, i getOrElse Nil))) { result => 270 | (multiJvmTests, multiJvmMarker, multiNodeJavaName, multiNodeTestOptions, sourceDirectory, multiNodeWorkAround, streams, result).map { 271 | case (allTests, marker, _java, options, srcDir, (_jarName, (hostsAndUsers, javas), targetDir), s, (selected, _extraOptions)) => 272 | val opts = options.copy(extra = (s: String) => { options.extra(s) ++ _extraOptions }) 273 | val tests = selected flatMap { name => allTests.get(name) map ((name, _)) } 274 | val results = runMultiNodeTests(tests.toMap, marker, _java, options, srcDir, _jarName, hostsAndUsers, javas, targetDir, s.log) 275 | Tests.showResults(s.log, results, "No tests to run for MultiNode") 276 | } 277 | } 278 | 279 | def runMultiNodeTests(tests: Map[String, Seq[String]], marker: String, java: String, options: Options, 280 | srcDir: File, jarName: String, hostsAndUsers: IndexedSeq[String], 281 | javas: IndexedSeq[String], targetDir: String, 282 | log: Logger): Tests.Output = { 283 | val results = 284 | if (tests.isEmpty) 285 | List() 286 | else tests.map { 287 | case (_name, classes) => multiNode(_name, classes, marker, java, options, srcDir, false, jarName, 288 | hostsAndUsers, javas, targetDir, log) 289 | } 290 | Tests.Output(Tests.overall(results.map(_._2)), Map.empty, results.map(result => Tests.Summary("multi-jvm", result._1))) 291 | } 292 | 293 | def multiNode(name: String, classes: Seq[String], marker: String, defaultJava: String, options: Options, srcDir: File, 294 | input: Boolean, testJar: String, hostsAndUsers: IndexedSeq[String], javas: IndexedSeq[String], targetDir: String, 295 | log: Logger): (String, TestResult.Value) = { 296 | val logName = "* " + name 297 | log.info(if (log.ansiCodesSupported) GREEN + logName + RESET else logName) 298 | val classesHostsJavas = getClassesHostsJavas(classes, hostsAndUsers, javas, defaultJava) 299 | val hosts = classesHostsJavas.map(_._2) 300 | // TODO move this out, maybe to the hosts string as well? 301 | val syncProcesses = classesHostsJavas.map { 302 | case ((testClass, hostAndUser, java)) => 303 | (testClass + " sync", Jvm.syncJar(testJar, hostAndUser, targetDir, log)) 304 | } 305 | val syncResult = processExitCodes(name, syncProcesses, log) 306 | if (syncResult._2 == TestResult.Passed) { 307 | val processes = classesHostsJavas.zipWithIndex map { 308 | case ((testClass, hostAndUser, java), index) => { 309 | val jvmName = "JVM-" + (index + 1) 310 | val jvmLogger = new JvmLogger(jvmName) 311 | val className = multiSimpleName(testClass) 312 | val optionsFile = (srcDir ** (className + ".opts")).get.headOption 313 | val optionsFromFile = optionsFile map (IO.read(_)) map (_.trim.replace("\\n", " ").split("\\s+").toList) getOrElse (Seq.empty[String]) 314 | val multiNodeOptions = getMultiNodeCommandLineOptions(hosts, index, classes.size) 315 | val allJvmOptions = options.jvm ++ optionsFromFile ++ options.extra(className) ++ multiNodeOptions 316 | val runOptions = options.run(testClass) 317 | val connectInput = input && index == 0 318 | log.debug("Starting %s for %s" format (jvmName, testClass)) 319 | log.debug(" with JVM options: %s" format allJvmOptions.mkString(" ")) 320 | (testClass, Jvm.forkRemoteJava(java, allJvmOptions, runOptions, testJar, hostAndUser, targetDir, 321 | jvmLogger, connectInput, log)) 322 | } 323 | } 324 | processExitCodes(name, processes, log) 325 | } 326 | else { 327 | syncResult 328 | } 329 | } 330 | 331 | private def padSeqOrDefaultTo(seq: IndexedSeq[String], default: String, max: Int): IndexedSeq[String] = { 332 | val realSeq = if (seq.isEmpty) IndexedSeq(default) else seq 333 | if (realSeq.size >= max) 334 | realSeq 335 | else 336 | (realSeq /: (0 until (max - realSeq.size)))((mySeq, pos) => mySeq :+ realSeq(pos % realSeq.size)) 337 | } 338 | 339 | private def getClassesHostsJavas(classes: Seq[String], hostsAndUsers: IndexedSeq[String], javas: IndexedSeq[String], 340 | defaultJava: String): IndexedSeq[(String, String, String)] = { 341 | val max = classes.length 342 | val tuple = (classes.toIndexedSeq, padSeqOrDefaultTo(hostsAndUsers, "localhost", max), padSeqOrDefaultTo(javas, defaultJava, max)) 343 | tuple.zipped.map { case (className: String, hostAndUser: String, _java: String) => (className, hostAndUser, _java) } 344 | } 345 | 346 | private def getMultiNodeCommandLineOptions(hosts: Seq[String], index: Int, maxNodes: Int): Seq[String] = { 347 | Seq("-Dmultinode.max-nodes=" + maxNodes, "-Dmultinode.server-host=" + hosts(0).split("@").last, 348 | "-Dmultinode.host=" + hosts(index).split("@").last, "-Dmultinode.index=" + index) 349 | } 350 | 351 | private def processMultiNodeHosts(hosts: Seq[String], hostsFileName: String, defaultJava: String, s: Types.Id[Keys.TaskStreams]): 352 | (IndexedSeq[String], IndexedSeq[String]) = { 353 | val hostsFile = new File(hostsFileName) 354 | val theHosts: IndexedSeq[String] = 355 | if (hosts.isEmpty) { 356 | if (hostsFile.exists && hostsFile.canRead) { 357 | s.log.info("Using hosts defined in file " + hostsFile.getAbsolutePath) 358 | IO.readLines(hostsFile).map(_.trim).filter(_.length > 0).toIndexedSeq 359 | } 360 | else 361 | hosts.toIndexedSeq 362 | } 363 | else { 364 | if (hostsFile.exists && hostsFile.canRead) 365 | s.log.info("Hosts from setting " + multiNodeHosts.key.label + " is overrriding file " + hostsFile.getAbsolutePath) 366 | hosts.toIndexedSeq 367 | } 368 | 369 | theHosts.map { x => 370 | val elems = x.split(":").toList.take(2).padTo(2, defaultJava) 371 | (elems(0), elems(1)) 372 | } unzip 373 | } 374 | } 375 | -------------------------------------------------------------------------------- /src/main/scala/com/typesafe/sbt/multijvm/Jvm.scala: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2011-2012 Typesafe Inc. 3 | */ 4 | 5 | package com.typesafe.sbt.multijvm 6 | 7 | import java.io.File 8 | import java.lang.{ProcessBuilder => JProcessBuilder} 9 | 10 | import sbt.{Process, _} 11 | 12 | object Jvm { 13 | def startJvm(javaBin: File, jvmOptions: Seq[String], runOptions: Seq[String], logger: Logger, connectInput: Boolean) = { 14 | forkJava(javaBin, jvmOptions ++ runOptions, logger, connectInput) 15 | } 16 | 17 | def forkJava(javaBin: File, options: Seq[String], logger: Logger, connectInput: Boolean) = { 18 | val java = javaBin.toString 19 | val command = (java :: options.toList).toArray 20 | val builder = new JProcessBuilder(command: _*) 21 | Process(builder).run(logger, connectInput) 22 | } 23 | 24 | /** 25 | * check if the current operating system is some OS 26 | **/ 27 | def isOS(os:String) = try { 28 | System.getProperty("os.name").toUpperCase startsWith os.toUpperCase 29 | } catch { 30 | case _ : Throwable => false 31 | } 32 | 33 | /** 34 | * convert to proper path for the operating system 35 | **/ 36 | def osPath(path:String) = if (isOS("WINDOWS")) Process(Seq("cygpath", path)).lines.mkString else path 37 | 38 | def syncJar(jarName: String, hostAndUser: String, remoteDir: String, sbtLogger: Logger) : Process = { 39 | val command: Array[String] = Array("ssh", hostAndUser, "mkdir -p " + remoteDir) 40 | val builder = new JProcessBuilder(command: _*) 41 | sbtLogger.debug("Jvm.syncJar about to run " + command.mkString(" ")) 42 | val process = Process(builder).run(sbtLogger, false) 43 | if (process.exitValue() == 0) { 44 | val command: Array[String] = Array("rsync", "-ace", "ssh", osPath(jarName), hostAndUser +":" + remoteDir +"/") 45 | val builder = new JProcessBuilder(command: _*) 46 | sbtLogger.debug("Jvm.syncJar about to run " + command.mkString(" ")) 47 | Process(builder).run(sbtLogger, false) 48 | } 49 | else { 50 | process 51 | } 52 | } 53 | 54 | def forkRemoteJava(java: String, jvmOptions: Seq[String], appOptions: Seq[String], jarName: String, 55 | hostAndUser: String, remoteDir: String, logger: Logger, connectInput: Boolean, 56 | sbtLogger: Logger): Process = { 57 | sbtLogger.debug("About to use java " + java) 58 | val shortJarName = new File(jarName).getName 59 | val javaCommand = List(List(java), jvmOptions, List("-cp", shortJarName), appOptions).flatten 60 | val command = Array("ssh", hostAndUser, ("cd " :: (remoteDir :: (" ; " :: javaCommand))).mkString(" ")) 61 | sbtLogger.debug("Jvm.forkRemoteJava about to run " + command.mkString(" ")) 62 | val builder = new JProcessBuilder(command: _*) 63 | Process(builder).run(logger, connectInput) 64 | } 65 | } 66 | 67 | final class JvmLogger(name: String) extends BasicLogger { 68 | def jvm(message: String) = "[%s] %s" format (name, message) 69 | 70 | def log(level: Level.Value, message: => String) = System.out.synchronized { 71 | System.out.println(jvm(message)) 72 | } 73 | 74 | def trace(t: => Throwable) = System.out.synchronized { 75 | val traceLevel = getTrace 76 | if (traceLevel >= 0) System.out.print(StackTrace.trimmed(t, traceLevel)) 77 | } 78 | 79 | def success(message: => String) = log(Level.Info, message) 80 | def control(event: ControlEvent.Value, message: => String) = log(Level.Info, message) 81 | 82 | def logAll(events: Seq[LogEvent]) = System.out.synchronized { events.foreach(log) } 83 | } 84 | --------------------------------------------------------------------------------