Skip to content

Commit

Permalink
Merge pull request #260 from scalacenter/topic/remove-in-process
Browse files Browse the repository at this point in the history
Remove in process run and test
  • Loading branch information
jvican authored Mar 1, 2018
2 parents 88e0a91 + 6a79785 commit f9dc784
Show file tree
Hide file tree
Showing 25 changed files with 286 additions and 554 deletions.
4 changes: 2 additions & 2 deletions frontend/src/main/scala/bloop/Bloop.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import bloop.cli.{CliOptions, Commands, ExitStatus}
import bloop.cli.CliParsers.{inputStreamRead, printStreamRead, OptionsParser, pathParser}
import bloop.engine.{Build, Exit, Interpreter, Run, State}
import bloop.engine.tasks.Tasks
import bloop.exec.MultiplexedStreams
import bloop.io.AbsolutePath
import bloop.io.Timer.timed
import bloop.logging.BloopLogger
Expand All @@ -22,7 +21,8 @@ object Bloop extends CaseApp[CliOptions] {
val configDirectory = options.configDir.map(AbsolutePath.apply).getOrElse(AbsolutePath(".bloop-config"))
val logger = BloopLogger.default(configDirectory.syntax)
logger.warn("The Nailgun integration should be preferred over the Bloop shell.")
logger.warn("The Bloop shell provides less features, is not supported and can be removed without notice.")
logger.warn(
"The Bloop shell provides less features, is not supported and can be removed without notice.")
logger.warn("Please refer to our documentation for more information.")
logger.verboseIf(options.verbose) {
val projects = Project.fromDir(configDirectory, logger)
Expand Down
4 changes: 1 addition & 3 deletions frontend/src/main/scala/bloop/Project.scala
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ case class Project(name: String,
properties.setProperty("allScalaJars",
scalaInstance.allJars.map(_.getAbsolutePath).mkString(","))
properties.setProperty("tmp", tmp.syntax)
properties.setProperty("fork", javaEnv.fork.toString)
properties.setProperty("javaHome", javaEnv.javaHome.syntax)
properties.setProperty("javaOptions", javaEnv.javaOptions.mkString(";"))
properties
Expand Down Expand Up @@ -97,10 +96,9 @@ object Project {
.map(toPath)
val testFrameworks =
properties.getProperty("testFrameworks").split(";").map(_.split(",").filterNot(_.isEmpty))
val fork = JBoolean.parseBoolean(properties.getProperty("fork"))
val javaHome = toPath(properties.getProperty("javaHome"))
val javaOptions = properties.getProperty("javaOptions").split(";").filterNot(_.isEmpty)
val javaEnv = JavaEnv(fork, javaHome, javaOptions)
val javaEnv = JavaEnv(javaHome, javaOptions)
val tmp = AbsolutePath(NioPaths.get(properties.getProperty("tmp")))
Project(
name,
Expand Down
6 changes: 4 additions & 2 deletions frontend/src/main/scala/bloop/engine/Interpreter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -161,9 +161,10 @@ object Interpreter {
case Some(project) =>
def doTest(state: State): Task[State] = {
val testFilter = TestInternals.parseFilters(cmd.filter)
val cwd = cmd.cliOptions.common.workingPath
for {
compiled <- Tasks.compile(state, project, reporterConfig, excludeRoot = false)
result <- Tasks.test(compiled, project, cmd.isolated, testFilter)
result <- Tasks.test(compiled, project, cwd, cmd.isolated, testFilter)
} yield result
}
if (cmd.watch) watch(project, state, doTest _)
Expand Down Expand Up @@ -229,7 +230,8 @@ object Interpreter {
Task(compiled.mergeStatus(ExitStatus.UnexpectedError))
case Some(main) =>
val args = cmd.args.toArray
Tasks.run(compiled, project, main, args)
val cwd = cmd.cliOptions.common.workingPath
Tasks.run(compiled, project, cwd, main, args)
}
}
}
Expand Down
24 changes: 16 additions & 8 deletions frontend/src/main/scala/bloop/engine/tasks/Tasks.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import java.util.Optional

import bloop.cli.ExitStatus
import bloop.engine.{Dag, Leaf, Parent, State}
import bloop.exec.ProcessConfig
import bloop.exec.ForkProcess
import bloop.io.AbsolutePath
import bloop.reporter.{Reporter, ReporterConfig}
import bloop.testing.{DiscoveredTests, TestInternals}
import bloop.{CompileInputs, Compiler, Project}
Expand Down Expand Up @@ -169,13 +170,15 @@ object Tasks {
*
* @param state The current state of Bloop.
* @param project The project for which to run the tests.
* @param cwd The directory in which to start the forked JVM.
* @param isolated Do not run the tests for the dependencies of `project`.
* @param testFilter A function from a fully qualified class name to a Boolean, indicating whether
* a test must be included.
* @return The new state of Bloop.
*/
def test(state: State,
project: Project,
cwd: AbsolutePath,
isolated: Boolean,
testFilter: String => Boolean): Task[State] = Task {
// TODO(jvican): This method should cache the test loader always.
Expand All @@ -185,7 +188,7 @@ object Tasks {
val projectsToTest = if (isolated) List(project) else Dag.dfs(state.build.getDagFor(project))
projectsToTest.foreach { project =>
val projectName = project.name
val processConfig = ProcessConfig(project.javaEnv, project.classpath)
val processConfig = ForkProcess(project.javaEnv, project.classpath)
val testLoader = processConfig.toExecutionClassLoader(Some(TestInternals.filteredLoader))
val frameworks = project.testFrameworks
.flatMap(fname => TestInternals.getFramework(testLoader, fname.toList, logger))
Expand Down Expand Up @@ -216,7 +219,7 @@ object Tasks {
DiscoveredTests(testLoader, includedTests.groupBy(_._1).mapValues(_.map(_._2)))
}

TestInternals.executeTasks(processConfig, discoveredTests, eventHandler, logger)
TestInternals.executeTasks(cwd, processConfig, discoveredTests, eventHandler, logger)
}

// Return the previous state, test execution doesn't modify it.
Expand All @@ -228,15 +231,20 @@ object Tasks {
*
* @param state The current state of Bloop.
* @param project The project to run.
* @param fqn The fully qualified name of the main class.
* @param cwd The directory in which to start the forked JVM.
* @param fqn The fully qualified name of the main class.
* @param args The arguments to pass to the main class.
*/
def run(state: State, project: Project, fqn: String, args: Array[String]): Task[State] = Task {
def run(state: State,
project: Project,
cwd: AbsolutePath,
fqn: String,
args: Array[String]): Task[State] = Task {
val classpath = project.classpath
val processConfig = ProcessConfig(project.javaEnv, classpath)
val exitCode = processConfig.runMain(fqn, args, state.logger)
val processConfig = ForkProcess(project.javaEnv, classpath)
val exitCode = processConfig.runMain(cwd, fqn, args, state.logger)
val exitStatus = {
if (exitCode == ProcessConfig.EXIT_OK) ExitStatus.Ok
if (exitCode == ForkProcess.EXIT_OK) ExitStatus.Ok
else ExitStatus.UnexpectedError
}

Expand Down
92 changes: 92 additions & 0 deletions frontend/src/main/scala/bloop/exec/ForkProcess.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package bloop.exec

import java.io.File.{separator, pathSeparator}
import java.lang.ClassLoader
import java.nio.file.Files
import java.net.URLClassLoader
import java.util.concurrent.ConcurrentHashMap

import scala.util.control.NonFatal

import bloop.io.AbsolutePath
import bloop.logging.{Logger, ProcessLogger}

/**
* Configuration to start a new JVM to execute Java code.
*
* @param javaEnv The configuration describing how to start the new JVM.
* @param classpath The full classpath with which the code should be executed.
*/
final case class ForkProcess(javaEnv: JavaEnv, classpath: Array[AbsolutePath]) {

/**
* Creates a `ClassLoader` from the classpath of this `ForkProcess`.
*
* @param parent A parent classloader
* @return A classloader constructed from the classpath of this `ForkProcess`.
*/
def toExecutionClassLoader(parent: Option[ClassLoader]): ClassLoader = {
def makeNew(parent: Option[ClassLoader]): ClassLoader = {
val classpathEntries = classpath.map(_.underlying.toUri.toURL)
new URLClassLoader(classpathEntries, parent.orNull)
}
ForkProcess.classLoaderCache.computeIfAbsent(parent, makeNew)
}

/**
* Run the main function in class `className`, passing it `args`.
*
* @param cwd The directory in which to start the forked JVM.
* @param className The fully qualified name of the class to run.
* @param args The arguments to pass to the main method.
* @param logger Where to log the messages from execution.
* @param extraClasspath Paths to append to the classpath before running.
* @return 0 if the execution exited successfully, a non-zero number otherwise.
*/
def runMain(cwd: AbsolutePath,
className: String,
args: Array[String],
logger: Logger,
extraClasspath: Array[AbsolutePath] = Array.empty): Int = {
val fullClasspath = classpath ++ extraClasspath

val java = javaEnv.javaHome.resolve("bin").resolve("java")
val classpathOption = "-cp" :: fullClasspath.map(_.syntax).mkString(pathSeparator) :: Nil
val appOptions = className :: args.toList
val cmd = java.syntax :: javaEnv.javaOptions.toList ::: classpathOption ::: appOptions

logger.debug(s"Running '$className' in a new JVM.")
logger.debug(s" java_home = '${javaEnv.javaHome}'")
logger.debug(s" javaOptions = '${javaEnv.javaOptions.mkString(" ")}'")
logger.debug(s" classpath = '${fullClasspath.map(_.syntax).mkString(pathSeparator)}'")
logger.debug(s" command = '${cmd.mkString(" ")}'")
logger.debug(s" cwd = '$cwd'")

if (!Files.exists(cwd.underlying)) {
logger.error(s"Couldn't start the forked JVM because '$cwd' doesn't exist.")
ForkProcess.EXIT_ERROR
} else {
val processBuilder = new ProcessBuilder(cmd: _*)
processBuilder.directory(cwd.toFile)
val process = processBuilder.start()
val processLogger = new ProcessLogger(logger, process)
processLogger.start()
val exitCode = process.waitFor()
logger.debug(s"Forked JVM exited with code: $exitCode")

exitCode
}
}
}

object ForkProcess {

private val classLoaderCache: ConcurrentHashMap[Option[ClassLoader], ClassLoader] =
new ConcurrentHashMap

/** The code returned after a successful execution. */
final val EXIT_OK = 0

/** The code returned after the execution errored. */
final val EXIT_ERROR = 1
}
11 changes: 3 additions & 8 deletions frontend/src/main/scala/bloop/exec/JavaEnv.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,22 @@ import bloop.io.AbsolutePath

/**
* The configuration of the Java environment for a given project.
* Defines whether we should fork, and how to spawn a new JVM.
*
* @param fork If true, fork a new JVM. Run in process otherwise.
* @param javaHome Location of the java home. The `java` binary is expected to be found
* in `$javaHome/bin/java`.
* @param javaOptions The options to pass the JVM when starting.
*/
final case class JavaEnv(fork: Boolean, javaHome: AbsolutePath, javaOptions: Array[String])
final case class JavaEnv(javaHome: AbsolutePath, javaOptions: Array[String])

object JavaEnv {

/**
* Default `JavaEnv` constructed from this JVM. Uses the same `javaHome`,
* and specifies no arguments.
*
* @param fork If true, for ka new JVM. Run in process otherwise.
* @return A `JavaEnv` configured from this JVM.
*/
def default(fork: Boolean): JavaEnv = {
val default: JavaEnv = {
val javaHome = AbsolutePath(sys.props("java.home"))
val javaOptions = Array.empty[String]
JavaEnv(fork, javaHome, javaOptions)
JavaEnv(javaHome, javaOptions)
}
}
84 changes: 0 additions & 84 deletions frontend/src/main/scala/bloop/exec/MultiplexedStreams.scala

This file was deleted.

Loading

0 comments on commit f9dc784

Please sign in to comment.