diff --git a/build-integrations/sbt-1.0/build.sbt b/build-integrations/sbt-1.0/build.sbt index 919f61686b..7c40b13c4b 100644 --- a/build-integrations/sbt-1.0/build.sbt +++ b/build-integrations/sbt-1.0/build.sbt @@ -3,7 +3,6 @@ val GuardianFrontend = Integrations.GuardianFrontend val MiniBetterFiles = Integrations.MiniBetterFiles val WithResources = Integrations.WithResources val WithTests = Integrations.WithTests - val integrations = List(SbtSbt, GuardianFrontend, MiniBetterFiles, WithResources, WithTests) import bloop.build.integrations.PluginKeys diff --git a/build-integrations/sbt-1.0/project/Integrations.scala b/build-integrations/sbt-1.0/project/Integrations.scala index c2133290d7..65ebb7b3fb 100644 --- a/build-integrations/sbt-1.0/project/Integrations.scala +++ b/build-integrations/sbt-1.0/project/Integrations.scala @@ -9,8 +9,8 @@ object Integrations { val MiniBetterFiles = RootProject(uri( "git://github.com/scalacenter/mini-better-files.git#0ed848993a2fd5a36e4366b5efb9c68dce958fc2")) val WithResources = RootProject( - uri("git://github.com/scalacenter/with-resources.git#7529b2c3ac455cbb1889d4791c4e0d4957e29306")) + uri("git://github.com/scalacenter/with-resources.git#f0a46830cae7ef6282d9bba64b6da34bae18f339")) val WithTests = RootProject( - uri("git://github.com/scalacenter/with-tests.git#7a0c7f7d38efd53ca9ec3a347df3638932bd619e")) + uri("git://github.com/scalacenter/with-tests.git#3be26f4f21427c5bc0b83deb96d6e66973102eb2")) } diff --git a/frontend/src/main/scala/bloop/Bloop.scala b/frontend/src/main/scala/bloop/Bloop.scala index 6592334a76..3d74a3b15f 100644 --- a/frontend/src/main/scala/bloop/Bloop.scala +++ b/frontend/src/main/scala/bloop/Bloop.scala @@ -1,7 +1,13 @@ package bloop import bloop.cli.{CliOptions, Commands, ExitStatus} -import bloop.cli.CliParsers.{OptionsParser, inputStreamRead, pathParser, printStreamRead} +import bloop.cli.CliParsers.{ + OptionsParser, + inputStreamRead, + pathParser, + printStreamRead, + propertiesParser +} import bloop.engine.{Build, Exit, Interpreter, NoPool, Run, State} import bloop.engine.tasks.Tasks import bloop.io.AbsolutePath @@ -17,7 +23,8 @@ object Bloop extends CaseApp[CliOptions] { private val reader = consoleReader() override def run(options: CliOptions, remainingArgs: RemainingArgs): Unit = { - val configDirectory = options.configDir.map(AbsolutePath.apply).getOrElse(AbsolutePath(".bloop")) + val configDirectory = + options.configDir.map(AbsolutePath.apply).getOrElse(AbsolutePath(".bloop")) val logger = BloopLogger.default(configDirectory.syntax) logger.warn("The Nailgun integration should be preferred over the Bloop shell.") logger.warn( diff --git a/frontend/src/main/scala/bloop/Cli.scala b/frontend/src/main/scala/bloop/Cli.scala index 4373b112eb..060ba985fb 100644 --- a/frontend/src/main/scala/bloop/Cli.scala +++ b/frontend/src/main/scala/bloop/Cli.scala @@ -26,6 +26,7 @@ object Cli { ngout = server.out, ngerr = server.err, workingDirectory = ngContext.getWorkingDirectory, + env = ngContext.getEnv() ) val command = ngContext.getCommand val args = { diff --git a/frontend/src/main/scala/bloop/cli/CliParsers.scala b/frontend/src/main/scala/bloop/cli/CliParsers.scala index 185958329c..07dd603f2d 100644 --- a/frontend/src/main/scala/bloop/cli/CliParsers.scala +++ b/frontend/src/main/scala/bloop/cli/CliParsers.scala @@ -2,6 +2,7 @@ package bloop.cli import java.io.{InputStream, PrintStream} import java.nio.file.{Path, Paths} +import java.util.Properties import caseapp.CommandParser import caseapp.core.{ArgParser, DefaultBaseCommand} @@ -27,6 +28,10 @@ object CliParsers { } } + implicit val propertiesParser: ArgParser[Properties] = ArgParser.instance("A properties parser") { + case whatever => Left("You cannot pass in properties through the command line.") + } + val BaseMessages: caseapp.core.Messages[DefaultBaseCommand] = caseapp.core.Messages[DefaultBaseCommand] val OptionsParser: caseapp.core.Parser[CliOptions] = diff --git a/frontend/src/main/scala/bloop/cli/CommonOptions.scala b/frontend/src/main/scala/bloop/cli/CommonOptions.scala index 02dc31b72b..4fbb9354b3 100644 --- a/frontend/src/main/scala/bloop/cli/CommonOptions.scala +++ b/frontend/src/main/scala/bloop/cli/CommonOptions.scala @@ -1,6 +1,7 @@ package bloop.cli import java.io.{InputStream, PrintStream} +import java.util.Properties import bloop.engine.ExecutionContext import bloop.io.AbsolutePath @@ -22,6 +23,7 @@ case class CommonOptions( @Hidden err: PrintStream = System.err, @Hidden ngout: PrintStream = System.out, @Hidden ngerr: PrintStream = System.err, + @Hidden env: Properties = CommonOptions.currentProperties, threads: Int = ExecutionContext.nCPUs ) { def workingPath: AbsolutePath = AbsolutePath(workingDirectory) @@ -29,4 +31,10 @@ case class CommonOptions( object CommonOptions { final val default = CommonOptions() + final val currentProperties: Properties = { + import scala.collection.JavaConverters._ + System.getenv().asScala.foldLeft(new Properties()) { + case (props, (key, value)) => props.setProperty(key, value); props + } + } } diff --git a/frontend/src/main/scala/bloop/engine/Interpreter.scala b/frontend/src/main/scala/bloop/engine/Interpreter.scala index 5fc9a3ac2a..dfc3d9896e 100644 --- a/frontend/src/main/scala/bloop/engine/Interpreter.scala +++ b/frontend/src/main/scala/bloop/engine/Interpreter.scala @@ -349,7 +349,8 @@ object Interpreter { result } catch { case NonFatal(t) => - previousState.logger.error(t.getMessage) + if (t.getMessage != null) + previousState.logger.error(t.getMessage) previousState.logger.trace(t) previousState } diff --git a/frontend/src/main/scala/bloop/engine/tasks/Tasks.scala b/frontend/src/main/scala/bloop/engine/tasks/Tasks.scala index 61e5a2e216..20172d7a18 100644 --- a/frontend/src/main/scala/bloop/engine/tasks/Tasks.scala +++ b/frontend/src/main/scala/bloop/engine/tasks/Tasks.scala @@ -280,7 +280,8 @@ object Tasks { DiscoveredTests(testLoader, includedTests.groupBy(_._1).mapValues(_.map(_._2))) } - TestInternals.executeTasks(cwd, processConfig, discoveredTests, eventHandler, logger) + val env = state.commonOptions.env + TestInternals.executeTasks(cwd, processConfig, discoveredTests, eventHandler, logger, env) } // Return the previous state, test execution doesn't modify it. @@ -303,7 +304,7 @@ object Tasks { args: Array[String]): Task[State] = Task { val classpath = project.classpath val processConfig = ForkProcess(project.javaEnv, classpath) - val exitCode = processConfig.runMain(cwd, fqn, args, state.logger) + val exitCode = processConfig.runMain(cwd, fqn, args, state.logger, state.commonOptions.env) val exitStatus = { if (exitCode == ForkProcess.EXIT_OK) ExitStatus.Ok else ExitStatus.UnexpectedError diff --git a/frontend/src/main/scala/bloop/exec/ForkProcess.scala b/frontend/src/main/scala/bloop/exec/ForkProcess.scala index aecb04ae59..52050d2d9e 100644 --- a/frontend/src/main/scala/bloop/exec/ForkProcess.scala +++ b/frontend/src/main/scala/bloop/exec/ForkProcess.scala @@ -1,13 +1,13 @@ package bloop.exec -import java.io.File.{separator, pathSeparator} +import java.io.File.{pathSeparator, separator} import java.lang.ClassLoader import java.nio.file.Files import java.net.URLClassLoader +import java.util.Properties import java.util.concurrent.ConcurrentHashMap import scala.util.control.NonFatal - import bloop.io.AbsolutePath import bloop.logging.{Logger, ProcessLogger} @@ -40,6 +40,7 @@ final case class ForkProcess(javaEnv: JavaEnv, classpath: Array[AbsolutePath]) { * @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 properties The environment properties to run the program with. * @param extraClasspath Paths to append to the classpath before running. * @return 0 if the execution exited successfully, a non-zero number otherwise. */ @@ -47,7 +48,9 @@ final case class ForkProcess(javaEnv: JavaEnv, classpath: Array[AbsolutePath]) { className: String, args: Array[String], logger: Logger, + env: Properties, extraClasspath: Array[AbsolutePath] = Array.empty): Int = { + import scala.collection.JavaConverters.{propertiesAsScalaMap, mapAsJavaMapConverter} val fullClasspath = classpath ++ extraClasspath val java = javaEnv.javaHome.resolve("bin").resolve("java") @@ -68,6 +71,9 @@ final case class ForkProcess(javaEnv: JavaEnv, classpath: Array[AbsolutePath]) { } else { val processBuilder = new ProcessBuilder(cmd: _*) processBuilder.directory(cwd.toFile) + val processEnv = processBuilder.environment() + processEnv.clear() + processEnv.putAll(propertiesAsScalaMap(env).asJava) val process = processBuilder.start() val processLogger = new ProcessLogger(logger, process) processLogger.start() diff --git a/frontend/src/main/scala/bloop/testing/TestInternals.scala b/frontend/src/main/scala/bloop/testing/TestInternals.scala index 745d6f1fbf..3b760cd636 100644 --- a/frontend/src/main/scala/bloop/testing/TestInternals.scala +++ b/frontend/src/main/scala/bloop/testing/TestInternals.scala @@ -1,23 +1,17 @@ package bloop.testing +import java.util.Properties import java.util.regex.Pattern import bloop.DependencyResolution import bloop.exec.{ForkProcess, JavaEnv} import bloop.io.AbsolutePath import bloop.logging.Logger -import sbt.testing.{ - AnnotatedFingerprint, - EventHandler, - Fingerprint, - Framework, - SubclassFingerprint, - Task => TestTask -} +import sbt.testing.{AnnotatedFingerprint, EventHandler, Fingerprint, Framework, SubclassFingerprint, Task => TestTask} import org.scalatools.testing.{Framework => OldFramework} import sbt.internal.inc.Analysis import sbt.internal.inc.classpath.{FilteredLoader, IncludePackagesFilter} -import sbt.testing.{Framework, Task => TestTask, TaskDef} +import sbt.testing.{Framework, TaskDef, Task => TestTask} import xsbt.api.Discovered import xsbti.api.ClassLike import xsbti.compile.CompileAnalysis @@ -74,12 +68,14 @@ object TestInternals { * @param discoveredTests The tests that were discovered. * @param eventHandler Handler that reacts on messages from the testing frameworks. * @param logger Logger receiving test output. + * @param env The environment properties to run the program with. */ def executeTasks(cwd: AbsolutePath, fork: ForkProcess, discoveredTests: DiscoveredTests, eventHandler: EventHandler, - logger: Logger): Unit = { + logger: Logger, + env: Properties): Unit = { logger.debug("Starting forked test execution.") val testLoader = fork.toExecutionClassLoader(Some(filteredLoader)) @@ -91,7 +87,7 @@ object TestInternals { logger.debug("Test agent jars: " + testAgentFiles.mkString(", ")) val exitCode = server.whileRunning { - fork.runMain(cwd, forkMain, arguments, logger, testAgentJars) + fork.runMain(cwd, forkMain, arguments, logger, env, testAgentJars) } if (exitCode != 0) logger.error(s"Forked execution terminated with non-zero code: $exitCode") diff --git a/frontend/src/test/scala/bloop/engine/FileWatchingSpec.scala b/frontend/src/test/scala/bloop/engine/FileWatchingSpec.scala index 565737a9cd..f5928f8e55 100644 --- a/frontend/src/test/scala/bloop/engine/FileWatchingSpec.scala +++ b/frontend/src/test/scala/bloop/engine/FileWatchingSpec.scala @@ -117,7 +117,9 @@ class FileWatchingSpec { def watchTest(): Unit = { val TestProjectName = "with-tests" val testProject = s"$TestProjectName-test" - val state = ProjectHelpers.loadTestProject(TestProjectName) + val state0 = ProjectHelpers.loadTestProject(TestProjectName) + val commonOptions = state0.commonOptions.copy(env = ProjectHelpers.runAndTestProperties) + val state = state0.copy(commonOptions = commonOptions) val workerAction: FileWatchingContext => Unit = { case (state, project, bloopOut) => @@ -126,8 +128,8 @@ class FileWatchingSpec { val loggerName = UUID.randomUUID().toString val newLogger = BloopLogger.at(loggerName, newOut, newOut) val newState = state.copy(logger = newLogger) - val commonOptions = cliOptions0.common.copy(out = newOut) - val cliOptions = cliOptions0.copy(common = commonOptions, verbose = true) + val commonOptions1 = commonOptions.copy(out = newOut) + val cliOptions = cliOptions0.copy(common = commonOptions1, verbose = true) val cmd = Commands.Test(project.name, watch = true, cliOptions = cliOptions) Interpreter.execute(Run(cmd), newState) () @@ -144,21 +146,25 @@ class FileWatchingSpec { // Wait for #1 compilation to finish readCompilingLines(1, "Compiling 1 Scala source to", bloopOut) - readCompilingLines(1, "Compiling 5 Scala sources to", bloopOut) + readCompilingLines(1, "Compiling 6 Scala sources to", bloopOut) readCompilingLines(1, "+ is very personal", bloopOut) readCompilingLines(1, "+ Greeting.is personal: OK", bloopOut) readCompilingLines(1, "- should be very personal", bloopOut) - Thread.sleep(1500) + readCompilingLines(1, "Total for specification Specs2Test", bloopOut) + readCompilingLines(2, "Terminating test server.", bloopOut) + readCompilingLines(1, "File watching 8 directories.", bloopOut) // Write the contents of a source back to the same source Files.write(newSource, "object ForceRecompilation {}".getBytes("UTF-8")) // Wait for #2 compilation to finish readCompilingLines(2, "Compiling 1 Scala source to", bloopOut) - readCompilingLines(1, "Compiling 5 Scala sources to", bloopOut) + readCompilingLines(1, "Compiling 6 Scala sources to", bloopOut) readCompilingLines(2, "+ is very personal", bloopOut) readCompilingLines(2, "+ Greeting.is personal: OK", bloopOut) readCompilingLines(2, "- should be very personal", bloopOut) + readCompilingLines(2, "Total for specification Specs2Test", bloopOut) + readCompilingLines(4, "Terminating test server.", bloopOut) // Finish source file watching workerThread.interrupt() diff --git a/frontend/src/test/scala/bloop/exec/ForkProcessSpec.scala b/frontend/src/test/scala/bloop/exec/ForkProcessSpec.scala index 990dc19663..04ddf03ae6 100644 --- a/frontend/src/test/scala/bloop/exec/ForkProcessSpec.scala +++ b/frontend/src/test/scala/bloop/exec/ForkProcessSpec.scala @@ -42,7 +42,8 @@ class ForkProcessSpec { val classpath = project.classpath val config = ForkProcess(env, classpath) val logger = new RecordingLogger - val exitCode = config.runMain(cwdPath, s"$packageName.$mainClassName", args, logger) + val userEnv = ProjectHelpers.runAndTestProperties + val exitCode = config.runMain(cwdPath, s"$packageName.$mainClassName", args, logger, userEnv) val messages = logger.getMessages op(exitCode, messages) } diff --git a/frontend/src/test/scala/bloop/nailgun/NailgunTest.scala b/frontend/src/test/scala/bloop/nailgun/NailgunTest.scala index 439d97dc74..19c5b5cfb6 100644 --- a/frontend/src/test/scala/bloop/nailgun/NailgunTest.scala +++ b/frontend/src/test/scala/bloop/nailgun/NailgunTest.scala @@ -109,8 +109,10 @@ abstract class NailgunTest { val cmdBase = if (BspServer.isWindows) "python" :: clientPath.toString :: Nil else clientPath.toString :: Nil - new ProcessBuilder((cmdBase ++ (s"--nailgun-port=$port" +: cmd)): _*) - .directory(base.toFile) + val builder = new ProcessBuilder((cmdBase ++ (s"--nailgun-port=$port" +: cmd)): _*) + val env = builder.environment() + env.put("BLOOP_OWNER", "owner") + builder.directory(base.toFile) } /** diff --git a/frontend/src/test/scala/bloop/tasks/ProjectHelpers.scala b/frontend/src/test/scala/bloop/tasks/ProjectHelpers.scala index 3421f3668b..058e06ef5c 100644 --- a/frontend/src/test/scala/bloop/tasks/ProjectHelpers.scala +++ b/frontend/src/test/scala/bloop/tasks/ProjectHelpers.scala @@ -86,6 +86,12 @@ object ProjectHelpers { State.forTests(build, CompilationHelpers.getCompilerCache(logger), logger) } + private[bloop] final val runAndTestProperties = { + val props = new java.util.Properties() + props.put("BLOOP_OWNER", "owner") + props + } + /** * Compile the given sources and then run `cmd`. Log messages are then given to `check`. * @@ -119,7 +125,8 @@ object ProjectHelpers { check: List[(String, String)] => Unit): Unit = { val recordingLogger = new RecordingLogger val recordingStream = ProcessLogger.toOutputStream(recordingLogger.info _) - val recordingState = state.copy(logger = recordingLogger) + val commonOptions = state.commonOptions.copy(env = runAndTestProperties) + val recordingState = state.copy(logger = recordingLogger).copy(commonOptions = commonOptions) val project = getProject(cmd.project, recordingState) val _ = Interpreter.execute(Run(cmd), recordingState) check(recordingLogger.getMessages) diff --git a/frontend/src/test/scala/bloop/tasks/TestLoggingSpec.scala b/frontend/src/test/scala/bloop/tasks/TestLoggingSpec.scala index ce4daceab4..fd294182d9 100644 --- a/frontend/src/test/scala/bloop/tasks/TestLoggingSpec.scala +++ b/frontend/src/test/scala/bloop/tasks/TestLoggingSpec.scala @@ -21,7 +21,9 @@ class TestLoggingSpec { val beforeCompilation = state.copy( build = build.copy(projects = build.projects.map(_.copy(javaEnv = inProcessEnv)))) val action = Run(Commands.Compile(moduleName)) - Interpreter.execute(action, beforeCompilation) + val state0 = Interpreter.execute(action, beforeCompilation) + val commonOptions = state.commonOptions.copy(env = ProjectHelpers.runAndTestProperties) + state.copy(commonOptions = commonOptions) } val testAction = Run(Commands.Test(moduleName)) diff --git a/frontend/src/test/scala/bloop/tasks/TestTaskTest.scala b/frontend/src/test/scala/bloop/tasks/TestTaskTest.scala index 68be4f58de..9ffddb7176 100644 --- a/frontend/src/test/scala/bloop/tasks/TestTaskTest.scala +++ b/frontend/src/test/scala/bloop/tasks/TestTaskTest.scala @@ -88,7 +88,8 @@ class TestTaskTest(framework: String) { Seq(framework -> filteredDefs) }.toMap val discoveredTests = DiscoveredTests(classLoader, tests) - TestInternals.executeTasks(cwd, config, discoveredTests, Tasks.eventHandler, logger) + val env = ProjectHelpers.runAndTestProperties + TestInternals.executeTasks(cwd, config, discoveredTests, Tasks.eventHandler, logger, env) } } } diff --git a/project/BuildPlugin.scala b/project/BuildPlugin.scala index c37896ce40..3fbb53b897 100644 --- a/project/BuildPlugin.scala +++ b/project/BuildPlugin.scala @@ -196,7 +196,7 @@ object BuildImplementation { import ch.epfl.scala.sbt.release.ReleaseEarlyPlugin.{autoImport => ReleaseEarlyKeys} final val globalSettings: Seq[Def.Setting[_]] = Seq( - BuildKeys.schemaVersion := "1.3-refresh2", + BuildKeys.schemaVersion := "1.3-refresh4", Keys.testOptions in Test += sbt.Tests.Argument("-oD"), Keys.onLoadMessage := Header.intro, Keys.publishArtifact in Test := false,